@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
@@ -3,6 +3,7 @@ import { Field as FieldPrimitive } from "@base-ui/react/field";
3
3
  import { ChevronsUpDownIcon, XIcon } from "lucide-react";
4
4
  import * as React from "react";
5
5
  import { cn } from "../../lib";
6
+ import { useIsInsideFieldRoot } from "../field";
6
7
  import { Input, inputBaseClasses } from "../input";
7
8
  import { ScrollArea } from "../scroll-area";
8
9
 
@@ -25,15 +26,12 @@ export function ComboboxRoot<
25
26
  Value,
26
27
  Multiple extends boolean | undefined = false,
27
28
  >({
28
- onChange,
29
29
  ...props
30
- }: Omit<ComboboxPrimitive.Root.Props<Value, Multiple>, "onValueChange"> & {
31
- onChange?: ComboboxPrimitive.Root.Props<Value, Multiple>["onValueChange"];
32
- }): React.ReactElement {
30
+ }: ComboboxPrimitive.Root.Props<Value, Multiple>): React.ReactElement {
33
31
  const chipsRef = React.useRef<Element | null>(null);
34
32
  return (
35
33
  <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}>
36
- <ComboboxPrimitive.Root onValueChange={onChange} {...props} />
34
+ <ComboboxPrimitive.Root {...props} />
37
35
  </ComboboxContext.Provider>
38
36
  );
39
37
  }
@@ -95,12 +93,7 @@ export function ComboboxInput({
95
93
  )}
96
94
  data-slot="combobox-input"
97
95
  render={(props) => (
98
- <Input
99
- className="has-disabled:opacity-100"
100
- nativeInput
101
- {...props}
102
- onChange={(_, event) => props?.onChange?.(event)}
103
- />
96
+ <Input className="has-disabled:opacity-100" nativeInput {...props} />
104
97
  )}
105
98
  {...props}
106
99
  />
