@react-fabric/form 1.0.0-beta.8 → 1.0.1

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 (215) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +95 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +94 -33
  4. package/{src/input/File.module.css → css/_file.css} +21 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +107 -78
  7. package/dist/cjs/builder/FormField.js +60 -39
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
  11. package/dist/cjs/builder/components/SchemaField.js +423 -0
  12. package/dist/cjs/form/Controller.js +66 -19
  13. package/dist/cjs/form/Form.js +97 -39
  14. package/dist/cjs/form/useForm.js +32 -0
  15. package/dist/cjs/hooks/useFileUploader.js +74 -45
  16. package/dist/cjs/hooks/useFormBuilder.js +85 -31
  17. package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
  18. package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
  19. package/dist/cjs/i18n/index.js +4 -4
  20. package/dist/cjs/index.js +41 -2
  21. package/dist/cjs/input/ArrayInput.js +259 -135
  22. package/dist/cjs/input/AvatarInput.js +99 -63
  23. package/dist/cjs/input/Checkbox.js +24 -25
  24. package/dist/cjs/input/ColorInput.js +138 -81
  25. package/dist/cjs/input/ColorPicker.js +78 -95
  26. package/dist/cjs/input/DateInput.js +192 -0
  27. package/dist/cjs/input/Field.js +21 -20
  28. package/dist/cjs/input/File.js +85 -80
  29. package/dist/cjs/input/Hidden.js +27 -13
  30. package/dist/cjs/input/Input.js +49 -48
  31. package/dist/cjs/input/Number.js +46 -44
  32. package/dist/cjs/input/Password.js +51 -49
  33. package/dist/cjs/input/Radio.js +26 -16
  34. package/dist/cjs/input/RadioGroup.js +26 -11
  35. package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
  36. package/dist/cjs/input/Search.js +58 -64
  37. package/dist/cjs/input/Slider.js +57 -66
  38. package/dist/cjs/input/Switch.js +29 -27
  39. package/dist/cjs/input/Textarea.js +65 -53
  40. package/dist/cjs/internal/ErrorIcon.js +9 -9
  41. package/dist/cjs/internal/FieldWrapper.js +23 -13
  42. package/dist/cjs/internal/InputWrapper.js +85 -87
  43. package/dist/cjs/ranger/index.js +7 -7
  44. package/dist/cjs/ranger/utils.js +9 -4
  45. package/dist/cjs/select/AutoComplete.js +93 -76
  46. package/dist/cjs/select/DualList.js +235 -154
  47. package/dist/cjs/select/List.js +122 -89
  48. package/dist/cjs/select/Options.js +58 -54
  49. package/dist/cjs/select/Select.js +119 -89
  50. package/dist/cjs/select/helpers.js +5 -5
  51. package/dist/cjs/select/useSelect.js +83 -58
  52. package/dist/cjs/types/schema.js +43 -0
  53. package/dist/cjs/utils/eventHandlers.js +16 -6
  54. package/dist/cjs/utils/getColor.js +6 -6
  55. package/dist/cjs/utils/getProps.js +49 -0
  56. package/dist/esm/builder/FileField.js +109 -80
  57. package/dist/esm/builder/FormField.js +60 -39
  58. package/dist/esm/builder/SchemaEditor.js +72 -0
  59. package/dist/esm/builder/components/AddSchemaFields.js +64 -0
  60. package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
  61. package/dist/esm/builder/components/SchemaField.js +423 -0
  62. package/dist/esm/form/Controller.js +66 -19
  63. package/dist/esm/form/Form.js +97 -39
  64. package/dist/esm/form/useForm.js +32 -0
  65. package/dist/esm/hooks/useFileUploader.js +75 -46
  66. package/dist/esm/hooks/useFormBuilder.js +84 -30
  67. package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
  68. package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
  69. package/dist/esm/i18n/index.js +2 -2
  70. package/dist/esm/index.js +36 -1
  71. package/dist/esm/input/ArrayInput.js +260 -136
  72. package/dist/esm/input/AvatarInput.js +99 -63
  73. package/dist/esm/input/Checkbox.js +26 -27
  74. package/dist/esm/input/ColorInput.js +141 -84
  75. package/dist/esm/input/ColorPicker.js +80 -97
  76. package/dist/esm/input/DateInput.js +192 -0
  77. package/dist/esm/input/Field.js +21 -20
  78. package/dist/esm/input/File.js +89 -84
  79. package/dist/esm/input/Hidden.js +28 -14
  80. package/dist/esm/input/Input.js +51 -50
  81. package/dist/esm/input/Number.js +48 -46
  82. package/dist/esm/input/Password.js +54 -52
  83. package/dist/esm/input/Radio.js +27 -17
  84. package/dist/esm/input/RadioGroup.js +27 -12
  85. package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
  86. package/dist/esm/input/Search.js +61 -67
  87. package/dist/esm/input/Slider.js +58 -67
  88. package/dist/esm/input/Switch.js +31 -29
  89. package/dist/esm/input/Textarea.js +67 -55
  90. package/dist/esm/internal/ErrorIcon.js +11 -11
  91. package/dist/esm/internal/FieldWrapper.js +23 -13
  92. package/dist/esm/internal/InputWrapper.js +88 -90
  93. package/dist/esm/ranger/index.js +7 -7
  94. package/dist/esm/ranger/utils.js +9 -4
  95. package/dist/esm/select/AutoComplete.js +95 -78
  96. package/dist/esm/select/DualList.js +233 -152
  97. package/dist/esm/select/List.js +125 -92
  98. package/dist/esm/select/Options.js +58 -54
  99. package/dist/esm/select/Select.js +122 -92
  100. package/dist/esm/select/helpers.js +5 -5
  101. package/dist/esm/select/useSelect.js +84 -59
  102. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  103. package/dist/esm/utils/eventHandlers.js +16 -6
  104. package/dist/esm/utils/getColor.js +6 -6
  105. package/dist/esm/utils/getProps.js +49 -0
  106. package/dist/types/builder/FileField.d.ts +7 -4
  107. package/dist/types/builder/FormField.d.ts +4 -2
  108. package/dist/types/builder/SchemaEditor.d.ts +6 -0
  109. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  110. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  111. package/dist/types/builder/components/SchemaField.d.ts +12 -0
  112. package/dist/types/form/Controller.d.ts +45 -3
  113. package/dist/types/form/Form.d.ts +38 -7
  114. package/dist/types/form/useForm.d.ts +6 -0
  115. package/dist/types/hooks/useFileUploader.d.ts +24 -3
  116. package/dist/types/hooks/useFormBuilder.d.ts +41 -3
  117. package/dist/types/index.d.ts +32 -2
  118. package/dist/types/input/ArrayInput.d.ts +68 -13
  119. package/dist/types/input/AvatarInput.d.ts +23 -6
  120. package/dist/types/input/Checkbox.d.ts +12 -10
  121. package/dist/types/input/ColorInput.d.ts +15 -5
  122. package/dist/types/input/ColorPicker.d.ts +10 -2
  123. package/dist/types/input/DateInput.d.ts +21 -0
  124. package/dist/types/input/Field.d.ts +9 -36
  125. package/dist/types/input/File.d.ts +16 -4
  126. package/dist/types/input/Hidden.d.ts +7 -6
  127. package/dist/types/input/Input.d.ts +11 -4
  128. package/dist/types/input/Number.d.ts +9 -4
  129. package/dist/types/input/Password.d.ts +9 -4
  130. package/dist/types/input/Radio.d.ts +9 -4
  131. package/dist/types/input/RadioGroup.d.ts +14 -6
  132. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
  133. package/dist/types/input/Search.d.ts +14 -4
  134. package/dist/types/input/Slider.d.ts +9 -6
  135. package/dist/types/input/Switch.d.ts +13 -8
  136. package/dist/types/input/Textarea.d.ts +15 -4
  137. package/dist/types/internal/ErrorIcon.d.ts +4 -1
  138. package/dist/types/internal/FieldWrapper.d.ts +10 -1
  139. package/dist/types/internal/InputWrapper.d.ts +19 -1
  140. package/dist/types/ranger/index.d.ts +7 -3
  141. package/dist/types/ranger/utils.d.ts +2 -2
  142. package/dist/types/select/AutoComplete.d.ts +7 -2
  143. package/dist/types/select/DualList.d.ts +11 -37
  144. package/dist/types/select/List.d.ts +7 -2
  145. package/dist/types/select/Options.d.ts +8 -7
  146. package/dist/types/select/Select.d.ts +7 -2
  147. package/dist/types/select/helpers.d.ts +2 -2
  148. package/dist/types/select/useSelect.d.ts +10 -28
  149. package/dist/types/types/index.d.ts +73 -39
  150. package/dist/types/types/schema.d.ts +78 -32
  151. package/dist/types/utils/eventHandlers.d.ts +4 -4
  152. package/dist/types/utils/getColor.d.ts +4 -4
  153. package/dist/types/utils/getProps.d.ts +26 -0
  154. package/dist/types/utils/index.d.ts +1 -0
  155. package/package.json +25 -25
  156. package/src/builder/FileField.tsx +73 -56
  157. package/src/builder/FormField.tsx +66 -58
  158. package/src/builder/SchemaEditor.tsx +78 -0
  159. package/src/builder/components/AddSchemaFields.tsx +64 -0
  160. package/src/builder/components/SchemaArrayForm.tsx +146 -0
  161. package/src/builder/components/SchemaField.tsx +405 -0
  162. package/src/form/Controller.tsx +61 -24
  163. package/src/form/Form.tsx +74 -50
  164. package/src/form/useForm.ts +53 -0
  165. package/src/hooks/useFileUploader.ts +115 -84
  166. package/src/hooks/useFormBuilder.tsx +80 -41
  167. package/src/i18n/{ar.json → ar/form.json} +5 -4
  168. package/src/i18n/{en.json → en/form.json} +5 -5
  169. package/src/i18n/index.ts +2 -2
  170. package/src/index.ts +34 -20
  171. package/src/input/ArrayInput.tsx +255 -173
  172. package/src/input/AvatarInput.tsx +82 -39
  173. package/src/input/Checkbox.tsx +30 -54
  174. package/src/input/ColorInput.tsx +146 -105
  175. package/src/input/ColorPicker.tsx +76 -102
  176. package/src/input/DateInput.tsx +225 -0
  177. package/src/input/Field.tsx +23 -65
  178. package/src/input/File.tsx +76 -98
  179. package/src/input/Hidden.tsx +36 -24
  180. package/src/input/Input.tsx +55 -53
  181. package/src/input/Number.tsx +49 -54
  182. package/src/input/Password.tsx +61 -66
  183. package/src/input/Radio.tsx +28 -37
  184. package/src/input/RadioGroup.tsx +30 -25
  185. package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
  186. package/src/input/Search.tsx +64 -77
  187. package/src/input/Slider.tsx +80 -109
  188. package/src/input/Switch.tsx +39 -53
  189. package/src/input/Textarea.tsx +72 -59
  190. package/src/internal/ErrorIcon.tsx +22 -21
  191. package/src/internal/FieldWrapper.tsx +33 -23
  192. package/src/internal/InputWrapper.tsx +132 -143
  193. package/src/ranger/index.tsx +37 -117
  194. package/src/ranger/utils.ts +12 -11
  195. package/src/select/AutoComplete.tsx +113 -123
  196. package/src/select/DualList.tsx +218 -204
  197. package/src/select/List.tsx +130 -97
  198. package/src/select/Options.tsx +59 -57
  199. package/src/select/Select.tsx +148 -122
  200. package/src/select/helpers.ts +13 -8
  201. package/src/select/useSelect.tsx +110 -127
  202. package/src/types/index.ts +76 -45
  203. package/src/types/schema.ts +97 -40
  204. package/src/utils/eventHandlers.ts +24 -26
  205. package/src/utils/getColor.ts +9 -8
  206. package/src/utils/getProps.ts +47 -0
  207. package/src/utils/index.ts +1 -0
  208. package/dist/cjs/input/ArrayInput.module.css +0 -1
  209. package/dist/cjs/input/ColorPicker.module.css +0 -1
  210. package/dist/cjs/input/File.module.css +0 -1
  211. package/dist/cjs/internal/Field.module.css +0 -1
  212. package/dist/esm/input/ArrayInput.module.css +0 -1
  213. package/dist/esm/input/ColorPicker.module.css +0 -1
  214. package/dist/esm/input/File.module.css +0 -1
  215. package/dist/esm/internal/Field.module.css +0 -1
