fragment-headless-sdk 2.3.3 → 2.4.1
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/CountdownTimer.d.ts +1 -1
- package/dist/components/Announcement/CountdownTimer.js +4 -0
- package/dist/components/Hero/DesktopHero.d.ts +3 -1
- package/dist/components/Hero/DesktopHero.js +6 -6
- package/dist/components/Hero/HeroCountdownTimer.d.ts +5 -0
- package/dist/components/Hero/HeroCountdownTimer.js +69 -0
- package/dist/components/Hero/MobileHero.d.ts +3 -1
- package/dist/components/Hero/MobileHero.js +7 -5
- package/dist/components/Hero/index.js +5 -3
- package/dist/constants/hero.d.ts +2 -1
- package/dist/constants/hero.js +1 -0
- package/dist/types/announcement.d.ts +1 -0
- package/dist/types/hero.d.ts +3 -0
- package/dist/utils/hero-resolvers.d.ts +4 -0
- package/dist/utils/hero-resolvers.js +35 -0
- package/dist/utils/styling.js +9 -1
- package/docs/CHANGELOG.md +21 -5
- package/package.json +2 -2
- package/readme.md +1 -1
|
@@ -7,6 +7,7 @@ export default function CountdownTimer({ content, }) {
|
|
|
7
7
|
minutes: "00",
|
|
8
8
|
seconds: "00",
|
|
9
9
|
});
|
|
10
|
+
const [isEnded, setIsEnded] = useState(false);
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
if (!content.counterEndDate)
|
|
12
13
|
return;
|
|
@@ -21,6 +22,7 @@ export default function CountdownTimer({ content, }) {
|
|
|
21
22
|
const minutes = String(Math.floor((diff / (1000 * 60)) % 60)).padStart(2, "0");
|
|
22
23
|
const seconds = String(Math.floor((diff / 1000) % 60)).padStart(2, "0");
|
|
23
24
|
setTimeLeft({ days, hours, minutes, seconds });
|
|
25
|
+
setIsEnded(diff === 0);
|
|
24
26
|
};
|
|
25
27
|
updateTimer();
|
|
26
28
|
const interval = setInterval(updateTimer, 1000);
|
|
@@ -50,6 +52,8 @@ export default function CountdownTimer({ content, }) {
|
|
|
50
52
|
color: colors.textColor,
|
|
51
53
|
}, styling, "countdownSeparator");
|
|
52
54
|
const separatorAttributes = mergeSlotAttributes(styling, "countdownSeparator");
|
|
55
|
+
if (isEnded)
|
|
56
|
+
return null;
|
|
53
57
|
const renderBlock = (value, label) => (React.createElement("div", { className: blockClass, style: blockStyle, ...(blockAttributes ?? {}) },
|
|
54
58
|
React.createElement("div", { className: "flex gap-1" }, value.split("").map((digit, i) => (React.createElement("span", { key: i, className: digitClass, style: digitStyle, ...(digitAttributes ?? {}) }, digit)))),
|
|
55
59
|
React.createElement("span", { className: labelClass, style: labelStyle, ...(labelAttributes ?? {}) }, label)));
|
|
@@ -9,6 +9,8 @@ interface HeroViewProps {
|
|
|
9
9
|
typography: ReturnType<typeof resolveHeroTypography>;
|
|
10
10
|
position: "left" | "center" | "right";
|
|
11
11
|
height: string;
|
|
12
|
+
buttonSpacing?: string;
|
|
13
|
+
contentSpacing?: string;
|
|
12
14
|
}
|
|
13
|
-
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, height, }: HeroViewProps): React.JSX.Element;
|
|
15
|
+
export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, height, buttonSpacing, contentSpacing, }: HeroViewProps): React.JSX.Element;
|
|
14
16
|
export {};
|
|
@@ -2,7 +2,8 @@ import React from "react";
|
|
|
2
2
|
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, height, buttonSpacing, contentSpacing, }) {
|
|
6
7
|
const getPositionClasses = () => {
|
|
7
8
|
switch (position) {
|
|
8
9
|
case "center":
|
|
@@ -22,12 +23,11 @@ export default function DesktopHero({ buttonHref, content, colors, contentWidthC
|
|
|
22
23
|
/* Image Background */
|
|
23
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" }))),
|
|
24
25
|
React.createElement("div", { className: `relative z-10 mx-auto flex h-full max-w-screen-xl flex-col justify-center px-10 ${getPositionClasses()} xl:px-4` },
|
|
25
|
-
React.createElement("div", { className: joinClassNames("max-w-full", contentWidthClass || DEFAULT_CONTENT_WIDTH_CLASS) },
|
|
26
|
+
React.createElement("div", { className: joinClassNames("max-w-full flex flex-col", contentSpacing ?? "gap-4", contentWidthClass || DEFAULT_CONTENT_WIDTH_CLASS) },
|
|
26
27
|
renderText({
|
|
27
28
|
fontSize: typography.title.fontSize,
|
|
28
29
|
lineHeight: typography.title.lineHeight,
|
|
29
30
|
text: content?.title,
|
|
30
|
-
className: "mt-4",
|
|
31
31
|
color: colors.title,
|
|
32
32
|
font: typography.title.font,
|
|
33
33
|
}),
|
|
@@ -35,12 +35,12 @@ export default function DesktopHero({ buttonHref, content, colors, contentWidthC
|
|
|
35
35
|
fontSize: typography.description.fontSize,
|
|
36
36
|
lineHeight: typography.description.lineHeight,
|
|
37
37
|
text: content?.description,
|
|
38
|
-
className: "mt-4",
|
|
39
38
|
color: colors.text,
|
|
40
39
|
font: typography.description.font,
|
|
41
40
|
}),
|
|
42
|
-
content?.
|
|
43
|
-
|
|
41
|
+
content?.showCountdown && content?.counterEndDate && (React.createElement(HeroCountdownTimer, { content: content })),
|
|
42
|
+
content?.buttonLink && content?.buttonText && (React.createElement("a", { href: buttonHref, onClick: handleClick, className: joinClassNames("no-underline", buttonSpacing) },
|
|
43
|
+
React.createElement("div", { className: "inline-block rounded-md px-8 py-2 text-2xl font-semibold drop-shadow-lg transition-all duration-200 hover:opacity-90", style: {
|
|
44
44
|
color: colors.buttonText,
|
|
45
45
|
backgroundColor: colors.buttonBackground,
|
|
46
46
|
} }, content.buttonText)))))));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, } from "../../utils";
|
|
3
|
+
import { resolveHeroCountdownColors } from "../../utils/hero-resolvers";
|
|
4
|
+
export default function HeroCountdownTimer({ content, }) {
|
|
5
|
+
const [timeLeft, setTimeLeft] = useState({
|
|
6
|
+
days: "00",
|
|
7
|
+
hours: "00",
|
|
8
|
+
minutes: "00",
|
|
9
|
+
seconds: "00",
|
|
10
|
+
});
|
|
11
|
+
const [isEnded, setIsEnded] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!content.counterEndDate)
|
|
14
|
+
return;
|
|
15
|
+
const updateTimer = () => {
|
|
16
|
+
const now = new Date().getTime();
|
|
17
|
+
const target = content.counterEndDate
|
|
18
|
+
? new Date(content.counterEndDate).getTime()
|
|
19
|
+
: 0;
|
|
20
|
+
const diff = Math.max(0, target - now);
|
|
21
|
+
const days = String(Math.floor(diff / (1000 * 60 * 60 * 24))).padStart(2, "0");
|
|
22
|
+
const hours = String(Math.floor((diff / (1000 * 60 * 60)) % 24)).padStart(2, "0");
|
|
23
|
+
const minutes = String(Math.floor((diff / (1000 * 60)) % 60)).padStart(2, "0");
|
|
24
|
+
const seconds = String(Math.floor((diff / 1000) % 60)).padStart(2, "0");
|
|
25
|
+
setTimeLeft({ days, hours, minutes, seconds });
|
|
26
|
+
setIsEnded(diff === 0);
|
|
27
|
+
};
|
|
28
|
+
updateTimer();
|
|
29
|
+
const interval = setInterval(updateTimer, 1000);
|
|
30
|
+
return () => clearInterval(interval);
|
|
31
|
+
}, [content.counterEndDate]);
|
|
32
|
+
const styling = content.styling;
|
|
33
|
+
const containerClass = mergeSlotClasses("flex items-center justify-start gap-2 py-2 w-fit", styling, "countdownContainer");
|
|
34
|
+
const containerStyle = mergeSlotStyles(undefined, styling, "countdownContainer");
|
|
35
|
+
const containerAttributes = mergeSlotAttributes(styling, "countdownContainer");
|
|
36
|
+
const blockClass = mergeSlotClasses("flex flex-col items-center", styling, "countdownBlock");
|
|
37
|
+
const blockStyle = mergeSlotStyles(undefined, styling, "countdownBlock");
|
|
38
|
+
const blockAttributes = mergeSlotAttributes(styling, "countdownBlock");
|
|
39
|
+
const colors = resolveHeroCountdownColors(content);
|
|
40
|
+
const digitClass = mergeSlotClasses("text-[1.65rem] font-semibold flex items-center justify-center leading-snug rounded px-1.5 drop-shadow-md", styling, "countdownDigit");
|
|
41
|
+
const digitStyle = mergeSlotStyles({
|
|
42
|
+
color: colors.digitColor,
|
|
43
|
+
backgroundColor: colors.digitBackgroundColor,
|
|
44
|
+
}, styling, "countdownDigit");
|
|
45
|
+
const digitAttributes = mergeSlotAttributes(styling, "countdownDigit");
|
|
46
|
+
const labelClass = mergeSlotClasses("mt-1.5 text-base font-medium leading-tight", styling, "countdownLabel");
|
|
47
|
+
const labelStyle = mergeSlotStyles({
|
|
48
|
+
color: colors.textColor,
|
|
49
|
+
}, styling, "countdownLabel");
|
|
50
|
+
const labelAttributes = mergeSlotAttributes(styling, "countdownLabel");
|
|
51
|
+
const separatorClass = mergeSlotClasses("text-[1.65rem] font-semibold -mt-4", styling, "countdownSeparator");
|
|
52
|
+
const separatorStyle = mergeSlotStyles({
|
|
53
|
+
color: colors.textColor,
|
|
54
|
+
}, styling, "countdownSeparator");
|
|
55
|
+
const separatorAttributes = mergeSlotAttributes(styling, "countdownSeparator");
|
|
56
|
+
if (isEnded)
|
|
57
|
+
return null;
|
|
58
|
+
const renderBlock = (value, label) => (React.createElement("div", { className: blockClass, style: blockStyle, ...(blockAttributes ?? {}) },
|
|
59
|
+
React.createElement("div", { className: "flex gap-2" }, value.split("").map((digit, i) => (React.createElement("span", { key: i, className: digitClass, style: digitStyle, ...(digitAttributes ?? {}) }, digit)))),
|
|
60
|
+
React.createElement("span", { className: labelClass, style: labelStyle, ...(labelAttributes ?? {}) }, label)));
|
|
61
|
+
return (React.createElement("div", { className: containerClass, style: containerStyle, ...(containerAttributes ?? {}) },
|
|
62
|
+
renderBlock(timeLeft.days, "Days"),
|
|
63
|
+
React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
|
|
64
|
+
renderBlock(timeLeft.hours, "Hours"),
|
|
65
|
+
React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
|
|
66
|
+
renderBlock(timeLeft.minutes, "Mins"),
|
|
67
|
+
React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
|
|
68
|
+
renderBlock(timeLeft.seconds, "Secs")));
|
|
69
|
+
}
|
|
@@ -6,6 +6,8 @@ interface MobileHeroProps {
|
|
|
6
6
|
content: IHeroContent;
|
|
7
7
|
colors: ReturnType<typeof resolveHeroColors>;
|
|
8
8
|
typography: ReturnType<typeof resolveHeroTypography>;
|
|
9
|
+
buttonSpacing?: string;
|
|
10
|
+
contentSpacing?: string;
|
|
9
11
|
}
|
|
10
|
-
export default function MobileHero({ buttonHref, content, colors, typography, }: MobileHeroProps): React.JSX.Element;
|
|
12
|
+
export default function MobileHero({ buttonHref, content, colors, typography, buttonSpacing, contentSpacing, }: MobileHeroProps): React.JSX.Element;
|
|
11
13
|
export {};
|
|
@@ -2,11 +2,12 @@ import React from "react";
|
|
|
2
2
|
import { SectionType } from "../../constants";
|
|
3
3
|
import { fireClickMetric } from "../../utils";
|
|
4
4
|
import { renderText, } from "../../utils/hero-resolvers";
|
|
5
|
-
|
|
5
|
+
import HeroCountdownTimer from "./HeroCountdownTimer";
|
|
6
|
+
export default function MobileHero({ buttonHref, content, colors, typography, buttonSpacing, contentSpacing, }) {
|
|
6
7
|
const handleClick = React.useCallback(() => {
|
|
7
8
|
fireClickMetric(content.measurementId, content.sectionType || SectionType.HeroBanner, content.sectionId);
|
|
8
9
|
}, [content.measurementId, content.sectionType, content.sectionId]);
|
|
9
|
-
return (React.createElement("div", { className:
|
|
10
|
+
return (React.createElement("div", { className: `relative z-10 mx-auto flex max-w-screen-md flex-col items-center justify-center py-6 text-center ${contentSpacing ?? "gap-4"}`, style: { backgroundColor: colors.background } },
|
|
10
11
|
renderText({
|
|
11
12
|
fontSize: typography.title.fontSize,
|
|
12
13
|
lineHeight: typography.title.lineHeight,
|
|
@@ -23,12 +24,13 @@ export default function MobileHero({ buttonHref, content, colors, typography, })
|
|
|
23
24
|
fontSize: typography.description.fontSize,
|
|
24
25
|
lineHeight: typography.description.lineHeight,
|
|
25
26
|
text: content?.description,
|
|
26
|
-
className: "px-4 drop-shadow-lg text-center
|
|
27
|
+
className: "px-4 drop-shadow-lg text-center",
|
|
27
28
|
color: colors.text,
|
|
28
29
|
font: typography.description.font,
|
|
29
30
|
}),
|
|
30
|
-
content?.
|
|
31
|
-
|
|
31
|
+
content?.showCountdown && content?.counterEndDate && (React.createElement(HeroCountdownTimer, { content: content })),
|
|
32
|
+
content?.buttonLink && content?.buttonText && (React.createElement("a", { href: buttonHref, onClick: handleClick, className: `no-underline ${buttonSpacing ?? ""}`.trim() },
|
|
33
|
+
React.createElement("div", { className: "rounded-md px-6 py-2 text-lg font-semibold drop-shadow-lg transition-all duration-200 hover:opacity-90", style: {
|
|
32
34
|
color: colors.buttonText,
|
|
33
35
|
backgroundColor: colors.buttonBackground,
|
|
34
36
|
} }, content.buttonText)))));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import React, { useEffect, useRef } from "react";
|
|
3
3
|
import { SectionType } from "../../constants";
|
|
4
4
|
import { fireImpressionWhenVisible, } from "../../utils";
|
|
5
|
-
import { resolveContentWidthClass, resolveHeight, resolveHeroColors, resolveHeroTypography, resolvePosition, } from "../../utils/hero-resolvers";
|
|
5
|
+
import { resolveContentWidthClass, resolveHeight, resolveHeroColors, resolveHeroTypography, resolvePosition, resolveButtonSpacing, resolveContentSpacing, } from "../../utils/hero-resolvers";
|
|
6
6
|
import DesktopHero from "./DesktopHero";
|
|
7
7
|
import MobileHero from "./MobileHero";
|
|
8
8
|
export default function Hero({ content }) {
|
|
@@ -20,9 +20,11 @@ export default function Hero({ content }) {
|
|
|
20
20
|
const typography = resolveHeroTypography(content);
|
|
21
21
|
const position = resolvePosition(content);
|
|
22
22
|
const height = resolveHeight(content);
|
|
23
|
+
const buttonSpacing = resolveButtonSpacing(content);
|
|
24
|
+
const contentSpacing = resolveContentSpacing(content);
|
|
23
25
|
return (React.createElement("div", { className: "bg-black", ref: ref, style: { backgroundColor: colors.background } },
|
|
24
26
|
React.createElement("div", { className: "hidden lg:block" },
|
|
25
|
-
React.createElement(DesktopHero, { content: content, buttonHref: buttonHref, colors: colors, contentWidthClass: contentWidthClass, typography: typography, position: position, height: height })),
|
|
27
|
+
React.createElement(DesktopHero, { content: content, buttonHref: buttonHref, colors: colors, contentWidthClass: contentWidthClass, typography: typography, position: position, height: height, buttonSpacing: buttonSpacing, contentSpacing: contentSpacing })),
|
|
26
28
|
React.createElement("div", { className: "block lg:hidden" },
|
|
27
|
-
React.createElement(MobileHero, { content: content, buttonHref: buttonHref, colors: colors, typography: typography }))));
|
|
29
|
+
React.createElement(MobileHero, { content: content, buttonHref: buttonHref, colors: colors, typography: typography, buttonSpacing: buttonSpacing, contentSpacing: contentSpacing }))));
|
|
28
30
|
}
|
package/dist/constants/hero.d.ts
CHANGED
package/dist/constants/hero.js
CHANGED
|
@@ -22,6 +22,7 @@ export interface IAnnouncement {
|
|
|
22
22
|
name: string;
|
|
23
23
|
status: AnnouncementStatus;
|
|
24
24
|
content: IAnnouncementContent | null;
|
|
25
|
+
page_order: number | null;
|
|
25
26
|
active_duration_seconds: number | null;
|
|
26
27
|
active_start_date: string | null;
|
|
27
28
|
active_end_date: string | null;
|
package/dist/types/hero.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface IHeroContent {
|
|
|
13
13
|
imageUrl: string;
|
|
14
14
|
mobileImageUrl: string;
|
|
15
15
|
videoUrl?: string;
|
|
16
|
+
counterEndDate?: string;
|
|
17
|
+
showCountdown?: boolean;
|
|
16
18
|
measurementId?: string;
|
|
17
19
|
sectionId?: string;
|
|
18
20
|
sectionType?: SectionType;
|
|
@@ -24,6 +26,7 @@ export interface IHero {
|
|
|
24
26
|
name: string;
|
|
25
27
|
type: HeroType;
|
|
26
28
|
page: ShopPage["handle"] | null;
|
|
29
|
+
page_order: number | null;
|
|
27
30
|
status: HeroStatus;
|
|
28
31
|
content: IHeroContent | null;
|
|
29
32
|
active_duration_seconds: number | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { IHeroContent } from "../types";
|
|
3
|
+
import { CountdownResolvedColors } from "./announcement-resolvers";
|
|
3
4
|
export interface HeroResolvedColors {
|
|
4
5
|
title: string;
|
|
5
6
|
text: string;
|
|
@@ -51,7 +52,10 @@ export declare function resolveHeroColors(content: IHeroContent): HeroResolvedCo
|
|
|
51
52
|
export declare function resolveContentWidthClass(content: IHeroContent): string;
|
|
52
53
|
export declare function resolvePosition(content: IHeroContent): "left" | "center" | "right";
|
|
53
54
|
export declare function resolveHeight(content: IHeroContent): string;
|
|
55
|
+
export declare function resolveButtonSpacing(content: IHeroContent): string;
|
|
56
|
+
export declare function resolveContentSpacing(content: IHeroContent): string;
|
|
54
57
|
export declare function resolveHeroTypography(content: IHeroContent): HeroTypographySettings;
|
|
58
|
+
export declare function resolveHeroCountdownColors(content: IHeroContent): CountdownResolvedColors;
|
|
55
59
|
export declare function renderText({ fontSize, lineHeight, text, className, color, font, }: {
|
|
56
60
|
fontSize: string;
|
|
57
61
|
lineHeight: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { resolveToken } from "./styling";
|
|
2
3
|
export const DEFAULT_COLORS = {
|
|
3
4
|
title: "#ffffff",
|
|
4
5
|
text: "#ffffff",
|
|
@@ -70,6 +71,24 @@ export function resolveHeight(content) {
|
|
|
70
71
|
}
|
|
71
72
|
return DEFAULT_HEIGHT_CLASS;
|
|
72
73
|
}
|
|
74
|
+
const DEFAULT_BUTTON_SPACING = "mt-4";
|
|
75
|
+
export function resolveButtonSpacing(content) {
|
|
76
|
+
const layout = content?.styling?.tokens?.layout ?? {};
|
|
77
|
+
const raw = layout.buttonSpacing;
|
|
78
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
79
|
+
return raw.trim();
|
|
80
|
+
}
|
|
81
|
+
return DEFAULT_BUTTON_SPACING;
|
|
82
|
+
}
|
|
83
|
+
const DEFAULT_CONTENT_SPACING = "gap-4";
|
|
84
|
+
export function resolveContentSpacing(content) {
|
|
85
|
+
const layout = content?.styling?.tokens?.layout ?? {};
|
|
86
|
+
const raw = layout.contentSpacing;
|
|
87
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
88
|
+
return raw.trim();
|
|
89
|
+
}
|
|
90
|
+
return DEFAULT_CONTENT_SPACING;
|
|
91
|
+
}
|
|
73
92
|
export function resolveHeroTypography(content) {
|
|
74
93
|
const typography = content?.styling?.tokens?.typography;
|
|
75
94
|
return {
|
|
@@ -88,6 +107,22 @@ export function resolveHeroTypography(content) {
|
|
|
88
107
|
},
|
|
89
108
|
};
|
|
90
109
|
}
|
|
110
|
+
const DEFAULT_HERO_COUNTDOWN_COLORS = {
|
|
111
|
+
digitColor: "#ffffff",
|
|
112
|
+
digitBackgroundColor: "#000000",
|
|
113
|
+
textColor: "#ffffff",
|
|
114
|
+
};
|
|
115
|
+
export function resolveHeroCountdownColors(content) {
|
|
116
|
+
const styling = content.styling;
|
|
117
|
+
const digitColor = resolveToken(styling, "counterDigitColor");
|
|
118
|
+
const digitBackgroundColor = resolveToken(styling, "counterDigitBackgroundColor");
|
|
119
|
+
const textColor = resolveToken(styling, "counterTextColor", digitColor);
|
|
120
|
+
return {
|
|
121
|
+
digitColor: fallbackColor(digitColor, DEFAULT_HERO_COUNTDOWN_COLORS.digitColor),
|
|
122
|
+
digitBackgroundColor: fallbackColor(digitBackgroundColor, DEFAULT_HERO_COUNTDOWN_COLORS.digitBackgroundColor),
|
|
123
|
+
textColor: fallbackColor(textColor, DEFAULT_HERO_COUNTDOWN_COLORS.textColor),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
91
126
|
export function renderText({ fontSize, lineHeight, text, className, color, font, }) {
|
|
92
127
|
if (!text || text.trim().length === 0) {
|
|
93
128
|
return null;
|
package/dist/utils/styling.js
CHANGED
|
@@ -18,6 +18,10 @@ const KNOWN_ANNOUNCEMENT_SLOTS = [
|
|
|
18
18
|
"countdownItem",
|
|
19
19
|
"countdownLabel",
|
|
20
20
|
"countdownValue",
|
|
21
|
+
"countdownContainer",
|
|
22
|
+
"countdownBlock",
|
|
23
|
+
"countdownDigit",
|
|
24
|
+
"countdownSeparator",
|
|
21
25
|
];
|
|
22
26
|
const KNOWN_HERO_SLOTS = [
|
|
23
27
|
"root",
|
|
@@ -43,7 +47,11 @@ const KNOWN_HERO_SLOTS = [
|
|
|
43
47
|
"mobileDescription",
|
|
44
48
|
"button",
|
|
45
49
|
"desktopButton",
|
|
46
|
-
"mobileButton",
|
|
50
|
+
"mobileButton", "countdownContainer",
|
|
51
|
+
"countdownBlock",
|
|
52
|
+
"countdownDigit",
|
|
53
|
+
"countdownLabel",
|
|
54
|
+
"countdownSeparator",
|
|
47
55
|
];
|
|
48
56
|
const ALL_KNOWN_SLOTS = [...KNOWN_ANNOUNCEMENT_SLOTS, ...KNOWN_HERO_SLOTS];
|
|
49
57
|
function getCacheKey(base, slots, extra) {
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -7,15 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
### [Unreleased]
|
|
9
9
|
|
|
10
|
+
### [2.4.1] - 2026-04-10
|
|
11
|
+
|
|
12
|
+
#### Added
|
|
13
|
+
|
|
14
|
+
- **`IHero.page_order`** – New `page_order: number | null` field on `IHero`. Consumers can use this to sort hero banners by their merchant-defined display order.
|
|
15
|
+
|
|
16
|
+
### [2.4.0] - 2026-03-08
|
|
17
|
+
|
|
18
|
+
#### Added
|
|
19
|
+
|
|
20
|
+
- **Hero countdown option** – Hero content supports optional countdown via `content.showCountdown` and `content.counterEndDate`. New `HeroCountdownTimer` component is used by Desktop/Mobile Hero when countdown is enabled.
|
|
21
|
+
- **Button spacing** – Hero resolvers support `buttonSpacing` for configurable CTA spacing (`hero.ts`, `hero-resolvers.ts`).
|
|
22
|
+
- **Content spacing** – Hero resolvers support `contentSpacing` for configurable gap between title, description, countdown, and button (`hero-resolvers.ts`); UI options in the app mirror this.
|
|
23
|
+
- **Countdown color tokens** – Hero countdown supports `counterDigitColor`, `counterDigitBackgroundColor`, and `counterTextColor` via `content.styling.tokens.colors`; resolved by `resolveHeroCountdownColors()` in `hero-resolvers.ts`.
|
|
24
|
+
|
|
25
|
+
#### Changed
|
|
26
|
+
|
|
27
|
+
- **Hero types** – Countdown is no longer a separate hero type in the display list; countdown is shown when `content.showCountdown && content.counterEndDate` are set.
|
|
28
|
+
- **IHeroContent** – Added `showCountdown?: boolean`; countdown rendering is gated on this and `counterEndDate` in both DesktopHero and MobileHero.
|
|
29
|
+
|
|
10
30
|
### [2.3.3] - 2026-03-07
|
|
11
31
|
|
|
12
32
|
#### Changed
|
|
13
33
|
|
|
14
|
-
- **`fetchResource`
|
|
15
|
-
- **Next.js production**: Defaults to `force-cache` with 60-second revalidation and resource-type cache tags (`fragment-hero-banners`, `fragment-announcements`) so responses are cached at the edge while staying reasonably fresh.
|
|
16
|
-
- **Next.js development / non–Next.js**: Defaults to `cache: "default"` with no revalidation.
|
|
17
|
-
- User-provided `cacheOptions` still override these defaults.
|
|
18
|
-
- **Request deduplication**: Cache keys now normalize `status` to `"enabled"` when omitted, so identical requests are deduplicated consistently.
|
|
34
|
+
- **`fetchResource` authentication** – API key is now sent as a `?apiKey=` URL query parameter instead of an `Authorization: Bearer` header, enabling Vercel's Edge Network (and other CDNs) to cache responses. No call-site changes required. Requires the Fragment app to be on a matching version; mismatched deployments will receive a 401.
|
|
19
35
|
|
|
20
36
|
### [2.3.2] - 2026-02-27
|
|
21
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fragment-headless-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "
|
|
32
|
+
"url": "https://github.com/sevenbrand/fragment-monorepo.git",
|
|
33
33
|
"directory": "packages/sdk"
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/sevenbrand/fragment-monorepo/tree/main/packages/sdk#readme",
|
package/readme.md
CHANGED
|
@@ -4,7 +4,7 @@ The official SDK for integrating with Fragment-Shopify CMS. Provides React compo
|
|
|
4
4
|
|
|
5
5
|
## ✨ What's New
|
|
6
6
|
|
|
7
|
-
**v2.
|
|
7
|
+
**v2.4.1** – `IHero` now includes a `page_order: number | null` field. Use this to sort hero banners by their merchant-defined display order; when all values are `null` the recommended behaviour is to randomize.
|
|
8
8
|
|
|
9
9
|
> See [CHANGELOG.md](./docs/CHANGELOG.md) for full release history
|
|
10
10
|
|