chore(Icon): migrate to CSS Modules with visual regression baseline#1050
chore(Icon): migrate to CSS Modules with visual regression baseline#1050DreaminDani wants to merge 5 commits into
Conversation
🦋 Changeset detectedLatest commit: 714f8dd The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Address Copilot's standing review feedback on the migration PRs: tests/utils/ is reserved for shared helpers like getStoryUrl; component specs belong in component-family folders. Migration skill updated in PR #1049 to codify this for future migrations. Visual regression remains byte-for-byte: 37/37 snapshots match without regeneration.
There was a problem hiding this comment.
Pull request overview
Migrates the shared Icon component styling from styled-components to CSS Modules while adding Playwright visual regression coverage to lock in rendering parity across themes, sizes, states, overrides, and asset-backed variants (flags/logos/payments).
Changes:
- Added Storybook harness stories + Playwright screenshot spec and baselines for icon variants in light/dark.
- Replaced
styled-componentswrappers with CSS Modules +cva/cnforIconandSvgImageElement. - Updated four asset call sites to use
sizeinstead of the transient styled-components prop$size.
Reviewed changes
Copilot reviewed 11 out of 47 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/utils/icon.spec.ts | Adds Playwright visual regression coverage for Icon variants (light/dark). |
| src/components/Icon/SvgImageElement.tsx | Converts SvgImageElement from styled svg to a normal component using CSS Modules + cva. |
| src/components/Icon/Icon.tsx | Replaces SvgWrapper styled-component with CSS Module classes and CSS custom props for overrides. |
| src/components/Icon/Icon.stories.tsx | Adds harness stories used by visual tests; replaces responsive grid styled-component with CSS module class. |
| src/components/Icon/Icon.stories.module.css | Implements responsive grid styling previously in styled-components. |
| src/components/Icon/Icon.module.css | New CSS Module for wrapper sizing/state styling and asset svg sizing. |
| src/components/Assets/Payments/system/Payment.tsx | Renames $size prop usage to size for SvgImageElement. |
| src/components/Assets/Logos/system/Logo.tsx | Renames $size prop usage to size for SvgImageElement. |
| src/components/Assets/Icons/system/Icon.tsx | Renames $size prop usage to size for SvgImageElement. |
| src/components/Assets/Flags/system/Flag.tsx | Renames $size prop usage to size for SvgImageElement. |
| .changeset/migrate-icon-to-css-modules.md | Publishes the migration as a patch changeset. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… prettier Address Copilot review feedback on PR #1050: type ResponsiveGridContainer as `ComponentProps<typeof GridContainer>` and merge className via `cn` so callers can extend the wrapper without losing the responsive-grid class. Also runs prettier on the file to clear pre-existing code-quality CI failures from the migration commit (one-line JSX expressions that prettier wants on multiple lines). Auto-format, no behavior change. The unaddressed Copilot point — `circle[fill]` appearing in both the stroke and fill rules of Icon.module.css — is a pre-existing bug that the migration deliberately preserved per the byte-for-byte parity rule (`circle[stroke]` was missing from the original styled-components source too). Fix in a separate PR after this lands.
The IconHarness wrapper applied a hardcoded grey #888 background that made sense for Spacer/Separator (where you need contrast against an otherwise invisible primitive) but hurt Icon's stories — it muddied the state pills (success/warning/danger/info badges) and washed out glyphs whose visibility doesn't need any artificial contrast against Storybook's page background. Drop the backdrop and the 24px padding; keep the wrapper as a testid-only inline-flex container so the Playwright locator still attaches. All 36 Icon snapshots regenerated. No component code changes; stories/spec layout only.
Storybook Preview Deployed✅ Preview URL: https://click-g4twkisn6-clickhouse.vercel.app Built from commit: |
Commits
test(Icon): add visual regression baseline before CSS Modules migration— adds stories, Playwright spec, and fresh PNG snapshots that capture the current rendering across size, state, custom-color/width-height, and asset (flag/logo/payment) variants in light and dark themes.chore(Icon): migrate styling from styled-components to CSS Modules— replaces all three styled-components (the SvgWrapper inIcon.tsx, the polymorphicSvgImageElement.svg, and the galleryResponsiveGridContainerin stories) with.module.css+cva/cn. DOM-identical, byte-for-byte visual parity verified.Notes
SvgImageElementforces a tiny consumer prop rename:$size->sizeinFlag.tsx,Logo.tsx,Payment.tsx, andAssets/Icons/system/Icon.tsx. These four call-site updates are an unavoidable consequence of dropping the transient styled-components prop and are bundled into the migration commit so main stays green.width: ${$width}interpolation emitted invalid CSS (e.g.width: 40without unit), causing the SVG to fall back to its intrinsicwidth/heightattributes. That quirk is preserved by stringifying the value into a CSS custom property used insidevar(), which keeps the unit-less value invalid and triggers the same fallback path.Icon.stories.module.cssfor the responsive grid that previously lived instyled(GridContainer).Verification
yarn test:visual tests/utils/icon.spec.tspasses with zero snapshot regenerations (37 tests, 36 PNGs)yarn lint:css,yarn lint:code,yarn buildall greengrep -r 'styled-components' src/components/Icon/emptyCloses CUI-36
Note
Medium Risk
Icon is a core design-system surface; the migration is broad but guarded by extensive visual regression tests and a minimal public API tweak ($size→size on internal SvgImageElement consumers).
Overview
Icon styling moves off styled-components onto CSS Modules plus
cva/cn, with a patch changeset and the stated goal of unchanged visuals.The glyph wrapper (
SvgWrapperinIcon.tsx) and sharedSvgImageElementare reimplemented withIcon.module.css(sizes, semantic states, stroke/fill, custom color via--svg-icon-color, width/height via CSS variables).SvgImageElementis no longer a styledsvg; it is a small polymorphic component with a publicsizeprop, so Flag, Logo, Payment, and Assets/Icons call sites switch from transient$sizetosize.Storybook adds an
IconHarness(data-testid="icon-harness") and many focused stories (sizes, states, custom color/dimensions, flag/logo/payment assets); the icon gallery’s responsive grid moves fromstyled(GridContainer)toIcon.stories.module.css. A largetests/display/icon.spec.tsPlaywright suite snapshots those stories in light and dark (plus one a11y check onaria-label).Reviewed by Cursor Bugbot for commit 714f8dd. Bugbot is set up for automated code reviews on this repo. Configure here.