@windstream/react-shared-components 0.2.33 → 0.2.35

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 (27) hide show
  1. package/dist/contentful/index.d.ts +1 -5
  2. package/dist/contentful/index.esm.js +2 -2
  3. package/dist/contentful/index.esm.js.map +1 -1
  4. package/dist/contentful/index.js +3 -3
  5. package/dist/contentful/index.js.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/styles.css +1 -1
  8. package/package.json +1 -1
  9. package/src/components/video-link/contentful-video-embed.test.tsx +76 -0
  10. package/src/components/video-link/contentful-video-embed.tsx +42 -0
  11. package/src/components/video-link/index.test.tsx +227 -0
  12. package/src/components/video-link/index.tsx +142 -0
  13. package/src/components/video-link/types.ts +28 -0
  14. package/src/contentful/blocks/blogs-grid-base/index.tsx +1 -1
  15. package/src/contentful/blocks/button/index.test.tsx +52 -0
  16. package/src/contentful/blocks/button/index.tsx +8 -3
  17. package/src/contentful/blocks/image-promo-bar/index.test.tsx +15 -198
  18. package/src/contentful/blocks/image-promo-bar/index.tsx +8 -92
  19. package/src/contentful/blocks/image-promo-bar/types.ts +1 -17
  20. package/src/contentful/blocks/primary-hero/index.test.tsx +21 -1
  21. package/src/contentful/blocks/primary-hero/index.tsx +25 -21
  22. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/helper.test.tsx +0 -0
  23. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/helper.tsx +0 -0
  24. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/vimeo-embed.test.tsx +0 -0
  25. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/vimeo-embed.tsx +0 -0
  26. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/youtube-embed.test.tsx +0 -0
  27. /package/src/{contentful/blocks/image-promo-bar → components/video-link}/youtube-embed.tsx +0 -0
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { ImagePromoBar } from "./index";
3
3
  import { ImagePromoBarProps } from "./types";
4
4
 
5
- import { fireEvent, render, screen } from "@testing-library/react";
5
+ import { render, screen } from "@testing-library/react";
6
6
 
