@boxcustodia/library 2.0.0-alpha.12 → 2.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1087 -720
  3. package/dist/index.es.js +7011 -56097
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Examples.tsx +1 -1
  7. package/src/__doc__/Intro.mdx +3 -3
  8. package/src/__doc__/Tabs.mdx +112 -0
  9. package/src/__doc__/V2.mdx +1246 -0
  10. package/src/components/accordion/accordion.stories.tsx +143 -0
  11. package/src/components/accordion/accordion.tsx +135 -0
  12. package/src/components/accordion/index.ts +1 -0
  13. package/src/components/alert/alert.stories.tsx +24 -4
  14. package/src/components/alert/alert.tsx +17 -9
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  17. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  18. package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
  19. package/src/components/auto-complete/auto-complete.tsx +420 -68
  20. package/src/components/auto-complete/index.ts +0 -1
  21. package/src/components/avatar/avatar.stories.tsx +162 -21
  22. package/src/components/avatar/avatar.tsx +79 -20
  23. package/src/components/button/button.stories.tsx +219 -294
  24. package/src/components/button/button.test.tsx +10 -17
  25. package/src/components/button/button.tsx +78 -19
  26. package/src/components/button/components/base-button.tsx +30 -53
  27. package/src/components/button/index.ts +0 -1
  28. package/src/components/calendar/calendar.stories.tsx +1 -1
  29. package/src/components/calendar/calendar.tsx +4 -4
  30. package/src/components/card/card.stories.tsx +141 -69
  31. package/src/components/card/card.tsx +155 -54
  32. package/src/components/center/center.stories.tsx +22 -39
  33. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  34. package/src/components/checkbox/checkbox.tsx +76 -15
  35. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  36. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  37. package/src/components/combobox/combobox.stories.tsx +33 -23
  38. package/src/components/combobox/combobox.tsx +99 -77
  39. package/src/components/date-picker/date-input.stories.tsx +14 -6
  40. package/src/components/date-picker/date-input.tsx +2 -2
  41. package/src/components/date-picker/date-picker.model.ts +13 -4
  42. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  43. package/src/components/date-picker/date-picker.tsx +28 -14
  44. package/src/components/dialog/dialog.stories.tsx +18 -0
  45. package/src/components/dialog/dialog.test.tsx +1 -1
  46. package/src/components/dialog/dialog.tsx +51 -20
  47. package/src/components/divider/divider.stories.tsx +126 -51
  48. package/src/components/divider/divider.tsx +16 -16
  49. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  50. package/src/components/dropzone/dropzone.tsx +383 -105
  51. package/src/components/dropzone/index.ts +0 -1
  52. package/src/components/empty/empty.stories.tsx +165 -0
  53. package/src/components/empty/empty.tsx +156 -0
  54. package/src/components/empty/index.ts +1 -0
  55. package/src/components/field/field.stories.tsx +227 -4
  56. package/src/components/field/field.tsx +77 -42
  57. package/src/components/form/form.stories.tsx +320 -197
  58. package/src/components/form/form.tsx +3 -23
  59. package/src/components/index.ts +2 -6
  60. package/src/components/input/input.stories.tsx +5 -5
  61. package/src/components/input/input.tsx +4 -4
  62. package/src/components/kbd/kbd.stories.tsx +1 -0
  63. package/src/components/label/label.stories.tsx +16 -0
  64. package/src/components/label/label.tsx +13 -2
  65. package/src/components/loader/loader.stories.tsx +7 -5
  66. package/src/components/loader/loader.tsx +8 -3
  67. package/src/components/menu/menu-primitives.tsx +207 -196
  68. package/src/components/menu/menu.stories.tsx +276 -146
  69. package/src/components/menu/menu.tsx +146 -54
  70. package/src/components/number-input/number-input.stories.tsx +27 -4
  71. package/src/components/number-input/number-input.test.tsx +2 -2
  72. package/src/components/number-input/number-input.tsx +31 -33
  73. package/src/components/otp/index.ts +1 -0
  74. package/src/components/otp/otp.stories.tsx +209 -0
  75. package/src/components/otp/otp.tsx +100 -0
  76. package/src/components/pagination/index.ts +1 -0
  77. package/src/components/pagination/pagination.model.ts +2 -0
  78. package/src/components/pagination/pagination.stories.tsx +154 -59
  79. package/src/components/pagination/pagination.test.tsx +122 -57
  80. package/src/components/pagination/pagination.tsx +575 -77
  81. package/src/components/password/password.stories.tsx +18 -3
  82. package/src/components/password/password.tsx +29 -9
  83. package/src/components/popover/popover.stories.tsx +26 -5
  84. package/src/components/popover/popover.tsx +15 -23
  85. package/src/components/progress/progress.stories.tsx +1 -0
  86. package/src/components/radio-group/index.ts +1 -0
  87. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  88. package/src/components/radio-group/radio-group.tsx +212 -0
  89. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  90. package/src/components/select/select.stories.tsx +118 -19
  91. package/src/components/select/select.tsx +67 -62
  92. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  93. package/src/components/stack/stack.stories.tsx +179 -89
  94. package/src/components/stack/stack.tsx +2 -2
  95. package/src/components/stepper/index.ts +1 -1
  96. package/src/components/stepper/stepper.stories.tsx +767 -83
  97. package/src/components/stepper/stepper.test.tsx +18 -18
  98. package/src/components/stepper/stepper.tsx +554 -0
  99. package/src/components/switch/switch.stories.tsx +15 -1
  100. package/src/components/switch/switch.tsx +17 -4
  101. package/src/components/table/index.ts +0 -2
  102. package/src/components/table/table.stories.tsx +131 -18
  103. package/src/components/table/table.test.tsx +1 -1
  104. package/src/components/table/table.tsx +183 -77
  105. package/src/components/tabs/tabs.stories.tsx +373 -155
  106. package/src/components/tabs/tabs.test.tsx +12 -12
  107. package/src/components/tabs/tabs.tsx +72 -149
  108. package/src/components/tag/index.ts +0 -1
  109. package/src/components/tag/tag.stories.tsx +155 -120
  110. package/src/components/tag/tag.tsx +47 -95
  111. package/src/components/textarea/textarea.stories.tsx +8 -22
  112. package/src/components/textarea/textarea.tsx +17 -79
  113. package/src/components/timeline/timeline.stories.tsx +323 -42
  114. package/src/components/timeline/timeline.tsx +359 -132
  115. package/src/components/toast/toast.stories.tsx +1 -0
  116. package/src/components/tooltip/tooltip.tsx +11 -9
  117. package/src/components/tree/index.ts +0 -1
  118. package/src/components/tree/tree.stories.tsx +365 -408
  119. package/src/components/tree/tree.test.tsx +163 -0
  120. package/src/components/tree/tree.tsx +212 -36
  121. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  122. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  123. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  124. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  125. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  126. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  127. package/src/hooks/usePagination/usePagination.tsx +36 -24
  128. package/src/styles/theme.css +1 -1
  129. package/src/utils/form.tsx +67 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -475
  132. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  133. package/src/components/background-image/background-image.stories.tsx +0 -21
  134. package/src/components/background-image/background-image.test.tsx +0 -29
  135. package/src/components/background-image/background-image.tsx +0 -23
  136. package/src/components/background-image/index.ts +0 -1
  137. package/src/components/button/button.variants.ts +0 -44
  138. package/src/components/button/components/loader-overlay.tsx +0 -21
  139. package/src/components/button/components/loading-icon.tsx +0 -47
  140. package/src/components/dropzone/upload-primitives.tsx +0 -310
  141. package/src/components/dropzone/use-dropzone.ts +0 -122
  142. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  143. package/src/components/empty-state/empty-state.tsx +0 -39
  144. package/src/components/empty-state/index.ts +0 -1
  145. package/src/components/heading/heading.stories.tsx +0 -74
  146. package/src/components/heading/heading.tsx +0 -28
  147. package/src/components/heading/heading.variants.ts +0 -27
  148. package/src/components/heading/index.ts +0 -1
  149. package/src/components/kbd/kbd.variants.ts +0 -26
  150. package/src/components/menu/util/render-menu-item.tsx +0 -54
  151. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  152. package/src/components/multi-select/index.ts +0 -1
  153. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  154. package/src/components/multi-select/multi-select.tsx +0 -300
  155. package/src/components/multi-select/multi-select.variants.ts +0 -22
  156. package/src/components/pagination/components/pagination-option.tsx +0 -27
  157. package/src/components/show/index.ts +0 -1
  158. package/src/components/show/show.stories.tsx +0 -197
  159. package/src/components/show/show.test.tsx +0 -41
  160. package/src/components/show/show.tsx +0 -16
  161. package/src/components/stepper/Stepper.tsx +0 -190
  162. package/src/components/stepper/context/stepper-context.tsx +0 -11
  163. package/src/components/table/table-primitives.tsx +0 -122
  164. package/src/components/table/table.model.ts +0 -20
  165. package/src/components/table-pagination/index.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  167. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  168. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  169. package/src/components/table-pagination/table-pagination.tsx +0 -108
  170. package/src/components/tabs/context/tabs-context.tsx +0 -14
  171. package/src/components/tag/tag.variants.ts +0 -31
  172. package/src/components/timeline/timeline-status.ts +0 -5
  173. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  174. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,22 +1,51 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { useState } from "react";
