@windstream/react-shared-components 0.2.86 → 0.2.87
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 +19 -3
- package/dist/contentful/index.esm.js +4 -4
- 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/package.json +1 -1
- package/src/contentful/blocks/accordion/index.test.tsx +53 -0
- package/src/contentful/blocks/accordion/index.tsx +7 -2
- package/src/contentful/blocks/accordion/types.ts +4 -0
- package/src/contentful/blocks/callout/{callout.stories.tsx → Callout.stories.tsx} +1 -1
- package/src/contentful/blocks/callout/field-props.test.tsx +184 -0
- package/src/contentful/blocks/callout/index.tsx +26 -4
- package/src/contentful/blocks/callout/types.ts +17 -1
- package/src/contentful/blocks/cards/blog-card/index.tsx +6 -2
- package/src/contentful/blocks/cards/blog-card/types.ts +4 -0
- package/src/contentful/blocks/cards/floating-image-card/index.tsx +4 -2
- package/src/contentful/blocks/cards/floating-image-card/types.ts +4 -0
- package/src/contentful/blocks/cards/full-image-card/index.tsx +24 -5
- package/src/contentful/blocks/cards/full-image-card/types.ts +4 -0
- package/src/contentful/blocks/cards/simple-card/index.tsx +21 -3
- package/src/contentful/blocks/cards/simple-card/types.ts +4 -0
- package/src/contentful/blocks/comparison-table/index.test.tsx +75 -0
- package/src/contentful/blocks/comparison-table/index.tsx +15 -3
- package/src/contentful/blocks/comparison-table/types.ts +8 -2
- package/src/contentful/blocks/cta-callout/index.test.tsx +93 -1
- package/src/contentful/blocks/cta-callout/index.tsx +9 -2
- package/src/contentful/blocks/cta-callout/types.ts +3 -0
- package/src/contentful/blocks/modal/index.test.tsx +75 -2
- package/src/contentful/blocks/modal/index.tsx +11 -2
- package/src/contentful/blocks/modal/types.ts +4 -0
- package/src/contentful/blocks/navigation/{navigation.stories.tsx → Navigation.stories.tsx} +1 -1
|
@@ -97,7 +97,13 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
97
97
|
>
|
|
98
98
|
<NextImage
|
|
99
99
|
src={card.image}
|
|
100
|
-
alt={
|
|
100
|
+
alt={
|
|
101
|
+
card.imageAltText ??
|
|
102
|
+
card.imageDescription ??
|
|
103
|
+
card.imageAlt ??
|
|
104
|
+
card.title ??
|
|
105
|
+
"card-image"
|
|
106
|
+
}
|
|
101
107
|
width={card.imageWidth || 400}
|
|
102
108
|
height={card.imageHeight || 280}
|
|
103
109
|
className={cx("w-full", !margin && "rounded-t-3xl")}
|
|
@@ -124,7 +130,13 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
124
130
|
>
|
|
125
131
|
<NextImage
|
|
126
132
|
src={card.image}
|
|
127
|
-
alt={
|
|
133
|
+
alt={
|
|
134
|
+
card.imageAltText ??
|
|
135
|
+
card.imageDescription ??
|
|
136
|
+
card.imageAlt ??
|
|
137
|
+
card.title ??
|
|
138
|
+
"card-icon"
|
|
139
|
+
}
|
|
128
140
|
width={imgWidth}
|
|
129
141
|
height={imgHeight}
|
|
130
142
|
className={cx(
|
|
@@ -144,6 +156,7 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
144
156
|
as="h3"
|
|
145
157
|
className="heading6 mb-2 text-text md:heading6"
|
|
146
158
|
style={textColorStyle}
|
|
159
|
+
{...card.fieldProps?.title}
|
|
147
160
|
>
|
|
148
161
|
{card.title}
|
|
149
162
|
</Text>
|
|
@@ -300,6 +313,7 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
300
313
|
as="div"
|
|
301
314
|
className="body1 text-text"
|
|
302
315
|
style={textColorStyle}
|
|
316
|
+
{...card.fieldProps?.body}
|
|
303
317
|
>
|
|
304
318
|
{card.body}
|
|
305
319
|
</Text>
|
|
@@ -309,6 +323,7 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
309
323
|
as="div"
|
|
310
324
|
className="body1 text-text"
|
|
311
325
|
style={textColorStyle}
|
|
326
|
+
{...card.fieldProps?.richText}
|
|
312
327
|
>
|
|
313
328
|
{card.richText}
|
|
314
329
|
</Text>
|
|
@@ -318,7 +333,10 @@ export const SimpleCard: React.FC<SimpleCardProps> = ({
|
|
|
318
333
|
</div>
|
|
319
334
|
|
|
320
335
|
{card.cta ? (
|
|
321
|
-
<div
|
|
336
|
+
<div
|
|
337
|
+
className={cx(!isCtaOnly && "mt-2", ctaAlignmentClass)}
|
|
338
|
+
{...card.fieldProps?.cta}
|
|
339
|
+
>
|
|
322
340
|
<Button
|
|
323
341
|
linkVariant="unstyled"
|
|
324
342
|
linkClassName={cx(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
import type { ButtonProps } from "../../button/types";
|
|
3
3
|
|
|
4
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
5
|
+
|
|
4
6
|
export type PinwheelColor =
|
|
5
7
|
| "Blue-#307998"
|
|
6
8
|
| "Green-#209A61"
|
|
@@ -54,6 +56,8 @@ export type Item = {
|
|
|
54
56
|
/** Each entry uses a `label|value` separator. */
|
|
55
57
|
workingHours?: string[];
|
|
56
58
|
shadow?: boolean;
|
|
59
|
+
/** Per-field DOM attributes supplied by the consumer or parent Callout item. */
|
|
60
|
+
fieldProps?: FieldProps<"title" | "body" | "richText" | "cta">;
|
|
57
61
|
};
|
|
58
62
|
|
|
59
63
|
export type SimpleCardProps = {
|
|
@@ -41,6 +41,81 @@ describe("ComparisonTable", () => {
|
|
|
41
41
|
expect(container.querySelector(".component-container")).toBeInTheDocument();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
describe("fieldProps", () => {
|
|
45
|
+
it("tags the block fields and preserves consumer-supplied row tags", () => {
|
|
46
|
+
const tag = (entry: string, field: string) => ({
|
|
47
|
+
"data-contentful-entry-id": entry,
|
|
48
|
+
"data-contentful-field-id": field,
|
|
49
|
+
"data-contentful-locale": "en-US",
|
|
50
|
+
});
|
|
51
|
+
const { container } = render(
|
|
52
|
+
<ComparisonTable
|
|
53
|
+
{...defaultProps}
|
|
54
|
+
fieldProps={{
|
|
55
|
+
title: tag("comparison", "title"),
|
|
56
|
+
table: tag("comparison", "table"),
|
|
57
|
+
disclaimer: tag("comparison", "disclaimer"),
|
|
58
|
+
}}
|
|
59
|
+
table={
|
|
60
|
+
<table>
|
|
61
|
+
<tbody>
|
|
62
|
+
<tr {...tag("row-1", "feature")}>
|
|
63
|
+
<td>Speed</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<tr {...tag("row-2", "feature")}>
|
|
66
|
+
<td>Price</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
expect(screen.getByText("Compare Plans")).toHaveAttribute(
|
|
74
|
+
"data-contentful-field-id",
|
|
75
|
+
"title"
|
|
76
|
+
);
|
|
77
|
+
expect(
|
|
78
|
+
container.querySelector(".comparison-table-container")
|
|
79
|
+
).toHaveAttribute("data-contentful-field-id", "table");
|
|
80
|
+
expect(
|
|
81
|
+
screen.getByText("* Terms and conditions apply").parentElement
|
|
82
|
+
).toHaveAttribute("data-contentful-field-id", "disclaimer");
|
|
83
|
+
expect(screen.getByText("Speed").closest("tr")).toHaveAttribute(
|
|
84
|
+
"data-contentful-entry-id",
|
|
85
|
+
"row-1"
|
|
86
|
+
);
|
|
87
|
+
expect(screen.getByText("Price").closest("tr")).toHaveAttribute(
|
|
88
|
+
"data-contentful-entry-id",
|
|
89
|
+
"row-2"
|
|
90
|
+
);
|
|
91
|
+
container
|
|
92
|
+
.querySelectorAll("[data-contentful-field-id]")
|
|
93
|
+
.forEach(element => {
|
|
94
|
+
expect(element).toHaveAttribute("data-contentful-locale", "en-US");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("supports partial fieldProps", () => {
|
|
99
|
+
const { container } = render(
|
|
100
|
+
<ComparisonTable
|
|
101
|
+
{...defaultProps}
|
|
102
|
+
fieldProps={{ table: { "data-contentful-field-id": "table" } }}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
expect(
|
|
106
|
+
container.querySelectorAll("[data-contentful-field-id]")
|
|
107
|
+
).toHaveLength(1);
|
|
108
|
+
expect(screen.getByText("Compare Plans")).not.toHaveAttribute(
|
|
109
|
+
"data-contentful-field-id"
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("does not add tagging attributes when fieldProps is omitted", () => {
|
|
114
|
+
const { container } = render(<ComparisonTable {...defaultProps} />);
|
|
115
|
+
expect(container.querySelector("[data-contentful-field-id]")).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
44
119
|
it("renders the title as h2", () => {
|
|
45
120
|
render(<ComparisonTable {...defaultProps} />);
|
|
46
121
|
const title = screen.getByText("Compare Plans");
|
|
@@ -8,19 +8,31 @@ export const ComparisonTable: React.FC<ComparisonTableProps> = ({
|
|
|
8
8
|
disclaimer,
|
|
9
9
|
table,
|
|
10
10
|
maxWidth = true,
|
|
11
|
+
fieldProps,
|
|
11
12
|
}) => {
|
|
12
13
|
return (
|
|
13
14
|
<div className="component-container">
|
|
14
15
|
<div
|
|
15
16
|
className={`mx-5 mb-5 mt-8 md:mt-18 ${maxWidth ? "max-w-120 xl:mx-auto" : ""}`}
|
|
16
17
|
>
|
|
17
|
-
<Text
|
|
18
|
+
<Text
|
|
19
|
+
as="h2"
|
|
20
|
+
className="heading2 md:heading1 md:text-center"
|
|
21
|
+
{...fieldProps?.title}
|
|
22
|
+
>
|
|
18
23
|
{title}
|
|
19
24
|
</Text>
|
|
20
|
-
<div
|
|
25
|
+
<div
|
|
26
|
+
className="comparison-table-container mt-5 p-5 pt-0 md:mt-10 md:px-10 md:py-9"
|
|
27
|
+
{...fieldProps?.table}
|
|
28
|
+
>
|
|
21
29
|
{table}
|
|
22
30
|
</div>
|
|
23
|
-
<Text
|
|
31
|
+
<Text
|
|
32
|
+
as="div"
|
|
33
|
+
className="micro mt-8 text-center"
|
|
34
|
+
{...fieldProps?.disclaimer}
|
|
35
|
+
>
|
|
24
36
|
{disclaimer}
|
|
25
37
|
</Text>
|
|
26
38
|
</div>
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
4
|
+
|
|
1
5
|
export type ComparisonTableProps = {
|
|
2
6
|
title?: string;
|
|
3
|
-
disclaimer?:
|
|
4
|
-
table:
|
|
7
|
+
disclaimer?: ReactNode;
|
|
8
|
+
table: ReactNode;
|
|
5
9
|
maxWidth?: boolean;
|
|
10
|
+
/** DOM attributes for the block fields. Tag individual rows in the consumer-supplied `table` node. */
|
|
11
|
+
fieldProps?: FieldProps<"title" | "table" | "disclaimer">;
|
|
6
12
|
};
|
|
@@ -19,8 +19,12 @@ jest.mock("@shared/contentful/blocks/button", () => ({
|
|
|
19
19
|
data-testid="cta-button"
|
|
20
20
|
data-size={JSON.stringify(props.size)}
|
|
21
21
|
data-href={props.href}
|
|
22
|
+
{...props.fieldProps}
|
|
22
23
|
>
|
|
23
|
-
{props.
|
|
24
|
+
{props.preDefinedFunctionExecution === "check availability" &&
|
|
25
|
+
props.renderCheckPlans
|
|
26
|
+
? props.renderCheckPlans()
|
|
27
|
+
: props.buttonLabel || props.children}
|
|
24
28
|
</button>
|
|
25
29
|
),
|
|
26
30
|
}));
|
|
@@ -37,6 +41,94 @@ describe("CtaCallout", () => {
|
|
|
37
41
|
descriptionAlignment: "center",
|
|
38
42
|
};
|
|
39
43
|
|
|
44
|
+
describe("fieldProps", () => {
|
|
45
|
+
it("tags all block fields without changing nested button tags", () => {
|
|
46
|
+
const tag = (field: string) => ({
|
|
47
|
+
"data-contentful-field-id": field,
|
|
48
|
+
"data-contentful-entry-id": "cta-callout",
|
|
49
|
+
"data-contentful-locale": "en-US",
|
|
50
|
+
});
|
|
51
|
+
const { container } = render(
|
|
52
|
+
<CtaCallout
|
|
53
|
+
{...defaultProps}
|
|
54
|
+
button={{
|
|
55
|
+
...defaultProps.button,
|
|
56
|
+
fieldProps: { "data-contentful-field-id": "buttonLabel" },
|
|
57
|
+
}}
|
|
58
|
+
fieldProps={{
|
|
59
|
+
title: tag("title"),
|
|
60
|
+
subTitle: tag("subTitle"),
|
|
61
|
+
description: tag("description"),
|
|
62
|
+
button: tag("button"),
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
expect(screen.getByText(defaultProps.title!)).toHaveAttribute(
|
|
67
|
+
"data-contentful-field-id",
|
|
68
|
+
"title"
|
|
69
|
+
);
|
|
70
|
+
expect(screen.getByText(defaultProps.subTitle!)).toHaveAttribute(
|
|
71
|
+
"data-contentful-field-id",
|
|
72
|
+
"subTitle"
|
|
73
|
+
);
|
|
74
|
+
expect(
|
|
75
|
+
screen.getByText("Choose the plan that works for you.").parentElement
|
|
76
|
+
).toHaveAttribute("data-contentful-field-id", "description");
|
|
77
|
+
expect(screen.getByTestId("cta-button").parentElement).toHaveAttribute(
|
|
78
|
+
"data-contentful-field-id",
|
|
79
|
+
"button"
|
|
80
|
+
);
|
|
81
|
+
expect(screen.getByTestId("cta-button")).toHaveAttribute(
|
|
82
|
+
"data-contentful-field-id",
|
|
83
|
+
"buttonLabel"
|
|
84
|
+
);
|
|
85
|
+
container
|
|
86
|
+
.querySelectorAll("[data-contentful-entry-id]")
|
|
87
|
+
.forEach(element => {
|
|
88
|
+
expect(element).toHaveAttribute(
|
|
89
|
+
"data-contentful-entry-id",
|
|
90
|
+
"cta-callout"
|
|
91
|
+
);
|
|
92
|
+
expect(element).toHaveAttribute("data-contentful-locale", "en-US");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("tags the CTA region for custom renderers", () => {
|
|
97
|
+
render(
|
|
98
|
+
<CtaCallout
|
|
99
|
+
button={{ preDefinedFunctionExecution: "check availability" }}
|
|
100
|
+
renderCheckPlans={() => <span>Custom check plans</span>}
|
|
101
|
+
fieldProps={{ button: { "data-contentful-field-id": "button" } }}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
expect(screen.getByTestId("cta-button").parentElement).toHaveAttribute(
|
|
105
|
+
"data-contentful-field-id",
|
|
106
|
+
"button"
|
|
107
|
+
);
|
|
108
|
+
expect(screen.getByText("Custom check plans")).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("does not add tagging attributes when fieldProps is omitted", () => {
|
|
112
|
+
const { container } = render(<CtaCallout {...defaultProps} />);
|
|
113
|
+
expect(container.querySelector("[data-contentful-field-id]")).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("supports partial fieldProps and does not create absent fields", () => {
|
|
117
|
+
const { container } = render(
|
|
118
|
+
<CtaCallout
|
|
119
|
+
title="Only title"
|
|
120
|
+
fieldProps={{
|
|
121
|
+
title: { "data-contentful-field-id": "title" },
|
|
122
|
+
description: { "data-contentful-field-id": "description" },
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
expect(
|
|
127
|
+
container.querySelectorAll("[data-contentful-field-id]")
|
|
128
|
+
).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
40
132
|
it("renders the component", () => {
|
|
41
133
|
const { container } = render(<CtaCallout {...defaultProps} />);
|
|
42
134
|
expect(container.firstChild).toBeInTheDocument();
|
|
@@ -17,6 +17,7 @@ export const CtaCallout: React.FC<CtaCalloutProps> = ({
|
|
|
17
17
|
maxWidth = true,
|
|
18
18
|
onModalButtonClick,
|
|
19
19
|
renderCheckPlans,
|
|
20
|
+
fieldProps,
|
|
20
21
|
}) => {
|
|
21
22
|
const bgColorClasses: Record<ThemeKey, string> = {
|
|
22
23
|
navy: "bg-[#00002D]",
|
|
@@ -28,7 +29,7 @@ export const CtaCallout: React.FC<CtaCalloutProps> = ({
|
|
|
28
29
|
};
|
|
29
30
|
return (
|
|
30
31
|
<div
|
|
31
|
-
className={`${bgColorClasses[background]} component-container px-5 py-16 lg:px-13 lg:py-24
|
|
32
|
+
className={`${bgColorClasses[background]} component-container w-full px-5 py-16 lg:px-13 lg:py-24`}
|
|
32
33
|
>
|
|
33
34
|
<div
|
|
34
35
|
className={`${maxWidth ? "mx-auto max-w-120" : ""} color-${color} flex flex-col ${color == "dark" ? "text-text" : "text-text-inverse"}`}
|
|
@@ -37,6 +38,7 @@ export const CtaCallout: React.FC<CtaCalloutProps> = ({
|
|
|
37
38
|
<Text
|
|
38
39
|
as={enableHeading ? "h1" : "h2"}
|
|
39
40
|
className={`heading2 lg:heading1 text-${contentAlignment}`}
|
|
41
|
+
{...fieldProps?.title}
|
|
40
42
|
>
|
|
41
43
|
{title}
|
|
42
44
|
</Text>
|
|
@@ -45,6 +47,7 @@ export const CtaCallout: React.FC<CtaCalloutProps> = ({
|
|
|
45
47
|
<Text
|
|
46
48
|
as="h3"
|
|
47
49
|
className={`subheading3 pt-2 lg:subheading1 lg:pt-3 text-${contentAlignment}`}
|
|
50
|
+
{...fieldProps?.subTitle}
|
|
48
51
|
>
|
|
49
52
|
{subTitle}
|
|
50
53
|
</Text>
|
|
@@ -52,12 +55,16 @@ export const CtaCallout: React.FC<CtaCalloutProps> = ({
|
|
|
52
55
|
{description && (
|
|
53
56
|
<div
|
|
54
57
|
className={`pt-2 text-body1 lg:pt-3 text-${descriptionAlignment} break-words [&_table]:w-full`}
|
|
58
|
+
{...fieldProps?.description}
|
|
55
59
|
>
|
|
56
60
|
{description}
|
|
57
61
|
</div>
|
|
58
62
|
)}
|
|
59
63
|
{button || renderCheckPlans ? (
|
|
60
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
className="flex justify-center pt-5 lg:pt-14"
|
|
66
|
+
{...fieldProps?.button}
|
|
67
|
+
>
|
|
61
68
|
<Button
|
|
62
69
|
{...button}
|
|
63
70
|
renderCheckPlans={renderCheckPlans}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
3
4
|
import { CheckPlansProps } from "@shared/types/micro-components";
|
|
4
5
|
|
|
5
6
|
export type CtaCalloutProps = {
|
|
@@ -15,6 +16,8 @@ export type CtaCalloutProps = {
|
|
|
15
16
|
maxWidth?: boolean;
|
|
16
17
|
onModalButtonClick?: (id?: string) => void;
|
|
17
18
|
renderCheckPlans?: (overrides?: CheckPlansProps) => React.ReactNode;
|
|
19
|
+
/** Consumer-supplied DOM attributes; `button` tags the CTA region, including custom renderers. */
|
|
20
|
+
fieldProps?: FieldProps<"title" | "subTitle" | "description" | "button">;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
export type ThemeKey =
|
|
@@ -51,8 +51,10 @@ jest.mock("@shared/components/material-icon", () => ({
|
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
53
|
jest.mock("@shared/components/text", () => ({
|
|
54
|
-
Text: ({ as: Tag = "span", children, className }: any) => (
|
|
55
|
-
<Tag className={className}
|
|
54
|
+
Text: ({ as: Tag = "span", children, className, ...rest }: any) => (
|
|
55
|
+
<Tag className={className} {...rest}>
|
|
56
|
+
{children}
|
|
57
|
+
</Tag>
|
|
56
58
|
),
|
|
57
59
|
}));
|
|
58
60
|
|
|
@@ -74,6 +76,77 @@ describe("Modal", () => {
|
|
|
74
76
|
|
|
75
77
|
beforeEach(() => jest.clearAllMocks());
|
|
76
78
|
|
|
79
|
+
describe("fieldProps", () => {
|
|
80
|
+
it("tags title, description and content without changing child tags", () => {
|
|
81
|
+
const tag = (field: string) => ({
|
|
82
|
+
"data-contentful-field-id": field,
|
|
83
|
+
"data-contentful-entry-id": "modal",
|
|
84
|
+
"data-contentful-locale": "en-US",
|
|
85
|
+
});
|
|
86
|
+
const { container } = render(
|
|
87
|
+
<Modal
|
|
88
|
+
{...defaultProps}
|
|
89
|
+
title="Tagged modal"
|
|
90
|
+
description="Tagged description"
|
|
91
|
+
content={<span {...tag("nested")}>Nested content</span>}
|
|
92
|
+
fieldProps={{
|
|
93
|
+
title: tag("title"),
|
|
94
|
+
description: tag("description"),
|
|
95
|
+
content: tag("content"),
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<span>Extra child</span>
|
|
99
|
+
</Modal>
|
|
100
|
+
);
|
|
101
|
+
expect(screen.getByText("Tagged modal")).toHaveAttribute(
|
|
102
|
+
"data-contentful-field-id",
|
|
103
|
+
"title"
|
|
104
|
+
);
|
|
105
|
+
expect(screen.getByText("Tagged description")).toHaveAttribute(
|
|
106
|
+
"data-contentful-field-id",
|
|
107
|
+
"description"
|
|
108
|
+
);
|
|
109
|
+
expect(screen.getByText("Nested content").parentElement).toHaveAttribute(
|
|
110
|
+
"data-contentful-field-id",
|
|
111
|
+
"content"
|
|
112
|
+
);
|
|
113
|
+
expect(screen.getByText("Nested content")).toHaveAttribute(
|
|
114
|
+
"data-contentful-field-id",
|
|
115
|
+
"nested"
|
|
116
|
+
);
|
|
117
|
+
expect(screen.getByText("Extra child").parentElement).toBe(
|
|
118
|
+
screen.getByText("Nested content").parentElement
|
|
119
|
+
);
|
|
120
|
+
container
|
|
121
|
+
.querySelectorAll("[data-contentful-field-id]")
|
|
122
|
+
.forEach(element => {
|
|
123
|
+
expect(element).toHaveAttribute("data-contentful-entry-id", "modal");
|
|
124
|
+
expect(element).toHaveAttribute("data-contentful-locale", "en-US");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("supports partial fieldProps without creating absent fields", () => {
|
|
129
|
+
const { container } = render(
|
|
130
|
+
<Modal
|
|
131
|
+
{...defaultProps}
|
|
132
|
+
title="Title only"
|
|
133
|
+
fieldProps={{
|
|
134
|
+
title: { "data-contentful-field-id": "title" },
|
|
135
|
+
description: { "data-contentful-field-id": "description" },
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
expect(
|
|
140
|
+
container.querySelectorAll("[data-contentful-field-id]")
|
|
141
|
+
).toHaveLength(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("does not add tagging attributes when fieldProps is omitted", () => {
|
|
145
|
+
const { container } = render(<Modal {...defaultProps} title="No tags" />);
|
|
146
|
+
expect(container.querySelector("[data-contentful-field-id]")).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
77
150
|
describe("Rendering", () => {
|
|
78
151
|
it("renders dialog root with open state", () => {
|
|
79
152
|
render(<Modal {...defaultProps} />);
|
|
@@ -31,6 +31,7 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
|
|
|
31
31
|
bodyClassName,
|
|
32
32
|
type = "modal",
|
|
33
33
|
index = 0,
|
|
34
|
+
fieldProps,
|
|
34
35
|
} = props;
|
|
35
36
|
|
|
36
37
|
return (
|
|
@@ -93,18 +94,26 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
|
|
|
93
94
|
"mb-4 md:mb-[60px]"
|
|
94
95
|
)}
|
|
95
96
|
as="h2"
|
|
97
|
+
{...fieldProps?.title}
|
|
96
98
|
>
|
|
97
99
|
{title}
|
|
98
100
|
</Text>
|
|
99
101
|
) : null}
|
|
100
102
|
|
|
101
103
|
{description ? (
|
|
102
|
-
<Text
|
|
104
|
+
<Text
|
|
105
|
+
as="div"
|
|
106
|
+
className="mb-8 sm:mb-5"
|
|
107
|
+
{...fieldProps?.description}
|
|
108
|
+
>
|
|
103
109
|
{description}
|
|
104
110
|
</Text>
|
|
105
111
|
) : null}
|
|
106
112
|
|
|
107
|
-
<div
|
|
113
|
+
<div
|
|
114
|
+
className="flex flex-col gap-4 sm:gap-5"
|
|
115
|
+
{...fieldProps?.content}
|
|
116
|
+
>
|
|
108
117
|
{content}
|
|
109
118
|
{children ? children : null}
|
|
110
119
|
</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
import type { FieldProps } from "@shared/types/field-props";
|
|
4
|
+
|
|
3
5
|
export type ModalProps = {
|
|
4
6
|
isOpen: boolean;
|
|
5
7
|
onRequestClose?: () => void;
|
|
@@ -9,4 +11,6 @@ export type ModalProps = {
|
|
|
9
11
|
content?: React.ReactNode;
|
|
10
12
|
children?: React.ReactNode;
|
|
11
13
|
bodyClassName?: string;
|
|
14
|
+
/** DOM attributes for modal fields; `content` tags the region containing content and children. */
|
|
15
|
+
fieldProps?: FieldProps<"title" | "description" | "content">;
|
|
12
16
|
};
|