fragment-headless-sdk 2.4.3 → 2.5.0
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/components/Announcement/index.js +11 -9
- package/dist/components/Hero/DesktopHero.d.ts +2 -2
- package/dist/components/Hero/DesktopHero.js +2 -2
- package/dist/components/Hero/index.js +2 -2
- package/dist/styles/fragment-sdk.css +11 -0
- package/dist/utils/announcement-resolvers.d.ts +8 -0
- package/dist/utils/announcement-resolvers.js +24 -0
- package/dist/utils/hero-resolvers.d.ts +11 -2
- package/dist/utils/hero-resolvers.js +35 -5
- package/dist/utils/html.d.ts +7 -0
- package/dist/utils/html.js +7 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/CHANGELOG.md +26 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
3
3
|
import React, { useEffect, useRef } from "react";
|
|
4
4
|
import { AnnouncementType, ButtonType, SectionType } from "../../constants";
|
|
5
|
-
import { fireImpressionWhenVisible, getThemeClasses, mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, resolveAnnouncementColors, } from "../../utils";
|
|
5
|
+
import { fireImpressionWhenVisible, getThemeClasses, mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, normalizeRichTextHtml, resolveAnnouncementColors, resolveAnnouncementTypography, } from "../../utils";
|
|
6
6
|
import AnnouncementButton from "./AnnouncementButton";
|
|
7
7
|
import { AnnouncementStyles } from "./AnnouncementStyles";
|
|
8
8
|
import CountdownTimer from "./CountdownTimer";
|
|
@@ -18,14 +18,16 @@ export default function Announcement({ content, type, handleClose, }) {
|
|
|
18
18
|
return null;
|
|
19
19
|
const styling = content.styling;
|
|
20
20
|
const colors = resolveAnnouncementColors(content);
|
|
21
|
+
const typography = resolveAnnouncementTypography(content);
|
|
22
|
+
const announcementHtml = normalizeRichTextHtml(content.announcementHtml);
|
|
21
23
|
const themeClasses = getThemeClasses(styling);
|
|
22
|
-
const rootClass = mergeSlotClasses(`relative w-full ${themeClasses}`, styling, "root");
|
|
24
|
+
const rootClass = mergeSlotClasses(`fragment-announcement relative w-full ${themeClasses}`, styling, "root");
|
|
23
25
|
const rootStyle = mergeSlotStyles({
|
|
24
26
|
backgroundColor: colors.background,
|
|
25
27
|
color: colors.text,
|
|
26
28
|
}, styling, "root");
|
|
27
29
|
const rootAttributes = mergeSlotAttributes(styling, "root");
|
|
28
|
-
const innerClass = mergeSlotClasses("relative mx-auto flex max-w-screen-xl flex-col md:flex-row items-center justify-center gap-4 px-10 md:
|
|
30
|
+
const innerClass = mergeSlotClasses("relative mx-auto flex max-w-screen-xl flex-col md:flex-row items-center justify-center gap-4 px-10 md:pl-4 md:pr-10 py-2 md:py-0 text-center min-h-[50px]", styling, "inner", "wrapper");
|
|
29
31
|
const innerStyle = mergeSlotStyles(undefined, styling, "inner", "wrapper");
|
|
30
32
|
const innerAttributes = mergeSlotAttributes(styling, "inner", "wrapper");
|
|
31
33
|
const marqueeContainerClass = mergeSlotClasses("flex w-full flex-col md:flex-row items-center justify-between gap-2 md:gap-4 overflow-hidden md:pr-8", styling, "marqueeContainer");
|
|
@@ -37,14 +39,14 @@ export default function Announcement({ content, type, handleClose, }) {
|
|
|
37
39
|
const marqueeTextClass = mergeSlotClasses("whitespace-nowrap animate-marquee", styling, "marqueeText");
|
|
38
40
|
const marqueeTextStyle = mergeSlotStyles(undefined, styling, "marqueeText");
|
|
39
41
|
const marqueeTextAttributes = mergeSlotAttributes(styling, "marqueeText");
|
|
40
|
-
const marqueeContentClass = mergeSlotClasses(
|
|
41
|
-
const marqueeContentStyle = mergeSlotStyles(
|
|
42
|
+
const marqueeContentClass = mergeSlotClasses(`fragment-announcement-text inline-block max-w-none font-semibold ${typography.fontSize} ${typography.lineHeight}`, styling, "marqueeContent");
|
|
43
|
+
const marqueeContentStyle = mergeSlotStyles({ fontFamily: typography.fontFamily }, styling, "marqueeContent");
|
|
42
44
|
const marqueeContentAttributes = mergeSlotAttributes(styling, "marqueeContent");
|
|
43
45
|
const contentRowClass = mergeSlotClasses("flex flex-col md:flex-row items-center justify-center gap-2 md:gap-4 w-full", styling, "contentRow");
|
|
44
46
|
const contentRowStyle = mergeSlotStyles(undefined, styling, "contentRow");
|
|
45
47
|
const contentRowAttributes = mergeSlotAttributes(styling, "contentRow");
|
|
46
|
-
const announcementTextClass = mergeSlotClasses(
|
|
47
|
-
const announcementTextStyle = mergeSlotStyles(
|
|
48
|
+
const announcementTextClass = mergeSlotClasses(`fragment-announcement-text max-w-none font-semibold ${typography.fontSize} ${typography.lineHeight}`, styling, "announcementText");
|
|
49
|
+
const announcementTextStyle = mergeSlotStyles({ fontFamily: typography.fontFamily }, styling, "announcementText");
|
|
48
50
|
const announcementTextAttributes = mergeSlotAttributes(styling, "announcementText");
|
|
49
51
|
const closeButtonClass = mergeSlotClasses("absolute right-4 top-1/2 -translate-y-1/2 cursor-pointer", styling, "closeButton");
|
|
50
52
|
const closeButtonStyle = mergeSlotStyles({ color: colors.closeButton }, styling, "closeButton");
|
|
@@ -56,12 +58,12 @@ export default function Announcement({ content, type, handleClose, }) {
|
|
|
56
58
|
React.createElement("div", { className: marqueeTextWrapperClass, style: marqueeTextWrapperStyle, ...(marqueeTextWrapperAttributes ?? {}) },
|
|
57
59
|
React.createElement("div", { className: marqueeTextClass, style: marqueeTextStyle, ...(marqueeTextAttributes ?? {}) },
|
|
58
60
|
React.createElement("div", { className: marqueeContentClass, style: marqueeContentStyle, ...(marqueeContentAttributes ?? {}), dangerouslySetInnerHTML: {
|
|
59
|
-
__html:
|
|
61
|
+
__html: announcementHtml,
|
|
60
62
|
} }))),
|
|
61
63
|
content.buttonText && content.buttonType !== ButtonType.None && (React.createElement(AnnouncementButton, { content: content, buttonHref: buttonHref })))) : (React.createElement("div", { className: contentRowClass, style: contentRowStyle, ...(contentRowAttributes ?? {}) },
|
|
62
64
|
React.createElement("div", { className: announcementTextClass, style: announcementTextStyle, ...(announcementTextAttributes ?? {}) },
|
|
63
65
|
React.createElement("div", { dangerouslySetInnerHTML: {
|
|
64
|
-
__html:
|
|
66
|
+
__html: announcementHtml,
|
|
65
67
|
} })),
|
|
66
68
|
type === AnnouncementType.Countdown && (React.createElement(CountdownTimer, { content: content })),
|
|
67
69
|
content.buttonText && content.buttonType !== ButtonType.None && (React.createElement(AnnouncementButton, { content: content, buttonHref: buttonHref })))),
|
|
@@ -8,9 +8,9 @@ interface HeroViewProps {
|
|
|
8
8
|
contentWidthClass: string;
|
|
9
9
|
typography: ReturnType<typeof resolveHeroTypography>;
|
|
10
10
|
position: "left" | "center" | "right";
|
|
11
|
-
|
|
11
|
+
heightCss: string;
|
|
12
12
|
buttonSpacing?: string;
|
|
13
13
|
contentSpacing?: string;
|
|
14
14
|
}
|
|
15
|
-
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position,
|
|
15
|
+
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, heightCss, buttonSpacing, contentSpacing, }: HeroViewProps): React.JSX.Element;
|
|
16
16
|
export {};
|
|
@@ -3,7 +3,7 @@ import { SectionType } from "../../constants";
|
|
|
3
3
|
import { fireClickMetric } from "../../utils";
|
|
4
4
|
import { DEFAULT_CONTENT_WIDTH_CLASS, joinClassNames, renderText, } from "../../utils/hero-resolvers";
|
|
5
5
|
import HeroCountdownTimer from "./HeroCountdownTimer";
|
|
6
|
-
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position,
|
|
6
|
+
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, heightCss, buttonSpacing, contentSpacing, }) {
|
|
7
7
|
const getPositionClasses = () => {
|
|
8
8
|
switch (position) {
|
|
9
9
|
case "center":
|
|
@@ -18,7 +18,7 @@ export default function DesktopHero({ buttonHref, content, colors, contentWidthC
|
|
|
18
18
|
const handleClick = React.useCallback(() => {
|
|
19
19
|
fireClickMetric(content.measurementId, content.sectionType || SectionType.HeroBanner, content.sectionId);
|
|
20
20
|
}, [content.measurementId, content.sectionType, content.sectionId]);
|
|
21
|
-
return (React.createElement("div", { className:
|
|
21
|
+
return (React.createElement("div", { className: "relative gap-4 w-full", style: { backgroundColor: colors.background, height: heightCss } },
|
|
22
22
|
content?.videoUrl ? (React.createElement("video", { src: content.videoUrl, autoPlay: true, muted: true, loop: true, playsInline: true, className: "absolute inset-0 z-0 object-cover w-full h-full" })) : (
|
|
23
23
|
/* Image Background */
|
|
24
24
|
content?.imageUrl && (React.createElement("img", { src: content.imageUrl, alt: content.title || "Hero", className: "absolute inset-0 z-0 object-cover w-full h-full" }))),
|
|
@@ -19,12 +19,12 @@ export default function Hero({ content }) {
|
|
|
19
19
|
const contentWidthClass = resolveContentWidthClass(content);
|
|
20
20
|
const typography = resolveHeroTypography(content);
|
|
21
21
|
const position = resolvePosition(content);
|
|
22
|
-
const
|
|
22
|
+
const heightCss = resolveHeight(content);
|
|
23
23
|
const buttonSpacing = resolveButtonSpacing(content);
|
|
24
24
|
const contentSpacing = resolveContentSpacing(content);
|
|
25
25
|
return (React.createElement("div", { className: "bg-black", ref: ref, style: { backgroundColor: colors.background } },
|
|
26
26
|
React.createElement("div", { className: "hidden lg:block" },
|
|
27
|
-
React.createElement(DesktopHero, { content: content, buttonHref: buttonHref, colors: colors, contentWidthClass: contentWidthClass, typography: typography, position: position,
|
|
27
|
+
React.createElement(DesktopHero, { content: content, buttonHref: buttonHref, colors: colors, contentWidthClass: contentWidthClass, typography: typography, position: position, heightCss: heightCss, buttonSpacing: buttonSpacing, contentSpacing: contentSpacing })),
|
|
28
28
|
React.createElement("div", { className: "block lg:hidden" },
|
|
29
29
|
React.createElement(MobileHero, { content: content, buttonHref: buttonHref, colors: colors, typography: typography, buttonSpacing: buttonSpacing, contentSpacing: contentSpacing }))));
|
|
30
30
|
}
|
|
@@ -29,3 +29,14 @@
|
|
|
29
29
|
color: currentColor;
|
|
30
30
|
text-decoration: underline;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/* Announcement text links: always inherit the surrounding text color and stay underlined */
|
|
34
|
+
.fragment-announcement-text a {
|
|
35
|
+
color: currentColor;
|
|
36
|
+
text-decoration: underline;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.fragment-announcement-text a:hover {
|
|
40
|
+
color: currentColor;
|
|
41
|
+
text-decoration: underline;
|
|
42
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IAnnouncementContent } from "../types";
|
|
2
|
+
import { FontKey } from "./hero-resolvers";
|
|
2
3
|
export interface AnnouncementResolvedColors {
|
|
3
4
|
background: string;
|
|
4
5
|
text: string;
|
|
@@ -11,5 +12,12 @@ export interface CountdownResolvedColors {
|
|
|
11
12
|
digitBackgroundColor: string;
|
|
12
13
|
textColor: string;
|
|
13
14
|
}
|
|
15
|
+
export interface AnnouncementTypographySettings {
|
|
16
|
+
fontSize: string;
|
|
17
|
+
lineHeight: string;
|
|
18
|
+
font: FontKey;
|
|
19
|
+
fontFamily: string;
|
|
20
|
+
}
|
|
14
21
|
export declare function resolveAnnouncementColors(content: IAnnouncementContent): AnnouncementResolvedColors;
|
|
22
|
+
export declare function resolveAnnouncementTypography(content: IAnnouncementContent): AnnouncementTypographySettings;
|
|
15
23
|
export declare function resolveCountdownColors(content: IAnnouncementContent): CountdownResolvedColors;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FONT_FAMILY_MAP } from "./hero-resolvers";
|
|
1
2
|
import { resolveToken, resolveTokenByCategory } from "./styling";
|
|
2
3
|
const DEFAULT_ANNOUNCEMENT_COLORS = {
|
|
3
4
|
background: "#000000",
|
|
@@ -6,6 +7,12 @@ const DEFAULT_ANNOUNCEMENT_COLORS = {
|
|
|
6
7
|
buttonText: "#000000",
|
|
7
8
|
closeButton: "#ffffff",
|
|
8
9
|
};
|
|
10
|
+
const DEFAULT_ANNOUNCEMENT_TYPOGRAPHY = {
|
|
11
|
+
fontSize: "text-base",
|
|
12
|
+
lineHeight: "leading-relaxed",
|
|
13
|
+
font: "geist",
|
|
14
|
+
fontFamily: FONT_FAMILY_MAP.geist,
|
|
15
|
+
};
|
|
9
16
|
const DEFAULT_COUNTDOWN_COLORS = {
|
|
10
17
|
digitColor: "#ffffff",
|
|
11
18
|
digitBackgroundColor: "#000000",
|
|
@@ -32,6 +39,23 @@ export function resolveAnnouncementColors(content) {
|
|
|
32
39
|
closeButton: fallbackColor(closeButton, fallbackColor(text, DEFAULT_ANNOUNCEMENT_COLORS.closeButton)),
|
|
33
40
|
};
|
|
34
41
|
}
|
|
42
|
+
export function resolveAnnouncementTypography(content) {
|
|
43
|
+
const typography = content?.styling?.tokens?.typography;
|
|
44
|
+
const rawFont = typography?.announcementFont;
|
|
45
|
+
const isValidFont = typeof rawFont === "string" &&
|
|
46
|
+
Object.prototype.hasOwnProperty.call(FONT_FAMILY_MAP, rawFont);
|
|
47
|
+
const font = isValidFont
|
|
48
|
+
? rawFont
|
|
49
|
+
: DEFAULT_ANNOUNCEMENT_TYPOGRAPHY.font;
|
|
50
|
+
return {
|
|
51
|
+
fontSize: typography?.announcementFontSize ??
|
|
52
|
+
DEFAULT_ANNOUNCEMENT_TYPOGRAPHY.fontSize,
|
|
53
|
+
lineHeight: typography?.announcementLineHeight ??
|
|
54
|
+
DEFAULT_ANNOUNCEMENT_TYPOGRAPHY.lineHeight,
|
|
55
|
+
font,
|
|
56
|
+
fontFamily: FONT_FAMILY_MAP[font],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
35
59
|
export function resolveCountdownColors(content) {
|
|
36
60
|
const styling = content.styling;
|
|
37
61
|
const digitColor = resolveToken(styling, "counterDigitColor");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { IHeroContent } from "../types";
|
|
3
|
-
import { CountdownResolvedColors } from "./announcement-resolvers";
|
|
3
|
+
import type { CountdownResolvedColors } from "./announcement-resolvers";
|
|
4
4
|
export interface HeroResolvedColors {
|
|
5
5
|
title: string;
|
|
6
6
|
text: string;
|
|
@@ -10,7 +10,16 @@ export interface HeroResolvedColors {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const DEFAULT_COLORS: HeroResolvedColors;
|
|
12
12
|
export declare const DEFAULT_CONTENT_WIDTH_CLASS = "w-2/5";
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const DEFAULT_HEIGHT_CSS = "400px";
|
|
14
|
+
/**
|
|
15
|
+
* Converts a Tailwind height class to a CSS value for use in inline styles.
|
|
16
|
+
* Handles arbitrary values (`h-[400px]`), named tokens (`h-screen`), and
|
|
17
|
+
* raw CSS values passed through unchanged (`400px`, `50vh`).
|
|
18
|
+
*
|
|
19
|
+
* Inline style is required because height tokens are stored in the database
|
|
20
|
+
* at runtime — Tailwind's static scanner cannot include them in the CSS bundle.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseTailwindHeight(cls: string): string;
|
|
14
23
|
export declare const FONT_FAMILY_MAP: {
|
|
15
24
|
readonly geist: "\"Geist\", -apple-system, BlinkMacSystemFont, sans-serif";
|
|
16
25
|
readonly georgia: "\"Georgia\", \"Times New Roman\", serif";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { normalizeRichTextHtml } from "./html";
|
|
2
3
|
import { resolveToken } from "./styling";
|
|
3
4
|
export const DEFAULT_COLORS = {
|
|
4
5
|
title: "#ffffff",
|
|
@@ -8,7 +9,35 @@ export const DEFAULT_COLORS = {
|
|
|
8
9
|
background: "#000000",
|
|
9
10
|
};
|
|
10
11
|
export const DEFAULT_CONTENT_WIDTH_CLASS = "w-2/5";
|
|
11
|
-
export const
|
|
12
|
+
export const DEFAULT_HEIGHT_CSS = "400px";
|
|
13
|
+
/**
|
|
14
|
+
* Converts a Tailwind height class to a CSS value for use in inline styles.
|
|
15
|
+
* Handles arbitrary values (`h-[400px]`), named tokens (`h-screen`), and
|
|
16
|
+
* raw CSS values passed through unchanged (`400px`, `50vh`).
|
|
17
|
+
*
|
|
18
|
+
* Inline style is required because height tokens are stored in the database
|
|
19
|
+
* at runtime — Tailwind's static scanner cannot include them in the CSS bundle.
|
|
20
|
+
*/
|
|
21
|
+
export function parseTailwindHeight(cls) {
|
|
22
|
+
const trimmed = cls?.trim() ?? "";
|
|
23
|
+
if (trimmed.length === 0)
|
|
24
|
+
return DEFAULT_HEIGHT_CSS;
|
|
25
|
+
const match = trimmed.match(/^h-\[(.+)\]$/);
|
|
26
|
+
if (match)
|
|
27
|
+
return match[1];
|
|
28
|
+
const map = {
|
|
29
|
+
"h-screen": "100vh",
|
|
30
|
+
"h-full": "100%",
|
|
31
|
+
"h-auto": "auto",
|
|
32
|
+
"h-fit": "fit-content",
|
|
33
|
+
};
|
|
34
|
+
if (map[trimmed])
|
|
35
|
+
return map[trimmed];
|
|
36
|
+
// Pass through values that are already valid CSS (e.g. "500px", "50vh")
|
|
37
|
+
if (!trimmed.startsWith("h-"))
|
|
38
|
+
return trimmed;
|
|
39
|
+
return DEFAULT_HEIGHT_CSS;
|
|
40
|
+
}
|
|
12
41
|
export const FONT_FAMILY_MAP = {
|
|
13
42
|
geist: '"Geist", -apple-system, BlinkMacSystemFont, sans-serif',
|
|
14
43
|
georgia: '"Georgia", "Times New Roman", serif',
|
|
@@ -67,9 +96,9 @@ export function resolveHeight(content) {
|
|
|
67
96
|
const layout = content?.styling?.tokens?.layout ?? {};
|
|
68
97
|
const rawHeight = layout.height;
|
|
69
98
|
if (typeof rawHeight === "string" && rawHeight.trim().length > 0) {
|
|
70
|
-
return rawHeight.trim();
|
|
99
|
+
return parseTailwindHeight(rawHeight.trim());
|
|
71
100
|
}
|
|
72
|
-
return
|
|
101
|
+
return DEFAULT_HEIGHT_CSS;
|
|
73
102
|
}
|
|
74
103
|
const DEFAULT_BUTTON_SPACING = "mt-4";
|
|
75
104
|
export function resolveButtonSpacing(content) {
|
|
@@ -127,16 +156,17 @@ export function renderText({ fontSize, lineHeight, text, className, color, font,
|
|
|
127
156
|
if (!text || text.trim().length === 0) {
|
|
128
157
|
return null;
|
|
129
158
|
}
|
|
130
|
-
const baseClasses = "drop-shadow-lg max-w-full
|
|
159
|
+
const baseClasses = "drop-shadow-lg max-w-full";
|
|
131
160
|
const combinedClasses = className
|
|
132
161
|
? `${baseClasses} ${fontSize} ${lineHeight} ${className}`
|
|
133
162
|
: `${baseClasses} ${fontSize} ${lineHeight}`;
|
|
163
|
+
const displayHtml = normalizeRichTextHtml(text);
|
|
134
164
|
return React.createElement("div", {
|
|
135
165
|
className: combinedClasses,
|
|
136
166
|
style: {
|
|
137
167
|
color,
|
|
138
168
|
fontFamily: FONT_FAMILY_MAP[font] ?? FONT_FAMILY_MAP.geist,
|
|
139
169
|
},
|
|
140
|
-
dangerouslySetInnerHTML: { __html:
|
|
170
|
+
dangerouslySetInnerHTML: { __html: displayHtml },
|
|
141
171
|
});
|
|
142
172
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich-text editors often emit ` ` between words to preserve spacing.
|
|
3
|
+
* That prevents normal word wrapping and causes overflow in constrained
|
|
4
|
+
* containers (marquees, single-line announcements, heros). Replace them with
|
|
5
|
+
* regular spaces so the browser can break on them as expected.
|
|
6
|
+
*/
|
|
7
|
+
export declare const normalizeRichTextHtml: (html: string | null | undefined) => string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich-text editors often emit ` ` between words to preserve spacing.
|
|
3
|
+
* That prevents normal word wrapping and causes overflow in constrained
|
|
4
|
+
* containers (marquees, single-line announcements, heros). Replace them with
|
|
5
|
+
* regular spaces so the browser can break on them as expected.
|
|
6
|
+
*/
|
|
7
|
+
export const normalizeRichTextHtml = (html) => (html ?? "").replace(/ /g, "\u0020");
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/docs/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
### [Unreleased]
|
|
9
9
|
|
|
10
|
+
#### Added
|
|
11
|
+
|
|
12
|
+
- **`normalizeRichTextHtml` utility** – New shared helper in `utils/html.ts` that normalizes rich-text editor output (replaces ` ` with regular spaces so words wrap at natural boundaries). Exported from the SDK entrypoint and used by both `Announcement` and `renderText` in `hero-resolvers`, replacing previously duplicated inline implementations.
|
|
13
|
+
- **`parseTailwindHeight` is now public** – Previously an internal helper, now exported so preview/template consumers (e.g. `HeroBannerTemplate`) can share the same Tailwind-height → CSS conversion. Also accepts raw CSS values (`500px`, `50vh`) as pass-through (`hero-resolvers.ts`).
|
|
14
|
+
- **`AnnouncementTypographySettings.fontFamily`** – `resolveAnnouncementTypography` now returns a resolved `fontFamily` CSS value alongside `font`/`fontSize`/`lineHeight`, so callers no longer need to import `FONT_FAMILY_MAP` to translate the font key themselves.
|
|
15
|
+
- **Font key validation in `resolveAnnouncementTypography`** – Invalid/stale `announcementFont` values stored in the database now fall back to `geist` instead of being silently cast to a non-existent `FontKey` (`announcement-resolvers.ts`).
|
|
16
|
+
|
|
17
|
+
#### Changed
|
|
18
|
+
|
|
19
|
+
- **Announcement applies typography tokens** – `Announcement` now applies `announcementFontSize`, `announcementLineHeight`, and `announcementFont` (as `fontFamily`) to marquee and standard text elements. Previously these tokens were ignored and text always rendered at `text-base` with no font override (`components/Announcement/index.tsx`).
|
|
20
|
+
- **Announcement inner wrapper padding** – Reserves right-side padding on `md+` (`md:pl-4 md:pr-10`) so the close button no longer overlaps long announcement text, and drops `md:text-left` so copy stays centered across breakpoints (`components/Announcement/index.tsx`).
|
|
21
|
+
- **`DesktopHero` prop rename** – `height` → `heightCss` to reflect that the value is a CSS string, not a Tailwind class. Callers updated accordingly (`components/Hero/DesktopHero.tsx`, `components/Hero/index.tsx`).
|
|
22
|
+
|
|
23
|
+
#### Fixed
|
|
24
|
+
|
|
25
|
+
- **Announcement text breaking mid-word** – `Announcement` now normalizes ` ` entities to regular spaces before rendering so words wrap at natural boundaries. Applied to both marquee and standard/countdown layouts (`components/Announcement/index.tsx`).
|
|
26
|
+
- **Announcement text class typo** – Replaced `max-w-none text-based` (invalid Tailwind class) with the correct resolved `${fontSize} ${lineHeight}` tokens (`components/Announcement/index.tsx`).
|
|
27
|
+
- **Announcement text links** – Links inside `.fragment-announcement-text` now inherit surrounding text color and stay underlined on hover, matching the existing hero link behavior (`styles/fragment-sdk.css`).
|
|
28
|
+
|
|
29
|
+
### [2.4.4] - 2026-04-14
|
|
30
|
+
|
|
31
|
+
#### Fixed
|
|
32
|
+
|
|
33
|
+
- **Hero height ignored for non-default values**: `resolveHeight` now converts stored Tailwind height classes (e.g. `h-[500px]`) to CSS values and applies them via inline `style`. Arbitrary-value classes from the database are never included in Tailwind's generated CSS bundle at build time, so any height other than the 400 px default was silently ignored (`hero-resolvers.ts`, `DesktopHero.tsx`).
|
|
34
|
+
- **Hero text breaking mid-word**: `renderText` now replaces ` ` entities with regular spaces before rendering so words wrap naturally at boundaries. Removed `break-words` which was masking the underlying cause rather than fixing it (`hero-resolvers.ts`).
|
|
35
|
+
|
|
10
36
|
### [2.4.3] - 2026-04-14
|
|
11
37
|
|
|
12
38
|
#### Fixed
|
|
13
39
|
|
|
14
|
-
- **Hero text overflow**: `renderText` base classes now include `max-w-full break-words` so title and description respect their content column width and wrap correctly when content contains non-breaking spaces.
|
|
15
40
|
- **Desktop hero content alignment**: `DesktopHero` content column now uses `items-start` so child elements (button, countdown) no longer stretch to full column width.
|
|
16
41
|
|
|
17
42
|
### [2.4.2] - 2026-04-14
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fragment-headless-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Official SDK for Fragment-Shopify CMS: React components, TypeScript types, and utilities for headless Shopify storefronts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|