@windstream/react-shared-components 0.2.55 → 0.2.57
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/core.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/collapse/index.test.tsx +20 -0
- package/src/components/collapse/index.tsx +4 -0
- 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/fiber-form/index.tsx +1 -0
- package/src/contentful/index.ts +20 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { constants } from "./constants";
|
|
2
|
+
import { DynamicFormFieldConfig, DynamicFormItem, FormAction } from "./types";
|
|
3
|
+
|
|
4
|
+
export const generateSubmissionId = (): string => {
|
|
5
|
+
if (
|
|
6
|
+
typeof crypto !== "undefined" &&
|
|
7
|
+
typeof crypto.randomUUID === "function"
|
|
8
|
+
) {
|
|
9
|
+
return crypto.randomUUID();
|
|
10
|
+
}
|
|
11
|
+
// RFC4122-ish fallback
|
|
12
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
|
|
13
|
+
const r = (Math.random() * 16) | 0;
|
|
14
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
15
|
+
return v.toString(16);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const isFormField = (
|
|
20
|
+
item: DynamicFormItem
|
|
21
|
+
): item is DynamicFormItem & { itemType: "formField" } =>
|
|
22
|
+
item.itemType === "formField";
|
|
23
|
+
|
|
24
|
+
const isConditional = (
|
|
25
|
+
item: DynamicFormItem
|
|
26
|
+
): item is DynamicFormItem & { itemType: "conditional" } =>
|
|
27
|
+
item.itemType === "conditional";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Expand conditional field groups: when the conditional trigger value is "yes"
|
|
31
|
+
* its nested fields participate in validation / payload building.
|
|
32
|
+
*/
|
|
33
|
+
export const processFields = (
|
|
34
|
+
items: DynamicFormItem[],
|
|
35
|
+
formData: Record<string, any>
|
|
36
|
+
): DynamicFormItem[] => {
|
|
37
|
+
const additional = items.filter(isConditional).flatMap(entry => {
|
|
38
|
+
const value = formData[entry.fieldId];
|
|
39
|
+
if (typeof value === "string" && value.toLowerCase() === "yes") {
|
|
40
|
+
return entry.formFieldsToShow;
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
});
|
|
44
|
+
return [...items, ...additional];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const arrayForFieldValidations = (
|
|
48
|
+
items: DynamicFormItem[],
|
|
49
|
+
sftp = false,
|
|
50
|
+
formData: Record<string, any> = {}
|
|
51
|
+
): any[] => {
|
|
52
|
+
const processed = processFields(items, formData);
|
|
53
|
+
return processed.filter(isFormField).map(field => {
|
|
54
|
+
if (sftp) {
|
|
55
|
+
return { [field.fieldId]: field.sftpFieldId };
|
|
56
|
+
}
|
|
57
|
+
const { fieldId, requiredField, fieldType, formSection, dataSource } =
|
|
58
|
+
field;
|
|
59
|
+
return { fieldId, requiredField, fieldType, formSection, dataSource };
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const buildSftpMap = (
|
|
64
|
+
items: DynamicFormItem[],
|
|
65
|
+
formData: Record<string, any> = {}
|
|
66
|
+
): Record<string, string> => {
|
|
67
|
+
return arrayForFieldValidations(items, true, formData).reduce(
|
|
68
|
+
(acc, curr) => ({ ...acc, ...curr }),
|
|
69
|
+
{} as Record<string, string>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const groupFormDataBySection = (
|
|
74
|
+
extractedArray: Array<any>,
|
|
75
|
+
formObject: Record<string, any>
|
|
76
|
+
): Record<string, Array<Record<string, any>>> => {
|
|
77
|
+
const groupedData: Record<string, Array<Record<string, any>>> = {};
|
|
78
|
+
extractedArray.forEach(item => {
|
|
79
|
+
const { fieldId, formSection } = item;
|
|
80
|
+
if (!formSection || typeof formSection !== "string") return;
|
|
81
|
+
if (!groupedData[formSection]) {
|
|
82
|
+
groupedData[formSection] = [];
|
|
83
|
+
}
|
|
84
|
+
if (fieldId in formObject) {
|
|
85
|
+
groupedData[formSection].push({ [fieldId]: formObject[fieldId] });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return groupedData;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const formPayloadCreation = (
|
|
92
|
+
items: DynamicFormItem[],
|
|
93
|
+
formData: Record<string, any>
|
|
94
|
+
): Record<string, any> => {
|
|
95
|
+
const strippedFormData = Object.fromEntries(
|
|
96
|
+
Object.entries(formData).filter(
|
|
97
|
+
([, value]) => value !== "" && value !== undefined
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
const extractedArray = arrayForFieldValidations(
|
|
101
|
+
items,
|
|
102
|
+
false,
|
|
103
|
+
strippedFormData
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const hasMentionedFormSection = extractedArray.every(item => {
|
|
107
|
+
const formSection = item.formSection;
|
|
108
|
+
return typeof formSection === "string" && formSection !== "";
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (hasMentionedFormSection && extractedArray.length > 0) {
|
|
112
|
+
return { ...groupFormDataBySection(extractedArray, strippedFormData) };
|
|
113
|
+
}
|
|
114
|
+
return { form1: { ...strippedFormData } };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const getInvalidFields = (
|
|
118
|
+
elementsArray: Array<any>,
|
|
119
|
+
formObject: Record<string, any>
|
|
120
|
+
): string[] => {
|
|
121
|
+
return elementsArray
|
|
122
|
+
.filter(element => {
|
|
123
|
+
if (!element.requiredField) return false;
|
|
124
|
+
const value = formObject[element.fieldId];
|
|
125
|
+
if (element.fieldType === "Checkbox" && !element.dataSource) {
|
|
126
|
+
return value !== true;
|
|
127
|
+
}
|
|
128
|
+
return value === "" || value === null || value === undefined;
|
|
129
|
+
})
|
|
130
|
+
.map(element => element.fieldId);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build the section-aware, width-aware layout groups used for rendering.
|
|
135
|
+
* Mirrors the original grouping logic but operates on normalized items.
|
|
136
|
+
*/
|
|
137
|
+
export const groupFieldsForLayout = (items: DynamicFormItem[]): any[] => {
|
|
138
|
+
const groups: any[] = [];
|
|
139
|
+
let i = 0;
|
|
140
|
+
|
|
141
|
+
while (i < items.length) {
|
|
142
|
+
const current = items[i];
|
|
143
|
+
|
|
144
|
+
if (!isFormField(current)) {
|
|
145
|
+
groups.push({ type: "single", fields: [current] });
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const currentValidationGroupId = current.validationGroupId;
|
|
151
|
+
|
|
152
|
+
if (currentValidationGroupId) {
|
|
153
|
+
const groupFields: DynamicFormItem[] = [];
|
|
154
|
+
let j = i;
|
|
155
|
+
while (j < items.length) {
|
|
156
|
+
const field = items[j];
|
|
157
|
+
if (
|
|
158
|
+
isFormField(field) &&
|
|
159
|
+
field.validationGroupId === currentValidationGroupId
|
|
160
|
+
) {
|
|
161
|
+
groupFields.push(field);
|
|
162
|
+
j++;
|
|
163
|
+
} else {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const validationGroup: any = {
|
|
169
|
+
type: "validationGroup",
|
|
170
|
+
validationGroupId: currentValidationGroupId,
|
|
171
|
+
validationNote: current.validationNote,
|
|
172
|
+
validationType: current.validationType,
|
|
173
|
+
subGroups: [] as any[],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
let k = 0;
|
|
177
|
+
while (k < groupFields.length) {
|
|
178
|
+
const field = groupFields[k] as DynamicFormFieldConfig & {
|
|
179
|
+
itemType: string;
|
|
180
|
+
};
|
|
181
|
+
const fieldWidth = field.fieldWidth;
|
|
182
|
+
|
|
183
|
+
if (fieldWidth === false) {
|
|
184
|
+
const nextField = groupFields[k + 1] as
|
|
185
|
+
| (DynamicFormFieldConfig & { itemType: string })
|
|
186
|
+
| undefined;
|
|
187
|
+
if (nextField && nextField.fieldWidth === false) {
|
|
188
|
+
validationGroup.subGroups.push({
|
|
189
|
+
type: "double",
|
|
190
|
+
fields: [field, nextField],
|
|
191
|
+
});
|
|
192
|
+
k += 2;
|
|
193
|
+
} else {
|
|
194
|
+
validationGroup.subGroups.push({ type: "single", fields: [field] });
|
|
195
|
+
k++;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
validationGroup.subGroups.push({ type: "single", fields: [field] });
|
|
199
|
+
k++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
groups.push(validationGroup);
|
|
204
|
+
i = j;
|
|
205
|
+
} else {
|
|
206
|
+
const currentFieldWidth = current.fieldWidth;
|
|
207
|
+
if (currentFieldWidth === false) {
|
|
208
|
+
const nextField = items[i + 1];
|
|
209
|
+
if (
|
|
210
|
+
nextField &&
|
|
211
|
+
isFormField(nextField) &&
|
|
212
|
+
nextField.fieldWidth === false &&
|
|
213
|
+
!nextField.validationGroupId
|
|
214
|
+
) {
|
|
215
|
+
groups.push({ type: "double", fields: [current, nextField] });
|
|
216
|
+
i += 2;
|
|
217
|
+
} else {
|
|
218
|
+
groups.push({ type: "single", fields: [current] });
|
|
219
|
+
i++;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
groups.push({ type: "single", fields: [current] });
|
|
223
|
+
i++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return groups;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const shouldShowPopup = (
|
|
232
|
+
items: DynamicFormItem[],
|
|
233
|
+
formData: Record<string, any>
|
|
234
|
+
): boolean => {
|
|
235
|
+
return items.some(item => {
|
|
236
|
+
if (!isFormField(item)) return false;
|
|
237
|
+
const ds = item.dataSource;
|
|
238
|
+
if (!Array.isArray(ds)) return false;
|
|
239
|
+
const selectedValue = formData[item.fieldId];
|
|
240
|
+
const selectedOption = ds.find((opt: any) => opt.value === selectedValue);
|
|
241
|
+
return selectedOption?.popup === true;
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const filterActionsForEmailTemplate = (
|
|
246
|
+
actions: FormAction[],
|
|
247
|
+
emailTemplate?: string
|
|
248
|
+
): FormAction[] => {
|
|
249
|
+
if (!emailTemplate) {
|
|
250
|
+
return actions.filter(a => a !== "email");
|
|
251
|
+
}
|
|
252
|
+
return actions;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export { constants, isFormField, isConditional };
|
|
@@ -536,6 +536,7 @@ export const FiberForm: React.FC<FiberFormProps> = ({
|
|
|
536
536
|
<Button
|
|
537
537
|
type="submit"
|
|
538
538
|
disabled={isSubmitting}
|
|
539
|
+
aria-busy={isSubmitting}
|
|
539
540
|
className={cx(
|
|
540
541
|
"flex h-16 w-[198px] max-w-full cursor-pointer items-center justify-center overflow-hidden rounded-button border border-brand bg-bg-fill-brand px-[60px] py-4 text-center font-black leading-7 text-text-brand-on-bg-fill no-underline transition-colors duration-300 ease-in-out enabled:hover:border-[var(--color-bg-fill-brand-hover)] enabled:hover:bg-bg-fill-brand-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-brand disabled:cursor-not-allowed",
|
|
541
542
|
isSubmitting && "opacity-75",
|
package/src/contentful/index.ts
CHANGED
|
@@ -102,6 +102,26 @@ export type { TypeCartRetentionBannerFields as CartRetentionBannerProps } from "
|
|
|
102
102
|
export { IframeEmbed } from "./blocks/iframe-embed";
|
|
103
103
|
export type { IframeEmbedProps } from "./blocks/iframe-embed/types";
|
|
104
104
|
|
|
105
|
+
export { DynamicForm } from "./blocks/dynamic-form";
|
|
106
|
+
export type {
|
|
107
|
+
DynamicFormProps,
|
|
108
|
+
DynamicFormItem,
|
|
109
|
+
DynamicFormFieldConfig,
|
|
110
|
+
DynamicFormFieldOption,
|
|
111
|
+
DynamicFormFieldType,
|
|
112
|
+
ConditionalFieldConfig,
|
|
113
|
+
DynamicFormPayload,
|
|
114
|
+
SubmitHandler,
|
|
115
|
+
SubmitContext,
|
|
116
|
+
FormAction,
|
|
117
|
+
FieldRenderProps,
|
|
118
|
+
ExistingDataSourceResolver,
|
|
119
|
+
} from "./blocks/dynamic-form/types";
|
|
120
|
+
export {
|
|
121
|
+
constants as dynamicFormConstants,
|
|
122
|
+
Regex as dynamicFormRegex,
|
|
123
|
+
} from "./blocks/dynamic-form/constants";
|
|
124
|
+
|
|
105
125
|
// Contentful Rich Text Hooks & Utils
|
|
106
126
|
export { toDocument } from "../utils/contentful/to-document";
|
|
107
127
|
|