@datum-cloud/datum-ui 0.5.0 → 0.6.0-alpha.a49f238

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