@windstream/react-shared-components 0.2.89 → 0.2.90

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.
@@ -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 FloatingBannerProps = {
4
6
  title?: string;
5
7
  enableHeading?: boolean;
@@ -11,6 +13,10 @@ export type FloatingBannerProps = {
11
13
  background?: "green" | "navy" | "white" | "purple" | "blue" | "yellow";
12
14
  color?: "dark" | "light";
13
15
  maxWidth?: boolean;
16
+ /** Consumer-supplied DOM attributes for the rendered content fields. */
17
+ fieldProps?: FieldProps<
18
+ "title" | "subtitle" | "description" | "disclaimer" | "cta"
19
+ >;
14
20
  };
15
21
 
16
22
  export type ThemeKey =
@@ -7,22 +7,42 @@ import { render, screen } from "@testing-library/react";
7
7
  jest.mock("../button", () => ({
8
8
  __esModule: true,
9
9
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
- Button: ({ buttonLabel, disableAnimation, ...rest }: any) => (
11
- <button
12
- data-testid="cta-button"
13
- data-disable-animation={String(disableAnimation)}
14
- >
15
- {buttonLabel || "CTA"}
16
- </button>
17
- ),
18
- default: ({ buttonLabel, disableAnimation }: any) => (
19
- <button
20
- data-testid="cta-button"
21
- data-disable-animation={String(disableAnimation)}
22
- >
23
- {buttonLabel || "CTA"}
24
- </button>
25
- ),
10
+ Button: ({
11
+ buttonLabel,
12
+ disableAnimation,
13
+ fieldProps,
14
+ preDefinedFunctionExecution,
15
+ renderCheckPlans,
16
+ }: any) =>
17
+ preDefinedFunctionExecution === "check availability" && renderCheckPlans ? (
18
+ renderCheckPlans()
19
+ ) : (
20
+ <button
21
+ data-testid="cta-button"
22
+ data-disable-animation={String(disableAnimation)}
23
+ {...fieldProps}
24
+ >
25
+ {buttonLabel || "CTA"}
26
+ </button>
27
+ ),
28
+ default: ({
29
+ buttonLabel,
30
+ disableAnimation,
31
+ fieldProps,
32
+ preDefinedFunctionExecution,
33
+ renderCheckPlans,
34
+ }: any) =>
35
+ preDefinedFunctionExecution === "check availability" && renderCheckPlans ? (
36
+ renderCheckPlans()
37
+ ) : (
38
+ <button
39
+ data-testid="cta-button"
40
+ data-disable-animation={String(disableAnimation)}
41
+ {...fieldProps}
42
+ >
43
+ {buttonLabel || "CTA"}
44
+ </button>
45
+ ),
26
46
  }));
27
47
 
28
48
  jest.mock("@shared/components/video-link", () => ({
@@ -63,8 +83,10 @@ jest.mock("@shared/components/next-image", () => ({
63
83
  }));
64
84
 
65
85
  jest.mock("@shared/components/text", () => ({
66
- Text: ({ as: Tag = "span", children, className }: any) => (
67
- <Tag className={className}>{children}</Tag>
86
+ Text: ({ as: Tag = "span", children, className, ...rest }: any) => (
87
+ <Tag className={className} {...rest}>
88
+ {children}
89
+ </Tag>
68
90
  ),
69
91
  }));
70
92
 
@@ -94,6 +116,142 @@ const defaultProps: ImagePromoBarProps = {
94
116
  };
95
117
 
96
118
  describe("ImagePromoBar", () => {
119
+ it("tags block content, CTAs, disclaimers, checklist and image links", () => {
120
+ const tag = (field: string) => ({
121
+ "data-contentful-field-id": field,
122
+ "data-contentful-entry-id": "image-promo",
123
+ "data-contentful-locale": "en-US",
124
+ });
125
+ const { container } = render(
126
+ <ImagePromoBar
127
+ {...defaultProps}
128
+ brow="Brow"
129
+ title="Title"
130
+ subTitle="Subtitle"
131
+ description="Description"
132
+ checklist={["Check"]}
133
+ ctaDisclaimer="CTA disclaimer"
134
+ disclaimer="Disclaimer"
135
+ imageLinks={[
136
+ {
137
+ url: "/image",
138
+ image: "/image.png",
139
+ fieldProps: {
140
+ image: {
141
+ ...tag("image"),
142
+ "data-contentful-entry-id": "image-link-item",
143
+ },
144
+ },
145
+ },
146
+ ]}
147
+ cta={{
148
+ buttonLabel: "Primary",
149
+ fieldProps: { "data-contentful-field-id": "nestedPrimaryCta" },
150
+ }}
151
+ secondaryCta={{
152
+ buttonLabel: "Secondary",
153
+ fieldProps: { "data-contentful-field-id": "nestedSecondaryCta" },
154
+ }}
155
+ fieldProps={{
156
+ brow: tag("brow"),
157
+ title: tag("title"),
158
+ subTitle: tag("subTitle"),
159
+ description: tag("description"),
160
+ checklist: tag("checklist"),
161
+ ctaDisclaimer: tag("ctaDisclaimer"),
162
+ cta: tag("cta"),
163
+ secondaryCta: tag("secondaryCta"),
164
+ disclaimer: tag("disclaimer"),
165
+ imageLinks: tag("imageLinks"),
166
+ }}
167
+ />
168
+ );
169
+ [
170
+ "brow",
171
+ "title",
172
+ "subTitle",
173
+ "description",
174
+ "checklist",
175
+ "ctaDisclaimer",
176
+ "cta",
177
+ "secondaryCta",
178
+ "disclaimer",
179
+ "imageLinks",
180
+ ].forEach(field => {
181
+ expect(
182
+ container.querySelector(`[data-contentful-field-id="${field}"]`)
183
+ ).toHaveAttribute("data-contentful-entry-id", "image-promo");
184
+ });
185
+ expect(
186
+ container.querySelector(
187
+ '[data-contentful-entry-id="image-link-item"][data-contentful-field-id="image"]'
188
+ )
189
+ ).toBeInTheDocument();
190
+ expect(screen.getAllByTestId("cta-button")[0]).toHaveAttribute(
191
+ "data-contentful-field-id",
192
+ "nestedPrimaryCta"
193
+ );
194
+ expect(screen.getAllByTestId("cta-button")[1]).toHaveAttribute(
195
+ "data-contentful-field-id",
196
+ "nestedSecondaryCta"
197
+ );
198
+ });
199
+
200
+ it("applies block CTA tags to wrappers around custom check-plans content", () => {
201
+ const tag = (field: string) => ({
202
+ "data-contentful-field-id": field,
203
+ "data-contentful-entry-id": "image-promo",
204
+ });
205
+ const { container } = render(
206
+ <ImagePromoBar
207
+ {...defaultProps}
208
+ cta={{
209
+ preDefinedFunctionExecution: "check availability",
210
+ fieldProps: { "data-contentful-field-id": "primaryButton" },
211
+ }}
212
+ secondaryCta={{
213
+ preDefinedFunctionExecution: "check availability",
214
+ fieldProps: { "data-contentful-field-id": "secondaryButton" },
215
+ }}
216
+ renderCheckPlans={() => <button>Check availability</button>}
217
+ fieldProps={{
218
+ cta: tag("cta"),
219
+ secondaryCta: tag("secondaryCta"),
220
+ }}
221
+ />
222
+ );
223
+
224
+ const primaryWrapper = container.querySelector(".primary-cta");
225
+ const secondaryWrapper = container.querySelector(".secondary-cta");
226
+ expect(primaryWrapper).toHaveAttribute("data-contentful-field-id", "cta");
227
+ expect(secondaryWrapper).toHaveAttribute(
228
+ "data-contentful-field-id",
229
+ "secondaryCta"
230
+ );
231
+ expect(primaryWrapper).toHaveAttribute(
232
+ "data-contentful-entry-id",
233
+ "image-promo"
234
+ );
235
+ expect(secondaryWrapper).toHaveAttribute(
236
+ "data-contentful-entry-id",
237
+ "image-promo"
238
+ );
239
+ expect(primaryWrapper?.querySelector("button")).toHaveTextContent(
240
+ "Check availability"
241
+ );
242
+ expect(secondaryWrapper?.querySelector("button")).toHaveTextContent(
243
+ "Check availability"
244
+ );
245
+ expect(primaryWrapper?.querySelector("button")).not.toHaveAttribute(
246
+ "data-contentful-field-id",
247
+ "primaryButton"
248
+ );
249
+ expect(secondaryWrapper?.querySelector("button")).not.toHaveAttribute(
250
+ "data-contentful-field-id",
251
+ "secondaryButton"
252
+ );
253
+ });
254
+
97
255
  describe("Text content", () => {
98
256
  it("renders brow text when provided", () => {
99
257
  render(<ImagePromoBar {...defaultProps} brow="New" />);
@@ -37,6 +37,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
37
37
  onVideoEvent,
38
38
  renderCheckPlans,
39
39
  enableAnimation = false,
40
+ fieldProps,
40
41
  }) => {
41
42
  const hasVideoLink = Boolean(videoLink);
42
43
 
@@ -56,6 +57,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
56
57
  <Text
57
58
  as="div"
58
59
  className="subheading4 mb-4 text-text-brand xl:subheading2 xl:mb-3"
60
+ {...fieldProps?.brow}
59
61
  >
60
62
  {brow}
61
63
  </Text>
@@ -64,6 +66,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
64
66
  <Text
65
67
  as={enableHeading ? "h1" : "h2"}
66
68
  className="heading2 xl:heading1"
69
+ {...fieldProps?.title}
67
70
  >
68
71
  {title}
69
72
  </Text>
@@ -72,6 +75,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
72
75
  <Text
73
76
  as={enableHeading ? "h2" : "h3"}
74
77
  className="subheading3 mt-3 md:subheading1"
78
+ {...fieldProps?.subTitle}
75
79
  >
76
80
  {subTitle}
77
81
  </Text>
@@ -79,45 +83,54 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
79
83
  </div>
80
84
  {/* Content Section */}
81
85
  {description && (
82
- <Text as="div" className="body1">
86
+ <Text as="div" className="body1" {...fieldProps?.description}>
83
87
  {description}
84
88
  </Text>
85
89
  )}
86
90
  {/* Checklist Rendering */}
87
91
  {checklist.length > 0 && (
88
- <Checklist
89
- items={checklist}
90
- iconPosition="top"
91
- iconSize={24}
92
- listItemClassName="body1 text-text"
93
- listContainerClassName="mt-0 space-y-0 flex flex-col gap-3"
94
- />
92
+ <div {...fieldProps?.checklist}>
93
+ <Checklist
94
+ items={checklist}
95
+ iconPosition="top"
96
+ iconSize={24}
97
+ listItemClassName="body1 text-text"
98
+ listContainerClassName="mt-0 space-y-0 flex flex-col gap-3"
99
+ />
100
+ </div>
101
+ )}
102
+ {ctaDisclaimer && (
103
+ <div {...fieldProps?.ctaDisclaimer}>{ctaDisclaimer}</div>
95
104
  )}
96
- {ctaDisclaimer && <div>{ctaDisclaimer}</div>}
97
105
  {imageLinks.length > 0 && (
98
- <div className="flex gap-4">
106
+ <div className="flex gap-4" {...fieldProps?.imageLinks}>
99
107
  {/* Image Links Collection */}
100
- {imageLinks?.map(
101
- (link: { url: string; image: string }, index: number) => (
102
- <div key={index} className="image-link w-[147px]">
103
- <Link
104
- variant="unstyled"
105
- href={link.url}
106
- target="_blank"
107
- rel="noopener noreferrer"
108
- >
109
- <Image src={link.image} alt="icon-link" />
110
- </Link>
111
- </div>
112
- )
113
- )}
108
+ {imageLinks?.map((link, index: number) => (
109
+ <div
110
+ key={index}
111
+ className="image-link w-[147px]"
112
+ {...link.fieldProps?.image}
113
+ >
114
+ <Link
115
+ variant="unstyled"
116
+ href={link.url}
117
+ target="_blank"
118
+ rel="noopener noreferrer"
119
+ >
120
+ <Image src={link.image} alt="icon-link" />
121
+ </Link>
122
+ </div>
123
+ ))}
114
124
  </div>
115
125
  )}
116
126
  {/* CTAs and Disclaimers */}
117
127
  {(cta || secondaryCta) && (
118
128
  <div className="flex w-full flex-col gap-4 xl:flex-row">
119
129
  {cta && (
120
- <div className="primary-cta w-full xl:w-auto xl:shrink-0">
130
+ <div
131
+ className="primary-cta w-full xl:w-auto xl:shrink-0"
132
+ {...fieldProps?.cta}
133
+ >
121
134
  <Button
122
135
  {...cta}
123
136
  // Block-level `enableAnimation` takes precedence:
@@ -134,11 +147,15 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
134
147
  renderCheckPlans={renderCheckPlans}
135
148
  onModalButtonClick={onModalButtonClick}
136
149
  onClick={(e: React.MouseEvent) => onClick?.(e, "primary")}
150
+ fieldProps={cta.fieldProps}
137
151
  />
138
152
  </div>
139
153
  )}
140
154
  {secondaryCta && (
141
- <div className="secondary-cta w-full xl:w-auto xl:shrink-0">
155
+ <div
156
+ className="secondary-cta w-full xl:w-auto xl:shrink-0"
157
+ {...fieldProps?.secondaryCta}
158
+ >
142
159
  <Button
143
160
  {...secondaryCta}
144
161
  // Block-level `enableAnimation` takes precedence:
@@ -158,6 +175,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
158
175
  onClick={(e: React.MouseEvent) =>
159
176
  onClick?.(e, "secondary")
160
177
  }
178
+ fieldProps={secondaryCta.fieldProps}
161
179
  />
162
180
  </div>
163
181
  )}
@@ -209,7 +227,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
209
227
 
210
228
  {disclaimer && (
211
229
  <div className="mt-10 flex justify-center xl:mt-18">
212
- <div>{disclaimer}</div>
230
+ <div {...fieldProps?.disclaimer}>{disclaimer}</div>
213
231
  </div>
214
232
  )}
215
233
  </div>
@@ -4,6 +4,7 @@ import {
4
4
  VideoAnalyticsHandler,
5
5
  VideoLinkProps,
6
6
  } from "@shared/components/video-link";
7
+ import type { FieldProps } from "@shared/types/field-props";
7
8
  import { CheckPlansProps } from "@shared/types/micro-components";
8
9
 
9
10
  export type ImagePromoBarProps = {
@@ -22,7 +23,11 @@ export type ImagePromoBarProps = {
22
23
  openDescriptionLinksOnANewTab: boolean;
23
24
  checklist: string[];
24
25
  disclaimer: React.ReactNode;
25
- imageLinks: { url: string; image: string }[];
26
+ imageLinks: {
27
+ url: string;
28
+ image: string;
29
+ fieldProps?: FieldProps<"image">;
30
+ }[];
26
31
  cta: any;
27
32
  secondaryCta: any;
28
33
  ctaDisclaimer: React.ReactNode;
@@ -34,4 +39,17 @@ export type ImagePromoBarProps = {
34
39
  onModalButtonClick?: (id?: string) => void;
35
40
  onClick?: (event: React.MouseEvent, source: "primary" | "secondary") => void;
36
41
  renderCheckPlans?: (overrides?: CheckPlansProps) => React.ReactNode;
42
+ /** Consumer-supplied DOM attributes for the rendered content fields. */
43
+ fieldProps?: FieldProps<
44
+ | "brow"
45
+ | "title"
46
+ | "subTitle"
47
+ | "description"
48
+ | "checklist"
49
+ | "ctaDisclaimer"
50
+ | "cta"
51
+ | "secondaryCta"
52
+ | "disclaimer"
53
+ | "imageLinks"
54
+ >;
37
55
  };