@windstream/react-shared-components 0.2.39 → 0.2.42
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 +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/core.d.ts +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/next/index.esm.js +1 -1
- package/dist/next/index.esm.js.map +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.esm.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/brand-button/index.tsx +4 -4
- package/src/components/link/index.test.tsx +2 -2
- package/src/components/link/index.tsx +1 -1
- package/src/contentful/blocks/button/Button.stories.tsx +0 -1
- package/src/contentful/blocks/button/index.test.tsx +109 -8
- package/src/contentful/blocks/button/index.tsx +64 -18
- package/src/contentful/blocks/button/types.ts +1 -1
- package/src/contentful/blocks/cards/blog-card/index.tsx +3 -5
- package/src/contentful/blocks/cards/floating-image-card/index.test.tsx +69 -2
- package/src/contentful/blocks/cards/floating-image-card/index.tsx +7 -4
- package/src/contentful/blocks/carousel/helper.test.tsx +33 -10
- package/src/contentful/blocks/carousel/index.test.tsx +6 -8
- package/src/contentful/blocks/carousel/types.test.ts +10 -0
- package/src/contentful/blocks/cta-callout/CtaCallout.stories.tsx +0 -1
- package/src/contentful/blocks/dynamic-tabs/ordered-tab-views.tsx +1 -1
- package/src/contentful/blocks/ordered-list/index.tsx +2 -2
- package/src/contentful/blocks/primary-hero/index.tsx +1 -1
- package/src/hooks/contentful/use-contentful-rich-text.tsx +69 -9
- package/src/hooks/use-carousel-swipe.test.ts +26 -6
- package/src/types/micro-components.ts +2 -2
|
@@ -152,13 +152,12 @@ describe("Carousel", () => {
|
|
|
152
152
|
expect(screen.getByTestId("product-card-carousel")).toBeInTheDocument();
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
it("
|
|
155
|
+
it("renders ProductCardCarousel when hasProductCards is true even if showSwitch is false", () => {
|
|
156
156
|
render(
|
|
157
157
|
<Carousel {...defaultProps} hasProductCards={true} showSwitch={false} />
|
|
158
158
|
);
|
|
159
|
-
expect(
|
|
160
|
-
|
|
161
|
-
).not.toBeInTheDocument();
|
|
159
|
+
expect(screen.getByTestId("product-card-carousel")).toBeInTheDocument();
|
|
160
|
+
expect(screen.queryByTestId("tab-switch")).not.toBeInTheDocument();
|
|
162
161
|
});
|
|
163
162
|
|
|
164
163
|
it("renders TabSwitch when showSwitch and hasProductCards with multiple tabs", () => {
|
|
@@ -279,7 +278,7 @@ describe("Carousel", () => {
|
|
|
279
278
|
expect(screen.getByTestId("testimonial-carousel")).toBeInTheDocument();
|
|
280
279
|
});
|
|
281
280
|
|
|
282
|
-
it("
|
|
281
|
+
it("renders product carousel and hides tab switch when showSwitch is false with hasProductCards", () => {
|
|
283
282
|
render(
|
|
284
283
|
<Carousel
|
|
285
284
|
{...defaultProps}
|
|
@@ -288,9 +287,8 @@ describe("Carousel", () => {
|
|
|
288
287
|
hasTestimonialCards={false}
|
|
289
288
|
/>
|
|
290
289
|
);
|
|
291
|
-
expect(
|
|
292
|
-
|
|
293
|
-
).not.toBeInTheDocument();
|
|
290
|
+
expect(screen.getByTestId("product-card-carousel")).toBeInTheDocument();
|
|
291
|
+
expect(screen.queryByTestId("tab-switch")).not.toBeInTheDocument();
|
|
294
292
|
});
|
|
295
293
|
|
|
296
294
|
it("renders with empty tabs array and no TabSwitch", () => {
|
|
@@ -10,6 +10,16 @@ describe("carousel types exports", () => {
|
|
|
10
10
|
expect(backgroundColorMap.white).toBe("bg-white");
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
it("exports backgroundColorMap with only the supported color keys", () => {
|
|
14
|
+
expect(Object.keys(backgroundColorMap)).toEqual([
|
|
15
|
+
"blue",
|
|
16
|
+
"green",
|
|
17
|
+
"orange",
|
|
18
|
+
"purple",
|
|
19
|
+
"white",
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
13
23
|
it("exports DEFAULT_TABS", () => {
|
|
14
24
|
expect(DEFAULT_TABS).toEqual(["Business Ready", "Internet Only"]);
|
|
15
25
|
});
|
|
@@ -175,7 +175,7 @@ export function OrderedSidebarView({
|
|
|
175
175
|
return (
|
|
176
176
|
<div className="flex gap-10 px-5 py-20">
|
|
177
177
|
<div className="flex-shrink-0">
|
|
178
|
-
<div className="flex min-w-[320px] gap-4 rounded-[20px] border-2
|
|
178
|
+
<div className="flex min-w-[320px] gap-4 rounded-[20px] border-2 px-6 py-7 shadow-lg">
|
|
179
179
|
{[...new Array(Math.min(2, items.length))].map((_, colIdx) => {
|
|
180
180
|
const perColumn = Math.ceil(items.length / 2);
|
|
181
181
|
|
|
@@ -30,7 +30,7 @@ export const OrderedList = ({
|
|
|
30
30
|
return (
|
|
31
31
|
<Text
|
|
32
32
|
key={`ordered-item-${index}`}
|
|
33
|
-
className="
|
|
33
|
+
className="body1 relative text-left 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="
|
|
47
|
+
<Text className="body1 relative cursor-pointer text-left leading-7">
|
|
48
48
|
{formatItemName(item.itemName)}
|
|
49
49
|
</Text>
|
|
50
50
|
</Link>
|
|
@@ -328,7 +328,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
328
328
|
) : null}
|
|
329
329
|
|
|
330
330
|
{bottomLink && (bottomLinkLabel || bottomLink?.href) && (
|
|
331
|
-
<div className="lg:hidden">
|
|
331
|
+
<div className="flex lg:hidden">
|
|
332
332
|
<Button
|
|
333
333
|
{...bottomLink}
|
|
334
334
|
onModalButtonClick={onModalButtonClick}
|
|
@@ -20,10 +20,66 @@ const isEmptyParagraph = (node: any): boolean =>
|
|
|
20
20
|
Array.isArray(node?.content) &&
|
|
21
21
|
node.content.every(
|
|
22
22
|
(child: any) =>
|
|
23
|
-
child?.nodeType === "text" &&
|
|
24
|
-
(!child.value || child.value.trim() === "")
|
|
23
|
+
child?.nodeType === "text" && (!child.value || child.value.trim() === "")
|
|
25
24
|
);
|
|
26
25
|
|
|
26
|
+
// A link is "external" only when it belongs to a different app than the one
|
|
27
|
+
// currently rendering this shared component (business vs. main/www are separate
|
|
28
|
+
// apps). Matching is environment-agnostic so a prod link like
|
|
29
|
+
// business.gokinetic.com is still "internal" while running on business-dev.
|
|
30
|
+
const hostFromUrl = (value?: string): string => {
|
|
31
|
+
if (!value) return "";
|
|
32
|
+
try {
|
|
33
|
+
const withProtocol = /^https?:\/\//i.test(value)
|
|
34
|
+
? value
|
|
35
|
+
: `https://${value}`;
|
|
36
|
+
return new URL(withProtocol).hostname.toLowerCase();
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// e.g. "business-dev.gokinetic.com" -> "gokinetic.com|business",
|
|
43
|
+
// "kinetic-dev.gokinetic.com" / "www.gokinetic.com" -> "gokinetic.com|main"
|
|
44
|
+
const appIdentity = (host: string): string => {
|
|
45
|
+
const clean = host.toLowerCase().replace(/\.$/, "");
|
|
46
|
+
const base = clean.split(".").slice(-2).join(".");
|
|
47
|
+
const subdomain = clean
|
|
48
|
+
.slice(0, clean.length - base.length)
|
|
49
|
+
.replace(/\.$/, "");
|
|
50
|
+
const firstLabel = (subdomain.split(".")[0] || "").replace(
|
|
51
|
+
/-(dev|development|staging|stage|qa|test|uat|prod|production)$/,
|
|
52
|
+
""
|
|
53
|
+
);
|
|
54
|
+
const app =
|
|
55
|
+
firstLabel === "" || firstLabel === "www" || firstLabel === "kinetic"
|
|
56
|
+
? "main"
|
|
57
|
+
: firstLabel;
|
|
58
|
+
return `${base}|${app}`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getCurrentHost = (): string => {
|
|
62
|
+
const envHost =
|
|
63
|
+
hostFromUrl(process.env.NEXT_PUBLIC_SITE_URL) ||
|
|
64
|
+
hostFromUrl(process.env.NEXT_PUBLIC_MAIN_SITE_URL);
|
|
65
|
+
if (envHost) return envHost;
|
|
66
|
+
if (typeof window !== "undefined")
|
|
67
|
+
return window.location.hostname.toLowerCase();
|
|
68
|
+
return "";
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const isExternalUrl = (url: string): boolean => {
|
|
72
|
+
if (!/^https?:\/\//i.test(url)) return false; // relative links are always internal
|
|
73
|
+
try {
|
|
74
|
+
const linkHost = new URL(url).hostname.toLowerCase();
|
|
75
|
+
const currentHost = getCurrentHost();
|
|
76
|
+
if (!currentHost) return true; // host unknown (SSR without env) → treat as external
|
|
77
|
+
return appIdentity(linkHost) !== appIdentity(currentHost);
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
27
83
|
const defaultOptions: Options = {
|
|
28
84
|
renderMark: {
|
|
29
85
|
[MARKS.BOLD]: text => <strong className="font-black">{text}</strong>,
|
|
@@ -80,12 +136,12 @@ const defaultOptions: Options = {
|
|
|
80
136
|
|
|
81
137
|
[INLINES.HYPERLINK]: (node, children) => {
|
|
82
138
|
const url = (node as any)?.data?.uri as string;
|
|
83
|
-
const external =
|
|
139
|
+
const external = isExternalUrl(url);
|
|
84
140
|
return (
|
|
85
141
|
<a
|
|
86
142
|
href={url}
|
|
87
143
|
target={external ? "_blank" : undefined}
|
|
88
|
-
rel={external ? "noopener
|
|
144
|
+
rel={external ? "noopener" : undefined}
|
|
89
145
|
>
|
|
90
146
|
{children}
|
|
91
147
|
</a>
|
|
@@ -167,22 +223,26 @@ export function renderContentfulRichText(
|
|
|
167
223
|
...options?.renderNode,
|
|
168
224
|
// Logic for links based on the isTargetBlank flag
|
|
169
225
|
[BLOCKS.PARAGRAPH]: (node, children) => (
|
|
170
|
-
<div
|
|
226
|
+
<div
|
|
227
|
+
className={[className, "whitespace-pre-line"]
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.join(" ")}
|
|
230
|
+
>
|
|
171
231
|
{isEmptyParagraph(node) ? "\n" : children}
|
|
172
232
|
</div>
|
|
173
233
|
),
|
|
174
234
|
[INLINES.HYPERLINK]: (node, children) => {
|
|
175
235
|
const url = (node as any)?.data?.uri as string;
|
|
176
|
-
const external =
|
|
236
|
+
const external = isExternalUrl(url);
|
|
177
237
|
|
|
178
|
-
// Priority: 1. Manual flag from Contentful, 2.
|
|
238
|
+
// Priority: 1. Manual flag from Contentful, 2. Same-domain URLs are internal
|
|
179
239
|
const shouldOpenBlank = target ?? external;
|
|
180
240
|
|
|
181
241
|
return (
|
|
182
242
|
<Link
|
|
183
243
|
href={url}
|
|
184
|
-
target={shouldOpenBlank ? "_blank" :
|
|
185
|
-
rel={shouldOpenBlank ? "noopener
|
|
244
|
+
target={shouldOpenBlank ? "_blank" : undefined}
|
|
245
|
+
rel={shouldOpenBlank ? "noopener" : undefined}
|
|
186
246
|
variant="default"
|
|
187
247
|
className={linkClassName}
|
|
188
248
|
>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TextDecoder, TextEncoder } from "util";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { CarouselSwipeConfig, useCarouselSwipe } from "./use-carousel-swipe";
|
|
3
4
|
|
|
@@ -368,6 +369,28 @@ describe("useCarouselSwipe", () => {
|
|
|
368
369
|
expect(result.current.containerWidth).toBe(800);
|
|
369
370
|
});
|
|
370
371
|
|
|
372
|
+
it("uses the attached containerRef width when present", () => {
|
|
373
|
+
Object.defineProperty(window, "innerWidth", {
|
|
374
|
+
writable: true,
|
|
375
|
+
value: 1024,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const { result } = renderHook(() => useCarouselSwipe(defaultConfig));
|
|
379
|
+
const container = document.createElement("div");
|
|
380
|
+
|
|
381
|
+
Object.defineProperty(container, "offsetWidth", {
|
|
382
|
+
configurable: true,
|
|
383
|
+
value: 640,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
act(() => {
|
|
387
|
+
result.current.containerRef.current = container as HTMLDivElement;
|
|
388
|
+
window.dispatchEvent(new Event("resize"));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
expect(result.current.containerWidth).toBe(640);
|
|
392
|
+
});
|
|
393
|
+
|
|
371
394
|
it("uses custom mobileBreakpoint", () => {
|
|
372
395
|
Object.defineProperty(window, "innerWidth", {
|
|
373
396
|
writable: true,
|
|
@@ -392,13 +415,10 @@ describe("useCarouselSwipe", () => {
|
|
|
392
415
|
});
|
|
393
416
|
|
|
394
417
|
describe("SSR safety (window undefined)", () => {
|
|
395
|
-
it("can be created without throwing when window is undefined and uses fallback containerWidth", () => {
|
|
418
|
+
it("can be created without throwing when window is undefined and uses fallback containerWidth", async () => {
|
|
396
419
|
const origWindow = globalThis.window;
|
|
397
420
|
// @ts-expect-error - intentionally deleting window to simulate SSR
|
|
398
421
|
delete globalThis.window;
|
|
399
|
-
|
|
400
|
-
// Polyfill TextEncoder/TextDecoder required by react-dom/server in jest
|
|
401
|
-
const { TextEncoder, TextDecoder } = require("util");
|
|
402
422
|
const origTE = globalThis.TextEncoder;
|
|
403
423
|
const origTD = globalThis.TextDecoder;
|
|
404
424
|
globalThis.TextEncoder = TextEncoder;
|
|
@@ -412,8 +432,8 @@ describe("useCarouselSwipe", () => {
|
|
|
412
432
|
}
|
|
413
433
|
|
|
414
434
|
try {
|
|
415
|
-
|
|
416
|
-
|
|
435
|
+
const { renderToString } = await import("react-dom/server");
|
|
436
|
+
|
|
417
437
|
// renderToString simulates real SSR: no window, no effects run
|
|
418
438
|
renderToString(React.createElement(TestComponent));
|
|
419
439
|
|
|
@@ -24,8 +24,8 @@ export interface Button {
|
|
|
24
24
|
preserveQueryParameters: boolean; // non-null in your model
|
|
25
25
|
badgeIcon?: Asset | null;
|
|
26
26
|
buttonVariant?: "primary_brand" | "primary_inverse" | "secondary" | null;
|
|
27
|
-
|
|
28
|
-
buttonIconPosition?:
|
|
27
|
+
buttonIconName?: string | null;
|
|
28
|
+
buttonIconPosition?: "left" | "right" | null;
|
|
29
29
|
clickToOpen?: "accordion" | "modal" | "tab" | null;
|
|
30
30
|
tabmodalNameToOpen?: string | null;
|
|
31
31
|
preDefinedFunctionExecution?: "check availability" | null;
|