fragment-headless-sdk 2.0.0 → 2.1.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.
@@ -2,7 +2,6 @@ import React, { useEffect, useRef } from "react";
2
2
  import { AnnouncementType, ButtonType } from "../../constants";
3
3
  import { buildClickUrl, fireImpressionWhenVisible, getThemeClasses, mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, resolveToken, resolveTokenByCategory, } from "../../utils";
4
4
  import AnnouncementButton from "./AnnouncementButton";
5
- import { AnnouncementStyles } from "./AnnouncementStyles";
6
5
  import CountdownTimer from "./CountdownTimer";
7
6
  export default function Announcement({ content, type, handleClose, }) {
8
7
  const ref = useRef(null);
@@ -55,7 +54,6 @@ export default function Announcement({ content, type, handleClose, }) {
55
54
  const closeButtonStyle = mergeSlotStyles({ color: closeButtonColor }, styling, "closeButton");
56
55
  const closeButtonAttributes = mergeSlotAttributes(styling, "closeButton");
57
56
  return (React.createElement("div", { ref: ref, className: rootClass, style: rootStyle, ...(rootAttributes ?? {}) },
58
- React.createElement(AnnouncementStyles, null),
59
57
  React.createElement("div", { className: innerClass, style: innerStyle, ...(innerAttributes ?? {}) },
60
58
  type === AnnouncementType.Marquee ? (React.createElement("div", { className: marqueeContainerClass, style: marqueeContainerStyle, ...(marqueeContainerAttributes ?? {}) },
61
59
  React.createElement("div", { className: marqueeTextWrapperClass, style: marqueeTextWrapperStyle, ...(marqueeTextWrapperAttributes ?? {}) },
@@ -1,6 +1,14 @@
1
1
  import React from "react";
2
2
  import { IHeroContent } from "../../types";
3
- export default function DesktopHero({ buttonHref, content, }: {
3
+ import { resolveHeroColors, resolveHeroTypography } from "../../utils/hero-resolvers";
4
+ interface HeroViewProps {
4
5
  buttonHref?: string;
5
6
  content: IHeroContent;
6
- }): React.JSX.Element;
7
+ colors: ReturnType<typeof resolveHeroColors>;
8
+ contentWidthClass: string;
9
+ typography: ReturnType<typeof resolveHeroTypography>;
10
+ position: "left" | "center" | "right";
11
+ height: string;
12
+ }
13
+ export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, height, }: HeroViewProps): React.JSX.Element;
14
+ export {};
@@ -1,45 +1,41 @@
1
1
  import React from "react";
2
- import { mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, resolveToken, resolveTokenByCategory, } from "../../utils";
3
- export default function DesktopHero({ buttonHref, content, }) {
4
- const styling = content.styling;
5
- const containerClass = mergeSlotClasses("relative h-[400px] gap-4 w-full", styling, "desktopContainer", "container");
6
- const containerStyle = mergeSlotStyles(undefined, styling, "desktopContainer", "container");
7
- const containerAttributes = mergeSlotAttributes(styling, "desktopContainer", "container");
8
- const imageClass = mergeSlotClasses("absolute inset-0 z-0 object-cover w-full h-full", styling, "desktopImage", "image");
9
- const imageStyle = mergeSlotStyles(undefined, styling, "desktopImage", "image");
10
- const imageAttributes = mergeSlotAttributes(styling, "desktopImage", "image");
11
- const titleColor = resolveTokenByCategory(styling, "colors", "title") ||
12
- resolveToken(styling, "titleColor", "#ffffff");
13
- const titleStyle = mergeSlotStyles({ color: titleColor }, styling, "desktopTitle", "title");
14
- const titleClass = mergeSlotClasses("text-5xl font-bold leading-tight drop-shadow-xl", styling, "desktopTitle", "title");
15
- const titleAttributes = mergeSlotAttributes(styling, "desktopTitle", "title");
16
- const descriptionColor = resolveTokenByCategory(styling, "colors", "text") ||
17
- resolveToken(styling, "textColor");
18
- const descriptionStyle = mergeSlotStyles(descriptionColor ? { color: descriptionColor } : undefined, styling, "desktopDescription", "description");
19
- const descriptionClass = mergeSlotClasses("mt-4 text-2xl drop-shadow-lg prose", styling, "desktopDescription", "description");
20
- const descriptionAttributes = mergeSlotAttributes(styling, "desktopDescription", "description");
21
- const contentWrapperClass = mergeSlotClasses("relative z-10 mx-auto flex h-full max-w-screen-xl flex-col items-start justify-center px-10 text-left xl:px-4", styling, "desktopContentWrapper", "contentWrapper");
22
- const contentWrapperStyle = mergeSlotStyles(undefined, styling, "desktopContentWrapper", "contentWrapper");
23
- const contentWrapperAttributes = mergeSlotAttributes(styling, "desktopContentWrapper", "contentWrapper");
24
- const contentInnerClass = mergeSlotClasses("w-2/5", styling, "desktopContent", "content");
25
- const contentInnerStyle = mergeSlotStyles(undefined, styling, "desktopContent", "content");
26
- const contentInnerAttributes = mergeSlotAttributes(styling, "desktopContent", "content");
27
- const buttonBgColor = resolveTokenByCategory(styling, "colors", "button") ||
28
- resolveToken(styling, "buttonColor");
29
- const buttonTextColor = resolveTokenByCategory(styling, "colors", "buttonText") ||
30
- resolveToken(styling, "buttonTextColor");
31
- const buttonStyle = mergeSlotStyles({
32
- backgroundColor: buttonBgColor,
33
- color: buttonTextColor,
34
- }, styling, "desktopButton", "button");
35
- const buttonClass = mergeSlotClasses("mt-6 rounded-md px-8 py-2 text-2xl font-semibold drop-shadow-lg transition-all duration-200 hover:bg-gray-800 inline-block", styling, "desktopButton", "button");
36
- const buttonAttributes = mergeSlotAttributes(styling, "desktopButton", "button");
37
- return (React.createElement("div", { className: containerClass, style: containerStyle, ...(containerAttributes ?? {}) },
38
- content?.imageUrl && (React.createElement("img", { src: content.imageUrl, alt: content.title || "Hero", className: imageClass, style: imageStyle, ...(imageAttributes ?? {}) })),
39
- React.createElement("div", { className: contentWrapperClass, style: contentWrapperStyle, ...(contentWrapperAttributes ?? {}) },
40
- React.createElement("div", { className: contentInnerClass, style: contentInnerStyle, ...(contentInnerAttributes ?? {}) },
41
- content?.title && (React.createElement("h1", { className: titleClass, style: titleStyle, ...(titleAttributes ?? {}) }, content.title)),
42
- content?.description && (React.createElement("div", { className: descriptionClass, style: descriptionStyle, ...(descriptionAttributes ?? {}), dangerouslySetInnerHTML: { __html: content.description } })),
2
+ import { DEFAULT_CONTENT_WIDTH_CLASS, joinClassNames, renderText, } from "../../utils/hero-resolvers";
3
+ export default function DesktopHero({ buttonHref, content, colors, contentWidthClass, typography, position, height, }) {
4
+ const getPositionClasses = () => {
5
+ switch (position) {
6
+ case "center":
7
+ return "items-center text-center";
8
+ case "right":
9
+ return "items-end text-right";
10
+ case "left":
11
+ default:
12
+ return "items-start text-left";
13
+ }
14
+ };
15
+ return (React.createElement("div", { className: `relative ${height} gap-4 w-full`, style: { backgroundColor: colors.background } },
16
+ 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" })) : (
17
+ /* Image Background */
18
+ 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
+ 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` },
20
+ React.createElement("div", { className: joinClassNames("max-w-full", contentWidthClass || DEFAULT_CONTENT_WIDTH_CLASS) },
21
+ renderText({
22
+ fontSize: typography.title.fontSize,
23
+ lineHeight: typography.title.lineHeight,
24
+ text: content?.title,
25
+ color: colors.title,
26
+ font: typography.title.font,
27
+ }),
28
+ renderText({
29
+ fontSize: typography.description.fontSize,
30
+ lineHeight: typography.description.lineHeight,
31
+ text: content?.description,
32
+ className: "mt-4",
33
+ color: colors.text,
34
+ font: typography.description.font,
35
+ }),
43
36
  content?.buttonLink && content?.buttonText && (React.createElement("a", { href: buttonHref, target: "_blank", rel: "noopener noreferrer", className: "no-underline" },
44
- React.createElement("div", { className: buttonClass, style: buttonStyle, ...(buttonAttributes ?? {}) }, content.buttonText)))))));
37
+ React.createElement("div", { className: "mt-6 inline-block rounded-md px-8 py-2 text-2xl font-semibold drop-shadow-lg transition-all duration-200 hover:opacity-90", style: {
38
+ color: colors.buttonText,
39
+ backgroundColor: colors.buttonBackground,
40
+ } }, content.buttonText)))))));
45
41
  }
@@ -1,6 +1,11 @@
1
1
  import React from "react";
2
2
  import { IHeroContent } from "../../types";
3
- export default function MobileHero({ buttonHref, content, }: {
3
+ import { resolveHeroColors, resolveHeroTypography } from "../../utils/hero-resolvers";
4
+ interface MobileHeroProps {
4
5
  buttonHref?: string;
5
6
  content: IHeroContent;
6
- }): React.JSX.Element;
7
+ colors: ReturnType<typeof resolveHeroColors>;
8
+ typography: ReturnType<typeof resolveHeroTypography>;
9
+ }
10
+ export default function MobileHero({ buttonHref, content, colors, typography, }: MobileHeroProps): React.JSX.Element;
11
+ export {};
@@ -1,41 +1,39 @@
1
1
  import React from "react";
2
- import { mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, resolveToken, resolveTokenByCategory, } from "../../utils";
3
- export default function MobileHero({ buttonHref, content, }) {
4
- const styling = content.styling;
5
- const containerClass = mergeSlotClasses("relative z-10 mx-auto gap-4 flex h-full max-w-screen-md flex-col items-center justify-center py-6 text-center", styling, "mobileContainer", "container");
6
- const containerStyle = mergeSlotStyles(undefined, styling, "mobileContainer", "container");
7
- const containerAttributes = mergeSlotAttributes(styling, "mobileContainer", "container");
8
- const titleColor = resolveTokenByCategory(styling, "colors", "title") ||
9
- resolveToken(styling, "titleColor");
10
- const titleClass = mergeSlotClasses("text-3xl font-bold drop-shadow-xl px-4", styling, "mobileTitle", "title");
11
- const titleStyle = mergeSlotStyles(titleColor ? { color: titleColor } : undefined, styling, "mobileTitle", "title");
12
- const titleAttributes = mergeSlotAttributes(styling, "mobileTitle", "title");
13
- const imageWrapperClass = mergeSlotClasses("w-full", styling, "mobileImageWrapper", "imageWrapper");
14
- const imageWrapperStyle = mergeSlotStyles(undefined, styling, "mobileImageWrapper", "imageWrapper");
15
- const imageWrapperAttributes = mergeSlotAttributes(styling, "mobileImageWrapper", "imageWrapper");
16
- const imageClass = mergeSlotClasses("h-full w-full object-cover", styling, "mobileImage", "image");
17
- const imageStyle = mergeSlotStyles(undefined, styling, "mobileImage", "image");
18
- const imageAttributes = mergeSlotAttributes(styling, "mobileImage", "image");
19
- const descriptionColor = resolveTokenByCategory(styling, "colors", "text") ||
20
- resolveToken(styling, "textColor");
21
- const descriptionClass = mergeSlotClasses("px-4 text-2xl drop-shadow-lg prose", styling, "mobileDescription", "description");
22
- const descriptionStyle = mergeSlotStyles(descriptionColor ? { color: descriptionColor } : undefined, styling, "mobileDescription", "description");
23
- const descriptionAttributes = mergeSlotAttributes(styling, "mobileDescription", "description");
24
- const buttonBgColor = resolveTokenByCategory(styling, "colors", "button") ||
25
- resolveToken(styling, "buttonColor");
26
- const buttonTextColor = resolveTokenByCategory(styling, "colors", "buttonText") ||
27
- resolveToken(styling, "buttonTextColor");
28
- const buttonClass = mergeSlotClasses("mb-2 rounded-md px-6 py-2 text-lg font-semibold drop-shadow-lg transition-all duration-200 hover:bg-gray-800", styling, "mobileButton", "button");
29
- const buttonStyle = mergeSlotStyles({
30
- backgroundColor: buttonBgColor,
31
- color: buttonTextColor,
32
- }, styling, "mobileButton", "button");
33
- const buttonAttributes = mergeSlotAttributes(styling, "mobileButton", "button");
34
- return (React.createElement("div", { className: containerClass, style: containerStyle, ...(containerAttributes ?? {}) },
35
- content?.title && (React.createElement("h1", { className: titleClass, style: titleStyle, ...(titleAttributes ?? {}) }, content.title)),
36
- (content?.mobileImageUrl || content?.imageUrl) && (React.createElement("div", { className: imageWrapperClass, style: imageWrapperStyle, ...(imageWrapperAttributes ?? {}) },
37
- React.createElement("img", { src: content.mobileImageUrl || content.imageUrl || "", alt: content.title || "Hero", className: imageClass, style: imageStyle, ...(imageAttributes ?? {}) }))),
38
- content?.description && (React.createElement("div", { className: descriptionClass, style: descriptionStyle, ...(descriptionAttributes ?? {}), dangerouslySetInnerHTML: { __html: content.description } })),
2
+ import { ensureSafeColor } from "../../utils/color";
3
+ import { renderText, } from "../../utils/hero-resolvers";
4
+ export default function MobileHero({ buttonHref, content, colors, typography, }) {
5
+ const safeTitleColor = ensureSafeColor(colors.title, "#ffffff");
6
+ const safeTextColor = ensureSafeColor(colors.text, "#ffffff");
7
+ const titleFontSize = typography.title.fontSize === "text-5xl"
8
+ ? "text-3xl"
9
+ : typography.title.fontSize;
10
+ const descriptionFontSize = typography.description.fontSize === "text-3xl"
11
+ ? "text-lg"
12
+ : typography.description.fontSize;
13
+ return (React.createElement("div", { className: "relative z-10 mx-auto gap-4 flex max-w-screen-md flex-col items-center justify-center py-6 text-center", style: { backgroundColor: colors.background } },
14
+ renderText({
15
+ fontSize: titleFontSize,
16
+ lineHeight: typography.title.lineHeight,
17
+ text: content?.title,
18
+ className: "px-4 drop-shadow-xl text-center font-bold",
19
+ color: safeTitleColor,
20
+ font: typography.title.font,
21
+ }),
22
+ content?.videoUrl ? (React.createElement("div", { className: "w-full" },
23
+ React.createElement("video", { src: content.videoUrl, autoPlay: true, muted: true, loop: true, playsInline: true, className: "h-full w-full object-cover" }))) : content?.mobileImageUrl ? (React.createElement("div", { className: "w-full" },
24
+ React.createElement("img", { src: content.mobileImageUrl, alt: content.title || "Hero", className: "h-full w-full object-cover" }))) : content?.imageUrl ? (React.createElement("div", { className: "w-full" },
25
+ React.createElement("img", { src: content.imageUrl, alt: content.title || "Hero", className: "h-full w-full object-cover" }))) : null,
26
+ renderText({
27
+ fontSize: descriptionFontSize,
28
+ lineHeight: typography.description.lineHeight,
29
+ text: content?.description,
30
+ className: "px-4 drop-shadow-lg text-center mt-4",
31
+ color: safeTextColor,
32
+ font: typography.description.font,
33
+ }),
39
34
  content?.buttonLink && content?.buttonText && (React.createElement("a", { href: buttonHref, target: "_blank", rel: "noopener noreferrer", className: "no-underline" },
40
- React.createElement("div", { className: buttonClass, style: buttonStyle, ...(buttonAttributes ?? {}) }, content.buttonText)))));
35
+ React.createElement("div", { className: "mb-2 rounded-md px-6 py-2 text-lg font-semibold drop-shadow-lg transition-all duration-200 hover:opacity-90", style: {
36
+ color: colors.buttonText,
37
+ backgroundColor: colors.buttonBackground,
38
+ } }, content.buttonText)))));
41
39
  }
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useRef } from "react";
2
- import { buildClickUrl, fireImpressionWhenVisible, getThemeClasses, mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, } from "../../utils";
2
+ import { buildClickUrl, fireImpressionWhenVisible } from "../../utils";
3
+ import { resolveContentWidthClass, resolveHeight, resolveHeroColors, resolveHeroTypography, resolvePosition, } from "../../utils/hero-resolvers";
3
4
  import DesktopHero from "./DesktopHero";
4
5
  import MobileHero from "./MobileHero";
5
6
  export default function Hero({ content }) {
@@ -14,18 +15,14 @@ export default function Hero({ content }) {
14
15
  : undefined;
15
16
  if (!content)
16
17
  return null;
17
- const styling = content.styling;
18
- const themeClasses = getThemeClasses(styling);
19
- const rootClassName = mergeSlotClasses(`bg-black ${themeClasses}`, styling, "root");
20
- const rootStyle = mergeSlotStyles(undefined, styling, "root");
21
- const rootAttributes = mergeSlotAttributes(styling, "root");
22
- const desktopWrapperClass = mergeSlotClasses("hidden lg:block", styling, "desktopWrapper");
23
- const desktopWrapperAttributes = mergeSlotAttributes(styling, "desktopWrapper");
24
- const mobileWrapperClass = mergeSlotClasses("block lg:hidden", styling, "mobileWrapper");
25
- const mobileWrapperAttributes = mergeSlotAttributes(styling, "mobileWrapper");
26
- return (React.createElement("div", { className: rootClassName, ref: ref, style: rootStyle, ...(rootAttributes ?? {}) },
27
- React.createElement("div", { className: desktopWrapperClass, ...(desktopWrapperAttributes ?? {}) },
28
- React.createElement(DesktopHero, { content: content, buttonHref: signedButtonHref })),
29
- React.createElement("div", { className: mobileWrapperClass, ...(mobileWrapperAttributes ?? {}) },
30
- React.createElement(MobileHero, { content: content, buttonHref: signedButtonHref }))));
18
+ const colors = resolveHeroColors(content);
19
+ const contentWidthClass = resolveContentWidthClass(content);
20
+ const typography = resolveHeroTypography(content);
21
+ const position = resolvePosition(content);
22
+ const height = resolveHeight(content);
23
+ return (React.createElement("div", { className: "bg-black", ref: ref, style: { backgroundColor: colors.background } },
24
+ React.createElement("div", { className: "hidden lg:block" },
25
+ React.createElement(DesktopHero, { content: content, buttonHref: signedButtonHref, colors: colors, contentWidthClass: contentWidthClass, typography: typography, position: position, height: height })),
26
+ React.createElement("div", { className: "block lg:hidden" },
27
+ React.createElement(MobileHero, { content: content, buttonHref: signedButtonHref, colors: colors, typography: typography }))));
31
28
  }
@@ -0,0 +1,31 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&family=Roboto:wght@300;400;500;700&family=Open+Sans:wght@300;400;600;700&family=Lato:wght@300;400;700&family=Montserrat:wght@300;400;500;700&family=Inter:wght@300;400;500;700&family=Poppins:wght@300;400;600;700&family=Raleway:wght@300;400;500;700&family=Playfair+Display:wght@400;700&family=Merriweather:wght@300;400;700&family=Oswald:wght@300;400;500;700&display=swap");
2
+
3
+ /* Custom animations for fragment-headless-sdk */
4
+ @keyframes marquee {
5
+ 0% {
6
+ transform: translateX(0%);
7
+ }
8
+ 100% {
9
+ transform: translateX(-100%);
10
+ }
11
+ }
12
+
13
+ /* Utility classes for marquee animations */
14
+ .animate-marquee {
15
+ animation: marquee 25s linear infinite;
16
+ }
17
+
18
+ /* Make hero/announcement prose links inherit the surrounding text color (v2.1+) */
19
+ .prose {
20
+ --tw-prose-links: currentColor;
21
+ --tw-prose-links-hover: currentColor;
22
+ }
23
+
24
+ .prose :where(a):not(:where([class~="not-prose"] *)) {
25
+ color: currentColor;
26
+ }
27
+
28
+ .prose :where(a):not(:where([class~="not-prose"] *)):hover {
29
+ color: currentColor;
30
+ text-decoration: underline;
31
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isDarkColor(color: string | undefined | null): boolean;
2
+ export declare function ensureSafeColor(color: string | undefined | null, fallback: string): string;
@@ -0,0 +1,67 @@
1
+ const HEX_COLOR_REGEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
2
+ const RGB_COLOR_REGEX = /^rgba?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d*\.?\d+))?\s*\)$/i;
3
+ function hexToRgb(color) {
4
+ if (!HEX_COLOR_REGEX.test(color)) {
5
+ return null;
6
+ }
7
+ let hex = color.slice(1);
8
+ if (hex.length === 3) {
9
+ hex = hex
10
+ .split("")
11
+ .map((char) => char + char)
12
+ .join("");
13
+ }
14
+ const bigint = parseInt(hex, 16);
15
+ return {
16
+ r: (bigint >> 16) & 255,
17
+ g: (bigint >> 8) & 255,
18
+ b: bigint & 255,
19
+ };
20
+ }
21
+ function rgbStringToRgb(color) {
22
+ const match = color.match(RGB_COLOR_REGEX);
23
+ if (!match) {
24
+ return null;
25
+ }
26
+ const [, r, g, b] = match;
27
+ const red = Number(r);
28
+ const green = Number(g);
29
+ const blue = Number(b);
30
+ if ([red, green, blue].some((value) => Number.isNaN(value))) {
31
+ return null;
32
+ }
33
+ return { r: red, g: green, b: blue };
34
+ }
35
+ function normalizeColor(color) {
36
+ if (!color) {
37
+ return null;
38
+ }
39
+ const trimmed = color.trim();
40
+ return hexToRgb(trimmed) ?? rgbStringToRgb(trimmed);
41
+ }
42
+ function getRelativeLuminance(rgb) {
43
+ const transform = (value) => {
44
+ const channel = value / 255;
45
+ if (channel <= 0.03928) {
46
+ return channel / 12.92;
47
+ }
48
+ return Math.pow((channel + 0.055) / 1.055, 2.4);
49
+ };
50
+ const r = transform(rgb.r);
51
+ const g = transform(rgb.g);
52
+ const b = transform(rgb.b);
53
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
54
+ }
55
+ export function isDarkColor(color) {
56
+ const rgb = normalizeColor(color);
57
+ if (!rgb) {
58
+ return false;
59
+ }
60
+ return getRelativeLuminance(rgb) < 0.5;
61
+ }
62
+ export function ensureSafeColor(color, fallback) {
63
+ if (!color) {
64
+ return fallback;
65
+ }
66
+ return isDarkColor(color) ? fallback : color;
67
+ }
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import { IHeroContent } from "../types";
3
+ export interface HeroResolvedColors {
4
+ title: string;
5
+ text: string;
6
+ buttonBackground: string;
7
+ buttonText: string;
8
+ background: string;
9
+ }
10
+ export declare const DEFAULT_COLORS: HeroResolvedColors;
11
+ export declare const DEFAULT_CONTENT_WIDTH_CLASS = "w-2/5";
12
+ export declare const DEFAULT_HEIGHT_CLASS = "h-[400px]";
13
+ export declare const FONT_FAMILY_MAP: {
14
+ readonly geist: "\"Geist\", -apple-system, BlinkMacSystemFont, sans-serif";
15
+ readonly georgia: "\"Georgia\", \"Times New Roman\", serif";
16
+ readonly geistMono: "\"Geist Mono\", \"SFMono-Regular\", Menlo, monospace";
17
+ readonly roboto: "\"Roboto\", sans-serif";
18
+ readonly openSans: "\"Open Sans\", sans-serif";
19
+ readonly lato: "\"Lato\", sans-serif";
20
+ readonly montserrat: "\"Montserrat\", sans-serif";
21
+ readonly inter: "\"Inter\", sans-serif";
22
+ readonly poppins: "\"Poppins\", sans-serif";
23
+ readonly raleway: "\"Raleway\", sans-serif";
24
+ readonly playfair: "\"Playfair Display\", serif";
25
+ readonly merriweather: "\"Merriweather\", serif";
26
+ readonly oswald: "\"Oswald\", sans-serif";
27
+ };
28
+ export type FontKey = keyof typeof FONT_FAMILY_MAP;
29
+ export declare const DEFAULT_TYPOGRAPHY: {
30
+ titleFontSize: string;
31
+ titleLineHeight: string;
32
+ titleFont: FontKey;
33
+ descriptionFontSize: string;
34
+ descriptionLineHeight: string;
35
+ descriptionFont: FontKey;
36
+ };
37
+ export interface HeroTypographySettings {
38
+ title: {
39
+ fontSize: string;
40
+ lineHeight: string;
41
+ font: FontKey;
42
+ };
43
+ description: {
44
+ fontSize: string;
45
+ lineHeight: string;
46
+ font: FontKey;
47
+ };
48
+ }
49
+ export declare const joinClassNames: (...classes: Array<string | false | null | undefined>) => string;
50
+ export declare function resolveHeroColors(content: IHeroContent): HeroResolvedColors;
51
+ export declare function resolveContentWidthClass(content: IHeroContent): string;
52
+ export declare function resolvePosition(content: IHeroContent): "left" | "center" | "right";
53
+ export declare function resolveHeight(content: IHeroContent): string;
54
+ export declare function resolveHeroTypography(content: IHeroContent): HeroTypographySettings;
55
+ export declare function renderText({ fontSize, lineHeight, text, className, color, font, }: {
56
+ fontSize: string;
57
+ lineHeight: string;
58
+ text: string | null | undefined;
59
+ className?: string;
60
+ color?: string;
61
+ font: FontKey;
62
+ }): React.JSX.Element | null;
@@ -0,0 +1,107 @@
1
+ import React from "react";
2
+ export const DEFAULT_COLORS = {
3
+ title: "#ffffff",
4
+ text: "#ffffff",
5
+ buttonBackground: "#ffffff",
6
+ buttonText: "#000000",
7
+ background: "#000000",
8
+ };
9
+ export const DEFAULT_CONTENT_WIDTH_CLASS = "w-2/5";
10
+ export const DEFAULT_HEIGHT_CLASS = "h-[400px]";
11
+ export const FONT_FAMILY_MAP = {
12
+ geist: '"Geist", -apple-system, BlinkMacSystemFont, sans-serif',
13
+ georgia: '"Georgia", "Times New Roman", serif',
14
+ geistMono: '"Geist Mono", "SFMono-Regular", Menlo, monospace',
15
+ roboto: '"Roboto", sans-serif',
16
+ openSans: '"Open Sans", sans-serif',
17
+ lato: '"Lato", sans-serif',
18
+ montserrat: '"Montserrat", sans-serif',
19
+ inter: '"Inter", sans-serif',
20
+ poppins: '"Poppins", sans-serif',
21
+ raleway: '"Raleway", sans-serif',
22
+ playfair: '"Playfair Display", serif',
23
+ merriweather: '"Merriweather", serif',
24
+ oswald: '"Oswald", sans-serif',
25
+ };
26
+ export const DEFAULT_TYPOGRAPHY = {
27
+ titleFontSize: "text-5xl",
28
+ titleLineHeight: "leading-none",
29
+ titleFont: "roboto",
30
+ descriptionFontSize: "text-3xl",
31
+ descriptionLineHeight: "leading-relaxed",
32
+ descriptionFont: "roboto",
33
+ };
34
+ export const joinClassNames = (...classes) => classes.filter(Boolean).join(" ");
35
+ const fallbackColor = (value, fallback) => typeof value === "string" && value.trim().length > 0 ? value : fallback;
36
+ export function resolveHeroColors(content) {
37
+ const tokens = content?.styling?.tokens?.colors ??
38
+ {};
39
+ return {
40
+ title: fallbackColor(tokens.title, DEFAULT_COLORS.title),
41
+ text: fallbackColor(tokens.text, DEFAULT_COLORS.text),
42
+ buttonBackground: fallbackColor(tokens.button ?? tokens.buttonBackground, DEFAULT_COLORS.buttonBackground),
43
+ buttonText: fallbackColor(tokens.buttonText, DEFAULT_COLORS.buttonText),
44
+ background: fallbackColor(tokens.background, DEFAULT_COLORS.background),
45
+ };
46
+ }
47
+ export function resolveContentWidthClass(content) {
48
+ const layout = content?.styling?.tokens?.layout ?? {};
49
+ const rawClass = layout.contentWidthClass;
50
+ if (typeof rawClass === "string" && rawClass.trim().length > 0) {
51
+ return rawClass.trim();
52
+ }
53
+ return DEFAULT_CONTENT_WIDTH_CLASS;
54
+ }
55
+ export function resolvePosition(content) {
56
+ const layout = content?.styling?.tokens?.layout ?? {};
57
+ const rawPosition = layout.position;
58
+ if (rawPosition === "center" ||
59
+ rawPosition === "right" ||
60
+ rawPosition === "left") {
61
+ return rawPosition;
62
+ }
63
+ return "left";
64
+ }
65
+ export function resolveHeight(content) {
66
+ const layout = content?.styling?.tokens?.layout ?? {};
67
+ const rawHeight = layout.height;
68
+ if (typeof rawHeight === "string" && rawHeight.trim().length > 0) {
69
+ return rawHeight.trim();
70
+ }
71
+ return DEFAULT_HEIGHT_CLASS;
72
+ }
73
+ export function resolveHeroTypography(content) {
74
+ const typography = content?.styling?.tokens?.typography;
75
+ return {
76
+ title: {
77
+ fontSize: typography?.titleFontSize ?? DEFAULT_TYPOGRAPHY.titleFontSize,
78
+ lineHeight: typography?.titleLineHeight ?? DEFAULT_TYPOGRAPHY.titleLineHeight,
79
+ font: typography?.titleFont ?? DEFAULT_TYPOGRAPHY.titleFont,
80
+ },
81
+ description: {
82
+ fontSize: typography?.descriptionFontSize ??
83
+ DEFAULT_TYPOGRAPHY.descriptionFontSize,
84
+ lineHeight: typography?.descriptionLineHeight ??
85
+ DEFAULT_TYPOGRAPHY.descriptionLineHeight,
86
+ font: typography?.descriptionFont ??
87
+ DEFAULT_TYPOGRAPHY.descriptionFont,
88
+ },
89
+ };
90
+ }
91
+ export function renderText({ fontSize, lineHeight, text, className, color, font, }) {
92
+ if (!text || text.trim().length === 0) {
93
+ return null;
94
+ }
95
+ const baseClasses = "drop-shadow-lg";
96
+ const combinedClasses = className
97
+ ? `${baseClasses} ${fontSize} ${lineHeight} ${className}`
98
+ : `${baseClasses} ${fontSize} ${lineHeight}`;
99
+ return React.createElement("div", {
100
+ className: combinedClasses,
101
+ style: {
102
+ color,
103
+ fontFamily: FONT_FAMILY_MAP[font] ?? FONT_FAMILY_MAP.geist,
104
+ },
105
+ dangerouslySetInnerHTML: { __html: text },
106
+ });
107
+ }
@@ -1,4 +1,6 @@
1
1
  export * from "./cache";
2
+ export * from "./color";
2
3
  export * from "./fetch-resource";
4
+ export * from "./hero-resolvers";
3
5
  export * from "./metrics";
4
6
  export * from "./styling";
@@ -1,4 +1,6 @@
1
1
  export * from "./cache";
2
+ export * from "./color";
2
3
  export * from "./fetch-resource";
4
+ export * from "./hero-resolvers";
3
5
  export * from "./metrics";
4
6
  export * from "./styling";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fragment-headless-sdk",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  ".": {
9
9
  "import": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts"
11
- }
11
+ },
12
+ "./styles": "./dist/styles/fragment-sdk.css"
12
13
  },
13
14
  "files": [
14
15
  "dist"
package/readme.md CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  The official SDK for integrating with Fragment-Shopify CMS. Provides React components, TypeScript types, and utilities for rendering published sections in headless Shopify storefronts.
4
4
 
5
- ## ✨ What's New in v2.0.0
5
+ ## ✨ What's New in v2.1.0
6
+
7
+ 🎨 **Enhanced Hero Styling System** - New hero resolvers utility with advanced typography, positioning, and layout controls
8
+ 🔤 **Advanced Typography** - Built-in font family support with granular control over sizes and line heights
9
+ 📐 **Flexible Layout Controls** - Content positioning (left/center/right), width management, and height configuration
10
+ 🎯 **Developer Experience** - Enhanced TypeScript interfaces and utility functions for better development workflow
11
+
12
+ ### Previous Major Release (v2.0.0)
6
13
 
7
14
  🎨 **Revolutionary Styling System** - Complete overhaul with advanced theming, responsive design, and scalable architecture
8
15
  🏗️ **Component-Specific Types** - Enhanced type safety with dedicated slot interfaces
@@ -46,6 +53,15 @@ Fragment-Shopify App (CMS) → API Endpoint → fragment-headless-sdk (Consumer)
46
53
  - 🔄 **Legacy Compatibility**: Seamless migration path from v1.x styling
47
54
  - 🎪 **CSS-in-JS Support**: Integration with styled-components, emotion, etc.
48
55
 
56
+ ### Hero Resolvers System (v2.1+)
57
+
58
+ - 🔤 **Advanced Typography**: Built-in font family support (Roboto, Inter, Montserrat, etc.)
59
+ - 📐 **Layout Controls**: Content positioning (left/center/right), width, and height management
60
+ - 🎨 **Smart Color Resolution**: Intelligent fallback handling for color tokens
61
+ - 🛠️ **Utility Functions**: Helper functions for class joining, text rendering, and style resolution
62
+ - 📝 **Type Safety**: Enhanced TypeScript interfaces for all styling options
63
+ - 🔄 **Backward Compatible**: Works seamlessly with existing Hero implementations
64
+
49
65
  ---
50
66
 
51
67
  ## 📦 Installation
@@ -104,6 +120,30 @@ module.exports = {
104
120
  @tailwind utilities;
105
121
  ```
106
122
 
123
+ ### 4. Import Fragment SDK Styles
124
+
125
+ **Required** - Import the SDK's CSS file for proper typography and animations:
126
+
127
+ #### Option A: JavaScript/TypeScript Import
128
+
129
+ ```typescript
130
+ // In your main app file (e.g., _app.tsx, layout.tsx, or main.tsx)
131
+ import "fragment-headless-sdk/styles";
132
+ ```
133
+
134
+ #### Option B: CSS Import
135
+
136
+ ```css
137
+ /* In your global CSS file */
138
+ @import "fragment-headless-sdk/dist/styles/fragment-sdk.css";
139
+ ```
140
+
141
+ This provides:
142
+
143
+ - **Google Fonts** for all supported font families (Roboto, Inter, Montserrat, etc.)
144
+ - **Marquee animations** for announcement components
145
+ - **Improved link styling** for prose content
146
+
107
147
  ---
108
148
 
109
149
  ## 🚀 Quick Start
@@ -417,6 +457,172 @@ slots: {
417
457
 
418
458
  ---
419
459
 
460
+ ## 🎨 Hero Resolvers System (v2.1+)
461
+
462
+ The Hero Resolvers system provides advanced utilities for customizing Hero components with enhanced typography, layout, and styling controls.
463
+
464
+ ### Typography Control
465
+
466
+ ```typescript
467
+ import { resolveHeroTypography, FontKey } from "fragment-headless-sdk";
468
+
469
+ // Enhanced typography configuration
470
+ const heroContent = {
471
+ title: "Welcome to Our Store",
472
+ description: "Discover amazing products",
473
+ styling: {
474
+ tokens: {
475
+ typography: {
476
+ // Title typography
477
+ titleFont: "montserrat" as FontKey,
478
+ titleFontSize: "text-6xl",
479
+ titleLineHeight: "leading-tight",
480
+
481
+ // Description typography
482
+ descriptionFont: "inter" as FontKey,
483
+ descriptionFontSize: "text-xl",
484
+ descriptionLineHeight: "leading-relaxed",
485
+ },
486
+ },
487
+ },
488
+ };
489
+
490
+ // Resolve typography settings
491
+ const typography = resolveHeroTypography(heroContent);
492
+ ```
493
+
494
+ ### Layout & Positioning
495
+
496
+ ```typescript
497
+ import {
498
+ resolvePosition,
499
+ resolveContentWidthClass,
500
+ resolveHeight,
501
+ } from "fragment-headless-sdk";
502
+
503
+ const heroContent = {
504
+ title: "Centered Hero",
505
+ styling: {
506
+ tokens: {
507
+ layout: {
508
+ position: "center", // "left" | "center" | "right"
509
+ contentWidth: "max-w-4xl", // Tailwind width classes
510
+ height: "min-h-screen", // Custom height classes
511
+ },
512
+ },
513
+ },
514
+ };
515
+
516
+ // Resolve layout settings
517
+ const position = resolvePosition(heroContent); // "center"
518
+ const contentWidth = resolveContentWidthClass(heroContent); // "max-w-4xl"
519
+ const height = resolveHeight(heroContent); // "min-h-screen"
520
+ ```
521
+
522
+ ### Color Resolution
523
+
524
+ ```typescript
525
+ import { resolveHeroColors } from "fragment-headless-sdk";
526
+
527
+ const heroContent = {
528
+ title: "Styled Hero",
529
+ styling: {
530
+ tokens: {
531
+ colors: {
532
+ title: "#ffffff",
533
+ text: "#f0f0f0",
534
+ button: "#007bff",
535
+ buttonText: "#ffffff",
536
+ background: "#1a1a1a",
537
+ },
538
+ },
539
+ },
540
+ };
541
+
542
+ // Resolve colors with intelligent fallbacks
543
+ const colors = resolveHeroColors(heroContent);
544
+ // Returns: { title: "#ffffff", text: "#f0f0f0", buttonBackground: "#007bff", buttonText: "#ffffff", background: "#1a1a1a" }
545
+ ```
546
+
547
+ ### Built-in Font Families
548
+
549
+ The system includes support for popular web fonts:
550
+
551
+ ```typescript
552
+ type FontKey =
553
+ | "roboto"
554
+ | "open-sans"
555
+ | "lato"
556
+ | "montserrat"
557
+ | "poppins"
558
+ | "inter"
559
+ | "nunito-sans"
560
+ | "source-sans-pro";
561
+ ```
562
+
563
+ ### Utility Functions
564
+
565
+ ```typescript
566
+ import { joinClassNames, renderText } from "fragment-headless-sdk";
567
+
568
+ // Safe class name joining
569
+ const classes = joinClassNames("text-xl", "font-bold", null, "text-center");
570
+ // Returns: "text-xl font-bold text-center"
571
+
572
+ // Unified text rendering
573
+ const titleElement = renderText({
574
+ fontSize: "text-4xl",
575
+ lineHeight: "leading-tight",
576
+ text: "Hero Title",
577
+ className: "font-bold",
578
+ color: "#ffffff",
579
+ font: "montserrat",
580
+ });
581
+ ```
582
+
583
+ ### Complete Example
584
+
585
+ ```typescript
586
+ import { Hero } from "fragment-headless-sdk";
587
+
588
+ const advancedHeroContent = {
589
+ title: "Advanced Hero Section",
590
+ description: "With enhanced typography and layout controls",
591
+ buttonText: "Get Started",
592
+ buttonLink: "/signup",
593
+ imageUrl: "https://example.com/hero.jpg",
594
+
595
+ styling: {
596
+ tokens: {
597
+ colors: {
598
+ title: "#ffffff",
599
+ text: "#e2e8f0",
600
+ button: "#3b82f6",
601
+ buttonText: "#ffffff",
602
+ background: "#1e293b",
603
+ },
604
+ typography: {
605
+ titleFont: "montserrat",
606
+ titleFontSize: "text-5xl lg:text-7xl",
607
+ titleLineHeight: "leading-tight",
608
+ descriptionFont: "inter",
609
+ descriptionFontSize: "text-lg lg:text-xl",
610
+ descriptionLineHeight: "leading-relaxed",
611
+ },
612
+ layout: {
613
+ position: "center",
614
+ contentWidth: "max-w-4xl",
615
+ height: "min-h-screen",
616
+ },
617
+ },
618
+ },
619
+ };
620
+
621
+ <Hero content={advancedHeroContent} />;
622
+ ```
623
+
624
+ ---
625
+
420
626
  ## 📡 API Reference
421
627
 
422
628
  ### `fetchResource<T>(params)`
@@ -725,6 +931,54 @@ fragment-headless-sdk/
725
931
 
726
932
  ## 🔄 Migration Guide
727
933
 
934
+ ### From v2.0 to v2.1
935
+
936
+ **No Breaking Changes** - v2.1 is fully backward compatible!
937
+
938
+ All existing Hero components continue to work exactly as before. The new Hero Resolvers system is completely opt-in.
939
+
940
+ #### New Features Available (Optional)
941
+
942
+ ```typescript
943
+ // v2.0 style (continues to work)
944
+ const heroContent = {
945
+ title: "My Hero",
946
+ styling: {
947
+ tokens: {
948
+ colors: {
949
+ title: "#ffffff",
950
+ button: "#007bff",
951
+ },
952
+ },
953
+ },
954
+ };
955
+
956
+ // v2.1 enhanced style (new features)
957
+ const heroContent = {
958
+ title: "My Hero",
959
+ styling: {
960
+ tokens: {
961
+ colors: {
962
+ title: "#ffffff",
963
+ button: "#007bff",
964
+ },
965
+ // NEW: Typography controls
966
+ typography: {
967
+ titleFont: "montserrat",
968
+ titleFontSize: "text-6xl",
969
+ titleLineHeight: "leading-tight",
970
+ },
971
+ // NEW: Layout controls
972
+ layout: {
973
+ position: "center",
974
+ contentWidth: "max-w-4xl",
975
+ height: "min-h-screen",
976
+ },
977
+ },
978
+ },
979
+ };
980
+ ```
981
+
728
982
  ### From v1.x to v2.0
729
983
 
730
984
  **No Breaking Changes** - v2.0 is fully backward compatible!
@@ -810,12 +1064,13 @@ MIT License - See LICENSE file for details.
810
1064
 
811
1065
  ## 🎉 What's Next?
812
1066
 
813
- The v2.0 styling system provides a foundation for unlimited customization and scalability. Future enhancements may include:
1067
+ The v2.1 Hero Resolvers system and v2.0 styling foundation provide unlimited customization and scalability. Future enhancements may include:
814
1068
 
815
1069
  - Visual styling editor integration
816
1070
  - Animation and transition presets
817
- - Advanced layout system
1071
+ - Advanced layout system for all components
818
1072
  - Component composition utilities
819
1073
  - Design system generator
1074
+ - Enhanced Announcement component resolvers
820
1075
 
821
- **Ready to build beautiful, scalable headless experiences!** 🚀
1076
+ **Ready to build beautiful, scalable headless experiences with advanced typography and layout controls!** 🚀