@k3-universe/react-kit 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/kit/builder/form/components/FormBuilder.d.ts +21 -1
  2. package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
  3. package/dist/kit/builder/form/components/FormBuilderField.d.ts +5 -5
  4. package/dist/kit/builder/form/components/FormBuilderField.d.ts.map +1 -1
  5. package/dist/kit/builder/form/components/fields/ArrayField.d.ts +3 -0
  6. package/dist/kit/builder/form/components/fields/ArrayField.d.ts.map +1 -0
  7. package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts +3 -0
  8. package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts.map +1 -0
  9. package/dist/kit/builder/form/components/fields/CheckboxField.d.ts +3 -0
  10. package/dist/kit/builder/form/components/fields/CheckboxField.d.ts.map +1 -0
  11. package/dist/kit/builder/form/components/fields/DateField.d.ts +3 -0
  12. package/dist/kit/builder/form/components/fields/DateField.d.ts.map +1 -0
  13. package/dist/kit/builder/form/components/fields/FileField.d.ts +3 -0
  14. package/dist/kit/builder/form/components/fields/FileField.d.ts.map +1 -0
  15. package/dist/kit/builder/form/components/fields/NumberField.d.ts +3 -0
  16. package/dist/kit/builder/form/components/fields/NumberField.d.ts.map +1 -0
  17. package/dist/kit/builder/form/components/fields/ObjectField.d.ts +3 -0
  18. package/dist/kit/builder/form/components/fields/ObjectField.d.ts.map +1 -0
  19. package/dist/kit/builder/form/components/fields/RadioField.d.ts +3 -0
  20. package/dist/kit/builder/form/components/fields/RadioField.d.ts.map +1 -0
  21. package/dist/kit/builder/form/components/fields/SelectField.d.ts +3 -0
  22. package/dist/kit/builder/form/components/fields/SelectField.d.ts.map +1 -0
  23. package/dist/kit/builder/form/components/fields/SwitchField.d.ts +3 -0
  24. package/dist/kit/builder/form/components/fields/SwitchField.d.ts.map +1 -0
  25. package/dist/kit/builder/form/components/fields/TextField.d.ts +3 -0
  26. package/dist/kit/builder/form/components/fields/TextField.d.ts.map +1 -0
  27. package/dist/kit/builder/form/components/fields/TextareaField.d.ts +3 -0
  28. package/dist/kit/builder/form/components/fields/TextareaField.d.ts.map +1 -0
  29. package/dist/kit/builder/form/components/fields/index.d.ts +14 -0
  30. package/dist/kit/builder/form/components/fields/index.d.ts.map +1 -0
  31. package/dist/kit/builder/form/components/fields/types.d.ts +14 -0
  32. package/dist/kit/builder/form/components/fields/types.d.ts.map +1 -0
  33. package/dist/kit/builder/form/utils/field-factories.d.ts +1 -0
  34. package/dist/kit/builder/form/utils/field-factories.d.ts.map +1 -1
  35. package/dist/kit/themes/clean-slate.css +1 -1
  36. package/dist/kit/themes/default.css +1 -1
  37. package/dist/kit/themes/minimal-modern.css +1 -1
  38. package/dist/kit/themes/spotify.css +1 -1
  39. package/package.json +1 -1
  40. package/src/kit/builder/form/components/FormBuilder.tsx +24 -1
  41. package/src/kit/builder/form/components/FormBuilderField.tsx +143 -340
  42. package/src/kit/builder/form/components/fields/ArrayField.tsx +222 -0
  43. package/src/kit/builder/form/components/fields/AutocompleteField.tsx +25 -0
  44. package/src/kit/builder/form/components/fields/CheckboxField.tsx +56 -0
  45. package/src/kit/builder/form/components/fields/DateField.tsx +15 -0
  46. package/src/kit/builder/form/components/fields/FileField.tsx +14 -0
  47. package/src/kit/builder/form/components/fields/NumberField.tsx +15 -0
  48. package/src/kit/builder/form/components/fields/ObjectField.tsx +30 -0
  49. package/src/kit/builder/form/components/fields/RadioField.tsx +29 -0
  50. package/src/kit/builder/form/components/fields/SelectField.tsx +31 -0
  51. package/src/kit/builder/form/components/fields/SwitchField.tsx +56 -0
  52. package/src/kit/builder/form/components/fields/TextField.tsx +18 -0
  53. package/src/kit/builder/form/components/fields/TextareaField.tsx +15 -0
  54. package/src/kit/builder/form/components/fields/index.ts +13 -0
  55. package/src/kit/builder/form/components/fields/types.ts +14 -0
  56. package/src/kit/builder/form/utils/field-factories.ts +13 -0
  57. package/src/stories/kit/builder/Form.ArrayLayouts.stories.tsx +153 -0
  58. package/src/stories/kit/builder/Form.Basic.stories.tsx +2 -0
  59. package/src/stories/kit/builder/Form.Simple.stories.tsx +4 -0
