@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.
- package/dist/contentful/index.d.ts +6 -1
- package/dist/contentful/index.esm.js +2 -2
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +2 -2
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/checklist/index.tsx +22 -1
- package/src/contentful/blocks/carousel/helper.tsx +118 -26
- package/src/contentful/blocks/email-input-block/index.tsx +35 -15
- package/src/contentful/blocks/email-input-block/types.ts +6 -1
- package/src/contentful/blocks/primary-hero/index.tsx +1 -1
- package/src/hooks/use-carousel-swipe.test.ts +39 -0
- package/src/hooks/use-carousel-swipe.ts +3 -1
|
@@ -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
|
-
|
|
40
|
+
const bgColorClass = themeColor
|
|
41
|
+
? themeColorClasses[themeColor] || themeColorClasses.green
|
|
42
|
+
: bgClasses[normalizedBg as keyof typeof bgClasses] || bgClasses.green;
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const textColor =
|
|
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(
|
|
61
|
+
setEmailError(requiredErrorMessage);
|
|
49
62
|
} else if (!validateEmail(email)) {
|
|
50
|
-
setEmailError(
|
|
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=
|
|
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(
|
|
113
|
+
setEmailError(requiredErrorMessage);
|
|
94
114
|
} else if (!validateEmail(value)) {
|
|
95
|
-
setEmailError(
|
|
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(
|
|
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);
|