@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.
Files changed (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1083 -717
  3. package/dist/index.es.js +7059 -56179
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Changelog.mdx +6 -6
  7. package/src/__doc__/Examples.tsx +1 -1
  8. package/src/__doc__/Intro.mdx +3 -3
  9. package/src/__doc__/Tabs.mdx +112 -0
  10. package/src/__doc__/V2.mdx +1245 -0
  11. package/src/components/accordion/accordion.stories.tsx +143 -0
  12. package/src/components/accordion/accordion.tsx +135 -0
  13. package/src/components/accordion/index.ts +1 -0
  14. package/src/components/alert/alert.stories.tsx +24 -4
  15. package/src/components/alert/alert.tsx +17 -9
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  17. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  18. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  19. package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
  20. package/src/components/auto-complete/auto-complete.tsx +420 -68
  21. package/src/components/auto-complete/index.ts +0 -1
  22. package/src/components/avatar/avatar.stories.tsx +162 -21
  23. package/src/components/avatar/avatar.tsx +79 -20
  24. package/src/components/button/button.stories.tsx +236 -294
  25. package/src/components/button/button.test.tsx +10 -17
  26. package/src/components/button/button.tsx +53 -18
  27. package/src/components/button/components/base-button.tsx +25 -53
  28. package/src/components/button/index.ts +0 -1
  29. package/src/components/calendar/calendar.stories.tsx +1 -1
  30. package/src/components/calendar/calendar.tsx +4 -4
  31. package/src/components/card/card.stories.tsx +140 -69
  32. package/src/components/card/card.tsx +155 -54
  33. package/src/components/center/center.stories.tsx +22 -39
  34. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  35. package/src/components/checkbox/checkbox.tsx +76 -15
  36. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  37. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  38. package/src/components/combobox/combobox.stories.tsx +33 -23
  39. package/src/components/combobox/combobox.tsx +120 -104
  40. package/src/components/date-picker/date-input.stories.tsx +14 -6
  41. package/src/components/date-picker/date-input.tsx +3 -3
  42. package/src/components/date-picker/date-picker.model.ts +13 -4
  43. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  44. package/src/components/date-picker/date-picker.tsx +29 -15
  45. package/src/components/dialog/dialog.stories.tsx +18 -0
  46. package/src/components/dialog/dialog.test.tsx +1 -1
  47. package/src/components/dialog/dialog.tsx +51 -20
  48. package/src/components/divider/divider.stories.tsx +6 -0
  49. package/src/components/dropzone/dropzone.stories.tsx +70 -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 +164 -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 +226 -3
  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 +5 -5
  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 +275 -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 +29 -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 +153 -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 +26 -10
  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 +766 -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 +372 -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 +147 -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 +322 -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 +364 -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 +69 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -451
  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,74 +1,175 @@
1
1
  import React from "react";
2
2
  import { cn } from "../../lib";
3
3
 
4
- export const Card = ({
4
+ export function CardRoot({
5
5
  className,
6
+ size = "default",
6
7
  ...props
7
- }: React.HTMLAttributes<HTMLDivElement>) => (
8
- <div
9
- className={cn(
10
- "rounded-lg border bg-card text-card-foreground shadow-sm",
11
- className,
12
- )}
13
- {...props}
14
- data-slot="card"
15
- />
16
- );
8
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
9
+ return (
10
+ <div
11
+ data-slot="card"
12
+ data-size={size}
13
+ className={cn(
14
+ "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
17
21
 
18
- export const CardHeader = ({
22
+ export function CardHeader({
19
23
  className,
20
24
  ...props
21
- }: React.HTMLAttributes<HTMLDivElement>) => (
22
- <div
23
- className={cn("flex flex-col space-y-1 p-6", className)}
24
- {...props}
25
- data-slot="card-header"
26
- />
27
- );
25
+ }: React.ComponentProps<"div">) {
26
+ return (
27
+ <div
28
+ data-slot="card-header"
29
+ className={cn(
30
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ );
36
+ }
28
37
 
29
- export const CardTitle = ({
38
+ export function CardTitle({
30
39
  className,
31
40
  ...props
32
- }: React.HTMLAttributes<HTMLHeadingElement>) => (
33
- <h3
34
- className={cn(
35
- "text-2xl font-semibold leading-none tracking-tight",
36
- className,
37
- )}
38
- {...props}
39
- data-slot="card-title"
40
- />
41
- );
41
+ }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-title"
45
+ className={cn(
46
+ "cn-font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
42
53
 
43
- export const CardDescription = ({
54
+ export function CardDescription({
44
55
  className,
45
56
  ...props
46
- }: React.HTMLAttributes<HTMLParagraphElement>) => (
47
- <p
48
- className={cn("text-sm text-muted-foreground", className)}
49
- {...props}
50
- data-slot="card-description"
51
- />
52
- );
57
+ }: React.ComponentProps<"div">) {
58
+ return (
59
+ <div
60
+ data-slot="card-description"
61
+ className={cn("text-sm text-muted-foreground", className)}
62
+ {...props}
63
+ />
64
+ );
65
+ }
53
66
 
54
- export const CardContent = ({
67
+ export function CardAction({
55
68
  className,
56
69
  ...props
57
- }: React.HTMLAttributes<HTMLDivElement>) => (
58
- <div
59
- className={cn("p-6 pt-0", className)}
60
- {...props}
61
- data-slot="card-content"
62
- />
63
- );
70
+ }: React.ComponentProps<"div">) {
71
+ return (
72
+ <div
73
+ data-slot="card-action"
74
+ className={cn(
75
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
64
82
 
65
- export const CardFooter = ({
83
+ export function CardContent({
66
84
  className,
67
85
  ...props
68
- }: React.HTMLAttributes<HTMLDivElement>) => (
69
- <div
70
- className={cn("flex items-center p-6 pt-0", className)}
71
- {...props}
72
- data-slot="card-footer"
73
- />
74
- );
86
+ }: React.ComponentProps<"div">) {
87
+ return (
88
+ <div
89
+ data-slot="card-content"
90
+ className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
91
+ {...props}
92
+ />
93
+ );
94
+ }
95
+
96
+ export function CardFooter({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<"div">) {
100
+ return (
101
+ <div
102
+ data-slot="card-footer"
103
+ className={cn(
104
+ "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
105
+ className,
106
+ )}
107
+ {...props}
108
+ />
109
+ );
110
+ }
111
+
112
+ interface CardProps
113
+ extends Omit<React.ComponentProps<"div">, "children" | "title"> {
114
+ title?: React.ReactNode;
115
+ description?: React.ReactNode;
116
+ action?: React.ReactNode;
117
+ footer?: React.ReactNode;
118
+ children?: React.ReactNode;
119
+ size?: "default" | "sm";
120
+ /** Styles applied to each internal slot. */
121
+ classNames?: {
122
+ /** Header wrapper holding title, description, and action. */
123
+ header?: string;
124
+ /** Card title text. */
125
+ title?: string;
126
+ /** Supporting description below the title. */
127
+ description?: string;
128
+ /** Action area on the right of the header. */
129
+ action?: string;
130
+ /** Body content rendered between the header and the footer. */
131
+ content?: string;
132
+ /** Footer area below the content. */
133
+ footer?: string;
134
+ };
135
+ }
136
+
137
+ export function Card({
138
+ title,
139
+ description,
140
+ action,
141
+ footer,
142
+ children,
143
+ size,
144
+ className,
145
+ classNames,
146
+ ...props
147
+ }: CardProps) {
148
+ const hasHeader = title || description || action;
149
+
150
+ return (
151
+ <CardRoot size={size} className={className} {...props}>
152
+ {hasHeader && (
153
+ <CardHeader className={classNames?.header}>
154
+ {title && (
155
+ <CardTitle className={classNames?.title}>{title}</CardTitle>
156
+ )}
157
+ {description && (
158
+ <CardDescription className={classNames?.description}>
159
+ {description}
160
+ </CardDescription>
161
+ )}
162
+ {action && (
163
+ <CardAction className={classNames?.action}>{action}</CardAction>
164
+ )}
165
+ </CardHeader>
166
+ )}
167
+ {children && (
168
+ <CardContent className={classNames?.content}>{children}</CardContent>
169
+ )}
170
+ {footer && (
171
+ <CardFooter className={classNames?.footer}>{footer}</CardFooter>
172
+ )}
173
+ </CardRoot>
174
+ );
175
+ }
@@ -1,18 +1,21 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { Inbox, Phone } from "lucide-react";
3
- import { Stack } from "../stack";
4
2
  import { Center } from ".";
5
3
 
6
4
  /**
7
- * Layout primitive that centers its children both horizontally and vertically using flexbox.
8
- * Supports polymorphic rendering via `as` and an `inline` mode for `inline-flex` contexts.
5
+ * Layout primitive that centers its single child on both axes via flexbox.
6
+ * Defaults to `display: flex`. `inline` switches to `inline-flex` for inline contexts.
7
+ * `as` renders any HTML element for semantic markup.
9
8
  */
10
9
  const meta: Meta<typeof Center> = {
11
10
  title: "Components/Center",
12
11
  component: Center,
12
+ parameters: {
13
+ layout: "centered",
14
+ },
13
15
  argTypes: {
14
16
  children: { control: false },
15
17
  className: { control: false },
18
+ as: { control: false },
16
19
  },
17
20
  };
18
21
 
@@ -21,61 +24,41 @@ type Story = StoryObj<typeof Center>;
21
24
 
22
25
  export const Default: Story = {
23
26
  render: () => (
24
- <Center className="h-[300px] w-full border rounded-lg border-dashed">
25
- <Stack
26
- direction="vertical"
27
- align="center"
28
- gap={8}
29
- className="text-muted-foreground"
30
- >
31
- <Inbox className="size-10 stroke-1" />
32
- <span className="text-sm font-medium">No results found</span>
33
- <span className="text-xs">Try adjusting your search or filters</span>
34
- </Stack>
35
- </Center>
36
- ),
37
- };
38
-
39
- export const Icon: Story = {
40
- render: () => (
41
- <Center className="w-fit p-4 bg-primary text-white rounded-full">
42
- <Phone />
27
+ <Center className="h-48 w-72 border border-dashed rounded-lg">
28
+ <span className="text-sm text-muted-foreground">Centered</span>
43
29
  </Center>
44
30
  ),
45
31
  };
46
32
 
47
33
  /**
48
- * `inline` switches the display to `inline-flex`, allowing the Center to sit inline with text
49
- * or other inline elements without breaking the flow.
34
+ * `inline` swaps `flex` for `inline-flex` so the Center flows with surrounding inline content
35
+ * instead of starting a new block. Useful for badges, counters, and icon chips inside text.
50
36
  */
51
37
  export const Inline: Story = {
52
38
  render: () => (
53
- <p className="text-sm">
54
- Text before{" "}
39
+ <p className="text-sm max-w-sm">
40
+ You have{" "}
55
41
  <Center
56
42
  inline
57
- className="w-8 h-8 bg-primary text-white rounded-full text-xs"
43
+ className="w-6 h-6 bg-primary text-primary-foreground rounded-full text-xs font-medium"
58
44
  >
59
- 42
45
+ 3
60
46
  </Center>{" "}
61
- text after
47
+ unread messages in your inbox.
62
48
  </p>
63
49
  ),
64
50
  };
65
51
 
66
52
  /**
67
- * `as` renders the Center as a different HTML element for semantic markup.
68
- * Common use cases: `main` for page root, `section` for content areas.
53
+ * `as` swaps the rendered element while keeping the centering behavior.
54
+ * Pick the tag that matches the role: `main`, `section`, `header`, `footer`.
69
55
  */
70
56
  export const As: Story = {
71
57
  render: () => (
72
- <Center
73
- as="main"
74
- className="bg-secondary w-[400px] aspect-square rounded p-4"
75
- >
76
- <div className="bg-primary text-primary-foreground rounded p-4">
77
- Rendered as &lt;main&gt;
78
- </div>
58
+ <Center as="section" className="h-48 w-72 bg-muted rounded-lg">
59
+ <code className="text-xs px-2 py-1 bg-background rounded">
60
+ &lt;section&gt;
61
+ </code>
79
62
  </Center>
80
63
  ),
81
64
  };
@@ -32,7 +32,7 @@ const meta: Meta<typeof Checkbox> = {
32
32
  },
33
33
  argTypes: {
34
34
  onCheckedChange: { control: false },
35
- indicatorProps: { control: false },
35
+ classNames: { control: false },
36
36
  tooltip: { control: false },
37
37
  },
38
38
  };
@@ -42,6 +42,22 @@ type Story = StoryObj<typeof Checkbox>;
42
42
 
43
43
  export const Default: Story = {};
44
44
 
45
+ /**
46
+ * `className` styles the checkbox root. `classNames` exposes the
47
+ * `wrapper`, `indicator`, and `label` slots — the `wrapper` slot only
48
+ * applies when a `label` is rendered.
49
+ */
50
+ export const WithClassNames: Story = {
51
+ args: {
52
+ label: "Custom styled",
53
+ classNames: {
54
+ wrapper: "gap-3",
55
+ indicator: "data-checked:bg-emerald-500",
56
+ label: "text-emerald-700 font-medium",
57
+ },
58
+ },
59
+ };
60
+
45
61
  /**
46
62
  * Pass `label` for a linked `<label>` element. `children` works as an alias —
47
63
  * `label` takes priority when both are provided.
@@ -179,7 +195,7 @@ export const Group: Story = {
179
195
  <Checkbox.Group
180
196
  legend="Notifications"
181
197
  defaultValue={["email"]}
182
- onChange={action("onChange")}
198
+ onValueChange={action("onValueChange")}
183
199
  >
184
200
  <Checkbox.Item value="email" label="Email" />
185
201
  <Checkbox.Item value="sms" label="SMS" />
@@ -227,7 +243,11 @@ export const GroupParent: Story = {
227
243
  onCheckedChange={(checked) => setValue(checked ? ALL : [])}
228
244
  />
229
245
  <div className="ml-6 border-l-2 pl-4">
230
- <Checkbox.Group value={value} onChange={setValue} allValues={ALL}>
246
+ <Checkbox.Group
247
+ value={value}
248
+ onValueChange={setValue}
249
+ allValues={ALL}
250
+ >
231
251
  <Checkbox.Item value="email" label="Email" />
232
252
  <Checkbox.Item value="sms" label="SMS" />
233
253
  <Checkbox.Item value="push" label="Push notifications" />
@@ -239,7 +259,7 @@ export const GroupParent: Story = {
239
259
  };
240
260
 
241
261
  /**
242
- * Use `value` + `onChange` for a controlled group.
262
+ * Use `value` + `onValueChange` for a controlled group.
243
263
  */
244
264
  export const GroupControlled: Story = {
245
265
  render: () => {
@@ -248,7 +268,7 @@ export const GroupControlled: Story = {
248
268
  <Checkbox.Group
249
269
  legend="Contact channel"
250
270
  value={value}
251
- onChange={setValue}
271
+ onValueChange={setValue}
252
272
  >
253
273
  <Checkbox.Item value="email" label="Email" />
254
274
  <Checkbox.Item value="sms" label="SMS" />
@@ -92,7 +92,7 @@ export function CheckboxIndicator({
92
92
 
93
93
  // ── Group context ──────────────────────────────────────────────────────────────
94
94
 
95
- const CheckboxGroupContext = createContext<{
95
+ export const CheckboxGroupContext = createContext<{
96
96
  controlFirst: boolean;
97
97
  invalid: boolean;
98
98
  }>({
@@ -106,13 +106,21 @@ interface CheckboxProps extends Omit<CheckboxPrimitive.Root.Props, "children"> {
106
106
  children?: ReactNode;
107
107
  label?: ReactNode;
108
108
  tooltip?: ReactNode;
109
- indicatorProps?: CheckboxPrimitive.Indicator.Props;
110
109
  controlFirst?: boolean;
110
+ /** Styles applied to each internal slot. */
111
+ classNames?: {
112
+ /** Flex wrapper around checkbox and label (only rendered when a label is provided). */
113
+ wrapper?: string;
114
+ /** Checkmark indicator inside the checkbox. */
115
+ indicator?: string;
116
+ /** Label element next to the checkbox. */
117
+ label?: string;
118
+ };
111
119
  }
112
120
 
113
121
  function CheckboxSingle({
114
122
  className,
115
- indicatorProps,
123
+ classNames,
116
124
  label,
117
125
  children,
118
126
  tooltip,
@@ -132,7 +140,7 @@ function CheckboxSingle({
132
140
  required={required}
133
141
  {...props}
134
142
  >
135
- <CheckboxIndicator {...indicatorProps} />
143
+ <CheckboxIndicator className={classNames?.indicator} />
136
144
  </CheckboxRoot>
137
145
  );
138
146
 
@@ -142,10 +150,16 @@ function CheckboxSingle({
142
150
  className={cn(
143
151
  "flex select-none items-center gap-x-2",
144
152
  !controlFirst && "flex-row-reverse justify-end",
153
+ classNames?.wrapper,
145
154
  )}
146
155
  >
147
156
  {checkbox}
148
- <Label htmlFor={idToUse} required={required} tooltip={tooltip}>
157
+ <Label
158
+ htmlFor={idToUse}
159
+ required={required}
160
+ tooltip={tooltip}
161
+ className={classNames?.label}
162
+ >
149
163
  {labelContent}
150
164
  </Label>
151
165
  </div>
@@ -160,10 +174,20 @@ function CheckboxSingle({
160
174
  export interface CheckboxItemProps
161
175
  extends Omit<CheckboxPrimitive.Root.Props, "children"> {
162
176
  label: ReactNode;
177
+ /** Styles applied to each internal slot. */
178
+ classNames?: {
179
+ /** Flex wrapper around checkbox and label. */
180
+ wrapper?: string;
181
+ /** Checkmark indicator inside the checkbox. */
182
+ indicator?: string;
183
+ /** Label element next to the checkbox. */
184
+ label?: string;
185
+ };
163
186
  }
164
187
 
165
188
  function CheckboxItem({
166
189
  className,
190
+ classNames,
167
191
  label,
168
192
  id,
169
193
  ...props
@@ -178,12 +202,15 @@ function CheckboxItem({
178
202
  className={cn(
179
203
  "flex select-none items-center gap-x-2",
180
204
  !controlFirst && "flex-row-reverse justify-end",
205
+ classNames?.wrapper,
181
206
  )}
182
207
  >
183
208
  <CheckboxRoot id={idToUse} className={className} {...props}>
184
- <CheckboxIndicator />
209
+ <CheckboxIndicator className={classNames?.indicator} />
185
210
  </CheckboxRoot>
186
- <Label htmlFor={idToUse}>{label}</Label>
211
+ <Label htmlFor={idToUse} className={classNames?.label}>
212
+ {label}
213
+ </Label>
187
214
  </div>
188
215
  </FieldBase.Root>
189
216
  );
@@ -211,18 +238,29 @@ function CheckboxLegend({
211
238
 
212
239
  // ── Composite: Checkbox.Group ──────────────────────────────────────────────────
213
240
 
214
- export interface CheckboxGroupProps {
241
+ interface CheckboxGroupProps {
215
242
  legend?: ReactNode;
216
243
  children: ReactNode;
217
244
  error?: string;
218
245
  description?: ReactNode;
219
246
  defaultValue?: string[];
220
247
  value?: string[];
221
- onChange?: (value: string[]) => void;
248
+ onValueChange?: (value: string[]) => void;
222
249
  allValues?: string[];
223
250
  disabled?: boolean;
224
251
  controlFirst?: boolean;
225
252
  className?: string;
253
+ /** Styles applied to each internal slot. */
254
+ classNames?: {
255
+ /** Legend element above the items. */
256
+ legend?: string;
257
+ /** Wrapper around the rendered children (items). */
258
+ items?: string;
259
+ /** Description shown below the items when there is no error. */
260
+ description?: string;
261
+ /** Error message shown below the items. */
262
+ error?: string;
263
+ };
226
264
  }
227
265
 
228
266
  function CheckboxGroup({
@@ -232,27 +270,50 @@ function CheckboxGroup({
232
270
  description,
233
271
  defaultValue,
234
272
  value,
235
- onChange,
273
+ onValueChange,
236
274
  allValues,
237
275
  disabled,
238
276
  controlFirst = true,
239
277
  className,
278
+ classNames,
240
279
  }: CheckboxGroupProps): React.ReactElement {
241
280
  return (
242
281
  <CheckboxGroupContext.Provider value={{ controlFirst, invalid: !!error }}>
243
282
  <CheckboxGroupPrimitive
244
283
  defaultValue={defaultValue}
245
284
  value={value}
246
- onValueChange={onChange}
285
+ onValueChange={onValueChange}
247
286
  allValues={allValues}
248
287
  disabled={disabled}
249
288
  >
250
289
  <Fieldset.Root className={cn("flex flex-col gap-3", className)}>
251
- {legend && <CheckboxLegend>{legend}</CheckboxLegend>}
252
- <div className="flex flex-col gap-2">{children}</div>
253
- {error && <p className="text-sm font-medium text-error">{error}</p>}
290
+ {legend && (
291
+ <CheckboxLegend className={classNames?.legend}>
292
+ {legend}
293
+ </CheckboxLegend>
294
+ )}
295
+ <div className={cn("flex flex-col gap-2", classNames?.items)}>
296
+ {children}
297
+ </div>
298
+ {error && (
299
+ <p
300
+ className={cn(
301
+ "text-sm font-medium text-error",
302
+ classNames?.error,
303
+ )}
304
+ >
305
+ {error}
306
+ </p>
307
+ )}
254
308
  {!error && description && (
255
- <p className="text-sm text-muted-foreground">{description}</p>
309
+ <p
310
+ className={cn(
311
+ "text-sm text-muted-foreground",
312
+ classNames?.description,
313
+ )}
314
+ >
315
+ {description}
316
+ </p>
256
317
  )}
257
318
  </Fieldset.Root>
258
319
  </CheckboxGroupPrimitive>