@boxcustodia/library 2.0.0-alpha.13 → 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 (173) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1083 -715
  3. package/dist/index.es.js +7077 -56175
  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 +119 -103
  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 +6 -0
  48. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  49. package/src/components/dropzone/dropzone.tsx +383 -105
  50. package/src/components/dropzone/index.ts +0 -1
  51. package/src/components/empty/empty.stories.tsx +165 -0
  52. package/src/components/empty/empty.tsx +156 -0
  53. package/src/components/empty/index.ts +1 -0
  54. package/src/components/field/field.stories.tsx +226 -3
  55. package/src/components/field/field.tsx +77 -42
  56. package/src/components/form/form.stories.tsx +320 -197
  57. package/src/components/form/form.tsx +3 -23
  58. package/src/components/index.ts +2 -6
  59. package/src/components/input/input.stories.tsx +5 -5
  60. package/src/components/input/input.tsx +4 -4
  61. package/src/components/kbd/kbd.stories.tsx +1 -0
  62. package/src/components/label/label.stories.tsx +16 -0
  63. package/src/components/label/label.tsx +13 -2
  64. package/src/components/loader/loader.stories.tsx +7 -5
  65. package/src/components/loader/loader.tsx +8 -3
  66. package/src/components/menu/menu-primitives.tsx +207 -196
  67. package/src/components/menu/menu.stories.tsx +276 -146
  68. package/src/components/menu/menu.tsx +146 -54
  69. package/src/components/number-input/number-input.stories.tsx +27 -4
  70. package/src/components/number-input/number-input.test.tsx +2 -2
  71. package/src/components/number-input/number-input.tsx +25 -29
  72. package/src/components/otp/index.ts +1 -0
  73. package/src/components/otp/otp.stories.tsx +209 -0
  74. package/src/components/otp/otp.tsx +100 -0
  75. package/src/components/pagination/index.ts +1 -0
  76. package/src/components/pagination/pagination.model.ts +2 -0
  77. package/src/components/pagination/pagination.stories.tsx +154 -59
  78. package/src/components/pagination/pagination.test.tsx +122 -57
  79. package/src/components/pagination/pagination.tsx +575 -77
  80. package/src/components/password/password.stories.tsx +18 -3
  81. package/src/components/password/password.tsx +26 -10
  82. package/src/components/popover/popover.stories.tsx +26 -5
  83. package/src/components/popover/popover.tsx +15 -23
  84. package/src/components/progress/progress.stories.tsx +1 -0
  85. package/src/components/radio-group/index.ts +1 -0
  86. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  87. package/src/components/radio-group/radio-group.tsx +212 -0
  88. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  89. package/src/components/select/select.stories.tsx +118 -19
  90. package/src/components/select/select.tsx +67 -62
  91. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  92. package/src/components/stack/stack.stories.tsx +179 -89
  93. package/src/components/stack/stack.tsx +2 -2
  94. package/src/components/stepper/index.ts +1 -1
  95. package/src/components/stepper/stepper.stories.tsx +767 -83
  96. package/src/components/stepper/stepper.test.tsx +18 -18
  97. package/src/components/stepper/stepper.tsx +554 -0
  98. package/src/components/switch/switch.stories.tsx +15 -1
  99. package/src/components/switch/switch.tsx +17 -4
  100. package/src/components/table/index.ts +0 -2
  101. package/src/components/table/table.stories.tsx +131 -18
  102. package/src/components/table/table.test.tsx +1 -1
  103. package/src/components/table/table.tsx +183 -77
  104. package/src/components/tabs/tabs.stories.tsx +373 -155
  105. package/src/components/tabs/tabs.test.tsx +12 -12
  106. package/src/components/tabs/tabs.tsx +72 -149
  107. package/src/components/tag/index.ts +0 -1
  108. package/src/components/tag/tag.stories.tsx +155 -120
  109. package/src/components/tag/tag.tsx +47 -95
  110. package/src/components/textarea/textarea.stories.tsx +8 -22
  111. package/src/components/textarea/textarea.tsx +17 -79
  112. package/src/components/timeline/timeline.stories.tsx +323 -42
  113. package/src/components/timeline/timeline.tsx +359 -132
  114. package/src/components/toast/toast.stories.tsx +1 -0
  115. package/src/components/tooltip/tooltip.tsx +11 -9
  116. package/src/components/tree/index.ts +0 -1
  117. package/src/components/tree/tree.stories.tsx +365 -408
  118. package/src/components/tree/tree.test.tsx +163 -0
  119. package/src/components/tree/tree.tsx +212 -36
  120. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  121. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  122. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  123. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  124. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  125. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  126. package/src/hooks/usePagination/usePagination.tsx +36 -24
  127. package/src/styles/theme.css +1 -1
  128. package/src/utils/form.tsx +67 -37
  129. package/src/utils/index.ts +1 -1
  130. package/src/__doc__/Migration.mdx +0 -451
  131. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  132. package/src/components/background-image/background-image.stories.tsx +0 -21
  133. package/src/components/background-image/background-image.test.tsx +0 -29
  134. package/src/components/background-image/background-image.tsx +0 -23
  135. package/src/components/background-image/index.ts +0 -1
  136. package/src/components/button/button.variants.ts +0 -44
  137. package/src/components/button/components/loader-overlay.tsx +0 -21
  138. package/src/components/button/components/loading-icon.tsx +0 -47
  139. package/src/components/dropzone/upload-primitives.tsx +0 -310
  140. package/src/components/dropzone/use-dropzone.ts +0 -122
  141. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  142. package/src/components/empty-state/empty-state.tsx +0 -39
  143. package/src/components/empty-state/index.ts +0 -1
  144. package/src/components/heading/heading.stories.tsx +0 -74
  145. package/src/components/heading/heading.tsx +0 -28
  146. package/src/components/heading/heading.variants.ts +0 -27
  147. package/src/components/heading/index.ts +0 -1
  148. package/src/components/kbd/kbd.variants.ts +0 -26
  149. package/src/components/menu/util/render-menu-item.tsx +0 -54
  150. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  151. package/src/components/multi-select/index.ts +0 -1
  152. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  153. package/src/components/multi-select/multi-select.tsx +0 -300
  154. package/src/components/multi-select/multi-select.variants.ts +0 -22
  155. package/src/components/pagination/components/pagination-option.tsx +0 -27
  156. package/src/components/show/index.ts +0 -1
  157. package/src/components/show/show.stories.tsx +0 -197
  158. package/src/components/show/show.test.tsx +0 -41
  159. package/src/components/show/show.tsx +0 -16
  160. package/src/components/stepper/Stepper.tsx +0 -190
  161. package/src/components/stepper/context/stepper-context.tsx +0 -11
  162. package/src/components/table/table-primitives.tsx +0 -122
  163. package/src/components/table/table.model.ts +0 -20
  164. package/src/components/table-pagination/index.ts +0 -2
  165. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  167. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  168. package/src/components/table-pagination/table-pagination.tsx +0 -108
  169. package/src/components/tabs/context/tabs-context.tsx +0 -14
  170. package/src/components/tag/tag.variants.ts +0 -31
  171. package/src/components/timeline/timeline-status.ts +0 -5
  172. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  173. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,6 +1,6 @@