@@ -0,0 +1,222 @@
1
+ import { useFieldArray } from 'react-hook-form'
2
+ import type { FieldArrayPath, FieldValues } from 'react-hook-form'
3
+ import { Card, CardContent, CardHeader, CardTitle } from '../../../../../shadcn/ui/card'
4
+ import { Button } from '../../../../../shadcn/ui/button'
5
+ import { Input } from '../../../../../shadcn/ui/input'
6
+ import { GripVertical, Plus, Trash2 } from 'lucide-react'
7
+ import { cn } from '../../../../../shadcn/lib/utils'
8
+ import type { FieldRenderProps } from './types'
9
+ import { FormBuilderField } from '../FormBuilderField'
10
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../../../shadcn/ui/table'
11
+
12
+ export function ArrayField({ field, control, fieldPath, value, onChange }: FieldRenderProps) {
13
+ const { fields, append, remove } = useFieldArray({
14
+ control,
15
+ name: fieldPath as FieldArrayPath<FieldValues>,
16
+ })
17
+
18
+ const addItem = () => {
19
+ // For custom layout, prefer appending an object so useFieldArray can generate stable IDs
20
+ if (field.arrayLayout === 'custom') {
21
+ append({} as never)
22
+ return
23
+ }
24
+ if (field.fields && field.fields.length === 1) {
25
+ const defaultValue = field.fields[0].defaultValue ?? ''
26
+ append(defaultValue as never)
27
+ } else if (field.fields) {
28
+ const defaultObject: Record<string, unknown> = {}
29
+ field.fields.forEach((subField) => {
30
+ defaultObject[subField.name] = subField.defaultValue ?? ''
31
+ })
32
+ append(defaultObject as never)
33
+ } else {
34
+ append('' as never)
35
+ }
36
+ }
37
+
38
+ const removeItem = (index: number) => remove(index)
39
+
40
+ // Custom layout hook
41
+ if (field.arrayLayout === 'custom' && typeof field.arrayRender === 'function') {
42
+ return (
43
+ <>{field.arrayRender({ field, control, fieldPath, value, onChange, addItem, removeItem, disabled: field.disabled, rows: fields })}</>
44
+ )
45
+ }
46
+
47
+ // Table layout
48
+ if (field.arrayLayout === 'table') {
49
+ const hasNested = Array.isArray(field.fields) && field.fields.length > 0
50
+ const fFields = field.fields ?? []
51
+ const singleNested = hasNested && fFields.length === 1
52
+ const headerBg = field.arrayColors?.headerBgClass ?? 'bg-primary'
53
+ const headerText = field.arrayColors?.headerTextClass ?? 'text-primary-foreground'
54
+ const altRow = field.arrayColors?.rowAltBgClass ?? 'bg-muted/40'
55
+
56
+ return (
57
+ <Card className={cn(field.className, 'py-3 rounded-md gap-3')}>
58
+ <CardHeader>
59
+ <div className="flex items-center justify-between">
60
+ <div>
61
+ <CardTitle className="text-base">{field.label}</CardTitle>
62
+ {field.description && (
63
+ <p className="text-sm text-muted-foreground">{field.description}</p>
64
+ )}
65
+ </div>
66
+ <Button type="button" variant="outline" size="sm" onClick={addItem} disabled={field.disabled}>
67
+ <Plus className="h-4 w-4 mr-1" />
68
+ Add Item
69
+ </Button>
70
+ </div>
71
+ </CardHeader>
72
+ <CardContent className="space-y-4">
73
+ {fields.length === 0 ? (
74
+ <p className="text-sm text-muted-foreground text-center py-2">
75
+ No items added yet. Click "Add Item" to get started.
76
+ </p>
77
+ ) : (
78
+ <Table>
79
+ <TableHeader>
80
+ <TableRow className={cn(headerBg, headerText)}>
81
+ {hasNested ? (
82
+ singleNested ? (
83
+ <TableHead className={cn(headerText)}>{fFields[0]?.label || 'Value'}</TableHead>
84
+ ) : (
85
+ fFields.map(sf => (
86
+ <TableHead key={sf.name} className={cn(headerText)}>{sf.label || sf.name}</TableHead>
87
+ ))
88
+ )
89
+ ) : (
90
+ <TableHead className={cn(headerText)}>Value</TableHead>
91
+ )}
92
+ <TableHead className={cn(headerText)}>Action</TableHead>
93
+ </TableRow>
94
+ </TableHeader>
95
+ <TableBody>
96
+ {fields.map((item, index) => (
97
+ <TableRow key={item.id} className={cn(index % 2 === 1 && altRow)}>
98
+ {hasNested ? (
99
+ singleNested ? (
100
+ <TableCell>
101
+ <FormBuilderField
102
+ field={{ ...fFields[0], name: fFields[0]?.name, label: fFields[0]?.label || 'Value' }}
103
+ control={control}
104
+ parentPath={`${fieldPath}.${index}`}
105
+ />
106
+ </TableCell>
107
+ ) : (
108
+ fFields.map(subField => (
109
+ <TableCell key={subField.name}>
110
+ <FormBuilderField
111
+ field={subField}
112
+ control={control}
113
+ parentPath={`${fieldPath}.${index}`}
114
+ />
115
+ </TableCell>
116
+ ))
117
+ )
118
+ ) : (
119
+ <TableCell>
120
+ <Input
121
+ value={String(((value as unknown[] | undefined)?.[index] ?? ''))}
122
+ onChange={(e) => {
123
+ const current = (value as unknown[] | undefined) ?? []
124
+ const newArray = [...current]
125
+ newArray[index] = e.target.value
126
+ onChange(newArray)
127
+ }}
128
+ placeholder={`Item ${index + 1}`}
129
+ disabled={field.disabled}
130
+ />
131
+ </TableCell>
132
+ )}
133
+ <TableCell className="w-1 text-right">
134
+ <Button type="button" variant="destructive" size="sm" onClick={() => remove(index)} disabled={field.disabled}>
135
+ <Trash2 className="h-4 w-4" />
136
+ </Button>
137
+ </TableCell>
138
+ </TableRow>
139
+ ))}
140
+ </TableBody>
141
+ </Table>
142
+ )}
143
+ </CardContent>
144
+ </Card>
145
+ )
146
+ }
147
+
148
+ // Default: card layout (existing UI)
149
+ return (
150
+ <Card className={cn(field.className, 'py-3 rounded-md gap-3')}>
151
+ <CardHeader>
152
+ <div className="flex items-center justify-between">
153
+ <div>
154
+ <CardTitle className="text-base">{field.label}</CardTitle>
155
+ {field.description && (
156
+ <p className="text-sm text-muted-foreground">{field.description}</p>
157
+ )}
158
+ </div>
159
+ <Button type="button" variant="outline" size="sm" onClick={addItem} disabled={field.disabled}>
160
+ <Plus className="h-4 w-4 mr-1" />
161
+ Add Item
162
+ </Button>
163
+ </div>
164
+ </CardHeader>
165
+ <CardContent className="space-y-4">
166
+ {fields.length === 0 ? (
167
+ <p className="text-sm text-muted-foreground text-center py-2">
168
+ No items added yet. Click "Add Item" to get started.
169
+ </p>
170
+ ) : (
171
+ fields.map((item, index) => (
172
+ <Card key={item.id} className="relative py-3 rounded-md gap-3">
173
+ <CardHeader>
174
+ <div className="flex items-center justify-between">
175
+ <div className="flex items-center gap-2">
176
+ <GripVertical className="h-4 w-4 text-muted-foreground" />
177
+ <span className="text-sm font-medium">Item {index + 1}</span>
178
+ </div>
179
+ <Button type="button" variant="ghost" size="sm" onClick={() => remove(index)} disabled={field.disabled}>
180
+ <Trash2 className="h-4 w-4" />
181
+ </Button>
182
+ </div>
183
+ </CardHeader>
184
+ <CardContent>
185
+ {field.fields && field.fields.length === 1 ? (
186
+ <FormBuilderField
187
+ field={{ ...field.fields[0], name: field.fields[0].name, label: field.fields[0].label || 'Value' }}
188
+ control={control}
189
+ parentPath={`${fieldPath}.${index}`}
190
+ />
191
+ ) : field.fields ? (
192
+ <div className="grid gap-2 md:grid-cols-2">
193
+ {field.fields.map(subField => (
194
+ <FormBuilderField
195
+ key={subField.name}
196
+ field={subField}
197
+ control={control}
198
+ parentPath={`${fieldPath}.${index}`}
199
+ />
200
+ ))}
201
+ </div>
202
+ ) : (
203
+ <Input
204
+ value={String(((value as unknown[] | undefined)?.[index] ?? ''))}
205
+ onChange={(e) => {
206
+ const current = (value as unknown[] | undefined) ?? []
207
+ const newArray = [...current]
208
+ newArray[index] = e.target.value
209
+ onChange(newArray)
210
+ }}
211
+ placeholder={`Item ${index + 1}`}
212
+ disabled={field.disabled}
213
+ />
214
+ )}
215
+ </CardContent>
216
+ </Card>
217
+ ))
218
+ )}
219
+ </CardContent>
220
+ </Card>
221
+ )
222
+ }
@@ -0,0 +1,25 @@
1
+ import { Autocomplete } from '../../../../../kit/components/autocomplete/Autocomplete'
2
+ import type { AutocompleteOption } from '../../../../../kit/components/autocomplete/types'
3
+ import type { FieldRenderProps } from './types'
4
+
5
+ export function AutocompleteField({ field, value, onChange, className }: FieldRenderProps) {
6
+ const options: AutocompleteOption[] = (field.options ?? [])
7
+ .filter((o): o is { label: string; value: string | number } => o.value !== null && o.value !== undefined)
8
+ .map(o => ({ label: o.label, value: o.value as string | number }))
9
+
10
+ return (
11
+ <Autocomplete
12
+ mode={field.autocompleteMode ?? 'client'}
13
+ options={options}
14
+ fetcher={field.fetcher}
15
+ pageSize={field.pageSize}
16
+ value={(value as string | number | null) ?? null}
17
+ onChange={(val) => onChange(val)}
18
+ placeholder={field.placeholder}
19
+ searchPlaceholder={field.searchPlaceholder}
20
+ renderOption={field.renderOption}
21
+ disabled={field.disabled}
22
+ className={className}
23
+ />
24
+ )
25
+ }
@@ -0,0 +1,56 @@
1
+ import { Checkbox } from '../../../../../shadcn/ui/checkbox'
2
+ import { Label } from '../../../../../shadcn/ui/label'
3
+ import { cn } from '../../../../../shadcn/lib/utils'
4
+ import type { FieldRenderProps } from './types'
5
+
6
+ export function CheckboxField({ field, fieldPath, value, onChange, className }: FieldRenderProps) {
7
+ const placement = field.labelPlacement ?? 'inline'
8
+
9
+ if (placement === 'stacked') {
10
+ const labelId = `${fieldPath}-label`
11
+ return (
12
+ <div className="space-y-2">
13
+ <Label id={labelId} className="text-sm font-medium">
14
+ {field.label}
15
+ {field.required && <span className="text-destructive ml-1">*</span>}
16
+ </Label>
17
+ <Checkbox
18
+ aria-labelledby={labelId}
19
+ id={fieldPath}
20
+ checked={(value as boolean) || false}
21
+ onCheckedChange={onChange as (val: boolean) => void}
22
+ disabled={field.disabled}
23
+ className={cn(className)}
24
+ />
25
+ </div>
26
+ )
27
+ }
28
+
29
+ if (placement === 'hidden') {
30
+ return (
31
+ <Checkbox
32
+ id={fieldPath}
33
+ checked={(value as boolean) || false}
34
+ onCheckedChange={onChange as (val: boolean) => void}
35
+ disabled={field.disabled}
36
+ className={cn(className)}
37
+ />
38
+ )
39
+ }
40
+
41
+ return (
42
+ <div className="flex items-center space-x-2">
43
+ <Checkbox
44
+ id={fieldPath}
45
+ checked={(value as boolean) || false}
46
+ onCheckedChange={onChange as (val: boolean) => void}
47
+ disabled={field.disabled}
48
+ className={cn(className)}
49
+ />
50
+ <Label htmlFor={fieldPath} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
51
+ {field.label}
52
+ {field.required && <span className="text-destructive ml-1">*</span>}
53
+ </Label>
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,15 @@
1
+ import { Input } from '../../../../../shadcn/ui/input'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ export function DateField({ field, value, onChange, className }: FieldRenderProps) {
5
+ return (
6
+ <Input
7
+ className={className}
8
+ disabled={field.disabled}
9
+ placeholder={field.placeholder}
10
+ type="date"
11
+ value={value ? new Date(value as Date | string).toISOString().split('T')[0] : ''}
12
+ onChange={(e) => onChange(e.target.value ? new Date(e.target.value) : null)}
13
+ />
14
+ )
15
+ }
@@ -0,0 +1,14 @@
1
+ import { Input } from '../../../../../shadcn/ui/input'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ export function FileField({ field, onChange, className }: FieldRenderProps) {
5
+ return (
6
+ <Input
7
+ className={className}
8
+ disabled={field.disabled}
9
+ placeholder={field.placeholder}
10
+ type="file"
11
+ onChange={(e) => onChange((e.target as HTMLInputElement).files?.[0] || null)}
12
+ />
13
+ )
14
+ }
@@ -0,0 +1,15 @@
1
+ import { Input } from '../../../../../shadcn/ui/input'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ export function NumberField({ field, value, onChange, className }: FieldRenderProps) {
5
+ return (
6
+ <Input
7
+ className={className}
8
+ disabled={field.disabled}
9
+ placeholder={field.placeholder}
10
+ type="number"
11
+ value={(value as number | string) ?? ''}
12
+ onChange={(e) => onChange(Number(e.target.value))}
13
+ />
14
+ )
15
+ }
@@ -0,0 +1,30 @@
1
+ import { Card, CardContent, CardHeader, CardTitle } from '../../../../../shadcn/ui/card'
2
+ import { cn } from '../../../../../shadcn/lib/utils'
3
+ import type { FieldRenderProps } from './types'
4
+ import { FormBuilderField } from '../FormBuilderField'
5
+
6
+ export function ObjectField({ field, control, fieldPath }: FieldRenderProps) {
7
+ if (!field.fields) return null
8
+ return (
9
+ <Card className={cn(field.className)}>
10
+ <CardHeader className="pb-3">
11
+ <CardTitle className="text-base">{field.label}</CardTitle>
12
+ {field.description && (
13
+ <p className="text-sm text-muted-foreground">{field.description}</p>
14
+ )}
15
+ </CardHeader>
16
+ <CardContent className="space-y-4">
17
+ <div className="grid gap-4 md:grid-cols-2">
18
+ {field.fields.map(subField => (
19
+ <FormBuilderField
20
+ key={subField.name}
21
+ field={subField}
22
+ control={control}
23
+ parentPath={fieldPath}
24
+ />
25
+ ))}
26
+ </div>
27
+ </CardContent>
28
+ </Card>
29
+ )
30
+ }
@@ -0,0 +1,29 @@
1
+ import { RadioGroup, RadioGroupItem } from '../../../../../shadcn/ui/radio-group'
2
+ import { Label } from '../../../../../shadcn/ui/label'
3
+ import type { FieldRenderProps } from './types'
4
+
5
+ const NULL_SENTINEL = '__NULL__'
6
+
7
+ export function RadioField({ field, value, onChange, className, fieldPath }: FieldRenderProps) {
8
+ const toUiValue = (val: unknown) => (val === null || val === undefined ? NULL_SENTINEL : String(val))
9
+ const fromUiValue = (val: string) => {
10
+ const match = field.options?.find(opt => toUiValue(opt.value) === val)
11
+ return match ? match.value : null
12
+ }
13
+
14
+ return (
15
+ <RadioGroup
16
+ value={toUiValue(value)}
17
+ onValueChange={(val) => onChange(fromUiValue(val))}
18
+ disabled={field.disabled}
19
+ className={className}
20
+ >
21
+ {field.options?.map(option => (
22
+ <div key={toUiValue(option.value)} className="flex items-center space-x-2">
23
+ <RadioGroupItem value={toUiValue(option.value)} id={`${fieldPath}-${toUiValue(option.value)}`} />
24
+ <Label htmlFor={`${fieldPath}-${toUiValue(option.value)}`}>{option.label}</Label>
25
+ </div>
26
+ ))}
27
+ </RadioGroup>
28
+ )
29
+ }
@@ -0,0 +1,31 @@
1
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../../../shadcn/ui/select'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ const NULL_SENTINEL = '__NULL__'
5
+
6
+ export function SelectField({ field, value, onChange, className }: FieldRenderProps) {
7
+ const toUiValue = (val: unknown) => (val === null || val === undefined ? NULL_SENTINEL : String(val))
8
+ const fromUiValue = (val: string) => {
9
+ const match = field.options?.find(opt => toUiValue(opt.value) === val)
10
+ return match ? match.value : null
11
+ }
12
+
13
+ return (
14
+ <Select
15
+ value={toUiValue(value)}
16
+ onValueChange={(val) => onChange(fromUiValue(val))}
17
+ disabled={field.disabled}
18
+ >
19
+ <SelectTrigger className={className}>
20
+ <SelectValue placeholder={field.placeholder} />
21
+ </SelectTrigger>
22
+ <SelectContent>
23
+ {field.options?.map(option => (
24
+ <SelectItem key={toUiValue(option.value)} value={toUiValue(option.value)}>
25
+ {option.label}
26
+ </SelectItem>
27
+ ))}
28
+ </SelectContent>
29
+ </Select>
30
+ )
31
+ }
@@ -0,0 +1,56 @@
1
+ import { Switch } from '../../../../../shadcn/ui/switch'
2
+ import { Label } from '../../../../../shadcn/ui/label'
3
+ import { cn } from '../../../../../shadcn/lib/utils'
4
+ import type { FieldRenderProps } from './types'
5
+
6
+ export function SwitchField({ field, fieldPath, value, onChange, className }: FieldRenderProps) {
7
+ const placement = field.labelPlacement ?? 'inline'
8
+
9
+ if (placement === 'stacked') {
10
+ const labelId = `${fieldPath}-label`
11
+ return (
12
+ <div className="space-y-2">
13
+ <Label id={labelId} className="text-sm font-medium">
14
+ {field.label}
15
+ {field.required && <span className="text-destructive ml-1">*</span>}
16
+ </Label>
17
+ <Switch
18
+ aria-labelledby={labelId}
19
+ id={fieldPath}
20
+ checked={(value as boolean) || false}
21
+ onCheckedChange={onChange as (val: boolean) => void}
22
+ disabled={field.disabled}
23
+ className={cn(className)}
24
+ />
25
+ </div>
26
+ )
27
+ }
28
+
29
+ if (placement === 'hidden') {
30
+ return (
31
+ <Switch
32
+ id={fieldPath}
33
+ checked={(value as boolean) || false}
34
+ onCheckedChange={onChange as (val: boolean) => void}
35
+ disabled={field.disabled}
36
+ className={cn(className)}
37
+ />
38
+ )
39
+ }
40
+
41
+ return (
42
+ <div className="flex items-center space-x-2">
43
+ <Switch
44
+ id={fieldPath}
45
+ checked={(value as boolean) || false}
46
+ onCheckedChange={onChange as (val: boolean) => void}
47
+ disabled={field.disabled}
48
+ className={cn(className)}
49
+ />
50
+ <Label htmlFor={fieldPath} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
51
+ {field.label}
52
+ {field.required && <span className="text-destructive ml-1">*</span>}
53
+ </Label>
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,18 @@
1
+ import { Input } from '../../../../../shadcn/ui/input'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ export function TextField({ field, fieldPath, value, onChange, className }: FieldRenderProps) {
5
+ const type: 'text' | 'email' | 'password' =
6
+ field.type === 'email' || field.type === 'password' ? field.type : 'text'
7
+ return (
8
+ <Input
9
+ id={fieldPath}
10
+ className={className}
11
+ disabled={field.disabled}
12
+ placeholder={field.placeholder}
13
+ type={type}
14
+ value={(value as string) || ''}
15
+ onChange={(e) => onChange(e.target.value)}
16
+ />
17
+ )
18
+ }
@@ -0,0 +1,15 @@
1
+ import { Textarea } from '../../../../../shadcn/ui/textarea'
2
+ import type { FieldRenderProps } from './types'
3
+
4
+ export function TextareaField({ field, value, onChange, className }: FieldRenderProps) {
5
+ return (
6
+ <Textarea
7
+ className={className}
8
+ disabled={field.disabled}
9
+ placeholder={field.placeholder}
10
+ value={(value as string) || ''}
11
+ onChange={(e) => onChange(e.target.value)}
12
+ rows={4}
13
+ />
14
+ )
15
+ }
@@ -0,0 +1,13 @@
1
+ export * from './types'
2
+ export * from './AutocompleteField'
3
+ export * from './TextField'
4
+ export * from './NumberField'
5
+ export * from './TextareaField'
6
+ export * from './SelectField'
7
+ export * from './CheckboxField'
8
+ export * from './SwitchField'
9
+ export * from './RadioField'
10
+ export * from './DateField'
11
+ export * from './FileField'
12
+ export * from './ObjectField'
13
+ export * from './ArrayField'
@@ -0,0 +1,14 @@
1
+ import type { Control, FieldValues } from 'react-hook-form'
2
+ import type { FormBuilderFieldConfig } from '../FormBuilder'
3
+
4
+ export interface FieldRenderProps {
5
+ field: FormBuilderFieldConfig
6
+ control: Control<FieldValues>
7
+ fieldPath: string
8
+ value: unknown
9
+ onChange: (value: unknown) => void
10
+ className?: string
11
+ disabled?: boolean
12
+ errorMessage?: string
13
+ onFieldChange?: (name: string, value: unknown, allValues: Record<string, unknown>) => void
14
+ }
@@ -93,6 +93,19 @@ export const createField = {
93
93
  ...options,
94
94
  }),
95
95
 
96
+ switch: (
97
+ name: string,
98
+ label: string,
99
+ options: Partial<FormBuilderFieldConfig> = {},
100
+ ): FormBuilderFieldConfig => ({
101
+ name,
102
+ label,
103
+ type: 'switch',
104
+ required: false,
105
+ defaultValue: false,
106
+ ...options,
107
+ }),
108
+
96
109
  radio: (
97
110
  name: string,
98
111
  label: string,