@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.
- package/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1083 -715
- package/dist/index.es.js +7077 -56175
- 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 +119 -103
- 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 +6 -0
- 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 +226 -3
- 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 +25 -29
- 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 +26 -10
- 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 -451
- 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
|
@@ -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
|
-
}:
|
|
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
|
|
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
|
-
|
|
491
|
-
<
|
|
492
|
-
{
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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
|
-
|
|
645
|
+
getId,
|
|
646
646
|
}: {
|
|
647
647
|
value: unknown[];
|
|
648
648
|
getLabel: (item: unknown) => string;
|
|
649
|
-
|
|
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={
|
|
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"
|
|
679
|
-
|
|
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
|
|
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"
|
|
687
|
-
|
|
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
|
-
|
|
701
|
+
startAddon,
|
|
702
|
+
className,
|
|
695
703
|
}: {
|
|
696
704
|
placeholder?: string;
|
|
697
705
|
showClear?: boolean;
|
|
698
|
-
|
|
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
|
-
{
|
|
714
|
+
startAddon={startAddon}
|
|
705
715
|
/>
|
|
706
716
|
);
|
|
707
717
|
}
|
|
708
718
|
|
|
709
719
|
function ComboboxMultipleTrigger({
|
|
710
720
|
getLabel,
|
|
711
|
-
|
|
712
|
-
chipsProps,
|
|
721
|
+
getId,
|
|
713
722
|
showClear,
|
|
714
|
-
|
|
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
|
-
|
|
720
|
-
chipsProps?: React.ComponentProps<typeof ComboboxChips>;
|
|
729
|
+
getId: (item: any) => string;
|
|
721
730
|
showClear?: boolean;
|
|
722
|
-
|
|
731
|
+
startAddon?: React.ReactNode;
|
|
732
|
+
className?: string;
|
|
723
733
|
}): React.ReactElement {
|
|
724
734
|
return (
|
|
725
|
-
<ComboboxChips {
|
|
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
|
-
|
|
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
|
-
|
|
751
|
+
ComboboxPrimitive.Root.Props<TItem, boolean>,
|
|
753
752
|
| "items"
|
|
754
753
|
| "itemToStringLabel"
|
|
755
754
|
| "itemToStringValue"
|
|
756
755
|
| "children"
|
|
757
756
|
| "multiple"
|
|
758
|
-
| "
|
|
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
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 }
|
|
795
|
-
* 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`.
|
|
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
|
-
|
|
829
|
+
getId: getIdProp,
|
|
816
830
|
renderItem,
|
|
817
831
|
placeholder,
|
|
818
832
|
emptyText = "Sin resultados.",
|
|
819
833
|
multiple,
|
|
820
|
-
|
|
834
|
+
onValueChange,
|
|
821
835
|
value,
|
|
822
836
|
defaultValue,
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
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
|
|
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={
|
|
855
|
+
itemToStringValue={getId}
|
|
846
856
|
multiple={multiple as boolean}
|
|
847
|
-
|
|
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
|
-
|
|
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
|
-
|
|
876
|
+
startAddon={startAddon}
|
|
877
|
+
className={classNames?.input}
|
|
866
878
|
/>
|
|
867
879
|
)}
|
|
868
|
-
<ComboboxPopup {
|
|
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 {
|
|
892
|
+
<ComboboxEmpty className={classNames?.empty}>{emptyText}</ComboboxEmpty>
|
|
893
|
+
<ComboboxList className={classNames?.list}>
|
|
882
894
|
{(item: TItem) => (
|
|
883
|
-
<ComboboxItem
|
|
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
|
-
|
|
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(", ")
|