@@ -486,37 +479,44 @@ export function ComboboxChips({
486
479
  startAddon?: React.ReactNode;
487
480
  }): React.ReactElement {
488
481
  const { chipsRef } = React.useContext(ComboboxContext);
482
+ const insideField = useIsInsideFieldRoot();
489
483
 
490
- return (
491
- <FieldPrimitive.Validity>
492
- {({ validity }) => (
493
- <ComboboxPrimitive.Chips
494
- aria-invalid={validity.valid === false ? true : undefined}
495
- className={cn(
496
- inputBaseClasses,
497
- "relative inline-flex pr-6 w-full flex-wrap gap-1",
498
- "placeholder:text-muted-foreground",
499
- "focus-within:border-ring",
500
- "aria-invalid:border-error focus-within:aria-invalid:ring-error/20",
501
- "has-disabled:cursor-not-allowed has-disabled:opacity-50",
502
- className,
503
- )}
504
- data-slot="combobox-chips"
505
- ref={chipsRef as React.Ref<HTMLDivElement> | null}
506
- {...props}
484
+ const renderChips = (ariaInvalid: true | undefined) => (
485
+ <ComboboxPrimitive.Chips
486
+ aria-invalid={ariaInvalid}
487
+ className={cn(
488
+ inputBaseClasses,
489
+ "relative inline-flex pr-6 w-full flex-wrap gap-1",
490
+ "placeholder:text-muted-foreground",
491
+ "focus-within:border-ring",
492
+ "aria-invalid:border-error focus-within:aria-invalid:ring-error/20",
493
+ "has-disabled:cursor-not-allowed has-disabled:opacity-50",
494
+ className,
495
+ )}
496
+ data-slot="combobox-chips"
497
+ ref={chipsRef as React.Ref<HTMLDivElement> | null}
498
+ {...props}
499
+ >
500
+ {startAddon && (
501
+ <div
502
+ aria-hidden="true"
503
+ className="flex shrink-0 items-center ps-2 opacity-80 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-ms-0.5 [&_svg]:-me-1.5"
504
+ data-slot="combobox-start-addon"
507
505
  >
508
- {startAddon && (
509
- <div
510
- aria-hidden="true"
511
- className="flex shrink-0 items-center ps-2 opacity-80 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-ms-0.5 [&_svg]:-me-1.5"
512
- data-slot="combobox-start-addon"
513
- >
514
- {startAddon}
515
- </div>
516
- )}
517
- {children}
518
- </ComboboxPrimitive.Chips>
506
+ {startAddon}
507
+ </div>
519
508
  )}
509
+ {children}
510
+ </ComboboxPrimitive.Chips>
511
+ );
512
+
513
+ if (!insideField) return renderChips(undefined);
514
+
515
+ return (
516
+ <FieldPrimitive.Validity>
517
+ {({ validity }) =>
518
+ renderChips(validity.valid === false ? true : undefined)
519
+ }
520
520
  </FieldPrimitive.Validity>
521
521
  );
522
522
  }
@@ -642,11 +642,11 @@ function useChipOverflow(
642
642
  function ComboboxMultipleChipsContent({
643
643
  value,
644
644
  getLabel,
645
- getValue,
645
+ getId,
646
646
  }: {
647
647
  value: unknown[];
648
648
  getLabel: (item: unknown) => string;
649
- getValue: (item: unknown) => string;
649
+ getId: (item: unknown) => string;
650
650
  }): React.ReactElement {
651
651
  const { chipsRef } = React.useContext(ComboboxContext);
652
652
  const { visibleCount, isMeasuring } = useChipOverflow(value.length, chipsRef);
@@ -657,7 +657,7 @@ function ComboboxMultipleChipsContent({
657
657
  return (
658
658
  <>
659
659
  {visibleItems.map((item) => (
660
- <ComboboxChip key={getValue(item)} aria-label={getLabel(item)}>
660
+ <ComboboxChip key={getId(item)} aria-label={getLabel(item)}>
661
661
  {getLabel(item)}
662
662
  </ComboboxChip>
663
663
  ))}
@@ -675,103 +675,117 @@ function ComboboxMultipleChipsContent({
675
675
  function defaultGetLabel(item: unknown): string {
676
676
  if (item == null) return "";
677
677
  if (typeof item === "string" || typeof item === "number") return String(item);
678
- if (typeof item === "object" && "label" in item)
679
- return String((item as Record<string, unknown>).label);
678
+ if (typeof item === "object") {
679
+ const record = item as Record<string, unknown>;
680
+ if ("label" in record) return String(record.label);
681
+ if ("name" in record) return String(record.name);
682
+ if ("title" in record) return String(record.title);
683
+ }
680
684
  return String(item);
681
685
  }
682
686
 
683
- function defaultGetValue(item: unknown): string {
687
+ function defaultGetId(item: unknown): string {
684
688
  if (item == null) return "";
685
689
  if (typeof item === "string" || typeof item === "number") return String(item);
686
- if (typeof item === "object" && "value" in item)
687
- return String((item as Record<string, unknown>).value);
690
+ if (typeof item === "object") {
691
+ const record = item as Record<string, unknown>;
692
+ if ("id" in record) return String(record.id);
693
+ if ("value" in record) return String(record.value);
694
+ }
688
695
  return String(item);
689
696
  }
690
697
 
691
698
  function ComboboxSingleTrigger({
692
699
  placeholder,
693
700
  showClear,
694
- inputProps,
701
+ startAddon,
702
+ className,
695
703
  }: {
696
704
  placeholder?: string;
697
705
  showClear?: boolean;
698
- inputProps?: React.ComponentProps<typeof ComboboxInput>;
706
+ startAddon?: React.ReactNode;
707
+ className?: string;
699
708
  }): React.ReactElement {
700
709
  return (
701
710
  <ComboboxInput
711
+ className={className}
702
712
  placeholder={placeholder}
703
713
  showClear={showClear}
704
- {...inputProps}
714
+ startAddon={startAddon}
705
715
  />
706
716
  );
707
717
  }
708
718
 
709
719
  function ComboboxMultipleTrigger({
710
720
  getLabel,
711
- getValue,
712
- chipsProps,
721
+ getId,
713
722
  showClear,
714
- clearProps,
723
+ startAddon,
724
+ className,
715
725
  }: {
716
726
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
717
727
  getLabel: (item: any) => string;
718
728
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
719
- getValue: (item: any) => string;
720
- chipsProps?: React.ComponentProps<typeof ComboboxChips>;
729
+ getId: (item: any) => string;
721
730
  showClear?: boolean;
722
- clearProps?: ComboboxPrimitive.Clear.Props;
731
+ startAddon?: React.ReactNode;
732
+ className?: string;
723
733
  }): React.ReactElement {
724
734
  return (
725
- <ComboboxChips {...chipsProps}>
735
+ <ComboboxChips className={className} startAddon={startAddon}>
726
736
  <ComboboxValue>
727
737
  {(value: unknown[]) => (
728
738
  <ComboboxMultipleChipsContent
729
739
  value={value}
730
740
  getLabel={getLabel}
731
- getValue={getValue}
741
+ getId={getId}
732
742
  />
733
743
  )}
734
744
  </ComboboxValue>
735
- <ComboboxEndAdornment
736
- showTrigger={!showClear}
737
- showClear={showClear}
738
- clearProps={clearProps}
739
- />
745
+ <ComboboxEndAdornment showTrigger={!showClear} showClear={showClear} />
740
746
  </ComboboxChips>
741
747
  );
742
748
  }
743
749
 
744
- type ComboboxRootPropsAlias<V, M extends boolean | undefined = false> = Omit<
745
- ComboboxPrimitive.Root.Props<V, M>,
746
- "onValueChange"
747
- > & {
748
- onChange?: ComboboxPrimitive.Root.Props<V, M>["onValueChange"];
749
- };
750
-
751
750
  type ComboboxBaseProps<TItem = unknown> = Omit<
752
- ComboboxRootPropsAlias<TItem, boolean>,
751
+ ComboboxPrimitive.Root.Props<TItem, boolean>,
753
752
  | "items"
754
753
  | "itemToStringLabel"
755
754
  | "itemToStringValue"
756
755
  | "children"
757
756
  | "multiple"
758
- | "onChange"
757
+ | "onValueChange"
759
758
  | "value"
760
759
  | "defaultValue"
761
760
  > & {
762
761
  items: readonly TItem[];
762
+ /** Returns the display text for an item. Used for filter matching, ARIA, and the trigger input. Defaults to `item.label`, `item.name`, `item.title`, or the stringified primitive. */
763
763
  getLabel?: (item: TItem) => string;
764
- getValue?: (item: TItem) => string;
764
+ /** Returns a stable string identifier for an item. Used as the React key and as the hidden form value when `name` is set. Defaults to `item.id`, `item.value`, or the stringified primitive. */
765
+ getId?: (item: TItem) => string;
765
766
  renderItem?: (item: TItem) => React.ReactNode;
766
767
  placeholder?: string;
767
768
  emptyText?: string;
768
- inputProps?: React.ComponentProps<typeof ComboboxInput>;
769
- chipsProps?: React.ComponentProps<typeof ComboboxChips>;
770
- chipsInputProps?: React.ComponentProps<typeof ComboboxChipsInput>;
771
- popupProps?: React.ComponentProps<typeof ComboboxPopup>;
772
- itemProps?: ComboboxPrimitive.Item.Props;
773
- listProps?: Omit<ComboboxPrimitive.List.Props, "children">;
769
+ /** Icon or element displayed before the input text (single mode) or chips (multiple mode). */
770
+ startAddon?: React.ReactNode;
774
771
  showClear?: boolean;
772
+ /** Styles applied to each internal slot. */
773
+ classNames?: {
774
+ /** Text input field in single-select mode. */
775
+ input?: string;
776
+ /** Chips container in multiple-select mode. */
777
+ chips?: string;
778
+ /** Search input inside the popup in multiple-select mode. */
779
+ chipsInput?: string;
780
+ /** Popup panel containing the item list. */
781
+ popup?: string;
782
+ /** Scrollable item list inside the popup. */
783
+ list?: string;
784
+ /** Individual item row. */
785
+ item?: string;
786
+ /** Rendered when no items match the search. */
787
+ empty?: string;
788
+ };
775
789
  };
776
790
 
777
791
  export type ComboboxProps<TItem = unknown> =
@@ -779,20 +793,20 @@ export type ComboboxProps<TItem = unknown> =
779
793
  multiple?: false;
780
794
  value?: TItem | null;
781
795
  defaultValue?: TItem | null;
782
- onChange?: (value: TItem | null) => void;
796
+ onValueChange?: (value: TItem | null) => void;
783
797
  })
784
798
  | (ComboboxBaseProps<TItem> & {
785
799
  multiple: true;
786
800
  value?: TItem[];
787
801
  defaultValue?: TItem[];
788
- onChange?: (value: TItem[]) => void;
802
+ onValueChange?: (value: TItem[]) => void;
789
803
  });
790
804
 
791
805
  /**
792
806
  * Composite combobox for single and multiple selection.
793
807
  *
794
- * Items shaped as `{ label, value }` or plain strings/numbers work with no
795
- * extra props. For other shapes, provide `getLabel` and/or `getValue`.
808
+ * Items shaped as `{ id, name }`, `{ label, value }`, or plain strings/numbers
809
+ * work with no extra props. For other shapes, provide `getLabel` and/or `getId`.
796
810
  *
797
811
  * In multiple mode, chips are fit dynamically based on the container width —
798
812
  * overflowing items show as "+N más". The popup anchors to the chips container automatically.
@@ -812,39 +826,35 @@ export function Combobox<TItem = unknown>(
812
826
  const {
813
827
  items,
814
828
  getLabel: getLabelProp,
815
- getValue: getValueProp,
829
+ getId: getIdProp,
816
830
  renderItem,
817
831
  placeholder,
818
832
  emptyText = "Sin resultados.",
819
833
  multiple,
820
- onChange,
834
+ onValueChange,
821
835
  value,
822
836
  defaultValue,
823
- inputProps,
824
- chipsProps,
825
- chipsInputProps,
826
- popupProps,
827
- itemProps,
828
- listProps,
837
+ startAddon,
838
+ classNames,
829
839
  showClear = true,
830
840
  ...rest
831
841
  } = allProps as ComboboxBaseProps<TItem> & {
832
842
  multiple?: boolean;
833
- onChange?: (value: any, eventDetails?: any) => void;
843
+ onValueChange?: (value: any, eventDetails?: any) => void;
834
844
  value?: any;
835
845
  defaultValue?: any;
836
846
  };
837
847
 
838
848
  const getLabel: (item: TItem) => string = getLabelProp ?? defaultGetLabel;
839
- const getValue: (item: TItem) => string = getValueProp ?? defaultGetValue;
849
+ const getId: (item: TItem) => string = getIdProp ?? defaultGetId;
840
850
 
841
851
  return (
842
852
  <ComboboxRoot
843
853
  items={items}
844
854
  itemToStringLabel={getLabel}
845
- itemToStringValue={getValue}
855
+ itemToStringValue={getId}
846
856
  multiple={multiple as boolean}
847
- onChange={onChange}
857
+ onValueChange={onValueChange}
848
858
  value={value}
849
859
  defaultValue={defaultValue}
850
860
  autoHighlight
@@ -854,33 +864,39 @@ export function Combobox<TItem = unknown>(
854
864
  {multiple ? (
855
865
  <ComboboxMultipleTrigger
856
866
  getLabel={getLabel}
857
- getValue={getValue}
858
- chipsProps={chipsProps}
867
+ getId={getId}
859
868
  showClear={showClear}
869
+ startAddon={startAddon}
870
+ className={classNames?.chips}
860
871
  />
861
872
  ) : (
862
873
  <ComboboxSingleTrigger
863
874
  placeholder={placeholder}
864
875
  showClear={showClear}
865
- inputProps={inputProps}
876
+ startAddon={startAddon}
877
+ className={classNames?.input}
866
878
  />
867
879
  )}
868
- <ComboboxPopup {...popupProps}>
880
+ <ComboboxPopup className={classNames?.popup}>
869
881
  {multiple && (
870
882
  <div
871
883
  className="border-b p-2"
872
884
  data-slot="combobox-chips-input-wrapper"
873
885
  >
874
886
  <ComboboxChipsInput
887
+ className={classNames?.chipsInput}
875
888
  placeholder={placeholder}
876
- {...chipsInputProps}
877
889
  />
878
890
  </div>
879
891
  )}
880
- <ComboboxEmpty>{emptyText}</ComboboxEmpty>
881
- <ComboboxList {...listProps}>
892
+ <ComboboxEmpty className={classNames?.empty}>{emptyText}</ComboboxEmpty>
893
+ <ComboboxList className={classNames?.list}>
882
894
  {(item: TItem) => (
883
- <ComboboxItem key={getValue(item)} value={item} {...itemProps}>
895
+ <ComboboxItem
896
+ key={getId(item)}
897
+ value={item}
898
+ className={classNames?.item}
899
+ >
884
900
  {renderItem ? renderItem(item) : getLabel(item)}
885
901
  </ComboboxItem>
886
902
  )}
@@ -23,12 +23,20 @@ import { DateInput } from "./date-input";
23
23
  const meta: Meta<typeof DateInput> = {
24
24
  title: "Components/DateInput",
25
25
  component: DateInput,
26
+ tags: ["new"],
26
27
  parameters: { layout: "centered" },
28
+ decorators: [
29
+ (Story) => (
30
+ <div className="w-72">
31
+ <Story />
32
+ </div>
33
+ ),
34
+ ],
27
35
  args: {
28
- onChange: action("onChange"),
36
+ onValueChange: action("onValueChange"),
29
37
  },
30
38
  argTypes: {
31
- onChange: { control: false },
39
+ onValueChange: { control: false },
32
40
  onBlur: { control: false },
33
41
  disabledDate: { control: false },
34
42
  renderFooter: { control: false },
@@ -42,7 +50,7 @@ export const Default: Story = {};
42
50
 
43
51
  /**
44
52
  * Pass `defaultValue` for one-shot initialisation without managing state.
45
- * Use `value` + `onChange` for fully controlled usage.
53
+ * Use `value` + `onValueChange` for fully controlled usage.
46
54
  */
47
55
  export const WithDefaultValue: Story = {
48
56
  args: {
@@ -80,7 +88,7 @@ export const DisabledDates: Story = {
80
88
  export const WithFooter: Story = {
81
89
  render: () => (
82
90
  <DateInput
83
- onChange={action("onChange")}
91
+ onValueChange={action("onValueChange")}
84
92
  renderFooter={({ value, clear, selectToday, setValue }) => (
85
93
  <div className="flex flex-col gap-2">
86
94
  <Button variant="outline" size="sm" onClick={selectToday}>
@@ -115,7 +123,7 @@ export const WithFooter: Story = {
115
123
  };
116
124
 
117
125
  /**
118
- * `value` + `onChange` make the field fully controlled. Updating `value`
126
+ * `value` + `onValueChange` make the field fully controlled. Updating `value`
119
127
  * externally always syncs the text input and the calendar selection.
120
128
  */
121
129
  export const Controlled: Story = {
@@ -124,7 +132,7 @@ export const Controlled: Story = {
124
132
 
125
133
  return (
126
134
  <div className="flex flex-col gap-4">
127
- <DateInput value={date} onChange={setDate} />
135
+ <DateInput value={date} onValueChange={setDate} />
128
136
  <p className="text-sm text-muted-foreground">
129
137
  {date ? date.toLocaleDateString() : "No selection"}
130
138
  </p>
@@ -19,7 +19,7 @@ export const DateInput = (props: DateInputProps) => {
19
19
  const {
20
20
  placeholder,
21
21
  value: valueProp,
22
- onChange,
22
+ onValueChange,
23
23
  defaultValue: defaultValueProp,
24
24
  autoComplete,
25
25
  className,
@@ -32,7 +32,7 @@ export const DateInput = (props: DateInputProps) => {
32
32
 
33
33
  const [selectedDate, setSelectedDate] = useControllableState<Date | null>({
34
34
  prop: valueProp,
35
- onChange: (nextValue) => onChange?.(nextValue ?? null),
35
+ onChange: (nextValue) => onValueChange?.(nextValue ?? null),
36
36
  defaultProp: defaultValueProp ?? null,
37
37
  });
38
38
 
@@ -40,13 +40,22 @@ type DatePickerBaseProps = {
40
40
  renderFooter?: (props: DatePickerFooterProps) => ReactNode;
41
41
  className?: string;
42
42
  required?: boolean;
43
+ /** Styles applied to each internal slot. */
44
+ classNames?: {
45
+ /** Popup panel that opens below the trigger. */
46
+ popup?: string;
47
+ /** Calendar grid inside the popup. */
48
+ calendar?: string;
49
+ /** Footer wrapper rendered below the calendar when `renderFooter` is provided. */
50
+ footer?: string;
51
+ };
43
52
  };
44
53
 
45
54
  export type SingleDatePickerProps = DatePickerBaseProps & {
46
55
  mode: "single";
47
56
  value?: Date | null;
48
57
  defaultValue?: Date | null;
49
- onChange?: (value: Date | null) => void;
58
+ onValueChange?: (value: Date | null) => void;
50
59
  placeholder?: string;
51
60
  };
52
61
 
@@ -54,7 +63,7 @@ export type RangeDatePickerProps = DatePickerBaseProps & {
54
63
  mode: "range";
55
64
  value?: DateRange;
56
65
  defaultValue?: DateRange;
57
- onChange?: (value: DateRange) => void;
66
+ onValueChange?: (value: DateRange) => void;
58
67
  placeholder?: string;
59
68
  };
60
69
 
@@ -62,7 +71,7 @@ export type MultipleDatePickerProps = DatePickerBaseProps & {
62
71
  mode: "multiple";
63
72
  value?: Date[];
64
73
  defaultValue?: Date[];
65
- onChange?: (value: Date[]) => void;
74
+ onValueChange?: (value: Date[]) => void;
66
75
  placeholder?: string;
67
76
  };
68
77
 
@@ -84,7 +93,7 @@ export type DateInputProps = Omit<
84
93
  > & {
85
94
  value?: Date | null;
86
95
  defaultValue?: Date | null;
87
- onChange?: (value: Date | null) => void;
96
+ onValueChange?: (value: Date | null) => void;
88
97
  disabledDate?: (date: Date) => boolean;
89
98
  renderFooter?: (props: DateInputFooterProps) => ReactNode;
90
99
  };
@@ -14,7 +14,7 @@ import type { DatePickerFooterProps, DateRange } from "./date-picker.model";
14
14
  * - `"range"` — selects `DateRange = { start: Date | null; end: Date | null }`
15
15
  * - `"multiple"` — selects `Date[]`
16
16
  *
17
- * The types of `value`, `defaultValue`, and `onChange` are **inferred from `mode`** —
17
+ * The types of `value`, `defaultValue`, and `onValueChange` are **inferred from `mode`** —
18
18
  * TypeScript will error if `mode="range"` is paired with a `Date | null` value.
19
19
  *
20
20
  * `renderFooter` receives a mode-discriminated prop bag. Always narrow `mode` before
@@ -24,16 +24,28 @@ const meta: Meta<typeof DatePicker> = {
24
24
  title: "Components/DatePicker",
25
25
  component: DatePicker,
26
26
  parameters: { layout: "centered" },
27
+ decorators: [
28
+ (Story) => (
29
+ <div className="w-72">
30
+ <Story />
31
+ </div>
32
+ ),
33
+ ],
27
34
  args: {
28
35
  mode: "single",
29
- onChange: action("onChange"),
36
+ onValueChange: action("onValueChange"),
30
37
  },
31
38
  argTypes: {
32
- onChange: { control: false },
39
+ mode: {
40
+ control: { type: "radio" },
41
+ options: ["single", "range", "multiple"],
42
+ },
43
+ onValueChange: { control: false },
33
44
  disabledDate: { control: false },
34
45
  renderFooter: { control: false },
35
46
  value: { control: false },
36
47
  defaultValue: { control: false },
48
+ classNames: { control: false },
37
49
  },
38
50
  };
39
51
 
@@ -42,9 +54,23 @@ type Story = StoryObj<typeof DatePicker>;
42
54
 
43
55
  export const Default: Story = {};
44
56
 
57
+ /**
58
+ * `className` styles the trigger button. `classNames` exposes the
59
+ * `popup`, `calendar`, and `footer` slots.
60
+ */
61
+ export const WithClassNames: Story = {
62
+ args: {
63
+ className: "w-60",
64
+ classNames: {
65
+ popup: "shadow-lg",
66
+ calendar: "p-3",
67
+ },
68
+ },
69
+ };
70
+
45
71
  /**
46
72
  * Pass `defaultValue` for one-shot initialisation without managing state.
47
- * Use `value` + `onChange` for fully controlled usage.
73
+ * Use `value` + `onValueChange` for fully controlled usage.
48
74
  */
49
75
  export const WithDefaultValue: Story = {
50
76
  args: {
@@ -69,22 +95,22 @@ export const DisabledDates: Story = {
69
95
  /**
70
96
  * Selects a contiguous date range. The first click sets `start`, the second
71
97
  * sets `end`. A third click resets `start` and begins a new range.
72
- * `value` and `onChange` use `DateRange = { start: Date | null; end: Date | null }`.
98
+ * `value` and `onValueChange` use `DateRange = { start: Date | null; end: Date | null }`.
73
99
  */
74
100
  export const Range: Story = {
75
- args: { mode: "range", onChange: action("onChange") },
101
+ args: { mode: "range", onValueChange: action("onValueChange") },
76
102
  };
77
103
 
78
104
  /**
79
105
  * Selects multiple non-contiguous dates. Each click toggles the day on or off.
80
- * `value` and `onChange` use `Date[]`.
106
+ * `value` and `onValueChange` use `Date[]`.
81
107
  */
82
108
  export const Multiple: Story = {
83
- args: { mode: "multiple", onChange: action("onChange") },
109
+ args: { mode: "multiple", onValueChange: action("onValueChange") },
84
110
  };
85
111
 
86
112
  /**
87
- * `value` + `onChange` make the picker fully controlled. Setting `value`
113
+ * `value` + `onValueChange` make the picker fully controlled. Setting `value`
88
114
  * externally always updates the trigger label.
89
115
  */
90
116
  export const Controlled: Story = {
@@ -93,7 +119,7 @@ export const Controlled: Story = {
93
119
 
94
120
  return (
95
121
  <div className="flex flex-col gap-4">
96
- <DatePicker mode="single" value={date} onChange={setDate} />
122
+ <DatePicker mode="single" value={date} onValueChange={setDate} />
97
123
  <p className="text-sm text-muted-foreground">
98
124
  {date ? date.toLocaleDateString() : "No selection"}
99
125
  </p>
@@ -116,7 +142,7 @@ export const ControlledRange: Story = {
116
142
 
117
143
  return (
118
144
  <div className="flex flex-col gap-4">
119
- <DatePicker mode="range" value={range} onChange={setRange} />
145
+ <DatePicker mode="range" value={range} onValueChange={setRange} />
120
146
  <p className="text-sm text-muted-foreground">
121
147
  {range.start?.toLocaleDateString() ?? "—"} →{" "}
122
148
  {range.end?.toLocaleDateString() ?? "—"}
@@ -135,7 +161,7 @@ export const ControlledMultiple: Story = {
135
161
 
136
162
  return (
137
163
  <div className="flex flex-col gap-4">
138
- <DatePicker mode="multiple" value={dates} onChange={setDates} />
164
+ <DatePicker mode="multiple" value={dates} onValueChange={setDates} />
139
165
  <p className="text-sm text-muted-foreground">
140
166
  {dates.length > 0
141
167
  ? dates.map((d) => d.toLocaleDateString()).join(", ")