@windstream/react-shared-components 0.2.57 → 0.2.58

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 (32) hide show
  1. package/dist/contentful/index.d.ts +15 -1
  2. package/dist/contentful/index.esm.js +4 -4
  3. package/dist/contentful/index.esm.js.map +1 -1
  4. package/dist/contentful/index.js +4 -4
  5. package/dist/contentful/index.js.map +1 -1
  6. package/dist/core.d.ts +2 -2
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.esm.js +1 -1
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/next/index.esm.js +1 -17
  13. package/dist/next/index.esm.js.map +1 -1
  14. package/dist/next/index.js +1 -17
  15. package/dist/next/index.js.map +1 -1
  16. package/dist/styles.css +1 -1
  17. package/package.json +1 -1
  18. package/src/components/brand-button/index.tsx +2 -2
  19. package/src/components/next-image/index.tsx +1 -1
  20. package/src/components/video-link/contentful-video-embed.tsx +1 -1
  21. package/src/components/video-link/index.tsx +1 -1
  22. package/src/components/video-link/vimeo-embed.tsx +1 -1
  23. package/src/components/video-link/youtube-embed.tsx +1 -1
  24. package/src/contentful/blocks/button/index.tsx +1 -0
  25. package/src/contentful/blocks/cart-retention-banner/CartRetentionBanner.stories.tsx +89 -1
  26. package/src/contentful/blocks/cart-retention-banner/index.test.tsx +85 -0
  27. package/src/contentful/blocks/cart-retention-banner/index.tsx +222 -39
  28. package/src/contentful/blocks/cart-retention-banner/types.ts +34 -0
  29. package/src/contentful/blocks/modal/index.tsx +14 -3
  30. package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +17 -1
  31. package/src/contentful/blocks/shape-background-wrapper/index.tsx +30 -0
  32. package/src/contentful/index.ts +5 -0
@@ -45,6 +45,13 @@ jest.mock("@shared/utils/contentful/to-document", () => ({
45
45
  toDocument: (json: any) => json,
46
46
  }));
47
47
 
