From 882badfd9caa9d1c6d98012419e210a35b5e2940 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 15 Apr 2026 14:42:27 -0400 Subject: [PATCH 1/4] feat(checkbox)!: expose eventDetails in onCheckedChange, remove deprecated props BREAKING CHANGE: onCheckedChange signature now includes eventDetails as second argument. Removed deprecated props: onChange, onValueChange, onClick --- .../checkbox-breaking-onCheckedChange.md | 37 +++++++++++++ .../kumo/src/components/checkbox/checkbox.tsx | 55 ++++--------------- 2 files changed, 49 insertions(+), 43 deletions(-) create mode 100644 .changeset/checkbox-breaking-onCheckedChange.md 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/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 (