@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.
- package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
- package/css/_colorPicker.css +95 -0
- package/{src/internal/Field.module.css → css/_field.css} +94 -33
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +107 -78
- package/dist/cjs/builder/FormField.js +60 -39
- package/dist/cjs/builder/SchemaEditor.js +72 -0
- package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
- package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
- package/dist/cjs/builder/components/SchemaField.js +423 -0
- package/dist/cjs/form/Controller.js +66 -19
- package/dist/cjs/form/Form.js +97 -39
- package/dist/cjs/form/useForm.js +32 -0
- package/dist/cjs/hooks/useFileUploader.js +74 -45
- package/dist/cjs/hooks/useFormBuilder.js +85 -31
- package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +41 -2
- package/dist/cjs/input/ArrayInput.js +259 -135
- package/dist/cjs/input/AvatarInput.js +99 -63
- package/dist/cjs/input/Checkbox.js +24 -25
- package/dist/cjs/input/ColorInput.js +138 -81
- package/dist/cjs/input/ColorPicker.js +78 -95
- package/dist/cjs/input/DateInput.js +192 -0
- package/dist/cjs/input/Field.js +21 -20
- package/dist/cjs/input/File.js +85 -80
- package/dist/cjs/input/Hidden.js +27 -13
- package/dist/cjs/input/Input.js +49 -48
- package/dist/cjs/input/Number.js +46 -44
- package/dist/cjs/input/Password.js +51 -49
- package/dist/cjs/input/Radio.js +26 -16
- package/dist/cjs/input/RadioGroup.js +26 -11
- package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
- package/dist/cjs/input/Search.js +58 -64
- package/dist/cjs/input/Slider.js +57 -66
- package/dist/cjs/input/Switch.js +29 -27
- package/dist/cjs/input/Textarea.js +65 -53
- package/dist/cjs/internal/ErrorIcon.js +9 -9
- package/dist/cjs/internal/FieldWrapper.js +23 -13
- package/dist/cjs/internal/InputWrapper.js +85 -87
- package/dist/cjs/ranger/index.js +7 -7
- package/dist/cjs/ranger/utils.js +9 -4
- package/dist/cjs/select/AutoComplete.js +93 -76
- package/dist/cjs/select/DualList.js +235 -154
- package/dist/cjs/select/List.js +122 -89
- package/dist/cjs/select/Options.js +58 -54
- package/dist/cjs/select/Select.js +119 -89
- package/dist/cjs/select/helpers.js +5 -5
- package/dist/cjs/select/useSelect.js +83 -58
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +16 -6
- package/dist/cjs/utils/getColor.js +6 -6
- package/dist/cjs/utils/getProps.js +49 -0
- package/dist/esm/builder/FileField.js +109 -80
- package/dist/esm/builder/FormField.js +60 -39
- package/dist/esm/builder/SchemaEditor.js +72 -0
- package/dist/esm/builder/components/AddSchemaFields.js +64 -0
- package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
- package/dist/esm/builder/components/SchemaField.js +423 -0
- package/dist/esm/form/Controller.js +66 -19
- package/dist/esm/form/Form.js +97 -39
- package/dist/esm/form/useForm.js +32 -0
- package/dist/esm/hooks/useFileUploader.js +75 -46
- package/dist/esm/hooks/useFormBuilder.js +84 -30
- package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +36 -1
- package/dist/esm/input/ArrayInput.js +260 -136
- package/dist/esm/input/AvatarInput.js +99 -63
- package/dist/esm/input/Checkbox.js +26 -27
- package/dist/esm/input/ColorInput.js +141 -84
- package/dist/esm/input/ColorPicker.js +80 -97
- package/dist/esm/input/DateInput.js +192 -0
- package/dist/esm/input/Field.js +21 -20
- package/dist/esm/input/File.js +89 -84
- package/dist/esm/input/Hidden.js +28 -14
- package/dist/esm/input/Input.js +51 -50
- package/dist/esm/input/Number.js +48 -46
- package/dist/esm/input/Password.js +54 -52
- package/dist/esm/input/Radio.js +27 -17
- package/dist/esm/input/RadioGroup.js +27 -12
- package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
- package/dist/esm/input/Search.js +61 -67
- package/dist/esm/input/Slider.js +58 -67
- package/dist/esm/input/Switch.js +31 -29
- package/dist/esm/input/Textarea.js +67 -55
- package/dist/esm/internal/ErrorIcon.js +11 -11
- package/dist/esm/internal/FieldWrapper.js +23 -13
- package/dist/esm/internal/InputWrapper.js +88 -90
- package/dist/esm/ranger/index.js +7 -7
- package/dist/esm/ranger/utils.js +9 -4
- package/dist/esm/select/AutoComplete.js +95 -78
- package/dist/esm/select/DualList.js +233 -152
- package/dist/esm/select/List.js +125 -92
- package/dist/esm/select/Options.js +58 -54
- package/dist/esm/select/Select.js +122 -92
- package/dist/esm/select/helpers.js +5 -5
- package/dist/esm/select/useSelect.js +84 -59
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +16 -6
- package/dist/esm/utils/getColor.js +6 -6
- package/dist/esm/utils/getProps.js +49 -0
- package/dist/types/builder/FileField.d.ts +7 -4
- package/dist/types/builder/FormField.d.ts +4 -2
- package/dist/types/builder/SchemaEditor.d.ts +6 -0
- package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
- package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
- package/dist/types/builder/components/SchemaField.d.ts +12 -0
- package/dist/types/form/Controller.d.ts +45 -3
- package/dist/types/form/Form.d.ts +38 -7
- package/dist/types/form/useForm.d.ts +6 -0
- package/dist/types/hooks/useFileUploader.d.ts +24 -3
- package/dist/types/hooks/useFormBuilder.d.ts +41 -3
- package/dist/types/index.d.ts +32 -2
- package/dist/types/input/ArrayInput.d.ts +68 -13
- package/dist/types/input/AvatarInput.d.ts +23 -6
- package/dist/types/input/Checkbox.d.ts +12 -10
- package/dist/types/input/ColorInput.d.ts +15 -5
- package/dist/types/input/ColorPicker.d.ts +10 -2
- package/dist/types/input/DateInput.d.ts +21 -0
- package/dist/types/input/Field.d.ts +9 -36
- package/dist/types/input/File.d.ts +16 -4
- package/dist/types/input/Hidden.d.ts +7 -6
- package/dist/types/input/Input.d.ts +11 -4
- package/dist/types/input/Number.d.ts +9 -4
- package/dist/types/input/Password.d.ts +9 -4
- package/dist/types/input/Radio.d.ts +9 -4
- package/dist/types/input/RadioGroup.d.ts +14 -6
- package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
- package/dist/types/input/Search.d.ts +14 -4
- package/dist/types/input/Slider.d.ts +9 -6
- package/dist/types/input/Switch.d.ts +13 -8
- package/dist/types/input/Textarea.d.ts +15 -4
- package/dist/types/internal/ErrorIcon.d.ts +4 -1
- package/dist/types/internal/FieldWrapper.d.ts +10 -1
- package/dist/types/internal/InputWrapper.d.ts +19 -1
- package/dist/types/ranger/index.d.ts +7 -3
- package/dist/types/ranger/utils.d.ts +2 -2
- package/dist/types/select/AutoComplete.d.ts +7 -2
- package/dist/types/select/DualList.d.ts +11 -37
- package/dist/types/select/List.d.ts +7 -2
- package/dist/types/select/Options.d.ts +8 -7
- package/dist/types/select/Select.d.ts +7 -2
- package/dist/types/select/helpers.d.ts +2 -2
- package/dist/types/select/useSelect.d.ts +10 -28
- package/dist/types/types/index.d.ts +73 -39
- package/dist/types/types/schema.d.ts +78 -32
- package/dist/types/utils/eventHandlers.d.ts +4 -4
- package/dist/types/utils/getColor.d.ts +4 -4
- package/dist/types/utils/getProps.d.ts +26 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/package.json +25 -25
- package/src/builder/FileField.tsx +73 -56
- package/src/builder/FormField.tsx +66 -58
- package/src/builder/SchemaEditor.tsx +78 -0
- package/src/builder/components/AddSchemaFields.tsx +64 -0
- package/src/builder/components/SchemaArrayForm.tsx +146 -0
- package/src/builder/components/SchemaField.tsx +405 -0
- package/src/form/Controller.tsx +61 -24
- package/src/form/Form.tsx +74 -50
- package/src/form/useForm.ts +53 -0
- package/src/hooks/useFileUploader.ts +115 -84
- package/src/hooks/useFormBuilder.tsx +80 -41
- package/src/i18n/{ar.json → ar/form.json} +5 -4
- package/src/i18n/{en.json → en/form.json} +5 -5
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +34 -20
- package/src/input/ArrayInput.tsx +255 -173
- package/src/input/AvatarInput.tsx +82 -39
- package/src/input/Checkbox.tsx +30 -54
- package/src/input/ColorInput.tsx +146 -105
- package/src/input/ColorPicker.tsx +76 -102
- package/src/input/DateInput.tsx +225 -0
- package/src/input/Field.tsx +23 -65
- package/src/input/File.tsx +76 -98
- package/src/input/Hidden.tsx +36 -24
- package/src/input/Input.tsx +55 -53
- package/src/input/Number.tsx +49 -54
- package/src/input/Password.tsx +61 -66
- package/src/input/Radio.tsx +28 -37
- package/src/input/RadioGroup.tsx +30 -25
- package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
- package/src/input/Search.tsx +64 -77
- package/src/input/Slider.tsx +80 -109
- package/src/input/Switch.tsx +39 -53
- package/src/input/Textarea.tsx +72 -59
- package/src/internal/ErrorIcon.tsx +22 -21
- package/src/internal/FieldWrapper.tsx +33 -23
- package/src/internal/InputWrapper.tsx +132 -143
- package/src/ranger/index.tsx +37 -117
- package/src/ranger/utils.ts +12 -11
- package/src/select/AutoComplete.tsx +113 -123
- package/src/select/DualList.tsx +218 -204
- package/src/select/List.tsx +130 -97
- package/src/select/Options.tsx +59 -57
- package/src/select/Select.tsx +148 -122
- package/src/select/helpers.ts +13 -8
- package/src/select/useSelect.tsx +110 -127
- package/src/types/index.ts +76 -45
- package/src/types/schema.ts +97 -40
- package/src/utils/eventHandlers.ts +24 -26
- package/src/utils/getColor.ts +9 -8
- package/src/utils/getProps.ts +47 -0
- package/src/utils/index.ts +1 -0
- package/dist/cjs/input/ArrayInput.module.css +0 -1
- package/dist/cjs/input/ColorPicker.module.css +0 -1
- package/dist/cjs/input/File.module.css +0 -1
- package/dist/cjs/internal/Field.module.css +0 -1
- package/dist/esm/input/ArrayInput.module.css +0 -1
- package/dist/esm/input/ColorPicker.module.css +0 -1
- package/dist/esm/input/File.module.css +0 -1
- package/dist/esm/internal/Field.module.css +0 -1
|
@@ -21,6 +21,9 @@
|
|
|
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
|
+
/* eslint-disable @eslint-react/static-components */
|
|
25
|
+
|
|
26
|
+
import { isEmpty } from "@react-fabric/utilities";
|
|
24
27
|
import { useMemo } from "react";
|
|
25
28
|
import { useTranslation } from "react-i18next";
|
|
26
29
|
import { Controller } from "../form/Controller";
|
|
@@ -28,23 +31,38 @@ import { type UploadHandler } from "../hooks/useFileUploader";
|
|
|
28
31
|
import { ArrayInput } from "../input/ArrayInput";
|
|
29
32
|
import { AvatarInput } from "../input/AvatarInput";
|
|
30
33
|
import { ColorInput } from "../input/ColorInput";
|
|
34
|
+
import { DateInput } from "../input/DateInput";
|
|
31
35
|
import { Input } from "../input/Input";
|
|
32
36
|
import { Number } from "../input/Number";
|
|
33
|
-
import {
|
|
37
|
+
import { RangeSlider } from "../input/RangeSlider";
|
|
34
38
|
import { Switch } from "../input/Switch";
|
|
35
39
|
import { Textarea } from "../input/Textarea";
|
|
36
40
|
import { Select } from "../select/Select";
|
|
37
|
-
import { type
|
|
41
|
+
import { type BaseSelectProps } from "../types";
|
|
42
|
+
import { DATA_TYPES, type SchemaType } from "../types/schema";
|
|
38
43
|
import { FileField } from "./FileField";
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
const getField = (datatype: DATA_TYPES, hasOptions: boolean) => {
|
|
46
|
+
if (datatype === DATA_TYPES.NUMBER) return Number;
|
|
47
|
+
if (datatype === DATA_TYPES.RANGE) return RangeSlider;
|
|
48
|
+
if (datatype === DATA_TYPES.BOOL) return Switch;
|
|
49
|
+
if (datatype === DATA_TYPES.TEXT) return Textarea;
|
|
50
|
+
if (datatype === DATA_TYPES.DATE) return DateInput;
|
|
51
|
+
if (datatype === DATA_TYPES.DATETIME) return DateInput;
|
|
52
|
+
if (datatype === DATA_TYPES.COLOR) return ColorInput;
|
|
53
|
+
if (hasOptions) return Select;
|
|
54
|
+
|
|
55
|
+
return Input;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function FormField<T extends AnyObject = string>({
|
|
41
59
|
id,
|
|
42
60
|
label,
|
|
43
61
|
required,
|
|
44
62
|
inline,
|
|
45
63
|
prefix,
|
|
46
64
|
autoFocus,
|
|
47
|
-
|
|
65
|
+
optionLists,
|
|
48
66
|
fileUrl,
|
|
49
67
|
uploadHandler,
|
|
50
68
|
...rest
|
|
@@ -52,41 +70,28 @@ export const FormField = ({
|
|
|
52
70
|
inline?: boolean;
|
|
53
71
|
prefix?: string;
|
|
54
72
|
autoFocus?: boolean;
|
|
73
|
+
optionLists?: KeyValue<BaseSelectProps<T>>;
|
|
55
74
|
fileUrl?: (path: string) => string;
|
|
56
75
|
uploadHandler?: UploadHandler;
|
|
57
|
-
})
|
|
76
|
+
}) {
|
|
58
77
|
const { t } = useTranslation();
|
|
59
|
-
const fieldName = useMemo(
|
|
60
|
-
() => [prefix, id].filter(Boolean).join("."),
|
|
61
|
-
[id, prefix],
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const E = useMemo(() => {
|
|
65
|
-
if (rest.datatype === "number" || rest.datatype === "decimal")
|
|
66
|
-
return Number;
|
|
67
|
-
if (rest.datatype === "range") return Range;
|
|
68
|
-
if (rest.datatype === "boolean") return Switch;
|
|
69
|
-
if (rest.datatype === "text") return Textarea;
|
|
70
|
-
if (rest.datatype === "color") return ColorInput;
|
|
71
|
-
if (rest.datatype === "string" && rest.options) return Select;
|
|
78
|
+
const fieldName = useMemo(() => [prefix, id].filter(Boolean).join("."), [id, prefix]);
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const hasOptions = useMemo(
|
|
81
|
+
() =>
|
|
82
|
+
rest.datatype === DATA_TYPES.STRING &&
|
|
83
|
+
((rest.optionList === "--CUSTOM--" && !isEmpty(rest.options)) || !isEmpty(optionLists?.[rest.optionList ?? ""])),
|
|
84
|
+
[rest, optionLists],
|
|
85
|
+
);
|
|
75
86
|
|
|
76
87
|
const props = useMemo(() => {
|
|
77
|
-
if (rest.datatype ===
|
|
88
|
+
if (rest.datatype === DATA_TYPES.NUMBER)
|
|
78
89
|
return {
|
|
79
90
|
min: rest.min,
|
|
80
91
|
max: rest.max,
|
|
81
92
|
step: rest.step ?? 1,
|
|
82
93
|
};
|
|
83
|
-
if (rest.datatype ===
|
|
84
|
-
return {
|
|
85
|
-
min: rest.min,
|
|
86
|
-
max: rest.max,
|
|
87
|
-
step: rest.step ?? 0.1,
|
|
88
|
-
};
|
|
89
|
-
if (rest.datatype === "range")
|
|
94
|
+
if (rest.datatype === DATA_TYPES.RANGE)
|
|
90
95
|
return {
|
|
91
96
|
min: rest.min ?? 1,
|
|
92
97
|
max: rest.max ?? 99,
|
|
@@ -94,54 +99,57 @@ export const FormField = ({
|
|
|
94
99
|
showValue: true,
|
|
95
100
|
showLabels: true,
|
|
96
101
|
};
|
|
97
|
-
if (rest.datatype ===
|
|
98
|
-
|
|
102
|
+
if (rest.datatype === DATA_TYPES.DATE)
|
|
103
|
+
return {
|
|
104
|
+
min: rest.min,
|
|
105
|
+
max: rest.max,
|
|
106
|
+
type: "date",
|
|
107
|
+
};
|
|
108
|
+
if (rest.datatype === DATA_TYPES.DATETIME)
|
|
109
|
+
return {
|
|
110
|
+
min: rest.min,
|
|
111
|
+
max: rest.max,
|
|
112
|
+
type: "datetime",
|
|
113
|
+
};
|
|
114
|
+
if (rest.datatype === DATA_TYPES.TEXT) return { rows: 3 };
|
|
115
|
+
if (rest.datatype === DATA_TYPES.COLOR) return { showPicker: true };
|
|
99
116
|
|
|
100
|
-
if (rest.datatype ===
|
|
117
|
+
if (rest.datatype === DATA_TYPES.STRING && hasOptions)
|
|
101
118
|
return {
|
|
102
|
-
options: rest.options,
|
|
103
119
|
multiple: rest.multiple,
|
|
120
|
+
allowCreate: rest.allowCustom,
|
|
104
121
|
searchable: true,
|
|
122
|
+
...(optionLists?.[rest.optionList ?? ""] ?? { options: rest.options }),
|
|
105
123
|
};
|
|
106
124
|
|
|
107
125
|
return {};
|
|
108
|
-
}, [rest]);
|
|
126
|
+
}, [rest, hasOptions, optionLists]);
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
129
|
+
const E = useMemo<AnyObject>(() => {
|
|
130
|
+
return getField(rest.datatype, hasOptions);
|
|
131
|
+
}, [rest.datatype, hasOptions]);
|
|
132
|
+
|
|
133
|
+
if (rest.datatype === DATA_TYPES.AVATAR)
|
|
111
134
|
return (
|
|
112
135
|
uploadHandler && (
|
|
113
136
|
<Controller name={fieldName}>
|
|
114
|
-
<AvatarInput
|
|
115
|
-
uploadHandler={uploadHandler}
|
|
116
|
-
fileUrl={fileUrl}
|
|
117
|
-
name={fieldName}
|
|
118
|
-
/>
|
|
137
|
+
<AvatarInput uploadHandler={uploadHandler} fileUrl={fileUrl} name={fieldName} />
|
|
119
138
|
</Controller>
|
|
120
139
|
)
|
|
121
140
|
);
|
|
122
141
|
|
|
123
|
-
if (rest.datatype ===
|
|
142
|
+
if (rest.datatype === DATA_TYPES.FILE)
|
|
124
143
|
return (
|
|
125
144
|
uploadHandler && (
|
|
126
145
|
<Controller name={fieldName}>
|
|
127
|
-
<FileField
|
|
128
|
-
uploadHandler={uploadHandler}
|
|
129
|
-
inline={inline}
|
|
130
|
-
label={label}
|
|
131
|
-
id={id}
|
|
132
|
-
{...rest}
|
|
133
|
-
/>
|
|
146
|
+
<FileField uploadHandler={uploadHandler} inline={inline} label={label} id={id} {...rest} />
|
|
134
147
|
</Controller>
|
|
135
148
|
)
|
|
136
149
|
);
|
|
137
150
|
|
|
138
|
-
return rest.multiple && !
|
|
139
|
-
<ArrayInput
|
|
140
|
-
name={fieldName}
|
|
141
|
-
label={t(label, label)}
|
|
142
|
-
required={required}
|
|
143
|
-
onAdd={() => ""}
|
|
144
|
-
>
|
|
151
|
+
return rest.multiple && !hasOptions ? (
|
|
152
|
+
<ArrayInput name={fieldName} label={t(`fb.${id}`, label || id)} required={required} onAdd={() => String("")}>
|
|
145
153
|
<Controller>
|
|
146
154
|
<E {...props} />
|
|
147
155
|
</Controller>
|
|
@@ -150,12 +158,12 @@ export const FormField = ({
|
|
|
150
158
|
<Controller name={fieldName}>
|
|
151
159
|
<E
|
|
152
160
|
{...props}
|
|
153
|
-
|
|
154
|
-
label={t(label
|
|
155
|
-
placeholder={t(label
|
|
161
|
+
{...{ autoFocus }}
|
|
162
|
+
label={t(`fb.${id}`, label || id)}
|
|
163
|
+
placeholder={t(`fb.${id}`, label || id)}
|
|
156
164
|
required={required}
|
|
157
165
|
inline={inline}
|
|
158
166
|
/>
|
|
159
167
|
</Controller>
|
|
160
168
|
);
|
|
161
|
-
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { Footer } from "@react-fabric/core";
|
|
25
|
+
import { EMPTY_ARRAY } from "@react-fabric/utilities";
|
|
26
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
27
|
+
import { Form, type FormRef } from "../form/Form";
|
|
28
|
+
import { DATA_TYPES, type FormSchema, type SchemaEditorProps } from "../types/schema";
|
|
29
|
+
import { SchemaArrayForm } from "./components/SchemaArrayForm";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* SchemaEditor component allows users to edit a schema definition.
|
|
33
|
+
* It provides a form interface to modify schema properties dynamically.
|
|
34
|
+
*/
|
|
35
|
+
export function SchemaEditor({
|
|
36
|
+
schemaDef,
|
|
37
|
+
dynamic,
|
|
38
|
+
acceptableTypes,
|
|
39
|
+
optionLists = EMPTY_ARRAY,
|
|
40
|
+
children,
|
|
41
|
+
onSubmit,
|
|
42
|
+
onChange,
|
|
43
|
+
}: SchemaEditorProps) {
|
|
44
|
+
const formRef = useRef<FormRef<{ schema: FormSchema }>>(null);
|
|
45
|
+
|
|
46
|
+
const handleChange = useCallback(
|
|
47
|
+
(model: { schema: FormSchema }) => {
|
|
48
|
+
onChange?.(model.schema);
|
|
49
|
+
},
|
|
50
|
+
[onChange],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const handleSubmit = useCallback(
|
|
54
|
+
(model: { schema: FormSchema }) => {
|
|
55
|
+
onSubmit?.(model.schema);
|
|
56
|
+
},
|
|
57
|
+
[onSubmit],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
formRef.current?.setValues({ schema: schemaDef });
|
|
62
|
+
}, [schemaDef]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Form formRef={formRef} onChange={handleChange} onSubmit={handleSubmit}>
|
|
66
|
+
<SchemaArrayForm
|
|
67
|
+
dynamic={dynamic}
|
|
68
|
+
optionLists={optionLists}
|
|
69
|
+
acceptableTypes={acceptableTypes ?? Object.values(DATA_TYPES)}
|
|
70
|
+
/>
|
|
71
|
+
{children && (
|
|
72
|
+
<Footer flex className="border-t bg-default px-6 py-2">
|
|
73
|
+
{children}
|
|
74
|
+
</Footer>
|
|
75
|
+
)}
|
|
76
|
+
</Form>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 { yupResolver } from "@hookform/resolvers/yup";
|
|
25
|
+
import { Button, Content, Footer, Modal, type ModalProps } from "@react-fabric/core";
|
|
26
|
+
import { yup } from "@react-fabric/utilities";
|
|
27
|
+
import { useCallback } from "react";
|
|
28
|
+
import { Controller } from "../../form/Controller";
|
|
29
|
+
import { Form } from "../../form/Form";
|
|
30
|
+
import { ArrayInput } from "../../input/ArrayInput";
|
|
31
|
+
import { Input } from "../../input/Input";
|
|
32
|
+
|
|
33
|
+
const fieldSchema = yup.object({
|
|
34
|
+
fields: yup.array(yup.string().trim().required()).required().min(1),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type FieldSchemaType = yup.InferType<typeof fieldSchema>;
|
|
38
|
+
|
|
39
|
+
export function AddSchemaFields(props: ModalProps) {
|
|
40
|
+
const handleSubmit = useCallback(
|
|
41
|
+
(model: FieldSchemaType) => {
|
|
42
|
+
props.onClose?.(model.fields.map((f: string) => f.split(",").map((ff) => ff.trim())).flat(2));
|
|
43
|
+
},
|
|
44
|
+
[props],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Modal {...props}>
|
|
49
|
+
<Form resolver={yupResolver(fieldSchema)} defaultValues={{ fields: [" "] }} onSubmit={handleSubmit}>
|
|
50
|
+
<Content>
|
|
51
|
+
<ArrayInput minItems={1} name="fields" onAdd={() => String("")}>
|
|
52
|
+
<Controller>
|
|
53
|
+
<Input {...{ autoFocus: true }} />
|
|
54
|
+
</Controller>
|
|
55
|
+
</ArrayInput>
|
|
56
|
+
<p className="text-sm text-muted">Provide list of fields or comma-separated value</p>
|
|
57
|
+
</Content>
|
|
58
|
+
<Footer flex justify="end" className="px-2 py-1">
|
|
59
|
+
<Button type="submit">Add</Button>
|
|
60
|
+
</Footer>
|
|
61
|
+
</Form>
|
|
62
|
+
</Modal>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
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 { RestrictToVerticalAxis } from "@dnd-kit/abstract/modifiers";
|
|
25
|
+
import { DragDropProvider, type DragEndEvent } from "@dnd-kit/react";
|
|
26
|
+
import { Button, Content, Header, useOverlayService } from "@react-fabric/core";
|
|
27
|
+
import { isArray } from "@react-fabric/utilities";
|
|
28
|
+
import { useCallback, useState } from "react";
|
|
29
|
+
import { useFieldArray, useFormContext } from "react-hook-form";
|
|
30
|
+
import type { FormSchema, SchemaDef } from "../../types/schema";
|
|
31
|
+
import { DATA_TYPES, type SchemaEditorProps } from "../../types/schema";
|
|
32
|
+
import { AddSchemaFields } from "./AddSchemaFields";
|
|
33
|
+
import { SchemaField } from "./SchemaField";
|
|
34
|
+
|
|
35
|
+
function Wrapper({
|
|
36
|
+
children,
|
|
37
|
+
fields,
|
|
38
|
+
onMove,
|
|
39
|
+
}: {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
fields: KeyValue[];
|
|
42
|
+
onMove: (active: number, end: number) => void;
|
|
43
|
+
}) {
|
|
44
|
+
// pass id list to dnd context
|
|
45
|
+
const idMap = fields.map((item) => item.__ID__ as string);
|
|
46
|
+
const handleDragEnd = (e: DragEndEvent) => {
|
|
47
|
+
if (e.operation.source?.id !== e.operation.target?.id) {
|
|
48
|
+
// find index of item and drop over
|
|
49
|
+
const active = idMap.indexOf(`${e.operation.source?.id}`);
|
|
50
|
+
const end = idMap.indexOf(`${e.operation.target?.id}`);
|
|
51
|
+
onMove(active, end);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return (
|
|
55
|
+
<DragDropProvider onDragEnd={handleDragEnd} modifiers={[RestrictToVerticalAxis]}>
|
|
56
|
+
{children}
|
|
57
|
+
</DragDropProvider>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Need to disable prop-types lint due to uknown lint error
|
|
62
|
+
export function SchemaArrayForm({ dynamic, acceptableTypes, optionLists }: Partial<SchemaEditorProps>) {
|
|
63
|
+
const form = useFormContext<{ schema: FormSchema }>();
|
|
64
|
+
const [defaultOpen, setDefaultOpen] = useState(true);
|
|
65
|
+
const { fields, append, remove, move } = useFieldArray({
|
|
66
|
+
name: "schema",
|
|
67
|
+
control: form.control,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const handleTypeChange = useCallback(
|
|
71
|
+
(index: number, val: SchemaDef) => {
|
|
72
|
+
form.setValue(`schema.${index}`, val);
|
|
73
|
+
},
|
|
74
|
+
[form],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const handleRemove = useCallback(
|
|
78
|
+
(index: number) => {
|
|
79
|
+
remove(index);
|
|
80
|
+
},
|
|
81
|
+
[remove],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const [renderModal, openAddfields] = useOverlayService<unknown, string[]>(AddSchemaFields);
|
|
85
|
+
const handleAdd = useCallback(() => {
|
|
86
|
+
void openAddfields().then((fields) => {
|
|
87
|
+
if (isArray(fields)) {
|
|
88
|
+
const existing = form.getValues().schema.map((field) => field.id);
|
|
89
|
+
fields.forEach((field) => {
|
|
90
|
+
if (!existing.includes(field)) {
|
|
91
|
+
append({
|
|
92
|
+
id: field,
|
|
93
|
+
datatype: DATA_TYPES.STRING,
|
|
94
|
+
label: "",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Wrapper onMove={move} fields={fields}>
|
|
105
|
+
<Header flex justify="end" className="gap-1 px-2">
|
|
106
|
+
{dynamic && (
|
|
107
|
+
<Button size="sm" variant="link" icon="icon-[mdi--plus-circle-outline]" aria-label="add field" onClick={handleAdd} />
|
|
108
|
+
)}
|
|
109
|
+
<Button
|
|
110
|
+
size="sm"
|
|
111
|
+
variant="link"
|
|
112
|
+
icon="icon-[mdi--arrow-collapse-vertical]"
|
|
113
|
+
aria-label="collapse all"
|
|
114
|
+
onClick={() => setDefaultOpen(false)}
|
|
115
|
+
/>
|
|
116
|
+
<Button
|
|
117
|
+
size="sm"
|
|
118
|
+
variant="link"
|
|
119
|
+
icon="icon-[mdi--arrow-expand-vertical]"
|
|
120
|
+
aria-label="expand all"
|
|
121
|
+
onClick={() => setDefaultOpen(true)}
|
|
122
|
+
/>
|
|
123
|
+
</Header>
|
|
124
|
+
<Content className="divide-y">
|
|
125
|
+
{fields.map((field, index) => (
|
|
126
|
+
<SchemaField
|
|
127
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
128
|
+
key={index}
|
|
129
|
+
index={index}
|
|
130
|
+
defaultOpen={defaultOpen}
|
|
131
|
+
id={form.getValues().schema[index].id}
|
|
132
|
+
fieldId={field.id}
|
|
133
|
+
field={`schema.${index}`}
|
|
134
|
+
dynamic={dynamic}
|
|
135
|
+
optionLists={optionLists}
|
|
136
|
+
onRemove={() => handleRemove(index)}
|
|
137
|
+
getModel={() => form.getValues().schema[index]}
|
|
138
|
+
onTypeChange={(val: SchemaDef) => handleTypeChange(index, val)}
|
|
139
|
+
acceptableTypes={acceptableTypes ?? Object.values(DATA_TYPES)}
|
|
140
|
+
/>
|
|
141
|
+
))}
|
|
142
|
+
</Content>
|
|
143
|
+
{renderModal()}
|
|
144
|
+
</Wrapper>
|
|
145
|
+
);
|
|
146
|
+
}
|