@@ -0,0 +1,405 @@
1
+ /*
2
+ * React Fabric
3
+ * @version: 1.0.0
4
+ *
5
+ *
6
+ * The MIT License (MIT)
7
+ * Copyright (c) 2025 Adarsh Pastakia
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
10
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
11
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
13
+ * subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
16
+ * portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19
+ * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ import { useSortable } from "@dnd-kit/react/sortable";
25
+ import {
26
+ Button,
27
+ Collapsable,
28
+ Content,
29
+ Footer,
30
+ Modal,
31
+ type ModalProps,
32
+ ToggleButtonGroup,
33
+ useOverlayService,
34
+ } from "@react-fabric/core";
35
+ import { EMPTY_ARRAY, cn } from "@react-fabric/utilities";
36
+ import { Fragment, type PropsWithChildren, useCallback, useEffect, useState } from "react";
37
+ import { Controller } from "../../form/Controller";
38
+ import { ArrayInput } from "../../input/ArrayInput";
39
+ import { Checkbox } from "../../input/Checkbox";
40
+ import { DateInput } from "../../input/DateInput";
41
+ import { Field } from "../../input/Field";
42
+ import { HiddenInput } from "../../input/Hidden";
43
+ import { Input } from "../../input/Input";
44
+ import { Number } from "../../input/Number";
45
+ import { Select } from "../../select/Select";
46
+ import type { SchemaDef } from "../../types/schema";
47
+ import {
48
+ DATA_TYPES,
49
+ type DateSchema,
50
+ type DefaultSchema,
51
+ type NumberSchema,
52
+ SCHEMA_CUSTOM_LIST,
53
+ type SchemaEditorProps,
54
+ type StringSchema,
55
+ type ValueType,
56
+ } from "../../types/schema";
57
+
58
+ function SortableItem({
59
+ id,
60
+ index,
61
+ dynamic,
62
+ children,
63
+ className,
64
+ }: PropsWithChildren<{ id: string; index: number; dynamic: boolean; className: string }>) {
65
+ const { ref, handleRef, isDragging } = useSortable({ id, index, disabled: !dynamic });
66
+ if (dynamic) {
67
+ return (
68
+ <div className={className} ref={ref}>
69
+ <div
70
+ ref={handleRef}
71
+ className={cn(
72
+ "invisible group-hover/field:visible my-2 w-4",
73
+ isDragging ? "cursor-grabbing bg-default" : "cursor-grab",
74
+ )}
75
+ style={{
76
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='4' width='4'%3E%3Ccircle cx='2' cy='2' r='1' fill='%238888' /%3E%3C/svg%3E ")`,
77
+ }}
78
+ />
79
+ {children}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ return <div className={className}>{children}</div>;
85
+ }
86
+
87
+ function OptionModal({ field, ...props }: ModalProps<{ field: string; title?: string }>) {
88
+ return (
89
+ <Modal {...props}>
90
+ <Content>
91
+ <ArrayInput name={`${field}.options`} onAdd={() => String("")}>
92
+ <Controller>
93
+ <Input />
94
+ </Controller>
95
+ </ArrayInput>
96
+ </Content>
97
+ <Footer flex justify="end" className="sticky bottom-0 border-t bg-default px-6 py-2">
98
+ <Button variant="link" onClick={props.onClose}>
99
+ Close
100
+ </Button>
101
+ </Footer>
102
+ </Modal>
103
+ );
104
+ }
105
+
106
+ export function SchemaField({
107
+ id,
108
+ index,
109
+ field,
110
+ fieldId,
111
+ getModel,
112
+ onRemove,
113
+ defaultOpen = true,
114
+ optionLists = EMPTY_ARRAY,
115
+ dynamic,
116
+ onTypeChange,
117
+ acceptableTypes,
118
+ }: {
119
+ id: string;
120
+ index: number;
121
+ field: string;
122
+ fieldId: string;
123
+ defaultOpen?: boolean;
124
+ onRemove: () => void;
125
+ getModel: () => SchemaDef;
126
+ onTypeChange: (val: SchemaDef) => void;
127
+ } & Omit<SchemaEditorProps, "schemaDef" | "onChange">) {
128
+ const [type, setType] = useState<DATA_TYPES>();
129
+ const [multiple, setMultiple] = useState(false);
130
+ const [hasOptions, setHasOptions] = useState(false);
131
+ const [customOptions, setCustomOptions] = useState(false);
132
+
133
+ const [renderOptionModal, openOptionModal] = useOverlayService(OptionModal);
134
+
135
+ useEffect(() => {
136
+ const tmr = setTimeout(() => {
137
+ const model = getModel();
138
+ setType(model.datatype);
139
+ setMultiple(!!model.multiple);
140
+ if ("optionList" in model) {
141
+ setHasOptions(!!model.optionList);
142
+ setCustomOptions(model.optionList === SCHEMA_CUSTOM_LIST);
143
+ }
144
+ }, 10);
145
+
146
+ return () => {
147
+ clearTimeout(tmr);
148
+ };
149
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
150
+ }, []);
151
+
152
+ const handleTypeChange = useCallback(
153
+ (type: DATA_TYPES) => {
154
+ if (type) {
155
+ const modelRef = getModel();
156
+ let reset = {
157
+ datatype: type,
158
+ id,
159
+ label: modelRef.label,
160
+ defaultValue: modelRef.defaultValue,
161
+ required: modelRef.required,
162
+ multiple: type === DATA_TYPES.AVATAR ? false : modelRef.multiple,
163
+ minItems: modelRef.minItems,
164
+ maxItems: modelRef.maxItems,
165
+ } as DefaultSchema & ValueType;
166
+ if (type === DATA_TYPES.STRING) {
167
+ reset = Object.assign(reset, {
168
+ optionList: (modelRef as StringSchema).optionList,
169
+ options: (modelRef as StringSchema).options,
170
+ regex: (modelRef as StringSchema).regex,
171
+ } as StringSchema);
172
+ } else if (type === DATA_TYPES.DATE) {
173
+ reset = Object.assign(reset, {
174
+ min: (modelRef as DateSchema).min,
175
+ max: (modelRef as DateSchema).max,
176
+ } as DateSchema);
177
+ } else if (type === DATA_TYPES.NUMBER || type === DATA_TYPES.RANGE) {
178
+ reset = Object.assign(reset, {
179
+ min: (modelRef as NumberSchema).min,
180
+ max: (modelRef as NumberSchema).max,
181
+ step: (modelRef as NumberSchema).step,
182
+ } as NumberSchema);
183
+ }
184
+ if (type === DATA_TYPES.AVATAR) {
185
+ setMultiple(false);
186
+ }
187
+ onTypeChange(reset);
188
+ setType(type);
189
+ }
190
+ },
191
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
192
+ [id],
193
+ );
194
+
195
+ return (
196
+ <SortableItem id={fieldId} index={index} dynamic={!!dynamic} className="flex flex-nowrap gap-1 group/field">
197
+ <Collapsable key={fieldId} open={defaultOpen} className="flex-1">
198
+ <div className="flex flex-nowrap items-center">
199
+ <span className="flex-1 truncate">{id}</span>
200
+ {dynamic && (
201
+ <Button
202
+ size="sm"
203
+ color="danger"
204
+ variant="link"
205
+ aria-label="Delete"
206
+ icon="icon-[mdi--trash-can-outline]"
207
+ onClick={onRemove}
208
+ className="invisible group-hover/field:visible"
209
+ />
210
+ )}
211
+ </div>
212
+ <div className="flex flex-col gap-1 pb-4">
213
+ <Controller name={`${field}.id`}>
214
+ <HiddenInput hiddenValue={id} />
215
+ </Controller>
216
+ <Controller name={`${field}.datatype`}>
217
+ <Select
218
+ inline
219
+ label="Type"
220
+ placeholder="Data type"
221
+ options={acceptableTypes ?? EMPTY_ARRAY}
222
+ onChange={(val) => handleTypeChange(val as unknown as DATA_TYPES)}
223
+ />
224
+ </Controller>
225
+ <Controller name={`${field}.label`}>
226
+ <Input
227
+ inline
228
+ label="Label"
229
+ allowClear
230
+ placeholder="Label..."
231
+ decorateEndShowWhenEmpty
232
+ decorateEnd={<span className="text-sm text-tint-300 px-1">OPTIONAL</span>}
233
+ />
234
+ </Controller>
235
+ {type === DATA_TYPES.STRING && (
236
+ <Fragment>
237
+ <Controller name={`${field}.regex`}>
238
+ <Input
239
+ inline
240
+ label="Regex validation"
241
+ allowClear
242
+ placeholder="Regex value..."
243
+ decorateEndShowWhenEmpty
244
+ decorateEnd={<span className="text-sm text-tint-300 px-1">OPTIONAL</span>}
245
+ />
246
+ </Controller>
247
+ <Field inline label="Option list">
248
+ <Controller name={`${field}.optionList`}>
249
+ <Select
250
+ allowClear
251
+ options={[SCHEMA_CUSTOM_LIST, ...optionLists]}
252
+ onChange={(v) => setCustomOptions(v === SCHEMA_CUSTOM_LIST)}
253
+ />
254
+ </Controller>
255
+ {customOptions && (
256
+ <Button
257
+ icon="icon-[mdi--list-box-outline]"
258
+ aria-label="Custom options"
259
+ onClick={() => {
260
+ void openOptionModal({ field, title: `${id} Options` });
261
+ }}
262
+ />
263
+ )}
264
+ </Field>
265
+ {renderOptionModal()}
266
+ </Fragment>
267
+ )}
268
+ {type === DATA_TYPES.FILE && (
269
+ <Fragment>
270
+ <Controller name={`${field}.accept`}>
271
+ <Input
272
+ inline
273
+ label="Accept types"
274
+ allowClear
275
+ placeholder="Accept value..."
276
+ decorateEndShowWhenEmpty
277
+ decorateEnd={<span className="text-sm text-tint-300 px-1">OPTIONAL</span>}
278
+ />
279
+ </Controller>
280
+ </Fragment>
281
+ )}
282
+ {[DATA_TYPES.NUMBER, DATA_TYPES.RANGE].includes(type as unknown as DATA_TYPES) && (
283
+ <Fragment>
284
+ <Controller name={`${field}.min`}>
285
+ <Number
286
+ inline
287
+ label="Min value"
288
+ allowClear
289
+ placeholder="Minimum value..."
290
+ decorateEndShowWhenEmpty
291
+ decorateEnd={
292
+ <span className="text-sm text-tint-300 px-1 absolute inset-e-4 top-1 pointer-events-none">OPTIONAL</span>
293
+ }
294
+ />
295
+ </Controller>
296
+ <Controller name={`${field}.max`}>
297
+ <Number
298
+ inline
299
+ label="Max value"
300
+ allowClear
301
+ placeholder="Maximum value..."
302
+ decorateEndShowWhenEmpty
303
+ decorateEnd={
304
+ <span className="text-sm text-tint-300 px-1 absolute inset-e-4 top-1 pointer-events-none">OPTIONAL</span>
305
+ }
306
+ />
307
+ </Controller>
308
+ <Controller name={`${field}.step`}>
309
+ <Number
310
+ inline
311
+ label="Step value"
312
+ allowClear
313
+ placeholder="Step value..."
314
+ decorateEndShowWhenEmpty
315
+ decorateEnd={
316
+ <span className="text-sm text-tint-300 px-1 absolute inset-e-4 top-1 pointer-events-none">OPTIONAL</span>
317
+ }
318
+ />
319
+ </Controller>
320
+ </Fragment>
321
+ )}
322
+ {type === DATA_TYPES.DATE && (
323
+ <Fragment>
324
+ <Field inline label="Type" plain>
325
+ <Controller name={`${field}.type`}>
326
+ <ToggleButtonGroup value="" fullWidth className="flex-1">
327
+ <Button className="flex-1" value="date">
328
+ Date
329
+ </Button>
330
+ <Button className="flex-1" value="datetime">
331
+ Date Time
332
+ </Button>
333
+ </ToggleButtonGroup>
334
+ </Controller>
335
+ </Field>
336
+ <Controller name={`${field}.min`}>
337
+ <DateInput
338
+ inline
339
+ label="Min value"
340
+ allowClear
341
+ placeholder="Minimum value..."
342
+ decorateEndShowWhenEmpty
343
+ decorateEnd={<span className="text-sm text-tint-300 px-1">OPTIONAL</span>}
344
+ />
345
+ </Controller>
346
+ <Controller name={`${field}.max`}>
347
+ <DateInput
348
+ inline
349
+ label="Max value"
350
+ allowClear
351
+ placeholder="Maximum value..."
352
+ decorateEndShowWhenEmpty
353
+ decorateEnd={<span className="text-sm text-tint-300 px-1">OPTIONAL</span>}
354
+ />
355
+ </Controller>
356
+ </Fragment>
357
+ )}
358
+ <Field label=" " inline plain>
359
+ <Controller name={`${field}.required`}>
360
+ <Checkbox label="Required" />
361
+ </Controller>
362
+ {type !== DATA_TYPES.AVATAR && (
363
+ <Controller name={`${field}.multiple`}>
364
+ <Checkbox label="Multiple" onChange={setMultiple} />
365
+ </Controller>
366
+ )}
367
+ {hasOptions && (
368
+ <Controller name={`${field}.allowCustom`}>
369
+ <Checkbox label="Allow custom value" />
370
+ </Controller>
371
+ )}
372
+ </Field>
373
+ {multiple && (
374
+ <Fragment>
375
+ <Controller name={`${field}.minItems`}>
376
+ <Number
377
+ inline
378
+ label="Min items"
379
+ allowClear
380
+ placeholder="Minimum items..."
381
+ decorateEndShowWhenEmpty
382
+ decorateEnd={
383
+ <span className="text-sm text-tint-300 px-1 absolute inset-e-4 top-1 pointer-events-none">OPTIONAL</span>
384
+ }
385
+ />
386
+ </Controller>
387
+ <Controller name={`${field}.maxItems`}>
388
+ <Number
389
+ inline
390
+ label="Max items"
391
+ allowClear
392
+ placeholder="Maximum items..."
393
+ decorateEndShowWhenEmpty
394
+ decorateEnd={
395
+ <span className="text-sm text-tint-300 px-1 absolute inset-e-4 top-1 pointer-events-none">OPTIONAL</span>
396
+ }
397
+ />
398
+ </Controller>
399
+ </Fragment>
400
+ )}
401
+ </div>
402
+ </Collapsable>
403
+ </SortableItem>
404
+ );
405
+ }
@@ -21,12 +21,7 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import {
25
- Children,
26
- cloneElement,
27
- isValidElement,
28
- type ReactElement,
29
- } from "react";
24
+ import { Children, cloneElement, isValidElement, type ReactElement } from "react";
30
25
  import { Controller as HFController } from "react-hook-form";
31
26
 
32
27
  export interface ControllerProps {
@@ -35,7 +30,7 @@ export interface ControllerProps {
35
30
  */
