@boxcustodia/library 2.0.0-alpha.13 → 2.0.0-alpha.15
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.
- package/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1083 -717
- package/dist/index.es.js +7059 -56179
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Changelog.mdx +6 -6
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1245 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +236 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +53 -18
- package/src/components/button/components/base-button.tsx +25 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +140 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +120 -104
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +3 -3
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +29 -15
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +6 -0
- package/src/components/dropzone/dropzone.stories.tsx +70 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +164 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +226 -3
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +5 -5
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +275 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +29 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +153 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +26 -10
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +766 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +372 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +147 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +322 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +364 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +69 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -451
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- 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 {
|
|
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
|
|
8
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
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
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
-
<
|
|
186
|
+
<CheckboxGroupRoot defaultValue={["comments"]}>
|
|
99
187
|
{NOTIFICATIONS.map(({ label, value }) => (
|
|
100
188
|
<Checkbox.Item key={value} value={value} label={label} />
|
|
101
189
|
))}
|
|
102
|
-
</
|
|
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
|
|
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
|
-
|
|
12
|
+
// ── Root (primitive escape hatch) ─────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export function CheckboxGroupRoot({
|
|
6
15
|
className,
|
|
7
16
|
...props
|
|
8
|
-
}: CheckboxGroupPrimitive.Props)
|
|
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 }
|
|
44
|
-
* `getLabel` / `
|
|
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
|
-
*
|
|
53
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
74
|
+
getId: { control: false },
|
|
77
75
|
renderItem: { control: false },
|
|
78
|
-
|
|
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 `
|
|
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
|
-
|
|
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 `
|
|
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
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
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}
|
|
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}
|
|
269
|
+
<ComboboxRoot items={items} value={value} onValueChange={setValue}>
|
|
260
270
|
<ComboboxInput
|
|
261
271
|
aria-label="Select a fruit"
|
|
262
272
|
placeholder="Select a fruit…"
|