@engagebay/engagebay-form-module 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +126 -0
  2. package/link.sh +2 -0
  3. package/package.json +30 -0
  4. package/src/api/index.ts +25 -0
  5. package/src/form/Form.tsx +157 -0
  6. package/src/form/FormField.tsx +80 -0
  7. package/src/form/FormFieldUtils.ts +241 -0
  8. package/src/form/FormFields.tsx +41 -0
  9. package/src/form/context/FormContext.tsx +66 -0
  10. package/src/form/formfields/ArrayField.tsx +169 -0
  11. package/src/form/formfields/BusinessHoursField.tsx +204 -0
  12. package/src/form/formfields/CheckboxButtonsField.tsx +97 -0
  13. package/src/form/formfields/CheckboxField.tsx +118 -0
  14. package/src/form/formfields/ColorPickerField.tsx +59 -0
  15. package/src/form/formfields/ComboMultiSelect.tsx +290 -0
  16. package/src/form/formfields/ComboSelect.tsx +278 -0
  17. package/src/form/formfields/DatePickerField.tsx +89 -0
  18. package/src/form/formfields/DateRangePickerField.tsx +104 -0
  19. package/src/form/formfields/DynamicMultiSelect.tsx +189 -0
  20. package/src/form/formfields/DynamicSelect.tsx +187 -0
  21. package/src/form/formfields/Error.tsx +15 -0
  22. package/src/form/formfields/ErrorContextHandler.tsx +77 -0
  23. package/src/form/formfields/FileUploadField.tsx +196 -0
  24. package/src/form/formfields/IframeField.tsx +65 -0
  25. package/src/form/formfields/InputField.tsx +67 -0
  26. package/src/form/formfields/InputGroupField.tsx +44 -0
  27. package/src/form/formfields/MultipleSelectField.tsx +98 -0
  28. package/src/form/formfields/NumberField.tsx +61 -0
  29. package/src/form/formfields/PasswordField.tsx +93 -0
  30. package/src/form/formfields/PhoneNumberField.tsx +163 -0
  31. package/src/form/formfields/RadioField.tsx +104 -0
  32. package/src/form/formfields/RadioGroupComponent.tsx +94 -0
  33. package/src/form/formfields/RangeField.tsx +53 -0
  34. package/src/form/formfields/SelectField.tsx +82 -0
  35. package/src/form/formfields/SwitchField.tsx +131 -0
  36. package/src/form/formfields/TextAreaField.tsx +48 -0
  37. package/src/form/formfields/TimeField.tsx +53 -0
  38. package/src/form/formfields/Typeahead.tsx +211 -0
  39. package/src/form/formfields/TypeaheadMultiSelect.tsx +203 -0
  40. package/src/form/formfields/UrlField.tsx +53 -0
  41. package/src/form/hooks/useDynamicReducer.tsx +42 -0
  42. package/src/form/schema/CustomValidators.ts +63 -0
  43. package/src/form/schema/FormFieldSchema.ts +342 -0
  44. package/src/form/util/RenderFormField.tsx +149 -0
  45. package/src/form/util/RenderListOptions.tsx +424 -0
  46. package/src/form/util/index.ts +185 -0
  47. package/src/util/LoaderWithText.tsx +28 -0
  48. package/src/util/svg/HELPER_ICONS.ts +16 -0
  49. package/src/util/svg/SVGIcon.tsx +23 -0
  50. package/tsconfig.json +25 -0
