@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
|
@@ -2,28 +2,28 @@ import { render, screen, waitFor } from "@testing-library/react";
|
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
Stepper,
|
|
5
|
-
StepperContainer,
|
|
6
5
|
StepperContent,
|
|
7
|
-
|
|
6
|
+
StepperNav,
|
|
7
|
+
StepperRoot,
|
|
8
8
|
StepperTrigger,
|
|
9
9
|
} from "../../components";
|
|
10
|
-
import { click } from "../../utils";
|
|
10
|
+
import { click } from "../../utils/tests";
|
|
11
11
|
|
|
12
12
|
describe("Stepper component", () => {
|
|
13
13
|
it("should render correctly", () => {
|
|
14
|
-
render(<
|
|
14
|
+
render(<StepperRoot data-testid="steps" />);
|
|
15
15
|
const component = screen.getByTestId("steps");
|
|
16
16
|
expect(component).toBeInTheDocument();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it("should show steps", () => {
|
|
20
20
|
render(
|
|
21
|
-
<
|
|
22
|
-
<
|
|
21
|
+
<StepperRoot>
|
|
22
|
+
<StepperNav>
|
|
23
23
|
<StepperTrigger value={0} title="trigger"></StepperTrigger>
|
|
24
24
|
<StepperTrigger value={1} title="step"></StepperTrigger>
|
|
25
|
-
</
|
|
26
|
-
</
|
|
25
|
+
</StepperNav>
|
|
26
|
+
</StepperRoot>,
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
expect(screen.getByText(/trigger/i)).toBeInTheDocument();
|
|
@@ -32,13 +32,13 @@ describe("Stepper component", () => {
|
|
|
32
32
|
|
|
33
33
|
it("should show step content", () => {
|
|
34
34
|
render(
|
|
35
|
-
<
|
|
36
|
-
<
|
|
35
|
+
<StepperRoot value={0}>
|
|
36
|
+
<StepperNav>
|
|
37
37
|
<StepperTrigger value={0} title="trigger"></StepperTrigger>
|
|
38
38
|
<StepperTrigger value={1} title="test"></StepperTrigger>
|
|
39
|
-
</
|
|
39
|
+
</StepperNav>
|
|
40
40
|
<StepperContent value={0}>Contenido de trigger</StepperContent>
|
|
41
|
-
</
|
|
41
|
+
</StepperRoot>,
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
expect(screen.getByText("Contenido de trigger")).toBeInTheDocument();
|
|
@@ -47,14 +47,14 @@ describe("Stepper component", () => {
|
|
|
47
47
|
it("should be controlled", () => {
|
|
48
48
|
const mock = vi.fn();
|
|
49
49
|
render(
|
|
50
|
-
<
|
|
51
|
-
<
|
|
50
|
+
<StepperRoot value={0} onValueChange={mock}>
|
|
51
|
+
<StepperNav>
|
|
52
52
|
<StepperTrigger value={0} title="trigger"></StepperTrigger>
|
|
53
53
|
<StepperTrigger value={1} title="test"></StepperTrigger>
|
|
54
|
-
</
|
|
54
|
+
</StepperNav>
|
|
55
55
|
<StepperContent value={0}>Contenido de trigger</StepperContent>
|
|
56
56
|
<StepperContent value={1}>Contenido de test</StepperContent>
|
|
57
|
-
</
|
|
57
|
+
</StepperRoot>,
|
|
58
58
|
);
|
|
59
59
|
|
|
60
60
|
const testTrigger = screen.getByText("test");
|
|
@@ -69,8 +69,8 @@ describe("Stepper component", () => {
|
|
|
69
69
|
const mock = vi.fn();
|
|
70
70
|
render(
|
|
71
71
|
<Stepper
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
value={0}
|
|
73
|
+
onValueChange={mock}
|
|
74
74
|
items={[
|
|
75
75
|
{ title: "trigger", content: "Contenido de trigger" },
|
|
76
76
|
{ title: "step1", content: "Contenido de step1" },
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
useContext,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
import { cn } from "../../lib";
|
|
14
|
+
|
|
15
|
+
type StepperOrientation = "horizontal" | "vertical";
|
|
16
|
+
type StepState = "active" | "completed" | "inactive" | "loading";
|
|
17
|
+
type StepIndicators = Partial<Record<StepState, ReactNode>>;
|
|
18
|
+
type StepperTriggerMode = "both" | "forward-only" | "backward-only" | "none";
|
|
19
|
+
|
|
20
|
+
function isStepNavigable(
|
|
21
|
+
step: number,
|
|
22
|
+
activeStep: number,
|
|
23
|
+
triggerMode: StepperTriggerMode,
|
|
24
|
+
): boolean {
|
|
25
|
+
if (triggerMode === "none") return false;
|
|
26
|
+
if (triggerMode === "both") return true;
|
|
27
|
+
if (triggerMode === "backward-only") return step <= activeStep;
|
|
28
|
+
return step >= activeStep;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface StepperContextValue {
|
|
32
|
+
activeStep: number;
|
|
33
|
+
setActiveStep: (step: number) => void;
|
|
34
|
+
orientation: StepperOrientation;
|
|
35
|
+
indicators: StepIndicators;
|
|
36
|
+
triggerMode: StepperTriggerMode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const StepperContext = createContext<StepperContextValue | undefined>(
|
|
40
|
+
undefined,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function useStepper() {
|
|
44
|
+
const context = useContext(StepperContext);
|
|
45
|
+
if (!context) {
|
|
46
|
+
throw new Error("useStepper must be used within a StepperRoot");
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface StepItemContextValue {
|
|
52
|
+
step: number;
|
|
53
|
+
state: StepState;
|
|
54
|
+
isDisabled: boolean;
|
|
55
|
+
isLoading: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const StepItemContext = createContext<StepItemContextValue | undefined>(
|
|
59
|
+
undefined,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
function useStepItem() {
|
|
63
|
+
const context = useContext(StepItemContext);
|
|
64
|
+
if (!context) {
|
|
65
|
+
throw new Error("useStepItem must be used within a StepperItem");
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface StepperRootProps extends useRender.ComponentProps<"div"> {
|
|
71
|
+
defaultValue?: number;
|
|
72
|
+
value?: number;
|
|
73
|
+
onValueChange?: (value: number) => void;
|
|
74
|
+
orientation?: StepperOrientation;
|
|
75
|
+
indicators?: StepIndicators;
|
|
76
|
+
triggerMode?: StepperTriggerMode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function StepperRoot({
|
|
80
|
+
defaultValue = 1,
|
|
81
|
+
value,
|
|
82
|
+
onValueChange,
|
|
83
|
+
orientation = "horizontal",
|
|
84
|
+
indicators = {},
|
|
85
|
+
triggerMode = "both",
|
|
86
|
+
className,
|
|
87
|
+
render,
|
|
88
|
+
children,
|
|
89
|
+
...props
|
|
90
|
+
}: StepperRootProps) {
|
|
91
|
+
const [activeStep = 1, setActiveStep] = useControllableState<number>({
|
|
92
|
+
prop: value,
|
|
93
|
+
defaultProp: defaultValue,
|
|
94
|
+
onChange: onValueChange,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const defaultProps = {
|
|
98
|
+
className: cn(
|
|
99
|
+
"flex w-full data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row data-[orientation=vertical]:gap-8",
|
|
100
|
+
className,
|
|
101
|
+
),
|
|
102
|
+
"data-orientation": orientation,
|
|
103
|
+
"data-slot": "stepper",
|
|
104
|
+
children,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<StepperContext.Provider
|
|
109
|
+
value={{
|
|
110
|
+
activeStep,
|
|
111
|
+
setActiveStep,
|
|
112
|
+
orientation,
|
|
113
|
+
indicators,
|
|
114
|
+
triggerMode,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{useRender({
|
|
118
|
+
defaultTagName: "div",
|
|
119
|
+
render,
|
|
120
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
121
|
+
})}
|
|
122
|
+
</StepperContext.Provider>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function StepperNav({
|
|
127
|
+
className,
|
|
128
|
+
render,
|
|
129
|
+
children,
|
|
130
|
+
...props
|
|
131
|
+
}: useRender.ComponentProps<"nav">) {
|
|
132
|
+
const { orientation } = useStepper();
|
|
133
|
+
|
|
134
|
+
const defaultProps = {
|
|
135
|
+
role: "tablist",
|
|
136
|
+
"aria-orientation": orientation,
|
|
137
|
+
"data-orientation": orientation,
|
|
138
|
+
"data-slot": "stepper-nav",
|
|
139
|
+
className: cn(
|
|
140
|
+
"group/stepper-nav inline-flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col",
|
|
141
|
+
className,
|
|
142
|
+
),
|
|
143
|
+
children,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return useRender({
|
|
147
|
+
defaultTagName: "nav",
|
|
148
|
+
render,
|
|
149
|
+
props: mergeProps<"nav">(defaultProps, props),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface StepperItemProps extends useRender.ComponentProps<"div"> {
|
|
154
|
+
step: number;
|
|
155
|
+
disabled?: boolean;
|
|
156
|
+
loading?: boolean;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function StepperItem({
|
|
160
|
+
step,
|
|
161
|
+
disabled = false,
|
|
162
|
+
loading = false,
|
|
163
|
+
className,
|
|
164
|
+
render,
|
|
165
|
+
children,
|
|
166
|
+
...props
|
|
167
|
+
}: StepperItemProps) {
|
|
168
|
+
const { activeStep } = useStepper();
|
|
169
|
+
|
|
170
|
+
const state: StepState =
|
|
171
|
+
step < activeStep
|
|
172
|
+
? "completed"
|
|
173
|
+
: step === activeStep
|
|
174
|
+
? "active"
|
|
175
|
+
: "inactive";
|
|
176
|
+
|
|
177
|
+
const isLoading = loading && step === activeStep;
|
|
178
|
+
|
|
179
|
+
const defaultProps = {
|
|
180
|
+
"data-state": state,
|
|
181
|
+
"data-loading": isLoading || undefined,
|
|
182
|
+
"data-slot": "stepper-item",
|
|
183
|
+
className: cn(
|
|
184
|
+
"group/step flex items-center justify-center not-last:flex-1",
|
|
185
|
+
"group-data-[orientation=horizontal]/stepper-nav:flex-row group-data-[orientation=vertical]/stepper-nav:flex-col",
|
|
186
|
+
className,
|
|
187
|
+
),
|
|
188
|
+
children,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<StepItemContext.Provider
|
|
193
|
+
value={{ step, state, isDisabled: disabled, isLoading }}
|
|
194
|
+
>
|
|
195
|
+
{useRender({
|
|
196
|
+
defaultTagName: "div",
|
|
197
|
+
render,
|
|
198
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
199
|
+
})}
|
|
200
|
+
</StepItemContext.Provider>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function StepperTrigger({
|
|
205
|
+
className,
|
|
206
|
+
render,
|
|
207
|
+
children,
|
|
208
|
+
...props
|
|
209
|
+
}: useRender.ComponentProps<"button">) {
|
|
210
|
+
const { activeStep, setActiveStep, triggerMode } = useStepper();
|
|
211
|
+
const { step, state, isDisabled, isLoading } = useStepItem();
|
|
212
|
+
const isSelected = activeStep === step;
|
|
213
|
+
const isNavigable = isStepNavigable(step, activeStep, triggerMode);
|
|
214
|
+
|
|
215
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
|
|
216
|
+
const triggers = Array.from(
|
|
217
|
+
event.currentTarget
|
|
218
|
+
.closest('[data-slot="stepper-nav"]')
|
|
219
|
+
?.querySelectorAll<HTMLButtonElement>(
|
|
220
|
+
'[data-slot="stepper-trigger"]:not([disabled]):not([aria-disabled="true"])',
|
|
221
|
+
) ?? [],
|
|
222
|
+
);
|
|
223
|
+
const index = triggers.indexOf(event.currentTarget);
|
|
224
|
+
if (index === -1) return;
|
|
225
|
+
|
|
226
|
+
switch (event.key) {
|
|
227
|
+
case "ArrowRight":
|
|
228
|
+
case "ArrowDown":
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
triggers[(index + 1) % triggers.length]?.focus();
|
|
231
|
+
break;
|
|
232
|
+
case "ArrowLeft":
|
|
233
|
+
case "ArrowUp":
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
triggers[(index - 1 + triggers.length) % triggers.length]?.focus();
|
|
236
|
+
break;
|
|
237
|
+
case "Home":
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
triggers[0]?.focus();
|
|
240
|
+
break;
|
|
241
|
+
case "End":
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
triggers[triggers.length - 1]?.focus();
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const defaultProps = {
|
|
249
|
+
type: "button" as const,
|
|
250
|
+
role: "tab",
|
|
251
|
+
id: `stepper-tab-${step}`,
|
|
252
|
+
"aria-selected": isSelected,
|
|
253
|
+
"aria-controls": `stepper-panel-${step}`,
|
|
254
|
+
"aria-disabled": !isNavigable || undefined,
|
|
255
|
+
tabIndex: isSelected ? 0 : -1,
|
|
256
|
+
disabled: isDisabled,
|
|
257
|
+
"data-state": state,
|
|
258
|
+
"data-loading": isLoading || undefined,
|
|
259
|
+
"data-slot": "stepper-trigger",
|
|
260
|
+
className: cn(
|
|
261
|
+
"inline-flex items-center gap-2.5 rounded-full outline-none",
|
|
262
|
+
isNavigable ? "cursor-pointer" : "cursor-default",
|
|
263
|
+
"focus-visible:z-10 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
|
|
264
|
+
"disabled:pointer-events-none disabled:opacity-60",
|
|
265
|
+
className,
|
|
266
|
+
),
|
|
267
|
+
onClick: () => {
|
|
268
|
+
if (!isNavigable) return;
|
|
269
|
+
setActiveStep(step);
|
|
270
|
+
},
|
|
271
|
+
onKeyDown: handleKeyDown,
|
|
272
|
+
children,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return useRender({
|
|
276
|
+
defaultTagName: "button",
|
|
277
|
+
render,
|
|
278
|
+
props: mergeProps<"button">(defaultProps, props),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function StepperIndicator({
|
|
283
|
+
className,
|
|
284
|
+
render,
|
|
285
|
+
children,
|
|
286
|
+
...props
|
|
287
|
+
}: useRender.ComponentProps<"div">) {
|
|
288
|
+
const { state, isLoading } = useStepItem();
|
|
289
|
+
const { indicators } = useStepper();
|
|
290
|
+
|
|
291
|
+
const override =
|
|
292
|
+
(isLoading && indicators.loading) ||
|
|
293
|
+
(state === "completed" && indicators.completed) ||
|
|
294
|
+
(state === "active" && indicators.active) ||
|
|
295
|
+
(state === "inactive" && indicators.inactive);
|
|
296
|
+
|
|
297
|
+
const defaultProps = {
|
|
298
|
+
"data-state": state,
|
|
299
|
+
"data-loading": isLoading || undefined,
|
|
300
|
+
"data-slot": "stepper-indicator",
|
|
301
|
+
className: cn(
|
|
302
|
+
"relative flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full border-background bg-accent text-accent-foreground text-xs",
|
|
303
|
+
"data-[state=active]:bg-primary data-[state=active]:text-primary-foreground",
|
|
304
|
+
"data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground",
|
|
305
|
+
className,
|
|
306
|
+
),
|
|
307
|
+
children: <div className="absolute">{override || children}</div>,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
return useRender({
|
|
311
|
+
defaultTagName: "div",
|
|
312
|
+
render,
|
|
313
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function StepperSeparator({
|
|
318
|
+
className,
|
|
319
|
+
render,
|
|
320
|
+
children,
|
|
321
|
+
...props
|
|
322
|
+
}: useRender.ComponentProps<"div">) {
|
|
323
|
+
const { state } = useStepItem();
|
|
324
|
+
|
|
325
|
+
const defaultProps = {
|
|
326
|
+
"aria-hidden": true,
|
|
327
|
+
"data-state": state,
|
|
328
|
+
"data-slot": "stepper-separator",
|
|
329
|
+
className: cn(
|
|
330
|
+
"m-0.5 rounded-sm bg-muted data-[state=completed]:bg-primary",
|
|
331
|
+
"group-data-[orientation=horizontal]/stepper-nav:h-0.5 group-data-[orientation=horizontal]/stepper-nav:flex-1",
|
|
332
|
+
"group-data-[orientation=vertical]/stepper-nav:h-12 group-data-[orientation=vertical]/stepper-nav:w-0.5",
|
|
333
|
+
className,
|
|
334
|
+
),
|
|
335
|
+
children,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return useRender({
|
|
339
|
+
defaultTagName: "div",
|
|
340
|
+
render,
|
|
341
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function StepperTitle({
|
|
346
|
+
className,
|
|
347
|
+
render,
|
|
348
|
+
children,
|
|
349
|
+
...props
|
|
350
|
+
}: useRender.ComponentProps<"h3">) {
|
|
351
|
+
const { state } = useStepItem();
|
|
352
|
+
|
|
353
|
+
const defaultProps = {
|
|
354
|
+
"data-state": state,
|
|
355
|
+
"data-slot": "stepper-title",
|
|
356
|
+
className: cn("font-medium text-sm leading-none", className),
|
|
357
|
+
children,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
return useRender({
|
|
361
|
+
defaultTagName: "h3",
|
|
362
|
+
render,
|
|
363
|
+
props: mergeProps<"h3">(defaultProps, props),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function StepperDescription({
|
|
368
|
+
className,
|
|
369
|
+
render,
|
|
370
|
+
children,
|
|
371
|
+
...props
|
|
372
|
+
}: useRender.ComponentProps<"p">) {
|
|
373
|
+
const { state } = useStepItem();
|
|
374
|
+
|
|
375
|
+
const defaultProps = {
|
|
376
|
+
"data-state": state,
|
|
377
|
+
"data-slot": "stepper-description",
|
|
378
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
379
|
+
children,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return useRender({
|
|
383
|
+
defaultTagName: "p",
|
|
384
|
+
render,
|
|
385
|
+
props: mergeProps<"p">(defaultProps, props),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function StepperPanel({
|
|
390
|
+
className,
|
|
391
|
+
render,
|
|
392
|
+
children,
|
|
393
|
+
...props
|
|
394
|
+
}: useRender.ComponentProps<"div">) {
|
|
395
|
+
const { activeStep } = useStepper();
|
|
396
|
+
|
|
397
|
+
const defaultProps = {
|
|
398
|
+
"data-state": activeStep,
|
|
399
|
+
"data-slot": "stepper-panel",
|
|
400
|
+
className: cn("w-full", className),
|
|
401
|
+
children,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
return useRender({
|
|
405
|
+
defaultTagName: "div",
|
|
406
|
+
render,
|
|
407
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
interface StepperContentProps extends useRender.ComponentProps<"div"> {
|
|
412
|
+
value: number;
|
|
413
|
+
forceMount?: boolean;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function StepperContent({
|
|
417
|
+
value,
|
|
418
|
+
forceMount = false,
|
|
419
|
+
className,
|
|
420
|
+
render,
|
|
421
|
+
children,
|
|
422
|
+
...props
|
|
423
|
+
}: StepperContentProps) {
|
|
424
|
+
const { activeStep } = useStepper();
|
|
425
|
+
const isActive = value === activeStep;
|
|
426
|
+
|
|
427
|
+
if (!forceMount && !isActive) return null;
|
|
428
|
+
|
|
429
|
+
const defaultProps = {
|
|
430
|
+
role: "tabpanel",
|
|
431
|
+
id: `stepper-panel-${value}`,
|
|
432
|
+
"aria-labelledby": `stepper-tab-${value}`,
|
|
433
|
+
hidden: !isActive,
|
|
434
|
+
"data-state": isActive ? "active" : "inactive",
|
|
435
|
+
"data-slot": "stepper-content",
|
|
436
|
+
className: cn("w-full", className, !isActive && "hidden"),
|
|
437
|
+
children,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
return useRender({
|
|
441
|
+
defaultTagName: "div",
|
|
442
|
+
render,
|
|
443
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
interface StepperItemData {
|
|
448
|
+
title?: ReactNode;
|
|
449
|
+
description?: ReactNode;
|
|
450
|
+
indicator?: ReactNode;
|
|
451
|
+
content?: ReactNode;
|
|
452
|
+
disabled?: boolean;
|
|
453
|
+
loading?: boolean;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
interface StepperProps extends Omit<StepperRootProps, "children"> {
|
|
457
|
+
items: StepperItemData[];
|
|
458
|
+
/** Styles applied to each internal slot. */
|
|
459
|
+
classNames?: {
|
|
460
|
+
/** Navigation list that holds every step. */
|
|
461
|
+
nav?: string;
|
|
462
|
+
/** Single step container (indicator + label + separator). */
|
|
463
|
+
item?: string;
|
|
464
|
+
/** Clickable step button. */
|
|
465
|
+
trigger?: string;
|
|
466
|
+
/** Round indicator (number, icon, or check). */
|
|
467
|
+
indicator?: string;
|
|
468
|
+
/** Step title above the description. */
|
|
469
|
+
title?: string;
|
|
470
|
+
/** Step description below the title. */
|
|
471
|
+
description?: string;
|
|
472
|
+
/** Line between steps. */
|
|
473
|
+
separator?: string;
|
|
474
|
+
/** Panel below the nav that holds the active step's content. */
|
|
475
|
+
panel?: string;
|
|
476
|
+
/** Single step content rendered when the step is active. */
|
|
477
|
+
content?: string;
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function Stepper({ items, classNames, ...props }: StepperProps) {
|
|
482
|
+
const hasContent = items.some((item) => item.content != null);
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<StepperRoot {...props}>
|
|
486
|
+
<StepperNav className={classNames?.nav}>
|
|
487
|
+
{items.map((item, i) => {
|
|
488
|
+
const step = i + 1;
|
|
489
|
+
return (
|
|
490
|
+
<StepperItem
|
|
491
|
+
key={step}
|
|
492
|
+
step={step}
|
|
493
|
+
disabled={item.disabled}
|
|
494
|
+
loading={item.loading}
|
|
495
|
+
className={classNames?.item}
|
|
496
|
+
>
|
|
497
|
+
<StepperTrigger className={classNames?.trigger}>
|
|
498
|
+
<StepperIndicator className={classNames?.indicator}>
|
|
499
|
+
{item.indicator ?? step}
|
|
500
|
+
</StepperIndicator>
|
|
501
|
+
{(item.title || item.description) && (
|
|
502
|
+
<div className="flex flex-col gap-0.5 text-left">
|
|
503
|
+
{item.title ? (
|
|
504
|
+
<StepperTitle className={classNames?.title}>
|
|
505
|
+
{item.title}
|
|
506
|
+
</StepperTitle>
|
|
507
|
+
) : null}
|
|
508
|
+
{item.description ? (
|
|
509
|
+
<StepperDescription className={classNames?.description}>
|
|
510
|
+
{item.description}
|
|
511
|
+
</StepperDescription>
|
|
512
|
+
) : null}
|
|
513
|
+
</div>
|
|
514
|
+
)}
|
|
515
|
+
</StepperTrigger>
|
|
516
|
+
{i < items.length - 1 ? (
|
|
517
|
+
<StepperSeparator className={classNames?.separator} />
|
|
518
|
+
) : null}
|
|
519
|
+
</StepperItem>
|
|
520
|
+
);
|
|
521
|
+
})}
|
|
522
|
+
</StepperNav>
|
|
523
|
+
|
|
524
|
+
{hasContent ? (
|
|
525
|
+
<StepperPanel className={classNames?.panel}>
|
|
526
|
+
{items.map((item, i) =>
|
|
527
|
+
item.content != null ? (
|
|
528
|
+
<StepperContent
|
|
529
|
+
key={i + 1}
|
|
530
|
+
value={i + 1}
|
|
531
|
+
className={classNames?.content}
|
|
532
|
+
>
|
|
533
|
+
{item.content}
|
|
534
|
+
</StepperContent>
|
|
535
|
+
) : null,
|
|
536
|
+
)}
|
|
537
|
+
</StepperPanel>
|
|
538
|
+
) : null}
|
|
539
|
+
</StepperRoot>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export { useStepper, useStepItem };
|
|
544
|
+
export type {
|
|
545
|
+
StepperRootProps,
|
|
546
|
+
StepperItemProps,
|
|
547
|
+
StepperContentProps,
|
|
548
|
+
StepperOrientation,
|
|
549
|
+
StepperTriggerMode,
|
|
550
|
+
StepState,
|
|
551
|
+
StepIndicators,
|
|
552
|
+
StepperItemData,
|
|
553
|
+
StepperProps,
|
|
554
|
+
};
|
|
@@ -24,7 +24,7 @@ const meta: Meta<typeof Switch> = {
|
|
|
24
24
|
argTypes: {
|
|
25
25
|
children: { control: false },
|
|
26
26
|
onCheckedChange: { control: false },
|
|
27
|
-
|
|
27
|
+
classNames: { control: false },
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -33,6 +33,20 @@ type Story = StoryObj<typeof Switch>;
|
|
|
33
33
|
|
|
34
34
|
export const Default: Story = {};
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* `className` estila el switch (root visible). `classNames` expone los slots
|
|
38
|
+
* `wrapper` (flex outer), `thumb` (perilla) y `label` (texto al costado).
|
|
39
|
+
*/
|
|
40
|
+
export const WithClassNames: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
classNames: {
|
|
43
|
+
wrapper: "gap-x-4",
|
|
44
|
+
thumb: "bg-primary-foreground",
|
|
45
|
+
label: "text-primary",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
36
50
|
/**
|
|
37
51
|
* Pass `label` or `children` — both render an accessible label next to the switch.
|
|
38
52
|
* `label` takes priority when both are provided.
|
|
@@ -48,8 +48,16 @@ interface SwitchProps extends Omit<SwitchPrimitive.Root.Props, "children"> {
|
|
|
48
48
|
children?: React.ReactNode;
|
|
49
49
|
label?: React.ReactNode;
|
|
50
50
|
tooltip?: string;
|
|
51
|
-
thumbProps?: SwitchPrimitive.Thumb.Props;
|
|
52
51
|
controlFirst?: boolean;
|
|
52
|
+
/** Styles applied to each internal slot. */
|
|
53
|
+
classNames?: {
|
|
54
|
+
/** Outer flex wrapper that holds the switch and label. */
|
|
55
|
+
wrapper?: string;
|
|
56
|
+
/** Sliding thumb (knob) inside the switch. */
|
|
57
|
+
thumb?: string;
|
|
58
|
+
/** Label rendered next to the switch. */
|
|
59
|
+
label?: string;
|
|
60
|
+
};
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
export function Switch({
|
|
@@ -57,9 +65,9 @@ export function Switch({
|
|
|
57
65
|
label,
|
|
58
66
|
className,
|
|
59
67
|
id,
|
|
60
|
-
thumbProps,
|
|
61
68
|
controlFirst = true,
|
|
62
69
|
tooltip,
|
|
70
|
+
classNames,
|
|
63
71
|
...props
|
|
64
72
|
}: SwitchProps): React.ReactElement {
|
|
65
73
|
const generatedId = useId();
|
|
@@ -71,13 +79,18 @@ export function Switch({
|
|
|
71
79
|
className={cn(
|
|
72
80
|
"flex select-none items-center gap-x-2",
|
|
73
81
|
!controlFirst && "flex-row-reverse justify-end",
|
|
82
|
+
classNames?.wrapper,
|
|
74
83
|
)}
|
|
75
84
|
>
|
|
76
85
|
<SwitchRoot id={idToUse} className={className} {...props}>
|
|
77
|
-
<SwitchThumb {
|
|
86
|
+
<SwitchThumb className={classNames?.thumb} />
|
|
78
87
|
</SwitchRoot>
|
|
79
88
|
{labelContent && (
|
|
80
|
-
<Label
|
|
89
|
+
<Label
|
|
90
|
+
htmlFor={idToUse}
|
|
91
|
+
tooltip={tooltip}
|
|
92
|
+
className={classNames?.label}
|
|
93
|
+
>
|
|
81
94
|
{labelContent}
|
|
82
95
|
</Label>
|
|
83
96
|
)}
|