@datum-cloud/datum-ui 0.2.0-alpha.3 → 0.2.0-alpha.4

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 (132) hide show
  1. package/README.md +46 -21
  2. package/dist/alert/index.mjs +3 -0
  3. package/dist/alert-BC2Mccfo.mjs +95 -0
  4. package/dist/autocomplete/index.mjs +7 -0
  5. package/dist/autocomplete-DZtI97HP.mjs +295 -0
  6. package/dist/avatar-stack/index.mjs +5 -0
  7. package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
  8. package/dist/badge/index.mjs +3 -0
  9. package/dist/badge-bFgeYceE.mjs +185 -0
  10. package/dist/breadcrumb/index.mjs +4 -0
  11. package/dist/breadcrumb-BGYJgom_.mjs +71 -0
  12. package/dist/button/index.mjs +4 -0
  13. package/dist/button-AzpnV-WB.mjs +49 -0
  14. package/dist/button-C1wRfGtT.mjs +230 -0
  15. package/dist/button-group/index.mjs +5 -0
  16. package/dist/button-group-C1IB2K5s.mjs +40 -0
  17. package/dist/calendar/index.mjs +5 -0
  18. package/dist/calendar-DlIHeWb0.mjs +113 -0
  19. package/dist/card/index.mjs +4 -0
  20. package/dist/card-3Kd0VdNf.mjs +63 -0
  21. package/dist/chart/index.mjs +4 -0
  22. package/dist/chart-BZqUKpkh.mjs +143 -0
  23. package/dist/checkbox/index.mjs +4 -0
  24. package/dist/checkbox-LG1OKTpG.mjs +34 -0
  25. package/dist/col-lrLMZaTJ.mjs +184 -0
  26. package/dist/collapsible/index.mjs +3 -0
  27. package/dist/collapsible-Bt9UYfv3.mjs +9 -0
  28. package/dist/command/index.mjs +5 -0
  29. package/dist/command-s0Yv3abE.mjs +86 -0
  30. package/dist/components/features/date-picker/index.d.ts +3 -0
  31. package/dist/components/features/date-picker/index.d.ts.map +1 -0
  32. package/dist/components/features/dropzone/index.d.ts +1 -0
  33. package/dist/components/features/dropzone/index.d.ts.map +1 -1
  34. package/dist/date-picker/index.mjs +9 -0
  35. package/dist/{datum.provider-D6VMjSV0.mjs → datum.provider-B77goJgl.mjs} +1 -1
  36. package/dist/dialog/index.mjs +5 -0
  37. package/dist/dialog-DXBaT9gA.mjs +86 -0
  38. package/dist/dialog-bnMMf9GD.mjs +73 -0
  39. package/dist/dropdown/index.mjs +3 -0
  40. package/dist/dropdown-DtSa_lqc.mjs +112 -0
  41. package/dist/dropzone/index.mjs +5 -0
  42. package/dist/dropzone-BkOnwrS4.mjs +221 -0
  43. package/dist/empty-content/index.mjs +3 -0
  44. package/dist/empty-content-BM9rzI13.mjs +196 -0
  45. package/dist/exports/map.d.ts +3 -0
  46. package/dist/exports/map.d.ts.map +1 -0
  47. package/dist/form/index.mjs +146 -0
  48. package/dist/grid/index.mjs +3 -0
  49. package/dist/hooks/index.mjs +2 -3
  50. package/dist/hover-card/index.mjs +4 -0
  51. package/dist/hover-card-CUPfFUqE.mjs +33 -0
  52. package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
  53. package/dist/icons/index.mjs +3 -3
  54. package/dist/index.mjs +66 -8
  55. package/dist/input/index.mjs +5 -0
  56. package/dist/input-DuyjEKEW.mjs +17 -0
  57. package/dist/input-fzXBheCN.mjs +17 -0
  58. package/dist/input-group/index.mjs +7 -0
  59. package/dist/input-group-CPaFSTEV.mjs +80 -0
  60. package/dist/input-number/index.mjs +6 -0
  61. package/dist/input-number-9o62JHRl.mjs +106 -0
  62. package/dist/input-with-addons/index.mjs +3 -0
  63. package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
  64. package/dist/label/index.mjs +4 -0
  65. package/dist/label-_ste_Re3.mjs +44 -0
  66. package/dist/link-button-TIF2Zdrk.mjs +36 -0
  67. package/dist/loader-overlay/index.mjs +3 -0
  68. package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
  69. package/dist/map/index.mjs +13 -0
  70. package/dist/map-Df8QMcX0.mjs +1094 -0
  71. package/dist/more-actions/index.mjs +5 -0
  72. package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
  73. package/dist/nprogress/index.mjs +32 -0
  74. package/dist/page-title/index.mjs +3 -0
  75. package/dist/page-title-BJuo81rT.mjs +26 -0
  76. package/dist/popover/index.mjs +4 -0
  77. package/dist/popover-SQlKSz6L.mjs +36 -0
  78. package/dist/provider/index.mjs +4 -0
  79. package/dist/radio-group/index.mjs +4 -0
  80. package/dist/radio-group-Oshv0b-U.mjs +49 -0
  81. package/dist/select/index.mjs +4 -0
  82. package/dist/select-DVlEzD2W.mjs +166 -0
  83. package/dist/separator/index.mjs +4 -0
  84. package/dist/separator-T2ppyD-8.mjs +18 -0
  85. package/dist/sheet/index.mjs +5 -0
  86. package/dist/sheet-BKiCwtNO.mjs +45 -0
  87. package/dist/sheet-CtnP6gTD.mjs +77 -0
  88. package/dist/sidebar/index.mjs +11 -0
  89. package/dist/sidebar-DfqezV8t.mjs +945 -0
  90. package/dist/skeleton/index.mjs +4 -0
  91. package/dist/skeleton-vzbxA-DQ.mjs +13 -0
  92. package/dist/spinner/index.mjs +4 -0
  93. package/dist/spinner-BE7k2bAD.mjs +16 -0
  94. package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
  95. package/dist/stepper/index.mjs +5 -0
  96. package/dist/stepper-SWB-u_nM.mjs +323 -0
  97. package/dist/switch/index.mjs +4 -0
  98. package/dist/switch-Calk7Gyw.mjs +32 -0
  99. package/dist/table/index.mjs +4 -0
  100. package/dist/table-CsXBcQLI.mjs +68 -0
  101. package/dist/tabs/index.mjs +3 -0
  102. package/dist/tabs-D8n-dqnw.mjs +52 -0
  103. package/dist/tag-input/index.mjs +5 -0
  104. package/dist/tag-input-Di7SDNbK.mjs +284 -0
  105. package/dist/task-queue/index.mjs +7 -0
  106. package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
  107. package/dist/textarea/index.mjs +5 -0
  108. package/dist/textarea-CxE3YbC7.mjs +17 -0
  109. package/dist/textarea-QYRcDEpK.mjs +15 -0
  110. package/dist/theme/index.mjs +4 -0
  111. package/dist/theme-script-XBouzsNR.mjs +66 -0
  112. package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
  113. package/dist/toast/index.mjs +3 -0
  114. package/dist/tooltip/index.mjs +4 -0
  115. package/dist/tooltip-Dd3ActSS.mjs +74 -0
  116. package/dist/typography/index.mjs +3 -0
  117. package/dist/typography-UA7ZZvgJ.mjs +200 -0
  118. package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
  119. package/dist/use-stepper-BaToCYMs.mjs +2017 -0
  120. package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
  121. package/dist/utils/index.mjs +0 -1
  122. package/dist/utils-Bfgoe-Gm.mjs +20 -0
  123. package/dist/visually-hidden/index.mjs +3 -0
  124. package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
  125. package/package.json +208 -8
  126. package/dist/components/index.mjs +0 -8
  127. package/dist/providers/index.mjs +0 -4
  128. package/dist/theme-script-DHyLk25i.mjs +0 -11128
  129. /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
  130. /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-CdzvEnzY.mjs} +0 -0
  131. /package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-DgGshapa.mjs} +0 -0
  132. /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-DQ1tmxOL.mjs} +0 -0
