@windstream/react-shared-components 0.2.55 → 0.2.56
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/dist/contentful/index.d.ts +200 -2
- package/dist/contentful/index.esm.js +5 -5
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +4 -4
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/contentful/blocks/dynamic-form/DynamicForm.stories.tsx +154 -0
- package/src/contentful/blocks/dynamic-form/conditional-fields.test.tsx +108 -0
- package/src/contentful/blocks/dynamic-form/conditional-fields.tsx +103 -0
- package/src/contentful/blocks/dynamic-form/constants.ts +28 -0
- package/src/contentful/blocks/dynamic-form/context.ts +35 -0
- package/src/contentful/blocks/dynamic-form/field-map.test.tsx +421 -0
- package/src/contentful/blocks/dynamic-form/field-map.tsx +486 -0
- package/src/contentful/blocks/dynamic-form/form-field.test.tsx +319 -0
- package/src/contentful/blocks/dynamic-form/form-field.tsx +224 -0
- package/src/contentful/blocks/dynamic-form/index.test.tsx +355 -0
- package/src/contentful/blocks/dynamic-form/index.tsx +552 -0
- package/src/contentful/blocks/dynamic-form/render.test.tsx +116 -0
- package/src/contentful/blocks/dynamic-form/render.tsx +172 -0
- package/src/contentful/blocks/dynamic-form/types.ts +211 -0
- package/src/contentful/blocks/dynamic-form/utils.test.ts +238 -0
- package/src/contentful/blocks/dynamic-form/utils.ts +255 -0
- package/src/contentful/index.ts +20 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { FormContext } from "./context";
|
|
5
|
+
import { FieldRenderProps } from "./types";
|
|
6
|
+
|
|
7
|
+
import { Checkbox } from "@shared/components/checkbox";
|
|
8
|
+
import { Input } from "@shared/components/input";
|
|
9
|
+
import { MaterialIcon } from "@shared/components/material-icon";
|
|
10
|
+
import { RadioButton } from "@shared/components/radio-button";
|
|
11
|
+
import { Select } from "@shared/components/select";
|
|
12
|
+
import { Text } from "@shared/components/text";
|
|
13
|
+
import { cx } from "@shared/utils";
|
|
14
|
+
|
|
15
|
+
export const formatFormPhoneNumber = (value: string): string => {
|
|
16
|
+
const phoneNumber = (value || "").replace(/\D/g, "");
|
|
17
|
+
if (phoneNumber.length <= 3) {
|
|
18
|
+
return phoneNumber;
|
|
19
|
+
} else if (phoneNumber.length <= 6) {
|
|
20
|
+
return `${phoneNumber.slice(0, 3)}-${phoneNumber.slice(3)}`;
|
|
21
|
+
} else if (phoneNumber.length <= 9) {
|
|
22
|
+
return `${phoneNumber.slice(0, 3)}-${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6)}`;
|
|
23
|
+
}
|
|
24
|
+
return `${phoneNumber.slice(0, 3)}-${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6, 10)}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const labelCreator = (
|
|
28
|
+
label: string | undefined,
|
|
29
|
+
required?: boolean,
|
|
30
|
+
variant = "footnote"
|
|
31
|
+
) => {
|
|
32
|
+
return (
|
|
33
|
+
<Text as="div" className={cx(variant, "validation-label mb-1")}>
|
|
34
|
+
{label}
|
|
35
|
+
{required ? <span className="text-text-critical">*</span> : null}
|
|
36
|
+
</Text>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const TextInputField: React.FC<FieldRenderProps> = args => {
|
|
41
|
+
const {
|
|
42
|
+
displayName,
|
|
43
|
+
formData,
|
|
44
|
+
textInputType,
|
|
45
|
+
handleInputChange,
|
|
46
|
+
fieldId,
|
|
47
|
+
errorClass,
|
|
48
|
+
} = args;
|
|
49
|
+
|
|
50
|
+
if (textInputType === "phone") {
|
|
51
|
+
return (
|
|
52
|
+
<div className="w-full">
|
|
53
|
+
<Input
|
|
54
|
+
aria-label={displayName}
|
|
55
|
+
name={fieldId}
|
|
56
|
+
type="tel"
|
|
57
|
+
value={
|
|
58
|
+
formData[fieldId!] ? formatFormPhoneNumber(formData[fieldId!]) : ""
|
|
59
|
+
}
|
|
60
|
+
onChange={handleInputChange}
|
|
61
|
+
className={cx("w-full", errorClass)}
|
|
62
|
+
maxLength={14}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (textInputType === "multiline") {
|
|
69
|
+
return (
|
|
70
|
+
<div className="w-full">
|
|
71
|
+
<textarea
|
|
72
|
+
aria-label={displayName}
|
|
73
|
+
name={fieldId}
|
|
74
|
+
value={formData?.[fieldId!] ?? ""}
|
|
75
|
+
onChange={handleInputChange}
|
|
76
|
+
rows={4}
|
|
77
|
+
className={cx(
|
|
78
|
+
"w-full rounded-xl border border-input-border bg-white px-4 py-3 text-text outline-none focus:border-input-border-selected",
|
|
79
|
+
errorClass
|
|
80
|
+
)}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="w-full">
|
|
88
|
+
<Input
|
|
89
|
+
aria-label={displayName}
|
|
90
|
+
name={fieldId}
|
|
91
|
+
value={formData?.[fieldId!] ?? ""}
|
|
92
|
+
onChange={handleInputChange}
|
|
93
|
+
className={cx("w-full", errorClass)}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const ConsentCheckbox: React.FC<FieldRenderProps> = args => {
|
|
100
|
+
const { requiredField, handleInputChange, fieldId, displayName, hasError } =
|
|
101
|
+
args;
|
|
102
|
+
const [checked, setChecked] = useState(false);
|
|
103
|
+
|
|
104
|
+
const handleCheckboxChange = () => {
|
|
105
|
+
setChecked(prev => {
|
|
106
|
+
handleInputChange(!prev);
|
|
107
|
+
return !prev;
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Checkbox
|
|
113
|
+
name={fieldId}
|
|
114
|
+
id={fieldId}
|
|
115
|
+
checked={checked}
|
|
116
|
+
error={hasError}
|
|
117
|
+
onChange={handleCheckboxChange}
|
|
118
|
+
labelClassName="footnote text-text-secondary"
|
|
119
|
+
label={
|
|
120
|
+
<>
|
|
121
|
+
{displayName}
|
|
122
|
+
{requiredField ? <span className="text-text-critical">*</span> : null}
|
|
123
|
+
</>
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const CheckboxInputField: React.FC<FieldRenderProps> = args => {
|
|
130
|
+
const {
|
|
131
|
+
formData,
|
|
132
|
+
handleInputChange,
|
|
133
|
+
fieldId,
|
|
134
|
+
dataSource,
|
|
135
|
+
isConsentCheckbox,
|
|
136
|
+
hasError,
|
|
137
|
+
} = args;
|
|
138
|
+
|
|
139
|
+
if (isConsentCheckbox) {
|
|
140
|
+
return <ConsentCheckbox {...args} />;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const raw = formData?.[fieldId!];
|
|
144
|
+
const selectedValues: string[] =
|
|
145
|
+
typeof raw === "string" && raw.length > 0 ? raw.split(",") : [];
|
|
146
|
+
|
|
147
|
+
const handleCheckboxChange = (value: string) => {
|
|
148
|
+
let updated: string[];
|
|
149
|
+
if (selectedValues.includes(value)) {
|
|
150
|
+
updated = selectedValues.filter(item => item !== value);
|
|
151
|
+
} else {
|
|
152
|
+
updated = [...selectedValues, value];
|
|
153
|
+
}
|
|
154
|
+
handleInputChange(updated.join(","));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
159
|
+
{(dataSource || []).map((data: any, idx: number) => (
|
|
160
|
+
<div key={`${fieldId}-${idx}`} className="w-full lg:w-[384px]">
|
|
161
|
+
<Checkbox
|
|
162
|
+
name={fieldId}
|
|
163
|
+
label={data.label}
|
|
164
|
+
value={data.value}
|
|
165
|
+
checked={selectedValues.includes(data.value)}
|
|
166
|
+
onChange={() => handleCheckboxChange(data.value)}
|
|
167
|
+
error={hasError}
|
|
168
|
+
labelClassName="footnote"
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
))}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const DropdownInputField: React.FC<FieldRenderProps> = args => {
|
|
177
|
+
const {
|
|
178
|
+
formData,
|
|
179
|
+
handleInputChange,
|
|
180
|
+
fieldId,
|
|
181
|
+
dataSource,
|
|
182
|
+
existingDataSource,
|
|
183
|
+
errorClass,
|
|
184
|
+
} = args;
|
|
185
|
+
const [options, setOptions] = useState<any[]>(dataSource ?? []);
|
|
186
|
+
const context = useContext(FormContext);
|
|
187
|
+
const resolve = context?.resolveExistingDataSource;
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const fetchData = async () => {
|
|
191
|
+
if (
|
|
192
|
+
(dataSource == null || dataSource.length <= 0) &&
|
|
193
|
+
existingDataSource
|
|
194
|
+
) {
|
|
195
|
+
if (existingDataSource === "City List") return;
|
|
196
|
+
if (!resolve) return;
|
|
197
|
+
try {
|
|
198
|
+
const resolved = await resolve(existingDataSource, {
|
|
199
|
+
selectedState: context?.selectedState,
|
|
200
|
+
});
|
|
201
|
+
setOptions(resolved || []);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
context?.onError?.({
|
|
204
|
+
type: "resolve_data_source",
|
|
205
|
+
error,
|
|
206
|
+
metadata: { existingDataSource },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
fetchData();
|
|
212
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
213
|
+
}, [dataSource, existingDataSource]);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (existingDataSource === "City List") {
|
|
217
|
+
handleInputChange(null);
|
|
218
|
+
setOptions([]);
|
|
219
|
+
if (context?.selectedState && resolve) {
|
|
220
|
+
const fetchCities = async () => {
|
|
221
|
+
try {
|
|
222
|
+
const resolved = await resolve("City List", {
|
|
223
|
+
selectedState: context?.selectedState,
|
|
224
|
+
});
|
|
225
|
+
setOptions(resolved || []);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
context?.onError?.({
|
|
228
|
+
type: "resolve_data_source",
|
|
229
|
+
error,
|
|
230
|
+
metadata: { existingDataSource: "City List" },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
fetchCities();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
|
+
}, [context?.selectedState, existingDataSource]);
|
|
239
|
+
|
|
240
|
+
const selectedOption = formData?.[fieldId!]
|
|
241
|
+
? options.find((option: any) => option.value === formData?.[fieldId!])
|
|
242
|
+
: null;
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<Select
|
|
246
|
+
className={cx("body3", errorClass)}
|
|
247
|
+
controlClassName="px-4 [&>div]:p-0"
|
|
248
|
+
options={options}
|
|
249
|
+
value={selectedOption ?? undefined}
|
|
250
|
+
onChange={handleInputChange as any}
|
|
251
|
+
aria-label={args.displayName ?? "Select"}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const DatePickerField: React.FC<FieldRenderProps> = args => {
|
|
257
|
+
const { displayName, handleInputChange, errorClass, formData, fieldId } =
|
|
258
|
+
args;
|
|
259
|
+
return (
|
|
260
|
+
<div className="w-full">
|
|
261
|
+
<input
|
|
262
|
+
type="datetime-local"
|
|
263
|
+
aria-label={displayName}
|
|
264
|
+
value={formData?.[fieldId!] ?? ""}
|
|
265
|
+
onChange={e => handleInputChange(e.target.value)}
|
|
266
|
+
className={cx(
|
|
267
|
+
"w-full rounded-xl border border-input-border bg-white px-4 py-3 text-text outline-none focus:border-input-border-selected",
|
|
268
|
+
errorClass
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const RadioButtonInputField: React.FC<FieldRenderProps> = args => {
|
|
276
|
+
const {
|
|
277
|
+
requiredField,
|
|
278
|
+
formData,
|
|
279
|
+
handleInputChange,
|
|
280
|
+
fieldId,
|
|
281
|
+
hasError,
|
|
282
|
+
dataSource,
|
|
283
|
+
} = args;
|
|
284
|
+
return (
|
|
285
|
+
<div className="mt-6 flex flex-col gap-8 sm:flex-row">
|
|
286
|
+
{(dataSource || []).map((data: any, idx: number) => (
|
|
287
|
+
<div key={idx} className="w-full lg:w-[384px]">
|
|
288
|
+
<RadioButton
|
|
289
|
+
name={fieldId!}
|
|
290
|
+
label={data.label}
|
|
291
|
+
value={data.value}
|
|
292
|
+
checked={formData?.[fieldId!] === data.value}
|
|
293
|
+
hasError={hasError}
|
|
294
|
+
onChange={handleInputChange}
|
|
295
|
+
required={requiredField}
|
|
296
|
+
labelClassName="footnote"
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
))}
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const ImageuploadField: React.FC<FieldRenderProps> = args => {
|
|
305
|
+
const { handleInputChange, loading } = args;
|
|
306
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
307
|
+
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
|
|
308
|
+
const [dragActive, setDragActive] = useState(false);
|
|
309
|
+
const [error, setError] = useState<string | null>(null);
|
|
310
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
311
|
+
const context = useContext(FormContext);
|
|
312
|
+
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
const urls = files.map(file =>
|
|
315
|
+
file.type.startsWith("image/") ? URL.createObjectURL(file) : ""
|
|
316
|
+
);
|
|
317
|
+
setPreviewUrls(urls);
|
|
318
|
+
return () => {
|
|
319
|
+
urls.forEach(url => {
|
|
320
|
+
if (url) URL.revokeObjectURL(url);
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
}, [files]);
|
|
324
|
+
|
|
325
|
+
const openFileDialog = () => inputRef.current?.click();
|
|
326
|
+
const isMedia = (file: File) => file.type.startsWith("image/");
|
|
327
|
+
|
|
328
|
+
const validateTotalSize = (list: File[], maxBytes: number) => {
|
|
329
|
+
const total = list.reduce((sum, f) => sum + f.size, 0);
|
|
330
|
+
return { total, ok: total <= maxBytes };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const addFiles = (incoming: FileList | File[]) => {
|
|
334
|
+
const next: File[] = [];
|
|
335
|
+
if (incoming && incoming.length) {
|
|
336
|
+
const fileList = Array.from(incoming);
|
|
337
|
+
fileList.forEach(f => {
|
|
338
|
+
if (isMedia(f)) next.push(f);
|
|
339
|
+
});
|
|
340
|
+
if (next.length === 0) {
|
|
341
|
+
setError("Only image files are allowed.");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (files.length + next.length > 5) {
|
|
345
|
+
setError("You can only upload a maximum of 5 files.");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const combined = [...files, ...next];
|
|
349
|
+
if (!validateTotalSize(combined, 20 * 1024 * 1024).ok) {
|
|
350
|
+
setError("Total file size should not be more than 20 MB.");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
context?.setFiles(combined);
|
|
354
|
+
setFiles(combined);
|
|
355
|
+
handleInputChange(combined);
|
|
356
|
+
setError(null);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
361
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
362
|
+
addFiles(e.target.files);
|
|
363
|
+
e.target.value = "";
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const onDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
e.stopPropagation();
|
|
370
|
+
setDragActive(false);
|
|
371
|
+
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
372
|
+
addFiles(e.dataTransfer.files);
|
|
373
|
+
e.dataTransfer.clearData();
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const removeFile = (idx: number) => {
|
|
378
|
+
const copy = [...files];
|
|
379
|
+
copy.splice(idx, 1);
|
|
380
|
+
setFiles(copy);
|
|
381
|
+
context?.setFiles(copy);
|
|
382
|
+
handleInputChange(copy);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
<div>
|
|
387
|
+
<div
|
|
388
|
+
onDragOver={e => {
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
setDragActive(true);
|
|
392
|
+
}}
|
|
393
|
+
onDragLeave={e => {
|
|
394
|
+
e.preventDefault();
|
|
395
|
+
e.stopPropagation();
|
|
396
|
+
setDragActive(false);
|
|
397
|
+
}}
|
|
398
|
+
onDrop={onDrop}
|
|
399
|
+
className={cx(
|
|
400
|
+
"relative flex flex-col items-center justify-center rounded-xl border border-input-border bg-white px-4 py-10 text-center transition",
|
|
401
|
+
dragActive
|
|
402
|
+
? "border-input-border-selected bg-bg-surface-active"
|
|
403
|
+
: "hover:border-input-border-hover"
|
|
404
|
+
)}
|
|
405
|
+
>
|
|
406
|
+
<p className="mb-3 text-sm text-text-secondary">Drop files here or</p>
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
onClick={openFileDialog}
|
|
410
|
+
className="inline-flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-text shadow-sm ring-1 ring-inset ring-input-border hover:bg-bg-surface-hover"
|
|
411
|
+
>
|
|
412
|
+
<MaterialIcon name="upload" weight="700" size={24} />
|
|
413
|
+
Choose files
|
|
414
|
+
</button>
|
|
415
|
+
<input
|
|
416
|
+
ref={inputRef}
|
|
417
|
+
type="file"
|
|
418
|
+
accept="image/*"
|
|
419
|
+
multiple={true}
|
|
420
|
+
name="files"
|
|
421
|
+
onChange={onInputChange}
|
|
422
|
+
className="pointer-events-none absolute inset-0 h-0 w-0 opacity-0"
|
|
423
|
+
aria-hidden={true}
|
|
424
|
+
tabIndex={-1}
|
|
425
|
+
/>
|
|
426
|
+
{error && <p className="mt-2 text-sm text-text-critical">{error}</p>}
|
|
427
|
+
{files.length > 0 && (
|
|
428
|
+
<div className="mt-8">
|
|
429
|
+
<ul className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4">
|
|
430
|
+
{files.map((file, idx) => (
|
|
431
|
+
<li
|
|
432
|
+
key={idx}
|
|
433
|
+
className="group relative rounded-xl border border-input-border p-2"
|
|
434
|
+
>
|
|
435
|
+
{file.type.startsWith("image/") && previewUrls[idx] ? (
|
|
436
|
+
<img
|
|
437
|
+
src={previewUrls[idx]}
|
|
438
|
+
alt={file.name}
|
|
439
|
+
className="h-28 w-full rounded object-cover"
|
|
440
|
+
/>
|
|
441
|
+
) : (
|
|
442
|
+
<div className="h-28 w-full rounded bg-bg-surface-active" />
|
|
443
|
+
)}
|
|
444
|
+
<div className="mt-2 flex items-center justify-between">
|
|
445
|
+
<span
|
|
446
|
+
className="max-w-[50%] truncate text-sm text-text"
|
|
447
|
+
title={file.name}
|
|
448
|
+
>
|
|
449
|
+
{file.name}
|
|
450
|
+
</span>
|
|
451
|
+
{loading ? (
|
|
452
|
+
<button
|
|
453
|
+
type="button"
|
|
454
|
+
disabled={true}
|
|
455
|
+
className="cursor-default text-sm text-text-secondary"
|
|
456
|
+
>
|
|
457
|
+
Remove
|
|
458
|
+
</button>
|
|
459
|
+
) : (
|
|
460
|
+
<button
|
|
461
|
+
type="button"
|
|
462
|
+
onClick={() => removeFile(idx)}
|
|
463
|
+
className="text-sm text-text-critical hover:underline"
|
|
464
|
+
>
|
|
465
|
+
Remove
|
|
466
|
+
</button>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
</li>
|
|
470
|
+
))}
|
|
471
|
+
</ul>
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
export const FormFieldTypeMap: Record<string, React.FC<FieldRenderProps>> = {
|
|
480
|
+
Checkbox: CheckboxInputField,
|
|
481
|
+
Imageupload: ImageuploadField,
|
|
482
|
+
Datepicker: DatePickerField,
|
|
483
|
+
Dropdown: DropdownInputField,
|
|
484
|
+
Radio: RadioButtonInputField,
|
|
485
|
+
Text: TextInputField,
|
|
486
|
+
};
|