diff --git a/.changeset/checkbox-breaking-onCheckedChange.md b/.changeset/checkbox-breaking-onCheckedChange.md new file mode 100644 index 0000000000..d20640d732 --- /dev/null +++ b/.changeset/checkbox-breaking-onCheckedChange.md @@ -0,0 +1,37 @@ +--- +"@cloudflare/kumo": major +--- + +**BREAKING:** Checkbox `onCheckedChange` now receives event details as second argument + +The `onCheckedChange` callback signature now matches Base UI, providing access to the underlying event: + +```tsx +// Before +onCheckedChange={(checked) => console.log(checked)} + +// After (event details available as optional second arg) +onCheckedChange={(checked, eventDetails) => { + console.log(checked); + console.log(eventDetails.event); // native event +}} +``` + +**Removed deprecated props:** + +- `onChange` - use `onCheckedChange` instead +- `onValueChange` on individual checkboxes - use `onCheckedChange` instead +- `onClick` - was redundant, use standard React event handling via spread props + +**Migration:** + +```tsx +// Before (deprecated) + console.log(e.target.checked)} /> + setChecked(checked)} /> + +// After + setChecked(checked)} /> +``` + +Note: `Checkbox.Group`'s `onValueChange` prop is unchanged - it still accepts `(values: string[]) => void`. diff --git a/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx b/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx index 6c89f56369..8d0fcc4215 100644 --- a/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx +++ b/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx @@ -296,7 +296,7 @@ export function HomeGrid() { { + onCheckedChange={(checked) => { setChecked(checked); }} /> diff --git a/packages/kumo/scripts/component-registry/metadata.ts b/packages/kumo/scripts/component-registry/metadata.ts index 71d39f43ef..73852008f9 100644 --- a/packages/kumo/scripts/component-registry/metadata.ts +++ b/packages/kumo/scripts/component-registry/metadata.ts @@ -254,12 +254,6 @@ export const ADDITIONAL_COMPONENT_PROPS: Record< description: "Callback when collapsed state changes", }, }, - Checkbox: { - onValueChange: { - type: "(checked: boolean) => void", - description: "Callback when checkbox value changes", - }, - }, }; // ============================================================================= @@ -318,7 +312,12 @@ export const COMPONENT_STYLING_METADATA: Record = { ], }, ClipboardText: { - baseTokens: ["bg-kumo-base", "text-kumo-default", "ring-kumo-line", "border-kumo-fill"], + baseTokens: [ + "bg-kumo-base", + "text-kumo-default", + "ring-kumo-line", + "border-kumo-fill", + ], states: { input: ["bg-kumo-control", "text-kumo-default", "ring-kumo-line"], text: ["bg-kumo-base", "font-mono"], @@ -403,7 +402,12 @@ export const COMPONENT_STYLING_METADATA: Record = { }, }, Input: { - baseTokens: ["bg-kumo-control", "text-kumo-default", "text-kumo-subtle", "ring-kumo-line"], + baseTokens: [ + "bg-kumo-control", + "text-kumo-default", + "text-kumo-subtle", + "ring-kumo-line", + ], sizeVariants: { xs: { height: 20, @@ -473,7 +477,12 @@ export const COMPONENT_STYLING_METADATA: Record = { }, }, Dialog: { - baseTokens: ["bg-kumo-base", "text-kumo-default", "border-kumo-line", "shadow-m"], + baseTokens: [ + "bg-kumo-base", + "text-kumo-default", + "border-kumo-line", + "shadow-m", + ], sizeVariants: { sm: { height: 0, // Dialog height is auto (content-driven) diff --git a/packages/kumo/src/components/checkbox/checkbox.tsx b/packages/kumo/src/components/checkbox/checkbox.tsx index 27838c08c7..e9dc82fb01 100644 --- a/packages/kumo/src/components/checkbox/checkbox.tsx +++ b/packages/kumo/src/components/checkbox/checkbox.tsx @@ -5,10 +5,12 @@ import { Label } from "../label"; import { Fieldset } from "@base-ui/react/fieldset"; import { Field as FieldBase } from "@base-ui/react/field"; import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui/react/checkbox-group"; -import { - Checkbox as BaseCheckbox, - type CheckboxRootChangeEventDetails, -} from "@base-ui/react/checkbox"; +import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox"; + +/** Event details passed to onCheckedChange callback. Re-exported from Base UI. */ +export type CheckboxChangeEventDetails = Parameters< + NonNullable +>[1]; /** Checkbox variant definitions mapping variant names to their Tailwind classes. */ export const KUMO_CHECKBOX_VARIANTS = { @@ -116,13 +118,7 @@ export type CheckboxProps = { /** Whether the checkbox is disabled */ disabled?: boolean; /** Callback when the checked state changes */ - onCheckedChange?: (checked: boolean) => void; - /** @deprecated Use onCheckedChange instead */ - onValueChange?: (checked: boolean) => void; - /** @deprecated Use onCheckedChange instead */ - onChange?: (event: React.ChangeEvent) => void; - /** Click handler */ - onClick?: (event: React.MouseEvent) => void; + onCheckedChange?: BaseCheckbox.Root.Props["onCheckedChange"]; /** Name for form submission */ name?: string; /** Whether the field is required */ @@ -191,9 +187,7 @@ export type CheckboxItemProps = { indeterminate?: boolean; disabled?: boolean; /** Callback when the checked state changes */ - onCheckedChange?: (checked: boolean) => void; - /** @deprecated Use onCheckedChange instead */ - onValueChange?: (checked: boolean) => void; + onCheckedChange?: BaseCheckbox.Root.Props["onCheckedChange"]; name?: string; }; @@ -210,8 +204,6 @@ const CheckboxBase = forwardRef( labelTooltip, controlFirst = true, onCheckedChange, - onValueChange, - onChange, required, name, ...props @@ -235,23 +227,6 @@ const CheckboxBase = forwardRef( } } - // Handle onCheckedChange (preferred) and deprecated onValueChange/onChange - const handleCheckedChange = ( - newChecked: boolean, - eventDetails: CheckboxRootChangeEventDetails, - ) => { - onCheckedChange?.(newChecked); - onValueChange?.(newChecked); - if (onChange) { - // Backwards compatibility: extend native event with target.checked - // so existing code using `e.target.checked` continues to work - const event = Object.assign(eventDetails.event, { - target: { checked: newChecked }, - }); - onChange(event as never); - } - }; - const checkboxControl = ( ( checked={checked} indeterminate={indeterminate} disabled={disabled} - onCheckedChange={handleCheckedChange} + onCheckedChange={onCheckedChange} className={cn( "relative flex h-4 w-4 items-center justify-center rounded-sm border-0 bg-kumo-base ring after:absolute after:-inset-x-3 after:-inset-y-2", variant === "error" ? "ring-kumo-danger" : "ring-kumo-hairline", - !disabled && "hover:ring-kumo-hairline focus-visible:ring-kumo-hairline", + !disabled && + "hover:ring-kumo-hairline focus-visible:ring-kumo-hairline", "data-[checked]:bg-kumo-contrast data-[checked]:ring-kumo-contrast data-[indeterminate]:bg-kumo-contrast data-[indeterminate]:ring-kumo-contrast", disabled && "cursor-not-allowed opacity-50", className, @@ -330,19 +306,12 @@ const CheckboxItem = forwardRef( label, value, onCheckedChange, - onValueChange, name, }, ref, ) => { const { controlFirst } = useContext(CheckboxGroupContext); - // Handle onCheckedChange (preferred) and deprecated onValueChange - const handleCheckedChange = (newChecked: boolean) => { - onCheckedChange?.(newChecked); - onValueChange?.(newChecked); - }; - return (