@datum-cloud/datum-ui 0.4.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.
- package/README.md +78 -39
- package/dist/adapter-context-BFqfq4Io.mjs +25 -0
- package/dist/alert/index.mjs +2 -3
- package/dist/{alert-BC2Mccfo.mjs → alert-BDj6od5I.mjs} +2 -4
- package/dist/app-navigation/index.mjs +4 -12
- package/dist/{app-navigation-DsCKgfPe.mjs → app-navigation-84ro28PU.mjs} +5 -8
- package/dist/autocomplete/index.mjs +2 -7
- package/dist/{autocomplete-DRB_kSVx.mjs → autocomplete-V5-qslzS.mjs} +5 -7
- package/dist/avatar/index.mjs +2 -4
- package/dist/{avatar-DyLq0xkt.mjs → avatar-BtKVcvO4.mjs} +2 -4
- package/dist/avatar-stack/index.mjs +2 -6
- package/dist/{avatar-stack-BT0dBswq.mjs → avatar-stack-oVr8tsU7.mjs} +4 -6
- package/dist/badge/index.mjs +2 -3
- package/dist/{badge-BgFj4Nsc.mjs → badge-DJR33ftJ.mjs} +2 -4
- package/dist/breadcrumb/index.mjs +2 -4
- package/dist/{breadcrumb-CJNaYyk1.mjs → breadcrumb-B-9G347O.mjs} +2 -4
- package/dist/button/index.mjs +3 -4
- package/dist/{button-0N61fmAR.mjs → button-BllvE9Lm.mjs} +3 -5
- package/dist/{button-D6AORsOz.mjs → button-D3RrsMfQ.mjs} +2 -4
- package/dist/{link-button-Cby0p4LW.mjs → button-fO8nazJE.mjs} +3 -5
- package/dist/button-group/index.mjs +2 -5
- package/dist/{button-group-BDk8btAy.mjs → button-group-CYPka2zz.mjs} +3 -5
- package/dist/calendar/index.mjs +2 -5
- package/dist/{calendar-BtfraIvX.mjs → calendar-DEkCw7I1.mjs} +4 -6
- package/dist/{calendar-date-picker-B9mxJM7f.mjs → calendar-date-picker-DWK94_DC.mjs} +6 -8
- package/dist/card/index.mjs +2 -4
- package/dist/{card-BiHXFt4s.mjs → card-DKG1Cwlj.mjs} +3 -6
- package/dist/chart/index.mjs +2 -4
- package/dist/{chart-CL0i-xIt.mjs → chart-CUa21ynK.mjs} +2 -4
- package/dist/checkbox/index.mjs +2 -4
- package/dist/{checkbox-CQrjygFt.mjs → checkbox-I5BvrMPe.mjs} +3 -6
- package/dist/{close.icon-D2r5q3bj.mjs → close.icon-HCfS4Y-N.mjs} +2 -4
- package/dist/{cn-DWCc1QRE.mjs → cn-D2KYQ917.mjs} +1 -3
- package/dist/code-editor/index.mjs +2 -0
- package/dist/{col-C9PDhvm5.mjs → col-CiSpQPUT.mjs} +2 -7
- package/dist/collapsible/index.mjs +2 -3
- package/dist/{collapsible-Dw71o2um.mjs → collapsible-CUphkSBt.mjs} +1 -3
- package/dist/command/index.mjs +2 -5
- package/dist/{command-DVroicgn.mjs → command-DqHWukGK.mjs} +3 -5
- package/dist/components/features/code-editor/code-editor-tabs.d.ts +63 -0
- package/dist/components/features/code-editor/code-editor-tabs.d.ts.map +1 -0
- package/dist/components/features/code-editor/code-editor.d.ts +58 -0
- package/dist/components/features/code-editor/code-editor.d.ts.map +1 -0
- package/dist/components/features/code-editor/index.d.ts +6 -0
- package/dist/components/features/code-editor/index.d.ts.map +1 -0
- package/dist/components/features/code-editor/lib/editor.d.ts +7 -0
- package/dist/components/features/code-editor/lib/editor.d.ts.map +1 -0
- package/dist/components/features/code-editor/types.d.ts +98 -0
- package/dist/components/features/code-editor/types.d.ts.map +1 -0
- package/dist/components/features/form/adapter-context.d.ts +17 -0
- package/dist/components/features/form/adapter-context.d.ts.map +1 -0
- package/dist/components/features/form/adapter-types.d.ts +120 -0
- package/dist/components/features/form/adapter-types.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts +9 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/index.d.ts +3 -0
- package/dist/components/features/form/adapters/conform/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts +3 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts +10 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts.map +1 -0
- package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
- package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
- package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
- package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field-array.d.ts +5 -17
- package/dist/components/features/form/components/form-field-array.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field.d.ts +7 -21
- package/dist/components/features/form/components/form-field.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input-group.d.ts +4 -4
- package/dist/components/features/form/components/form-input-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input.d.ts.map +1 -1
- package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-root.d.ts +5 -25
- package/dist/components/features/form/components/form-root.d.ts.map +1 -1
- package/dist/components/features/form/components/form-select.d.ts.map +1 -1
- package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
- package/dist/components/features/form/components/form-textarea.d.ts.map +1 -1
- package/dist/components/features/form/components/index.d.ts +0 -1
- package/dist/components/features/form/components/index.d.ts.map +1 -1
- package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
- package/dist/components/features/form/context/form-context.d.ts +2 -2
- package/dist/components/features/form/context/form-context.d.ts.map +1 -1
- package/dist/components/features/form/hooks/index.d.ts +1 -1
- package/dist/components/features/form/hooks/index.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-field.d.ts +12 -18
- package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
- package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
- package/dist/components/features/form/hooks/use-watch.d.ts +9 -20
- package/dist/components/features/form/hooks/use-watch.d.ts.map +1 -1
- package/dist/components/features/form/index.d.ts +63 -44
- package/dist/components/features/form/index.d.ts.map +1 -1
- package/dist/components/features/form/stepper/index.d.ts +17 -0
- package/dist/components/features/form/stepper/index.d.ts.map +1 -0
- package/dist/components/features/form/types/index.d.ts +68 -32
- package/dist/components/features/form/types/index.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts +11 -0
- package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -0
- package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
- package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
- package/dist/components/features/index.d.ts +1 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/toast.d.ts +2 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/data-table/index.mjs +21 -51
- package/dist/date-picker/index.mjs +3 -10
- package/dist/dialog/index.mjs +2 -5
- package/dist/{dialog-B0B3Kbfk.mjs → dialog-Bm2H9lrx.mjs} +4 -6
- package/dist/{dialog-DdrHeboM.mjs → dialog-DASRaFxD.mjs} +2 -4
- package/dist/dropdown/index.mjs +2 -3
- package/dist/{dropdown-Cdx7rOKv.mjs → dropdown-DZiAt-jS.mjs} +3 -5
- package/dist/{dropdown-menu-CdShrDz_.mjs → dropdown-menu-lALvDnab.mjs} +5 -7
- package/dist/dropzone/index.mjs +2 -5
- package/dist/{dropzone-B6kSN3DY.mjs → dropzone-ogtpQ4fy.mjs} +5 -8
- package/dist/empty-content/index.mjs +2 -3
- package/dist/{empty-content-B1lwLr40.mjs → empty-content-C63GPJ5d.mjs} +3 -9
- package/dist/form/adapters/conform/index.mjs +320 -0
- package/dist/form/adapters/rhf/index.mjs +275 -0
- package/dist/form/index.mjs +3 -146
- package/dist/form/stepper/index.mjs +542 -0
- package/dist/form-C6AOB2f4.mjs +1397 -0
- package/dist/form-context-Ccxm-wqL.mjs +17 -0
- package/dist/get-field-constraints-D4xnXJEg.mjs +48 -0
- package/dist/grid/index.mjs +2 -3
- package/dist/hooks/index.mjs +3 -4
- package/dist/{use-debounce-MnfjH51L.mjs → hooks-DNjmSsJT.mjs} +1 -3
- package/dist/hover-card/index.mjs +2 -4
- package/dist/{hover-card-CEIauuie.mjs → hover-card-DDWWD5Hx.mjs} +2 -4
- package/dist/{icon-wrapper-BBK4z4tj.mjs → icon-wrapper-DuLp3RM1.mjs} +1 -3
- package/dist/icons/index.mjs +4 -5
- package/dist/index.mjs +66 -71
- package/dist/input/index.mjs +2 -5
- package/dist/{input-DEMoi_8F.mjs → input-DOmNpcQJ.mjs} +2 -4
- package/dist/{input-CYFN0Ap2.mjs → input-FKGqZypx.mjs} +3 -5
- package/dist/input-group/index.mjs +2 -7
- package/dist/{input-group-DJgYpOlq.mjs → input-group-DDtz-RT7.mjs} +5 -7
- package/dist/input-number/index.mjs +2 -6
- package/dist/{input-number-Cuy9CCg_.mjs → input-number-BTQdHqVZ.mjs} +4 -6
- package/dist/input-with-addons/index.mjs +28 -3
- package/dist/label/index.mjs +2 -4
- package/dist/{label-mOg07fuQ.mjs → label-cnAhY-ej.mjs} +3 -6
- package/dist/loader-overlay/index.mjs +2 -3
- package/dist/{loader-overlay-8IWX_1Ga.mjs → loader-overlay-BTFdkp7W.mjs} +3 -5
- package/dist/map/index.mjs +2 -14
- package/dist/{map-CaI1EshG.mjs → map-BqpteT_8.mjs} +10 -14
- package/dist/{map-leaflet-imports-J7w1V7mh.mjs → map-leaflet-imports-CT4SpoDi.mjs} +1 -2
- package/dist/more-actions/index.mjs +2 -5
- package/dist/{more-actions-BO5ikUxY.mjs → more-actions-CucrYUnA.mjs} +5 -7
- package/dist/nprogress/index.mjs +1 -3
- package/dist/page-title/index.mjs +2 -3
- package/dist/{page-title-DWteBy1E.mjs → page-title-CmsIi_A3.mjs} +2 -4
- package/dist/popover/index.mjs +2 -4
- package/dist/{popover-ugw5MpuT.mjs → popover-FJAcbYoH.mjs} +2 -4
- package/dist/radio-group/index.mjs +2 -4
- package/dist/{radio-group-_gMymwnb.mjs → radio-group-CiITR0LO.mjs} +3 -6
- package/dist/select/index.mjs +2 -4
- package/dist/{select-BZOKWjlH.mjs → select-CiLR_DiQ.mjs} +3 -6
- package/dist/separator/index.mjs +2 -4
- package/dist/{separator-BzyALya2.mjs → separator-DXVTncCK.mjs} +2 -4
- package/dist/sheet/index.mjs +3 -5
- package/dist/{sheet-BX6lae56.mjs → sheet-BzXksqYY.mjs} +4 -6
- package/dist/{sheet-DAcFjaGw.mjs → sheet-Di3b-oPu.mjs} +2 -4
- package/dist/sidebar/index.mjs +2 -10
- package/dist/{sidebar-B3EV33mG.mjs → sidebar-BnhnjvfO.mjs} +10 -14
- package/dist/skeleton/index.mjs +2 -5
- package/dist/{skeleton-2vQ0vFQk.mjs → skeleton-BKl4mfJt.mjs} +2 -4
- package/dist/{skeleton-BgOwIgE0.mjs → skeleton-D1MUhAVo.mjs} +3 -5
- package/dist/spinner/index.mjs +2 -4
- package/dist/{spinner-osyXAlhr.mjs → spinner-OyOf9-Yu.mjs} +2 -4
- package/dist/{spinner.icon-C0MbtgqX.mjs → spinner.icon-C-vjSM6o.mjs} +2 -4
- package/dist/stepper/index.mjs +2 -5
- package/dist/{stepper-BMsn7I78.mjs → stepper-C92Ib8Iy.mjs} +3 -5
- package/dist/switch/index.mjs +2 -4
- package/dist/{switch-C60FpEal.mjs → switch-DQJQhPIQ.mjs} +3 -6
- package/dist/table/index.mjs +2 -4
- package/dist/{table-Cl3UzIhI.mjs → table-Cdsh-39-.mjs} +2 -4
- package/dist/tabs/index.mjs +50 -3
- package/dist/tag-input/index.mjs +2 -5
- package/dist/{tag-input-DR2gukhL.mjs → tag-input-B91C2wdr.mjs} +5 -7
- package/dist/task-queue/index.mjs +2 -7
- package/dist/{task-queue-dropdown-C9KHKbGh.mjs → task-queue-dropdown-OOFuJcHb.mjs} +10 -30
- package/dist/textarea/index.mjs +2 -5
- package/dist/{textarea-CVo38n3S.mjs → textarea-94vq_G_S.mjs} +2 -4
- package/dist/{textarea-CZF5n57i.mjs → textarea-BwD-MmTV.mjs} +3 -5
- package/dist/theme/index.mjs +2 -3
- package/dist/{theme.provider-TUHlMsjM.mjs → themes-DG1md8FI.mjs} +1 -6
- package/dist/{to-api-format-naIpF-NI.mjs → to-api-format-P0gmlqe8.mjs} +9 -18
- package/dist/toast/index.mjs +3 -3
- package/dist/{use-toast-By9HuFwP.mjs → toast-BWnN5fax.mjs} +5 -42
- package/dist/toast-DpxlFNNx.mjs +37 -0
- package/dist/tooltip/index.mjs +2 -4
- package/dist/{tooltip-CuX2jQA9.mjs → tooltip-Cruvl5F6.mjs} +3 -6
- package/dist/types-BZNk3q65.mjs +357 -0
- package/dist/typography/index.mjs +2 -3
- package/dist/{typography-Iap9fU5P.mjs → typography-ClB8k55E.mjs} +2 -4
- package/dist/{use-copy-to-clipboard-n29wJwvW.mjs → use-copy-to-clipboard-C2IEmhDn.mjs} +2 -4
- package/dist/utils/index.mjs +2 -3
- package/dist/{utils-DJboNGQM.mjs → utils-C8KwMfT_.mjs} +1 -3
- package/dist/visually-hidden/index.mjs +2 -3
- package/dist/{visuallyhidden-BJsQCmg-.mjs → visuallyhidden-BLUsJpYH.mjs} +1 -3
- package/package.json +49 -3
- package/dist/input-with-addons-B8rzNhpq.mjs +0 -30
- package/dist/tabs-DJU7JA3h.mjs +0 -52
- package/dist/use-stepper-DigoyHhX.mjs +0 -2017
|
@@ -1,2017 +0,0 @@
|
|
|
1
|
-
import { t as cn } from "./cn-DWCc1QRE.mjs";
|
|
2
|
-
import { t as Button } from "./button-0N61fmAR.mjs";
|
|
3
|
-
import { t as Icon } from "./icon-wrapper-BBK4z4tj.mjs";
|
|
4
|
-
import { t as Checkbox } from "./checkbox-CQrjygFt.mjs";
|
|
5
|
-
import { t as Dialog } from "./dialog-B0B3Kbfk.mjs";
|
|
6
|
-
import { t as Input } from "./input-CYFN0Ap2.mjs";
|
|
7
|
-
import { t as Label } from "./label-mOg07fuQ.mjs";
|
|
8
|
-
import { n as RadioGroupItem, t as RadioGroup } from "./radio-group-_gMymwnb.mjs";
|
|
9
|
-
import { i as SelectItem, l as SelectTrigger, n as SelectContent, t as Select, u as SelectValue } from "./select-BZOKWjlH.mjs";
|
|
10
|
-
import { t as Tooltip } from "./tooltip-CuX2jQA9.mjs";
|
|
11
|
-
import { t as Switch } from "./switch-C60FpEal.mjs";
|
|
12
|
-
import { t as Textarea } from "./textarea-CZF5n57i.mjs";
|
|
13
|
-
import { t as Autocomplete } from "./autocomplete-DRB_kSVx.mjs";
|
|
14
|
-
import { r as toast } from "./use-toast-By9HuFwP.mjs";
|
|
15
|
-
import { t as useCopyToClipboard } from "./use-copy-to-clipboard-n29wJwvW.mjs";
|
|
16
|
-
import { t as defineStepper } from "./stepper-BMsn7I78.mjs";
|
|
17
|
-
import { t as InputWithAddons } from "./input-with-addons-B8rzNhpq.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 };
|