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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1087 -720
  3. package/dist/index.es.js +7011 -56097
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Examples.tsx +1 -1
  7. package/src/__doc__/Intro.mdx +3 -3
  8. package/src/__doc__/Tabs.mdx +112 -0
  9. package/src/__doc__/V2.mdx +1246 -0
  10. package/src/components/accordion/accordion.stories.tsx +143 -0
  11. package/src/components/accordion/accordion.tsx +135 -0
  12. package/src/components/accordion/index.ts +1 -0
  13. package/src/components/alert/alert.stories.tsx +24 -4
  14. package/src/components/alert/alert.tsx +17 -9
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  17. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  18. package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
  19. package/src/components/auto-complete/auto-complete.tsx +420 -68
  20. package/src/components/auto-complete/index.ts +0 -1
  21. package/src/components/avatar/avatar.stories.tsx +162 -21
  22. package/src/components/avatar/avatar.tsx +79 -20
  23. package/src/components/button/button.stories.tsx +219 -294
  24. package/src/components/button/button.test.tsx +10 -17
  25. package/src/components/button/button.tsx +78 -19
  26. package/src/components/button/components/base-button.tsx +30 -53
  27. package/src/components/button/index.ts +0 -1
  28. package/src/components/calendar/calendar.stories.tsx +1 -1
  29. package/src/components/calendar/calendar.tsx +4 -4
  30. package/src/components/card/card.stories.tsx +141 -69
  31. package/src/components/card/card.tsx +155 -54
  32. package/src/components/center/center.stories.tsx +22 -39
  33. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  34. package/src/components/checkbox/checkbox.tsx +76 -15
  35. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  36. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  37. package/src/components/combobox/combobox.stories.tsx +33 -23
  38. package/src/components/combobox/combobox.tsx +99 -77
  39. package/src/components/date-picker/date-input.stories.tsx +14 -6
  40. package/src/components/date-picker/date-input.tsx +2 -2
  41. package/src/components/date-picker/date-picker.model.ts +13 -4
  42. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  43. package/src/components/date-picker/date-picker.tsx +28 -14
  44. package/src/components/dialog/dialog.stories.tsx +18 -0
  45. package/src/components/dialog/dialog.test.tsx +1 -1
  46. package/src/components/dialog/dialog.tsx +51 -20
  47. package/src/components/divider/divider.stories.tsx +126 -51
  48. package/src/components/divider/divider.tsx +16 -16
  49. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  50. package/src/components/dropzone/dropzone.tsx +383 -105
  51. package/src/components/dropzone/index.ts +0 -1
  52. package/src/components/empty/empty.stories.tsx +165 -0
  53. package/src/components/empty/empty.tsx +156 -0
  54. package/src/components/empty/index.ts +1 -0
  55. package/src/components/field/field.stories.tsx +227 -4
  56. package/src/components/field/field.tsx +77 -42
  57. package/src/components/form/form.stories.tsx +320 -197
  58. package/src/components/form/form.tsx +3 -23
  59. package/src/components/index.ts +2 -6
  60. package/src/components/input/input.stories.tsx +5 -5
  61. package/src/components/input/input.tsx +4 -4
  62. package/src/components/kbd/kbd.stories.tsx +1 -0
  63. package/src/components/label/label.stories.tsx +16 -0
  64. package/src/components/label/label.tsx +13 -2
  65. package/src/components/loader/loader.stories.tsx +7 -5
  66. package/src/components/loader/loader.tsx +8 -3
  67. package/src/components/menu/menu-primitives.tsx +207 -196
  68. package/src/components/menu/menu.stories.tsx +276 -146
  69. package/src/components/menu/menu.tsx +146 -54
  70. package/src/components/number-input/number-input.stories.tsx +27 -4
  71. package/src/components/number-input/number-input.test.tsx +2 -2
  72. package/src/components/number-input/number-input.tsx +31 -33
  73. package/src/components/otp/index.ts +1 -0
  74. package/src/components/otp/otp.stories.tsx +209 -0
  75. package/src/components/otp/otp.tsx +100 -0
  76. package/src/components/pagination/index.ts +1 -0
  77. package/src/components/pagination/pagination.model.ts +2 -0
  78. package/src/components/pagination/pagination.stories.tsx +154 -59
  79. package/src/components/pagination/pagination.test.tsx +122 -57
  80. package/src/components/pagination/pagination.tsx +575 -77
  81. package/src/components/password/password.stories.tsx +18 -3
  82. package/src/components/password/password.tsx +29 -9
  83. package/src/components/popover/popover.stories.tsx +26 -5
  84. package/src/components/popover/popover.tsx +15 -23
  85. package/src/components/progress/progress.stories.tsx +1 -0
  86. package/src/components/radio-group/index.ts +1 -0
  87. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  88. package/src/components/radio-group/radio-group.tsx +212 -0
  89. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  90. package/src/components/select/select.stories.tsx +118 -19
  91. package/src/components/select/select.tsx +67 -62
  92. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  93. package/src/components/stack/stack.stories.tsx +179 -89
  94. package/src/components/stack/stack.tsx +2 -2
  95. package/src/components/stepper/index.ts +1 -1
  96. package/src/components/stepper/stepper.stories.tsx +767 -83
  97. package/src/components/stepper/stepper.test.tsx +18 -18
  98. package/src/components/stepper/stepper.tsx +554 -0
  99. package/src/components/switch/switch.stories.tsx +15 -1
  100. package/src/components/switch/switch.tsx +17 -4
  101. package/src/components/table/index.ts +0 -2
  102. package/src/components/table/table.stories.tsx +131 -18
  103. package/src/components/table/table.test.tsx +1 -1
  104. package/src/components/table/table.tsx +183 -77
  105. package/src/components/tabs/tabs.stories.tsx +373 -155
  106. package/src/components/tabs/tabs.test.tsx +12 -12
  107. package/src/components/tabs/tabs.tsx +72 -149
  108. package/src/components/tag/index.ts +0 -1
  109. package/src/components/tag/tag.stories.tsx +155 -120
  110. package/src/components/tag/tag.tsx +47 -95
  111. package/src/components/textarea/textarea.stories.tsx +8 -22
  112. package/src/components/textarea/textarea.tsx +17 -79
  113. package/src/components/timeline/timeline.stories.tsx +323 -42
  114. package/src/components/timeline/timeline.tsx +359 -132
  115. package/src/components/toast/toast.stories.tsx +1 -0
  116. package/src/components/tooltip/tooltip.tsx +11 -9
  117. package/src/components/tree/index.ts +0 -1
  118. package/src/components/tree/tree.stories.tsx +365 -408
  119. package/src/components/tree/tree.test.tsx +163 -0
  120. package/src/components/tree/tree.tsx +212 -36
  121. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  122. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  123. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  124. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  125. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  126. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  127. package/src/hooks/usePagination/usePagination.tsx +36 -24
  128. package/src/styles/theme.css +1 -1
  129. package/src/utils/form.tsx +67 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -475
  132. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  133. package/src/components/background-image/background-image.stories.tsx +0 -21
  134. package/src/components/background-image/background-image.test.tsx +0 -29
  135. package/src/components/background-image/background-image.tsx +0 -23
  136. package/src/components/background-image/index.ts +0 -1
  137. package/src/components/button/button.variants.ts +0 -44
  138. package/src/components/button/components/loader-overlay.tsx +0 -21
  139. package/src/components/button/components/loading-icon.tsx +0 -47
  140. package/src/components/dropzone/upload-primitives.tsx +0 -310
  141. package/src/components/dropzone/use-dropzone.ts +0 -122
  142. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  143. package/src/components/empty-state/empty-state.tsx +0 -39
  144. package/src/components/empty-state/index.ts +0 -1
  145. package/src/components/heading/heading.stories.tsx +0 -74
  146. package/src/components/heading/heading.tsx +0 -28
  147. package/src/components/heading/heading.variants.ts +0 -27
  148. package/src/components/heading/index.ts +0 -1
  149. package/src/components/kbd/kbd.variants.ts +0 -26
  150. package/src/components/menu/util/render-menu-item.tsx +0 -54
  151. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  152. package/src/components/multi-select/index.ts +0 -1
  153. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  154. package/src/components/multi-select/multi-select.tsx +0 -300
  155. package/src/components/multi-select/multi-select.variants.ts +0 -22
  156. package/src/components/pagination/components/pagination-option.tsx +0 -27
  157. package/src/components/show/index.ts +0 -1
  158. package/src/components/show/show.stories.tsx +0 -197
  159. package/src/components/show/show.test.tsx +0 -41
  160. package/src/components/show/show.tsx +0 -16
  161. package/src/components/stepper/Stepper.tsx +0 -190
  162. package/src/components/stepper/context/stepper-context.tsx +0 -11
  163. package/src/components/table/table-primitives.tsx +0 -122
  164. package/src/components/table/table.model.ts +0 -20
  165. package/src/components/table-pagination/index.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  167. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  168. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  169. package/src/components/table-pagination/table-pagination.tsx +0 -108
  170. package/src/components/tabs/context/tabs-context.tsx +0 -14
  171. package/src/components/tag/tag.variants.ts +0 -31
  172. package/src/components/timeline/timeline-status.ts +0 -5
  173. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  174. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,7 +1,9 @@
