@windstream/react-shared-components 0.2.11 → 0.2.12
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 +43 -2
- package/dist/contentful/index.esm.js +1 -1
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +3 -3
- package/dist/contentful/index.js.map +1 -1
- package/dist/core.d.ts +2 -2
- package/package.json +1 -1
- package/src/contentful/blocks/iframe-embed/index.test.tsx +105 -0
- package/src/contentful/blocks/iframe-embed/index.tsx +79 -0
- package/src/contentful/blocks/iframe-embed/types.ts +18 -0
- package/src/contentful/index.ts +3 -0
package/dist/core.d.ts
CHANGED
|
@@ -170,7 +170,7 @@ interface ListItemProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
|
170
170
|
|
|
171
171
|
declare const ListItem: React$1.ForwardRefExoticComponent<ListItemProps & React$1.RefAttributes<HTMLLIElement>>;
|
|
172
172
|
|
|
173
|
-
declare const List: React__default.ForwardRefExoticComponent<ListProps & React__default.RefAttributes<
|
|
173
|
+
declare const List: React__default.ForwardRefExoticComponent<ListProps & React__default.RefAttributes<HTMLOListElement | HTMLUListElement>>;
|
|
174
174
|
|
|
175
175
|
declare const iconNameList: readonly ["info", "download", "featured_seasonal_and_gifts", "visibility", "paid", "check_box", "check_box_outline_blank", "radio_button_unchecked", "radio_button_checked", "counter_1", "counter_2", "counter_3", "error", "search", "cancel", "router", "broadcast_on_home", "check", "check_circle", "lock", "shield_lock", "phone_in_talk", "call", "chevron_left", "chevron_right", "keyboard_arrow_down", "keyboard_arrow_up", "arrow_back", "arrow_forward", "help", "home_pin", "location_on", "timer", "schedule", "add", "remove", "bolt", "rocket", "cloud_sync", "close", "groups", "speed", "mail", "credit_card", "language", "forum", "contract", "local_atm", "headset_mic", "sensors", "wifi", "settings", "home", "devices", "build", "thumb_up", "desktop_windows", "block", "all_inclusive", "verified_user", "extension", "stadia_controller", "visibility_off", "close_fullscreen", "progress_activity", "travel_explore", "share", "business_center", "upload", "videocam", "expand_circle_right", "add_circle", "do_not_disturb_on", "expand_circle_up", "expand_circle_down", "replay", "fiber_manual_record", "menu", "dangerous", "star", "play_arrow", "calendar_clock", "shopping_cart"];
|
|
176
176
|
declare const OpticalSizes: readonly ["20dp", "24dp", "40dp", "48dp"];
|
|
@@ -351,7 +351,7 @@ type ButtonAsAnchor = ButtonCustomProps & Omit<AnchorHTMLAttributes<HTMLAnchorEl
|
|
|
351
351
|
};
|
|
352
352
|
type ButtonProps = ButtonAsButton | ButtonAsAnchor;
|
|
353
353
|
|
|
354
|
-
declare const BrandButton: React__default.ForwardRefExoticComponent<ButtonProps$2 & React__default.RefAttributes<
|
|
354
|
+
declare const BrandButton: React__default.ForwardRefExoticComponent<ButtonProps$2 & React__default.RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
|
|
355
355
|
|
|
356
356
|
declare const Checklist: React__default.FC<ChecklistProps>;
|
|
357
357
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { IframeEmbed } from "./index";
|
|
3
|
+
|
|
4
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
5
|
+
|
|
6
|
+
describe("IframeEmbed", () => {
|
|
7
|
+
it("renders nothing when iframeUrl is not provided", () => {
|
|
8
|
+
const { container } = render(<IframeEmbed iframeUrl="" />);
|
|
9
|
+
expect(container.querySelector("section")).not.toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("renders the iframe when iframeUrl is provided", () => {
|
|
13
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
14
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
15
|
+
expect(iframe).toBeInTheDocument();
|
|
16
|
+
expect(iframe).toHaveAttribute("src", "https://example.com/embed");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("applies anchorId to the section", () => {
|
|
20
|
+
const { container } = render(
|
|
21
|
+
<IframeEmbed iframeUrl="https://example.com/embed" anchorId="embed-section" />
|
|
22
|
+
);
|
|
23
|
+
expect(container.querySelector("#embed-section")).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("applies background color when backgroundColor is provided", () => {
|
|
27
|
+
render(
|
|
28
|
+
<IframeEmbed iframeUrl="https://example.com/embed" backgroundColor="green" />
|
|
29
|
+
);
|
|
30
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
31
|
+
expect(iframe.style.background).toBe("rgb(36, 167, 106)");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("applies transparent background when backgroundColor is not provided", () => {
|
|
35
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
36
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
37
|
+
expect(iframe.style.background).toBe("transparent");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("updates iframe height on postMessage event", () => {
|
|
41
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
42
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
43
|
+
|
|
44
|
+
fireEvent(
|
|
45
|
+
window,
|
|
46
|
+
new MessageEvent("message", { data: { iframeHeight: 300 } })
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(iframe.style.height).toBe("300px");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("removes message listener on unmount", () => {
|
|
53
|
+
const removeEventListenerSpy = jest.spyOn(window, "removeEventListener");
|
|
54
|
+
const { unmount } = render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
55
|
+
|
|
56
|
+
unmount();
|
|
57
|
+
|
|
58
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
59
|
+
"message",
|
|
60
|
+
expect.any(Function)
|
|
61
|
+
);
|
|
62
|
+
removeEventListenerSpy.mockRestore();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("does not attach message listener when iframeUrl is empty", () => {
|
|
66
|
+
const addEventListenerSpy = jest.spyOn(window, "addEventListener");
|
|
67
|
+
render(<IframeEmbed iframeUrl="" />);
|
|
68
|
+
|
|
69
|
+
expect(addEventListenerSpy).not.toHaveBeenCalledWith(
|
|
70
|
+
"message",
|
|
71
|
+
expect.any(Function)
|
|
72
|
+
);
|
|
73
|
+
addEventListenerSpy.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("applies initial height when height prop is provided", () => {
|
|
77
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" height={200} />);
|
|
78
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
79
|
+
expect(iframe.style.height).toBe("200px");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("ignores non-numeric iframeHeight values", () => {
|
|
83
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
84
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
85
|
+
|
|
86
|
+
fireEvent(window, new MessageEvent("message", { data: { iframeHeight: "abc" } }));
|
|
87
|
+
expect(iframe.style.height).toBe("");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("ignores iframeHeight exceeding max cap", () => {
|
|
91
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
92
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
93
|
+
|
|
94
|
+
fireEvent(window, new MessageEvent("message", { data: { iframeHeight: 99999 } }));
|
|
95
|
+
expect(iframe.style.height).toBe("");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("ignores negative iframeHeight values", () => {
|
|
99
|
+
render(<IframeEmbed iframeUrl="https://example.com/embed" />);
|
|
100
|
+
const iframe = screen.getByTitle("Iframe Embed");
|
|
101
|
+
|
|
102
|
+
fireEvent(window, new MessageEvent("message", { data: { iframeHeight: -100 } }));
|
|
103
|
+
expect(iframe.style.height).toBe("");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef } from "react";
|
|
4
|
+
import { IframeEmbedProps, bgColorMap } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* IframeEmbed — A generic, responsive iframe component for embedding third-party content
|
|
8
|
+
* such as Brand Featured badges, widgets, or any external HTML page.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - **Auto-resize:** Listens for `postMessage` events with an `{ iframeHeight: number }` payload
|
|
12
|
+
* from the embedded page and adjusts the iframe height dynamically.
|
|
13
|
+
* - **Background color:** Supports predefined background colors (`gray90`, `green`, `white`)
|
|
14
|
+
* behind the iframe, defaulting to transparent.
|
|
15
|
+
* - **Lazy loading:** The iframe uses `loading="lazy"` for performance.
|
|
16
|
+
* - **Anchor support:** An optional `anchorId` is applied to the wrapping `<section>` for
|
|
17
|
+
* deep-linking or scroll-to navigation.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <IframeEmbed
|
|
22
|
+
* iframeUrl="https://api.brandfeatured.com/badges/badge-news_dark.html?id=123"
|
|
23
|
+
* backgroundColor="green"
|
|
24
|
+
* anchorId="brand-badge"
|
|
25
|
+
* />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const IframeEmbed: React.FC<IframeEmbedProps> = ({
|
|
29
|
+
iframeUrl,
|
|
30
|
+
anchorId,
|
|
31
|
+
backgroundColor,
|
|
32
|
+
height,
|
|
33
|
+
}) => {
|
|
34
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!iframeUrl) return;
|
|
38
|
+
|
|
39
|
+
const handleMessage = (event: MessageEvent) => {
|
|
40
|
+
const height = event.data?.iframeHeight;
|
|
41
|
+
if (
|
|
42
|
+
typeof height === "number" &&
|
|
43
|
+
Number.isFinite(height) &&
|
|
44
|
+
height > 0 &&
|
|
45
|
+
height <= 10000 &&
|
|
46
|
+
iframeRef.current
|
|
47
|
+
) {
|
|
48
|
+
iframeRef.current.style.height = `${height}px`;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener("message", handleMessage);
|
|
53
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
54
|
+
}, [iframeUrl]);
|
|
55
|
+
|
|
56
|
+
if (!iframeUrl) return null;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<section id={anchorId} className="w-full">
|
|
60
|
+
<iframe
|
|
61
|
+
ref={iframeRef}
|
|
62
|
+
src={iframeUrl}
|
|
63
|
+
width="100%"
|
|
64
|
+
frameBorder="0"
|
|
65
|
+
scrolling="no"
|
|
66
|
+
style={{
|
|
67
|
+
background: backgroundColor
|
|
68
|
+
? bgColorMap[backgroundColor]
|
|
69
|
+
: "transparent",
|
|
70
|
+
...(height ? { height: `${height}px` } : {}),
|
|
71
|
+
}}
|
|
72
|
+
title="Iframe Embed"
|
|
73
|
+
loading="lazy"
|
|
74
|
+
/>
|
|
75
|
+
</section>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default IframeEmbed;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const bgColorMap = {
|
|
2
|
+
gray90: "#464646",
|
|
3
|
+
green: "#24A76A",
|
|
4
|
+
white: "#FFFFFF",
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export type BackgroundColor = keyof typeof bgColorMap;
|
|
8
|
+
|
|
9
|
+
export type IframeEmbedProps = {
|
|
10
|
+
/** The full URL of the third-party iframe to embed (e.g., Brand Featured badge URL). */
|
|
11
|
+
iframeUrl: string;
|
|
12
|
+
/** Optional HTML `id` for the wrapping `<section>`, useful for anchor linking or scroll-to navigation. */
|
|
13
|
+
anchorId?: string;
|
|
14
|
+
/** Optional background color applied behind the iframe. Defaults to `"transparent"` when not provided. */
|
|
15
|
+
backgroundColor?: BackgroundColor;
|
|
16
|
+
/** Optional initial/minimum height (in px) for the iframe before postMessage auto-resize kicks in. */
|
|
17
|
+
height?: number;
|
|
18
|
+
};
|
package/src/contentful/index.ts
CHANGED
|
@@ -89,6 +89,9 @@ export type { EmailInputBlockProps } from "./blocks/email-input-block/types";
|
|
|
89
89
|
export { default as CartRetentionBanner } from "./blocks/cart-retention-banner";
|
|
90
90
|
export type { TypeCartRetentionBannerFields as CartRetentionBannerProps } from "./blocks/cart-retention-banner/types";
|
|
91
91
|
|
|
92
|
+
export { IframeEmbed } from "./blocks/iframe-embed";
|
|
93
|
+
export type { IframeEmbedProps } from "./blocks/iframe-embed/types";
|
|
94
|
+
|
|
92
95
|
// Contentful Rich Text Hooks & Utils
|
|
93
96
|
export { toDocument } from "../utils/contentful/to-document";
|
|
94
97
|
|