chore(EmptyButton): migrate to CSS Modules with visual regression baseline#1105
chore(EmptyButton): migrate to CSS Modules with visual regression baseline#1105DreaminDani wants to merge 4 commits into
Conversation
…migration Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ules Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 90092ad 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 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 443f9a6. Configure here.
There was a problem hiding this comment.
Pull request overview
This PR migrates EmptyButton from styled-components to CSS Modules while adding Storybook + Playwright visual-regression coverage to lock in rendering parity across the migration. It follows the same “zero-specificity base rule” approach previously used for Container to keep downstream styled(EmptyButton) overrides working without && specificity hacks.
Changes:
- Added Storybook stories and a new Playwright visual-regression spec for
EmptyButton(including light/dark and interactive states). - Replaced
styled.buttonimplementation with aforwardRef<button>component wired toEmptyButton.module.css. - Added a changeset entry for a patch release documenting the migration.
Reviewed changes
Copilot reviewed 5 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tests/buttons/emptybutton.spec.ts |
Adds visual regression coverage and basic interaction checks for EmptyButton. |
src/components/EmptyButton/EmptyButton.tsx |
Replaces styled-components implementation with a forwardRef button using CSS Modules + cn. |
src/components/EmptyButton/EmptyButton.stories.tsx |
Adds Storybook stories and a harness decorator for consistent snapshots. |
src/components/EmptyButton/EmptyButton.module.css |
Introduces CSS Module styles using :where() to keep the base selector at zero specificity. |
.changeset/migrate-emptybutton-to-css-modules.md |
Records the migration as a patch-level change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… win The .empty-button:disabled rule sat at specificity (0,2,0), tying with a downstream styled(EmptyButton) &:disabled (also 0,2,0) and winning by CSS Module source order — the exact failure :where() prevents on the base rule. Scope it as :where(.empty-button):disabled (0,1,0) so consumer :disabled overrides win naturally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Popover's close button (CloseButton as={RadixPopover.Close}) and CodeBlock's
copy/wrap buttons (CodeButton as={IconButton}) render styled(EmptyButton) via
the styled-components `as` prop, which bypasses EmptyButton's own render — so
EmptyButton's module class never reaches the DOM. There were no visual specs
covering these paths, so the migration was unverified there.
Add open-by-default Popover and a CodeBlock-with-wrap-button story plus
Playwright specs (light + dark, copied state). Baselines were generated against
the pre-migration styled.button EmptyButton and pass byte-for-byte against the
migrated CSS Modules version: the lost base reset is inconsequential because
each `as` target supplies its own styling (IconButton's own resets; the cross
Icon is an svg that never inherited button resets). These specs lock that in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Storybook Preview Deployed✅ Preview URL: https://click-owx63ansm-clickhouse.vercel.app Built from commit: |

Commits
test(EmptyButton): add visual regression baseline before CSS Modules migration— adds a story, a Playwright spec, and fresh PNG snapshots capturing the current styled-components rendering.chore(EmptyButton): migrate styling from styled-components to CSS Modules— replacesstyled.buttonwith aforwardRef<button>component +.module.css. DOM-identical, byte-for-byte visual parity verified.Cluster-aware approach
EmptyButtonis extended viastyled(EmptyButton)byCrossButton, the Popover close button, and the CodeBlock copy button, and is rendered directly byCollapsible. Following theContainerprecedent (#1101), the base rule is scoped with:where(.empty-button)so it sits at zero specificity — equal-specificity consumer overrides (background/padding/color) win by source order without an&&hack. Those consumers keep their existingstyled(EmptyButton)/<EmptyButton>usage unchanged and will migrate under their own follow-up tickets.Verification
yarn test:visual tests/buttons/emptybutton.spec.ts— passes with zero snapshot regenerationstests/display/dialog.spec.ts(renders CrossButton),tests/display/sidebar-collapsible-item.spec.tsandtests/display/sidebar-collapsible-title.spec.ts(render<EmptyButton>via Collapsible)$copied/$errortransient props do not leak to the DOM and produce no React "unknown DOM attribute" warningsyarn lint:css,yarn lint:code,yarn format,yarn buildall greensrc/components/EmptyButton/