1
1
  import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";
2
+ import { Field as FieldPrimitive } from "@base-ui/react/field";
2
3
  import { ChevronsUpDownIcon, XIcon } from "lucide-react";
3
4
  import * as React from "react";
4
5
  import { cn } from "../../lib";
6
+ import { useIsInsideFieldRoot } from "../field";
5
7
  import { Input, inputBaseClasses } from "../input";
6
8
  import { ScrollArea } from "../scroll-area";
7
9
 
@@ -24,15 +26,12 @@ export function ComboboxRoot<
24
26
  Value,
25
27
  Multiple extends boolean | undefined = false,
26
28
  >({
27
- onChange,
28
29
  ...props
29
- }: Omit<ComboboxPrimitive.Root.Props<Value, Multiple>, "onValueChange"> & {
30
- onChange?: ComboboxPrimitive.Root.Props<Value, Multiple>["onValueChange"];
31
- }): React.ReactElement {
30
+ }: ComboboxPrimitive.Root.Props<Value, Multiple>): React.ReactElement {
32
31
  const chipsRef = React.useRef<Element | null>(null);
33
32
  return (
34
33
  <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}>
35
- <ComboboxPrimitive.Root onValueChange={onChange} {...props} />
34
+ <ComboboxPrimitive.Root {...props} />
36
35
  </ComboboxContext.Provider>
37
36
  );