3
+ import { Button } from "../button/button";
3
4
  import { Checkbox } from "../checkbox";
4
- import { CheckboxGroup } from "./checkbox-group";
5
+ import { Field } from "../field/field";
6
+ import { Form } from "../form/form";
7
+ import { CheckboxGroup, CheckboxGroupRoot } from "./checkbox-group";
5
8
 
6
9
  /**
7
- * CheckboxGroup manages a set of `Checkbox` components as a single value
8
- * (`string[]`). Each child `Checkbox` must receive a `value` prop to be
9
- * tracked by the group.
10
+ * CheckboxGroup manages a set of checkboxes as a single `string[]` value.
11
+ * Built on `@base-ui/react/checkbox-group`.
10
12
  *
11
- * Use `defaultValue` for uncontrolled mode or `value` + `onValueChange` for
12
- * controlled mode. Pass `allValues` to enable the parent checkbox pattern:
13
- * a `Checkbox` with `parent` will automatically toggle between checked,
14
- * unchecked, and indeterminate based on how many items are selected no manual
15
- * state needed.
13
+ * Designed to be used inside `Field` `Field` provides the label, description,
14
+ * and error handling.
15
+ *
16
+ * **90% case** pass `items` and let the composite render all checkboxes:
17
+ * ```tsx
18
+ * <CheckboxGroup
19
+ * items={[{ value: "email", label: "Email" }, { value: "sms", label: "SMS" }]}
20
+ * onValueChange={(values) => console.log(values)}
21
+ * />
22
+ * ```
23
+ *
24
+ * **Compound API** for custom layouts (parent checkbox, custom ordering):
25
+ * ```tsx
26
+ * <CheckboxGroup allValues={allValues} value={value} onValueChange={setValue}>
27
+ * <Checkbox parent />
28
+ * <Checkbox.Item value="email" label="Email" />
29
+ * </CheckboxGroup>
30
+ * ```
31
+ *
32
+ * Use `onValueChange` to react to selection changes.
33
+ *
34
+ * Pass `allValues` to enable the parent checkbox pattern: a `Checkbox` with
35
+ * `parent` computes checked/unchecked/indeterminate automatically.
36
+ *
37
+ * Reference: [CheckboxGroup – Base UI](https://base-ui.com/react/components/checkbox-group)
16
38
  */
