@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.
Files changed (33) hide show
  1. package/dist/contentful/index.d.ts +200 -2
  2. package/dist/contentful/index.esm.js +5 -5
  3. package/dist/contentful/index.esm.js.map +1 -1
  4. package/dist/contentful/index.js +4 -4
  5. package/dist/contentful/index.js.map +1 -1
  6. package/dist/core.d.ts +2 -2
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.esm.js +2 -2
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles.css +1 -1
  13. package/package.json +1 -1
  14. package/src/components/collapse/index.test.tsx +20 -0
  15. package/src/components/collapse/index.tsx +4 -0
  16. package/src/contentful/blocks/dynamic-form/DynamicForm.stories.tsx +154 -0
  17. package/src/contentful/blocks/dynamic-form/conditional-fields.test.tsx +108 -0
  18. package/src/contentful/blocks/dynamic-form/conditional-fields.tsx +103 -0
  19. package/src/contentful/blocks/dynamic-form/constants.ts +28 -0
  20. package/src/contentful/blocks/dynamic-form/context.ts +35 -0
  21. package/src/contentful/blocks/dynamic-form/field-map.test.tsx +421 -0
  22. package/src/contentful/blocks/dynamic-form/field-map.tsx +486 -0
  23. package/src/contentful/blocks/dynamic-form/form-field.test.tsx +319 -0
  24. package/src/contentful/blocks/dynamic-form/form-field.tsx +224 -0
  25. package/src/contentful/blocks/dynamic-form/index.test.tsx +355 -0
  26. package/src/contentful/blocks/dynamic-form/index.tsx +552 -0
  27. package/src/contentful/blocks/dynamic-form/render.test.tsx +116 -0
  28. package/src/contentful/blocks/dynamic-form/render.tsx +172 -0
  29. package/src/contentful/blocks/dynamic-form/types.ts +211 -0
  30. package/src/contentful/blocks/dynamic-form/utils.test.ts +238 -0
  31. package/src/contentful/blocks/dynamic-form/utils.ts +255 -0
  32. package/src/contentful/blocks/fiber-form/index.tsx +1 -0
  33. 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",
@@ -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