-
Notifications
You must be signed in to change notification settings - Fork 21
chore(Checkbox): migrate to CSS Modules with visual regression baseline #1052
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e2161f6
e56aa7c
579a817
3a9fd2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@clickhouse/click-ui': patch | ||
| --- | ||
|
|
||
| Migrate Checkbox from styled-components to css modules with no change in behavior |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /* The wrapper class is applied alongside FormRoot's styled-components class. | ||
| The .wrapper.wrapper double-class boost matches the specificity behavior of | ||
| the original `styled(FormRoot)` chain so these overrides reliably beat | ||
| FormRoot's `align-items: flex-start` regardless of stylesheet insertion | ||
| order between CSS Modules and the runtime-injected styled-components. */ | ||
| .wrapper.wrapper { | ||
| /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- `fit-content` | ||
| keyword on `max-width` is widely supported on evergreen browsers; the original | ||
| styled-components rule used the same value. */ | ||
| max-width: fit-content; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .checkinput { | ||
| display: flex; | ||
| width: var(--click-checkbox-size-all); | ||
| height: var(--click-checkbox-size-all); | ||
| flex-shrink: 0; | ||
| justify-content: center; | ||
| align-items: center; | ||
| border: 1px solid var(--checkbox-stroke-default); | ||
| border-radius: var(--click-checkbox-radii-all); | ||
| background: var(--checkbox-bg-default); | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .checkinput_variant_default { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-default-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-default-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-default-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-default-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-default-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var1 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var1-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var1-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var1-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var1-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var1-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var2 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var2-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var2-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var2-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var2-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var2-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var3 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var3-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var3-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var3-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var3-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var3-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var4 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var4-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var4-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var4-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var4-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var4-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var5 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var5-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var5-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var5-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var5-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var5-stroke-active); | ||
| } | ||
|
|
||
| .checkinput_variant_var6 { | ||
| --checkbox-bg-default: var(--click-checkbox-color-variations-var6-background-default); | ||
| --checkbox-bg-hover: var(--click-checkbox-color-variations-var6-background-hover); | ||
| --checkbox-bg-active: var(--click-checkbox-color-variations-var6-background-active); | ||
| --checkbox-stroke-default: var(--click-checkbox-color-variations-var6-stroke-default); | ||
| --checkbox-stroke-active: var(--click-checkbox-color-variations-var6-stroke-active); | ||
| } | ||
|
|
||
| .checkinput:hover { | ||
| background: var(--checkbox-bg-hover); | ||
| } | ||
|
|
||
| .checkinput[data-state='checked'], | ||
| .checkinput[data-state='indeterminate'] { | ||
| border-color: var(--checkbox-stroke-active); | ||
| background: var(--checkbox-bg-active); | ||
| } | ||
|
|
||
| /* stylelint-disable no-descending-specificity -- disabled state intentionally | ||
| defined after checked/indeterminate to mirror the source cascade order; | ||
| matches the styled-components rule which used a nested `&[data-disabled]` | ||
| block placed after the state selectors. */ | ||
| .checkinput[data-disabled] { | ||
| border-color: var(--click-checkbox-color-stroke-disabled); | ||
| background: var(--click-checkbox-color-background-disabled); | ||
| cursor: not-allowed; | ||
| } | ||
|
|
||
| .checkinput[data-disabled][data-state='checked'], | ||
| .checkinput[data-disabled][data-state='indeterminate'] { | ||
| border-color: var(--click-checkbox-color-stroke-disabled); | ||
| background: var(--click-checkbox-color-background-disabled); | ||
| } | ||
| /* stylelint-enable no-descending-specificity */ | ||
|
|
||
| .checkicon { | ||
| color: var(--click-checkbox-color-check-active); | ||
| } | ||
|
|
||
| .checkicon[data-disabled] { | ||
| color: var(--click-checkbox-color-check-disabled); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,27 @@ import { Icon } from '@/components/Icon'; | |
|
|
||
| import * as RadixCheckbox from '@radix-ui/react-checkbox'; | ||
| import { useId } from 'react'; | ||
| import { styled } from 'styled-components'; | ||
| import { FormRoot } from '@/components/FormContainer'; | ||
| import { CheckboxProps, CheckboxVariants } from './Checkbox.types'; | ||
| import { cn, cva } from '@/lib/cva'; | ||
| import styles from './Checkbox.module.css'; | ||
| import { CheckboxProps } from './Checkbox.types'; | ||
|
|
||
| const Wrapper = styled(FormRoot)` | ||
| align-items: center; | ||
| max-width: fit-content; | ||
| `; | ||
| const checkInputVariants = cva(styles.checkinput, { | ||
| variants: { | ||
| variant: { | ||
| default: styles['checkinput_variant_default'], | ||
| var1: styles['checkinput_variant_var1'], | ||
| var2: styles['checkinput_variant_var2'], | ||
| var3: styles['checkinput_variant_var3'], | ||
| var4: styles['checkinput_variant_var4'], | ||
| var5: styles['checkinput_variant_var5'], | ||
| var6: styles['checkinput_variant_var6'], | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| variant: 'default', | ||
| }, | ||
| }); | ||
|
|
||
| export const Checkbox = ({ | ||
| id, | ||
|
|
@@ -20,30 +33,32 @@ export const Checkbox = ({ | |
| orientation = 'horizontal', | ||
| dir = 'end', | ||
| checked, | ||
| className, | ||
| ...delegated | ||
| }: CheckboxProps) => { | ||
| const defaultId = useId(); | ||
| return ( | ||
| <Wrapper | ||
| <FormRoot | ||
| $orientation={orientation} | ||
| $dir={dir} | ||
| className={styles.wrapper} | ||
| > | ||
| <CheckInput | ||
| <RadixCheckbox.Root | ||
| id={id ?? defaultId} | ||
| data-testid="checkbox" | ||
| variant={variant} | ||
| disabled={disabled} | ||
| aria-label={`${label}`} | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserving this exactly as-is in the migration: the original styled-components Checkbox had the same |
||
| checked={checked} | ||
| {...delegated} | ||
| className={cn(checkInputVariants({ variant }), className)} | ||
| > | ||
| <CheckIconWrapper> | ||
| <RadixCheckbox.Indicator className={styles.checkicon}> | ||
| <Icon | ||
| name={checked === 'indeterminate' ? 'minus' : 'check'} | ||
| size="sm" | ||
| /> | ||
| </CheckIconWrapper> | ||
| </CheckInput> | ||
| </RadixCheckbox.Indicator> | ||
| </RadixCheckbox.Root> | ||
| {label && ( | ||
| <GenericLabel | ||
| htmlFor={id ?? defaultId} | ||
|
|
@@ -52,52 +67,6 @@ export const Checkbox = ({ | |
| {label} | ||
| </GenericLabel> | ||
| )} | ||
| </Wrapper> | ||
| </FormRoot> | ||
| ); | ||
| }; | ||
|
|
||
| const CheckInput = styled(RadixCheckbox.Root)<{ | ||
| variant: CheckboxVariants; | ||
| }>` | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| flex-shrink: 0; | ||
|
|
||
| ${({ theme, variant }) => ` | ||
| border-radius: ${theme.click.checkbox.radii.all}; | ||
| width: ${theme.click.checkbox.size.all}; | ||
| height: ${theme.click.checkbox.size.all}; | ||
| background: ${theme.click.checkbox.color.variations[variant].background.default}; | ||
| border: 1px solid ${theme.click.checkbox.color.variations[variant].stroke.default}; | ||
| cursor: pointer; | ||
|
|
||
| &:hover { | ||
| background: ${theme.click.checkbox.color.variations[variant].background.hover}; | ||
| } | ||
| &[data-state="checked"], | ||
| &[data-state="indeterminate"] { | ||
| border-color: ${theme.click.checkbox.color.variations[variant].stroke.active}; | ||
| background: ${theme.click.checkbox.color.variations[variant].background.active}; | ||
| } | ||
| &[data-disabled] { | ||
| background: ${theme.click.checkbox.color.background.disabled}; | ||
| border-color: ${theme.click.checkbox.color.stroke.disabled}; | ||
| cursor: not-allowed; | ||
| &[data-state="checked"], | ||
| &[data-state="indeterminate"] { | ||
| background: ${theme.click.checkbox.color.background.disabled}; | ||
| border-color: ${theme.click.checkbox.color.stroke.disabled}; | ||
| } | ||
| } | ||
| `}; | ||
| `; | ||
|
|
||
| const CheckIconWrapper = styled(RadixCheckbox.Indicator)` | ||
| ${({ theme }) => ` | ||
| color: ${theme.click.checkbox.color.check.active}; | ||
| &[data-disabled] { | ||
| color: ${theme.click.checkbox.color.check.disabled}; | ||
| } | ||
| `} | ||
| `; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
NoLabelstory is intentionally exercising the current (broken) rendering so the visual-regression snapshot captures it. The underlyingaria-label="undefined"bug is a pre-existing styled-components behavior the migration preserves; fixing the story without first fixing the component would change what the snapshot tests. The fix will land separately and the story can be updated alongside it.