1
1
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
2
2
  import { CalendarIcon } from "lucide-react";
3
- import { ReactNode, useRef, useState } from "react";
3
+ import { type ReactElement, ReactNode, useRef, useState } from "react";
4
4
  import {
5
5
  Calendar,
6
6
  FieldControl,
@@ -24,11 +24,14 @@ import {
24
24
  formatSingleDate,
25
25
  } from "./date-picker.utils";
26
26
 
27
- export const DatePicker = (props: DatePickerProps) => {
27
+ export function DatePicker(props: SingleDatePickerProps): ReactElement;
28
+ export function DatePicker(props: RangeDatePickerProps): ReactElement;
29
+ export function DatePicker(props: MultipleDatePickerProps): ReactElement;
30
+ export function DatePicker(props: DatePickerProps): ReactElement {
28
31
  if (props.mode === "range") return <RangePicker {...props} />;
29
32
  if (props.mode === "multiple") return <MultiplePicker {...props} />;
30
33
  return <SinglePicker {...props} />;
31
- };
34
+ }
32
35
 
33
36
  // ─── Shared popover shell ────────────────────────────────────────────────────
34
37
 
@@ -39,6 +42,7 @@ type PickerShellProps = {
39
42
  hasValue: boolean;
40
43
  disabled?: boolean;
41
44
  className?: string;
45
+ classNames?: DatePickerProps["classNames"];
42
46
  children: ReactNode;
43
47
  footer?: ReactNode;
44
48
  slot: string;
@@ -53,6 +57,7 @@ const PickerShell = ({
53
57
  hasValue,
54
58
  disabled,
55
59
  className,
60
+ classNames,
56
61
  children,
57
62
  footer,
58
63
  slot,
@@ -100,13 +105,16 @@ const PickerShell = ({
100
105
  }
101
106
  />
102
107
  <PopoverPopup
103
- className="w-auto p-0"
108
+ className={cn("w-auto p-0", classNames?.popup)}
104
109
  align="start"
105
110
  data-slot={`${slot}-content`}
106
111
  >
107
112
  {children}
108
113
  {footer && (
109
- <div className="p-2" data-slot={`${slot}-footer`}>
114
+ <div
115
+ className={cn("p-2", classNames?.footer)}
116
+ data-slot={`${slot}-footer`}
117
+ >
110
118
  {footer}
111
119
  </div>
112
120
  )}
@@ -120,19 +128,20 @@ const PickerShell = ({
120
128
  const SinglePicker = ({
121
129
  value: valueProp,
122
130
  defaultValue,
123
- onChange,
131
+ onValueChange,
124
132
  placeholder = "Seleccionar fecha",
125
133
  disabled,
126
134
  disabledDate,
127
135
  renderFooter,
128
136
  className,
137
+ classNames,
129
138
  required,
130
139
  }: SingleDatePickerProps) => {
131
140
  const [open, setOpen] = useState(false);
132
141
 
133
142
  const [value, setValue] = useControllableState<Date | null>({
134
143
  prop: valueProp,
135
- onChange: (next) => onChange?.(next ?? null),
144
+ onChange: (next) => onValueChange?.(next ?? null),
136
145
  defaultProp: defaultValue ?? null,
137
146
  });
138
147
 
@@ -157,12 +166,13 @@ const SinglePicker = ({
157
166
  hasValue={!!value}
158
167
  disabled={disabled}
159
168
  className={className}
169
+ classNames={classNames}
160
170
  footer={renderFooter?.(footerProps)}
161
171
  formValue={formatSingleDate(value, "")}
162
172
  required={required}
163
173
  >
164
174
  <Calendar
165
- className="border-none"
175
+ className={cn("border-none", classNames?.calendar)}
166
176
  mode="single"
167
177
  selected={value ?? undefined}
168
178
  onSelect={(next) => {
@@ -183,19 +193,20 @@ const EMPTY_RANGE: DateRange = { start: null, end: null };
183
193
  const RangePicker = ({
184
194
  value: valueProp,
185
195
  defaultValue,
186
- onChange,
196
+ onValueChange,
187
197
  placeholder = "Seleccionar rango",
188
198
  disabled,
189
199
  disabledDate,
190
200
  renderFooter,
191
201
  className,
202
+ classNames,
192
203
  required,
193
204
  }: RangeDatePickerProps) => {
194
205
  const [open, setOpen] = useState(false);
195
206
 
196
207
  const [value, setValue] = useControllableState<DateRange>({
197
208
  prop: valueProp,
198
- onChange: (next) => onChange?.(next ?? EMPTY_RANGE),
209
+ onChange: (next) => onValueChange?.(next ?? EMPTY_RANGE),
199
210
  defaultProp: defaultValue ?? EMPTY_RANGE,
200
211
  });
201
212
 
@@ -220,6 +231,7 @@ const RangePicker = ({
220
231
  hasValue={!!(range.start || range.end)}
221
232
  disabled={disabled}
222
233
  className={className}
234
+ classNames={classNames}
223
235
  footer={renderFooter?.(footerProps)}
224
236
  formValue={formatRangeDate(
225
237
  range.start || range.end ? range : undefined,
@@ -228,7 +240,7 @@ const RangePicker = ({
228
240
  required={required}
229
241
  >
230
242
  <Calendar
231
- className="border-none"
243
+ className={cn("border-none", classNames?.calendar)}
232
244
  mode="range"
233
245
  selected={{
234
246
  from: range.start ?? undefined,
@@ -248,19 +260,20 @@ const RangePicker = ({
248
260
  const MultiplePicker = ({
249
261
  value: valueProp,
250
262
  defaultValue,
251
- onChange,
263
+ onValueChange,
252
264
  placeholder = "Seleccionar fechas",
253
265
  disabled,
254
266
  disabledDate,
255
267
  renderFooter,
256
268
  className,
269
+ classNames,
257
270
  required,
258
271
  }: MultipleDatePickerProps) => {
259
272
  const [open, setOpen] = useState(false);
260
273
 
261
274
  const [value, setValue] = useControllableState<Date[]>({
262
275
  prop: valueProp,
263
- onChange: (next) => onChange?.(next ?? []),
276
+ onChange: (next) => onValueChange?.(next ?? []),
264
277
  defaultProp: defaultValue ?? [],
265
278
  });
266
279
 
@@ -280,12 +293,13 @@ const MultiplePicker = ({
280
293
  hasValue={(value?.length ?? 0) > 0}
281
294
  disabled={disabled}
282
295
  className={className}
296
+ classNames={classNames}
283
297
  footer={renderFooter?.(footerProps)}
284
298
  formValue={formatMultipleDates(value, "")}
285
299
  required={required}
286
300
  >
287
301
  <Calendar
288
- className="border-none"
302
+ className={cn("border-none", classNames?.calendar)}
289
303
  mode="multiple"
290
304
  selected={value ?? []}
291
305
  onSelect={(next) => {
@@ -42,6 +42,7 @@ const meta: Meta<typeof Dialog> = {
42
42
  hideClose: { control: "boolean" },
43
43
  footer: { control: false },
44
44
  children: { control: false },
45
+ classNames: { control: false },
45
46
  },
46
47
  };
47
48
 
@@ -50,6 +51,23 @@ type Story = StoryObj<typeof Dialog>;
50
51
 
51
52
  export const Default: Story = {};
52
53
 
54
+ /**
55
+ * `className` styles the popup panel. `classNames` exposes the `backdrop`,
56
+ * `viewport`, `header`, `title`, `description`, `footer`, and `closeButton` slots.
57
+ */
58
+ export const WithClassNames: Story = {
59
+ args: {
60
+ trigger: <Button variant="outline">Open styled dialog</Button>,
61
+ title: "Styled dialog",
62
+ description: "Each internal slot can be tweaked via classNames.",
63
+ classNames: {
64
+ backdrop: "bg-primary/30",
65
+ title: "text-primary",
66
+ closeButton: "text-primary",
67
+ },
68
+ },
69
+ };
70
+
53
71
  /**
54
72
  * The primary use case: a dialog containing a form.
55
73
  * `children` renders between the description and the footer.
@@ -11,7 +11,7 @@ import {
11
11
  DialogTitle,
12
12
  DialogTrigger,
13
13
  } from "../../components";
14
- import { click } from "../../utils";
14
+ import { click } from "../../utils/tests";
15
15
 
16
16
  describe("Dialog component", () => {
17
17
  const setup = (
@@ -68,24 +68,31 @@ export function DialogViewport({
68
68
  /**
69
69
  * Dialog content container. Includes Portal, Backdrop and Viewport internally.
70
70
  * Pass `portalProps` to configure the portal (e.g. a custom `container`).
71
- * Pass `closeProps` to customise the close button.
71
+ * `classNames` styles the internal `backdrop`, `viewport` and `closeButton` slots.
72
72
  */
73
73
  export function DialogPopup({
74
74
  className,
75
+ classNames,
75
76
  children,
76
77
  hideClose,
77
- closeProps,
78
78
  portalProps,
79
79
  ...props
80
80
  }: DialogBase.Popup.Props & {
81
81
  hideClose?: boolean;
82
- closeProps?: DialogBase.Close.Props;
83
82
  portalProps?: DialogBase.Portal.Props;
83
+ classNames?: {
84
+ /** Overlay rendered behind the dialog. */
85
+ backdrop?: string;
86
+ /** Full-screen container that centers the popup. */
87
+ viewport?: string;
88
+ /** Close (×) button rendered inside the popup. */
89
+ closeButton?: string;
90
+ };
84
91
  }) {
85
92
  return (
86
93
  <DialogBase.Portal {...portalProps}>
87
- <DialogBackdrop />
88
- <DialogViewport>
94
+ <DialogBackdrop className={classNames?.backdrop} />
95
+ <DialogViewport className={classNames?.viewport}>
89
96
  <DialogBase.Popup
90
97
  data-slot="dialog-popup"
91
98
  className={cn(
@@ -101,9 +108,8 @@ export function DialogPopup({
101
108
  {!hideClose && (
102
109
  <DialogBase.Close
103
110
  aria-label="Close"
104
- className="absolute right-2 top-2"
111
+ className={cn("absolute right-2 top-2", classNames?.closeButton)}
105
112
  render={<Button size="icon" variant="ghost" />}
106
- {...closeProps}
107
113
  >
108
114
  <X className="size-4" />
109
115
  </DialogBase.Close>
@@ -201,12 +207,25 @@ export type DialogProps = Omit<DialogBase.Root.Props, "children"> & {
201
207
  onClose?: () => void;
202
208
  /** Hides the close (×) button inside the popup. */
203
209
  hideClose?: boolean;
204
- /** Props forwarded to the close button primitive. */
205
- closeProps?: DialogBase.Close.Props;
206
- /** Props forwarded to the portal. */
207
- portalProps?: DialogBase.Portal.Props;
208
- /** Additional className forwarded to the popup panel. */
210
+ /** Styles the dialog popup panel. */
209
211
  className?: string;
212
+ /** Styles applied to each internal slot. */
213
+ classNames?: {
214
+ /** Overlay rendered behind the dialog. */
215
+ backdrop?: string;
216
+ /** Full-screen container that centers the popup. */
217
+ viewport?: string;
218
+ /** Layout wrapper for title and description. */
219
+ header?: string;
220
+ /** Dialog heading. */
221
+ title?: string;
222
+ /** Supporting text below the title. */
223
+ description?: string;
224
+ /** Layout wrapper for footer content. */
225
+ footer?: string;
226
+ /** Close (×) button rendered inside the popup. */
227
+ closeButton?: string;
228
+ };
210
229
  };
211
230
 
212
231
  /**
@@ -216,6 +235,10 @@ export type DialogProps = Omit<DialogBase.Root.Props, "children"> & {
216
235
  * making it composable with `<Tooltip>` at the primitive level.
217
236
  * For full structural control, use the exported primitives directly.
218
237
  *
238
+ * `className` styles the popup panel. `classNames` exposes the `backdrop`,
239
+ * `viewport`, `header`, `title`, `description`, `footer`, and `closeButton`
240
+ * slots for fine-grained tweaks without dropping to primitives.
241
+ *
219
242
  * @example
220
243
  * ```tsx
221
244
  * <Dialog
@@ -236,9 +259,8 @@ export function Dialog({
236
259
  footer,
237
260
  onClose,
238
261
  hideClose,
239
- closeProps,
240
- portalProps,
241
262
  className,
263
+ classNames,
242
264
  onOpenChange,
243
265
  ...props
244
266
  }: DialogProps) {
@@ -254,19 +276,28 @@ export function Dialog({
254
276
  <DialogPopup
255
277
  className={className}
256
278
  hideClose={hideClose}
257
- closeProps={closeProps}
258
- portalProps={portalProps}
279
+ classNames={{
280
+ backdrop: classNames?.backdrop,
281
+ viewport: classNames?.viewport,
282
+ closeButton: classNames?.closeButton,
283
+ }}
259
284
  >
260
285
  {(title || description) && (
261
- <DialogHeader>
262
- {title && <DialogTitle>{title}</DialogTitle>}
286
+ <DialogHeader className={classNames?.header}>
287
+ {title && (
288
+ <DialogTitle className={classNames?.title}>{title}</DialogTitle>
289
+ )}
263
290
  {description && (
264
- <DialogDescription>{description}</DialogDescription>
291
+ <DialogDescription className={classNames?.description}>
292
+ {description}
293
+ </DialogDescription>
265
294
  )}
266
295
  </DialogHeader>
267
296
  )}
268
297
  {children}
269
- {footer && <DialogFooter>{footer}</DialogFooter>}
298
+ {footer && (
299
+ <DialogFooter className={classNames?.footer}>{footer}</DialogFooter>
300
+ )}
270
301
  </DialogPopup>
271
302
  </DialogRoot>
272
303
  );
@@ -14,6 +14,12 @@ const meta: Meta<typeof Divider> = {
14
14
  args: {
15
15
  orientation: "horizontal",
16
16
  },
17
+ argTypes: {
18
+ orientation: {
19
+ control: "radio",
20
+ options: ["horizontal", "vertical"],
21
+ },
22
+ },
17
23
  parameters: {
18
24
  layout: "centered",
19
25
  },
@@ -1,36 +1,61 @@
1
- import { Meta, StoryObj } from "@storybook/react-vite";
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { Camera, File, FileArchive, ImageIcon, Trash } from "lucide-react";
3
- import React from "react";
4
3
  import { Button } from "../../components/button";
5
- import { Dropzone } from ".";
6
- import { FileType, FileTypeGroups, FileTypeValue } from "./file-types";
7
4
  import {
8
- UploadAcceptedFiles,
9
- UploadContent,
10
- UploadRejectedFiles,
11
- UploadRoot,
12
- UploadTrigger,
13
- } from "./upload-primitives";
14
- import { FileError } from "./use-dropzone";
5
+ Dropzone,
6
+ DropzoneAcceptedFiles,
7
+ DropzoneContent,
8
+ DropzoneRejectedFiles,
9
+ DropzoneRoot,
10
+ DropzoneTrigger,
11
+ type FileError,
12
+ } from ".";
13
+ import { FileType, FileTypeGroups, type FileTypeValue } from "./file-types";
15
14
 
15
+ /**
16
+ * Drag-and-drop file uploader built on react-dropzone. Supports controlled and uncontrolled
17
+ * file state, MIME type filtering, size limits, and custom file item rendering via render props.
18
+ *
19
+ * Compose with primitives (`DropzoneRoot`, `DropzoneTrigger`, `DropzoneContent`,
20
+ * `DropzoneAcceptedFiles`, `DropzoneRejectedFiles`) for full layout control.
21
+ */
16
22
  const meta: Meta<typeof Dropzone> = {
17
- title: "data entry/Dropzone",
23
+ title: "Components/Dropzone",
18
24
  component: Dropzone,
19
25
  subcomponents: {
20
- UploadRoot: UploadRoot as React.ComponentType<unknown>,
21
- UploadTrigger: UploadTrigger as React.ComponentType<unknown>,
22
- UploadContent: UploadContent as React.ComponentType<unknown>,
23
- UploadAcceptedFiles: UploadAcceptedFiles as React.ComponentType<unknown>,
24
- UploadRejectedFiles: UploadRejectedFiles as React.ComponentType<unknown>,
26
+ DropzoneRoot: DropzoneRoot as React.ComponentType<unknown>,
27
+ DropzoneTrigger: DropzoneTrigger as React.ComponentType<unknown>,
28
+ DropzoneContent: DropzoneContent as React.ComponentType<unknown>,
29
+ DropzoneAcceptedFiles:
30
+ DropzoneAcceptedFiles as React.ComponentType<unknown>,
31
+ DropzoneRejectedFiles:
32
+ DropzoneRejectedFiles as React.ComponentType<unknown>,
33
+ },
34
+ args: {
35
+ allowedExtensions: [...FileTypeGroups.IMAGES, FileType.PDF],
36
+ },
37
+ argTypes: {
38
+ classNames: { control: false },
25
39
  },
40
+ tags: ["beta"],
26
41
  };
27
42
 
28
43
  export default meta;
29
44
  type Story = StoryObj<typeof Dropzone>;
30
45
 
31
- export const Default: Story = {
46
+ export const Default: Story = {};
47
+
48
+ /**
49
+ * `className` styles the outer wrapper. `classNames` exposes the `trigger`,
50
+ * `icon`, `label`, and `content` slots for fine-grained tweaks.
51
+ */
52
+ export const WithClassNames: Story = {
32
53
  args: {
33
- allowedExtensions: [...FileTypeGroups.IMAGES, FileType.PDF],
54
+ classNames: {
55
+ trigger: "border-primary bg-primary/5",
56
+ icon: "text-primary",
57
+ label: "text-primary",
58
+ },
34
59
  },
35
60
  };
36
61
 
@@ -59,60 +84,20 @@ export const MultipleFiles: Story = {
59
84
  };
60
85
 
61
86
  /**
62
- * El callback `onError` te permite manejar errores cuando se rechazan archivos durante la validación.
63
- *
64
- * ### Interfaz FileError
65
- * ```typescript
66
- * type FileError = {
67
- * file: File; // El archivo que causó el error
68
- * errorMessage: string; // Mensaje de error genérico
69
- * errorCode: FileErrorCode; // Código para identificar el tipo de error
70
- * };
71
- * ```
72
- *
73
- * ### Códigos de Error Disponibles
74
- * - `'INVALID_EXTENSION'`: El archivo no tiene una extensión permitida
75
- * - `'FILE_TOO_LARGE'`: El archivo excede el tamaño máximo permitido
76
- * - `'MAX_FILES_EXCEEDED'`: Se ha alcanzado el límite máximo de archivos
77
- *
78
- * ### Ejemplo de Uso
79
- * ```typescript
80
- * onError: (fileErrors) => {
81
- * fileErrors.forEach(({ file, errorCode }) => {
82
- * switch (errorCode) {
83
- * case 'INVALID_EXTENSION':
84
- * toast.error(`${file.name} no es un tipo válido`);
85
- * break;
86
- * case 'FILE_TOO_LARGE':
87
- * toast.error(`${file.name} es muy grande`);
88
- * break;
89
- * case 'MAX_FILES_EXCEEDED':
90
- * toast.error('Demasiados archivos');
91
- * break;
92
- * }
93
- * });
94
- * };
95
- * ```
96
- *
97
- * ### Notas Importantes
98
- * - El `onError` se ejecuta automáticamente cada vez que `validateFiles` encuentra errores
99
- * - Puedes acceder al archivo original para mostrar información personalizada
100
- * - Los mensajes de error son genéricos, puedes crear los tuyos propios basándote en el `errorCode`
87
+ * `onError` fires with a `FileError[]` whenever files are rejected. Each entry
88
+ * includes `errorCode` (`INVALID_EXTENSION`, `FILE_TOO_LARGE`, `MAX_FILES_EXCEEDED`)
89
+ * for granular error handling.
101
90
  */
102
91
  export const WithErrorHandling: Story = {
103
92
  args: {
104
93
  multiple: true,
105
94
  maxFiles: 2,
106
- maxFileSize: 2 * 1024 * 1024, // 2MB
95
+ maxFileSize: 2 * 1024 * 1024,
107
96
  allowedExtensions: [...FileTypeGroups.IMAGES, FileType.PDF],
108
97
  onError: (fileErrors: FileError[]) => {
109
- console.log("Errores de archivos:", fileErrors);
110
98
  alert(
111
99
  fileErrors
112
- .map(
113
- ({ file, errorMessage, errorCode }) =>
114
- `${file.name}: ${errorMessage} (${errorCode})`,
115
- )
100
+ .map(({ file, errorCode }) => `${file.name}: ${errorCode}`)
116
101
  .join("\n"),
117
102
  );
118
103
  },
@@ -174,37 +159,33 @@ export const CustomFilePreview: Story = {
174
159
  };
175
160
 
176
161
  /**
177
- * El componente Dropzone está compuesto por los componentes primitivos UploadRoot, UploadTrigger, UploadContent, UploadAcceptedFiles y UploadRejectedFiles.
162
+ * Use primitives directly for full layout control. `DropzoneTrigger` with `render`
163
+ * turns any element into the drag target and click handler.
178
164
  *
179
- * Este ejemplo muestra como armar el componente Dropzone usando los componentes primitivos.
180
- *
181
- * ##### Estructura
182
165
  * ```tsx
183
- * <UploadRoot>
184
- * <UploadTrigger>
185
- * ...
186
- * </UploadTrigger>
187
- * <UploadContent>
188
- * <UploadAcceptedFiles />
189
- * <UploadRejectedFiles />
190
- * </UploadContent>
191
- * </UploadRoot>
166
+ * <DropzoneRoot>
167
+ * <DropzoneTrigger render={<Button>Upload</Button>} />
168
+ * <DropzoneContent>
169
+ * <DropzoneAcceptedFiles />
170
+ * <DropzoneRejectedFiles />
171
+ * </DropzoneContent>
172
+ * </DropzoneRoot>
192
173
  * ```
193
174
  */
194
- export const Primitives: Story = {
195
- render: () => {
196
- return (
197
- <UploadRoot>
198
- <UploadTrigger asChild>
175
+ export const Primitive: Story = {
176
+ render: () => (
177
+ <DropzoneRoot>
178
+ <DropzoneTrigger
179
+ render={
199
180
  <Button>
200
181
  <Camera /> Upload
201
182
  </Button>
202
- </UploadTrigger>
203
- <UploadContent>
204
- <UploadAcceptedFiles />
205
- <UploadRejectedFiles />
206
- </UploadContent>
207
- </UploadRoot>
208
- );
209
- },
183
+ }
184
+ />
185
+ <DropzoneContent>
186
+ <DropzoneAcceptedFiles />
187
+ <DropzoneRejectedFiles />
188
+ </DropzoneContent>
189
+ </DropzoneRoot>
190
+ ),
210
191
  };