@windstream/react-shared-components 0.2.15 → 0.2.17

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.
@@ -16,23 +16,36 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
16
16
  backgroundColor = "green",
17
17
  successFeedback,
18
18
  onSubmit,
19
+ themeColor,
20
+ showPinWheel = true,
21
+ padding = "large",
22
+ requiredErrorMessage = "Email is required",
23
+ validationErrorMessage = "Invalid email address",
19
24
  } = props || {};
20
25
 
21
26
  const bgClasses: Record<string, string> = {
22
- navy: "bg-bg-fill-inverse",
23
27
  green: "bg-bg-fill-brand",
24
- blue: "bg-bg-fill-brand",
25
- yellow: "bg-bg-fill-brand-accent",
26
28
  white: "bg-bg",
29
+ gray90: "bg-[#464646]",
30
+ };
31
+
32
+ const themeColorClasses: Record<string, string> = {
33
+ green: "bg-bg-fill-brand",
34
+ blue: "bg-bg-fill-brand-supporting",
35
+ orange: "bg-orange-500",
36
+ purple: "bg-bg-fill-brand-tertiary",
27
37
  };
28
38
 
29
39
  const normalizedBg = (backgroundColor as string)?.toLowerCase?.() || "green";
30
- const bgColorClass =
31
- bgClasses[normalizedBg as keyof typeof bgClasses] || bgClasses.green;
40
+ const bgColorClass = themeColor
41
+ ? themeColorClasses[themeColor] || themeColorClasses.green
42
+ : bgClasses[normalizedBg as keyof typeof bgClasses] || bgClasses.green;
32
43
 
33
- // Determine if text should be white based on background
34
- const textWhite = ["green", "blue", "navy"].includes(normalizedBg);
35
- const textColor = textWhite ? "text-white" : "text-text";
44
+ const darkBgColors = ["green", "gray90", "blue", "orange", "purple"];
45
+ const effectiveColor = themeColor || normalizedBg;
46
+ const textColor = darkBgColors.includes(effectiveColor) ? "text-white" : "text-text";
47
+
48
+ const paddingClass = padding === "none" ? "!py-0 md:!py-0" : "!py-8 md:!py-20";
36
49
 
37
50
  const [email, setEmail] = useState("");
38
51
  const [formSubmitted, setFormSubmitted] = useState(false);
@@ -45,9 +58,9 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
45
58
 