48
+ jest.mock("@shared/components/next-image", () => ({
49
+ NextImage: ({ src, alt, ...props }: any) => (
50
+ // eslint-disable-next-line @next/next/no-img-element
51
+ <img src={src} alt={alt} data-testid="banner-image" {...props} />
52
+ ),
53
+ }));
54
+
48
55
  describe("CartRetentionBanner", () => {
49
56
  const defaultFields: TypeCartRetentionBannerFields = {
50
57
  entryTitle: "Cart Banner",
@@ -406,4 +413,82 @@ describe("CartRetentionBanner", () => {
406
413
  expect(screen.queryByTestId("rich-text")).not.toBeInTheDocument();
407
414
  });
408
415
  });
416
+
417
+ describe("image and layout options", () => {
418
+ it("does not render an image when none is provided", () => {
419
+ render(<CartRetentionBanner fields={defaultFields} isInPopupContainer={true} />);
420
+ expect(screen.queryByTestId("banner-image")).not.toBeInTheDocument();
421
+ });
422
+
423
+ it("renders the image when provided", () => {
424
+ const fields = {
425
+ ...defaultFields,
426
+ image: "https://example.com/image.jpg",
427
+ imageAltText: "Promo image",
428
+ };
429
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
430
+ const image = screen.getByTestId("banner-image");
431
+ expect(image).toHaveAttribute("src", "https://example.com/image.jpg");
432
+ expect(image).toHaveAttribute("alt", "Promo image");
433
+ });
434
+
435
+ it("lays the image out beside the text for left/right imagePosition", () => {
436
+ const fields = {
437
+ ...defaultFields,
438
+ image: "https://example.com/image.jpg",
439
+ imagePosition: "left" as const,
440
+ };
441
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
442
+ expect(
443
+ document.querySelector(".cart-retention-banner-image")
444
+ ).toBeInTheDocument();
445
+ });
446
+
447
+ it("applies the backgroundColor class to the popup container", () => {
448
+ const fields = { ...defaultFields, backgroundColor: "navy" as const };
449
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
450
+ const popupBody = document.querySelector(".pop-up-body");
451
+ expect(popupBody?.className).toContain("bg-[#00002D]");
452
+ });
453
+
454
+ it("passes the backgroundColor class through to the Modal", () => {
455
+ const fields = { ...defaultFields, backgroundColor: "cream500" as const };
456
+ render(<CartRetentionBanner fields={fields} />);
457
+ expect(screen.getByTestId("modal")).toBeInTheDocument();
458
+ });
459
+
460
+ it("does not render an accent shape when imageAccentColor is not provided", () => {
461
+ const fields = {
462
+ ...defaultFields,
463
+ image: "https://example.com/image.jpg",
464
+ imagePosition: "left" as const,
465
+ };
466
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
467
+ expect(document.querySelector(".shape-bg-svg")).not.toBeInTheDocument();
468
+ });
469
+
470
+ it("renders the accent shape behind the side-image layout when imageAccentColor is supported", () => {
471
+ const fields = {
472
+ ...defaultFields,
473
+ image: "https://example.com/image.jpg",
474
+ imagePosition: "left" as const,
475
+ imageAccentColor: "yellow" as const,
476
+ };
477
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
478
+ const shape = document.querySelector(".shape-bg-svg");
479
+ expect(shape).toBeInTheDocument();
480
+ expect(shape?.getAttribute("class")).toContain("text-[#F5FF1E]");
481
+ });
482
+
483
+ it("does not render an accent shape for a color ShapeBackgroundWrapper doesn't support", () => {
484
+ const fields = {
485
+ ...defaultFields,
486
+ image: "https://example.com/image.jpg",
487
+ imagePosition: "right" as const,
488
+ imageAccentColor: "cream500" as const,
489
+ };
490
+ render(<CartRetentionBanner fields={fields} isInPopupContainer={true} />);
491
+ expect(document.querySelector(".shape-bg-svg")).not.toBeInTheDocument();
492
+ });
493
+ });
409
494
  });
@@ -3,12 +3,46 @@
3
3
  import { useEffect, useState } from "react";
4
4
  import type { TypeCartRetentionBannerFields } from "./types";
5
5
 
6
+ import { NextImage } from "@shared/components/next-image";
6
7
  import { Text } from "@shared/components/text";
7
8
  import { Button } from "@shared/contentful/blocks/button";
8
9
  import { Modal } from "@shared/contentful/blocks/modal";
10
+ import { ShapeBackgroundWrapper } from "@shared/contentful/blocks/shape-background-wrapper";
9
11
  import { renderContentfulRichText } from "@shared/hooks/contentful/use-contentful-rich-text";
12
+ import { cx } from "@shared/utils";
10
13
  import { toDocument } from "@shared/utils/contentful/to-document";
11
14
 
