@windstream/react-shared-components 0.2.92 → 0.2.93
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 +46 -6
- package/dist/contentful/index.esm.js +4 -4
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +3 -3
- package/dist/contentful/index.js.map +1 -1
- package/dist/core.d.ts +4 -1
- package/dist/index.d.ts +6 -3
- 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/utils/index.d.ts +2 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.esm.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/checklist/index.test.tsx +18 -1
- package/src/components/checklist/index.tsx +25 -21
- package/src/components/checklist/types.ts +8 -2
- package/src/contentful/blocks/address-input-banner/index.test.tsx +14 -0
- package/src/contentful/blocks/address-input-banner/index.tsx +3 -1
- package/src/contentful/blocks/address-input-banner/types.ts +1 -1
- package/src/contentful/blocks/blogs-grid/index.test.tsx +21 -1
- package/src/contentful/blocks/blogs-grid/index.tsx +1 -0
- package/src/contentful/blocks/blogs-grid/types.ts +3 -0
- package/src/contentful/blocks/blogs-grid-base/index.test.tsx +49 -3
- package/src/contentful/blocks/blogs-grid-base/index.tsx +11 -2
- package/src/contentful/blocks/blogs-grid-base/types.ts +7 -1
- package/src/contentful/blocks/cards/blog-card/index.test.tsx +29 -0
- package/src/contentful/blocks/cards/blog-card/index.tsx +18 -4
- package/src/contentful/blocks/cards/blog-card/types.ts +3 -1
- package/src/contentful/blocks/cards/product-card/index.test.tsx +43 -0
- package/src/contentful/blocks/cards/product-card/index.tsx +37 -9
- package/src/contentful/blocks/cards/product-card/types.ts +3 -0
- package/src/contentful/blocks/carousel/helper.test.tsx +13 -0
- package/src/contentful/blocks/carousel/helper.tsx +1 -0
- package/src/contentful/blocks/carousel/types.ts +2 -0
- package/src/contentful/blocks/dynamic-form/conditional-fields.tsx +1 -0
- package/src/contentful/blocks/dynamic-form/form-field.tsx +1 -0
- package/src/contentful/blocks/dynamic-form/index.test.tsx +48 -0
- package/src/contentful/blocks/dynamic-form/index.tsx +19 -7
- package/src/contentful/blocks/dynamic-form/render.tsx +13 -7
- package/src/contentful/blocks/dynamic-form/types.ts +28 -2
- package/src/contentful/blocks/dynamic-tabs/index.test.tsx +52 -2
- package/src/contentful/blocks/dynamic-tabs/index.tsx +9 -2
- package/src/contentful/blocks/dynamic-tabs/ordered-tab-views.tsx +16 -6
- package/src/contentful/blocks/dynamic-tabs/tab-views.tsx +10 -6
- package/src/contentful/blocks/dynamic-tabs/types.ts +7 -0
- package/src/contentful/blocks/email-input-block/index.test.tsx +51 -3
- package/src/contentful/blocks/email-input-block/index.tsx +88 -31
- package/src/contentful/blocks/email-input-block/types.ts +14 -1
- package/src/contentful/blocks/fiber-form/index.test.tsx +40 -2
- package/src/contentful/blocks/fiber-form/index.tsx +32 -13
- package/src/contentful/blocks/fiber-form/types.ts +18 -0
- package/src/contentful/blocks/iframe-embed/index.test.tsx +44 -7
- package/src/contentful/blocks/iframe-embed/index.tsx +3 -2
- package/src/contentful/blocks/iframe-embed/types.ts +1 -0
- package/src/contentful/blocks/ordered-list/index.test.tsx +54 -4
- package/src/contentful/blocks/ordered-list/index.tsx +14 -2
- package/src/contentful/blocks/ordered-list/types.ts +15 -2
- package/src/contentful/blocks/primary-hero/index.tsx +5 -5
- package/src/contentful/blocks/search-block/index.test.tsx +36 -2
- package/src/contentful/blocks/search-block/index.tsx +24 -4
- package/src/contentful/blocks/search-block/types.ts +10 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/replace-tokens.test.ts +40 -0
- package/src/utils/replace-tokens.ts +13 -0
|
@@ -60,6 +60,8 @@ export interface ProductCardFields {
|
|
|
60
60
|
};
|
|
61
61
|
speed?: string;
|
|
62
62
|
price?: string;
|
|
63
|
+
/** Regular (pre-promo) price shown struck through on the card when truthy. */
|
|
64
|
+
strikethroughPrice?: string;
|
|
63
65
|
priceSuffix?: string;
|
|
64
66
|
priceSuffixBottom?: string;
|
|
65
67
|
techType?: string;
|
|
@@ -83,6 +83,7 @@ const ConditionalFormFields: React.FC<ConditionalFormFieldsProps> = props => {
|
|
|
83
83
|
fieldId === constants.TARGET_EMAIL &&
|
|
84
84
|
"rounded-b-xl border-x border-t border-border-secondary pb-5"
|
|
85
85
|
)}
|
|
86
|
+
{...props.fieldProps?.field}
|
|
86
87
|
>
|
|
87
88
|
<div className="mb-4">
|
|
88
89
|
{labelCreator(displayName, requiredField)}
|
|
@@ -23,6 +23,54 @@ const fields: DynamicFormItem[] = [
|
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
describe("DynamicForm", () => {
|
|
26
|
+
it("tags block fields, individual form fields and the submit region", () => {
|
|
27
|
+
const { container } = render(
|
|
28
|
+
<DynamicForm
|
|
29
|
+
title="Contact us"
|
|
30
|
+
subTitle="Get in touch"
|
|
31
|
+
formHeading="Your details"
|
|
32
|
+
description={<p>Form description</p>}
|
|
33
|
+
disclaimer="Privacy notice"
|
|
34
|
+
fields={[
|
|
35
|
+
{
|
|
36
|
+
...fields[0],
|
|
37
|
+
fieldProps: {
|
|
38
|
+
field: {
|
|
39
|
+
"data-contentful-entry-id": "form-field",
|
|
40
|
+
"data-contentful-field-id": "firstName",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
]}
|
|
45
|
+
fieldProps={{
|
|
46
|
+
title: { "data-contentful-field-id": "title" },
|
|
47
|
+
subTitle: { "data-contentful-field-id": "subTitle" },
|
|
48
|
+
formHeading: { "data-contentful-field-id": "formHeading" },
|
|
49
|
+
description: { "data-contentful-field-id": "description" },
|
|
50
|
+
disclaimer: { "data-contentful-field-id": "disclaimer" },
|
|
51
|
+
submit: { "data-contentful-field-id": "submit" },
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
[
|
|
56
|
+
"title",
|
|
57
|
+
"subTitle",
|
|
58
|
+
"formHeading",
|
|
59
|
+
"description",
|
|
60
|
+
"disclaimer",
|
|
61
|
+
"submit",
|
|
62
|
+
].forEach(field => {
|
|
63
|
+
expect(
|
|
64
|
+
container.querySelector(`[data-contentful-field-id="${field}"]`)
|
|
65
|
+
).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
expect(
|
|
68
|
+
container.querySelector(
|
|
69
|
+
'[data-contentful-entry-id="form-field"][data-contentful-field-id="firstName"]'
|
|
70
|
+
)
|
|
71
|
+
).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
26
74
|
it("renders the title, fields and submit button", () => {
|
|
27
75
|
render(
|
|
28
76
|
<DynamicForm title="Contact us" fields={fields} submitLabel="Send" />
|
|
@@ -123,6 +123,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
123
123
|
resolveExistingDataSource,
|
|
124
124
|
renderAddressAutocomplete,
|
|
125
125
|
renderSubmitButton,
|
|
126
|
+
fieldProps,
|
|
126
127
|
} = props;
|
|
127
128
|
|
|
128
129
|
const submissionIdRef = useRef<string>(generateSubmissionId());
|
|
@@ -501,7 +502,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
501
502
|
className="mx-auto min-h-screen w-full px-4 sm:px-6 lg:px-8"
|
|
502
503
|
>
|
|
503
504
|
<div className="max-w-2xl mx-auto py-4 sm:py-6 lg:py-8">
|
|
504
|
-
{successMessage}
|
|
505
|
+
<div {...fieldProps?.successMessage}>{successMessage}</div>
|
|
505
506
|
</div>
|
|
506
507
|
</div>
|
|
507
508
|
);
|
|
@@ -511,7 +512,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
511
512
|
return (
|
|
512
513
|
<div className="mx-auto min-h-screen w-full px-4 sm:px-6 lg:px-8">
|
|
513
514
|
<div className="max-w-2xl mx-auto py-4 sm:py-6 lg:py-8">
|
|
514
|
-
{errorNode}
|
|
515
|
+
<div {...fieldProps?.errorMessage}>{errorNode}</div>
|
|
515
516
|
</div>
|
|
516
517
|
</div>
|
|
517
518
|
);
|
|
@@ -523,24 +524,35 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
523
524
|
<div className="pb-8 sm:pb-6 lg:pb-4">
|
|
524
525
|
<div className="mb-8 lg:mb-16">
|
|
525
526
|
{title && (
|
|
526
|
-
<Text
|
|
527
|
+
<Text
|
|
528
|
+
as="h2"
|
|
529
|
+
className="heading2 text-center"
|
|
530
|
+
{...fieldProps?.title}
|
|
531
|
+
>
|
|
527
532
|
{title}
|
|
528
533
|
</Text>
|
|
529
534
|
)}
|
|
530
535
|
{subTitle && (
|
|
531
|
-
<Text
|
|
536
|
+
<Text
|
|
537
|
+
as="h3"
|
|
538
|
+
className="subheading3 mt-3 text-center"
|
|
539
|
+
{...fieldProps?.subTitle}
|
|
540
|
+
>
|
|
532
541
|
{subTitle}
|
|
533
542
|
</Text>
|
|
534
543
|
)}
|
|
535
544
|
</div>
|
|
536
545
|
{description ? (
|
|
537
|
-
<div className="text-center"
|
|
546
|
+
<div className="text-center" {...fieldProps?.description}>
|
|
547
|
+
{description}
|
|
548
|
+
</div>
|
|
538
549
|
) : null}
|
|
539
550
|
{formHeading ? (
|
|
540
551
|
<div className="mb-6 w-full px-2 sm:px-4">
|
|
541
552
|
<Text
|
|
542
553
|
as="div"
|
|
543
554
|
className="body3 mb-2 text-center text-xl sm:mb-4 md:text-left"
|
|
555
|
+
{...fieldProps?.formHeading}
|
|
544
556
|
>
|
|
545
557
|
{formHeading}
|
|
546
558
|
</Text>
|
|
@@ -567,7 +579,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
567
579
|
{validationMessage}
|
|
568
580
|
</Text>
|
|
569
581
|
)}
|
|
570
|
-
<div className="flex justify-center">
|
|
582
|
+
<div className="flex justify-center" {...fieldProps?.submit}>
|
|
571
583
|
{renderSubmitButton ? (
|
|
572
584
|
renderSubmitButton({
|
|
573
585
|
isLoading,
|
|
@@ -594,7 +606,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = props => {
|
|
|
594
606
|
</FormContext.Provider>
|
|
595
607
|
</div>
|
|
596
608
|
{disclaimer && (
|
|
597
|
-
<Text as="div" className="footnote m-2">
|
|
609
|
+
<Text as="div" className="footnote m-2" {...fieldProps?.disclaimer}>
|
|
598
610
|
{disclaimer}
|
|
599
611
|
</Text>
|
|
600
612
|
)}
|
|
@@ -52,16 +52,22 @@ const renderItem = (
|
|
|
52
52
|
const widthClass = item.dividerLength === false ? "w-1/2" : "w-full";
|
|
53
53
|
return (
|
|
54
54
|
<div key={key} className="flex w-full justify-center">
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
<div {...item.fieldProps?.field}>
|
|
56
|
+
<Divider
|
|
57
|
+
role="separator"
|
|
58
|
+
aria-label="Content divider"
|
|
59
|
+
className={widthClass}
|
|
60
|
+
style={{ height: `${strokeWidth}px` }}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
61
63
|
</div>
|
|
62
64
|
);
|
|
63
65
|
}
|
|
64
|
-
return
|
|
66
|
+
return (
|
|
67
|
+
<div key={key} {...item.fieldProps?.field}>
|
|
68
|
+
{item.content}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
65
71
|
};
|
|
66
72
|
|
|
67
73
|
export const dynamicRenderingWithGroupings = (
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* The set of submit actions the DynamicForm understands. Each maps to an
|
|
5
7
|
* injected async handler provided by the consuming application. The package
|
|
@@ -53,6 +55,8 @@ export interface DynamicFormFieldConfig {
|
|
|
53
55
|
selectPresetValidationsToApply?: string;
|
|
54
56
|
/** When true, a Text field renders the app-injected address autocomplete. */
|
|
55
57
|
enableAddressAutocomplete?: boolean;
|
|
58
|
+
/** Inspector attributes applied to the field's label/control group. */
|
|
59
|
+
fieldProps?: FieldProps<"field">;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export interface ConditionalFieldConfig {
|
|
@@ -67,6 +71,8 @@ export interface ConditionalFieldConfig {
|
|
|
67
71
|
* Injected by the consuming app so the shared package stays route-agnostic.
|
|
68
72
|
*/
|
|
69
73
|
grouped?: boolean;
|
|
74
|
+
/** Inspector attributes applied to the conditional field group. */
|
|
75
|
+
fieldProps?: FieldProps<"field">;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
export interface DynamicFormDividerConfig {
|
|
@@ -81,8 +87,17 @@ export interface DynamicFormDividerConfig {
|
|
|
81
87
|
export type DynamicFormItem =
|
|
82
88
|
| ({ itemType: "formField" } & DynamicFormFieldConfig)
|
|
83
89
|
| ({ itemType: "conditional" } & ConditionalFieldConfig)
|
|
84
|
-
| ({
|
|
85
|
-
|
|
90
|
+
| ({
|
|
91
|
+
itemType: "divider";
|
|
92
|
+
fieldId?: string;
|
|
93
|
+
fieldProps?: FieldProps<"field">;
|
|
94
|
+
} & DynamicFormDividerConfig)
|
|
95
|
+
| {
|
|
96
|
+
itemType: "custom";
|
|
97
|
+
fieldId?: string;
|
|
98
|
+
content: React.ReactNode;
|
|
99
|
+
fieldProps?: FieldProps<"field">;
|
|
100
|
+
};
|
|
86
101
|
|
|
87
102
|
export interface DynamicFormPayload {
|
|
88
103
|
submissionId: string;
|
|
@@ -250,4 +265,15 @@ export interface DynamicFormProps {
|
|
|
250
265
|
submitLabel: string;
|
|
251
266
|
onSubmit: (event: any) => void;
|
|
252
267
|
}) => React.ReactNode;
|
|
268
|
+
/** Inspector attributes for block-level copy and CTA fields. */
|
|
269
|
+
fieldProps?: FieldProps<
|
|
270
|
+
| "title"
|
|
271
|
+
| "subTitle"
|
|
272
|
+
| "formHeading"
|
|
273
|
+
| "description"
|
|
274
|
+
| "disclaimer"
|
|
275
|
+
| "successMessage"
|
|
276
|
+
| "errorMessage"
|
|
277
|
+
| "submit"
|
|
278
|
+
>;
|
|
253
279
|
}
|
|
@@ -49,8 +49,10 @@ jest.mock("@shared/components/material-icon", () => ({
|
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
51
|
jest.mock("@shared/components/text", () => ({
|
|
52
|
-
Text: ({ children, className }: any) => (
|
|
53
|
-
<span className={className}
|
|
52
|
+
Text: ({ children, className, ...props }: any) => (
|
|
53
|
+
<span className={className} {...props}>
|
|
54
|
+
{children}
|
|
55
|
+
</span>
|
|
54
56
|
),
|
|
55
57
|
}));
|
|
56
58
|
|
|
@@ -71,6 +73,54 @@ jest.mock("@shared/utils", () => ({
|
|
|
71
73
|
}));
|
|
72
74
|
|
|
73
75
|
describe("DynamicTabs", () => {
|
|
76
|
+
it("tags block headings and each tab name and content", () => {
|
|
77
|
+
const { container } = render(
|
|
78
|
+
<DynamicTabs
|
|
79
|
+
title="Plans"
|
|
80
|
+
subTitle="Choose a plan"
|
|
81
|
+
fieldProps={{
|
|
82
|
+
title: { "data-contentful-field-id": "title" },
|
|
83
|
+
subTitle: { "data-contentful-field-id": "subTitle" },
|
|
84
|
+
}}
|
|
85
|
+
tabs={{
|
|
86
|
+
items: [
|
|
87
|
+
{
|
|
88
|
+
tabName: "Internet",
|
|
89
|
+
anchorId: "internet",
|
|
90
|
+
tabContent: <div>Internet content</div>,
|
|
91
|
+
fieldProps: {
|
|
92
|
+
tabName: {
|
|
93
|
+
"data-contentful-entry-id": "tab-1",
|
|
94
|
+
"data-contentful-field-id": "tabName",
|
|
95
|
+
},
|
|
96
|
+
tabContent: {
|
|
97
|
+
"data-contentful-entry-id": "tab-1",
|
|
98
|
+
"data-contentful-field-id": "tabContent",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
expect(
|
|
107
|
+
container.querySelector('[data-contentful-field-id="title"]')
|
|
108
|
+
).toBeInTheDocument();
|
|
109
|
+
expect(
|
|
110
|
+
container.querySelector('[data-contentful-field-id="subTitle"]')
|
|
111
|
+
).toBeInTheDocument();
|
|
112
|
+
expect(
|
|
113
|
+
container.querySelector(
|
|
114
|
+
'[data-contentful-entry-id="tab-1"][data-contentful-field-id="tabName"]'
|
|
115
|
+
)
|
|
116
|
+
).toBeInTheDocument();
|
|
117
|
+
expect(
|
|
118
|
+
container.querySelector(
|
|
119
|
+
'[data-contentful-entry-id="tab-1"][data-contentful-field-id="tabContent"]'
|
|
120
|
+
)
|
|
121
|
+
).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
|
|
74
124
|
const defaultProps = {
|
|
75
125
|
tabs: {
|
|
76
126
|
items: [
|
|
@@ -23,6 +23,7 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
23
23
|
mobileSidebarTabType = true,
|
|
24
24
|
dynamic = false,
|
|
25
25
|
isOrderedList = false,
|
|
26
|
+
fieldProps,
|
|
26
27
|
} = props;
|
|
27
28
|
|
|
28
29
|
const items = tabs?.items ?? [];
|
|
@@ -89,12 +90,18 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
89
90
|
{title || subTitle ? (
|
|
90
91
|
<div className="mb-8 mt-5 flex flex-col gap-5 md:gap-6">
|
|
91
92
|
{title ? (
|
|
92
|
-
<Text
|
|
93
|
+
<Text
|
|
94
|
+
className="heading2 px-5 md:heading1 md:text-center"
|
|
95
|
+
{...fieldProps?.title}
|
|
96
|
+
>
|
|
93
97
|
{title}
|
|
94
98
|
</Text>
|
|
95
99
|
) : null}
|
|
96
100
|
{subTitle ? (
|
|
97
|
-
<Text
|
|
101
|
+
<Text
|
|
102
|
+
className="subheading3 px-5 md:subheading1 md:text-center"
|
|
103
|
+
{...fieldProps?.subTitle}
|
|
104
|
+
>
|
|
98
105
|
{subTitle}
|
|
99
106
|
</Text>
|
|
100
107
|
) : null}
|
|
@@ -64,6 +64,7 @@ export function OrderedTabView({
|
|
|
64
64
|
{items.map((tab, index) => (
|
|
65
65
|
<Button
|
|
66
66
|
showButtonAs="unstyled"
|
|
67
|
+
fieldProps={tab.fieldProps?.tabName}
|
|
67
68
|
key={index}
|
|
68
69
|
onClick={() => onTabClick(index, tab.anchorId || "")}
|
|
69
70
|
buttonClassName={cx(
|
|
@@ -73,7 +74,7 @@ export function OrderedTabView({
|
|
|
73
74
|
: "body1"
|
|
74
75
|
)}
|
|
75
76
|
>
|
|
76
|
-
{tab.tabName}
|
|
77
|
+
<span {...tab.fieldProps?.tabName}>{tab.tabName}</span>
|
|
77
78
|
</Button>
|
|
78
79
|
))}
|
|
79
80
|
</div>
|
|
@@ -96,7 +97,10 @@ export function OrderedTabView({
|
|
|
96
97
|
)}
|
|
97
98
|
id={tab.anchorId || `tab-${index}`}
|
|
98
99
|
>
|
|
99
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
className="px-5 py-4 md:px-10"
|
|
102
|
+
{...tab.fieldProps?.tabContent}
|
|
103
|
+
>
|
|
100
104
|
{renderOrderedChildren(tab.tabContent, subitemColCount)}
|
|
101
105
|
</div>
|
|
102
106
|
</motion.div>
|
|
@@ -129,7 +133,9 @@ export function OrderedAccordionView({
|
|
|
129
133
|
index === currentTabIndex ? "bg-bg-surface-hover" : ""
|
|
130
134
|
)}
|
|
131
135
|
>
|
|
132
|
-
<span className="heading6"
|
|
136
|
+
<span className="heading6" {...tab.fieldProps?.tabName}>
|
|
137
|
+
{tab.tabName}
|
|
138
|
+
</span>
|
|
133
139
|
<MaterialIcon
|
|
134
140
|
name={
|
|
135
141
|
index === currentTabIndex
|
|
@@ -155,7 +161,10 @@ export function OrderedAccordionView({
|
|
|
155
161
|
)}
|
|
156
162
|
id={tab.anchorId || `tab-${index}`}
|
|
157
163
|
>
|
|
158
|
-
<div
|
|
164
|
+
<div
|
|
165
|
+
className="px-5 py-4 md:px-10"
|
|
166
|
+
{...tab.fieldProps?.tabContent}
|
|
167
|
+
>
|
|
159
168
|
{renderOrderedChildren(tab.tabContent, subitemColCount)}
|
|
160
169
|
</div>
|
|
161
170
|
</motion.div>
|
|
@@ -192,6 +201,7 @@ export function OrderedSidebarView({
|
|
|
192
201
|
return (
|
|
193
202
|
<button
|
|
194
203
|
key={index}
|
|
204
|
+
{...tab.fieldProps?.tabName}
|
|
195
205
|
onClick={() => onTabClick(index, tab.anchorId || "")}
|
|
196
206
|
className={cx(
|
|
197
207
|
"label1 whitespace-nowrap rounded-lg px-4 py-3 text-left",
|
|
@@ -200,7 +210,7 @@ export function OrderedSidebarView({
|
|
|
200
210
|
: "text-text"
|
|
201
211
|
)}
|
|
202
212
|
>
|
|
203
|
-
{tab.tabName}
|
|
213
|
+
<span {...tab.fieldProps?.tabName}>{tab.tabName}</span>
|
|
204
214
|
</button>
|
|
205
215
|
);
|
|
206
216
|
})}
|
|
@@ -212,7 +222,7 @@ export function OrderedSidebarView({
|
|
|
212
222
|
|
|
213
223
|
<div className="w-full">
|
|
214
224
|
{items.map((tab, index) => (
|
|
215
|
-
<div key={index}>
|
|
225
|
+
<div key={index} {...tab.fieldProps?.tabContent}>
|
|
216
226
|
<AnimatePresence>
|
|
217
227
|
<motion.div
|
|
218
228
|
initial={{ opacity: 0, y: 20 }}
|
|
@@ -32,14 +32,14 @@ export function TabView({ items, currentTabIndex, onTabClick }: TabViewProps) {
|
|
|
32
32
|
: "body1"
|
|
33
33
|
)}
|
|
34
34
|
>
|
|
35
|
-
{item.tabName}
|
|
35
|
+
<span {...item.fieldProps?.tabName}>{item.tabName}</span>
|
|
36
36
|
</Button>
|
|
37
37
|
))}
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
{items.map((item, index) => (
|
|
42
|
-
<div key={index}>
|
|
42
|
+
<div key={index} {...item.fieldProps?.tabContent}>
|
|
43
43
|
<AnimatePresence>
|
|
44
44
|
<motion.div
|
|
45
45
|
initial={{ height: 0, opacity: 0 }}
|
|
@@ -76,7 +76,9 @@ export function AccordionView({
|
|
|
76
76
|
index === currentTabIndex ? "bg-bg-surface-active" : ""
|
|
77
77
|
)}
|
|
78
78
|
>
|
|
79
|
-
<span className=
|
|
79
|
+
<span className="heading6" {...item.fieldProps?.tabName}>
|
|
80
|
+
{item.tabName}
|
|
81
|
+
</span>
|
|
80
82
|
<MaterialIcon
|
|
81
83
|
name={
|
|
82
84
|
index === currentTabIndex
|
|
@@ -88,7 +90,9 @@ export function AccordionView({
|
|
|
88
90
|
/>
|
|
89
91
|
</Button>
|
|
90
92
|
<Collapse open={index === currentTabIndex}>
|
|
91
|
-
<div className={cx("px-4 py-4")}
|
|
93
|
+
<div className={cx("px-4 py-4")} {...item.fieldProps?.tabContent}>
|
|
94
|
+
{item.tabContent}
|
|
95
|
+
</div>
|
|
92
96
|
</Collapse>
|
|
93
97
|
</div>
|
|
94
98
|
))}
|
|
@@ -117,7 +121,7 @@ export function SidebarView({
|
|
|
117
121
|
: "body1"
|
|
118
122
|
)}
|
|
119
123
|
>
|
|
120
|
-
{item.tabName}
|
|
124
|
+
<span {...item.fieldProps?.tabName}>{item.tabName}</span>
|
|
121
125
|
</Button>
|
|
122
126
|
))}
|
|
123
127
|
</div>
|
|
@@ -125,7 +129,7 @@ export function SidebarView({
|
|
|
125
129
|
|
|
126
130
|
<div className="grow">
|
|
127
131
|
{items.map((item, index) => (
|
|
128
|
-
<div key={index}>
|
|
132
|
+
<div key={index} {...item.fieldProps?.tabContent}>
|
|
129
133
|
<AnimatePresence>
|
|
130
134
|
<motion.div
|
|
131
135
|
initial={{ height: 0, opacity: 0 }}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
4
|
+
|
|
3
5
|
export interface DynamicTabT {
|
|
4
6
|
tabName: string;
|
|
7
|
+
fieldProps?: { tabName?: Record<string, unknown> };
|
|
5
8
|
anchorId: string;
|
|
6
9
|
tabContent: ReactNode;
|
|
10
|
+
/** Inspector attributes for this tab's name and content. */
|
|
11
|
+
fieldProps?: FieldProps<"tabName" | "tabContent">;
|
|
7
12
|
}
|
|
8
13
|
|
|
9
14
|
export interface DynamicTabsT {
|
|
@@ -22,4 +27,6 @@ export interface DynamicTabsT {
|
|
|
22
27
|
textColor?: "navy";
|
|
23
28
|
navigationType?: boolean;
|
|
24
29
|
mobileSidebarTabType?: boolean;
|
|
30
|
+
/** Inspector attributes for the block heading fields. */
|
|
31
|
+
fieldProps?: FieldProps<"title" | "subTitle">;
|
|
25
32
|
}
|
|
@@ -15,6 +15,56 @@ describe("EmailInputBlock", () => {
|
|
|
15
15
|
jest.clearAllMocks();
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
it("tags content, email input, CTA, validation and caption fields", () => {
|
|
19
|
+
const { container } = render(
|
|
20
|
+
<EmailInputBlock
|
|
21
|
+
{...defaultProps}
|
|
22
|
+
title="Join us"
|
|
23
|
+
subTitle="Stay in touch"
|
|
24
|
+
emailInputDescription="Sign up now"
|
|
25
|
+
successFeedback="Thanks!"
|
|
26
|
+
caption="No spam"
|
|
27
|
+
fieldProps={{
|
|
28
|
+
title: { "data-contentful-field-id": "title" },
|
|
29
|
+
subTitle: { "data-contentful-field-id": "subTitle" },
|
|
30
|
+
emailInputDescription: {
|
|
31
|
+
"data-contentful-field-id": "emailInputDescription",
|
|
32
|
+
},
|
|
33
|
+
email: { "data-contentful-field-id": "email" },
|
|
34
|
+
cta: { "data-contentful-field-id": "cta" },
|
|
35
|
+
errorMessage: { "data-contentful-field-id": "errorMessage" },
|
|
36
|
+
successFeedback: {
|
|
37
|
+
"data-contentful-field-id": "successFeedback",
|
|
38
|
+
},
|
|
39
|
+
caption: { "data-contentful-field-id": "caption" },
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
[
|
|
44
|
+
"title",
|
|
45
|
+
"subTitle",
|
|
46
|
+
"emailInputDescription",
|
|
47
|
+
"email",
|
|
48
|
+
"cta",
|
|
49
|
+
"caption",
|
|
50
|
+
].forEach(field => {
|
|
51
|
+
expect(
|
|
52
|
+
container.querySelector(`[data-contentful-field-id="${field}"]`)
|
|
53
|
+
).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
fireEvent.click(screen.getByText("Sign me up"));
|
|
56
|
+
expect(
|
|
57
|
+
container.querySelector('[data-contentful-field-id="errorMessage"]')
|
|
58
|
+
).toBeInTheDocument();
|
|
59
|
+
fireEvent.change(screen.getByPlaceholderText("Enter your email here"), {
|
|
60
|
+
target: { value: "user@example.com" },
|
|
61
|
+
});
|
|
62
|
+
fireEvent.click(screen.getByText("Sign me up"));
|
|
63
|
+
expect(
|
|
64
|
+
container.querySelector('[data-contentful-field-id="successFeedback"]')
|
|
65
|
+
).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
18
68
|
describe("Rendering", () => {
|
|
19
69
|
it("renders with default props", () => {
|
|
20
70
|
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
@@ -176,9 +226,7 @@ describe("EmailInputBlock", () => {
|
|
|
176
226
|
describe("Themes", () => {
|
|
177
227
|
it("applies green theme background by default", () => {
|
|
178
228
|
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
179
|
-
expect(
|
|
180
|
-
container.querySelector(".bg-bg-fill-brand")
|
|
181
|
-
).toBeInTheDocument();
|
|
229
|
+
expect(container.querySelector(".bg-bg-fill-brand")).toBeInTheDocument();
|
|
182
230
|
});
|
|
183
231
|
|
|
184
232
|
it("uses min-height instead of fixed height on the container", () => {
|