@windstream/react-shared-components 0.2.54 → 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/blocks/primary-hero/index.test.tsx +28 -3
- package/src/contentful/blocks/primary-hero/index.tsx +5 -1
- package/src/contentful/index.ts +20 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FormContext, FormContextType } from "./context";
|
|
3
|
+
import FormFieldElement, { FormFieldElementProps } from "./form-field";
|
|
4
|
+
import { FieldRenderProps } from "./types";
|
|
5
|
+
|
|
6
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
7
|
+
|
|
8
|
+
const makeContext = (
|
|
9
|
+
overrides: Partial<FormContextType> = {}
|
|
10
|
+
): FormContextType => ({
|
|
11
|
+
fieldErrors: {},
|
|
12
|
+
setFieldError: jest.fn(),
|
|
13
|
+
clearFieldError: jest.fn(),
|
|
14
|
+
hasError: false,
|
|
15
|
+
setHasError: jest.fn(),
|
|
16
|
+
selectedState: null,
|
|
17
|
+
setSelectedState: jest.fn(),
|
|
18
|
+
files: [],
|
|
19
|
+
setFiles: jest.fn(),
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type FieldProps = Partial<FormFieldElementProps> & {
|
|
24
|
+
fieldId: string;
|
|
25
|
+
fieldType: FormFieldElementProps["fieldType"];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders a FormFieldElement whose inner field component is swapped for a
|
|
30
|
+
* button so the field's `handleInputChange` can be invoked with a controlled
|
|
31
|
+
* event, exercising every branch of the change handler deterministically.
|
|
32
|
+
*/
|
|
33
|
+
const setup = (opts: {
|
|
34
|
+
field: FieldProps;
|
|
35
|
+
ctx?: Partial<FormContextType>;
|
|
36
|
+
event?: any;
|
|
37
|
+
}) => {
|
|
38
|
+
const setFormData = jest.fn();
|
|
39
|
+
const Trigger: React.FC<FieldRenderProps> = props => (
|
|
40
|
+
<button
|
|
41
|
+
data-testid="trigger"
|
|
42
|
+
onClick={() => props.handleInputChange(opts.event)}
|
|
43
|
+
>
|
|
44
|
+
go
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
const context = makeContext({
|
|
48
|
+
...opts.ctx,
|
|
49
|
+
fieldComponentOverrides: { [opts.field.fieldType]: Trigger },
|
|
50
|
+
});
|
|
51
|
+
const utils = render(
|
|
52
|
+
<FormContext.Provider value={context}>
|
|
53
|
+
<FormFieldElement
|
|
54
|
+
formData={{}}
|
|
55
|
+
setFormData={setFormData}
|
|
56
|
+
submitErrorFields={[]}
|
|
57
|
+
{...(opts.field as FormFieldElementProps)}
|
|
58
|
+
/>
|
|
59
|
+
</FormContext.Provider>
|
|
60
|
+
);
|
|
61
|
+
return { ...utils, setFormData, context };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const trigger = () => fireEvent.click(screen.getByTestId("trigger"));
|
|
65
|
+
|
|
66
|
+
describe("FormFieldElement", () => {
|
|
67
|
+
it("renders nothing for an unknown field type", () => {
|
|
68
|
+
const { container } = render(
|
|
69
|
+
<FormFieldElement
|
|
70
|
+
formData={{}}
|
|
71
|
+
setFormData={jest.fn()}
|
|
72
|
+
submitErrorFields={[]}
|
|
73
|
+
fieldId="x"
|
|
74
|
+
fieldType={"Nope" as any}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
expect(container).toBeEmptyDOMElement();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("renders a display label for a standard field", () => {
|
|
81
|
+
render(
|
|
82
|
+
<FormFieldElement
|
|
83
|
+
formData={{}}
|
|
84
|
+
setFormData={jest.fn()}
|
|
85
|
+
submitErrorFields={[]}
|
|
86
|
+
fieldId="firstName"
|
|
87
|
+
fieldType="Text"
|
|
88
|
+
displayName="First Name"
|
|
89
|
+
requiredField
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
expect(screen.getByText("First Name")).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders the grouping instruction label for a grouped validation field", () => {
|
|
96
|
+
render(
|
|
97
|
+
<FormFieldElement
|
|
98
|
+
formData={{}}
|
|
99
|
+
setFormData={jest.fn()}
|
|
100
|
+
submitErrorFields={[]}
|
|
101
|
+
fieldId="g1"
|
|
102
|
+
fieldType="Text"
|
|
103
|
+
displayName="Group Field"
|
|
104
|
+
validationGroupId="grp"
|
|
105
|
+
validationType="At least One Required"
|
|
106
|
+
validationNote="Provide one of these"
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
expect(screen.getByText("Provide one of these")).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("renders a note when provided", () => {
|
|
113
|
+
render(
|
|
114
|
+
<FormFieldElement
|
|
115
|
+
formData={{}}
|
|
116
|
+
setFormData={jest.fn()}
|
|
117
|
+
submitErrorFields={[]}
|
|
118
|
+
fieldId="firstName"
|
|
119
|
+
fieldType="Text"
|
|
120
|
+
displayName="First Name"
|
|
121
|
+
note="Helpful hint"
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
expect(screen.getByText("Helpful hint")).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("applies an error border class for fields in the submit-error set", () => {
|
|
128
|
+
const { container } = render(
|
|
129
|
+
<FormContext.Provider value={makeContext()}>
|
|
130
|
+
<FormFieldElement
|
|
131
|
+
formData={{}}
|
|
132
|
+
setFormData={jest.fn()}
|
|
133
|
+
submitErrorFields={["Dropdown"]}
|
|
134
|
+
fieldId="Dropdown"
|
|
135
|
+
fieldType="Text"
|
|
136
|
+
displayName="Dropdown"
|
|
137
|
+
/>
|
|
138
|
+
</FormContext.Provider>
|
|
139
|
+
);
|
|
140
|
+
expect(container.querySelector(".rounded-xl")).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("handleInputChange", () => {
|
|
144
|
+
it("stores the raw value for a Checkbox field", () => {
|
|
145
|
+
const { setFormData, context } = setup({
|
|
146
|
+
field: { fieldId: "f1", fieldType: "Checkbox", dataSource: [] },
|
|
147
|
+
event: "a,b",
|
|
148
|
+
});
|
|
149
|
+
trigger();
|
|
150
|
+
expect(context.clearFieldError).toHaveBeenCalledWith("f1");
|
|
151
|
+
expect(context.setHasError).toHaveBeenCalledWith(false);
|
|
152
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({ f1: "a,b" });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("stores the value for a Radio field", () => {
|
|
156
|
+
const { setFormData } = setup({
|
|
157
|
+
field: { fieldId: "f1", fieldType: "Radio" },
|
|
158
|
+
event: "yes",
|
|
159
|
+
});
|
|
160
|
+
trigger();
|
|
161
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({ f1: "yes" });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("stores the option value for a Dropdown field", () => {
|
|
165
|
+
const { setFormData, context } = setup({
|
|
166
|
+
field: { fieldId: "f1", fieldType: "Dropdown" },
|
|
167
|
+
event: { value: "x" },
|
|
168
|
+
});
|
|
169
|
+
trigger();
|
|
170
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({ f1: "x" });
|
|
171
|
+
expect(context.setSelectedState).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("tracks the selected state for a State List Dropdown", () => {
|
|
175
|
+
const { context } = setup({
|
|
176
|
+
field: {
|
|
177
|
+
fieldId: "state",
|
|
178
|
+
fieldType: "Dropdown",
|
|
179
|
+
existingDataSource: "State List",
|
|
180
|
+
},
|
|
181
|
+
event: { value: "TX" },
|
|
182
|
+
});
|
|
183
|
+
trigger();
|
|
184
|
+
expect(context.setSelectedState).toHaveBeenCalledWith({ value: "TX" });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("stores the value for a Datepicker field", () => {
|
|
188
|
+
const { setFormData } = setup({
|
|
189
|
+
field: { fieldId: "when", fieldType: "Datepicker" },
|
|
190
|
+
event: "2026-01-01T09:00",
|
|
191
|
+
});
|
|
192
|
+
trigger();
|
|
193
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({
|
|
194
|
+
when: "2026-01-01T09:00",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("raises an error for an empty Datepicker value", () => {
|
|
199
|
+
const { context } = setup({
|
|
200
|
+
field: {
|
|
201
|
+
fieldId: "when",
|
|
202
|
+
fieldType: "Datepicker",
|
|
203
|
+
errorMessage: "Pick a date",
|
|
204
|
+
},
|
|
205
|
+
event: "",
|
|
206
|
+
});
|
|
207
|
+
trigger();
|
|
208
|
+
expect(context.setHasError).toHaveBeenCalledWith(true);
|
|
209
|
+
expect(context.setFieldError).toHaveBeenCalledWith("when", "Pick a date");
|
|
210
|
+
expect(screen.getByText("Pick a date")).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("stores uploaded files under the 'files' key for Imageupload", () => {
|
|
214
|
+
const files = [new File(["x"], "a.png", { type: "image/png" })];
|
|
215
|
+
const { setFormData } = setup({
|
|
216
|
+
field: { fieldId: "img", fieldType: "Imageupload" },
|
|
217
|
+
event: files,
|
|
218
|
+
});
|
|
219
|
+
trigger();
|
|
220
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({ files });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("stores an empty text value and skips further validation", () => {
|
|
224
|
+
const { setFormData } = setup({
|
|
225
|
+
field: { fieldId: "f1", fieldType: "Text" },
|
|
226
|
+
event: { target: { name: "f1", value: "" } },
|
|
227
|
+
});
|
|
228
|
+
trigger();
|
|
229
|
+
expect(setFormData.mock.calls[0][0]({})).toEqual({ f1: "" });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("formats a phone value and flags an incomplete number", () => {
|
|
233
|
+
const { context } = setup({
|
|
234
|
+
field: {
|
|
235
|
+
fieldId: "phone",
|
|
236
|
+
fieldType: "Text",
|
|
237
|
+
textInputType: "phone",
|
|
238
|
+
errorMessage: "Bad phone",
|
|
239
|
+
},
|
|
240
|
+
event: { target: { name: "phone", value: "123" } },
|
|
241
|
+
});
|
|
242
|
+
trigger();
|
|
243
|
+
expect(context.setHasError).toHaveBeenCalledWith(true);
|
|
244
|
+
expect(context.setFieldError).toHaveBeenCalledWith("phone", "Bad phone");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("accepts a complete phone number without flagging an error", () => {
|
|
248
|
+
const { context } = setup({
|
|
249
|
+
field: {
|
|
250
|
+
fieldId: "phone",
|
|
251
|
+
fieldType: "Text",
|
|
252
|
+
textInputType: "phone",
|
|
253
|
+
errorMessage: "Bad phone",
|
|
254
|
+
},
|
|
255
|
+
event: { target: { name: "phone", value: "1234567890" } },
|
|
256
|
+
});
|
|
257
|
+
trigger();
|
|
258
|
+
expect(context.setFieldError).not.toHaveBeenCalled();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("flags an invalid email value", () => {
|
|
262
|
+
const { context } = setup({
|
|
263
|
+
field: {
|
|
264
|
+
fieldId: "email",
|
|
265
|
+
fieldType: "Text",
|
|
266
|
+
textInputType: "email",
|
|
267
|
+
errorMessage: "Bad email",
|
|
268
|
+
},
|
|
269
|
+
event: { target: { name: "email", value: "not-an-email" } },
|
|
270
|
+
});
|
|
271
|
+
trigger();
|
|
272
|
+
expect(context.setFieldError).toHaveBeenCalledWith("email", "Bad email");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("accepts a valid email value", () => {
|
|
276
|
+
const { context } = setup({
|
|
277
|
+
field: {
|
|
278
|
+
fieldId: "email",
|
|
279
|
+
fieldType: "Text",
|
|
280
|
+
textInputType: "email",
|
|
281
|
+
errorMessage: "Bad email",
|
|
282
|
+
},
|
|
283
|
+
event: { target: { name: "email", value: "user@example.com" } },
|
|
284
|
+
});
|
|
285
|
+
trigger();
|
|
286
|
+
expect(context.setFieldError).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("flags a value that fails a custom regular expression", () => {
|
|
290
|
+
const { context } = setup({
|
|
291
|
+
field: {
|
|
292
|
+
fieldId: "code",
|
|
293
|
+
fieldType: "Text",
|
|
294
|
+
applyRegularExpression: true,
|
|
295
|
+
regexPattern: "^[0-9]+$",
|
|
296
|
+
errorMessage: "Digits only",
|
|
297
|
+
},
|
|
298
|
+
event: { target: { name: "code", value: "abc" } },
|
|
299
|
+
});
|
|
300
|
+
trigger();
|
|
301
|
+
expect(context.setFieldError).toHaveBeenCalledWith("code", "Digits only");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("accepts a value that matches a custom regular expression", () => {
|
|
305
|
+
const { context } = setup({
|
|
306
|
+
field: {
|
|
307
|
+
fieldId: "code",
|
|
308
|
+
fieldType: "Text",
|
|
309
|
+
applyRegularExpression: true,
|
|
310
|
+
regexPattern: "^[0-9]+$",
|
|
311
|
+
errorMessage: "Digits only",
|
|
312
|
+
},
|
|
313
|
+
event: { target: { name: "code", value: "123" } },
|
|
314
|
+
});
|
|
315
|
+
trigger();
|
|
316
|
+
expect(context.setFieldError).not.toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useContext, useState } from "react";
|
|
4
|
+
import { constants, Regex } from "./constants";
|
|
5
|
+
import { FormContext } from "./context";
|
|
6
|
+
import {
|
|
7
|
+
formatFormPhoneNumber,
|
|
8
|
+
FormFieldTypeMap,
|
|
9
|
+
labelCreator,
|
|
10
|
+
} from "./field-map";
|
|
11
|
+
import { DynamicFormFieldConfig, FieldRenderProps } from "./types";
|
|
12
|
+
|
|
13
|
+
import { Text } from "@shared/components/text";
|
|
14
|
+
import { cx } from "@shared/utils";
|
|
15
|
+
|
|
16
|
+
type FieldError = { fieldId?: string; errorMessage: string };
|
|
17
|
+
type FormValues = Record<string, any>;
|
|
18
|
+
|
|
19
|
+
export interface FormFieldElementProps
|
|
20
|
+
extends Omit<DynamicFormFieldConfig, "itemType"> {
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
formData: FormValues;
|
|
23
|
+
setFormData: (updater: (prev: FormValues) => FormValues) => void;
|
|
24
|
+
submitErrorFields: string[];
|
|
25
|
+
isGrouped?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const validateEmail = (email: string): boolean => {
|
|
29
|
+
const [, domainPart] = email.split("@");
|
|
30
|
+
if (!domainPart || domainPart.includes("..")) return false;
|
|
31
|
+
return Regex.email.test(email);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const FormFieldElement: React.FC<FormFieldElementProps> = props => {
|
|
35
|
+
const context = useContext(FormContext);
|
|
36
|
+
const {
|
|
37
|
+
loading,
|
|
38
|
+
formData,
|
|
39
|
+
setFormData,
|
|
40
|
+
submitErrorFields,
|
|
41
|
+
isGrouped = false,
|
|
42
|
+
fieldId,
|
|
43
|
+
fieldType,
|
|
44
|
+
fieldClassName,
|
|
45
|
+
displayName,
|
|
46
|
+
requiredField,
|
|
47
|
+
textInputType,
|
|
48
|
+
errorMessage,
|
|
49
|
+
dataSource,
|
|
50
|
+
existingDataSource,
|
|
51
|
+
note,
|
|
52
|
+
noteClassName,
|
|
53
|
+
validationNote,
|
|
54
|
+
validationGroupId,
|
|
55
|
+
validationType,
|
|
56
|
+
applyRegularExpression,
|
|
57
|
+
regexPattern,
|
|
58
|
+
} = props;
|
|
59
|
+
|
|
60
|
+
const [error, setError] = useState<FieldError>({
|
|
61
|
+
fieldId: "",
|
|
62
|
+
errorMessage: "",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const Override = fieldType
|
|
66
|
+
? context?.fieldComponentOverrides?.[fieldType]
|
|
67
|
+
: undefined;
|
|
68
|
+
const Component: React.FC<FieldRenderProps> | undefined =
|
|
69
|
+
Override || (fieldType ? FormFieldTypeMap[fieldType] : undefined);
|
|
70
|
+
|
|
71
|
+
if (!Component) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const groupingField = validationGroupId && validationType && !isGrouped;
|
|
76
|
+
const isConsentCheckbox =
|
|
77
|
+
fieldType === "Checkbox" && !dataSource && !existingDataSource;
|
|
78
|
+
|
|
79
|
+
const hasError = submitErrorFields.includes(fieldId!);
|
|
80
|
+
let errorClass = "";
|
|
81
|
+
if (hasError) {
|
|
82
|
+
errorClass =
|
|
83
|
+
fieldId === "Datepicker" || fieldId === "Dropdown"
|
|
84
|
+
? "border border-border-critical border-solid rounded-xl"
|
|
85
|
+
: "border border-border-critical";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const setFieldErrorLocal = (id: string | undefined, message: string) => {
|
|
89
|
+
setError({ fieldId: id, errorMessage: message });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const updateFormData = (name: string, value: any) => {
|
|
93
|
+
setFormData(prev => ({ ...prev, [name]: value }));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleInputChange = (event: any): void => {
|
|
97
|
+
context?.clearFieldError(fieldId!);
|
|
98
|
+
setError({ fieldId: "", errorMessage: "" });
|
|
99
|
+
context?.setHasError(false);
|
|
100
|
+
|
|
101
|
+
if (fieldType === "Checkbox" || fieldType === "Radio") {
|
|
102
|
+
updateFormData(fieldId!, event);
|
|
103
|
+
} else if (fieldType === "Dropdown") {
|
|
104
|
+
updateFormData(fieldId!, event?.value);
|
|
105
|
+
if (existingDataSource === "State List") {
|
|
106
|
+
context?.setSelectedState(event);
|
|
107
|
+
}
|
|
108
|
+
} else if (fieldType === "Datepicker") {
|
|
109
|
+
if (event) {
|
|
110
|
+
updateFormData(fieldId!, event);
|
|
111
|
+
} else {
|
|
112
|
+
setFieldErrorLocal(fieldId, errorMessage || "");
|
|
113
|
+
context?.setHasError(true);
|
|
114
|
+
context?.setFieldError(fieldId!, errorMessage || "");
|
|
115
|
+
}
|
|
116
|
+
} else if (fieldType === "Imageupload") {
|
|
117
|
+
const nextFiles = Array.isArray(event) ? event : [];
|
|
118
|
+
context?.setFiles(nextFiles);
|
|
119
|
+
updateFormData(fieldId!, nextFiles);
|
|
120
|
+
return;
|
|
121
|
+
} else {
|
|
122
|
+
const { name, value } = event.target;
|
|
123
|
+
if (!value) {
|
|
124
|
+
updateFormData(name, value);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (textInputType === "phone") {
|
|
128
|
+
const formattedNumber = formatFormPhoneNumber(value);
|
|
129
|
+
const digitsOnly = formattedNumber.replace(/\D/g, "");
|
|
130
|
+
updateFormData(name, digitsOnly);
|
|
131
|
+
event.target.value = digitsOnly;
|
|
132
|
+
if (digitsOnly.length !== 10) {
|
|
133
|
+
setFieldErrorLocal(fieldId, errorMessage || "");
|
|
134
|
+
context?.setHasError(true);
|
|
135
|
+
context?.setFieldError(fieldId!, errorMessage || "");
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (textInputType === "email" && !validateEmail(value)) {
|
|
140
|
+
setFieldErrorLocal(fieldId, errorMessage || "");
|
|
141
|
+
context?.setHasError(true);
|
|
142
|
+
context?.setFieldError(fieldId!, errorMessage || "");
|
|
143
|
+
}
|
|
144
|
+
if (applyRegularExpression && regexPattern) {
|
|
145
|
+
try {
|
|
146
|
+
const regex = new RegExp(regexPattern);
|
|
147
|
+
if (!regex.test(value) && value !== "") {
|
|
148
|
+
setFieldErrorLocal(fieldId, errorMessage || "");
|
|
149
|
+
context?.setHasError(true);
|
|
150
|
+
context?.setFieldError(fieldId!, errorMessage || "");
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
context?.onError?.({
|
|
154
|
+
type: "invalid_regex_pattern",
|
|
155
|
+
error,
|
|
156
|
+
metadata: { fieldId, regexPattern },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
updateFormData(name, value);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div
|
|
166
|
+
className={cx(
|
|
167
|
+
groupingField
|
|
168
|
+
? "groupingField rounded-b-xl rounded-t-xl border border-border-secondary bg-bg-surface-secondary px-5 pb-5 pt-1"
|
|
169
|
+
: "",
|
|
170
|
+
fieldClassName?.split("|")[1] || "w-full"
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
{groupingField && !isGrouped
|
|
174
|
+
? labelCreator(
|
|
175
|
+
validationNote || constants.PROVIDE_AT_LEAST_ONE,
|
|
176
|
+
true,
|
|
177
|
+
"body2"
|
|
178
|
+
)
|
|
179
|
+
: null}
|
|
180
|
+
|
|
181
|
+
{!isConsentCheckbox && labelCreator(displayName, requiredField)}
|
|
182
|
+
|
|
183
|
+
<div
|
|
184
|
+
className={
|
|
185
|
+
fieldType === "Checkbox" || fieldType === "Radio"
|
|
186
|
+
? !isConsentCheckbox
|
|
187
|
+
? "mt-6"
|
|
188
|
+
: ""
|
|
189
|
+
: ""
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
<Component
|
|
193
|
+
loading={loading}
|
|
194
|
+
displayName={displayName}
|
|
195
|
+
requiredField={requiredField}
|
|
196
|
+
formData={formData}
|
|
197
|
+
fieldId={fieldId}
|
|
198
|
+
textInputType={textInputType}
|
|
199
|
+
handleInputChange={handleInputChange}
|
|
200
|
+
dataSource={dataSource}
|
|
201
|
+
error={error}
|
|
202
|
+
errorClass={errorClass}
|
|
203
|
+
hasError={hasError}
|
|
204
|
+
existingDataSource={existingDataSource}
|
|
205
|
+
isConsentCheckbox={isConsentCheckbox}
|
|
206
|
+
data-testid="form-component"
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{error.fieldId === fieldId && error.errorMessage && (
|
|
211
|
+
<Text as="div" className="footnote text-text-critical">
|
|
212
|
+
{error.errorMessage}
|
|
213
|
+
</Text>
|
|
214
|
+
)}
|
|
215
|
+
{note && (
|
|
216
|
+
<Text as="div" className={cx("footnote", noteClassName)}>
|
|
217
|
+
{note}
|
|
218
|
+
</Text>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export default FormFieldElement;
|