@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.
- package/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1087 -720
- package/dist/index.es.js +7011 -56097
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1246 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +219 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +78 -19
- package/src/components/button/components/base-button.tsx +30 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +141 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +99 -77
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +2 -2
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +28 -14
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +126 -51
- package/src/components/divider/divider.tsx +16 -16
- package/src/components/dropzone/dropzone.stories.tsx +71 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +165 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +227 -4
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +4 -4
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +276 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +31 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +154 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +29 -9
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +767 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +373 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +155 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +323 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +365 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +67 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -475
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- package/src/components/tree/tree-primitives.tsx +0 -126
|
@@ -1,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
|
-
}:
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
645
|
+
getId,
|
|
640
646
|
}: {
|
|
641
647
|
value: unknown[];
|
|
642
648
|
getLabel: (item: unknown) => string;
|
|
643
|
-
|
|
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={
|
|
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"
|
|
673
|
-
|
|
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
|
|
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"
|
|
681
|
-
|
|
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
|
-
|
|
701
|
+
startAddon,
|
|
702
|
+
className,
|
|
689
703
|
}: {
|
|
690
704
|
placeholder?: string;
|
|
691
705
|
showClear?: boolean;
|
|
692
|
-
|
|
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
|
-
{
|
|
714
|
+
startAddon={startAddon}
|
|
699
715
|
/>
|
|
700
716
|
);
|
|
701
717
|
}
|
|
702
718
|
|
|
703
719
|
function ComboboxMultipleTrigger({
|
|
704
720
|
getLabel,
|
|
705
|
-
|
|
706
|
-
chipsProps,
|
|
721
|
+
getId,
|
|
707
722
|
showClear,
|
|
708
|
-
|
|
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
|
-
|
|
714
|
-
chipsProps?: React.ComponentProps<typeof ComboboxChips>;
|
|
729
|
+
getId: (item: any) => string;
|
|
715
730
|
showClear?: boolean;
|
|
716
|
-
|
|
731
|
+
startAddon?: React.ReactNode;
|
|
732
|
+
className?: string;
|
|
717
733
|
}): React.ReactElement {
|
|
718
734
|
return (
|
|
719
|
-
<ComboboxChips {
|
|
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
|
-
|
|
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
|
-
|
|
751
|
+
ComboboxPrimitive.Root.Props<TItem, boolean>,
|
|
747
752
|
| "items"
|
|
748
753
|
| "itemToStringLabel"
|
|
749
754
|
| "itemToStringValue"
|
|
750
755
|
| "children"
|
|
751
756
|
| "multiple"
|
|
752
|
-
| "
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 }
|
|
789
|
-
* extra props. For other shapes, provide `getLabel` and/or `
|
|
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
|
-
|
|
829
|
+
getId: getIdProp,
|
|
810
830
|
renderItem,
|
|
811
831
|
placeholder,
|
|
812
832
|
emptyText = "Sin resultados.",
|
|
813
833
|
multiple,
|
|
814
|
-
|
|
834
|
+
onValueChange,
|
|
815
835
|
value,
|
|
816
836
|
defaultValue,
|
|
817
|
-
|
|
818
|
-
|
|
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
|
-
|
|
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
|
|
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={
|
|
855
|
+
itemToStringValue={getId}
|
|
840
856
|
multiple={multiple as boolean}
|
|
841
|
-
|
|
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
|
-
|
|
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
|
-
|
|
876
|
+
startAddon={startAddon}
|
|
877
|
+
className={classNames?.input}
|
|
860
878
|
/>
|
|
861
879
|
)}
|
|
862
|
-
<ComboboxPopup {
|
|
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 {
|
|
892
|
+
<ComboboxEmpty className={classNames?.empty}>{emptyText}</ComboboxEmpty>
|
|
893
|
+
<ComboboxList className={classNames?.list}>
|
|
876
894
|
{(item: TItem) => (
|
|
877
|
-
<ComboboxItem
|
|
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
|
-
|
|
36
|
+
onValueChange: action("onValueChange"),
|
|
29
37
|
},
|
|
30
38
|
argTypes: {
|
|
31
|
-
|
|
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` + `
|
|
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
|
-
|
|
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` + `
|
|
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}
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
36
|
+
onValueChange: action("onValueChange"),
|
|
30
37
|
},
|
|
31
38
|
argTypes: {
|
|
32
|
-
|
|
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` + `
|
|
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 `
|
|
98
|
+
* `value` and `onValueChange` use `DateRange = { start: Date | null; end: Date | null }`.
|
|
73
99
|
*/
|
|
74
100
|
export const Range: Story = {
|
|
75
|
-
args: { mode: "range",
|
|
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 `
|
|
106
|
+
* `value` and `onValueChange` use `Date[]`.
|
|
81
107
|
*/
|
|
82
108
|
export const Multiple: Story = {
|
|
83
|
-
args: { mode: "multiple",
|
|
109
|
+
args: { mode: "multiple", onValueChange: action("onValueChange") },
|
|
84
110
|
};
|
|
85
111
|
|
|
86
112
|
/**
|
|
87
|
-
* `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}
|
|
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}
|
|
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}
|
|
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(", ")
|