@windstream/react-shared-components 0.2.42 → 0.2.44
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 +1 -1
- package/dist/contentful/index.esm.js +1 -1
- 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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/animation-wrapper/index.test.tsx +6 -6
- package/src/components/video-link/index.test.tsx +43 -4
- package/src/components/video-link/index.tsx +32 -4
- package/src/components/video-link/types.ts +1 -0
- package/src/contentful/blocks/blogs-grid/index.test.tsx +45 -14
- package/src/contentful/blocks/dynamic-tabs/index.test.tsx +2 -2
- package/src/contentful/blocks/dynamic-tabs/index.tsx +2 -2
- package/src/contentful/blocks/email-input-block/index.test.tsx +32 -41
- package/src/contentful/blocks/find-kinetic/index.test.tsx +3 -2
- package/src/contentful/blocks/image-promo-bar/index.test.tsx +55 -10
- package/src/contentful/blocks/image-promo-bar/index.tsx +5 -2
- package/src/contentful/blocks/image-promo-bar/types.ts +1 -1
- package/src/contentful/blocks/ordered-list/index.tsx +2 -2
- package/src/contentful/blocks/primary-hero/index.tsx +53 -41
- package/src/hooks/contentful/use-contentful-rich-text.test.tsx +4 -3
|
@@ -14,17 +14,31 @@ jest.mock("next/navigation", () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
jest.mock("@shared/components/pagination", () => ({
|
|
17
|
-
Pagination: ({
|
|
17
|
+
Pagination: ({
|
|
18
|
+
currentPage,
|
|
19
|
+
totalPages,
|
|
20
|
+
onPageChange,
|
|
21
|
+
getPageHref,
|
|
22
|
+
ariaLabel,
|
|
23
|
+
}: any) => (
|
|
18
24
|
<nav aria-label={ariaLabel} data-testid="pagination">
|
|
19
25
|
<span>
|
|
20
26
|
Page {currentPage} of {totalPages}
|
|
21
27
|
</span>
|
|
22
|
-
<
|
|
28
|
+
<a
|
|
29
|
+
data-testid="page-next"
|
|
30
|
+
href={getPageHref?.(2)}
|
|
31
|
+
onClick={() => onPageChange(2)}
|
|
32
|
+
>
|
|
23
33
|
Next
|
|
24
|
-
</
|
|
25
|
-
<
|
|
34
|
+
</a>
|
|
35
|
+
<a
|
|
36
|
+
data-testid="page-first"
|
|
37
|
+
href={getPageHref?.(1)}
|
|
38
|
+
onClick={() => onPageChange(1)}
|
|
39
|
+
>
|
|
26
40
|
First
|
|
27
|
-
</
|
|
41
|
+
</a>
|
|
28
42
|
</nav>
|
|
29
43
|
),
|
|
30
44
|
}));
|
|
@@ -222,21 +236,38 @@ describe("BlogGrid", () => {
|
|
|
222
236
|
});
|
|
223
237
|
|
|
224
238
|
describe("Pagination", () => {
|
|
225
|
-
it("
|
|
239
|
+
it("builds page href with page param", () => {
|
|
226
240
|
render(<BlogGrid {...defaultProps} />);
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
expect(screen.getByTestId("page-next")).toHaveAttribute(
|
|
242
|
+
"href",
|
|
243
|
+
"/blog?page=2"
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("removes page param when building href for page 1", () => {
|
|
248
|
+
mockSearchParams = new URLSearchParams("page=2");
|
|
249
|
+
render(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
250
|
+
expect(screen.getByTestId("page-first")).toHaveAttribute("href", "/blog");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("keeps other params when building page href", () => {
|
|
254
|
+
mockSearchParams = new URLSearchParams("category=tech");
|
|
255
|
+
render(<BlogGrid {...defaultProps} />);
|
|
256
|
+
expect(screen.getByTestId("page-first")).toHaveAttribute(
|
|
257
|
+
"href",
|
|
258
|
+
"/blog?category=tech"
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("scrolls to top when currentPage changes", () => {
|
|
263
|
+
const { rerender } = render(<BlogGrid {...defaultProps} />);
|
|
264
|
+
expect(window.scrollTo).not.toHaveBeenCalled();
|
|
265
|
+
rerender(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
229
266
|
expect(window.scrollTo).toHaveBeenCalledWith({
|
|
230
267
|
top: 0,
|
|
231
268
|
behavior: "smooth",
|
|
232
269
|
});
|
|
233
270
|
});
|
|
234
|
-
|
|
235
|
-
it("removes page param when navigating to page 1", () => {
|
|
236
|
-
render(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
237
|
-
fireEvent.click(screen.getByTestId("page-first"));
|
|
238
|
-
expect(mockPush).toHaveBeenCalledWith("/blog?", { scroll: false });
|
|
239
|
-
});
|
|
240
271
|
});
|
|
241
272
|
|
|
242
273
|
describe("Cover image handling", () => {
|
|
@@ -313,7 +313,7 @@ describe("DynamicTabs", () => {
|
|
|
313
313
|
const { container } = render(
|
|
314
314
|
<DynamicTabs {...orderedProps} navigationType={false} />
|
|
315
315
|
);
|
|
316
|
-
expect(container.querySelector(".hidden.
|
|
316
|
+
expect(container.querySelector(".hidden.lg\\:block")).toBeInTheDocument();
|
|
317
317
|
expect(screen.getAllByText("Group A").length).toBeGreaterThan(0);
|
|
318
318
|
});
|
|
319
319
|
|
|
@@ -321,7 +321,7 @@ describe("DynamicTabs", () => {
|
|
|
321
321
|
const { container } = render(
|
|
322
322
|
<DynamicTabs {...orderedProps} mobileSidebarTabType={false} />
|
|
323
323
|
);
|
|
324
|
-
expect(container.querySelector(".
|
|
324
|
+
expect(container.querySelector(".lg\\:hidden")).toBeInTheDocument();
|
|
325
325
|
expect(screen.getAllByText("Item B1").length).toBeGreaterThan(0);
|
|
326
326
|
});
|
|
327
327
|
});
|
|
@@ -103,7 +103,7 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
103
103
|
|
|
104
104
|
{dynamic && isOrderedList ? (
|
|
105
105
|
<>
|
|
106
|
-
<div className="
|
|
106
|
+
<div className="lg:hidden">
|
|
107
107
|
{mobileSidebarTabType ? (
|
|
108
108
|
<OrderedTabView
|
|
109
109
|
items={items}
|
|
@@ -122,7 +122,7 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
122
122
|
)}
|
|
123
123
|
</div>
|
|
124
124
|
|
|
125
|
-
<div className="hidden
|
|
125
|
+
<div className="hidden lg:block">
|
|
126
126
|
{navigationType ? (
|
|
127
127
|
<OrderedTabView
|
|
128
128
|
items={items}
|
|
@@ -99,24 +99,27 @@ describe("EmailInputBlock", () => {
|
|
|
99
99
|
const input = screen.getByPlaceholderText("Enter your email here");
|
|
100
100
|
fireEvent.change(input, { target: { value: "invalid" } });
|
|
101
101
|
fireEvent.click(screen.getByText("Sign me up"));
|
|
102
|
-
expect(
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument();
|
|
103
|
+
expect(defaultProps.onSubmit).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("shows required error for empty email", () => {
|
|
107
|
+
render(<EmailInputBlock {...defaultProps} />);
|
|
108
|
+
fireEvent.click(screen.getByText("Sign me up"));
|
|
109
|
+
expect(screen.getByText("Email is required")).toBeInTheDocument();
|
|
105
110
|
expect(defaultProps.onSubmit).not.toHaveBeenCalled();
|
|
106
111
|
});
|
|
107
112
|
|
|
108
|
-
it("clears error when user types after error", () => {
|
|
113
|
+
it("clears error when user types a valid email after error", () => {
|
|
109
114
|
render(<EmailInputBlock {...defaultProps} />);
|
|
110
115
|
const input = screen.getByPlaceholderText("Enter your email here");
|
|
111
116
|
fireEvent.change(input, { target: { value: "invalid" } });
|
|
112
117
|
fireEvent.click(screen.getByText("Sign me up"));
|
|
113
|
-
expect(
|
|
114
|
-
screen.getByText("Please enter a valid email address.")
|
|
115
|
-
).toBeInTheDocument();
|
|
118
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument();
|
|
116
119
|
|
|
117
|
-
fireEvent.change(input, { target: { value: "
|
|
120
|
+
fireEvent.change(input, { target: { value: "user@test.com" } });
|
|
118
121
|
expect(
|
|
119
|
-
screen.queryByText("
|
|
122
|
+
screen.queryByText("Invalid email address")
|
|
120
123
|
).not.toBeInTheDocument();
|
|
121
124
|
});
|
|
122
125
|
|
|
@@ -148,62 +151,50 @@ describe("EmailInputBlock", () => {
|
|
|
148
151
|
expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
|
|
149
152
|
});
|
|
150
153
|
|
|
151
|
-
it("applies green background by default", () => {
|
|
154
|
+
it("applies green theme background by default", () => {
|
|
152
155
|
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
153
|
-
|
|
154
|
-
expect(inner).toBeInTheDocument();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("applies navy background class", () => {
|
|
158
|
-
const { container } = render(
|
|
159
|
-
<EmailInputBlock {...defaultProps} backgroundColor="navy" />
|
|
160
|
-
);
|
|
161
|
-
expect(container.querySelector(".bg-bg-fill-inverse")).toBeInTheDocument();
|
|
156
|
+
expect(container.querySelector(".bg-bg-fill-brand")).toBeInTheDocument();
|
|
162
157
|
});
|
|
163
158
|
|
|
164
|
-
it("applies blue background class", () => {
|
|
159
|
+
it("applies blue theme background class", () => {
|
|
165
160
|
const { container } = render(
|
|
166
|
-
<EmailInputBlock {...defaultProps}
|
|
161
|
+
<EmailInputBlock {...defaultProps} themeColor="blue" />
|
|
167
162
|
);
|
|
168
|
-
expect(
|
|
163
|
+
expect(
|
|
164
|
+
container.querySelector(".bg-bg-fill-brand-supporting")
|
|
165
|
+
).toBeInTheDocument();
|
|
169
166
|
});
|
|
170
167
|
|
|
171
|
-
it("applies
|
|
168
|
+
it("applies purple theme background class", () => {
|
|
172
169
|
const { container } = render(
|
|
173
|
-
<EmailInputBlock {...defaultProps}
|
|
170
|
+
<EmailInputBlock {...defaultProps} themeColor="purple" />
|
|
174
171
|
);
|
|
175
172
|
expect(
|
|
176
|
-
container.querySelector(".bg-bg-fill-brand-
|
|
173
|
+
container.querySelector(".bg-bg-fill-brand-tertiary")
|
|
177
174
|
).toBeInTheDocument();
|
|
178
175
|
});
|
|
179
176
|
|
|
180
|
-
it("applies white background class", () => {
|
|
181
|
-
const { container } = render(
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
177
|
+
it("applies white outer background class by default", () => {
|
|
178
|
+
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
179
|
+
expect(container.querySelector("section")).toHaveClass("bg-bg");
|
|
185
180
|
});
|
|
186
181
|
|
|
187
|
-
it("uses white text for
|
|
182
|
+
it("uses white text for dark themes", () => {
|
|
188
183
|
render(
|
|
189
|
-
<EmailInputBlock
|
|
190
|
-
{...defaultProps}
|
|
191
|
-
backgroundColor="navy"
|
|
192
|
-
title="Navy title"
|
|
193
|
-
/>
|
|
184
|
+
<EmailInputBlock {...defaultProps} themeColor="blue" title="Blue title" />
|
|
194
185
|
);
|
|
195
|
-
expect(screen.getByText("
|
|
186
|
+
expect(screen.getByText("Blue title").className).toContain("text-white");
|
|
196
187
|
});
|
|
197
188
|
|
|
198
|
-
it("uses dark text for white
|
|
189
|
+
it("uses dark text for the white theme", () => {
|
|
199
190
|
render(
|
|
200
191
|
<EmailInputBlock
|
|
201
192
|
{...defaultProps}
|
|
202
|
-
|
|
203
|
-
title="
|
|
193
|
+
themeColor="white"
|
|
194
|
+
title="White title"
|
|
204
195
|
/>
|
|
205
196
|
);
|
|
206
|
-
expect(screen.getByText("
|
|
197
|
+
expect(screen.getByText("White title").className).toContain("text-text");
|
|
207
198
|
});
|
|
208
199
|
|
|
209
200
|
it("uses custom ctaText", () => {
|
|
@@ -260,12 +260,13 @@ describe("FindKinetic", () => {
|
|
|
260
260
|
expect(screen.queryAllByRole("list")).toHaveLength(0);
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
-
it("renders two column layout for mobile and
|
|
263
|
+
it("renders two column layout for mobile, three for tablet and four for desktop", () => {
|
|
264
264
|
const { container } = render(<FindKinetic list={mockList} />);
|
|
265
265
|
const lists = container.querySelectorAll("ul");
|
|
266
|
-
expect(lists).toHaveLength(
|
|
266
|
+
expect(lists).toHaveLength(3);
|
|
267
267
|
expect(lists[0]).toHaveClass("grid-cols-2");
|
|
268
268
|
expect(lists[1]).toHaveClass("md:grid-cols-3");
|
|
269
|
+
expect(lists[2]).toHaveClass("xl:grid-cols-4");
|
|
269
270
|
});
|
|
270
271
|
|
|
271
272
|
it("rearranges items in column-major order for 2-col layout", () => {
|
|
@@ -89,7 +89,6 @@ const defaultProps: ImagePromoBarProps = {
|
|
|
89
89
|
cta: null,
|
|
90
90
|
secondaryCta: null,
|
|
91
91
|
ctaDisclaimer: null,
|
|
92
|
-
videoLink: { link: "", image: "", videoPopup: false },
|
|
93
92
|
maxWidth: true,
|
|
94
93
|
color: "light",
|
|
95
94
|
};
|
|
@@ -182,15 +181,15 @@ describe("ImagePromoBar", () => {
|
|
|
182
181
|
const { container } = render(
|
|
183
182
|
<ImagePromoBar {...defaultProps} mediaPosition={true} />
|
|
184
183
|
);
|
|
185
|
-
expect(container.innerHTML).toContain("
|
|
184
|
+
expect(container.innerHTML).toContain("xl:flex-row-reverse");
|
|
186
185
|
});
|
|
187
186
|
|
|
188
187
|
it("applies lg:flex-row when mediaPosition is false", () => {
|
|
189
188
|
const { container } = render(
|
|
190
189
|
<ImagePromoBar {...defaultProps} mediaPosition={false} />
|
|
191
190
|
);
|
|
192
|
-
expect(container.innerHTML).toContain("
|
|
193
|
-
expect(container.innerHTML).not.toContain("
|
|
191
|
+
expect(container.innerHTML).toContain("xl:flex-row");
|
|
192
|
+
expect(container.innerHTML).not.toContain("xl:flex-row-reverse");
|
|
194
193
|
});
|
|
195
194
|
});
|
|
196
195
|
|
|
@@ -276,22 +275,68 @@ describe("ImagePromoBar", () => {
|
|
|
276
275
|
});
|
|
277
276
|
|
|
278
277
|
describe("Image", () => {
|
|
279
|
-
it("renders
|
|
278
|
+
it("renders the plain image when there is no videoLink content", () => {
|
|
280
279
|
render(
|
|
281
280
|
<ImagePromoBar {...defaultProps} image="https://img.test/hero.jpg" />
|
|
282
281
|
);
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
expect(screen.getByTestId("next-image")).toHaveAttribute(
|
|
283
|
+
"src",
|
|
284
|
+
"https://img.test/hero.jpg"
|
|
285
|
+
);
|
|
285
286
|
});
|
|
286
287
|
|
|
287
|
-
it("does not render image section when image is empty", () => {
|
|
288
|
+
it("does not render the image section when image is empty", () => {
|
|
288
289
|
render(<ImagePromoBar {...defaultProps} image="" />);
|
|
289
290
|
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
290
291
|
});
|
|
292
|
+
|
|
293
|
+
it("does not render the plain image when videoLink has a valid href", () => {
|
|
294
|
+
render(
|
|
295
|
+
<ImagePromoBar
|
|
296
|
+
{...defaultProps}
|
|
297
|
+
image="https://img.test/hero.jpg"
|
|
298
|
+
videoLink={{ link: "https://youtube.com/watch?v=123" }}
|
|
299
|
+
/>
|
|
300
|
+
);
|
|
301
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("does not render the plain image when videoLink has its own poster image, even without a valid href", () => {
|
|
305
|
+
render(
|
|
306
|
+
<ImagePromoBar
|
|
307
|
+
{...defaultProps}
|
|
308
|
+
image="https://img.test/hero.jpg"
|
|
309
|
+
videoLink={{ image: "thumb.jpg" }}
|
|
310
|
+
/>
|
|
311
|
+
);
|
|
312
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("renders the plain image when no videoLink is attached at all", () => {
|
|
316
|
+
render(
|
|
317
|
+
<ImagePromoBar
|
|
318
|
+
{...defaultProps}
|
|
319
|
+
image="https://img.test/hero.jpg"
|
|
320
|
+
videoLink={undefined}
|
|
321
|
+
/>
|
|
322
|
+
);
|
|
323
|
+
expect(screen.getByTestId("next-image")).toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("suppresses the plain image when an empty videoLink object is attached (VideoLink owns its own fallback from there)", () => {
|
|
327
|
+
render(
|
|
328
|
+
<ImagePromoBar
|
|
329
|
+
{...defaultProps}
|
|
330
|
+
image="https://img.test/hero.jpg"
|
|
331
|
+
videoLink={{}}
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
335
|
+
});
|
|
291
336
|
});
|
|
292
337
|
|
|
293
338
|
describe("Video", () => {
|
|
294
|
-
it("renders VideoLink
|
|
339
|
+
it("always renders VideoLink, passing videoLink through", () => {
|
|
295
340
|
render(
|
|
296
341
|
<ImagePromoBar
|
|
297
342
|
{...defaultProps}
|
|
@@ -317,7 +362,7 @@ describe("ImagePromoBar", () => {
|
|
|
317
362
|
);
|
|
318
363
|
});
|
|
319
364
|
|
|
320
|
-
it("renders VideoLink
|
|
365
|
+
it("still renders VideoLink when videoLink has no valid href (VideoLink owns its own poster fallback)", () => {
|
|
321
366
|
render(<ImagePromoBar {...defaultProps} videoLink={{}} />);
|
|
322
367
|
expect(screen.getByTestId("video-link")).toBeInTheDocument();
|
|
323
368
|
});
|
|
@@ -36,6 +36,8 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
36
36
|
renderCheckPlans,
|
|
37
37
|
enableAnimation = false,
|
|
38
38
|
}) => {
|
|
39
|
+
const hasVideoLink = Boolean(videoLink);
|
|
40
|
+
|
|
39
41
|
return (
|
|
40
42
|
<div className="component-container shared-component-ipb pb-16 pt-16">
|
|
41
43
|
<div
|
|
@@ -161,8 +163,8 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
161
163
|
)}
|
|
162
164
|
</div>
|
|
163
165
|
<aside className="flex w-full flex-1 shrink-0 items-center justify-center lg:w-auto">
|
|
164
|
-
{/* Media Section */}
|
|
165
|
-
{image && (
|
|
166
|
+
{/* Media Section: only when no Video Link is attached at all */}
|
|
167
|
+
{image && !hasVideoLink && (
|
|
166
168
|
<div
|
|
167
169
|
className={cx(
|
|
168
170
|
!useOriginalImageAspectRatio
|
|
@@ -196,6 +198,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
196
198
|
imageWidth={imageWidth}
|
|
197
199
|
imageHeight={imageHeight}
|
|
198
200
|
useOriginalImageAspectRatio={useOriginalImageAspectRatio}
|
|
201
|
+
isAboveTheFold={isAboveTheFold}
|
|
199
202
|
/>
|
|
200
203
|
</aside>
|
|
201
204
|
</div>
|
|
@@ -30,7 +30,7 @@ export const OrderedList = ({
|
|
|
30
30
|
return (
|
|
31
31
|
<Text
|
|
32
32
|
key={`ordered-item-${index}`}
|
|
33
|
-
className="body1 relative text-left leading-7"
|
|
33
|
+
className="body1 relative text-left font-semibold leading-7"
|
|
34
34
|
>
|
|
35
35
|
{formatItemName(item.itemName)}
|
|
36
36
|
</Text>
|
|
@@ -44,7 +44,7 @@ export const OrderedList = ({
|
|
|
44
44
|
title={item.itemName.toUpperCase()}
|
|
45
45
|
className="flex items-center underline underline-offset-4"
|
|
46
46
|
>
|
|
47
|
-
<Text className="body1 relative cursor-pointer text-left leading-7">
|
|
47
|
+
<Text className="body1 relative cursor-pointer text-left font-semibold leading-7">
|
|
48
48
|
{formatItemName(item.itemName)}
|
|
49
49
|
</Text>
|
|
50
50
|
</Link>
|
|
@@ -52,12 +52,28 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
52
52
|
const mobileVideoUrl = heroVideoMobile?.url?.trim();
|
|
53
53
|
const hasDesktopVideo = !!desktopVideoUrl;
|
|
54
54
|
const hasMobileVideo = !!mobileVideoUrl;
|
|
55
|
+
const normalizeImageUrl = (url?: string) =>
|
|
56
|
+
url?.startsWith("//") ? `https:${url}` : url;
|
|
57
|
+
const badgeImageUrl = normalizeImageUrl(badgeImage);
|
|
58
|
+
const heroImageUrl = normalizeImageUrl(carouselImages?.[0]);
|
|
55
59
|
const bottomLinkLabel = bottomLink?.buttonLabel ?? bottomLink?.label;
|
|
56
60
|
const baseTextColorClass = textColor === "dark" ? "text-text" : "text-white";
|
|
57
61
|
const textColorClass =
|
|
58
62
|
hasDesktopVideo && textColor === "dark"
|
|
59
63
|
? `${baseTextColorClass} xl:text-white`
|
|
60
64
|
: baseTextColorClass;
|
|
65
|
+
const Badge = ({ className }: { className: string }) =>
|
|
66
|
+
badgeImageUrl ? (
|
|
67
|
+
<NextImage
|
|
68
|
+
src={badgeImageUrl}
|
|
69
|
+
alt="Badge"
|
|
70
|
+
className={className}
|
|
71
|
+
width={208}
|
|
72
|
+
height={208}
|
|
73
|
+
quality={90}
|
|
74
|
+
loading="eager"
|
|
75
|
+
/>
|
|
76
|
+
) : null;
|
|
61
77
|
return (
|
|
62
78
|
<div className="component-container relative overflow-hidden p-4 sm:px-6 sm:py-12 xl:flex xl:min-h-[724px] xl:items-center xl:px-10">
|
|
63
79
|
{hasDesktopVideo ? (
|
|
@@ -267,23 +283,26 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
267
283
|
<div className="w-full xl:hidden">
|
|
268
284
|
<div className={"relative my-8"}>
|
|
269
285
|
{hasMobileVideo ? (
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
286
|
+
<div className="relative">
|
|
287
|
+
<video
|
|
288
|
+
autoPlay={true}
|
|
289
|
+
muted={true}
|
|
290
|
+
loop={true}
|
|
291
|
+
playsInline={true}
|
|
292
|
+
preload="auto"
|
|
293
|
+
aria-label={heroVideoMobile?.title || title || "Hero video"}
|
|
294
|
+
className="h-[350px] w-full rounded-[40px] object-cover object-center sm:h-[420px] md:h-[520px]"
|
|
295
|
+
data-testid="hero-video-mobile"
|
|
296
|
+
>
|
|
297
|
+
<source src={mobileVideoUrl} type="video/mp4" />
|
|
298
|
+
</video>
|
|
299
|
+
<Badge className="absolute left-5 top-5 z-10 aspect-square w-26 object-cover object-center" />
|
|
300
|
+
</div>
|
|
282
301
|
) : (
|
|
283
302
|
<>
|
|
284
303
|
{badgeImage ? (
|
|
285
304
|
<NextImage
|
|
286
|
-
src={
|
|
305
|
+
src={badgeImageUrl || ""}
|
|
287
306
|
alt={"Badge"}
|
|
288
307
|
className="absolute left-5 top-5 aspect-square w-26 object-cover object-center"
|
|
289
308
|
width={104}
|
|
@@ -292,9 +311,9 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
292
311
|
loading="eager"
|
|
293
312
|
/>
|
|
294
313
|
) : null}
|
|
295
|
-
{
|
|
314
|
+
{heroImageUrl ? (
|
|
296
315
|
<NextImage
|
|
297
|
-
src={
|
|
316
|
+
src={heroImageUrl}
|
|
298
317
|
alt={"Hero"}
|
|
299
318
|
className="h-[205px] w-full rounded-[40px] object-cover object-center sm:h-[420px]"
|
|
300
319
|
width={1024}
|
|
@@ -339,32 +358,25 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
339
358
|
</div>
|
|
340
359
|
|
|
341
360
|
{/* Right column: bright blue background, diagonal top edge, hero image */}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
height={600}
|
|
362
|
-
quality={90}
|
|
363
|
-
loading="eager"
|
|
364
|
-
/>
|
|
365
|
-
) : null}
|
|
366
|
-
</div>
|
|
367
|
-
) : null}
|
|
361
|
+
<div
|
|
362
|
+
className={cx(
|
|
363
|
+
"relative hidden xl:block",
|
|
364
|
+
hasDesktopVideo && "h-[600px] w-[600px]"
|
|
365
|
+
)}
|
|
366
|
+
>
|
|
367
|
+
<Badge className="absolute -left-26 top-18 z-10 aspect-square w-52 object-cover object-center" />
|
|
368
|
+
{!hasDesktopVideo && heroImageUrl ? (
|
|
369
|
+
<NextImage
|
|
370
|
+
src={heroImageUrl}
|
|
371
|
+
alt={"Hero"}
|
|
372
|
+
className="aspect-square rounded-[40px] object-cover object-center"
|
|
373
|
+
width={600}
|
|
374
|
+
height={600}
|
|
375
|
+
quality={90}
|
|
376
|
+
loading="eager"
|
|
377
|
+
/>
|
|
378
|
+
) : null}
|
|
379
|
+
</div>
|
|
368
380
|
</div>
|
|
369
381
|
</div>
|
|
370
382
|
);
|
|
@@ -549,9 +549,10 @@ describe("useContentfulRichText", () => {
|
|
|
549
549
|
const { container } = render(<>{result.current}</>);
|
|
550
550
|
expect(container.querySelector("h1")).toHaveTextContent("H1");
|
|
551
551
|
expect(container.querySelector("h2")).toHaveTextContent("H2");
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
expect(
|
|
552
|
+
expect(container.querySelector("h3")).toHaveTextContent("H3");
|
|
553
|
+
expect(container.querySelector("h4")).toHaveTextContent("H4");
|
|
554
|
+
expect(container.querySelector("h5")).toHaveTextContent("H5");
|
|
555
|
+
expect(container.querySelector("h6")).toHaveTextContent("H6");
|
|
555
556
|
});
|
|
556
557
|
|
|
557
558
|
it("renders unordered list", () => {
|