46
59
  const handleOnSubmit = () => {
47
60
  if (!email.trim()) {
48
- setEmailError("Email is required");
61
+ setEmailError(requiredErrorMessage);
49
62
  } else if (!validateEmail(email)) {
50
- setEmailError("Invalid email address");
63
+ setEmailError(validationErrorMessage);
51
64
  } else {
52
65
  onSubmit({ email });
53
66
  setEmail("");
@@ -57,10 +70,17 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
57
70
  };
58
71
 
59
72
  return (
60
- <section id={anchorId} className="w-full px-4 !py-8 md:!py-20 md:px-0">
73
+ <section id={anchorId} className={`w-full px-4 ${paddingClass} md:px-0`}>
61
74
  <div
62
- className={`mx-auto max-w-[1200px] overflow-hidden !rounded-[20px] md:rounded-[20px] flex flex-col items-start !px-5 !pt-6 !pb-10 md:!p-10 md:!pt-12 !h-[344px] md:!w-auto md:!h-[367px] ${bgColorClass}`}
75
+ className={`mx-auto max-w-[1200px] overflow-hidden !rounded-[20px] md:rounded-[20px] flex flex-col items-start !px-5 !pt-6 !pb-10 md:!p-10 md:!pt-12 !h-[344px] md:!w-auto md:!h-[367px] relative ${bgColorClass}`}
63
76
  >
77
+ {showPinWheel && (
78
+ <div className={`absolute right-[-28px] top-0 hidden h-[496px] w-[485px] pointer-events-none lg:block ${textColor}`} aria-hidden="true">
79
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 485 496" className="h-full w-full opacity-10">
80
+ <circle cx="242" cy="248" r="200" fill="currentColor" />
81
+ </svg>
82
+ </div>
83
+ )}
64
84
  <div className="w-full">
65
85
  <div className="lg:w-[548px]">
66
86
  {title && (
@@ -90,9 +110,9 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
90
110
  setEmail(value);
91
111
  if (formSubmitted) setFormSubmitted(false);
92
112
  if (!value.trim()) {
93
- setEmailError("Email is required");
113
+ setEmailError(requiredErrorMessage);
94
114
  } else if (!validateEmail(value)) {
95
- setEmailError("Invalid email address");
115
+ setEmailError(validationErrorMessage);
96
116
  } else {
97
117
  setEmailError("");
98
118
  }
@@ -126,4 +146,4 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
126
146
  );
127
147
  };
128
148
 
129
- export default EmailInputBlock;
149
+ export default EmailInputBlock;
@@ -9,7 +9,12 @@ export interface EmailInputBlockProps {
9
9
  successFeedback?: string;
10
10
  ctaText?: string;
11
11
  caption?: React.ReactNode;
12
- backgroundColor?: "green" | "white" | "yellow" | "blue" | "navy";
12
+ backgroundColor?: "green" | "white" | "yellow" | "blue" | "navy" | "gray90";
13
+ themeColor?: "blue" | "green" | "orange" | "purple";
14
+ showPinWheel?: boolean;
15
+ padding?: "none" | "large";
16
+ validationErrorMessage?: string;
17
+ requiredErrorMessage?: string;
13
18
  onSubmit: (values: EmailInputFormValues) => void;
14
19
  }
15
20
 
@@ -84,7 +84,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
84
84
  {/* checklist */}
85
85
  {checklist && (
86
86
  <Checklist
87
- listItemClassName="text-white text-left"
87
+ listItemClassName="body2 text-white text-left"
88
88
  items={checklist.map(item =>
89
89
  item.iconUrl
90
90
  ? { title: item.title, iconUrl: item.iconUrl }
@@ -390,4 +390,43 @@ describe("useCarouselSwipe", () => {
390
390
  removeEventListenerSpy.mockRestore();
391
391
  });
392
392
  });
393
+
394
+ describe("SSR safety (window undefined)", () => {
395
+ it("can be created without throwing when window is undefined and uses fallback containerWidth", () => {
396
+ const origWindow = globalThis.window;
397
+ // @ts-expect-error - intentionally deleting window to simulate SSR
398
+ delete globalThis.window;
399
+
400
+ // Polyfill TextEncoder/TextDecoder required by react-dom/server in jest
401
+ const { TextEncoder, TextDecoder } = require("util");
402
+ const origTE = globalThis.TextEncoder;
403
+ const origTD = globalThis.TextDecoder;
404
+ globalThis.TextEncoder = TextEncoder;
405
+ globalThis.TextDecoder = TextDecoder;
406
+
407
+ let hookResult: ReturnType<typeof useCarouselSwipe> | undefined;
408
+
409
+ function TestComponent() {
410
+ hookResult = useCarouselSwipe(defaultConfig);
411
+ return null;
412
+ }
413
+
414
+ try {
415
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
416
+ const { renderToString } = require("react-dom/server");
417
+ // renderToString simulates real SSR: no window, no effects run
418
+ renderToString(React.createElement(TestComponent));
419
+
420
+ // containerWidth should use the fallback value of 1 since window is absent
421
+ expect(hookResult!.containerWidth).toBe(1);
422
+ expect(hookResult!.isSwiping).toBe(false);
423
+ expect(hookResult!.currentIndex).toBe(0);
424
+ } finally {
425
+ // Always restore globals so subsequent tests aren't affected
426
+ globalThis.window = origWindow;
427
+ globalThis.TextEncoder = origTE;
428
+ globalThis.TextDecoder = origTD;
429
+ }
430
+ });
431
+ });
393
432
  });
@@ -115,7 +115,9 @@ export function useCarouselSwipe(
115
115
  const [currentIndex, setCurrentIndex] = useState(0);
116
116
  const [swipeOffset, setSwipeOffset] = useState(0);
117
117
  const [isSwiping, setIsSwiping] = useState(false);
118
- const [containerWidth, setContainerWidth] = useState(window.innerWidth);
118
+ const [containerWidth, setContainerWidth] = useState(
119
+ typeof window !== "undefined" ? window.innerWidth : 1,
120
+ );
119
121
  // Performance: Store mobile state to avoid repeated window.innerWidth checks on every render
120
122
  // This prevents expensive DOM queries during swipe operations
121
123
  const [isMobile, setIsMobile] = useState(false);