36
31
  name?: string;
37
32
  children:
38
- | ReactElement
33
+ | ReactElement<KeyValue>
39
34
  | ((props: {
40
35
  name: string;
41
36
  error?: string;
@@ -46,30 +41,71 @@ export interface ControllerProps {
46
41
  disabled?: boolean;
47
42
  onBlur: () => void;
48
43
  onChange: (value: AnyObject) => void;
49
- inputRef?: React.Ref<HTMLInputElement>;
44
+ ref?: React.Ref<AnyObject>;
50
45
  }) => ReactElement);
51
46
  }
52
47
 
53
- export const Controller = ({ name, children, ...rest }: ControllerProps) => {
48
+ /**
49
+ * This component is used to wrap form controls
50
+ * and provide them with the necessary props and state management.
51
+ *
52
+ * It uses the `Controller` component from `react-hook-form`
53
+ * to manage the form state and validation.
54
+ *
55
+ * It accepts a `name` prop to identify the form control,
56
+ * and a `children` prop which can either be a React element
57
+ * or a function that receives the necessary props to render the control.
58
+ *
59
+ * The component will pass down the following props to the child:
60
+ * - `name`: the name of the form control
61
+ * - `error`: any validation error message
62
+ * - `checked`: whether the control is checked (for checkboxes/radios)
63
+ * - `value`: the current value of the control
64
+ * - `invalid`: whether the control is invalid
65
+ * - `readOnly`: whether the control is read-only (disabled during submission)
66
+ * - `disabled`: whether the control is disabled
67
+ * - `onBlur`: function to call when the control loses focus
68
+ * - `onChange`: function to call when the control value changes
69
+ * - `ref`: a ref to the control element
70
+ * * The component also handles the `ref` prop to ensure that it is passed correctly
71
+ * to the child component, allowing for proper focus management and DOM access.
72
+ *
73
+ * @example
74
+ * ```jsx
75
+ * <Controller
76
+ * name="username"
77
+ * children={({ name, error, checked, value, invalid, readOnly, onChange }) => (
78
+ * <input
79
+ * type="text"
80
+ * name={name}
81
+ * onChange={onChange}
82
+ * readOnly={readOnly}
83
+ * aria-invalid={invalid}
84
+ * value={value}
85
+ * />
86
+ * )}
87
+ * />
88
+ * ```
89
+ */
90
+ export function Controller(props: ControllerProps) {
91
+ const { name, children, ...rest } = props;
54
92
  if (!name) throw Error("Form Controller missing `name` prop");
55
93
  return (
56
94
  <HFController
57
95
  name={name}
58
- render={({
59
- field: { ref, onChange, ...field },
60
- fieldState: { error },
61
- formState: { isSubmitting },
62
- }) =>
63
- isValidElement<AnyObject>(children)
64
- ? cloneElement(Children.only(children), {
96
+ render={({ field: { ref, onChange, ...field }, fieldState: { error }, formState: { isSubmitting } }) =>
97
+ isValidElement(children)
98
+ ? // eslint-disable-next-line @eslint-react/no-children-only
99
+ cloneElement(Children.only(children), {
65
100
  ...field,
66
101
  ...rest,
67
- ref: (el: AnyObject) => {
68
- el && ref(el);
102
+ ref: (el: HTMLInputElement) => {
103
+ if (el) ref(el);
69
104
  try {
70
- if (children.props.ref && "current" in children.props.ref)
71
- children.props.ref.current = el;
72
- if (children.props.ref?.prototype) children.props.ref(el);
105
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
106
+ if (children.props.ref && "current" in children.props.ref) children.props.ref.current = el;
107
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
108
+ if (typeof children.props.ref === "function") children.props.ref(el);
73
109
  } catch (_) {
74
110
  //
75
111
  }
@@ -80,14 +116,15 @@ export const Controller = ({ name, children, ...rest }: ControllerProps) => {
80
116
  error: error?.message ?? children.props.error,
81
117
  onChange: (v: AnyObject) => {
82
118
  onChange(v);
119
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
83
120
  children.props.onChange?.(v);
84
121
  },
85
122
  })
86
123
  : children({
87
124
  ...field,
88
125
  ...rest,
89
- inputRef: (el: AnyObject) => {
90
- el && ref(el);
126
+ ref: (el: AnyObject) => {
127
+ if (el) ref(el);
91
128
  },
92
129
  readOnly: isSubmitting,
93
130
  invalid: !!error?.message,
@@ -100,4 +137,4 @@ export const Controller = ({ name, children, ...rest }: ControllerProps) => {
100
137
  }
101
138
  />
102
139
  );
103
- };
140
+ }