@@ -0,0 +1,2017 @@
1
+ import { t as cn } from "./cn-DWCc1QRE.mjs";
2
+ import { t as Button } from "./button-C1wRfGtT.mjs";
3
+ import { t as Icon } from "./icon-wrapper-9ticVbRL.mjs";
4
+ import { t as Checkbox } from "./checkbox-LG1OKTpG.mjs";
5
+ import { t as Dialog } from "./dialog-bnMMf9GD.mjs";
6
+ import { t as Input } from "./input-fzXBheCN.mjs";
7
+ import { t as Label } from "./label-_ste_Re3.mjs";
8
+ import { n as RadioGroupItem, t as RadioGroup } from "./radio-group-Oshv0b-U.mjs";
9
+ import { i as SelectItem, l as SelectTrigger, n as SelectContent, t as Select, u as SelectValue } from "./select-DVlEzD2W.mjs";
10
+ import { t as Switch } from "./switch-Calk7Gyw.mjs";
11
+ import { t as Textarea } from "./textarea-CxE3YbC7.mjs";
12
+ import { t as Tooltip } from "./tooltip-Dd3ActSS.mjs";
13
+ import { t as Autocomplete } from "./autocomplete-DZtI97HP.mjs";
14
+ import { r as toast } from "./use-toast-mdn_CqRY.mjs";
15
+ import { t as useCopyToClipboard } from "./use-copy-to-clipboard-ki-WoTml.mjs";
16
+ import { t as defineStepper } from "./stepper-SWB-u_nM.mjs";
17
+ import { t as InputWithAddons } from "./input-with-addons-BQn7KCTU.mjs";
18
+ import { Form } from "./form/index.mjs";
19
+ import { CheckIcon, CircleHelp, CopyIcon } from "lucide-react";
20
+ import * as React$1 from "react";
21
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
22
+ import { FormProvider, getFormProps, getInputProps, getTextareaProps, useForm, useFormMetadata, useInputControl } from "@conform-to/react";
23
+ import { getZodConstraint, parseWithZod } from "@conform-to/zod/v4";
24
+ import { z } from "zod";
25
+
26
+ //#region src/components/features/form/context/field-context.tsx
27
+ const FieldContext = React$1.createContext(null);
28
+ function FieldProvider({ children, value }) {
29
+ return /* @__PURE__ */ jsx(FieldContext, {
30
+ value,
31
+ children
32
+ });
33
+ }
34
+ function useFieldContext$1() {
35
+ const context = React$1.use(FieldContext);
36
+ if (!context) throw new Error("useFieldContext must be used within a Form.Field component. Make sure your input component is wrapped with Form.Field.");
37
+ return context;
38
+ }
39
+ /**
40
+ * Optional field context - returns null if not within a Form.Field
41
+ * Useful for components that can work both inside and outside Form.Field
42
+ */
43
+ function useOptionalFieldContext() {
44
+ return React$1.use(FieldContext);
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/components/features/form/components/form-autocomplete.tsx
49
+ /**
50
+ * Form.Autocomplete - Searchable select component
51
+ *
52
+ * Automatically wired to the parent Form.Field context.
53
+ * Supports flat/grouped options, virtualization, custom rendering, and async search.
54
+ *
55
+ * @example Basic usage
56
+ * ```tsx
57
+ * <Form.Field name="timezone" label="Timezone" required>
58
+ * <Form.Autocomplete
59
+ * options={timezones}
60
+ * placeholder="Select timezone..."
61
+ * />
62
+ * </Form.Field>
63
+ * ```
64
+ *
65
+ * @example Async search
66
+ * ```tsx
67
+ * <Form.Field name="userId" label="User">
68
+ * <Form.Autocomplete
69
+ * options={users ?? []}
70
+ * onSearchChange={setSearch}
71
+ * loading={isLoading}
72
+ * placeholder="Search users..."
73
+ * />
74
+ * </Form.Field>
75
+ * ```
76
+ *
77
+ * @example Grouped options
78
+ * ```tsx
79
+ * <Form.Field name="role" label="Role" required>
80
+ * <Form.Autocomplete
81
+ * options={roleGroups}
82
+ * placeholder="Select a role..."
83
+ * />
84
+ * </Form.Field>
85
+ * ```
86
+ */
87
+ function FormAutocomplete({ disabled, className, ...props }) {
88
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
89
+ const control = useInputControl(fieldMeta);
90
+ const isDisabled = disabled ?? fieldDisabled;
91
+ const hasErrors = errors && errors.length > 0;
92
+ const selectValue = Array.isArray(control.value) ? control.value[0] : control.value;
93
+ return /* @__PURE__ */ jsx(Autocomplete, {
94
+ ...props,
95
+ name: fieldMeta.name,
96
+ id: fieldMeta.id,
97
+ value: selectValue ?? "",
98
+ onValueChange: control.change,
99
+ disabled: isDisabled,
100
+ triggerClassName: cn(hasErrors && "border-destructive", props.triggerClassName),
101
+ className
102
+ });
103
+ }
104
+ FormAutocomplete.displayName = "Form.Autocomplete";
105
+
106
+ //#endregion
107
+ //#region src/components/features/form/context/form-context.tsx
108
+ const FormContext = React$1.createContext(null);
109
+ function FormProvider$1({ children, value }) {
110
+ return /* @__PURE__ */ jsx(FormContext, {
111
+ value,
112
+ children
113
+ });
114
+ }
115
+ function useFormContext$1() {
116
+ const context = React$1.use(FormContext);
117
+ if (!context) throw new Error("useFormContext must be used within a Form.Root component");
118
+ return context;
119
+ }
120
+
121
+ //#endregion
122
+ //#region src/components/features/form/components/form-button.tsx
123
+ /**
124
+ * Form.Button - A button for non-submit actions within a form
125
+ *
126
+ * Automatically gets disabled when the form is submitting.
127
+ * Use this for cancel buttons, reset buttons, or other actions.
128
+ *
129
+ * @example
130
+ * ```tsx
131
+ * <Form.Button onClick={() => navigate(-1)}>
132
+ * Cancel
133
+ * </Form.Button>
134
+ *
135
+ * <Form.Button onClick={() => form.reset()} type="secondary">
136
+ * Reset
137
+ * </Form.Button>
138
+ * ```
139
+ */
140
+ function FormButton({ children, onClick, type = "quaternary", theme = "borderless", size, disabled, className, disableOnSubmit = true }) {
141
+ const { isSubmitting } = useFormContext$1();
142
+ return /* @__PURE__ */ jsx(Button, {
143
+ htmlType: "button",
144
+ type,
145
+ theme,
146
+ size,
147
+ disabled: disabled || disableOnSubmit && isSubmitting,
148
+ className,
149
+ onClick,
150
+ children
151
+ });
152
+ }
153
+ FormButton.displayName = "Form.Button";
154
+
155
+ //#endregion
156
+ //#region src/components/features/form/components/form-checkbox.tsx
157
+ /**
158
+ * Form.Checkbox - Checkbox input component
159
+ *
160
+ * Automatically wired to the parent Form.Field context.
161
+ *
162
+ * @example
163
+ * ```tsx
164
+ * <Form.Field name="terms">
165
+ * <Form.Checkbox label="I agree to the terms and conditions" />
166
+ * </Form.Field>
167
+ * ```
168
+ */
169
+ function FormCheckbox({ label, disabled, className }) {
170
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
171
+ const control = useInputControl(fieldMeta);
172
+ const isDisabled = disabled ?? fieldDisabled;
173
+ const hasErrors = errors && errors.length > 0;
174
+ const isChecked = control.value === "on" || control.value === "true";
175
+ const handleCheckedChange = (checked) => {
176
+ control.change(checked ? "on" : "");
177
+ };
178
+ const checkboxId = fieldMeta.id;
179
+ return /* @__PURE__ */ jsxs("div", {
180
+ className: cn("flex items-center space-x-2", className),
181
+ children: [/* @__PURE__ */ jsx(Checkbox, {
182
+ id: checkboxId,
183
+ name: fieldMeta.name,
184
+ checked: isChecked,
185
+ onCheckedChange: handleCheckedChange,
186
+ disabled: isDisabled,
187
+ "aria-invalid": hasErrors || void 0,
188
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0
189
+ }), label && /* @__PURE__ */ jsx(Label, {
190
+ htmlFor: checkboxId,
191
+ className: cn("cursor-pointer text-sm font-normal", isDisabled && "cursor-not-allowed opacity-70"),
192
+ children: label
193
+ })]
194
+ });
195
+ }
196
+ FormCheckbox.displayName = "Form.Checkbox";
197
+
198
+ //#endregion
199
+ //#region src/components/features/form/components/form-copy-box.tsx
200
+ /**
201
+ * Form.CopyBox - Read-only field with copy-to-clipboard functionality
202
+ *
203
+ * Displays field value in a read-only box with a copy button.
204
+ * Automatically gets value from Form.Field context.
205
+ *
206
+ * @example Basic usage
207
+ * ```tsx
208
+ * <Form.Field name="organizationId" label="Organization ID">
209
+ * <Form.CopyBox />
210
+ * </Form.Field>
211
+ * ```
212
+ *
213
+ * @example With icon-only button
214
+ * ```tsx
215
+ * <Form.Field name="apiKey" label="API Key">
216
+ * <Form.CopyBox variant="icon-only" />
217
+ * </Form.Field>
218
+ * ```
219
+ *
220
+ * @example With placeholder
221
+ * ```tsx
222
+ * <Form.Field name="webhookUrl" label="Webhook URL">
223
+ * <Form.CopyBox placeholder="Not configured" />
224
+ * </Form.Field>
225
+ * ```
226
+ */
227
+ function FormCopyBox({ variant = "default", className, contentClassName, buttonClassName, placeholder = "" }) {
228
+ const { fieldMeta } = useFieldContext$1();
229
+ const control = useInputControl(fieldMeta);
230
+ const [_, copy] = useCopyToClipboard();
231
+ const [copied, setCopied] = React$1.useState(false);
232
+ const value = control.value ?? placeholder;
233
+ const copyToClipboard = () => {
234
+ const stringValue = String(value);
235
+ if (!stringValue) return;
236
+ copy(stringValue).then(() => {
237
+ toast.success("Copied to clipboard");
238
+ setCopied(true);
239
+ setTimeout(() => {
240
+ setCopied(false);
241
+ }, 2e3);
242
+ });
243
+ };
244
+ return /* @__PURE__ */ jsxs("div", {
245
+ className: cn("group border-input flex h-10 w-full overflow-hidden rounded-lg border bg-[#F6F6F580] text-xs focus-within:outline-hidden", className),
246
+ children: [/* @__PURE__ */ jsx("div", {
247
+ className: cn("flex w-full items-center overflow-hidden px-3 py-2 text-xs opacity-50", contentClassName),
248
+ children: /* @__PURE__ */ jsx("span", {
249
+ className: "truncate",
250
+ children: String(value)
251
+ })
252
+ }), /* @__PURE__ */ jsx("div", {
253
+ className: "flex items-center py-2 pr-3",
254
+ children: variant === "icon-only" ? /* @__PURE__ */ jsx("button", {
255
+ type: "button",
256
+ className: cn("text-muted-foreground hover:text-foreground flex size-7 items-center justify-center rounded-sm transition-colors", buttonClassName),
257
+ onClick: copyToClipboard,
258
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(CopyIcon, { className: "size-4" })
259
+ }) : /* @__PURE__ */ jsxs(Button, {
260
+ type: "quaternary",
261
+ theme: "outline",
262
+ size: "small",
263
+ className: cn("h-7 w-fit gap-1 px-2 text-xs", buttonClassName),
264
+ onClick: copyToClipboard,
265
+ children: [/* @__PURE__ */ jsx(CopyIcon, { className: "size-3!" }), copied ? "Copied" : "Copy"]
266
+ })
267
+ })]
268
+ });
269
+ }
270
+
271
+ //#endregion
272
+ //#region src/components/features/form/components/form-custom.tsx
273
+ /**
274
+ * Form.Custom - Escape hatch for custom implementations
275
+ *
276
+ * Provides access to the underlying form context for complex use cases
277
+ * that don't fit the standard component patterns.
278
+ *
279
+ * @example
280
+ * ```tsx
281
+ * <Form.Custom>
282
+ * {({ form, fields, submit, reset }) => (
283
+ * <MyCustomComponent
284
+ * fields={fields}
285
+ * onCustomAction={() => {
286
+ * // Do something custom
287
+ * submit();
288
+ * }}
289
+ * />
290
+ * )}
291
+ * </Form.Custom>
292
+ * ```
293
+ */
294
+ function FormCustom({ children }) {
295
+ const { form, fields, isSubmitting, submit, reset } = useFormContext$1();
296
+ return /* @__PURE__ */ jsx(Fragment$1, { children: children({
297
+ form,
298
+ fields,
299
+ isSubmitting,
300
+ submit,
301
+ reset
302
+ }) });
303
+ }
304
+ FormCustom.displayName = "Form.Custom";
305
+
306
+ //#endregion
307
+ //#region src/components/features/form/components/form-description.tsx
308
+ /**
309
+ * Form.Description - Display field description/helper text
310
+ *
311
+ * @example
312
+ * ```tsx
313
+ * <Form.Field name="password">
314
+ * <Form.Input type="password" />
315
+ * <Form.Description>
316
+ * Must be at least 8 characters with one uppercase letter
317
+ * </Form.Description>
318
+ * </Form.Field>
319
+ * ```
320
+ */
321
+ function FormDescription({ children, className }) {
322
+ const fieldContext = useOptionalFieldContext();
323
+ return /* @__PURE__ */ jsx("p", {
324
+ id: fieldContext ? `${fieldContext.id}-description` : void 0,
325
+ className: cn("text-muted-foreground text-xs text-wrap", className),
326
+ children
327
+ });
328
+ }
329
+ FormDescription.displayName = "Form.Description";
330
+
331
+ //#endregion
332
+ //#region src/components/features/form/components/form-dialog.tsx
333
+ /**
334
+ * Form.Dialog - A dialog with an integrated form
335
+ *
336
+ * Combines Dialog and Form.Root into a single component with:
337
+ * - Automatic dialog state management (controlled or uncontrolled)
338
+ * - Built-in header with title and description
339
+ * - Built-in footer with submit and cancel buttons
340
+ * - Auto-close on successful submission
341
+ * - Prevents accidental close during submission
342
+ * - Supports render function pattern for form state access
343
+ *
344
+ * @example Basic usage
345
+ * ```tsx
346
+ * <Form.Dialog
347
+ * title="Add User"
348
+ * description="Enter user details"
349
+ * schema={userSchema}
350
+ * onSubmit={handleSubmit}
351
+ * trigger={<Button>Add User</Button>}
352
+ * >
353
+ * <Form.Field name="name" label="Name" required>
354
+ * <Form.Input />
355
+ * </Form.Field>
356
+ * <Form.Field name="email" label="Email" required>
357
+ * <Form.Input type="email" />
358
+ * </Form.Field>
359
+ * </Form.Dialog>
360
+ * ```
361
+ *
362
+ * @example With render function for form state access
363
+ * ```tsx
364
+ * <Form.Dialog
365
+ * title="Edit User"
366
+ * schema={userSchema}
367
+ * defaultValues={user}
368
+ * onSubmit={handleSubmit}
369
+ * trigger={<Button>Edit</Button>}
370
+ * >
371
+ * {({ form, fields, isSubmitting, reset }) => (
372
+ * <>
373
+ * <Form.Field name="name" label="Name">
374
+ * <Form.Input />
375
+ * </Form.Field>
376
+ * <Button variant="ghost" onClick={reset} disabled={isSubmitting}>
377
+ * Reset
378
+ * </Button>
379
+ * </>
380
+ * )}
381
+ * </Form.Dialog>
382
+ * ```
383
+ */
384
+ function FormDialog({ open, onOpenChange, defaultOpen, title, description, trigger, schema, defaultValues, onSubmit, onSuccess, onError, submitText = "Submit", submitTextLoading = "Submitting...", cancelText = "Cancel", showCancel = true, submitType = "primary", loading, formComponent, telemetry, className, formClassName, children }) {
385
+ const [internalOpen, setInternalOpen] = React$1.useState(defaultOpen ?? false);
386
+ const [internalIsSubmitting, setInternalIsSubmitting] = React$1.useState(false);
387
+ const isSubmitting = loading ?? internalIsSubmitting;
388
+ const isControlled = open !== void 0;
389
+ const isOpen = isControlled ? open : internalOpen;
390
+ const handleOpenChange = React$1.useCallback((value) => {
391
+ if (!value && isSubmitting) return;
392
+ if (!isControlled) setInternalOpen(value);
393
+ onOpenChange?.(value);
394
+ }, [
395
+ isControlled,
396
+ isSubmitting,
397
+ onOpenChange
398
+ ]);
399
+ const handleSubmit = React$1.useCallback(async (data) => {
400
+ if (loading === void 0) setInternalIsSubmitting(true);
401
+ try {
402
+ await onSubmit?.(data);
403
+ onSuccess?.(data);
404
+ } catch (error) {
405
+ console.error("Form submission error:", error);
406
+ throw error;
407
+ } finally {
408
+ if (loading === void 0) setInternalIsSubmitting(false);
409
+ }
410
+ }, [
411
+ onSubmit,
412
+ onSuccess,
413
+ loading
414
+ ]);
415
+ const handleCancel = React$1.useCallback(() => {
416
+ handleOpenChange(false);
417
+ }, [handleOpenChange]);
418
+ return /* @__PURE__ */ jsxs(Dialog, {
419
+ open: isOpen,
420
+ onOpenChange: handleOpenChange,
421
+ children: [trigger && /* @__PURE__ */ jsx(Dialog.Trigger, { children: trigger }), /* @__PURE__ */ jsx(Dialog.Content, {
422
+ className,
423
+ children: /* @__PURE__ */ jsx(Form.Root, {
424
+ schema,
425
+ defaultValues,
426
+ onSubmit: handleSubmit,
427
+ onError,
428
+ isSubmitting,
429
+ mode: "onSubmit",
430
+ formComponent,
431
+ telemetry,
432
+ className: cn("space-y-0", formClassName),
433
+ children: (renderProps) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
434
+ /* @__PURE__ */ jsx(Dialog.Header, {
435
+ title,
436
+ description,
437
+ onClose: handleCancel,
438
+ className: "border-b",
439
+ descriptionClassName: "text-foreground/80"
440
+ }),
441
+ /* @__PURE__ */ jsx(Dialog.Body, {
442
+ className: "space-y-0",
443
+ children: typeof children === "function" ? children(renderProps) : children
444
+ }),
445
+ /* @__PURE__ */ jsxs(Dialog.Footer, {
446
+ className: "border-t",
447
+ children: [showCancel && /* @__PURE__ */ jsx(Form.Button, {
448
+ type: "quaternary",
449
+ theme: "outline",
450
+ onClick: handleCancel,
451
+ disableOnSubmit: true,
452
+ children: cancelText
453
+ }), /* @__PURE__ */ jsx(Form.Submit, {
454
+ type: submitType,
455
+ children: isSubmitting ? submitTextLoading : submitText
456
+ })]
457
+ })
458
+ ] })
459
+ })
460
+ })]
461
+ });
462
+ }
463
+ FormDialog.displayName = "Form.Dialog";
464
+
465
+ //#endregion
466
+ //#region src/components/features/form/components/form-error.tsx
467
+ /**
468
+ * Form.Error - Display field errors
469
+ *
470
+ * Can be used inside Form.Field to display errors automatically,
471
+ * or standalone with custom rendering.
472
+ *
473
+ * @example
474
+ * ```tsx
475
+ * // Inside Form.Field - displays field errors automatically
476
+ * <Form.Field name="email">
477
+ * <Form.Input />
478
+ * <Form.Error />
479
+ * </Form.Field>
480
+ *
481
+ * // Custom rendering
482
+ * <Form.Field name="email">
483
+ * <Form.Input />
484
+ * <Form.Error>
485
+ * {(errors) => errors.map(e => <span key={e}>{e}</span>)}
486
+ * </Form.Error>
487
+ * </Form.Field>
488
+ * ```
489
+ */
490
+ function FormError({ children, className }) {
491
+ const errors = useOptionalFieldContext()?.errors;
492
+ if (!errors || errors.length === 0) return null;
493
+ if (typeof children === "function") return /* @__PURE__ */ jsx(Fragment$1, { children: children(errors) });
494
+ return /* @__PURE__ */ jsx("ul", {
495
+ className: cn("text-destructive space-y-1 text-sm font-medium", errors.length > 1 && "list-disc pl-4", className),
496
+ role: "alert",
497
+ "aria-live": "polite",
498
+ children: errors.map((error) => /* @__PURE__ */ jsx("li", {
499
+ className: "text-wrap",
500
+ children: error
501
+ }, error))
502
+ });
503
+ }
504
+ FormError.displayName = "Form.Error";
505
+
506
+ //#endregion
507
+ //#region src/components/features/form/components/form-field.tsx
508
+ /**
509
+ * Internal FieldLabel component with hover-reveal tooltip
510
+ */
511
+ function FieldLabel({ htmlFor, label, hasErrors, required, tooltip, className }) {
512
+ const [isTooltipVisible, setIsTooltipVisible] = React$1.useState(false);
513
+ return /* @__PURE__ */ jsxs("div", {
514
+ className: "relative flex w-fit items-center space-x-2",
515
+ children: [/* @__PURE__ */ jsxs(Label, {
516
+ htmlFor,
517
+ className: cn("text-foreground/80 gap-0 text-xs font-semibold", hasErrors && "text-destructive", className),
518
+ children: [label, required && /* @__PURE__ */ jsx("span", {
519
+ className: "text-destructive/80 align-super text-sm leading-0",
520
+ "aria-hidden": "true",
521
+ children: "*"
522
+ })]
523
+ }), tooltip && /* @__PURE__ */ jsx(Tooltip, {
524
+ message: tooltip,
525
+ open: isTooltipVisible,
526
+ onOpenChange: setIsTooltipVisible,
527
+ side: "bottom",
528
+ contentClassName: "max-w-xs text-wrap",
529
+ children: /* @__PURE__ */ jsx(Icon, {
530
+ icon: CircleHelp,
531
+ className: cn("text-ring absolute top-0.5 -right-3 size-3.5 cursor-pointer transition-opacity duration-400")
532
+ })
533
+ })]
534
+ });
535
+ }
536
+ /**
537
+ * Form.Field - Field wrapper component
538
+ *
539
+ * Provides field context to children with:
540
+ * - Automatic label rendering
541
+ * - Error display
542
+ * - Description text
543
+ * - Required indicator
544
+ * - Accessibility attributes
545
+ *
546
+ * Supports two patterns:
547
+ * 1. ReactNode children - for standard Form inputs
548
+ * 2. Render function - for custom components needing field access
549
+ *
550
+ * @example Standard usage
551
+ * ```tsx
552
+ * <Form.Field name="email" label="Email Address" required>
553
+ * <Form.Input type="email" />
554
+ * </Form.Field>
555
+ * ```
556
+ *
557
+ * @example Render function for custom components
558
+ * ```tsx
559
+ * <Form.Field name="role" label="Role" required>
560
+ * {({ control, meta, fields }) => (
561
+ * <CustomSelect
562
+ * name={meta.name}
563
+ * value={control.value}
564
+ * onChange={control.change}
565
+ * />
566
+ * )}
567
+ * </Form.Field>
568
+ * ```
569
+ */
570
+ function FormField({ name, children, label, description, tooltip, required = false, disabled = false, className, labelClassName }) {
571
+ const { fields, form, isSubmitting } = useFormContext$1();
572
+ const fieldMeta = React$1.useMemo(() => {
573
+ const parts = name.split(".");
574
+ let current = fields;
575
+ for (let i = 0; i < parts.length; i++) {
576
+ const part = parts[i];
577
+ if (!current) break;
578
+ if (/^\d+$/.test(part)) {
579
+ const fieldList = current.getFieldList?.();
580
+ if (fieldList) {
581
+ const item = fieldList[Number.parseInt(part, 10)];
582
+ if (i < parts.length - 1 && item?.getFieldset) current = item.getFieldset();
583
+ else current = item;
584
+ } else current = current[part];
585
+ } else if (current[part] !== void 0) current = current[part];
586
+ else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
587
+ else current = void 0;
588
+ }
589
+ return current;
590
+ }, [fields, name]);
591
+ const errors = fieldMeta?.errors;
592
+ const hasErrors = errors && errors.length > 0;
593
+ const fieldId = fieldMeta?.id ?? "";
594
+ const descriptionId = description ? `${fieldId}-description` : void 0;
595
+ const errorId = hasErrors ? `${fieldId}-error` : void 0;
596
+ const contextValue = React$1.useMemo(() => ({
597
+ name: fieldMeta?.name ?? "",
598
+ id: fieldId,
599
+ errors,
600
+ required,
601
+ disabled,
602
+ fieldMeta
603
+ }), [
604
+ fieldMeta,
605
+ fieldId,
606
+ errors,
607
+ required,
608
+ disabled
609
+ ]);
610
+ if (!fieldMeta) {
611
+ console.warn(`Form.Field: Field "${name}" not found in form schema`);
612
+ return null;
613
+ }
614
+ const isRenderFunction = typeof children === "function";
615
+ const renderContent = () => {
616
+ if (isRenderFunction) return /* @__PURE__ */ jsx(FormFieldRenderContent, {
617
+ fieldMeta,
618
+ fields,
619
+ form,
620
+ isSubmitting,
621
+ required,
622
+ disabled,
623
+ children
624
+ });
625
+ return children;
626
+ };
627
+ return /* @__PURE__ */ jsx(FieldProvider, {
628
+ value: contextValue,
629
+ children: /* @__PURE__ */ jsxs("div", {
630
+ className: cn("flex flex-col space-y-2", className),
631
+ children: [
632
+ label && /* @__PURE__ */ jsx(FieldLabel, {
633
+ htmlFor: fieldId,
634
+ label,
635
+ hasErrors,
636
+ required,
637
+ tooltip,
638
+ className: labelClassName
639
+ }),
640
+ renderContent(),
641
+ description && /* @__PURE__ */ jsx("p", {
642
+ id: descriptionId,
643
+ className: "text-ring text-xs text-wrap",
644
+ children: description
645
+ }),
646
+ hasErrors && /* @__PURE__ */ jsx("ul", {
647
+ id: errorId,
648
+ className: cn("text-destructive space-y-1 text-xs font-medium", errors.length > 1 && "list-disc pl-4"),
649
+ role: "alert",
650
+ "aria-live": "polite",
651
+ children: errors.map((error) => /* @__PURE__ */ jsx("li", {
652
+ className: "text-wrap",
653
+ children: error
654
+ }, error))
655
+ })
656
+ ]
657
+ })
658
+ });
659
+ }
660
+ /**
661
+ * Internal component to handle render function pattern
662
+ * This is needed because hooks (useInputControl) must be called unconditionally
663
+ */
664
+ function FormFieldRenderContent({ fieldMeta, fields, form, isSubmitting, required, disabled, children }) {
665
+ const control = useInputControl(fieldMeta);
666
+ const meta = React$1.useMemo(() => ({
667
+ name: fieldMeta.name,
668
+ id: fieldMeta.id,
669
+ errors: fieldMeta.errors,
670
+ required,
671
+ disabled
672
+ }), [
673
+ fieldMeta.name,
674
+ fieldMeta.id,
675
+ fieldMeta.errors,
676
+ required,
677
+ disabled
678
+ ]);
679
+ return /* @__PURE__ */ jsx(Fragment$1, { children: children({
680
+ field: fieldMeta,
681
+ control: {
682
+ value: control.value,
683
+ change: control.change,
684
+ blur: control.blur,
685
+ focus: control.focus
686
+ },
687
+ meta,
688
+ fields,
689
+ form,
690
+ isSubmitting
691
+ }) });
692
+ }
693
+ FormField.displayName = "Form.Field";
694
+
695
+ //#endregion
696
+ //#region src/components/features/form/components/form-field-array.tsx
697
+ /**
698
+ * Form.FieldArray - Dynamic array of fields
699
+ *
700
+ * Provides helpers for managing arrays of form fields.
701
+ *
702
+ * @example
703
+ * ```tsx
704
+ * <Form.FieldArray name="members">
705
+ * {({ fields, append, remove }) => (
706
+ * <>
707
+ * {fields.map((field, index) => (
708
+ * <div key={field.key} className="flex gap-2">
709
+ * <Form.Field name={`members.${index}.email`} label="Email">
710
+ * <Form.Input type="email" />
711
+ * </Form.Field>
712
+ * <Form.Field name={`members.${index}.role`} label="Role">
713
+ * <Form.Select>
714
+ * <Form.SelectItem value="admin">Admin</Form.SelectItem>
715
+ * <Form.SelectItem value="user">User</Form.SelectItem>
716
+ * </Form.Select>
717
+ * </Form.Field>
718
+ * <button type="button" onClick={() => remove(index)}>
719
+ * Remove
720
+ * </button>
721
+ * </div>
722
+ * ))}
723
+ * <button type="button" onClick={() => append({ email: '', role: 'user' })}>
724
+ * Add Member
725
+ * </button>
726
+ * </>
727
+ * )}
728
+ * </Form.FieldArray>
729
+ * ```
730
+ */
731
+ function FormFieldArray({ name, children }) {
732
+ const { fields, formId } = useFormContext$1();
733
+ const form = useFormMetadata(formId);
734
+ const arrayField = React$1.useMemo(() => {
735
+ const parts = name.split(".");
736
+ let current = fields;
737
+ for (const part of parts) {
738
+ if (!current) break;
739
+ if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
740
+ else current = current[part];
741
+ }
742
+ return current;
743
+ }, [fields, name]);
744
+ const arrayFieldName = arrayField?.name ?? "";
745
+ const append = React$1.useCallback((value = {}) => {
746
+ if (!arrayFieldName) return;
747
+ form.insert({
748
+ name: arrayFieldName,
749
+ defaultValue: value
750
+ });
751
+ }, [form, arrayFieldName]);
752
+ const remove = React$1.useCallback((index) => {
753
+ if (!arrayFieldName) return;
754
+ form.remove({
755
+ name: arrayFieldName,
756
+ index
757
+ });
758
+ }, [form, arrayFieldName]);
759
+ const move = React$1.useCallback((from, to) => {
760
+ if (!arrayFieldName) return;
761
+ form.reorder({
762
+ name: arrayFieldName,
763
+ from,
764
+ to
765
+ });
766
+ }, [form, arrayFieldName]);
767
+ if (!arrayField) {
768
+ console.warn(`Form.FieldArray: Field "${name}" not found in form schema`);
769
+ return null;
770
+ }
771
+ return /* @__PURE__ */ jsx(Fragment$1, { children: children({
772
+ fields: (arrayField.getFieldList?.() ?? []).map((field, index) => ({
773
+ id: field.id,
774
+ key: field.key,
775
+ name: `${name}.${index}`
776
+ })),
777
+ append,
778
+ remove,
779
+ move
780
+ }) });
781
+ }
782
+ FormFieldArray.displayName = "Form.FieldArray";
783
+
784
+ //#endregion
785
+ //#region src/components/features/form/components/form-input.tsx
786
+ /**
787
+ * Form.Input - Text input component
788
+ *
789
+ * Automatically wired to the parent Form.Field context.
790
+ *
791
+ * @example
792
+ * ```tsx
793
+ * <Form.Field name="email" label="Email" required>
794
+ * <Form.Input type="email" placeholder="john@example.com" />
795
+ * </Form.Field>
796
+ * ```
797
+ */
798
+ function FormInput({ ref, type = "text", className, disabled, ...props }) {
799
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
800
+ const inputProps = getInputProps(fieldMeta, { type });
801
+ const isDisabled = disabled ?? fieldDisabled;
802
+ const hasErrors = errors && errors.length > 0;
803
+ return /* @__PURE__ */ jsx(Input, {
804
+ ref,
805
+ ...inputProps,
806
+ ...props,
807
+ type,
808
+ disabled: isDisabled,
809
+ "aria-invalid": hasErrors || void 0,
810
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
811
+ className: cn("!text-xs", className)
812
+ });
813
+ }
814
+ FormInput.displayName = "Form.Input";
815
+
816
+ //#endregion
817
+ //#region src/components/features/form/components/form-radio-group.tsx
818
+ /**
819
+ * Form.RadioGroup - Radio button group component
820
+ *
821
+ * Automatically wired to the parent Form.Field context.
822
+ *
823
+ * @example
824
+ * ```tsx
825
+ * <Form.Field name="plan" label="Select Plan" required>
826
+ * <Form.RadioGroup orientation="vertical">
827
+ * <Form.RadioItem value="free" label="Free" description="Basic features" />
828
+ * <Form.RadioItem value="pro" label="Pro" description="Advanced features" />
829
+ * <Form.RadioItem value="enterprise" label="Enterprise" description="Custom solutions" />
830
+ * </Form.RadioGroup>
831
+ * </Form.Field>
832
+ * ```
833
+ */
834
+ function FormRadioGroup({ orientation = "vertical", disabled, className, children }) {
835
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
836
+ const control = useInputControl(fieldMeta);
837
+ const isDisabled = disabled ?? fieldDisabled;
838
+ const hasErrors = errors && errors.length > 0;
839
+ const radioValue = Array.isArray(control.value) ? control.value[0] : control.value;
840
+ return /* @__PURE__ */ jsx(RadioGroup, {
841
+ name: fieldMeta.name,
842
+ value: radioValue ?? "",
843
+ onValueChange: control.change,
844
+ disabled: isDisabled,
845
+ "aria-invalid": hasErrors || void 0,
846
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
847
+ className: cn(orientation === "horizontal" ? "flex flex-row space-x-4" : "flex flex-col space-y-2", className),
848
+ children
849
+ });
850
+ }
851
+ FormRadioGroup.displayName = "Form.RadioGroup";
852
+ /**
853
+ * Form.RadioItem - Individual radio button option
854
+ *
855
+ * @example
856
+ * ```tsx
857
+ * <Form.RadioItem value="option1" label="Option 1" />
858
+ * ```
859
+ */
860
+ function FormRadioItem({ value, label, description, disabled }) {
861
+ const radioId = `radio-${value}`;
862
+ return /* @__PURE__ */ jsxs("div", {
863
+ className: "flex items-start space-x-2",
864
+ children: [/* @__PURE__ */ jsx(RadioGroupItem, {
865
+ id: radioId,
866
+ value,
867
+ disabled,
868
+ className: "mt-1"
869
+ }), /* @__PURE__ */ jsxs("div", {
870
+ className: "flex flex-col",
871
+ children: [/* @__PURE__ */ jsx(Label, {
872
+ htmlFor: radioId,
873
+ className: cn("cursor-pointer text-sm font-normal", disabled && "cursor-not-allowed opacity-70"),
874
+ children: label
875
+ }), description && /* @__PURE__ */ jsx("span", {
876
+ className: "text-muted-foreground text-xs",
877
+ children: description
878
+ })]
879
+ })]
880
+ });
881
+ }
882
+ FormRadioItem.displayName = "Form.RadioItem";
883
+
884
+ //#endregion
885
+ //#region src/components/features/form/components/form-root.tsx
886
+ /**
887
+ * Form.Root - The root form component
888
+ *
889
+ * Provides form context to all children with built-in:
890
+ * - Zod schema validation
891
+ * - Conform integration
892
+ * - Optional telemetry callbacks
893
+ *
894
+ * Supports two patterns:
895
+ * 1. ReactNode children - for standard forms
896
+ * 2. Render function - for forms needing access to form state
897
+ *
898
+ * @example Standard usage
899
+ * ```tsx
900
+ * <Form.Root schema={userSchema} onSubmit={handleSubmit}>
901
+ * <Form.Field name="email" label="Email" required>
902
+ * <Form.Input type="email" />
903
+ * </Form.Field>
904
+ * <Form.Submit>Save</Form.Submit>
905
+ * </Form.Root>
906
+ * ```
907
+ *
908
+ * @example Render function for form state access
909
+ * ```tsx
910
+ * <Form.Root schema={userSchema} onSubmit={handleSubmit}>
911
+ * {({ form, fields, isSubmitting }) => (
912
+ * <>
913
+ * <Form.Field name="email" label="Email" required>
914
+ * <Form.Input type="email" />
915
+ * </Form.Field>
916
+ * <Button
917
+ * disabled={isSubmitting}
918
+ * onClick={() => form.update({ value: { email: '' } })}
919
+ * >
920
+ * Cancel
921
+ * </Button>
922
+ * <Form.Submit>Save</Form.Submit>
923
+ * </>
924
+ * )}
925
+ * </Form.Root>
926
+ * ```
927
+ */
928
+ function FormRoot({ schema, children, onSubmit, action, method = "POST", formComponent: FormComp = "form", id, name, defaultValues, mode = "onBlur", isSubmitting: externalIsSubmitting, onError, onSuccess, telemetry, className }) {
929
+ const [internalIsSubmitting, setInternalIsSubmitting] = React$1.useState(false);
930
+ const isSubmitting = externalIsSubmitting ?? internalIsSubmitting;
931
+ const formRef = React$1.useRef(null);
932
+ const shouldValidate = mode === "onChange" ? "onInput" : mode;
933
+ const [form, fields] = useForm({
934
+ id,
935
+ constraint: getZodConstraint(schema),
936
+ shouldValidate,
937
+ shouldRevalidate: mode === "onSubmit" ? "onSubmit" : "onInput",
938
+ defaultValue: defaultValues,
939
+ onValidate({ formData }) {
940
+ return parseWithZod(formData, { schema });
941
+ },
942
+ async onSubmit(event, { submission }) {
943
+ const formName = name || id || "unnamed-form";
944
+ telemetry?.onSubmit?.({
945
+ formName,
946
+ formId: id
947
+ });
948
+ if (!onSubmit) {
949
+ setInternalIsSubmitting(true);
950
+ return;
951
+ }
952
+ event.preventDefault();
953
+ if (submission?.status === "success") {
954
+ setInternalIsSubmitting(true);
955
+ try {
956
+ await onSubmit(submission.value);
957
+ onSuccess?.(submission.value);
958
+ telemetry?.onSuccess?.({
959
+ formName,
960
+ formId: id
961
+ });
962
+ } catch (error) {
963
+ telemetry?.onError?.({
964
+ formName,
965
+ formId: id,
966
+ error
967
+ });
968
+ telemetry?.captureError?.(error, {
969
+ message: `Form submission error: ${formName}`,
970
+ tags: {
971
+ "form.name": formName,
972
+ "form.id": id || "unknown"
973
+ }
974
+ });
975
+ onError?.(error);
976
+ } finally {
977
+ setInternalIsSubmitting(false);
978
+ }
979
+ } else if (submission?.status === "error") {
980
+ telemetry?.onValidationError?.({
981
+ formName,
982
+ formId: id,
983
+ fieldErrors: submission.error ?? {}
984
+ });
985
+ if (onError) {
986
+ const { ZodError } = await import("zod");
987
+ onError(new ZodError(Object.entries(submission.error ?? {}).flatMap(([path, messages]) => (messages ?? []).map((message) => ({
988
+ code: "custom",
989
+ path: path.split("."),
990
+ message
991
+ })))));
992
+ }
993
+ }
994
+ }
995
+ });
996
+ const submit = React$1.useCallback(() => {
997
+ formRef.current?.requestSubmit();
998
+ }, []);
999
+ const reset = React$1.useCallback(() => {
1000
+ form.reset();
1001
+ }, [form]);
1002
+ const contextValue = React$1.useMemo(() => ({
1003
+ form,
1004
+ fields,
1005
+ isSubmitting,
1006
+ submit,
1007
+ reset,
1008
+ formId: form.id
1009
+ }), [
1010
+ form,
1011
+ fields,
1012
+ isSubmitting,
1013
+ submit,
1014
+ reset
1015
+ ]);
1016
+ const isRenderFunction = typeof children === "function";
1017
+ const renderProps = React$1.useMemo(() => ({
1018
+ form,
1019
+ fields,
1020
+ isSubmitting,
1021
+ submit,
1022
+ reset
1023
+ }), [
1024
+ form,
1025
+ fields,
1026
+ isSubmitting,
1027
+ submit,
1028
+ reset
1029
+ ]);
1030
+ const renderChildren = () => {
1031
+ if (isRenderFunction) return children(renderProps);
1032
+ return children;
1033
+ };
1034
+ const { onSubmit: conformOnSubmit, ...conformFormProps } = getFormProps(form);
1035
+ return /* @__PURE__ */ jsx(FormProvider$1, {
1036
+ value: contextValue,
1037
+ children: /* @__PURE__ */ jsx(FormProvider, {
1038
+ context: form.context,
1039
+ children: /* @__PURE__ */ jsx(FormComp, {
1040
+ ref: formRef,
1041
+ ...conformFormProps,
1042
+ onSubmit: (e) => {
1043
+ e.stopPropagation();
1044
+ conformOnSubmit(e);
1045
+ },
1046
+ method,
1047
+ action,
1048
+ className: cn("space-y-6", className),
1049
+ autoComplete: "off",
1050
+ children: renderChildren()
1051
+ })
1052
+ })
1053
+ });
1054
+ }
1055
+ FormRoot.displayName = "Form.Root";
1056
+
1057
+ //#endregion
1058
+ //#region src/components/features/form/components/form-select.tsx
1059
+ /**
1060
+ * Form.Select - Select dropdown component
1061
+ *
1062
+ * Automatically wired to the parent Form.Field context.
1063
+ *
1064
+ * @example
1065
+ * ```tsx
1066
+ * <Form.Field name="country" label="Country" required>
1067
+ * <Form.Select placeholder="Select a country">
1068
+ * <Form.SelectItem value="us">United States</Form.SelectItem>
1069
+ * <Form.SelectItem value="uk">United Kingdom</Form.SelectItem>
1070
+ * <Form.SelectItem value="ca">Canada</Form.SelectItem>
1071
+ * </Form.Select>
1072
+ * </Form.Field>
1073
+ * ```
1074
+ */
1075
+ function FormSelect({ placeholder, disabled, className, children }) {
1076
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
1077
+ const control = useInputControl(fieldMeta);
1078
+ const isDisabled = disabled ?? fieldDisabled;
1079
+ const hasErrors = errors && errors.length > 0;
1080
+ const selectValue = Array.isArray(control.value) ? control.value[0] : control.value;
1081
+ return /* @__PURE__ */ jsxs(Select, {
1082
+ name: fieldMeta.name,
1083
+ value: selectValue ?? "",
1084
+ onValueChange: control.change,
1085
+ disabled: isDisabled,
1086
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1087
+ id: fieldMeta.id,
1088
+ "aria-invalid": hasErrors || void 0,
1089
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
1090
+ className: cn(className),
1091
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
1092
+ }), /* @__PURE__ */ jsx(SelectContent, { children })]
1093
+ });
1094
+ }
1095
+ FormSelect.displayName = "Form.Select";
1096
+ /**
1097
+ * Form.SelectItem - Individual select option
1098
+ *
1099
+ * @example
1100
+ * ```tsx
1101
+ * <Form.SelectItem value="option1">Option 1</Form.SelectItem>
1102
+ * ```
1103
+ */
1104
+ function FormSelectItem({ value, children, disabled }) {
1105
+ return /* @__PURE__ */ jsx(SelectItem, {
1106
+ value,
1107
+ disabled,
1108
+ children
1109
+ });
1110
+ }
1111
+ FormSelectItem.displayName = "Form.SelectItem";
1112
+
1113
+ //#endregion
1114
+ //#region src/components/features/form/components/form-submit.tsx
1115
+ /**
1116
+ * Form.Submit - Submit button with automatic loading state
1117
+ *
1118
+ * @example
1119
+ * ```tsx
1120
+ * <Form.Submit loadingText="Saving...">
1121
+ * Save Changes
1122
+ * </Form.Submit>
1123
+ * ```
1124
+ */
1125
+ function FormSubmit({ children, loadingText, loading = false, ...props }) {
1126
+ const { isSubmitting } = useFormContext$1();
1127
+ const isLoading = loading || isSubmitting;
1128
+ return /* @__PURE__ */ jsx(Button, {
1129
+ htmlType: "submit",
1130
+ disabled: props.disabled || isLoading,
1131
+ loading: isLoading,
1132
+ ...props,
1133
+ children: isLoading && loadingText ? loadingText : children
1134
+ });
1135
+ }
1136
+ FormSubmit.displayName = "Form.Submit";
1137
+
1138
+ //#endregion
1139
+ //#region src/components/features/form/components/form-switch.tsx
1140
+ /**
1141
+ * Form.Switch - Toggle switch component
1142
+ *
1143
+ * Automatically wired to the parent Form.Field context.
1144
+ *
1145
+ * @example
1146
+ * ```tsx
1147
+ * <Form.Field name="notifications">
1148
+ * <Form.Switch label="Enable email notifications" />
1149
+ * </Form.Field>
1150
+ * ```
1151
+ */
1152
+ function FormSwitch({ label, disabled, className }) {
1153
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
1154
+ const control = useInputControl(fieldMeta);
1155
+ const isDisabled = disabled ?? fieldDisabled;
1156
+ const hasErrors = errors && errors.length > 0;
1157
+ const isChecked = control.value === "on" || control.value === "true";
1158
+ const handleCheckedChange = (checked) => {
1159
+ control.change(checked ? "on" : "");
1160
+ };
1161
+ const switchId = fieldMeta.id;
1162
+ return /* @__PURE__ */ jsxs("div", {
1163
+ className: cn("flex items-center space-x-2", className),
1164
+ children: [/* @__PURE__ */ jsx(Switch, {
1165
+ id: switchId,
1166
+ name: fieldMeta.name,
1167
+ checked: isChecked,
1168
+ onCheckedChange: handleCheckedChange,
1169
+ disabled: isDisabled,
1170
+ "aria-invalid": hasErrors || void 0,
1171
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0
1172
+ }), label && /* @__PURE__ */ jsx(Label, {
1173
+ htmlFor: switchId,
1174
+ className: cn("cursor-pointer text-sm font-normal", isDisabled && "cursor-not-allowed opacity-70"),
1175
+ children: label
1176
+ })]
1177
+ });
1178
+ }
1179
+ FormSwitch.displayName = "Form.Switch";
1180
+
1181
+ //#endregion
1182
+ //#region src/components/features/form/components/form-textarea.tsx
1183
+ /**
1184
+ * Form.Textarea - Multi-line text input component
1185
+ *
1186
+ * Automatically wired to the parent Form.Field context.
1187
+ *
1188
+ * @example
1189
+ * ```tsx
1190
+ * <Form.Field name="bio" label="Bio">
1191
+ * <Form.Textarea rows={4} placeholder="Tell us about yourself..." />
1192
+ * </Form.Field>
1193
+ * ```
1194
+ */
1195
+ function FormTextarea({ ref, className, disabled, rows = 3, ...props }) {
1196
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
1197
+ const textareaProps = getTextareaProps(fieldMeta);
1198
+ const isDisabled = disabled ?? fieldDisabled;
1199
+ const hasErrors = errors && errors.length > 0;
1200
+ return /* @__PURE__ */ jsx(Textarea, {
1201
+ ref,
1202
+ ...textareaProps,
1203
+ ...props,
1204
+ rows,
1205
+ disabled: isDisabled,
1206
+ "aria-invalid": hasErrors || void 0,
1207
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
1208
+ className: cn(className)
1209
+ });
1210
+ }
1211
+ FormTextarea.displayName = "Form.Textarea";
1212
+
1213
+ //#endregion
1214
+ //#region src/components/features/form/hooks/use-watch.ts
1215
+ /**
1216
+ * Hook to watch a field's value
1217
+ * Triggers re-render when the watched field value changes
1218
+ *
1219
+ * @example
1220
+ * ```tsx
1221
+ * function ConditionalField() {
1222
+ * const contactMethod = useWatch('contactMethod');
1223
+ *
1224
+ * if (contactMethod === 'email') {
1225
+ * return <Form.Field name="email"><Form.Input type="email" /></Form.Field>;
1226
+ * }
1227
+ *
1228
+ * if (contactMethod === 'phone') {
1229
+ * return <Form.Field name="phone"><Form.Input type="tel" /></Form.Field>;
1230
+ * }
1231
+ *
1232
+ * return null;
1233
+ * }
1234
+ * ```
1235
+ */
1236
+ function useWatch(name) {
1237
+ const { fields } = useFormContext$1();
1238
+ return useInputControl(React$1.useMemo(() => {
1239
+ const parts = name.split(".");
1240
+ let current = fields;
1241
+ for (const part of parts) {
1242
+ if (!current) break;
1243
+ if (/^\d+$/.test(part)) {
1244
+ const fieldList = current.getFieldList?.();
1245
+ if (fieldList) current = fieldList[Number.parseInt(part, 10)]?.getFieldset?.();
1246
+ else current = current[part];
1247
+ } else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
1248
+ else current = current[part];
1249
+ }
1250
+ return current;
1251
+ }, [fields, name])).value;
1252
+ }
1253
+ /**
1254
+ * Hook to watch multiple fields at once
1255
+ *
1256
+ * @example
1257
+ * ```tsx
1258
+ * function Summary() {
1259
+ * const values = useWatchAll(['firstName', 'lastName', 'email']);
1260
+ *
1261
+ * return (
1262
+ * <div>
1263
+ * Name: {values.firstName} {values.lastName}
1264
+ * Email: {values.email}
1265
+ * </div>
1266
+ * );
1267
+ * }
1268
+ * ```
1269
+ */
1270
+ function useWatchAll(names) {
1271
+ const { fields } = useFormContext$1();
1272
+ return React$1.useMemo(() => {
1273
+ const result = {};
1274
+ for (const name of names) {
1275
+ const parts = name.split(".");
1276
+ let current = fields;
1277
+ for (const part of parts) {
1278
+ if (!current) break;
1279
+ if (/^\d+$/.test(part)) {
1280
+ const fieldList = current.getFieldList?.();
1281
+ if (fieldList) current = fieldList[Number.parseInt(part, 10)]?.getFieldset?.();
1282
+ else current = current[part];
1283
+ } else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
1284
+ else current = current[part];
1285
+ }
1286
+ if (current) result[name] = current.value;
1287
+ }
1288
+ return result;
1289
+ }, [fields, names]);
1290
+ }
1291
+
1292
+ //#endregion
1293
+ //#region src/components/features/form/components/form-when.tsx
1294
+ /**
1295
+ * Form.When - Conditional rendering based on field values
1296
+ *
1297
+ * Renders children only when the specified field matches the condition.
1298
+ *
1299
+ * @example
1300
+ * ```tsx
1301
+ * // Render when field equals value
1302
+ * <Form.When field="contactMethod" is="email">
1303
+ * <Form.Field name="email"><Form.Input type="email" /></Form.Field>
1304
+ * </Form.When>
1305
+ *
1306
+ * // Render when field does not equal value
1307
+ * <Form.When field="contactMethod" isNot="none">
1308
+ * <Form.Field name="contact"><Form.Input /></Form.Field>
1309
+ * </Form.When>
1310
+ *
1311
+ * // Render when field value is in array
1312
+ * <Form.When field="role" in={['admin', 'moderator']}>
1313
+ * <Form.Field name="permissions"><Form.Input /></Form.Field>
1314
+ * </Form.When>
1315
+ *
1316
+ * // Render when field value is not in array
1317
+ * <Form.When field="status" notIn={['archived', 'deleted']}>
1318
+ * <Form.Field name="actions"><Form.Input /></Form.Field>
1319
+ * </Form.When>
1320
+ * ```
1321
+ */
1322
+ function FormWhen({ field, is, isNot, in: inArray, notIn, children }) {
1323
+ const value = useWatch(field);
1324
+ let shouldRender = true;
1325
+ if (is !== void 0) shouldRender = value === is;
1326
+ if (isNot !== void 0 && shouldRender) shouldRender = value !== isNot;
1327
+ if (inArray !== void 0 && shouldRender) shouldRender = inArray.includes(value);
1328
+ if (notIn !== void 0 && shouldRender) shouldRender = !notIn.includes(value);
1329
+ if (!shouldRender) return null;
1330
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
1331
+ }
1332
+ FormWhen.displayName = "Form.When";
1333
+
1334
+ //#endregion
1335
+ //#region src/components/features/form/components/stepper/form-stepper.tsx
1336
+ const FormStepperContext = React$1.createContext(null);
1337
+ function useFormStepperContext() {
1338
+ const context = React$1.use(FormStepperContext);
1339
+ if (!context) throw new Error("useFormStepperContext must be used within a Form.Stepper component");
1340
+ return context;
1341
+ }
1342
+ /**
1343
+ * Recursively unwrap ZodIntersection (from .and()) to extract the base ZodObject.
1344
+ *
1345
+ * Zod v4 schema types use `def.type` as a string discriminant:
1346
+ * - "intersection" (from .and()): merge left + right base objects
1347
+ * - "object": return directly
1348
+ *
1349
+ * Note: In Zod v4, .superRefine() and .refine() return `this` (no wrapper),
1350
+ * so only ZodIntersection needs unwrapping.
1351
+ */
1352
+ function getBaseObject(schema) {
1353
+ if (schema.def.type === "intersection") {
1354
+ const intersectionDef = schema.def;
1355
+ const left = getBaseObject(intersectionDef.left);
1356
+ const right = getBaseObject(intersectionDef.right);
1357
+ return left.merge(right);
1358
+ }
1359
+ if (schema.def.type !== "object") {
1360
+ console.warn(`mergeSchemas: expected ZodObject or ZodIntersection but got "${schema.def.type}". Falling back to empty object.`);
1361
+ return z.object({});
1362
+ }
1363
+ return schema;
1364
+ }
1365
+ /**
1366
+ * Merge multiple zod schemas into one ZodObject for HTML constraint generation.
1367
+ * Handles ZodIntersection (.and()) by unwrapping to base ZodObject shapes.
1368
+ * Per-step validation still uses the original schemas with all refinements intact.
1369
+ */
1370
+ function mergeSchemas(steps) {
1371
+ if (steps.length === 0) throw new Error("Form.Stepper requires at least one step");
1372
+ return steps.reduce((acc, step, index) => {
1373
+ const base = getBaseObject(step.schema);
1374
+ if (index === 0) return base;
1375
+ return acc.merge(base);
1376
+ }, {});
1377
+ }
1378
+ /**
1379
+ * Convert StepConfig[] to Stepperize step format
1380
+ */
1381
+ function toStepperizeSteps(steps) {
1382
+ return steps.map((step) => ({
1383
+ id: step.id,
1384
+ label: step.label,
1385
+ description: step.description
1386
+ }));
1387
+ }
1388
+ /**
1389
+ * Form.Stepper - Multi-step form container
1390
+ *
1391
+ * Uses Stepperize internally for step navigation and a single Conform form
1392
+ * instance for all steps. Schemas are auto-merged for unified validation.
1393
+ *
1394
+ * @example
1395
+ * ```tsx
1396
+ * const steps = [
1397
+ * { id: 'account', label: 'Account', schema: accountSchema },
1398
+ * { id: 'profile', label: 'Profile', schema: profileSchema },
1399
+ * ];
1400
+ *
1401
+ * <Form.Stepper steps={steps} onComplete={handleComplete}>
1402
+ * <Form.StepperNavigation />
1403
+ *
1404
+ * <Form.Step id="account">
1405
+ * <Form.Field name="email" label="Email" required>
1406
+ * <Form.Input type="email" />
1407
+ * </Form.Field>
1408
+ * </Form.Step>
1409
+ *
1410
+ * <Form.Step id="profile">
1411
+ * <Form.Field name="name" label="Full Name" required>
1412
+ * <Form.Input />
1413
+ * </Form.Field>
1414
+ * </Form.Step>
1415
+ *
1416
+ * <Form.StepperControls />
1417
+ * </Form.Stepper>
1418
+ * ```
1419
+ */
1420
+ function FormStepper({ steps, children, onComplete, onStepChange, initialStep, className, defaultValues, id, formComponent }) {
1421
+ const stepperDef = React$1.useMemo(() => {
1422
+ return defineStepper(...toStepperizeSteps(steps));
1423
+ }, [steps]);
1424
+ const initialStepIndex = React$1.useMemo(() => {
1425
+ if (!initialStep) return void 0;
1426
+ const index = steps.findIndex((s) => s.id === initialStep);
1427
+ return index >= 0 ? steps[index].id : void 0;
1428
+ }, [initialStep, steps]);
1429
+ const { Stepper } = stepperDef;
1430
+ const providerProps = initialStepIndex ? { initialStep: initialStepIndex } : {};
1431
+ return /* @__PURE__ */ jsx(Stepper.Provider, {
1432
+ ...providerProps,
1433
+ children: /* @__PURE__ */ jsx(FormStepperContent, {
1434
+ steps,
1435
+ stepperDef,
1436
+ onComplete,
1437
+ onStepChange,
1438
+ className,
1439
+ defaultValues,
1440
+ id,
1441
+ formComponent,
1442
+ children
1443
+ })
1444
+ });
1445
+ }
1446
+ FormStepper.displayName = "Form.Stepper";
1447
+ function FormStepperContent({ steps, stepperDef, children, onComplete, onStepChange, className, defaultValues, id, formComponent }) {
1448
+ const { useStepper } = stepperDef;
1449
+ const stepper = useStepper();
1450
+ return /* @__PURE__ */ jsx(StepForm, {
1451
+ steps,
1452
+ stepper,
1453
+ currentStepConfig: React$1.useMemo(() => steps.find((s) => s.id === stepper.state.current.data.id) ?? steps[0], [steps, stepper.state.current.data.id]),
1454
+ combinedSchema: React$1.useMemo(() => mergeSchemas(steps), [steps]),
1455
+ storedValues: React$1.useMemo(() => {
1456
+ const allMetadata = steps.reduce((acc, step) => ({
1457
+ ...acc,
1458
+ ...stepper.metadata.get(step.id) || {}
1459
+ }), {});
1460
+ return {
1461
+ ...defaultValues,
1462
+ ...allMetadata
1463
+ };
1464
+ }, [
1465
+ steps,
1466
+ stepper,
1467
+ defaultValues,
1468
+ stepper.state.current.data.id
1469
+ ]),
1470
+ onComplete,
1471
+ onStepChange,
1472
+ className,
1473
+ id,
1474
+ formComponent,
1475
+ children
1476
+ }, stepper.state.current.data.id);
1477
+ }
1478
+ function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedValues, children, onComplete, onStepChange, className, id, formComponent: FormComp = "form" }) {
1479
+ const [isSubmitting, setIsSubmitting] = React$1.useState(false);
1480
+ const formRef = React$1.useRef(null);
1481
+ const [form, fields] = useForm({
1482
+ id: id ?? "stepper-form",
1483
+ constraint: getZodConstraint(combinedSchema),
1484
+ shouldValidate: "onBlur",
1485
+ shouldRevalidate: "onInput",
1486
+ defaultValue: storedValues,
1487
+ onValidate({ formData }) {
1488
+ return parseWithZod(formData, { schema: currentStepConfig.schema });
1489
+ },
1490
+ async onSubmit(event, { submission }) {
1491
+ event.preventDefault();
1492
+ if (submission?.status !== "success") return;
1493
+ if (submission.value) stepper.metadata.set(stepper.state.current.data.id, submission.value);
1494
+ if (stepper.state.isLast) {
1495
+ setIsSubmitting(true);
1496
+ try {
1497
+ await onComplete({
1498
+ ...steps.reduce((acc, step) => ({
1499
+ ...acc,
1500
+ ...stepper.metadata.get(step.id) || {}
1501
+ }), {}),
1502
+ ...submission.value
1503
+ });
1504
+ } catch (error) {
1505
+ console.error("Stepper form completion error:", error);
1506
+ } finally {
1507
+ setIsSubmitting(false);
1508
+ }
1509
+ } else {
1510
+ const nextStepId = stepper.lookup.getNext(stepper.state.current.data.id)?.id;
1511
+ if (nextStepId) {
1512
+ stepper.navigation.goTo(nextStepId);
1513
+ onStepChange?.(nextStepId, "next");
1514
+ }
1515
+ }
1516
+ }
1517
+ });
1518
+ const next = React$1.useCallback(() => {
1519
+ formRef.current?.requestSubmit();
1520
+ }, []);
1521
+ const prev = React$1.useCallback(() => {
1522
+ if (formRef.current) {
1523
+ const formData = new FormData(formRef.current);
1524
+ const currentData = {};
1525
+ formData.forEach((value, key) => {
1526
+ if (!key.startsWith("$")) currentData[key] = value;
1527
+ });
1528
+ if (Object.keys(currentData).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentData);
1529
+ }
1530
+ const prevStepId = stepper.lookup.getPrev(stepper.state.current.data.id)?.id;
1531
+ if (prevStepId) {
1532
+ stepper.navigation.goTo(prevStepId);
1533
+ onStepChange?.(prevStepId, "prev");
1534
+ }
1535
+ }, [stepper, onStepChange]);
1536
+ const goTo = React$1.useCallback((stepId) => {
1537
+ const currentIndex = stepper.lookup.getIndex(stepper.state.current.data.id);
1538
+ if (stepper.lookup.getIndex(stepId) < currentIndex) {
1539
+ stepper.navigation.goTo(stepId);
1540
+ onStepChange?.(stepId, "prev");
1541
+ }
1542
+ }, [stepper, onStepChange]);
1543
+ const getStepData = React$1.useCallback((stepId) => stepper.metadata.get(stepId), [stepper]);
1544
+ const getAllStepData = React$1.useCallback(() => {
1545
+ return steps.reduce((acc, step) => ({
1546
+ ...acc,
1547
+ ...stepper.metadata.get(step.id) || {}
1548
+ }), {});
1549
+ }, [steps, stepper]);
1550
+ const stepperContextValue = React$1.useMemo(() => ({
1551
+ steps,
1552
+ current: currentStepConfig,
1553
+ currentIndex: stepper.lookup.getIndex(stepper.state.current.data.id),
1554
+ next,
1555
+ prev,
1556
+ goTo,
1557
+ isFirst: stepper.state.isFirst,
1558
+ isLast: stepper.state.isLast,
1559
+ getStepData,
1560
+ getAllStepData,
1561
+ utils: { getIndex: (id) => stepper.lookup.getIndex(id) }
1562
+ }), [
1563
+ steps,
1564
+ currentStepConfig,
1565
+ stepper,
1566
+ next,
1567
+ prev,
1568
+ goTo,
1569
+ getStepData,
1570
+ getAllStepData
1571
+ ]);
1572
+ const formContextValue = React$1.useMemo(() => ({
1573
+ form,
1574
+ fields,
1575
+ isSubmitting,
1576
+ submit: () => formRef.current?.requestSubmit(),
1577
+ reset: () => form.reset(),
1578
+ formId: form.id
1579
+ }), [
1580
+ form,
1581
+ fields,
1582
+ isSubmitting
1583
+ ]);
1584
+ const renderProps = {
1585
+ steps,
1586
+ current: currentStepConfig,
1587
+ currentIndex: stepper.lookup.getIndex(stepper.state.current.data.id),
1588
+ next,
1589
+ prev,
1590
+ goTo,
1591
+ isFirst: stepper.state.isFirst,
1592
+ isLast: stepper.state.isLast,
1593
+ getStepData,
1594
+ getAllStepData
1595
+ };
1596
+ const resolvedChildren = typeof children === "function" ? children(renderProps) : children;
1597
+ return /* @__PURE__ */ jsx(FormStepperContext, {
1598
+ value: stepperContextValue,
1599
+ children: /* @__PURE__ */ jsx(FormProvider$1, {
1600
+ value: formContextValue,
1601
+ children: /* @__PURE__ */ jsx(FormProvider, {
1602
+ context: form.context,
1603
+ children: /* @__PURE__ */ jsx(FormComp, {
1604
+ ref: formRef,
1605
+ ...getFormProps(form),
1606
+ method: "POST",
1607
+ className: cn("space-y-6", className),
1608
+ autoComplete: "off",
1609
+ children: resolvedChildren
1610
+ })
1611
+ })
1612
+ })
1613
+ });
1614
+ }
1615
+
1616
+ //#endregion
1617
+ //#region src/components/features/form/components/stepper/form-step.tsx
1618
+ /**
1619
+ * Form.Step - Individual step content container
1620
+ *
1621
+ * Only renders its children when the step is active.
1622
+ * Works with the single-form architecture - fields remain registered
1623
+ * even when unmounted, preserving their values.
1624
+ *
1625
+ * @example
1626
+ * ```tsx
1627
+ * <Form.Step id="account">
1628
+ * <Form.Field name="email" label="Email" required>
1629
+ * <Form.Input type="email" />
1630
+ * </Form.Field>
1631
+ * </Form.Step>
1632
+ * ```
1633
+ */
1634
+ function FormStep({ id, children }) {
1635
+ const { current } = useFormStepperContext();
1636
+ if (current.id !== id) return null;
1637
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
1638
+ }
1639
+ FormStep.displayName = "Form.Step";
1640
+
1641
+ //#endregion
1642
+ //#region src/components/features/form/components/stepper/stepper-controls.tsx
1643
+ /**
1644
+ * Form.StepperControls - Navigation buttons (Previous/Next/Submit)
1645
+ *
1646
+ * Provides Previous and Next/Submit buttons for navigating between steps.
1647
+ * The Next button triggers form validation before advancing.
1648
+ * The Previous button navigates back without validation.
1649
+ *
1650
+ * @example
1651
+ * ```tsx
1652
+ * <Form.StepperControls
1653
+ * prevLabel={(isFirst) => isFirst ? 'Cancel' : 'Previous'}
1654
+ * nextLabel={(isLast) => isLast ? 'Submit' : 'Next'}
1655
+ * loadingText="Creating..."
1656
+ * onCancel={() => setOpen(false)}
1657
+ * />
1658
+ * ```
1659
+ *
1660
+ * @example With external loading state
1661
+ * ```tsx
1662
+ * <Form.StepperControls
1663
+ * loading={fetcher.state === 'submitting'}
1664
+ * disabled={!isValid}
1665
+ * loadingText="Saving..."
1666
+ * />
1667
+ * ```
1668
+ */
1669
+ function StepperControls({ prevLabel = "Previous", nextLabel = (isLast) => isLast ? "Submit" : "Next", loadingText = "Submitting...", showPrev = true, loading, disabled, onPrev, onCancel, className }) {
1670
+ const { prev, isFirst, isLast } = useFormStepperContext();
1671
+ const { isSubmitting: formIsSubmitting } = useFormContext$1();
1672
+ const isLoading = loading ?? formIsSubmitting;
1673
+ const isDisabled = disabled ?? false;
1674
+ const getPrevLabel = () => {
1675
+ if (typeof prevLabel === "function") return prevLabel(isFirst);
1676
+ return prevLabel;
1677
+ };
1678
+ const getNextLabel = () => {
1679
+ if (typeof nextLabel === "function") return nextLabel(isLast);
1680
+ return nextLabel;
1681
+ };
1682
+ const handlePrev = () => {
1683
+ if (isFirst && onCancel) onCancel();
1684
+ else {
1685
+ onPrev?.();
1686
+ prev();
1687
+ }
1688
+ };
1689
+ return /* @__PURE__ */ jsxs("div", {
1690
+ className: cn("flex items-center justify-between gap-3", className),
1691
+ children: [/* @__PURE__ */ jsx("div", { children: showPrev && /* @__PURE__ */ jsx(Button, {
1692
+ htmlType: "button",
1693
+ type: "quaternary",
1694
+ theme: "outline",
1695
+ size: "small",
1696
+ onClick: handlePrev,
1697
+ disabled: isLoading || isDisabled,
1698
+ children: getPrevLabel()
1699
+ }) }), /* @__PURE__ */ jsx(Button, {
1700
+ htmlType: "submit",
1701
+ type: "primary",
1702
+ size: "small",
1703
+ loading: isLoading,
1704
+ disabled: isLoading || isDisabled,
1705
+ children: isLoading && isLast ? loadingText : getNextLabel()
1706
+ })]
1707
+ });
1708
+ }
1709
+ StepperControls.displayName = "Form.StepperControls";
1710
+
1711
+ //#endregion
1712
+ //#region src/components/features/form/components/stepper/stepper-navigation.tsx
1713
+ /**
1714
+ * Form.StepperNavigation - Step indicators/progress
1715
+ *
1716
+ * Displays visual step indicators showing current progress through the form.
1717
+ * Supports horizontal and vertical variants with optional label orientation.
1718
+ *
1719
+ * @example
1720
+ * ```tsx
1721
+ * <Form.StepperNavigation variant="horizontal" labelOrientation="vertical" />
1722
+ * ```
1723
+ */
1724
+ function StepperNavigation({ variant = "horizontal", labelOrientation = "vertical", className }) {
1725
+ const { steps, currentIndex } = useFormStepperContext();
1726
+ if (variant === "horizontal" && labelOrientation === "vertical") return /* @__PURE__ */ jsx("nav", {
1727
+ "aria-label": "Form steps",
1728
+ className: cn("flex flex-row items-start justify-between", className),
1729
+ children: steps.map((step, index) => {
1730
+ const isActive = index === currentIndex;
1731
+ const isCompleted = index < currentIndex;
1732
+ return /* @__PURE__ */ jsxs("div", {
1733
+ className: "relative flex flex-1 flex-col items-center",
1734
+ children: [
1735
+ !(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" }),
1736
+ /* @__PURE__ */ jsx("div", {
1737
+ 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"),
1738
+ "aria-current": isActive ? "step" : void 0,
1739
+ children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary h-4 w-4" }) : index + 1
1740
+ }),
1741
+ /* @__PURE__ */ jsxs("div", {
1742
+ className: "mt-1",
1743
+ children: [/* @__PURE__ */ jsx("span", {
1744
+ className: cn("text-xs font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1745
+ children: step.label
1746
+ }), step.description && /* @__PURE__ */ jsx("p", {
1747
+ className: "text-muted-foreground mt-0.5 text-xs",
1748
+ children: step.description
1749
+ })]
1750
+ })
1751
+ ]
1752
+ }, step.id);
1753
+ })
1754
+ });
1755
+ if (variant === "horizontal") return /* @__PURE__ */ jsx("nav", {
1756
+ "aria-label": "Form steps",
1757
+ className: cn("flex flex-row items-center", className),
1758
+ children: steps.map((step, index) => {
1759
+ const isActive = index === currentIndex;
1760
+ const isCompleted = index < currentIndex;
1761
+ const isLast = index === steps.length - 1;
1762
+ return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1763
+ className: "flex items-center",
1764
+ children: [/* @__PURE__ */ jsx("div", {
1765
+ 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"),
1766
+ "aria-current": isActive ? "step" : void 0,
1767
+ children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
1768
+ }), /* @__PURE__ */ jsx("div", {
1769
+ className: "ml-2",
1770
+ children: /* @__PURE__ */ jsx("span", {
1771
+ className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1772
+ children: step.label
1773
+ })
1774
+ })]
1775
+ }), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line mx-4 h-0.5 min-w-8 flex-1" })] }, step.id);
1776
+ })
1777
+ });
1778
+ return /* @__PURE__ */ jsx("nav", {
1779
+ "aria-label": "Form steps",
1780
+ className: cn("flex flex-col", className),
1781
+ children: steps.map((step, index) => {
1782
+ const isActive = index === currentIndex;
1783
+ const isCompleted = index < currentIndex;
1784
+ const isLast = index === steps.length - 1;
1785
+ return /* @__PURE__ */ jsxs("div", {
1786
+ className: "flex flex-row",
1787
+ children: [/* @__PURE__ */ jsxs("div", {
1788
+ className: "flex flex-col items-center",
1789
+ children: [/* @__PURE__ */ jsx("div", {
1790
+ 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"),
1791
+ "aria-current": isActive ? "step" : void 0,
1792
+ children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
1793
+ }), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line my-1 min-h-8 w-0.5 flex-1" })]
1794
+ }), /* @__PURE__ */ jsxs("div", {
1795
+ className: "ml-3 pb-8",
1796
+ children: [/* @__PURE__ */ jsx("span", {
1797
+ className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1798
+ children: step.label
1799
+ }), step.description && /* @__PURE__ */ jsx("p", {
1800
+ className: "text-muted-foreground mt-0.5 text-xs",
1801
+ children: step.description
1802
+ })]
1803
+ })]
1804
+ }, step.id);
1805
+ })
1806
+ });
1807
+ }
1808
+ StepperNavigation.displayName = "Form.StepperNavigation";
1809
+
1810
+ //#endregion
1811
+ //#region src/components/features/form/components/form-input-group.tsx
1812
+ /**
1813
+ * Form.Input - Text input component
1814
+ *
1815
+ * Automatically wired to the parent Form.Field context.
1816
+ *
1817
+ * @example
1818
+ * ```tsx
1819
+ * <Form.Field name="email" label="Email" required>
1820
+ * <Form.Input type="email" placeholder="john@example.com" />
1821
+ * </Form.Field>
1822
+ * ```
1823
+ */
1824
+ function FormInputGroup({ ref, type = "text", className, disabled, ...props }) {
1825
+ const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
1826
+ const inputProps = getInputProps(fieldMeta, { type });
1827
+ const isDisabled = disabled ?? fieldDisabled;
1828
+ const hasErrors = errors && errors.length > 0;
1829
+ return /* @__PURE__ */ jsx(InputWithAddons, {
1830
+ ref,
1831
+ ...inputProps,
1832
+ ...props,
1833
+ type,
1834
+ disabled: isDisabled,
1835
+ "aria-invalid": hasErrors || void 0,
1836
+ "aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
1837
+ className: cn("text-xs!", className)
1838
+ });
1839
+ }
1840
+ FormInputGroup.displayName = "Form.InputGroup";
1841
+
1842
+ //#endregion
1843
+ //#region src/components/features/form/hooks/use-field.ts
1844
+ /**
1845
+ * Hook to access and control a specific field
1846
+ * Provides field metadata, control methods, and errors
1847
+ *
1848
+ * @example
1849
+ * ```tsx
1850
+ * function MyCustomInput({ name }: { name: string }) {
1851
+ * const { field, control, meta, errors } = useField(name);
1852
+ *
1853
+ * return (
1854
+ * <div>
1855
+ * <input
1856
+ * name={meta.name}
1857
+ * id={meta.id}
1858
+ * value={control.value ?? ''}
1859
+ * onChange={(e) => control.change(e.target.value)}
1860
+ * onBlur={control.blur}
1861
+ * aria-invalid={!!errors?.length}
1862
+ * />
1863
+ * {errors?.map((error) => (
1864
+ * <span key={error} className="text-red-500">{error}</span>
1865
+ * ))}
1866
+ * </div>
1867
+ * );
1868
+ * }
1869
+ * ```
1870
+ */
1871
+ function useField(name) {
1872
+ const { fields } = useFormContext$1();
1873
+ const field = React$1.useMemo(() => {
1874
+ const parts = name.split(".");
1875
+ let current = fields;
1876
+ for (let i = 0; i < parts.length; i++) {
1877
+ const part = parts[i];
1878
+ if (!current) break;
1879
+ if (/^\d+$/.test(part)) {
1880
+ const fieldList = current.getFieldList?.();
1881
+ if (fieldList) {
1882
+ const item = fieldList[Number.parseInt(part, 10)];
1883
+ if (i < parts.length - 1 && item?.getFieldset) current = item.getFieldset();
1884
+ else current = item;
1885
+ } else current = current[part];
1886
+ } else if (current[part] !== void 0) current = current[part];
1887
+ else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
1888
+ else current = void 0;
1889
+ }
1890
+ return current;
1891
+ }, [fields, name]);
1892
+ if (!field) throw new Error(`Field "${name}" not found in form. Make sure the field name matches your schema.`);
1893
+ const control = useInputControl(field);
1894
+ const controlValue = Array.isArray(control.value) ? control.value[0] : control.value;
1895
+ const meta = React$1.useMemo(() => ({
1896
+ name: field.name,
1897
+ id: field.id,
1898
+ errors: field.errors,
1899
+ required: field.required ?? false,
1900
+ disabled: field.disabled ?? false
1901
+ }), [
1902
+ field.name,
1903
+ field.id,
1904
+ field.errors,
1905
+ field.required,
1906
+ field.disabled
1907
+ ]);
1908
+ return {
1909
+ field,
1910
+ control: {
1911
+ value: controlValue,
1912
+ change: control.change,
1913
+ blur: control.blur,
1914
+ focus: control.focus
1915
+ },
1916
+ meta,
1917
+ errors: field.errors
1918
+ };
1919
+ }
1920
+
1921
+ //#endregion
1922
+ //#region src/components/features/form/hooks/use-field-context.ts
1923
+ /**
1924
+ * Hook to access the current field context
1925
+ * Must be used within a Form.Field component
1926
+ *
1927
+ * @example
1928
+ * ```tsx
1929
+ * function MyInput() {
1930
+ * const { name, id, errors, required, disabled, fieldMeta } = useFieldContext();
1931
+ *
1932
+ * return (
1933
+ * <input
1934
+ * name={name}
1935
+ * id={id}
1936
+ * required={required}
1937
+ * disabled={disabled}
1938
+ * aria-invalid={!!errors?.length}
1939
+ * />
1940
+ * );
1941
+ * }
1942
+ * ```
1943
+ */
1944
+ function useFieldContext() {
1945
+ return useFieldContext$1();
1946
+ }
1947
+
1948
+ //#endregion
1949
+ //#region src/components/features/form/hooks/use-form-context.ts
1950
+ /**
1951
+ * Hook to access the form context
1952
+ *
1953
+ * @example
1954
+ * ```tsx
1955
+ * function MyComponent() {
1956
+ * const { form, fields, isSubmitting, submit, reset } = useFormContext();
1957
+ *
1958
+ * return (
1959
+ * <button onClick={submit} disabled={isSubmitting}>
1960
+ * Submit
1961
+ * </button>
1962
+ * );
1963
+ * }
1964
+ * ```
1965
+ */
1966
+ function useFormContext() {
1967
+ return useFormContext$1();
1968
+ }
1969
+
1970
+ //#endregion
1971
+ //#region src/components/features/form/hooks/use-stepper.ts
1972
+ /**
1973
+ * Hook to access the stepper context
1974
+ * Must be used within a Form.Stepper component
1975
+ *
1976
+ * @example
1977
+ * ```tsx
1978
+ * function StepContent() {
1979
+ * const {
1980
+ * current,
1981
+ * currentIndex,
1982
+ * steps,
1983
+ * next,
1984
+ * prev,
1985
+ * goTo,
1986
+ * isFirst,
1987
+ * isLast,
1988
+ * } = useStepper();
1989
+ *
1990
+ * return (
1991
+ * <div>
1992
+ * <h2>Step {currentIndex + 1}: {current.label}</h2>
1993
+ * <button onClick={prev} disabled={isFirst}>Previous</button>
1994
+ * <button onClick={next} disabled={isLast}>Next</button>
1995
+ * </div>
1996
+ * );
1997
+ * }
1998
+ * ```
1999
+ */
2000
+ function useStepper() {
2001
+ const context = useFormStepperContext();
2002
+ return {
2003
+ steps: context.steps,
2004
+ current: context.current,
2005
+ currentIndex: context.currentIndex,
2006
+ next: context.next,
2007
+ prev: context.prev,
2008
+ goTo: context.goTo,
2009
+ isFirst: context.isFirst,
2010
+ isLast: context.isLast,
2011
+ getStepData: context.getStepData,
2012
+ getAllStepData: context.getAllStepData
2013
+ };
2014
+ }
2015
+
2016
+ //#endregion
2017
+ export { FormButton as A, FormField as C, FormCustom as D, FormDescription as E, FormCopyBox as O, FormFieldArray as S, FormDialog as T, FormSelectItem as _, FormInputGroup as a, FormRadioItem as b, FormStep as c, useWatch as d, useWatchAll as f, FormSelect as g, FormSubmit as h, useField as i, FormAutocomplete as j, FormCheckbox as k, FormStepper as l, FormSwitch as m, useFormContext as n, StepperNavigation as o, FormTextarea as p, useFieldContext as r, StepperControls as s, useStepper as t, FormWhen as u, FormRoot as v, FormError as w, FormInput as x, FormRadioGroup as y };