15
+ // Literal Tailwind classes (JIT only picks up literal tokens found in source).
16
+ const backgroundColorClassMap: Record<
17
+ NonNullable<TypeCartRetentionBannerFields["backgroundColor"]>,
18
+ string
19
+ > = {
20
+ white: "bg-white",
21
+ navy: "bg-[#00002D] text-white",
22
+ green: "bg-[#26B170] text-white",
23
+ blue: "bg-[#07B2E2] text-white",
24
+ purple: "bg-[#931D69] text-white",
25
+ yellow: "bg-[#F5FF1E]",
26
+ cream500: "bg-[#FFFEEF]",
27
+ gray100: "bg-bg-fill-secondary",
28
+ transparent: "",
29
+ };
30
+
31
+ // ShapeBackgroundWrapper only supports this subset of the accent color palette.
32
+ const shapeBackgroundThemeKeys = [
33
+ "blue",
34
+ "green",
35
+ "yellow",
36
+ "purple",
37
+ "white",
38
+ "navy",
39
+ ] as const;
40
+ type ShapeBackgroundThemeKey = (typeof shapeBackgroundThemeKeys)[number];
41
+ const isShapeBackgroundThemeKey = (
42
+ color?: TypeCartRetentionBannerFields["imageAccentColor"]
43
+ ): color is ShapeBackgroundThemeKey =>
44
+ !!color && (shapeBackgroundThemeKeys as readonly string[]).includes(color);
45
+
12
46
  const CartRetentionBanner: React.FC<{
13
47
  fields: TypeCartRetentionBannerFields;
14
48
  sys?: any;
@@ -25,7 +59,27 @@ const CartRetentionBanner: React.FC<{
25
59
  checkShouldShowBanner,
26
60
  onBannerShown,
27
61
  }) => {
28
- const { anchorId, mainHeading, description, cta } = fields;
62
+ const {
63
+ anchorId,
64
+ mainHeading,
65
+ description,
66
+ cta,
67
+ image,
68
+ imageAltText,
69
+ imagePosition = "center",
70
+ backgroundColor,
71
+ imageAccentColor,
72
+ showImageAsIcon = false,
73
+ Logo,
74
+ } = fields;
75
+
76
+ const hasSideImage = Boolean(image) && imagePosition !== "center";
77
+ const isRightSide = imagePosition === "right";
78
+ const backgroundColorClassName = backgroundColor
79
+ ? (backgroundColorClassMap[backgroundColor] ?? "")
80
+ : "";
81
+ // Decorative shape sits behind the whole card, both layouts.
82
+ const showAccentShape = isShapeBackgroundThemeKey(imageAccentColor);
29
83
 
30
84
  const [modalOpen, setModalOpen] = useState<boolean>(false);
31
85
 
@@ -50,30 +104,167 @@ const CartRetentionBanner: React.FC<{
50
104
  onContinueCheckout?.();
51
105
  };
52
106
 
107
+ const renderLogo = () => {
108
+ if (!Logo) return null;
109
+
110
+ return (
111
+ <div className="cart-retention-banner-logo relative mx-auto mb-4 h-12 w-40">
112
+ <NextImage
113
+ src={Logo}
114
+ alt={mainHeading ?? ""}
115
+ fill={true}
116
+ sizes="160px"
117
+ className="object-contain"
118
+ />
119
+ </div>
120
+ );
121
+ };
122
+
123
+ const renderImage = () => {
124
+ if (!image) return null;
125
+
126
+ if (showImageAsIcon) {
127
+ return (
128
+ <div className="cart-retention-banner-image relative mx-auto mb-4 h-[87px] w-[93px] overflow-hidden md:h-[193px] md:w-[207px]">
129
+ <NextImage
130
+ src={image}
131
+ alt={imageAltText ?? ""}
132
+ fill={true}
133
+ sizes="(min-width: 768px) 207px, 93px"
134
+ className="object-contain"
135
+ />
136
+ </div>
137
+ );
138
+ }
139
+
140
+ if (hasSideImage) {
141
+ // Mobile: full-width photo on top. md+: fixed 358px column filling the modal's height.
142
+ return (
143
+ <div className="cart-retention-banner-image relative md:h-full">
144
+ <div className="relative h-[258px] w-full overflow-hidden rounded-[20px] md:h-[400px]">
145
+ <NextImage
146
+ src={image}
147
+ alt={imageAltText ?? ""}
148
+ fill={true}
149
+ sizes="(min-width: 768px) 314px, 100vw"
150
+ className="object-cover object-top"
151
+ />
152
+ </div>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <div className="cart-retention-banner-image relative mb-4 aspect-[2/1] w-full overflow-hidden rounded-[20px]">
159
+ <NextImage
160
+ src={image}
161
+ alt={imageAltText ?? ""}
162
+ fill={true}
163
+ sizes="(min-width: 640px) 400px, 90vw"
164
+ className="object-cover"
165
+ />
166
+ </div>
167
+ );
168
+ };
169
+
170
+ const renderTextContent = () => (
171
+ <>
172
+ <Text
173
+ className={cx(
174
+ "heading2",
175
+ hasSideImage ? "text-center md:text-left" : "text-center"
176
+ )}
177
+ >
178
+ {mainHeading || "Welcome back. Let's finish your order"}
179
+ </Text>
180
+ {description &&
181
+ renderContentfulRichText(
182
+ toDocument(description?.json),
183
+ false,
184
+ cx("body2", hasSideImage ? "text-center md:text-left" : "text-center")
185
+ )}
186
+ <div
187
+ className={cx(
188
+ "flex w-full pt-5",
189
+ hasSideImage ? "justify-center md:justify-start" : "justify-center"
190
+ )}
191
+ >
192
+ <Button
193
+ onClick={continueCheckout}
194
+ buttonLabel={cta?.buttonLabel ?? "Continue checkout"}
195
+ disableAnimation={cta?.disableAnimation ?? true}
196
+ animationType={cta?.animationType}
197
+ fullWidth={true}
198
+ />
199
+ </div>
200
+ </>
201
+ );
202
+
203
+ const bodyContent = hasSideImage ? (
204
+ <>
205
+ {renderLogo()}
206
+ <div
207
+ className={cx(
208
+ "flex flex-col md:grid md:gap-6",
209
+ isRightSide ? "md:grid-cols-[1fr_314px]" : "md:grid-cols-[314px_1fr]"
210
+ )}
211
+ >
212
+ <div
213
+ className={cx(
214
+ "order-1",
215
+ isRightSide && "md:order-2",
216
+ "md:max-w-[314px]",
217
+ showImageAsIcon && "flex items-center justify-center"
218
+ )}
219
+ >
220
+ {renderImage()}
221
+ </div>
222
+ <div
223
+ className={cx(
224
+ "order-2 flex flex-col justify-center gap-8 pt-5 md:gap-10 md:pt-0",
225
+ isRightSide ? "md:order-1" : ""
226
+ )}
227
+ >
228
+ {renderTextContent()}
229
+ </div>
230
+ </div>
231
+ </>
232
+ ) : (
233
+ <>
234
+ {renderLogo()}
235
+ {renderImage()}
236
+ {renderTextContent()}
237
+ </>
238
+ );
239
+
240
+ const wrappedBodyContent = (
241
+ <ShapeBackgroundWrapper
242
+ path="path5"
243
+ fill={imageAccentColor as ShapeBackgroundThemeKey}
244
+ background="transparent"
245
+ className={cx(
246
+ "rounded-3xl px-6 pb-8 md:p-6",
247
+ showImageAsIcon && "md:!py-[95px]",
248
+ Logo ? "pt-6" : "pt-13"
249
+ )}
250
+ show={showAccentShape}
251
+ >
252
+ {bodyContent}
253
+ </ShapeBackgroundWrapper>
254
+ );
255
+
53
256
  if (isInPopupContainer) {
54
257
  return (
55
258
  <div
56
- className="pop-up-body mb-5 mt-10 w-full"
259
+ className={cx(
260
+ "pop-up-body w-full",
261
+ hasSideImage ? "overflow-hidden" : "mb-5 mt-10",
262
+ backgroundColorClassName
263
+ )}
57
264
  data-section-type={"cart-retention-banner"}
58
265
  data-section-index={"0"}
59
266
  >
60
- <Text className="heading5 text-center lg:heading3">
61
- {mainHeading || "Welcome back. Let’s finish your order"}
62
- </Text>
63
- {description &&
64
- renderContentfulRichText(
65
- toDocument(description?.json),
66
- false,
67
- "body1 my-4 text-center"
68
- )}
69
- <div className="flex w-full justify-center">
70
- <Button
71
- onClick={continueCheckout}
72
- buttonLabel={cta?.buttonLabel ?? "Continue checkout"}
73
- disableAnimation={cta?.disableAnimation ?? true}
74
- animationType={cta?.animationType}
75
- />
76
- </div>
267
+ {wrappedBodyContent}
77
268
  </div>
78
269
  );
79
270
  }
@@ -83,28 +274,20 @@ const CartRetentionBanner: React.FC<{
83
274
  size="sm"
84
275
  isOpen={modalOpen}
85
276
  onRequestClose={handleClose}
86
- bodyClassName="flex items-center justify-center"
277
+ bodyClassName={cx(
278
+ hasSideImage
279
+ ? cx("overflow-hidden md:!max-w-[828px]", !showAccentShape && "p-0")
280
+ : "flex items-center justify-center",
281
+ "min-[370px] p-[0px]",
282
+ backgroundColorClassName
283
+ )}
87
284
  type="cart-retention-banner"
88
285
  >
89
- <div className="pop-up-body mb-5 mt-10 w-full">
90
- <Text className="heading5 text-center lg:heading3">
91
- {mainHeading || "Welcome back. Let&apos;s finish your order"}
92
- </Text>
93
- {description &&
94
- renderContentfulRichText(
95
- toDocument(description?.json),
96
- false,
97
- "body1 my-4 text-center"
98
- )}
99
- <div className="flex w-full justify-center">
100
- <Button
101
- onClick={continueCheckout}
102
- buttonLabel={cta?.buttonLabel ?? "Continue checkout"}
103
- disableAnimation={cta?.disableAnimation ?? true}
104
- animationType={cta?.animationType}
105
- />
106
- </div>
107
- </div>
286
+ {hasSideImage ? (
287
+ wrappedBodyContent
288
+ ) : (
289
+ <div className="pop-up-body mb-5 w-full">{wrappedBodyContent}</div>
290
+ )}
108
291
  </Modal>
109
292
  </section>
110
293
  );
@@ -1,5 +1,28 @@
1
1
  import type { AnimationType } from "@shared/components/animation-wrapper";
2
2
 
3
+ export type CartRetentionBannerImagePosition = "left" | "right" | "center";
4
+
5
+ export type CartRetentionBannerBackgroundColor =
6
+ | "white"
7
+ | "navy"
8
+ | "green"
9
+ | "blue"
10
+ | "purple"
11
+ | "yellow"
12
+ | "cream500"
13
+ | "gray100"
14
+ | "transparent";
15
+
16
+ export type CartRetentionBannerImageAccentColor =
17
+ | "white"
18
+ | "navy"
19
+ | "green"
20
+ | "blue"
21
+ | "purple"
22
+ | "yellow"
23
+ | "cream500"
24
+ | "gray100";
25
+
3
26
  export interface TypeCartRetentionBannerFields {
4
27
  entryTitle?: string;
5
28
  anchorId?: string;
@@ -13,6 +36,17 @@ export interface TypeCartRetentionBannerFields {
13
36
  animationType?: AnimationType | AnimationType[];
14
37
  [key: string]: any;
15
38
  };
39
+ /** Image URL rendered alongside the banner content. */
40
+ image?: string;
41
+ imageAltText?: string;
42
+ /** Position of the image relative to the text content; "center" (default) preserves the original image-above-heading layout. */
43
+ imagePosition?: CartRetentionBannerImagePosition;
44
+ /** Background color token applied to the banner/modal content area. */
45
+ backgroundColor?: CartRetentionBannerBackgroundColor;
46
+ /** Accent color for the decorative shape peeking from behind the image's outer corner; omit for no accent. */
47
+ imageAccentColor?: CartRetentionBannerImageAccentColor;
48
+ Logo?: string;
49
+ showImageAsIcon?: boolean;
16
50
  }
17
51
 
18
52
  export type TypeCartRetentionBanner = TypeCartRetentionBannerFields;
@@ -59,7 +59,13 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
59
59
  <Dialog.Content
60
60
  onOpenAutoFocus={e => e.preventDefault()}
61
61
  className={cx(
62
- "relative mx-auto w-full rounded-modal bg-bg p-[25px] shadow-drop focus:outline-none",
62
+ // `rounded-modal` isn't a registered Tailwind token - use an explicit radius.
63
+ "relative mx-auto w-full overflow-hidden rounded-[24px] shadow-drop focus:outline-none",
64
+ // Only fall back to the default surface color / padding when
65
+ // the caller hasn't supplied its own override (avoids two
66
+ // competing `bg-*`/`p-*` classes racing on CSS order).
67
+ !/(^|\s)bg-/.test(bodyClassName ?? "") && "bg-bg",
68
+ !/(^|\s)p-/.test(bodyClassName ?? "") && "p-[25px]",
63
69
  bodyClassName
64
70
  )}
65
71
  style={{ maxWidth: sizeToPixel[size || "lg"] }}
@@ -68,12 +74,17 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
68
74
  <Dialog.Close asChild={true}>
69
75
  <Button
70
76
  showButtonAs="unstyled"
71
- buttonClassName="absolute right-0 top-0 mr-2 mt-2 bg-bg"
77
+ // Pin icon color: button always sits on a light bg-bg
78
+ // square, so it must not inherit a light text color (e.g.
79
+ // text-white) from a dark bodyClassName like navy.
80
+ // z-10: keeps the button clickable above positioned body
81
+ // content (e.g. ShapeBackgroundWrapper's decorative layer).
82
+ buttonClassName="absolute right-0 top-0 z-10 mr-2 mt-2"
72
83
  >
73
84
  <MaterialIcon name="close" />
74
85
  </Button>
75
86
  </Dialog.Close>
76
- <div className="custom-modal-body max-h-[85vh] overflow-visible">
87
+ <div className="custom-modal-body max-h-[85vh] overflow-y-auto scroll-smooth [scrollbar-color:rgba(0,0,0,0.2)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-black/20 hover:[&::-webkit-scrollbar-thumb]:bg-black/30 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1.5">
77
88
  {title ? (
78
89
  <Text
79
90
  className={cx(
@@ -130,9 +130,25 @@ describe("ShapeBackgroundWrapper", () => {
130
130
  expect(svgs[0]).toHaveAttribute("height", "3666");
131
131
  });
132
132
 
133
+ it("renders both mobile and desktop path5 SVGs when path='path5'", () => {
134
+ const { container } = render(
135
+ <ShapeBackgroundWrapper path="path5" background="white">
136
+ <span>Path5</span>
137
+ </ShapeBackgroundWrapper>
138
+ );
139
+ const svgs = container.querySelectorAll("svg");
140
+ expect(svgs).toHaveLength(2);
141
+ expect(svgs[0]).toHaveAttribute("width", "367");
142
+ expect(svgs[0]).toHaveAttribute("height", "644");
143
+ expect(svgs[0]).toHaveClass("md:hidden");
144
+ expect(svgs[1]).toHaveAttribute("width", "367");
145
+ expect(svgs[1]).toHaveAttribute("height", "448");
146
+ expect(svgs[1]).toHaveClass("md:block");
147
+ });
148
+
133
149
  it("does not render SVG for unrecognized path value", () => {
134
150
  const { container } = render(
135
- <ShapeBackgroundWrapper path={"path5" as any} background="white">
151
+ <ShapeBackgroundWrapper path={"path6" as any} background="white">
136
152
  <span>Unknown</span>
137
153
  </ShapeBackgroundWrapper>
138
154
  );
@@ -118,6 +118,36 @@ export const ShapeBackgroundWrapper: React.FC<ShapeBackgroundWrapperProps> = ({
118
118
  <path d="M1662.93 3664.31C2153.52 3637.23 2442.1 3474.65 2872.31 2646.41C3278.25 1865.1 4009.1 647.091 3634.26 264.381C3417.79 27.9951 3025.37 31.7826 2716.76 13.902C2511.72 8.81709 2327.31 8.87474 2126.55 0.512528C2060.73 -2.00052 2040.7 85.9483 2101.3 114.088C2417.76 260.743 2628.86 431.51 2731.94 722.143C2940.79 1225.9 2508.91 2178.82 2150.63 2656.7C1779.64 3169.63 1293.64 3217.84 798.345 3136.04C534.128 3085.8 291.072 2986.98 92.1133 2864.4C33.4593 2828.06 -29.3236 2901.01 15.967 2953.13C371.841 3364.12 1128.29 3656.09 1663.56 3665.34" />
119
119
  </svg>
120
120
  )}
121
+ {path === "path5" && (
122
+ // Exact Figma export, cropped via viewBox at 0,0 (no translate hack needed);
123
+ // mobile/desktop swap via CSS breakpoint since the crop window differs by height.
124
+ <>
125
+ <svg
126
+ className={`shape-bg-svg pointer-events-none absolute left-0 top-0 z-0 block md:hidden ${variantClass} fill-current ${fillClass}`}
127
+ width="367"
128
+ height="644"
129
+ viewBox="0 0 367 644"
130
+ fill="none"
131
+ xmlns="http://www.w3.org/2000/svg"
132
+ aria-hidden="true"
133
+ focusable="false"
134
+ >
135
+ <path d="M-2083.1 549.586C-1857.78 764.836 -1656.5 828.759 -1081.83 673.557C-539.683 527.203 347.968 344.365 364.89 7.43135C381.862 -193.724 210.008 -373.544 84.6305 -523.879C-1.85227 -620.854 -81.806 -706.094 -164.9 -802.494C-192.25 -834.002 -242.154 -805.661 -229.077 -765.61C-160.659 -556.59 -149.205 -385.975 -240.751 -214.056C-386.348 97.8893 -1020.17 305.621 -1399.44 344.278C-1800.65 392.049 -2033.89 187.954 -2210.22 -76.0251C-2301.18 -219.67 -2360.21 -374.3 -2388.99 -518.696C-2397.38 -561.352 -2458.78 -559.193 -2463.58 -515.969C-2501.97 -175.709 -2310.97 298.878 -2083.31 550.316" />
136
+ </svg>
137
+ <svg
138
+ className={`shape-bg-svg pointer-events-none absolute left-0 top-0 z-0 hidden md:block ${variantClass} fill-current ${fillClass}`}
139
+ width="367"
140
+ height="448"
141
+ viewBox="0 0 367 448"
142
+ fill="none"
143
+ xmlns="http://www.w3.org/2000/svg"
144
+ aria-hidden="true"
145
+ focusable="false"
146
+ >
147
+ <path d="M-2083.1 549.587C-1857.78 764.837 -1656.5 828.759 -1081.83 673.557C-539.682 527.203 347.968 344.365 364.89 7.43156C381.863 -193.724 210.008 -373.543 84.6307 -523.879C-1.85203 -620.854 -81.8058 -706.094 -164.9 -802.494C-192.249 -834.001 -242.154 -805.661 -229.077 -765.609C-160.658 -556.59 -149.205 -385.975 -240.751 -214.056C-386.348 97.8895 -1020.17 305.621 -1399.44 344.278C-1800.65 392.049 -2033.89 187.954 -2210.22 -76.0249C-2301.18 -219.669 -2360.21 -374.3 -2388.99 -518.696C-2397.38 -561.352 -2458.78 -559.193 -2463.58 -515.969C-2501.97 -175.709 -2310.97 298.879 -2083.31 550.317" />
148
+ </svg>
149
+ </>
150
+ )}
121
151
 
122
152
  <div className="shape-bg-content relative z-[1]">{children}</div>
123
153
  </div>
@@ -98,6 +98,11 @@ export type { EmailInputBlockProps } from "./blocks/email-input-block/types";
98
98
 
99
99
  export { default as CartRetentionBanner } from "./blocks/cart-retention-banner";
100
100
  export type { TypeCartRetentionBannerFields as CartRetentionBannerProps } from "./blocks/cart-retention-banner/types";
101
+ export type {
102
+ CartRetentionBannerImagePosition,
103
+ CartRetentionBannerBackgroundColor,
104
+ CartRetentionBannerImageAccentColor,
105
+ } from "./blocks/cart-retention-banner/types";
101
106
 
102
107
  export { IframeEmbed } from "./blocks/iframe-embed";
103
108
  export type { IframeEmbedProps } from "./blocks/iframe-embed/types";