7
7
  jest.mock("../button", () => ({
8
8
  __esModule: true,
@@ -25,24 +25,11 @@ jest.mock("../button", () => ({
25
25
  ),
26
26
  }));
27
27
 
28
- jest.mock("./helper", () => ({
29
- PlayButton: ({ isHovered }: any) => (
30
- <div data-testid="play-button" data-hovered={isHovered} />
31
- ),
32
- }));
33
-
34
- jest.mock("./vimeo-embed", () => ({
35
- VimeoEmbed: ({ link, autoplay }: any) => (
36
- <div data-testid="vimeo-embed" data-link={link} data-autoplay={autoplay} />
37
- ),
38
- }));
39
-
40
- jest.mock("./youtube-embed", () => ({
41
- YoutubeEmbed: ({ link, autoplay }: any) => (
28
+ jest.mock("@shared/components/video-link", () => ({
29
+ VideoLink: ({ videoLink }: any) => (
42
30
  <div
43
- data-testid="youtube-embed"
44
- data-link={link}
45
- data-autoplay={autoplay}
31
+ data-testid="video-link"
32
+ data-link={videoLink?.link || videoLink?.videoAssetUrl || ""}
46
33
  />
47
34
  ),
48
35
  }));
@@ -304,109 +291,7 @@ describe("ImagePromoBar", () => {
304
291
  });
305
292
 
306
293
  describe("Video", () => {
307
- it("renders video section with play button when videoLink has link and image", () => {
308
- render(
309
- <ImagePromoBar
310
- {...defaultProps}
311
- videoLink={{
312
- link: "https://youtube.com/watch?v=123",
313
- image: "thumb.jpg",
314
- }}
315
- />
316
- );
317
- expect(screen.getByTestId("play-button")).toBeInTheDocument();
318
- expect(screen.getByAltText("Video preview")).toBeInTheDocument();
319
- });
320
-
321
- it("shows youtube embed on click for youtube link", () => {
322
- render(
323
- <ImagePromoBar
324
- {...defaultProps}
325
- videoLink={{
326
- link: "https://youtube.com/watch?v=123",
327
- image: "thumb.jpg",
328
- videoPopup: false,
329
- }}
330
- />
331
- );
332
- fireEvent.click(
333
- screen.getByTestId("play-button").closest(".video-section")!
334
- );
335
- expect(screen.getByTestId("youtube-embed")).toBeInTheDocument();
336
- });
337
-
338
- it("shows vimeo embed on click for vimeo link", () => {
339
- render(
340
- <ImagePromoBar
341
- {...defaultProps}
342
- videoLink={{
343
- link: "https://vimeo.com/123",
344
- image: "thumb.jpg",
345
- videoPopup: false,
346
- }}
347
- />
348
- );
349
- fireEvent.click(
350
- screen.getByTestId("play-button").closest(".video-section")!
351
- );
352
- expect(screen.getByTestId("vimeo-embed")).toBeInTheDocument();
353
- });
354
-
355
- it("shows popup overlay when videoPopup is true and playing", () => {
356
- const { container } = render(
357
- <ImagePromoBar
358
- {...defaultProps}
359
- videoLink={{
360
- link: "https://youtube.com/watch?v=123",
361
- image: "thumb.jpg",
362
- videoPopup: true,
363
- }}
364
- />
365
- );
366
- fireEvent.click(
367
- screen.getByTestId("play-button").closest(".video-section")!
368
- );
369
- expect(container.querySelector(".fixed")).toBeInTheDocument();
370
- });
371
-
372
- it("closes popup on overlay click", () => {
373
- const { container } = render(
374
- <ImagePromoBar
375
- {...defaultProps}
376
- videoLink={{
377
- link: "https://youtube.com/watch?v=123",
378
- image: "thumb.jpg",
379
- videoPopup: true,
380
- }}
381
- />
382
- );
383
- fireEvent.click(
384
- screen.getByTestId("play-button").closest(".video-section")!
385
- );
386
- fireEvent.click(container.querySelector(".fixed")!);
387
- expect(container.querySelector(".fixed")).not.toBeInTheDocument();
388
- });
389
-
390
- it("does not close popup when clicking inside video container", () => {
391
- const { container } = render(
392
- <ImagePromoBar
393
- {...defaultProps}
394
- videoLink={{
395
- link: "https://youtube.com/watch?v=123",
396
- image: "thumb.jpg",
397
- videoPopup: true,
398
- }}
399
- />
400
- );
401
- fireEvent.click(
402
- screen.getByTestId("play-button").closest(".video-section")!
403
- );
404
- const inner = container.querySelector(".max-w-6xl");
405
- fireEvent.click(inner!);
406
- expect(container.querySelector(".fixed")).toBeInTheDocument();
407
- });
408
-
409
- it("handles hover state on video section", () => {
294
+ it("renders VideoLink when videoLink is provided", () => {
410
295
  render(
411
296
  <ImagePromoBar
412
297
  {...defaultProps}
@@ -416,93 +301,25 @@ describe("ImagePromoBar", () => {
416
301
  }}
417
302
  />
418
303
  );
419
- const videoSection = screen
420
- .getByTestId("play-button")
421
- .closest(".video-section")!;
422
- fireEvent.mouseEnter(videoSection);
423
- expect(screen.getByTestId("play-button")).toHaveAttribute(
424
- "data-hovered",
425
- "true"
426
- );
427
- fireEvent.mouseLeave(videoSection);
428
- expect(screen.getByTestId("play-button")).toHaveAttribute(
429
- "data-hovered",
430
- "false"
431
- );
304
+ expect(screen.getByTestId("video-link")).toBeInTheDocument();
432
305
  });
433
306
 
434
- it("does not render video section when videoLink.link is empty", () => {
435
- const { container } = render(
436
- <ImagePromoBar
437
- {...defaultProps}
438
- videoLink={{ link: "", image: "thumb.jpg" }}
439
- />
440
- );
441
- expect(container.querySelector(".video-section")).not.toBeInTheDocument();
442
- });
443
-
444
- it("does not render video preview image when videoLink.image is missing", () => {
307
+ it("passes the videoLink prop through to VideoLink", () => {
445
308
  render(
446
309
  <ImagePromoBar
447
310
  {...defaultProps}
448
- videoLink={{ link: "https://youtube.com/watch?v=123" }}
311
+ videoLink={{ link: "https://vimeo.com/123", image: "thumb.jpg" }}
449
312
  />
450
313
  );
451
- expect(screen.queryByAltText("Video preview")).not.toBeInTheDocument();
452
- });
453
-
454
- it("handles videoLink being undefined", () => {
455
- const { container } = render(
456
- <ImagePromoBar {...defaultProps} videoLink={undefined as any} />
314
+ expect(screen.getByTestId("video-link")).toHaveAttribute(
315
+ "data-link",
316
+ "https://vimeo.com/123"
457
317
  );
458
- expect(container.querySelector(".video-section")).not.toBeInTheDocument();
459
318
  });
460
319
 
461
- it("handles videoLink with no videoPopup property (defaults to false)", () => {
462
- const { container } = render(
463
- <ImagePromoBar
464
- {...defaultProps}
465
- videoLink={{
466
- link: "https://youtube.com/watch?v=123",
467
- image: "thumb.jpg",
468
- }}
469
- />
470
- );
471
- fireEvent.click(container.querySelector(".video-section")!);
472
- // Should show inline embed, not popup
473
- expect(container.querySelector(".fixed")).not.toBeInTheDocument();
474
- expect(screen.getByTestId("youtube-embed")).toBeInTheDocument();
475
- });
476
-
477
- it("does not render inline embed when videoPopup is true", () => {
478
- const { container } = render(
479
- <ImagePromoBar
480
- {...defaultProps}
481
- videoLink={{
482
- link: "https://youtube.com/watch?v=123",
483
- image: "thumb.jpg",
484
- videoPopup: true,
485
- }}
486
- />
487
- );
488
- fireEvent.click(container.querySelector(".video-section")!);
489
- // Popup renders, inline section hidden via the !videoPopup && isPlaying condition
490
- expect(container.querySelector(".fixed")).toBeInTheDocument();
491
- });
492
-
493
- it("does not render contentVideo embed when videoLink has no image", () => {
494
- const { container } = render(
495
- <ImagePromoBar
496
- {...defaultProps}
497
- videoLink={{
498
- link: "https://youtube.com/watch?v=123",
499
- videoPopup: false,
500
- }}
501
- />
502
- );
503
- fireEvent.click(container.querySelector(".video-section")!);
504
- // contentVideo is null when videoImage is falsy
505
- expect(screen.queryByTestId("youtube-embed")).not.toBeInTheDocument();
320
+ it("renders VideoLink even when videoLink has no link (VideoLink owns the gating)", () => {
321
+ render(<ImagePromoBar {...defaultProps} videoLink={{}} />);
322
+ expect(screen.getByTestId("video-link")).toBeInTheDocument();
506
323
  });
507
324
  });
508
325
  });
@@ -1,15 +1,13 @@
1
- import React, { useState } from "react";
1
+ import React from "react";
2
2
  import { Button } from "../button";
3
- import { PlayButton } from "./helper";
4
3
  import { ImagePromoBarProps } from "./types";
5
- import { VimeoEmbed } from "./vimeo-embed";
6
- import { YoutubeEmbed } from "./youtube-embed";
7
4
 
8
5
  import { Checklist } from "@shared/components/checklist";
9
6
  import { Image } from "@shared/components/image";
10
7
  import { Link } from "@shared/components/link";
11
8
  import { NextImage } from "@shared/components/next-image";
12
9
  import { Text } from "@shared/components/text";
10
+ import { VideoLink } from "@shared/components/video-link";
13
11
  import { cx } from "@shared/utils";
14
12
 
15
13
  export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
@@ -38,32 +36,6 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
38
36
  renderCheckPlans,
39
37
  enableAnimation = false,
40
38
  }) => {
41
- const [activeVideo, setActiveVideo] = useState("");
42
- const [isHovered, setIsHovered] = useState(false);
43
-
44
- const handlePlayClick = () => {
45
- setActiveVideo(videoLink?.link ?? "");
46
- };
47
-
48
- const handleCloseVideo = (e: React.MouseEvent) => {
49
- e.stopPropagation();
50
- setActiveVideo("");
51
- };
52
-
53
- const videoHref = videoLink?.link ?? "";
54
- const videoImage = videoLink?.image;
55
- const videoPopup = videoLink?.videoPopup ?? false;
56
- const isPlaying = Boolean(activeVideo && activeVideo === videoHref);
57
-
58
- const embedSelector = () => {
59
- if (videoHref.includes("vimeo")) {
60
- return <VimeoEmbed link={videoHref} autoplay={isPlaying} />;
61
- } else {
62
- return <YoutubeEmbed link={videoHref} autoplay={isPlaying} />;
63
- }
64
- };
65
- const contentVideo = videoLink && videoImage ? embedSelector() : null;
66
-
67
39
  return (
68
40
  <div className="component-container shared-component-ipb pb-16 pt-16">
69
41
  <div
@@ -219,72 +191,16 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
219
191
  </div>
220
192
  )}
221
193
  {/* Video Link Section */}
222
- {videoLink?.link && (
223
- <div
224
- className={cx(
225
- "video-section relative w-full cursor-pointer rounded-image transition-all duration-300 lg:w-[486px]",
226
- !isPlaying && "hover:ring-2 hover:ring-border-focus"
227
- )}
228
- onClick={handlePlayClick}
229
- onMouseEnter={() => setIsHovered(true)}
230
- onMouseLeave={() => setIsHovered(false)}
231
- >
232
- {/* Preview Image & Play Button */}
233
- <div
234
- className={cx(
235
- isPlaying && !videoPopup && "hidden",
236
- isPlaying && !videoPopup ? "opacity-0" : "opacity-100",
237
- "relative w-full transition-opacity duration-300"
238
- )}
239
- >
240
- {videoLink.image && (
241
- <NextImage
242
- src={videoLink.image}
243
- alt="Video preview"
244
- width={imageWidth || 660}
245
- height={imageHeight || 660}
246
- sizes="(max-width: 430px) 100vw, (max-width: 834px) 100vw, 50vw"
247
- className="h-auto w-full rounded-image"
248
- />
249
- )}
250
- <div className="absolute inset-0 flex items-center justify-center">
251
- <PlayButton isHovered={isHovered} />
252
- </div>
253
- </div>
254
-
255
- {/* Inline Player (if not popup) */}
256
- {!videoPopup && isPlaying && (
257
- <div
258
- className={cx(
259
- "aspect-[16/9] w-full overflow-hidden rounded-image transition-opacity duration-300",
260
- isPlaying ? "opacity-100" : "opacity-0",
261
- !useOriginalImageAspectRatio &&
262
- "xl:aspect-square xl:max-h-[486px] xl:w-[486px]"
263
- )}
264
- >
265
- {contentVideo}
266
- </div>
267
- )}
268
- </div>
269
- )}
194
+ <VideoLink
195
+ videoLink={videoLink}
196
+ imageWidth={imageWidth}
197
+ imageHeight={imageHeight}
198
+ useOriginalImageAspectRatio={useOriginalImageAspectRatio}
199
+ />
270
200
  </aside>
271
201
  </div>
272
202
  </div>
273
203
 
274
- {/* Video Popup Overlay */}
275
- {videoPopup && isPlaying && (
276
- <div
277
- className="fixed inset-0 top-20 z-[100] flex items-center justify-center bg-black/80 p-4 transition-all duration-300"
278
- onClick={handleCloseVideo}
279
- >
280
- <div
281
- className="max-w-6xl aspect-video w-full overflow-hidden rounded-3xl bg-black shadow-2xl md:w-4/6"
282
- onClick={e => e.stopPropagation()}
283
- >
284
- {contentVideo}
285
- </div>
286
- </div>
287
- )}
288
204
  {disclaimer && (
289
205
  <div className="mt-10 flex justify-center xl:mt-18">
290
206
  <div>{disclaimer}</div>
@@ -1,19 +1,8 @@
1
1
  import React from "react";
2
2
 
3
+ import { VideoLinkProps } from "@shared/components/video-link";
3
4
  import { CheckPlansProps } from "@shared/types/micro-components";
4
5
 
5
- export type VideoLinkProps = {
6
- image?: string;
7
- videoPopup?: boolean;
8
- link?: string;
9
- };
10
-
11
- export type VideoEmbedProps = {
12
- containerClassName?: string;
13
- autoplay?: boolean;
14
- debug?: boolean;
15
- } & VideoLinkProps;
16
-
17
6
  export type ImagePromoBarProps = {
18
7
  brow: string;
19
8
  enableHeading: boolean;
@@ -41,8 +30,3 @@ export type ImagePromoBarProps = {
41
30
  onClick?: (event: React.MouseEvent, source: "primary" | "secondary") => void;
42
31
  renderCheckPlans?: (overrides?: CheckPlansProps) => React.ReactNode;
43
32
  };
44
-
45
- export type PlayButtonProps = {
46
- isHovered?: boolean;
47
- containerClassName?: string;
48
- };
@@ -285,6 +285,27 @@ describe("PrimaryHero", () => {
285
285
  });
286
286
 
287
287
  describe("Pricing description disclaimer", () => {
288
+ it("renders pricingDescription with subheading and responsive alignment classes", () => {
289
+ render(
290
+ <PrimaryHero
291
+ {...baseProps}
292
+ pricingDescription="Building a new home? Contact us to see if you're eligible for Kinetic Internet."
293
+ />
294
+ );
295
+
296
+ const pricingText = screen.getByText(
297
+ "Building a new home? Contact us to see if you're eligible for Kinetic Internet."
298
+ );
299
+
300
+ expect(pricingText).toBeInTheDocument();
301
+ expect(pricingText).toHaveClass("subheading1");
302
+ expect(pricingText).toHaveClass(
303
+ "text-left",
304
+ "sm:text-center",
305
+ "xl:text-left"
306
+ );
307
+ });
308
+
288
309
  it("renders pricingDescriptionDisclaimer (desktop and mobile)", () => {
289
310
  render(
290
311
  <PrimaryHero
@@ -300,7 +321,6 @@ describe("PrimaryHero", () => {
300
321
  expect(screen.queryByText("Taxes apply")).not.toBeInTheDocument();
301
322
  });
302
323
  });
303
-
304
324
  describe("Images", () => {
305
325
  it("renders carousel image (mobile and desktop)", () => {
306
326
  render(
@@ -55,7 +55,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
55
55
  const bottomLinkLabel = bottomLink?.buttonLabel ?? bottomLink?.label;
56
56
  const textColorClass = textColor === "light" ? " text-white" : " text-text";
57
57
  return (
58
- <div className="component-container relative overflow-hidden p-5 xl:flex xl:min-h-[724px] xl:items-center xl:p-0 xl:py-4">
58
+ <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">
59
59
  {hasDesktopVideo ? (
60
60
  <video
61
61
  autoPlay={true}
@@ -72,13 +72,13 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
72
72
  ) : null}
73
73
  <div
74
74
  className={cx(
75
- "primary-hero-container relative z-10 mx-auto flex w-full flex-col sm:items-center sm:text-center xl:h-full xl:max-w-120 xl:flex-row xl:items-center xl:justify-between xl:gap-4 xl:text-left"
75
+ "primary-hero-container relative z-10 mx-auto flex w-full max-w-[626px] flex-col sm:items-center sm:text-center xl:h-full xl:max-w-120 xl:flex-row xl:items-center xl:justify-between xl:gap-4 xl:text-left"
76
76
  )}
77
77
  >
78
78
  {/* Left column: navy background, headline, body, address, button, link */}
79
79
  <div
80
80
  className={cx(
81
- `relative flex flex-col ${textColorClass} sm:w-[485px]`
81
+ `relative flex flex-col ${textColorClass} w-full xl:w-[485px]`
82
82
  )}
83
83
  >
84
84
  <div className="flex flex-col gap-5 lg:gap-6">
@@ -126,15 +126,19 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
126
126
 
127
127
  {pricingDescription ? (
128
128
  <Text
129
- data-cy="hero-pricing-disclaimer"
130
- className={cx("text-left sm:text-center xl:text-left")}
129
+ data-cy="hero-pricing-description"
130
+ className={cx("subheading1", "text-left sm:text-center xl:text-left")}
131
131
  >
132
132
  {pricingDescription}
133
133
  </Text>
134
134
  ) : null}
135
135
 
136
136
  {pricingDescriptionDisclaimer ? (
137
- <Text data-cy="hero-pricing-disclaimer" as="p" className="body3">
137
+ <Text
138
+ data-cy="hero-pricing-disclaimer"
139
+ as="p"
140
+ className={cx("body3", "text-left sm:text-center xl:text-left")}
141
+ >
138
142
  {pricingDescriptionDisclaimer}
139
143
  </Text>
140
144
  ) : null}
@@ -169,7 +173,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
169
173
  <div className="flex flex-col gap-3">
170
174
  {/* desktop CTA */}
171
175
  {showCtasSideBySide ? (
172
- <div className="flex flex-col justify-center gap-3 sm:flex-row sm:gap-4">
176
+ <div className="flex flex-col justify-center gap-3 sm:flex-row sm:gap-4 xl:justify-start">
173
177
  <div className={cx(hideOnMobileCta1 && "hidden md:block")}>
174
178
  <Button
175
179
  {...primaryCta1}
@@ -221,7 +225,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
221
225
  )}
222
226
  </>
223
227
  )}
224
- {secondaryCtaPrefix && secondaryCta ? (
228
+ {secondaryCtaPrefix || secondaryCta ? (
225
229
  <div className="hidden md:flex">
226
230
  {secondaryCtaPrefix && (
227
231
  <Text as="span" className="body2 mr-1">
@@ -253,7 +257,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
253
257
  </div>
254
258
 
255
259
  {/* mobile hero image and bottom link */}
256
- <div className="xl:hidden">
260
+ <div className="w-full xl:hidden">
257
261
  <div className={"relative my-8"}>
258
262
  {hasMobileVideo ? (
259
263
  <video
@@ -263,7 +267,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
263
267
  playsInline={true}
264
268
  preload="auto"
265
269
  aria-label={heroVideoMobile?.title || title || "Hero video"}
266
- className="aspect-[1.71:1] sm:aspect-[1.41:1] w-full rounded-[40px] object-cover object-center sm:h-[420px]"
270
+ className="h-[350px] w-full rounded-[40px] object-cover object-center sm:h-[420px] md:h-[520px]"
267
271
  data-testid="hero-video-mobile"
268
272
  >
269
273
  <source src={mobileVideoUrl} type="video/mp4" />
@@ -285,7 +289,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
285
289
  <NextImage
286
290
  src={carouselImages[0]}
287
291
  alt={"Hero"}
288
- className="aspect-[1.71:1] sm:aspect-[1.41:1] w-full rounded-[40px] object-cover object-center sm:h-[420px]"
292
+ className="h-[205px] w-full rounded-[40px] object-cover object-center sm:h-[420px]"
289
293
  width={1024}
290
294
  height={600}
291
295
  sizes="100vw"
@@ -297,16 +301,6 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
297
301
  )}
298
302
  </div>
299
303
 
300
- {bottomLink && (bottomLinkLabel || bottomLink?.href) && (
301
- <div className="md:hidden">
302
- <Button
303
- {...bottomLink}
304
- onModalButtonClick={onModalButtonClick}
305
- linkClassName={`body3 ${textColorClass}`}
306
- />
307
- </div>
308
- )}
309
-
310
304
  {secondaryCtaPrefix || secondaryCta ? (
311
305
  <div
312
306
  className={`flex flex-wrap items-center ${textColorClass} md:hidden`}
@@ -325,6 +319,16 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
325
319
  )}
326
320
  </div>
327
321
  ) : null}
322
+
323
+ {bottomLink && (bottomLinkLabel || bottomLink?.href) && (
324
+ <div className="md:hidden">
325
+ <Button
326
+ {...bottomLink}
327
+ onModalButtonClick={onModalButtonClick}
328
+ linkClassName={`body3 ${textColorClass}`}
329
+ />
330
+ </div>
331
+ )}
328
332
  </div>
329
333
 
330
334
  {/* Right column: bright blue background, diagonal top edge, hero image */}