17
39
  const meta: Meta<typeof CheckboxGroup> = {
18
40
  title: "Components/CheckboxGroup",
19
41
  component: CheckboxGroup,
42
+ parameters: { layout: "centered" },
43
+ argTypes: {
44
+ children: { control: false },
45
+ items: { control: false },
46
+ onValueChange: { control: false },
47
+ },
48
+ tags: ["new"],
20
49
  };
21
50
 
22
51
  export default meta;
@@ -30,30 +59,29 @@ const NOTIFICATIONS = [
30
59
  ];
31
60
 
32
61
  export const Default: Story = {
33
- render: () => (
34
- <CheckboxGroup defaultValue={["comments"]}>
35
- {NOTIFICATIONS.map(({ label, value }) => (
36
- <Checkbox.Item key={value} value={value} label={label} />
37
- ))}
38
- </CheckboxGroup>
62
+ render: (args) => (
63
+ <CheckboxGroup
64
+ {...args}
65
+ items={NOTIFICATIONS}
66
+ defaultValue={["comments"]}
67
+ />
39
68
  ),
40
69
  };
41
70
 
42
71
  /**
43
- * `value` + `onValueChange` for controlled mode. `onValueChange` receives the
44
- * updated array of selected values.
72
+ * `value` + `onValueChange` for controlled mode.
45
73
  */
46
74
  export const Controlled: Story = {
47
- render: () => {
75
+ render: (args) => {
48
76
  const [value, setValue] = useState<string[]>(["comments", "mentions"]);
49
-
50
77
  return (
51
78
  <div className="space-y-4">
52
- <CheckboxGroup value={value} onValueChange={setValue}>
53
- {NOTIFICATIONS.map(({ label, value: v }) => (
54
- <Checkbox.Item key={v} value={v} label={label} />
55
- ))}
56
- </CheckboxGroup>
79
+ <CheckboxGroup
80
+ {...args}
81
+ items={NOTIFICATIONS}
82
+ value={value}
83
+ onValueChange={setValue}
84
+ />
57
85
  <p className="text-sm text-muted-foreground">
58
86
  Active: {value.length > 0 ? value.join(", ") : "none"}
59
87
  </p>
@@ -69,19 +97,20 @@ export const Controlled: Story = {
69
97
  * needed.
70
98
  */
71
99
  export const WithParent: Story = {
72
- render: () => {
100
+ render: (args) => {
73
101
  const allValues = NOTIFICATIONS.map((n) => n.value);
74
102
  const [value, setValue] = useState<string[]>(["comments"]);
75
103
 
76
104
  return (
77
105
  <CheckboxGroup
106
+ {...args}
78
107
  value={value}
79
108
  onValueChange={setValue}
80
109
  allValues={allValues}
81
110
  >
82
111
  <div className="flex items-center gap-2">
83
112
  <Checkbox parent />
84
- <span className="text-sm select-none">Enable all notifications</span>
113
+ <span className="select-none text-sm">Enable all notifications</span>
85
114
  </div>
86
115
  <div className="ml-6 flex flex-col gap-3 border-l-2 pl-4">
87
116
  {NOTIFICATIONS.map(({ label, value: v }) => (
@@ -94,11 +123,70 @@ export const WithParent: Story = {
94
123
  };
95
124
 
96
125
  export const Disabled: Story = {
126
+ render: (args) => (
127
+ <CheckboxGroup
128
+ {...args}
129
+ items={NOTIFICATIONS}
130
+ defaultValue={["comments"]}
131
+ disabled
132
+ />
133
+ ),
134
+ };
135
+
136
+ /**
137
+ * Wrap `CheckboxGroup` with `Field` to add a label, description, and error
138
+ * handling. Since `CheckboxGroup` has no native `required`, use `Field`'s
139
+ * `validate` prop — it receives the current `string[]` and runs on submit.
140
+ * Return a string to show an error, `null` to pass.
141
+ */
142
+ export const WithForm: Story = {
143
+ render: () => {
144
+ const [submitted, setSubmitted] = useState<string[] | null>(null);
145
+
146
+ return (
147
+ <Form
148
+ className="flex w-80 flex-col gap-4"
149
+ onFormSubmit={(data) => setSubmitted(data.notifications as string[])}
150
+ >
151
+ <Field
152
+ name="notifications"
153
+ label="Notifications"
154
+ required
155
+ description="Select the notifications you want to receive."
156
+ validate={(value) =>
157
+ Array.isArray(value) && value.length === 0
158
+ ? "Select at least one notification."
159
+ : null
160
+ }
161
+ >
162
+ <CheckboxGroup items={NOTIFICATIONS} />
163
+ </Field>
164
+ <Button type="submit">Submit</Button>
165
+ {submitted && (
166
+ <pre className="rounded-md bg-muted p-3 text-xs">
167
+ {JSON.stringify(submitted, null, 2)}
168
+ </pre>
169
+ )}
170
+ </Form>
171
+ );
172
+ },
173
+ };
174
+
175
+ /**
176
+ * Direct composition with the primitive for full structural control.
177
+ *
178
+ * ```tsx
179
+ * <CheckboxGroupRoot allValues={allValues}>
180
+ * <Checkbox.Item value="email" label="Email" />
181
+ * </CheckboxGroupRoot>
182
+ * ```
183
+ */
184
+ export const Primitive: Story = {
97
185
  render: () => (
98
- <CheckboxGroup defaultValue={["comments"]} disabled>
186
+ <CheckboxGroupRoot defaultValue={["comments"]}>
99
187
  {NOTIFICATIONS.map(({ label, value }) => (
100
188
  <Checkbox.Item key={value} value={value} label={label} />
101
189
  ))}
102
- </CheckboxGroup>
190
+ </CheckboxGroupRoot>
103
191
  ),
104
192
  };
@@ -1,11 +1,20 @@
1
1
  import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group";
2
- import type * as React from "react";
2
+ import { type ReactNode } from "react";
3
3
  import { cn } from "../../lib";
4
+ import { Checkbox, CheckboxGroupContext } from "../checkbox/checkbox";
5
+ import {
6
+ FieldLegend,
7
+ FieldSet,
8
+ FieldValidity,
9
+ useIsInsideFieldRoot,
10
+ } from "../field/field";
4
11
 
5
- export function CheckboxGroup({
12
+ // ── Root (primitive escape hatch) ─────────────────────────────────────────────
13
+
14
+ export function CheckboxGroupRoot({
6
15
  className,
7
16
  ...props
8
- }: CheckboxGroupPrimitive.Props): React.ReactElement {
17
+ }: CheckboxGroupPrimitive.Props) {
9
18
  return (
10
19
  <CheckboxGroupPrimitive
11
20
  className={cn("flex flex-col items-start gap-3", className)}
@@ -14,3 +23,75 @@ export function CheckboxGroup({
14
23
  />
15
24
  );
16
25
  }
26
+
27
+ // ── Composite ─────────────────────────────────────────────────────────────────
28
+
29
+ export interface CheckboxOption {
30
+ value: string;
31
+ label: ReactNode;
32
+ disabled?: boolean;
33
+ }
34
+
35
+ export interface CheckboxGroupProps {
36
+ legend?: ReactNode;
37
+ children?: ReactNode;
38
+ items?: CheckboxOption[];
39
+ defaultValue?: string[];
40
+ value?: string[];
41
+ onValueChange?: (value: string[]) => void;
42
+ allValues?: string[];
43
+ disabled?: boolean;
44
+ controlFirst?: boolean;
45
+ className?: string;
46
+ }
47
+
48
+ export function CheckboxGroup({
49
+ legend,
50
+ children,
51
+ items,
52
+ defaultValue,
53
+ value,
54
+ onValueChange,
55
+ allValues,
56
+ disabled,
57
+ controlFirst = true,
58
+ className,
59
+ }: CheckboxGroupProps) {
60
+ const isInsideField = useIsInsideFieldRoot();
61
+
62
+ const renderGroup = (invalid: boolean) => (
63
+ <CheckboxGroupContext.Provider value={{ controlFirst, invalid }}>
64
+ <CheckboxGroupPrimitive
65
+ defaultValue={defaultValue}
66
+ value={value}
67
+ onValueChange={onValueChange}
68
+ allValues={allValues}
69
+ disabled={disabled}
70
+ >
71
+ <FieldSet className={className}>
72
+ {legend && <FieldLegend>{legend}</FieldLegend>}
73
+ <div className="flex flex-col gap-2">
74
+ {items
75
+ ? items.map((item) => (
76
+ <Checkbox.Item
77
+ key={item.value}
78
+ value={item.value}
79
+ label={item.label}
80
+ disabled={item.disabled}
81
+ />
82
+ ))
83
+ : children}
84
+ </div>
85
+ </FieldSet>
86
+ </CheckboxGroupPrimitive>
87
+ </CheckboxGroupContext.Provider>
88
+ );
89
+
90
+ if (!isInsideField) return renderGroup(false);
91
+
92
+ return (
93
+ <FieldValidity>
94
+ {({ validity }) => renderGroup(validity.valid === false)}
95
+ </FieldValidity>
96
+ );
97
+ }
@@ -40,8 +40,8 @@ const items: Item[] = [
40
40
  *
41
41
  * Renders a text input as the trigger. `multiple` mode renders a chip input that
42
42
  * fits as many chips as the container allows — extras show as "+N más". Items shaped
43
- * as `{ label, value }` or plain strings/numbers need no extra props; provide
44
- * `getLabel` / `getValue` for other shapes.
43
+ * as `{ id, name }`, `{ label, value }`, or plain strings/numbers need no extra props;
44
+ * provide `getLabel` / `getId` for other shapes.
45
45
  *
46
46
  * `showClear` (default `true`) shows a clear button when a value is selected —
47
47
  * in single mode the chevron switches to X; in multiple mode only X appears.
@@ -49,10 +49,8 @@ const items: Item[] = [
49
49
  * `renderItem` customizes the content inside each dropdown item — the checkmark
50
50
  * indicator is always rendered by the item wrapper.
51
51
  *
52
- * Exposes `onChange` instead of Base UI's `onValueChange`. For controlled usage,
53
- * pass `value` + `onChange` together. Use `ComboboxRoot` + primitives for full
54
- * structural control beyond what escape-hatch props (`inputProps`, `popupProps`,
55
- * etc.) offer.
52
+ * For controlled usage pass `value` + `onValueChange` together. Use `ComboboxRoot` +
53
+ * primitives for full structural control.
56
54
  *
57
55
  * Reference: [Combobox – Base UI](https://base-ui.com/react/components/combobox)
58
56
  */
@@ -68,19 +66,14 @@ const meta: Meta<typeof Combobox> = {
68
66
  ),
69
67
  ],
70
68
  argTypes: {
71
- onChange: { control: false },
69
+ onValueChange: { control: false },
72
70
  onOpenChange: { control: false },
73
71
  onInputValueChange: { control: false },
74
72
  onItemHighlighted: { control: false },
75
73
  getLabel: { control: false },
76
- getValue: { control: false },
74
+ getId: { control: false },
77
75
  renderItem: { control: false },
78
- inputProps: { control: false },
79
- chipsProps: { control: false },
80
- chipsInputProps: { control: false },
81
- popupProps: { control: false },
82
- itemProps: { control: false },
83
- listProps: { control: false },
76
+ classNames: { control: false },
84
77
  },
85
78
  };
86
79
 
@@ -93,9 +86,27 @@ export const Default: Story = {
93
86
  render: () => <Combobox<Item> items={items} placeholder="Select a fruit…" />,
94
87
  };
95
88
 
89
+ /**
90
+ * `className` styles the root input. `classNames` exposes the `input`, `chips`,
91
+ * `chipsInput`, `popup`, `list`, `item`, and `empty` slots.
92
+ */
93
+ export const WithClassNames: Story = {
94
+ render: () => (
95
+ <Combobox<Item>
96
+ items={items}
97
+ placeholder="Select a fruit…"
98
+ classNames={{
99
+ popup: "min-w-56",
100
+ item: "font-mono",
101
+ empty: "italic",
102
+ }}
103
+ />
104
+ ),
105
+ };
106
+
96
107
  /**
97
108
  * Fully controlled: selection state lives outside the component.
98
- * Omitting `onChange` while providing `value` locks the selection.
109
+ * Omitting `onValueChange` while providing `value` locks the selection.
99
110
  */
100
111
  export const Controlled: Story = {
101
112
  render: () => {
@@ -106,7 +117,7 @@ export const Controlled: Story = {
106
117
  items={items}
107
118
  placeholder="Select a fruit…"
108
119
  value={value}
109
- onChange={setValue}
120
+ onValueChange={setValue}
110
121
  />
111
122
  <p className="text-sm text-muted-foreground">
112
123
  Selected: {value ? value.label : "none"}
@@ -196,7 +207,7 @@ export const WithRenderItem: Story = {
196
207
  };
197
208
 
198
209
  /**
199
- * Plain strings require no extra props — `getLabel` and `getValue` default to
210
+ * Plain strings require no extra props — `getLabel` and `getId` default to
200
211
  * `String(item)` when items are primitives.
201
212
  */
202
213
  export const StringItems: Story = {
@@ -226,17 +237,16 @@ export const LibraryForm: Story = {
226
237
 
227
238
  /**
228
239
  * Fully controlled single selection using primitives. Use this pattern when
229
- * composite escape-hatch props (`inputProps`, `popupProps`, etc.) are not
230
- * enough for example to insert custom elements between parts, swap the input
231
- * for a select-style trigger (`ComboboxSelectTrigger`), or add a search input
232
- * inside the popup (`ComboboxSearchInput`).
240
+ * `classNames` is not enough for example to insert custom elements between
241
+ * parts, swap the input for a select-style trigger (`ComboboxSelectTrigger`),
242
+ * or add a search input inside the popup (`ComboboxSearchInput`).
233
243
  *
234
244
  * `ComboboxRoot` requires `items` so Base UI can manage the hidden form input
235
245
  * and built-in filtering. `itemToStringLabel` / `itemToStringValue` are
236
246
  * inferred automatically for `{ label, value }` shapes.
237
247
  *
238
248
  * ```tsx
239
- * <ComboboxRoot items={items} value={value} onChange={setValue}>
249
+ * <ComboboxRoot items={items} value={value} onValueChange={setValue}>
240
250
  * <ComboboxInput placeholder="…" />
241
251
  * <ComboboxPopup>
242
252
  * <ComboboxEmpty>No items found.</ComboboxEmpty>
@@ -256,7 +266,7 @@ export const PrimitiveControlled: Story = {
256
266
  const [value, setValue] = useState<Item | null>(null);
257
267
  return (
258
268
  <div className="flex flex-col gap-4">
259
- <ComboboxRoot items={items} value={value} onChange={setValue}>
269
+ <ComboboxRoot items={items} value={value} onValueChange={setValue}>
260
270
  <ComboboxInput
261
271
  aria-label="Select a fruit"
262
272
  placeholder="Select a fruit…"