@@ -0,0 +1,149 @@
1
+ import { Description, Field, Label } from "@headlessui/react";
2
+ import React from "react";
3
+ import { FieldAlignType, FormFieldSchema } from "../schema/FormFieldSchema";
4
+ import ErrorContextHandler from "../formfields/ErrorContextHandler";
5
+ import SVGIcon from "../../util/svg/SVGIcon";
6
+ /**
7
+ * RenderFormField Component
8
+ *
9
+ * This component renders a form field based on the configuration provided in `fieldConfig`.
10
+ * It supports different layouts (horizontal or vertical) and can be customized with optional
11
+ * classes, labels, and error handling.
12
+ *
13
+ * @param {Object} props - The properties for the RenderFormField component.
14
+ * @param {FormFieldSchema} props.fieldConfig - The configuration object for the form field. This object can include:
15
+ * @param {string} [props.fieldConfig.name] - The name of the form field.
16
+ * @param {string} [props.fieldConfig.label] - The label text for the form field.
17
+ * @param {boolean} [props.fieldConfig.required] - Whether the field is required.
18
+ * @param {boolean} [props.fieldConfig.disabled] - Whether the field is disabled.
19
+ * @param {string} [props.fieldConfig.align] - The alignment type of the form field. Can be `FieldAlignType.HORIZONTAL` or `FieldAlignType.VERTICAL`.
20
+ * @param {string} [props.fieldConfig.helpText] - The help text for the form field.
21
+ * @param {Object} [props.fieldConfig.customClassNames] - An object containing custom class names for different parts of the field:
22
+ * @param {string} [props.fieldConfig.customClassNames.wrapperClassName] - Custom class for the wrapper element.
23
+ * @param {string} [props.fieldConfig.customClassNames.labelClassName] - Custom class for the label element.
24
+ * @param {boolean} [props.fieldConfig.disableDefaultWrapper] - If true, the default wrapper will be disabled.
25
+ * @param {React.ElementType} [props.fieldConfig.wrapper] - Custom wrapper component if `disableDefaultWrapper` is true.
26
+ * @param {boolean} [props.fieldConfig.submitOnChange] - If true, the form will submit on field change.
27
+ *
28
+ * @param {Function} props.getInput - A function that returns the input element to be rendered.
29
+ *
30
+ * @returns {JSX.Element} The rendered form field component.
31
+ *
32
+ * @example
33
+ * // Example usage:
34
+ * const fieldConfig = {
35
+ * name: "username",
36
+ * label: "Username",
37
+ * required: true,
38
+ * align: FieldAlignType.HORIZONTAL,
39
+ * customClassNames: {
40
+ * wrapperClassName: "my-custom-wrapper",
41
+ * labelClassName: "my-custom-label"
42
+ * },
43
+ * helpText: "Please enter your username.",
44
+ * };
45
+ *
46
+ * const getInput = () => <input type="text" name="username" />;
47
+ *
48
+ * <RenderFormField fieldConfig={fieldConfig} getInput={getInput} />;
49
+ */
50
+ const RenderFormField = ({
51
+ fieldConfig,
52
+ getInput,
53
+ }: {
54
+ fieldConfig: FormFieldSchema;
55
+ getInput: Function;
56
+ }): JSX.Element => {
57
+ function formTypeHorizontal() {
58
+ return (
59
+ <Field
60
+ className={`${
61
+ fieldConfig.customClassNames?.wrapperClassName ??
62
+ "form-group flex sm:flex-row flex-col"
63
+ }`}
64
+ disabled={fieldConfig.disabled}>
65
+ {fieldConfig.label && (
66
+ <Label
67
+ htmlFor={fieldConfig.name}
68
+ className={`${
69
+ fieldConfig.customClassNames?.labelClassName ??
70
+ "form-label sm:w-1/4 sm:ltr:mr-2 rtl:ml-2"
71
+ }`}>
72
+ {fieldConfig.label}
73
+ {fieldConfig.required && (
74
+ <span className="field-required">
75
+ <SVGIcon name="starIcon" height={6} width={6} />
76
+ </span>
77
+ )}
78
+ </Label>
79
+ )}
80
+ {getInput()}
81
+
82
+ <ErrorContextHandler fieldConfig={fieldConfig} />
83
+
84
+ {fieldConfig && fieldConfig.helpText && (
85
+ <Description className="mt-2 text-tiny text-gray-500">
86
+ {fieldConfig.helpText}
87
+ </Description>
88
+ )}
89
+ </Field>
90
+ );
91
+ }
92
+
93
+ function formTypeVertical() {
94
+ return (
95
+ <Field
96
+ disabled={fieldConfig.disabled}
97
+ className={`${
98
+ fieldConfig.customClassNames?.wrapperClassName ??
99
+ "form-group"
100
+ }`}>
101
+ {fieldConfig.label && (
102
+ <Label
103
+ htmlFor={fieldConfig.name}
104
+ className={`${
105
+ fieldConfig.customClassNames?.labelClassName ??
106
+ "form-label"
107
+ }`}>
108
+ {fieldConfig.label}
109
+ {fieldConfig.required && (
110
+ <span className="field-required">
111
+ <SVGIcon name="starIcon" height={6} width={6} />
112
+ </span>
113
+ )}
114
+ </Label>
115
+ )}
116
+
117
+ {getInput()}
118
+
119
+ <ErrorContextHandler fieldConfig={fieldConfig} />
120
+
121
+ {fieldConfig && fieldConfig.helpText && (
122
+ <Description className="mt-2 text-tiny text-gray-500">
123
+ {fieldConfig.helpText}
124
+ </Description>
125
+ )}
126
+ </Field>
127
+ );
128
+ }
129
+
130
+ return fieldConfig.disableDefaultWrapper ? (
131
+ fieldConfig.wrapper ? (
132
+ <fieldConfig.wrapper>
133
+ {getInput()}
134
+ <ErrorContextHandler fieldConfig={fieldConfig} />
135
+ </fieldConfig.wrapper>
136
+ ) : (
137
+ <>
138
+ {getInput()}
139
+ <ErrorContextHandler fieldConfig={fieldConfig} />
140
+ </>
141
+ )
142
+ ) : fieldConfig.align === FieldAlignType.HORIZONTAL ? (
143
+ formTypeHorizontal()
144
+ ) : (
145
+ formTypeVertical()
146
+ );
147
+ };
148
+
149
+ export default RenderFormField;
@@ -0,0 +1,424 @@
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ } from "react";
8
+
9
+ import { ListboxOption, ListboxOptions } from "@headlessui/react";
10
+ import { handleChange } from ".";
11
+ import { LoaderWithText } from "../../util/LoaderWithText";
12
+ import SVGIcon from "../../util/svg/SVGIcon";
13
+ import { FormContextType } from "../context/FormContext";
14
+ import {
15
+ FieldOptionsSchema,
16
+ FormFieldSchema,
17
+ FormFieldType,
18
+ OutputFormatType,
19
+ } from "../schema/FormFieldSchema";
20
+
21
+ type RenderListOptionsProps = {
22
+ formContext: FormContextType;
23
+ fieldConfig: FormFieldSchema;
24
+ onChange?: (value: any) => void;
25
+ listOptions: FieldOptionsSchema[];
26
+ setListOptions: (value: any) => void;
27
+ loading?: boolean;
28
+ formField: FormFieldType;
29
+ setLoading?: React.Dispatch<React.SetStateAction<boolean>>;
30
+ createCallback?: (value: any) => void;
31
+ queryCallback?: (value: any) => void;
32
+ };
33
+
34
+ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
35
+ (
36
+ {
37
+ formContext,
38
+ fieldConfig,
39
+ listOptions,
40
+ setListOptions,
41
+ setLoading,
42
+ loading,
43
+ formField,
44
+ onChange,
45
+ queryCallback,
46
+ createCallback,
47
+ },
48
+ ref
49
+ ) => {
50
+ const [query, setQuery] = useState<string>("");
51
+ const [createdListItems, setCreatedListItems] = useState<
52
+ FieldOptionsSchema[]
53
+ >([]);
54
+ // const inputRef = useRef<HTMLInputElement>(null);
55
+ const createItem = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
56
+ if (fieldConfig.onCreateTrigger && createCallback) {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ fieldConfig.onCreateTrigger(query, createCallback);
60
+ return;
61
+ }
62
+
63
+ setLoading && setLoading(true);
64
+ try {
65
+ if (!fieldConfig.postUrl) {
66
+ const data: FieldOptionsSchema = {
67
+ label: query,
68
+ value: query,
69
+ };
70
+ setCreatedListItems((prevList) => [...prevList, data]);
71
+ }
72
+ } catch (e: any) {
73
+ console.error("Error in Creating the object ", e);
74
+ } finally {
75
+ resetToDefault();
76
+ setLoading && setLoading(false);
77
+ }
78
+ };
79
+ const resetToDefault = () => {
80
+ if (query != "") {
81
+ setQuery("");
82
+ if (isTypeahead) {
83
+ queryCallback && queryCallback("");
84
+ }
85
+ }
86
+ };
87
+ let resultList = [...createdListItems, ...listOptions];
88
+ resultList = [...resultList.filter((result) => result.label).filter((option, index, self) => index === self.findIndex((obj) => obj.value === option.value))]; // removing duplicate values
89
+
90
+ const caseSensitive =
91
+ query && fieldConfig.dropdownFieldConfig?.isCaseSensitive
92
+ ? query
93
+ : query?.toLowerCase();
94
+
95
+ const filteredList = query
96
+ ? resultList.filter((item) => {
97
+ const normalizedLabel = fieldConfig.dropdownFieldConfig
98
+ ?.isCaseSensitive
99
+ ? item.label
100
+ : item.label.toLowerCase();
101
+
102
+ return normalizedLabel
103
+ .replace(/\s+/g, "")
104
+ .includes(caseSensitive.replace(/\s+/g, ""));
105
+ })
106
+ : resultList;
107
+
108
+ let nullGroupOptions: any[] = [];
109
+ let groupedOptions: any = filteredList.reduce((acc: any, option: any) => {
110
+ if (!option.groupName) {
111
+ nullGroupOptions.push(option);
112
+ } else {
113
+ if (!acc[option.groupName]) {
114
+ acc[option.groupName] = [];
115
+ }
116
+ acc[option.groupName].push(option);
117
+ }
118
+ return acc;
119
+ }, {});
120
+
121
+ const handleQueryCallback = useCallback(() => {
122
+ if (filteredList.length == 0 && isTypeahead) {
123
+ queryCallback && queryCallback(query);
124
+ }
125
+ }, [filteredList]);
126
+
127
+ useEffect(() => {
128
+ handleQueryCallback();
129
+ }, [query]);
130
+
131
+ let enableCreateFlag =
132
+ formField != FormFieldType.SELECT &&
133
+ formField != FormFieldType.MULTI_SELECT &&
134
+ fieldConfig.dropdownFieldConfig?.showCreateOption;
135
+
136
+ let validTypeaheadFields = [
137
+ FormFieldType.TYPEAHEAD,
138
+ FormFieldType.TYPEAHEAD_MULTI_SELECT,
139
+ ];
140
+ let isTypeahead: boolean = validTypeaheadFields.indexOf(formField) > -1;
141
+ let isNotMatched: boolean =
142
+ query != "" && filteredList.findIndex((v) => v.label === query) === -1;
143
+
144
+ const renderOption = (option: FieldOptionsSchema) => {
145
+ let selected = false;
146
+ if (Array.isArray(formContext.getValues(fieldConfig.name))) {
147
+ selected = formContext
148
+ .getValues(fieldConfig.name)
149
+ .includes(option.value);
150
+ } else {
151
+ const formValue = formContext.getValues(fieldConfig.name);
152
+ // const defaultValue = fieldConfig.defaultValue;
153
+ // selected = formValue ? option.value == formValue : defaultValue == option.value;
154
+ selected = option.value == formValue;
155
+ }
156
+ return (
157
+ <ListboxOption
158
+ key={option.value}
159
+ disabled={option.isDisabled}
160
+ onClick={() => setTimeout(resetToDefault, 300)}
161
+ className={`form-listbox-option ${
162
+ selected ? " bg-gray-100 text-gray-900" : "bg-white text-gray-700"
163
+ }`}
164
+ value={option.value}
165
+ >
166
+ {fieldConfig.fieldOptionWrapper ? (
167
+ <fieldConfig.fieldOptionWrapper data={option} selected={selected} />
168
+ ) : (
169
+ <>
170
+ <div className="flex items-center justify-between gap-x-1.5">
171
+ <span
172
+ className={`block truncate w-full !max-w-[150px] space-x-2 ${
173
+ selected ? "font-medium" : "font-normal"
174
+ }`}
175
+ >
176
+ {option.icon && (
177
+ <span className="listbox-svg">{option.icon}</span>
178
+ )}
179
+ {option.label}
180
+ </span>
181
+ {isTypeahead ? (
182
+ <input
183
+ type="checkbox"
184
+ className="form-checkbox"
185
+ checked={selected}
186
+ />
187
+ ) : (
188
+ selected && (
189
+ <SVGIcon
190
+ name="checkIcon"
191
+ className="h-5 w-5"
192
+ aria-hidden="true"
193
+ />
194
+ )
195
+ )}
196
+ </div>
197
+ {option.helpText && (
198
+ <div className="mt-0 text-sm text-gray-500 font-normal truncate w-full !max-w-[150px]">
199
+ {option.helpText}
200
+ </div>
201
+ )}
202
+ </>
203
+ )}
204
+ </ListboxOption>
205
+ );
206
+ };
207
+
208
+ const renderList = useMemo(() => {
209
+ if (filteredList.length === 0 && (!enableCreateFlag || query === ""))
210
+ return (
211
+ <div className="form-listbox-option text-center">
212
+ <span className="empty-content text-gray-600 font-normal">
213
+ No Data Available
214
+ </span>
215
+ </div>
216
+ );
217
+
218
+ return (
219
+ <>
220
+ {nullGroupOptions.length > 0 && nullGroupOptions.map(renderOption)}
221
+
222
+ {query !== "" && enableCreateFlag && isNotMatched && (
223
+ <ListboxOption
224
+ key="create_new"
225
+ className="form-combobox-option"
226
+ value={query}
227
+ onClick={createItem}
228
+ >
229
+ <div className="flex items-center justify-between">
230
+ <span className="truncate overflow-x-auto">{query}</span>
231
+ <span className="text-primary flex items-center gap-x-1 cursor-pointer">
232
+ <SVGIcon name="plus" className="h-3 w-3" />
233
+ {fieldConfig.dropdownFieldConfig?.createLabelText || "Select"}
234
+ </span>
235
+ </div>
236
+ </ListboxOption>
237
+ )}
238
+ </>
239
+ );
240
+ }, [filteredList, nullGroupOptions]);
241
+
242
+ return (
243
+ <ListboxOptions
244
+ anchor="bottom start"
245
+ transition
246
+ portal={false}
247
+ modal={false}
248
+ onBlur={() => setTimeout(resetToDefault, 300)}
249
+ className={`form-listbox-options ${fieldConfig?.customClassNames?.optionsWrapperClassName}`}
250
+ >
251
+ {fieldConfig.dropdownFieldConfig?.showSearchBox && (
252
+ <div className="relative">
253
+ <input
254
+ onKeyDown={(e: any) => e.stopPropagation()}
255
+ onChange={(event) => {
256
+ setQuery(event.target.value);
257
+ if (event.target.value == "" && isTypeahead) {
258
+ queryCallback && queryCallback("");
259
+ }
260
+ }}
261
+ className="form-input !pr-[36px] Search-field"
262
+ placeholder="Search"
263
+ value={query}
264
+ autoFocus
265
+ type="text"
266
+ />
267
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center mr-3">
268
+ <svg
269
+ xmlns="http://www.w3.org/2000/svg"
270
+ viewBox="0 0 16 16"
271
+ fill="currentColor"
272
+ aria-hidden="true"
273
+ data-slot="icon"
274
+ className="h-5 w-5 text-gray-400"
275
+ >
276
+ <path
277
+ fillRule="evenodd"
278
+ d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"
279
+ clipRule="evenodd"
280
+ ></path>
281
+ </svg>
282
+ </div>
283
+ </div>
284
+ )}
285
+
286
+ <div className="form-listbox-options-container">
287
+ {loading ? (
288
+ <div className="form-listbox-option">
289
+ <div className="flex items-center justify-center p-3">
290
+ <LoaderWithText />
291
+ </div>
292
+ </div>
293
+ ) : (
294
+ renderList
295
+ )}
296
+
297
+ {Object.keys(groupedOptions).length > 0 &&
298
+ Object.keys(groupedOptions).map((groupName) => (
299
+ <div key={groupName}>
300
+ <h2 className="pb-1 pt-2 font-bold">{groupName}</h2>
301
+ {groupedOptions[groupName].map(renderOption)}
302
+ </div>
303
+ ))}
304
+ </div>
305
+
306
+
307
+ </ListboxOptions>
308
+ );
309
+ }
310
+ );
311
+
312
+ export default RenderListOptions;
313
+ export function renderListBoxValue(
314
+ formContext: FormContextType,
315
+ fieldConfig: FormFieldSchema,
316
+ listOptions: FieldOptionsSchema[],
317
+ onChange?: (value: any) => void
318
+ ): JSX.Element {
319
+ let value = formContext.getValues(fieldConfig.name);
320
+ const renderAsString = () => {
321
+ // if (!listOptions) {
322
+ // return value;
323
+ // }
324
+ let icon = listOptions.find((option) => option.value == value)?.icon,
325
+ label = listOptions.find((option) => option.value == value)?.label;
326
+ return icon ? (
327
+ <span className="flex items-center fs-8 whitespace-nowrap text-gray-500">
328
+ <span>{icon}</span>
329
+ <span>{label} </span>
330
+ </span>
331
+ ) : (
332
+ label || getPlaceholder()
333
+ );
334
+ };
335
+ const renderAsArray = () => {
336
+ if (value.length == 0 || (value.length == 1 && value[0] == "")) {
337
+ return getPlaceholder();
338
+ }
339
+ // if (!listOptions) {
340
+ // return value;
341
+ // }
342
+ const values = value;
343
+ return fieldConfig.dropdownFieldConfig?.showSelectedCount ? (
344
+ <span className="form-selected-badge">
345
+ <span className="form-selected-badge-name">
346
+ {values.length > 1
347
+ ? `${values.length} selected`
348
+ : listOptions.find((option) => option.value == value[0])?.label ||
349
+ values[0]}
350
+ </span>
351
+ {getDeleteButton()}
352
+ </span>
353
+ ) : (
354
+ Array.isArray(values) &&
355
+ values.map((opt: any) => {
356
+ const option = listOptions.find((option) => option.value == opt);
357
+ if (!option && values.length == 0) return getPlaceholder();
358
+
359
+ return (
360
+ <span key={option?.value} className="form-selected-badge">
361
+ <span className="form-selected-badge-name">{option?.label}</span>
362
+ {getDeleteButton(opt)}
363
+ </span>
364
+ );
365
+ })
366
+ );
367
+ };
368
+ const getDeleteButton = (option?: string) => {
369
+ return (
370
+ fieldConfig.dropdownFieldConfig?.showDeleteOption && (
371
+ <button
372
+ type="button"
373
+ className="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-gray-500/20"
374
+ onClick={(e) => {
375
+ e.preventDefault();
376
+ if (fieldConfig.dropdownFieldConfig?.showSelectedCount) {
377
+ handleChange(null, formContext, fieldConfig, onChange);
378
+ } else {
379
+ let options: string[] = value as string[];
380
+ options = options.filter((i) => i != option);
381
+ handleChange(options, formContext, fieldConfig, onChange);
382
+ }
383
+ }}
384
+ >
385
+ <span className="sr-only">Remove</span>
386
+ <svg
387
+ viewBox="0 0 14 14"
388
+ className="h-3.5 w-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75"
389
+ >
390
+ <path d="M4 4l6 6m0-6l-6 6" />
391
+ </svg>
392
+ <span className="absolute -inset-1" />
393
+ </button>
394
+ )
395
+ );
396
+ };
397
+ let outputFormat = fieldConfig.outputFormat
398
+ ? fieldConfig.outputFormat === OutputFormatType.ARRAY
399
+ : false;
400
+ const getPlaceholder = () => (
401
+ <span className="form-placeholder">
402
+ {fieldConfig.placeholder || "Select any option"}
403
+ </span>
404
+ );
405
+ const renderValue = () => {
406
+ if (!value && !listOptions) {
407
+ return getPlaceholder();
408
+ }
409
+ if (!outputFormat && Array.isArray(value)) {
410
+ return renderAsArray();
411
+ }
412
+ return renderAsString();
413
+ };
414
+ return (
415
+ <>
416
+ <span className="form-selected-option w-full">{renderValue()}</span>
417
+ <SVGIcon
418
+ name="chevronDown"
419
+ className="h-5 w-5 text-gray-400"
420
+ aria-hidden="true"
421
+ />
422
+ </>
423
+ );
424
+ }