38
37
  }
@@ -94,12 +93,7 @@ export function ComboboxInput({
94
93
  )}
95
94
  data-slot="combobox-input"
96
95
  render={(props) => (
97
- <Input
98
- className="has-disabled:opacity-100"
99
- nativeInput
100
- {...props}
101
- onChange={(_, event) => props?.onChange?.(event)}
102
- />
96
+ <Input className="has-disabled:opacity-100" nativeInput {...props} />
103
97
  )}
104
98
  {...props}
105
99
  />
@@ -485,15 +479,17 @@ export function ComboboxChips({
485
479
  startAddon?: React.ReactNode;
486
480
  }): React.ReactElement {
487
481
  const { chipsRef } = React.useContext(ComboboxContext);
482
+ const insideField = useIsInsideFieldRoot();
488
483
 
489
- return (
484
+ const renderChips = (ariaInvalid: true | undefined) => (
490
485
  <ComboboxPrimitive.Chips
486
+ aria-invalid={ariaInvalid}
491
487
  className={cn(
492
488
  inputBaseClasses,
493
489
  "relative inline-flex pr-6 w-full flex-wrap gap-1",
494
490
  "placeholder:text-muted-foreground",
495
491
  "focus-within:border-ring",
496
- "has-aria-invalid:border-error focus-within:has-aria-invalid:ring-error/20",
492
+ "aria-invalid:border-error focus-within:aria-invalid:ring-error/20",
497
493
  "has-disabled:cursor-not-allowed has-disabled:opacity-50",
498
494
  className,
499
495
  )}
@@ -513,6 +509,16 @@ export function ComboboxChips({
513
509
  {children}
514
510
  </ComboboxPrimitive.Chips>
515
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
+ </FieldPrimitive.Validity>
521
+ );
516
522
  }
517
523
 
518
524
  export function ComboboxChip({
@@ -636,11 +642,11 @@ function useChipOverflow(
636
642
  function ComboboxMultipleChipsContent({
637
643
  value,
638
644
  getLabel,
639
- getValue,
645
+ getId,
640
646
  }: {
641
647
  value: unknown[];
642
648
  getLabel: (item: unknown) => string;
643
- getValue: (item: unknown) => string;
649
+ getId: (item: unknown) => string;
644
650
  }): React.ReactElement {
645
651
  const { chipsRef } = React.useContext(ComboboxContext);
646
652
  const { visibleCount, isMeasuring } = useChipOverflow(value.length, chipsRef);
@@ -651,7 +657,7 @@ function ComboboxMultipleChipsContent({
651
657
  return (
652
658
  <>
653
659
  {visibleItems.map((item) => (
654
- <ComboboxChip key={getValue(item)} aria-label={getLabel(item)}>
660
+ <ComboboxChip key={getId(item)} aria-label={getLabel(item)}>
655
661
  {getLabel(item)}
656
662
  </ComboboxChip>
657
663
  ))}
@@ -669,103 +675,117 @@ function ComboboxMultipleChipsContent({
669
675
  function defaultGetLabel(item: unknown): string {
670
676
  if (item == null) return "";
671
677
  if (typeof item === "string" || typeof item === "number") return String(item);
672
- if (typeof item === "object" && "label" in item)
673
- 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
+ }
674
684
  return String(item);
675
685
  }
676
686
 
677
- function defaultGetValue(item: unknown): string {
687
+ function defaultGetId(item: unknown): string {
678
688
  if (item == null) return "";
679
689
  if (typeof item === "string" || typeof item === "number") return String(item);
680
- if (typeof item === "object" && "value" in item)
681
- 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
+ }
682
695
  return String(item);
683
696
  }
684
697
 
685
698
  function ComboboxSingleTrigger({
686
699
  placeholder,
687
700
  showClear,
688
- inputProps,
701
+ startAddon,
702
+ className,
689
703
  }: {
690
704
  placeholder?: string;
691
705
  showClear?: boolean;
692
- inputProps?: React.ComponentProps<typeof ComboboxInput>;
706
+ startAddon?: React.ReactNode;
707
+ className?: string;
693
708
  }): React.ReactElement {
694
709
  return (
695
710
  <ComboboxInput
711
+ className={className}
696
712
  placeholder={placeholder}
697
713
  showClear={showClear}
698
- {...inputProps}
714
+ startAddon={startAddon}
699
715
  />
700
716
  );
701
717
  }
702
718
 
703
719
  function ComboboxMultipleTrigger({
704
720
  getLabel,
705
- getValue,
706
- chipsProps,
721
+ getId,
707
722
  showClear,
708
- clearProps,
723
+ startAddon,
724
+ className,
709
725
  }: {
710
726
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
711
727
  getLabel: (item: any) => string;
712
728
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
713
- getValue: (item: any) => string;
714
- chipsProps?: React.ComponentProps<typeof ComboboxChips>;
729
+ getId: (item: any) => string;
715
730
  showClear?: boolean;
716
- clearProps?: ComboboxPrimitive.Clear.Props;
731
+ startAddon?: React.ReactNode;
732
+ className?: string;
717
733
  }): React.ReactElement {
718
734
  return (
719
- <ComboboxChips {...chipsProps}>
735
+ <ComboboxChips className={className} startAddon={startAddon}>
720
736
  <ComboboxValue>
721
737
  {(value: unknown[]) => (
722
738
  <ComboboxMultipleChipsContent
723
739
  value={value}
724
740
  getLabel={getLabel}
725
- getValue={getValue}
741
+ getId={getId}
726
742
  />
727
743
  )}
728
744
  </ComboboxValue>
729
- <ComboboxEndAdornment
730
- showTrigger={!showClear}
731
- showClear={showClear}
732
- clearProps={clearProps}
733
- />
745
+ <ComboboxEndAdornment showTrigger={!showClear} showClear={showClear} />
734
746
  </ComboboxChips>
735
747
  );
736
748
  }
737
749
 
738
- type ComboboxRootPropsAlias<V, M extends boolean | undefined = false> = Omit<
739
- ComboboxPrimitive.Root.Props<V, M>,
740
- "onValueChange"
741
- > & {
742
- onChange?: ComboboxPrimitive.Root.Props<V, M>["onValueChange"];
743
- };
744
-
745
750
  type ComboboxBaseProps<TItem = unknown> = Omit<
746
- ComboboxRootPropsAlias<TItem, boolean>,
751
+ ComboboxPrimitive.Root.Props<TItem, boolean>,
747
752
  | "items"
748
753
  | "itemToStringLabel"
749
754
  | "itemToStringValue"
750
755
  | "children"
751
756
  | "multiple"
752
- | "onChange"
757
+ | "onValueChange"
753
758
  | "value"
754
759
  | "defaultValue"
755
760
  > & {
756
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. */
757
763
  getLabel?: (item: TItem) => string;
758
- 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;
759
766
  renderItem?: (item: TItem) => React.ReactNode;
760
767
  placeholder?: string;
761
768
  emptyText?: string;
762
- inputProps?: React.ComponentProps<typeof ComboboxInput>;
763
- chipsProps?: React.ComponentProps<typeof ComboboxChips>;
764
- chipsInputProps?: React.ComponentProps<typeof ComboboxChipsInput>;
765
- popupProps?: React.ComponentProps<typeof ComboboxPopup>;
766
- itemProps?: ComboboxPrimitive.Item.Props;
767
- 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;
768
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
+ };
769
789
  };
770
790
 
771
791
  export type ComboboxProps<TItem = unknown> =
@@ -773,20 +793,20 @@ export type ComboboxProps<TItem = unknown> =
773
793
  multiple?: false;
774
794
  value?: TItem | null;
775
795
  defaultValue?: TItem | null;
776
- onChange?: (value: TItem | null) => void;
796
+ onValueChange?: (value: TItem | null) => void;
777
797
  })
778
798
  | (ComboboxBaseProps<TItem> & {
779
799
  multiple: true;
780
800
  value?: TItem[];
781
801
  defaultValue?: TItem[];
782
- onChange?: (value: TItem[]) => void;
802
+ onValueChange?: (value: TItem[]) => void;
783
803
  });
784
804
 
785
805
  /**
786
806
  * Composite combobox for single and multiple selection.
787
807
  *
788
- * Items shaped as `{ label, value }` or plain strings/numbers work with no
789
- * 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`.
790
810
  *
791
811
  * In multiple mode, chips are fit dynamically based on the container width —
792
812
  * overflowing items show as "+N más". The popup anchors to the chips container automatically.
@@ -806,39 +826,35 @@ export function Combobox<TItem = unknown>(
806
826
  const {
807
827
  items,
808
828
  getLabel: getLabelProp,
809
- getValue: getValueProp,
829
+ getId: getIdProp,
810
830
  renderItem,
811
831
  placeholder,
812
832
  emptyText = "Sin resultados.",
813
833
  multiple,
814
- onChange,
834
+ onValueChange,
815
835
  value,
816
836
  defaultValue,
817
- inputProps,
818
- chipsProps,
819
- chipsInputProps,
820
- popupProps,
821
- itemProps,
822
- listProps,
837
+ startAddon,
838
+ classNames,
823
839
  showClear = true,
824
840
  ...rest
825
841
  } = allProps as ComboboxBaseProps<TItem> & {
826
842
  multiple?: boolean;
827
- onChange?: (value: any, eventDetails?: any) => void;
843
+ onValueChange?: (value: any, eventDetails?: any) => void;
828
844
  value?: any;
829
845
  defaultValue?: any;
830
846
  };
831
847
 
832
848
  const getLabel: (item: TItem) => string = getLabelProp ?? defaultGetLabel;
833
- const getValue: (item: TItem) => string = getValueProp ?? defaultGetValue;
849
+ const getId: (item: TItem) => string = getIdProp ?? defaultGetId;
834
850
 
835
851
  return (
836
852
  <ComboboxRoot
837
853
  items={items}
838
854
  itemToStringLabel={getLabel}
839
- itemToStringValue={getValue}
855
+ itemToStringValue={getId}
840
856
  multiple={multiple as boolean}
841
- onChange={onChange}
857
+ onValueChange={onValueChange}
842
858
  value={value}
843
859
  defaultValue={defaultValue}
844
860
  autoHighlight
@@ -848,33 +864,39 @@ export function Combobox<TItem = unknown>(
848
864
  {multiple ? (
849
865
  <ComboboxMultipleTrigger
850
866
  getLabel={getLabel}
851
- getValue={getValue}
852
- chipsProps={chipsProps}
867
+ getId={getId}
853
868
  showClear={showClear}
869
+ startAddon={startAddon}
870
+ className={classNames?.chips}
854
871
  />
855
872
  ) : (
856
873
  <ComboboxSingleTrigger
857
874
  placeholder={placeholder}
858
875
  showClear={showClear}
859
- inputProps={inputProps}
876
+ startAddon={startAddon}
877
+ className={classNames?.input}
860
878
  />
861
879
  )}
862
- <ComboboxPopup {...popupProps}>
880
+ <ComboboxPopup className={classNames?.popup}>
863
881
  {multiple && (
864
882
  <div
865
883
  className="border-b p-2"
866
884
  data-slot="combobox-chips-input-wrapper"
867
885
  >
868
886
  <ComboboxChipsInput
887
+ className={classNames?.chipsInput}
869
888
  placeholder={placeholder}
870
- {...chipsInputProps}
871
889
  />
872
890
  </div>
873
891
  )}
874
- <ComboboxEmpty>{emptyText}</ComboboxEmpty>
875
- <ComboboxList {...listProps}>
892
+ <ComboboxEmpty className={classNames?.empty}>{emptyText}</ComboboxEmpty>
893
+ <ComboboxList className={classNames?.list}>
876
894
  {(item: TItem) => (
877
- <ComboboxItem key={getValue(item)} value={item} {...itemProps}>
895
+ <ComboboxItem
896
+ key={getId(item)}
897
+ value={item}
898
+ className={classNames?.item}
899
+ >
878
900
  {renderItem ? renderItem(item) : getLabel(item)}
879
901
  </ComboboxItem>
880
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(", ")