@datum-cloud/datum-ui 0.6.0-alpha.b817c77 → 0.6.0-alpha.b8a44ac
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/README.md +3 -0
- package/dist/combobox/index.mjs +2 -0
- package/dist/combobox-cKTFK4uN.mjs +96 -0
- package/dist/components/features/combobox/combobox.d.ts +27 -0
- package/dist/components/features/combobox/combobox.d.ts.map +1 -0
- package/dist/components/features/combobox/index.d.ts +3 -0
- package/dist/components/features/combobox/index.d.ts.map +1 -0
- package/dist/components/features/combobox/types.d.ts +78 -0
- package/dist/components/features/combobox/types.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
- package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/index.d.ts +3 -0
- package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/types.d.ts +53 -0
- package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
- package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
- package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
- package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
- package/dist/components/features/form/adapter-types.d.ts +20 -0
- package/dist/components/features/form/adapter-types.d.ts.map +1 -1
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -1
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -1
- package/dist/components/features/form/components/form-autosearch.d.ts +25 -0
- package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
- package/dist/components/features/form/components/form-combobox.d.ts +76 -0
- package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
- package/dist/components/features/form/components/form-copy-box.d.ts +3 -0
- package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
- package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
- package/dist/components/features/form/components/form-date-picker.d.ts +38 -0
- package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-date-time-picker.d.ts +37 -0
- package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-dialog.d.ts.map +1 -1
- package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-root.d.ts.map +1 -1
- package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
- package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-transfer.d.ts +37 -0
- package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
- package/dist/components/features/form/components/index.d.ts +7 -1
- package/dist/components/features/form/components/index.d.ts.map +1 -1
- package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
- package/dist/components/features/form/hooks/index.d.ts +1 -1
- package/dist/components/features/form/hooks/index.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
- package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
- package/dist/components/features/form/index.d.ts +39 -21
- package/dist/components/features/form/index.d.ts.map +1 -1
- package/dist/components/features/form/stepper/index.d.ts +17 -0
- package/dist/components/features/form/stepper/index.d.ts.map +1 -0
- package/dist/components/features/form/types/index.d.ts +36 -0
- package/dist/components/features/form/types/index.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts +23 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
- package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
- package/dist/components/features/form/utils/zod-helpers.d.ts +12 -0
- package/dist/components/features/form/utils/zod-helpers.d.ts.map +1 -0
- package/dist/components/features/time-picker/index.d.ts +3 -0
- package/dist/components/features/time-picker/index.d.ts.map +1 -0
- package/dist/components/features/time-picker/time-picker.d.ts +22 -0
- package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
- package/dist/components/features/time-picker/types.d.ts +31 -0
- package/dist/components/features/time-picker/types.d.ts.map +1 -0
- package/dist/components/features/transfer/components/index.d.ts +9 -0
- package/dist/components/features/transfer/components/index.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
- package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
- package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
- package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
- package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
- package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
- package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
- package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
- package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
- package/dist/components/features/transfer/index.d.ts +3 -0
- package/dist/components/features/transfer/index.d.ts.map +1 -0
- package/dist/components/features/transfer/transfer.d.ts +6 -0
- package/dist/components/features/transfer/transfer.d.ts.map +1 -0
- package/dist/components/features/transfer/types.d.ts +69 -0
- package/dist/components/features/transfer/types.d.ts.map +1 -0
- package/dist/date-picker/index.mjs +1 -1
- package/dist/date-time-picker/index.mjs +2 -0
- package/dist/date-time-picker-Dy2jrJoN.mjs +175 -0
- package/dist/form/adapters/conform/index.mjs +102 -12
- package/dist/form/adapters/rhf/index.mjs +112 -26
- package/dist/form/index.mjs +3 -3
- package/dist/form/stepper/index.mjs +541 -0
- package/dist/form-context-Ccxm-wqL.mjs +17 -0
- package/dist/{form-BE1xBne4.mjs → form-mlNLKaB5.mjs} +350 -592
- package/dist/{get-field-constraints-BPMW8VvY.mjs → get-field-constraints-BicgDkfH.mjs} +19 -16
- package/dist/grid/index.mjs +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/index.mjs +14 -14
- package/dist/input-number/index.mjs +1 -1
- package/dist/map/index.mjs +1 -1
- package/dist/{map-Cw7u8r6E.mjs → map-CWIQ-eql.mjs} +1 -1
- package/dist/more-actions/index.mjs +1 -1
- package/dist/page-title/index.mjs +1 -1
- package/dist/stepper/index.mjs +1 -320
- package/dist/stepper-DvIOp0hh.mjs +321 -0
- package/dist/tag-input/index.mjs +1 -1
- package/dist/task-queue/index.mjs +1 -1
- package/dist/time-picker/index.mjs +2 -0
- package/dist/time-picker-BoF7pZZ2.mjs +43 -0
- package/dist/transfer/index.mjs +2 -0
- package/dist/transfer-B2n8pgEQ.mjs +260 -0
- package/package.json +37 -1
- /package/dist/{adapter-context-B7L2ucTr.mjs → adapter-context-rWveHhDd.mjs} +0 -0
- /package/dist/{col-YBbQ5wlb.mjs → col-1T0Q3SlH.mjs} +0 -0
- /package/dist/{hooks-DYjN7lvC.mjs → hooks-D8r2M2U6.mjs} +0 -0
- /package/dist/{input-number-DEjXG2I6.mjs → input-number-a7uydAsw.mjs} +0 -0
- /package/dist/{map-leaflet-imports-D6nTEOIh.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
- /package/dist/{more-actions-BNQ2yfWZ.mjs → more-actions-ILnEZq_E.mjs} +0 -0
- /package/dist/{page-title-CNiRNZ7p.mjs → page-title-ChsnpBiH.mjs} +0 -0
- /package/dist/{tag-input-BKed-cul.mjs → tag-input-T9cUX9-G.mjs} +0 -0
- /package/dist/{task-queue-dropdown-Di_Wjspz.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
- /package/dist/{to-api-format-Cq4prffn.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
- /package/dist/{use-copy-to-clipboard-BGdTmkFV.mjs → use-copy-to-clipboard-uNeeVHC4.mjs} +0 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { t as cn } from "../../cn-D2KYQ917.mjs";
|
|
2
|
+
import { t as Button } from "../../button-BllvE9Lm.mjs";
|
|
3
|
+
import { n as useFormContext, t as FormProvider } from "../../form-context-Ccxm-wqL.mjs";
|
|
4
|
+
import { n as useAdapter } from "../../adapter-context-rWveHhDd.mjs";
|
|
5
|
+
import { t as defineStepper } from "../../stepper-DvIOp0hh.mjs";
|
|
6
|
+
import { CheckIcon } from "lucide-react";
|
|
7
|
+
import * as React$1 from "react";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
//#region src/components/features/form/components/stepper/form-stepper.tsx
|
|
11
|
+
const FormStepperContext = React$1.createContext(null);
|
|
12
|
+
function useFormStepperContext() {
|
|
13
|
+
const context = React$1.use(FormStepperContext);
|
|
14
|
+
if (!context) throw new Error("useFormStepperContext must be used within a Form.Stepper component");
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively unwrap ZodIntersection (from .and()) to extract the base ZodObject.
|
|
19
|
+
*
|
|
20
|
+
* Zod v4 schema types use `def.type` as a string discriminant:
|
|
21
|
+
* - "intersection" (from .and()): merge left + right base objects
|
|
22
|
+
* - "object": return directly
|
|
23
|
+
*
|
|
24
|
+
* Note: In Zod v4, .superRefine() and .refine() return `this` (no wrapper),
|
|
25
|
+
* so only ZodIntersection needs unwrapping.
|
|
26
|
+
*/
|
|
27
|
+
function getBaseObject(schema) {
|
|
28
|
+
if (schema.def.type === "intersection") {
|
|
29
|
+
const intersectionDef = schema.def;
|
|
30
|
+
const left = getBaseObject(intersectionDef.left);
|
|
31
|
+
const right = getBaseObject(intersectionDef.right);
|
|
32
|
+
return left.merge(right);
|
|
33
|
+
}
|
|
34
|
+
if (schema.def.type !== "object") return z.object({});
|
|
35
|
+
return schema;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge multiple zod schemas into one ZodObject for HTML constraint generation.
|
|
39
|
+
* Handles ZodIntersection (.and()) by unwrapping to base ZodObject shapes.
|
|
40
|
+
* Per-step validation still uses the original schemas with all refinements intact.
|
|
41
|
+
*/
|
|
42
|
+
function mergeSchemas(steps) {
|
|
43
|
+
if (steps.length === 0) throw new Error("Form.Stepper requires at least one step");
|
|
44
|
+
return steps.reduce((acc, step, index) => {
|
|
45
|
+
const base = getBaseObject(step.schema);
|
|
46
|
+
if (index === 0) return base;
|
|
47
|
+
return acc.merge(base);
|
|
48
|
+
}, {});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert StepConfig[] to Stepperize step format
|
|
52
|
+
*/
|
|
53
|
+
function toStepperizeSteps(steps) {
|
|
54
|
+
return steps.map((step) => ({
|
|
55
|
+
id: step.id,
|
|
56
|
+
label: step.label,
|
|
57
|
+
description: step.description
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Form.Stepper - Multi-step form container
|
|
62
|
+
*
|
|
63
|
+
* Uses Stepperize internally for step navigation and a single Conform form
|
|
64
|
+
* instance for all steps. Schemas are auto-merged for unified validation.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* const steps = [
|
|
69
|
+
* { id: 'account', label: 'Account', schema: accountSchema },
|
|
70
|
+
* { id: 'profile', label: 'Profile', schema: profileSchema },
|
|
71
|
+
* ];
|
|
72
|
+
*
|
|
73
|
+
* <Form.Stepper steps={steps} onComplete={handleComplete}>
|
|
74
|
+
* <Form.StepperNavigation />
|
|
75
|
+
*
|
|
76
|
+
* <Form.Step id="account">
|
|
77
|
+
* <Form.Field name="email" label="Email" required>
|
|
78
|
+
* <Form.Input type="email" />
|
|
79
|
+
* </Form.Field>
|
|
80
|
+
* </Form.Step>
|
|
81
|
+
*
|
|
82
|
+
* <Form.Step id="profile">
|
|
83
|
+
* <Form.Field name="name" label="Full Name" required>
|
|
84
|
+
* <Form.Input />
|
|
85
|
+
* </Form.Field>
|
|
86
|
+
* </Form.Step>
|
|
87
|
+
*
|
|
88
|
+
* <Form.StepperControls />
|
|
89
|
+
* </Form.Stepper>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function FormStepper({ steps, children, onComplete, onStepChange, initialStep, className, defaultValues, id, formComponent }) {
|
|
93
|
+
const stepperDef = React$1.useMemo(() => {
|
|
94
|
+
return defineStepper(...toStepperizeSteps(steps));
|
|
95
|
+
}, [steps]);
|
|
96
|
+
const initialStepIndex = React$1.useMemo(() => {
|
|
97
|
+
if (!initialStep) return void 0;
|
|
98
|
+
const index = steps.findIndex((s) => s.id === initialStep);
|
|
99
|
+
return index >= 0 ? steps[index].id : void 0;
|
|
100
|
+
}, [initialStep, steps]);
|
|
101
|
+
const { Stepper } = stepperDef;
|
|
102
|
+
const providerProps = initialStepIndex ? { initialStep: initialStepIndex } : {};
|
|
103
|
+
return /* @__PURE__ */ jsx(Stepper.Provider, {
|
|
104
|
+
...providerProps,
|
|
105
|
+
children: /* @__PURE__ */ jsx(FormStepperContent, {
|
|
106
|
+
steps,
|
|
107
|
+
stepperDef,
|
|
108
|
+
onComplete,
|
|
109
|
+
onStepChange,
|
|
110
|
+
className,
|
|
111
|
+
defaultValues,
|
|
112
|
+
id,
|
|
113
|
+
formComponent,
|
|
114
|
+
children
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
FormStepper.displayName = "Form.Stepper";
|
|
119
|
+
function FormStepperContent({ steps, stepperDef, children, onComplete, onStepChange, className, defaultValues, id, formComponent }) {
|
|
120
|
+
const { useStepper } = stepperDef;
|
|
121
|
+
const stepper = useStepper();
|
|
122
|
+
return /* @__PURE__ */ jsx(StepForm, {
|
|
123
|
+
steps,
|
|
124
|
+
stepper,
|
|
125
|
+
currentStepConfig: React$1.useMemo(() => steps.find((s) => s.id === stepper.state.current.data.id) ?? steps[0], [steps, stepper.state.current.data.id]),
|
|
126
|
+
combinedSchema: React$1.useMemo(() => mergeSchemas(steps), [steps]),
|
|
127
|
+
storedValues: React$1.useMemo(() => {
|
|
128
|
+
const allMetadata = steps.reduce((acc, step) => ({
|
|
129
|
+
...acc,
|
|
130
|
+
...stepper.metadata.get(step.id) || {}
|
|
131
|
+
}), {});
|
|
132
|
+
return {
|
|
133
|
+
...defaultValues,
|
|
134
|
+
...allMetadata
|
|
135
|
+
};
|
|
136
|
+
}, [
|
|
137
|
+
steps,
|
|
138
|
+
stepper,
|
|
139
|
+
defaultValues,
|
|
140
|
+
stepper.state.current.data.id
|
|
141
|
+
]),
|
|
142
|
+
onComplete,
|
|
143
|
+
onStepChange,
|
|
144
|
+
className,
|
|
145
|
+
id,
|
|
146
|
+
formComponent,
|
|
147
|
+
children
|
|
148
|
+
}, stepper.state.current.data.id);
|
|
149
|
+
}
|
|
150
|
+
function StepForm({ steps, stepper, currentStepConfig, combinedSchema: _combinedSchema, storedValues, children, onComplete, onStepChange, className, id, formComponent: FormComp = "form" }) {
|
|
151
|
+
const adapter = useAdapter();
|
|
152
|
+
const [isSubmitting, setIsSubmitting] = React$1.useState(false);
|
|
153
|
+
const formRef = React$1.useRef(null);
|
|
154
|
+
const currentIndex = stepper.lookup.getIndex(stepper.state.current.data.id);
|
|
155
|
+
const handleStepSubmit = React$1.useCallback(async (data) => {
|
|
156
|
+
stepper.metadata.set(stepper.state.current.data.id, data);
|
|
157
|
+
if (stepper.state.isLast) {
|
|
158
|
+
setIsSubmitting(true);
|
|
159
|
+
try {
|
|
160
|
+
await onComplete({
|
|
161
|
+
...steps.reduce((acc, step) => ({
|
|
162
|
+
...acc,
|
|
163
|
+
...stepper.metadata.get(step.id) || {}
|
|
164
|
+
}), {}),
|
|
165
|
+
...data
|
|
166
|
+
});
|
|
167
|
+
} catch {} finally {
|
|
168
|
+
setIsSubmitting(false);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
const nextStepId = stepper.lookup.getNext(stepper.state.current.data.id)?.id;
|
|
172
|
+
if (nextStepId) {
|
|
173
|
+
stepper.navigation.goTo(nextStepId);
|
|
174
|
+
onStepChange?.(nextStepId, "next");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, [
|
|
178
|
+
stepper,
|
|
179
|
+
steps,
|
|
180
|
+
onComplete,
|
|
181
|
+
onStepChange
|
|
182
|
+
]);
|
|
183
|
+
const instance = adapter.useCreateForm({
|
|
184
|
+
schema: currentStepConfig.schema,
|
|
185
|
+
defaultValues: storedValues,
|
|
186
|
+
mode: "onSubmit",
|
|
187
|
+
id: `${id ?? "stepper"}-${currentStepConfig.id}`,
|
|
188
|
+
onSubmit: handleStepSubmit,
|
|
189
|
+
formRef
|
|
190
|
+
});
|
|
191
|
+
const next = React$1.useCallback(() => {
|
|
192
|
+
formRef.current?.requestSubmit();
|
|
193
|
+
}, []);
|
|
194
|
+
const prev = React$1.useCallback(() => {
|
|
195
|
+
const currentValues = instance.getValues();
|
|
196
|
+
if (Object.keys(currentValues).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentValues);
|
|
197
|
+
const prevStepId = stepper.lookup.getPrev(stepper.state.current.data.id)?.id;
|
|
198
|
+
if (prevStepId) {
|
|
199
|
+
stepper.navigation.goTo(prevStepId);
|
|
200
|
+
onStepChange?.(prevStepId, "prev");
|
|
201
|
+
}
|
|
202
|
+
}, [
|
|
203
|
+
instance,
|
|
204
|
+
stepper,
|
|
205
|
+
onStepChange
|
|
206
|
+
]);
|
|
207
|
+
const goTo = React$1.useCallback((stepId) => {
|
|
208
|
+
if (stepper.lookup.getIndex(stepId) < currentIndex) {
|
|
209
|
+
const currentValues = instance.getValues();
|
|
210
|
+
if (Object.keys(currentValues).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentValues);
|
|
211
|
+
stepper.navigation.goTo(stepId);
|
|
212
|
+
onStepChange?.(stepId, "prev");
|
|
213
|
+
}
|
|
214
|
+
}, [
|
|
215
|
+
instance,
|
|
216
|
+
stepper,
|
|
217
|
+
currentIndex,
|
|
218
|
+
onStepChange
|
|
219
|
+
]);
|
|
220
|
+
const getStepData = React$1.useCallback((stepId) => stepper.metadata.get(stepId), [stepper]);
|
|
221
|
+
const getAllStepData = React$1.useCallback(() => {
|
|
222
|
+
return steps.reduce((acc, step) => ({
|
|
223
|
+
...acc,
|
|
224
|
+
...stepper.metadata.get(step.id) || {}
|
|
225
|
+
}), {});
|
|
226
|
+
}, [steps, stepper]);
|
|
227
|
+
const stepperContextValue = React$1.useMemo(() => ({
|
|
228
|
+
steps,
|
|
229
|
+
current: currentStepConfig,
|
|
230
|
+
currentIndex,
|
|
231
|
+
next,
|
|
232
|
+
prev,
|
|
233
|
+
goTo,
|
|
234
|
+
isFirst: stepper.state.isFirst,
|
|
235
|
+
isLast: stepper.state.isLast,
|
|
236
|
+
getStepData,
|
|
237
|
+
getAllStepData,
|
|
238
|
+
utils: { getIndex: (stepId) => stepper.lookup.getIndex(stepId) }
|
|
239
|
+
}), [
|
|
240
|
+
steps,
|
|
241
|
+
currentStepConfig,
|
|
242
|
+
currentIndex,
|
|
243
|
+
stepper,
|
|
244
|
+
next,
|
|
245
|
+
prev,
|
|
246
|
+
goTo,
|
|
247
|
+
getStepData,
|
|
248
|
+
getAllStepData
|
|
249
|
+
]);
|
|
250
|
+
const contextValue = React$1.useMemo(() => ({
|
|
251
|
+
form: instance,
|
|
252
|
+
fields: instance.fields,
|
|
253
|
+
isSubmitting,
|
|
254
|
+
isDirty: instance.formState.isDirty,
|
|
255
|
+
isValid: instance.formState.isValid,
|
|
256
|
+
isSubmitted: instance.formState.isSubmitted,
|
|
257
|
+
submitCount: instance.formState.submitCount,
|
|
258
|
+
dirtyFields: instance.formState.dirtyFields,
|
|
259
|
+
touchedFields: instance.formState.touchedFields,
|
|
260
|
+
submit: () => formRef.current?.requestSubmit(),
|
|
261
|
+
reset: () => instance.reset(),
|
|
262
|
+
formId: instance.id
|
|
263
|
+
}), [
|
|
264
|
+
instance,
|
|
265
|
+
isSubmitting,
|
|
266
|
+
instance.formState
|
|
267
|
+
]);
|
|
268
|
+
const renderProps = {
|
|
269
|
+
steps,
|
|
270
|
+
current: currentStepConfig,
|
|
271
|
+
currentIndex,
|
|
272
|
+
next,
|
|
273
|
+
prev,
|
|
274
|
+
goTo,
|
|
275
|
+
isFirst: stepper.state.isFirst,
|
|
276
|
+
isLast: stepper.state.isLast,
|
|
277
|
+
getStepData,
|
|
278
|
+
getAllStepData
|
|
279
|
+
};
|
|
280
|
+
const resolvedChildren = typeof children === "function" ? children(renderProps) : children;
|
|
281
|
+
return /* @__PURE__ */ jsx(FormStepperContext, {
|
|
282
|
+
value: stepperContextValue,
|
|
283
|
+
children: /* @__PURE__ */ jsx(FormProvider, {
|
|
284
|
+
value: contextValue,
|
|
285
|
+
children: /* @__PURE__ */ jsx(adapter.FormProvider, {
|
|
286
|
+
instance,
|
|
287
|
+
children: /* @__PURE__ */ jsx(FormComp, {
|
|
288
|
+
ref: formRef,
|
|
289
|
+
...instance.formProps,
|
|
290
|
+
className: cn("space-y-6", className),
|
|
291
|
+
autoComplete: "off",
|
|
292
|
+
noValidate: true,
|
|
293
|
+
onSubmit: (e) => {
|
|
294
|
+
e.stopPropagation();
|
|
295
|
+
const adapterSubmit = instance.formProps.onSubmit;
|
|
296
|
+
adapterSubmit?.(e);
|
|
297
|
+
},
|
|
298
|
+
children: resolvedChildren
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/components/features/form/components/stepper/form-step.tsx
|
|
306
|
+
/**
|
|
307
|
+
* Form.Step - Individual step content container
|
|
308
|
+
*
|
|
309
|
+
* Only renders its children when the step is active.
|
|
310
|
+
* Works with the single-form architecture - fields remain registered
|
|
311
|
+
* even when unmounted, preserving their values.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```tsx
|
|
315
|
+
* <Form.Step id="account">
|
|
316
|
+
* <Form.Field name="email" label="Email" required>
|
|
317
|
+
* <Form.Input type="email" />
|
|
318
|
+
* </Form.Field>
|
|
319
|
+
* </Form.Step>
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
function FormStep({ id, children }) {
|
|
323
|
+
const { current } = useFormStepperContext();
|
|
324
|
+
if (current.id !== id) return null;
|
|
325
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
326
|
+
}
|
|
327
|
+
FormStep.displayName = "Form.Step";
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/components/features/form/components/stepper/stepper-controls.tsx
|
|
330
|
+
/**
|
|
331
|
+
* Form.StepperControls - Navigation buttons (Previous/Next/Submit)
|
|
332
|
+
*
|
|
333
|
+
* Provides Previous and Next/Submit buttons for navigating between steps.
|
|
334
|
+
* The Next button triggers form validation before advancing.
|
|
335
|
+
* The Previous button navigates back without validation.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```tsx
|
|
339
|
+
* <Form.StepperControls
|
|
340
|
+
* prevLabel={(isFirst) => isFirst ? 'Cancel' : 'Previous'}
|
|
341
|
+
* nextLabel={(isLast) => isLast ? 'Submit' : 'Next'}
|
|
342
|
+
* loadingText="Creating..."
|
|
343
|
+
* onCancel={() => setOpen(false)}
|
|
344
|
+
* />
|
|
345
|
+
* ```
|
|
346
|
+
*
|
|
347
|
+
* @example With external loading state
|
|
348
|
+
* ```tsx
|
|
349
|
+
* <Form.StepperControls
|
|
350
|
+
* loading={fetcher.state === 'submitting'}
|
|
351
|
+
* disabled={!isValid}
|
|
352
|
+
* loadingText="Saving..."
|
|
353
|
+
* />
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
function StepperControls({ prevLabel = "Previous", nextLabel = (isLast) => isLast ? "Submit" : "Next", loadingText = "Submitting...", showPrev = true, loading, disabled, onPrev, onCancel, className }) {
|
|
357
|
+
const { prev, isFirst, isLast } = useFormStepperContext();
|
|
358
|
+
const { isSubmitting: formIsSubmitting } = useFormContext();
|
|
359
|
+
const isLoading = loading ?? formIsSubmitting;
|
|
360
|
+
const isDisabled = disabled ?? false;
|
|
361
|
+
const getPrevLabel = () => {
|
|
362
|
+
if (typeof prevLabel === "function") return prevLabel(isFirst);
|
|
363
|
+
return prevLabel;
|
|
364
|
+
};
|
|
365
|
+
const getNextLabel = () => {
|
|
366
|
+
if (typeof nextLabel === "function") return nextLabel(isLast);
|
|
367
|
+
return nextLabel;
|
|
368
|
+
};
|
|
369
|
+
const handlePrev = () => {
|
|
370
|
+
if (isFirst && onCancel) onCancel();
|
|
371
|
+
else {
|
|
372
|
+
onPrev?.();
|
|
373
|
+
prev();
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
377
|
+
className: cn("flex items-center justify-between gap-3", className),
|
|
378
|
+
children: [/* @__PURE__ */ jsx("div", { children: showPrev && /* @__PURE__ */ jsx(Button, {
|
|
379
|
+
htmlType: "button",
|
|
380
|
+
type: "quaternary",
|
|
381
|
+
theme: "outline",
|
|
382
|
+
size: "small",
|
|
383
|
+
onClick: handlePrev,
|
|
384
|
+
disabled: isLoading || isDisabled,
|
|
385
|
+
children: getPrevLabel()
|
|
386
|
+
}) }), /* @__PURE__ */ jsx(Button, {
|
|
387
|
+
htmlType: "submit",
|
|
388
|
+
type: "primary",
|
|
389
|
+
size: "small",
|
|
390
|
+
loading: isLoading,
|
|
391
|
+
disabled: isLoading || isDisabled,
|
|
392
|
+
children: isLoading && isLast ? loadingText : getNextLabel()
|
|
393
|
+
})]
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
StepperControls.displayName = "Form.StepperControls";
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/components/features/form/components/stepper/stepper-navigation.tsx
|
|
399
|
+
/**
|
|
400
|
+
* Form.StepperNavigation - Step indicators/progress
|
|
401
|
+
*
|
|
402
|
+
* Displays visual step indicators showing current progress through the form.
|
|
403
|
+
* Supports horizontal and vertical variants with optional label orientation.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```tsx
|
|
407
|
+
* <Form.StepperNavigation variant="horizontal" labelOrientation="vertical" />
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
function StepperNavigation({ variant = "horizontal", labelOrientation = "vertical", className }) {
|
|
411
|
+
const { steps, currentIndex } = useFormStepperContext();
|
|
412
|
+
if (variant === "horizontal" && labelOrientation === "vertical") return /* @__PURE__ */ jsx("nav", {
|
|
413
|
+
"aria-label": "Form steps",
|
|
414
|
+
className: cn("flex flex-row items-start justify-between", className),
|
|
415
|
+
children: steps.map((step, index) => {
|
|
416
|
+
const isActive = index === currentIndex;
|
|
417
|
+
const isCompleted = index < currentIndex;
|
|
418
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
419
|
+
className: "relative flex flex-1 flex-col items-center",
|
|
420
|
+
children: [
|
|
421
|
+
!(index === steps.length - 1) && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line absolute top-4 right-[calc(-50%+20px)] left-[calc(50%+20px)] h-0.5" }),
|
|
422
|
+
/* @__PURE__ */ jsx("div", {
|
|
423
|
+
className: cn("relative z-10 flex h-8 w-8 items-center justify-center rounded-full border bg-transparent text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
|
|
424
|
+
"aria-current": isActive ? "step" : void 0,
|
|
425
|
+
children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary h-4 w-4" }) : index + 1
|
|
426
|
+
}),
|
|
427
|
+
/* @__PURE__ */ jsxs("div", {
|
|
428
|
+
className: "mt-1",
|
|
429
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
430
|
+
className: cn("text-xs font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
|
|
431
|
+
children: step.label
|
|
432
|
+
}), step.description && /* @__PURE__ */ jsx("p", {
|
|
433
|
+
className: "text-muted-foreground mt-0.5 text-xs",
|
|
434
|
+
children: step.description
|
|
435
|
+
})]
|
|
436
|
+
})
|
|
437
|
+
]
|
|
438
|
+
}, step.id);
|
|
439
|
+
})
|
|
440
|
+
});
|
|
441
|
+
if (variant === "horizontal") return /* @__PURE__ */ jsx("nav", {
|
|
442
|
+
"aria-label": "Form steps",
|
|
443
|
+
className: cn("flex flex-row items-center", className),
|
|
444
|
+
children: steps.map((step, index) => {
|
|
445
|
+
const isActive = index === currentIndex;
|
|
446
|
+
const isCompleted = index < currentIndex;
|
|
447
|
+
const isLast = index === steps.length - 1;
|
|
448
|
+
return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
449
|
+
className: "flex items-center",
|
|
450
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
451
|
+
className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
|
|
452
|
+
"aria-current": isActive ? "step" : void 0,
|
|
453
|
+
children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
|
|
454
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
455
|
+
className: "ml-2",
|
|
456
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
457
|
+
className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
|
|
458
|
+
children: step.label
|
|
459
|
+
})
|
|
460
|
+
})]
|
|
461
|
+
}), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line mx-4 h-0.5 min-w-8 flex-1" })] }, step.id);
|
|
462
|
+
})
|
|
463
|
+
});
|
|
464
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
465
|
+
"aria-label": "Form steps",
|
|
466
|
+
className: cn("flex flex-col", className),
|
|
467
|
+
children: steps.map((step, index) => {
|
|
468
|
+
const isActive = index === currentIndex;
|
|
469
|
+
const isCompleted = index < currentIndex;
|
|
470
|
+
const isLast = index === steps.length - 1;
|
|
471
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
472
|
+
className: "flex flex-row",
|
|
473
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
474
|
+
className: "flex flex-col items-center",
|
|
475
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
476
|
+
className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
|
|
477
|
+
"aria-current": isActive ? "step" : void 0,
|
|
478
|
+
children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
|
|
479
|
+
}), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line my-1 min-h-8 w-0.5 flex-1" })]
|
|
480
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
481
|
+
className: "ml-3 pb-8",
|
|
482
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
483
|
+
className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
|
|
484
|
+
children: step.label
|
|
485
|
+
}), step.description && /* @__PURE__ */ jsx("p", {
|
|
486
|
+
className: "text-muted-foreground mt-0.5 text-xs",
|
|
487
|
+
children: step.description
|
|
488
|
+
})]
|
|
489
|
+
})]
|
|
490
|
+
}, step.id);
|
|
491
|
+
})
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
StepperNavigation.displayName = "Form.StepperNavigation";
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/components/features/form/hooks/use-stepper.ts
|
|
497
|
+
/**
|
|
498
|
+
* Hook to access the stepper context
|
|
499
|
+
* Must be used within a Form.Stepper component
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```tsx
|
|
503
|
+
* function StepContent() {
|
|
504
|
+
* const {
|
|
505
|
+
* current,
|
|
506
|
+
* currentIndex,
|
|
507
|
+
* steps,
|
|
508
|
+
* next,
|
|
509
|
+
* prev,
|
|
510
|
+
* goTo,
|
|
511
|
+
* isFirst,
|
|
512
|
+
* isLast,
|
|
513
|
+
* } = useStepper();
|
|
514
|
+
*
|
|
515
|
+
* return (
|
|
516
|
+
* <div>
|
|
517
|
+
* <h2>Step {currentIndex + 1}: {current.label}</h2>
|
|
518
|
+
* <button onClick={prev} disabled={isFirst}>Previous</button>
|
|
519
|
+
* <button onClick={next} disabled={isLast}>Next</button>
|
|
520
|
+
* </div>
|
|
521
|
+
* );
|
|
522
|
+
* }
|
|
523
|
+
* ```
|
|
524
|
+
*/
|
|
525
|
+
function useStepper() {
|
|
526
|
+
const context = useFormStepperContext();
|
|
527
|
+
return {
|
|
528
|
+
steps: context.steps,
|
|
529
|
+
current: context.current,
|
|
530
|
+
currentIndex: context.currentIndex,
|
|
531
|
+
next: context.next,
|
|
532
|
+
prev: context.prev,
|
|
533
|
+
goTo: context.goTo,
|
|
534
|
+
isFirst: context.isFirst,
|
|
535
|
+
isLast: context.isLast,
|
|
536
|
+
getStepData: context.getStepData,
|
|
537
|
+
getAllStepData: context.getAllStepData
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
export { FormStep, FormStepper, StepperControls, StepperNavigation, useStepper };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region src/components/features/form/context/form-context.tsx
|
|
4
|
+
const FormContext = React$1.createContext(null);
|
|
5
|
+
function FormProvider({ children, value }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(FormContext, {
|
|
7
|
+
value,
|
|
8
|
+
children
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function useFormContext() {
|
|
12
|
+
const context = React$1.use(FormContext);
|
|
13
|
+
if (!context) throw new Error("useFormContext must be used within a Form.Root component");
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useFormContext as n, FormProvider as t };
|