@liam-michel/validated-form 0.1.0
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/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/index.cjs +767 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +720 -0
- package/dist/index.js.map +1 -0
- package/package.json +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
// src/ValidatedForm.tsx
|
|
2
|
+
import { useForm, FormProvider } from "react-hook-form";
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import "zod";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function ValidatedForm({
|
|
7
|
+
children,
|
|
8
|
+
schema,
|
|
9
|
+
onSubmit,
|
|
10
|
+
defaultValues,
|
|
11
|
+
form: externalForm,
|
|
12
|
+
submitLabel,
|
|
13
|
+
hideSubmit,
|
|
14
|
+
renderSubmit,
|
|
15
|
+
renderError
|
|
16
|
+
}) {
|
|
17
|
+
const internalForm = useForm({
|
|
18
|
+
resolver: zodResolver(schema),
|
|
19
|
+
defaultValues
|
|
20
|
+
});
|
|
21
|
+
const form = externalForm ?? internalForm;
|
|
22
|
+
const handleSubmit = form.handleSubmit(async (data) => {
|
|
23
|
+
try {
|
|
24
|
+
await onSubmit(data);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
const fieldErrors = err?.data?.fieldErrors;
|
|
27
|
+
if (fieldErrors) {
|
|
28
|
+
Object.entries(fieldErrors).forEach(([field, message]) => {
|
|
29
|
+
form.setError(field, { message });
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
form.setError("root", {
|
|
33
|
+
message: err?.data?.message ?? err?.message ?? "Something went wrong"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
39
|
+
typeof children === "function" ? children(form) : children,
|
|
40
|
+
form.formState.errors.root && (renderError ? renderError(form.formState.errors.root.message) : /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: form.formState.errors.root.message })),
|
|
41
|
+
renderSubmit ? renderSubmit({
|
|
42
|
+
isSubmitting: form.formState.isSubmitting,
|
|
43
|
+
reset: form.reset
|
|
44
|
+
}) : !hideSubmit && /* @__PURE__ */ jsx(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "submit",
|
|
48
|
+
disabled: form.formState.isSubmitting,
|
|
49
|
+
className: "inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50",
|
|
50
|
+
children: form.formState.isSubmitting ? "Submitting..." : submitLabel ?? "Submit"
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
] }) });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/createForm.tsx
|
|
57
|
+
import "zod";
|
|
58
|
+
|
|
59
|
+
// src/fields/TextField.tsx
|
|
60
|
+
import "react";
|
|
61
|
+
import { Controller, useFormContext } from "react-hook-form";
|
|
62
|
+
import "zod";
|
|
63
|
+
|
|
64
|
+
// src/utils/cn.ts
|
|
65
|
+
import { clsx } from "clsx";
|
|
66
|
+
import { twMerge } from "tailwind-merge";
|
|
67
|
+
function cn(...inputs) {
|
|
68
|
+
return twMerge(clsx(inputs));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/fields/TextField.tsx
|
|
72
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
73
|
+
function TextField({
|
|
74
|
+
name,
|
|
75
|
+
label,
|
|
76
|
+
placeholder,
|
|
77
|
+
disabled,
|
|
78
|
+
description,
|
|
79
|
+
className,
|
|
80
|
+
form,
|
|
81
|
+
type = "text",
|
|
82
|
+
showReset
|
|
83
|
+
}) {
|
|
84
|
+
const contextForm = useFormContext();
|
|
85
|
+
const actualForm = form || contextForm;
|
|
86
|
+
return /* @__PURE__ */ jsx2(
|
|
87
|
+
Controller,
|
|
88
|
+
{
|
|
89
|
+
control: actualForm.control,
|
|
90
|
+
name,
|
|
91
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs2("div", { className: cn("space-y-2", className), children: [
|
|
92
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
|
|
93
|
+
/* @__PURE__ */ jsx2(
|
|
94
|
+
"label",
|
|
95
|
+
{
|
|
96
|
+
htmlFor: name,
|
|
97
|
+
className: "text-sm font-medium leading-none",
|
|
98
|
+
children: label
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
showReset && /* @__PURE__ */ jsx2(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
onClick: () => actualForm.resetField(name),
|
|
106
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
107
|
+
children: "Reset"
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx2(
|
|
112
|
+
"input",
|
|
113
|
+
{
|
|
114
|
+
...field,
|
|
115
|
+
id: name,
|
|
116
|
+
value: field.value ?? "",
|
|
117
|
+
placeholder,
|
|
118
|
+
disabled,
|
|
119
|
+
type,
|
|
120
|
+
className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
description && /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
124
|
+
fieldState.error && /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
125
|
+
] })
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/fields/TextAreaField.tsx
|
|
131
|
+
import "react";
|
|
132
|
+
import { Controller as Controller2, useFormContext as useFormContext2 } from "react-hook-form";
|
|
133
|
+
import "zod";
|
|
134
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
135
|
+
function TextAreaField({
|
|
136
|
+
name,
|
|
137
|
+
label,
|
|
138
|
+
placeholder,
|
|
139
|
+
disabled,
|
|
140
|
+
description,
|
|
141
|
+
className,
|
|
142
|
+
form,
|
|
143
|
+
showReset
|
|
144
|
+
}) {
|
|
145
|
+
const contextForm = useFormContext2();
|
|
146
|
+
const actualForm = form || contextForm;
|
|
147
|
+
return /* @__PURE__ */ jsx3(
|
|
148
|
+
Controller2,
|
|
149
|
+
{
|
|
150
|
+
control: actualForm.control,
|
|
151
|
+
name,
|
|
152
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs3("div", { className: cn("space-y-2", className), children: [
|
|
153
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
154
|
+
/* @__PURE__ */ jsx3("label", { htmlFor: name, className: "text-sm font-medium leading-none", children: label }),
|
|
155
|
+
showReset && /* @__PURE__ */ jsx3(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
type: "button",
|
|
159
|
+
onClick: () => actualForm.resetField(name),
|
|
160
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
161
|
+
children: "Reset"
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsx3(
|
|
166
|
+
"textarea",
|
|
167
|
+
{
|
|
168
|
+
...field,
|
|
169
|
+
id: name,
|
|
170
|
+
value: field.value ?? "",
|
|
171
|
+
placeholder,
|
|
172
|
+
disabled,
|
|
173
|
+
className: "flex min-h-[75px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
description && /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
177
|
+
fieldState.error && /* @__PURE__ */ jsx3("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
178
|
+
] })
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/fields/NumberField.tsx
|
|
184
|
+
import "react";
|
|
185
|
+
import { Controller as Controller3, useFormContext as useFormContext3 } from "react-hook-form";
|
|
186
|
+
import "zod";
|
|
187
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
188
|
+
function NumberField({
|
|
189
|
+
name,
|
|
190
|
+
label,
|
|
191
|
+
placeholder,
|
|
192
|
+
disabled,
|
|
193
|
+
description,
|
|
194
|
+
className,
|
|
195
|
+
form,
|
|
196
|
+
showReset
|
|
197
|
+
}) {
|
|
198
|
+
const contextForm = useFormContext3();
|
|
199
|
+
const actualForm = form || contextForm;
|
|
200
|
+
return /* @__PURE__ */ jsx4(
|
|
201
|
+
Controller3,
|
|
202
|
+
{
|
|
203
|
+
control: actualForm.control,
|
|
204
|
+
name,
|
|
205
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs4("div", { className: cn("space-y-2", className), children: [
|
|
206
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
|
|
207
|
+
/* @__PURE__ */ jsx4("label", { htmlFor: name, className: "text-sm font-medium leading-none", children: label }),
|
|
208
|
+
showReset && /* @__PURE__ */ jsx4(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
type: "button",
|
|
212
|
+
onClick: () => actualForm.resetField(name),
|
|
213
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
214
|
+
children: "Reset"
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsx4(
|
|
219
|
+
"input",
|
|
220
|
+
{
|
|
221
|
+
...field,
|
|
222
|
+
id: name,
|
|
223
|
+
value: field.value ?? "",
|
|
224
|
+
placeholder,
|
|
225
|
+
disabled,
|
|
226
|
+
type: "number",
|
|
227
|
+
onChange: (e) => {
|
|
228
|
+
const value = e.target.value;
|
|
229
|
+
field.onChange(value === "" ? void 0 : Number(value));
|
|
230
|
+
},
|
|
231
|
+
className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
description && /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
235
|
+
fieldState.error && /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
236
|
+
] })
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/fields/SelectField.tsx
|
|
242
|
+
import "react";
|
|
243
|
+
import { Controller as Controller4, useFormContext as useFormContext4 } from "react-hook-form";
|
|
244
|
+
import "zod";
|
|
245
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
246
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
247
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
248
|
+
function SelectField({
|
|
249
|
+
name,
|
|
250
|
+
label,
|
|
251
|
+
placeholder,
|
|
252
|
+
disabled,
|
|
253
|
+
description,
|
|
254
|
+
className,
|
|
255
|
+
form,
|
|
256
|
+
options,
|
|
257
|
+
showReset
|
|
258
|
+
}) {
|
|
259
|
+
const contextForm = useFormContext4();
|
|
260
|
+
const actualForm = form || contextForm;
|
|
261
|
+
return /* @__PURE__ */ jsx5(
|
|
262
|
+
Controller4,
|
|
263
|
+
{
|
|
264
|
+
control: actualForm.control,
|
|
265
|
+
name,
|
|
266
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs5("div", { className: cn("space-y-2", className), children: [
|
|
267
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
|
|
268
|
+
/* @__PURE__ */ jsx5("label", { htmlFor: name, className: "text-sm font-medium leading-none", children: label }),
|
|
269
|
+
showReset && /* @__PURE__ */ jsx5(
|
|
270
|
+
"button",
|
|
271
|
+
{
|
|
272
|
+
type: "button",
|
|
273
|
+
onClick: () => actualForm.resetField(name),
|
|
274
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
275
|
+
children: "Reset"
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
] }),
|
|
279
|
+
/* @__PURE__ */ jsxs5(
|
|
280
|
+
SelectPrimitive.Root,
|
|
281
|
+
{
|
|
282
|
+
onValueChange: field.onChange,
|
|
283
|
+
value: String(field.value || ""),
|
|
284
|
+
disabled,
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsxs5(SelectPrimitive.Trigger, { className: "flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50", children: [
|
|
287
|
+
/* @__PURE__ */ jsx5(
|
|
288
|
+
SelectPrimitive.Value,
|
|
289
|
+
{
|
|
290
|
+
placeholder: placeholder ?? "Select an option"
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
/* @__PURE__ */ jsx5(SelectPrimitive.Icon, { children: /* @__PURE__ */ jsx5(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
|
|
294
|
+
] }),
|
|
295
|
+
/* @__PURE__ */ jsx5(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsx5(SelectPrimitive.Content, { className: "relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md", children: /* @__PURE__ */ jsx5(SelectPrimitive.Viewport, { className: "p-1", children: options.map((option) => /* @__PURE__ */ jsxs5(
|
|
296
|
+
SelectPrimitive.Item,
|
|
297
|
+
{
|
|
298
|
+
value: String(option.value),
|
|
299
|
+
className: "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
300
|
+
children: [
|
|
301
|
+
/* @__PURE__ */ jsx5("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx5(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx5(Check, { className: "h-4 w-4" }) }) }),
|
|
302
|
+
/* @__PURE__ */ jsx5(SelectPrimitive.ItemText, { children: option.label })
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
String(option.value)
|
|
306
|
+
)) }) }) })
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
),
|
|
310
|
+
description && /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
311
|
+
fieldState.error && /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
312
|
+
] })
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/fields/CheckboxField.tsx
|
|
318
|
+
import { Controller as Controller5, useFormContext as useFormContext5 } from "react-hook-form";
|
|
319
|
+
import "zod";
|
|
320
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
321
|
+
import { Check as Check2 } from "lucide-react";
|
|
322
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
323
|
+
function CheckboxField({
|
|
324
|
+
name,
|
|
325
|
+
label,
|
|
326
|
+
disabled,
|
|
327
|
+
description,
|
|
328
|
+
className,
|
|
329
|
+
form
|
|
330
|
+
}) {
|
|
331
|
+
const contextForm = useFormContext5();
|
|
332
|
+
const actualForm = form || contextForm;
|
|
333
|
+
return /* @__PURE__ */ jsx6(
|
|
334
|
+
Controller5,
|
|
335
|
+
{
|
|
336
|
+
control: actualForm.control,
|
|
337
|
+
name,
|
|
338
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs6("div", { className: cn("space-y-2", className), children: [
|
|
339
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
|
|
340
|
+
/* @__PURE__ */ jsx6(
|
|
341
|
+
CheckboxPrimitive.Root,
|
|
342
|
+
{
|
|
343
|
+
id: name,
|
|
344
|
+
checked: !!field.value,
|
|
345
|
+
onCheckedChange: (checked) => field.onChange(checked),
|
|
346
|
+
disabled,
|
|
347
|
+
className: "h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
348
|
+
children: /* @__PURE__ */ jsx6(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center text-current", children: /* @__PURE__ */ jsx6(Check2, { className: "h-3 w-3" }) })
|
|
349
|
+
}
|
|
350
|
+
),
|
|
351
|
+
/* @__PURE__ */ jsx6(
|
|
352
|
+
"label",
|
|
353
|
+
{
|
|
354
|
+
htmlFor: name,
|
|
355
|
+
className: "text-sm font-medium leading-none",
|
|
356
|
+
children: label
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] }),
|
|
360
|
+
description && /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
361
|
+
fieldState.error && /* @__PURE__ */ jsx6("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
362
|
+
] })
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/fields/DateField.tsx
|
|
368
|
+
import "react";
|
|
369
|
+
import { Controller as Controller6, useFormContext as useFormContext6 } from "react-hook-form";
|
|
370
|
+
import "zod";
|
|
371
|
+
import { DayPicker } from "react-day-picker";
|
|
372
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
373
|
+
import { format } from "date-fns";
|
|
374
|
+
import { CalendarIcon } from "lucide-react";
|
|
375
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
376
|
+
function DateField({
|
|
377
|
+
name,
|
|
378
|
+
label,
|
|
379
|
+
description,
|
|
380
|
+
className,
|
|
381
|
+
form,
|
|
382
|
+
showReset
|
|
383
|
+
}) {
|
|
384
|
+
const contextForm = useFormContext6();
|
|
385
|
+
const actualForm = form || contextForm;
|
|
386
|
+
return /* @__PURE__ */ jsx7(
|
|
387
|
+
Controller6,
|
|
388
|
+
{
|
|
389
|
+
control: actualForm.control,
|
|
390
|
+
name,
|
|
391
|
+
render: ({ field, fieldState }) => {
|
|
392
|
+
const date = field.value ? new Date(field.value) : void 0;
|
|
393
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("space-y-2", className), children: [
|
|
394
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
|
|
395
|
+
/* @__PURE__ */ jsx7("label", { htmlFor: name, className: "text-sm font-medium leading-none", children: label }),
|
|
396
|
+
showReset && /* @__PURE__ */ jsx7(
|
|
397
|
+
"button",
|
|
398
|
+
{
|
|
399
|
+
type: "button",
|
|
400
|
+
onClick: () => actualForm.resetField(name),
|
|
401
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
402
|
+
children: "Reset"
|
|
403
|
+
}
|
|
404
|
+
)
|
|
405
|
+
] }),
|
|
406
|
+
/* @__PURE__ */ jsxs7(PopoverPrimitive.Root, { children: [
|
|
407
|
+
/* @__PURE__ */ jsx7(PopoverPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs7(
|
|
408
|
+
"button",
|
|
409
|
+
{
|
|
410
|
+
type: "button",
|
|
411
|
+
className: cn(
|
|
412
|
+
"flex h-9 w-full items-center justify-start gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
413
|
+
!date && "text-muted-foreground"
|
|
414
|
+
),
|
|
415
|
+
children: [
|
|
416
|
+
/* @__PURE__ */ jsx7(CalendarIcon, { className: "h-4 w-4" }),
|
|
417
|
+
date ? format(date, "PPP") : "Pick a date"
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
) }),
|
|
421
|
+
/* @__PURE__ */ jsx7(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx7(
|
|
422
|
+
PopoverPrimitive.Content,
|
|
423
|
+
{
|
|
424
|
+
align: "start",
|
|
425
|
+
className: "z-50 rounded-md border bg-popover p-0 text-popover-foreground shadow-md outline-none",
|
|
426
|
+
children: /* @__PURE__ */ jsx7(
|
|
427
|
+
DayPicker,
|
|
428
|
+
{
|
|
429
|
+
mode: "single",
|
|
430
|
+
selected: date,
|
|
431
|
+
onSelect: (d) => field.onChange(d),
|
|
432
|
+
className: "p-3"
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
) })
|
|
437
|
+
] }),
|
|
438
|
+
description && /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
439
|
+
fieldState.error && /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/fields/SliderField.tsx
|
|
447
|
+
import "react";
|
|
448
|
+
import { Controller as Controller7, useFormContext as useFormContext7 } from "react-hook-form";
|
|
449
|
+
import "zod";
|
|
450
|
+
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
451
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
452
|
+
function SliderField({
|
|
453
|
+
name,
|
|
454
|
+
label,
|
|
455
|
+
description,
|
|
456
|
+
disabled,
|
|
457
|
+
className,
|
|
458
|
+
form,
|
|
459
|
+
min = 0,
|
|
460
|
+
max = 100,
|
|
461
|
+
step = 1,
|
|
462
|
+
showReset
|
|
463
|
+
}) {
|
|
464
|
+
const contextForm = useFormContext7();
|
|
465
|
+
const actualForm = form || contextForm;
|
|
466
|
+
return /* @__PURE__ */ jsx8(
|
|
467
|
+
Controller7,
|
|
468
|
+
{
|
|
469
|
+
control: actualForm.control,
|
|
470
|
+
name,
|
|
471
|
+
render: ({ field, fieldState }) => {
|
|
472
|
+
const value = field.value !== void 0 ? field.value : min;
|
|
473
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("space-y-2", className), children: [
|
|
474
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
|
|
475
|
+
/* @__PURE__ */ jsx8("label", { htmlFor: name, className: "text-sm font-medium leading-none", children: label }),
|
|
476
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
477
|
+
/* @__PURE__ */ jsx8("span", { className: "text-sm text-muted-foreground", children: value }),
|
|
478
|
+
showReset && /* @__PURE__ */ jsx8(
|
|
479
|
+
"button",
|
|
480
|
+
{
|
|
481
|
+
type: "button",
|
|
482
|
+
onClick: () => actualForm.resetField(name),
|
|
483
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
484
|
+
children: "Reset"
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
] })
|
|
488
|
+
] }),
|
|
489
|
+
/* @__PURE__ */ jsxs8(
|
|
490
|
+
SliderPrimitive.Root,
|
|
491
|
+
{
|
|
492
|
+
value: [value],
|
|
493
|
+
onValueChange: ([val]) => field.onChange(val),
|
|
494
|
+
min,
|
|
495
|
+
max,
|
|
496
|
+
step,
|
|
497
|
+
disabled,
|
|
498
|
+
className: "relative flex w-full touch-none select-none items-center",
|
|
499
|
+
children: [
|
|
500
|
+
/* @__PURE__ */ jsx8(SliderPrimitive.Track, { className: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20", children: /* @__PURE__ */ jsx8(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
|
|
501
|
+
/* @__PURE__ */ jsx8(SliderPrimitive.Thumb, { className: "block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" })
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
),
|
|
505
|
+
description && /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
506
|
+
fieldState.error && /* @__PURE__ */ jsx8("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
507
|
+
] });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/fields/MultiSelectField.tsx
|
|
514
|
+
import "react";
|
|
515
|
+
import { Controller as Controller8, useFormContext as useFormContext8 } from "react-hook-form";
|
|
516
|
+
import "zod";
|
|
517
|
+
import * as PopoverPrimitive2 from "@radix-ui/react-popover";
|
|
518
|
+
import { Command } from "cmdk";
|
|
519
|
+
import { Check as Check3, ChevronsUpDown, X } from "lucide-react";
|
|
520
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
521
|
+
function MultiSelectField({
|
|
522
|
+
name,
|
|
523
|
+
label,
|
|
524
|
+
description,
|
|
525
|
+
disabled,
|
|
526
|
+
form,
|
|
527
|
+
className,
|
|
528
|
+
options,
|
|
529
|
+
placeholder = "Select options...",
|
|
530
|
+
showReset
|
|
531
|
+
}) {
|
|
532
|
+
const contextForm = useFormContext8();
|
|
533
|
+
const actualForm = form || contextForm;
|
|
534
|
+
return /* @__PURE__ */ jsx9(
|
|
535
|
+
Controller8,
|
|
536
|
+
{
|
|
537
|
+
control: actualForm.control,
|
|
538
|
+
name,
|
|
539
|
+
render: ({ field, fieldState }) => {
|
|
540
|
+
const selected = Array.isArray(field.value) ? field.value : [];
|
|
541
|
+
const toggle = (val) => {
|
|
542
|
+
const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val];
|
|
543
|
+
field.onChange(next);
|
|
544
|
+
};
|
|
545
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("space-y-2", className), children: [
|
|
546
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
|
|
547
|
+
/* @__PURE__ */ jsx9("label", { className: "text-sm font-medium leading-none", children: label }),
|
|
548
|
+
showReset && /* @__PURE__ */ jsx9(
|
|
549
|
+
"button",
|
|
550
|
+
{
|
|
551
|
+
type: "button",
|
|
552
|
+
onClick: () => actualForm.resetField(name),
|
|
553
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
554
|
+
children: "Reset"
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
] }),
|
|
558
|
+
/* @__PURE__ */ jsxs9(PopoverPrimitive2.Root, { children: [
|
|
559
|
+
/* @__PURE__ */ jsx9(PopoverPrimitive2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs9(
|
|
560
|
+
"button",
|
|
561
|
+
{
|
|
562
|
+
type: "button",
|
|
563
|
+
role: "combobox",
|
|
564
|
+
disabled,
|
|
565
|
+
className: cn(
|
|
566
|
+
"flex min-h-9 w-full flex-wrap items-center justify-between gap-1 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
567
|
+
!selected.length && "text-muted-foreground"
|
|
568
|
+
),
|
|
569
|
+
children: [
|
|
570
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-1", children: selected.length ? selected.map((val) => /* @__PURE__ */ jsxs9(
|
|
571
|
+
"span",
|
|
572
|
+
{
|
|
573
|
+
className: "inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-xs text-secondary-foreground",
|
|
574
|
+
children: [
|
|
575
|
+
options.find((o) => o.value === val)?.label ?? val,
|
|
576
|
+
/* @__PURE__ */ jsx9(
|
|
577
|
+
"span",
|
|
578
|
+
{
|
|
579
|
+
onClick: (e) => {
|
|
580
|
+
e.stopPropagation();
|
|
581
|
+
toggle(val);
|
|
582
|
+
},
|
|
583
|
+
className: "cursor-pointer",
|
|
584
|
+
children: /* @__PURE__ */ jsx9(X, { className: "h-3 w-3" })
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
]
|
|
588
|
+
},
|
|
589
|
+
val
|
|
590
|
+
)) : placeholder }),
|
|
591
|
+
/* @__PURE__ */ jsx9(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
) }),
|
|
595
|
+
/* @__PURE__ */ jsx9(PopoverPrimitive2.Portal, { children: /* @__PURE__ */ jsx9(
|
|
596
|
+
PopoverPrimitive2.Content,
|
|
597
|
+
{
|
|
598
|
+
align: "start",
|
|
599
|
+
className: "z-50 min-w-[200px] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none",
|
|
600
|
+
children: /* @__PURE__ */ jsxs9(Command, { children: [
|
|
601
|
+
/* @__PURE__ */ jsx9("div", { className: "flex items-center border-b px-3", children: /* @__PURE__ */ jsx9(
|
|
602
|
+
Command.Input,
|
|
603
|
+
{
|
|
604
|
+
placeholder: "Search...",
|
|
605
|
+
className: "flex h-9 w-full bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
|
606
|
+
}
|
|
607
|
+
) }),
|
|
608
|
+
/* @__PURE__ */ jsxs9(Command.List, { className: "max-h-[200px] overflow-y-auto p-1", children: [
|
|
609
|
+
/* @__PURE__ */ jsx9(Command.Empty, { className: "py-6 text-center text-sm", children: "No options found." }),
|
|
610
|
+
/* @__PURE__ */ jsx9(Command.Group, { children: options.map((option) => /* @__PURE__ */ jsxs9(
|
|
611
|
+
Command.Item,
|
|
612
|
+
{
|
|
613
|
+
value: option.value,
|
|
614
|
+
onSelect: () => toggle(option.value),
|
|
615
|
+
className: "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
616
|
+
children: [
|
|
617
|
+
/* @__PURE__ */ jsx9(
|
|
618
|
+
Check3,
|
|
619
|
+
{
|
|
620
|
+
className: cn(
|
|
621
|
+
"mr-2 h-4 w-4",
|
|
622
|
+
selected.includes(option.value) ? "opacity-100" : "opacity-0"
|
|
623
|
+
)
|
|
624
|
+
}
|
|
625
|
+
),
|
|
626
|
+
option.label
|
|
627
|
+
]
|
|
628
|
+
},
|
|
629
|
+
option.value
|
|
630
|
+
)) })
|
|
631
|
+
] })
|
|
632
|
+
] })
|
|
633
|
+
}
|
|
634
|
+
) })
|
|
635
|
+
] }),
|
|
636
|
+
description && /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
637
|
+
fieldState.error && /* @__PURE__ */ jsx9("p", { className: "text-sm font-medium text-destructive", children: fieldState.error.message })
|
|
638
|
+
] });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/createForm.tsx
|
|
645
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
646
|
+
function createForm(schema) {
|
|
647
|
+
const Form = ({
|
|
648
|
+
children,
|
|
649
|
+
onSubmit,
|
|
650
|
+
defaultValues,
|
|
651
|
+
components
|
|
652
|
+
}) => /* @__PURE__ */ jsx10(
|
|
653
|
+
ValidatedForm,
|
|
654
|
+
{
|
|
655
|
+
schema,
|
|
656
|
+
onSubmit,
|
|
657
|
+
defaultValues,
|
|
658
|
+
components,
|
|
659
|
+
children
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
Form.displayName = "Form";
|
|
663
|
+
const SchemaTextField = (props) => /* @__PURE__ */ jsx10(TextField, { ...props });
|
|
664
|
+
SchemaTextField.displayName = "TextField";
|
|
665
|
+
const SchemaPasswordField = (props) => /* @__PURE__ */ jsx10(TextField, { ...props });
|
|
666
|
+
SchemaPasswordField.displayName = "PasswordField";
|
|
667
|
+
const SchemaTextAreaField = (props) => /* @__PURE__ */ jsx10(TextAreaField, { ...props });
|
|
668
|
+
SchemaTextAreaField.displayName = "TextAreaField";
|
|
669
|
+
const SchemaNumberField = (props) => /* @__PURE__ */ jsx10(NumberField, { ...props });
|
|
670
|
+
SchemaNumberField.displayName = "NumberField";
|
|
671
|
+
const SchemaSelectField = (props) => /* @__PURE__ */ jsx10(SelectField, { ...props });
|
|
672
|
+
SchemaSelectField.displayName = "SelectField";
|
|
673
|
+
const SchemaCheckboxField = (props) => /* @__PURE__ */ jsx10(CheckboxField, { ...props });
|
|
674
|
+
SchemaCheckboxField.displayName = "CheckboxField";
|
|
675
|
+
const SchemaDateField = (props) => /* @__PURE__ */ jsx10(DateField, { ...props });
|
|
676
|
+
SchemaDateField.displayName = "DateField";
|
|
677
|
+
const SchemaSliderField = (props) => /* @__PURE__ */ jsx10(SliderField, { ...props });
|
|
678
|
+
SchemaSliderField.displayName = "SliderField";
|
|
679
|
+
const SchemaMultiSelectField = (props) => /* @__PURE__ */ jsx10(MultiSelectField, { ...props });
|
|
680
|
+
SchemaMultiSelectField.displayName = "MultiSelectField";
|
|
681
|
+
return {
|
|
682
|
+
Form,
|
|
683
|
+
TextField: SchemaTextField,
|
|
684
|
+
PasswordField: SchemaPasswordField,
|
|
685
|
+
TextAreaField: SchemaTextAreaField,
|
|
686
|
+
NumberField: SchemaNumberField,
|
|
687
|
+
SelectField: SchemaSelectField,
|
|
688
|
+
CheckboxField: SchemaCheckboxField,
|
|
689
|
+
DateField: SchemaDateField,
|
|
690
|
+
SliderField: SchemaSliderField,
|
|
691
|
+
MultiSelectField: SchemaMultiSelectField
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/hooks/useValidatedForm.ts
|
|
696
|
+
import { useForm as useForm2 } from "react-hook-form";
|
|
697
|
+
import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
|
|
698
|
+
function useValidatedForm({
|
|
699
|
+
schema,
|
|
700
|
+
defaultValues
|
|
701
|
+
}) {
|
|
702
|
+
return useForm2({
|
|
703
|
+
resolver: zodResolver2(schema),
|
|
704
|
+
defaultValues
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
export {
|
|
708
|
+
CheckboxField,
|
|
709
|
+
DateField,
|
|
710
|
+
MultiSelectField,
|
|
711
|
+
NumberField,
|
|
712
|
+
SelectField,
|
|
713
|
+
SliderField,
|
|
714
|
+
TextAreaField,
|
|
715
|
+
TextField,
|
|
716
|
+
ValidatedForm,
|
|
717
|
+
createForm,
|
|
718
|
+
useValidatedForm
|
|
719
|
+
};
|
|
720
|
+
//# sourceMappingURL=index.js.map
|