@otl-core/next-footer 1.1.19 → 1.1.20

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.
@@ -0,0 +1,15 @@
1
+ import { BlockInstance } from '@otl-core/cms-types';
2
+ import React__default from 'react';
3
+
4
+ /**
5
+ * Block Renderer for Footer Components
6
+ * Renders blocks within the footer context
7
+ * This is a minimal implementation that supports the blocks commonly used in footers
8
+ */
9
+
10
+ interface BlockRendererProps {
11
+ block: BlockInstance;
12
+ }
13
+ declare const BlockRenderer: React__default.FC<BlockRendererProps>;
14
+
15
+ export { BlockRenderer };
@@ -0,0 +1,69 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import ReactMarkdown from "react-markdown";
3
+ const BlockRenderer = ({ block }) => {
4
+ const { type, config } = block;
5
+ if (type === "markdown") {
6
+ const content = config.content || "";
7
+ return /* @__PURE__ */ jsx("div", { className: "footer-block footer-markdown", children: /* @__PURE__ */ jsx(ReactMarkdown, { children: content }) });
8
+ }
9
+ if (type === "link") {
10
+ const linkConfig = config;
11
+ const target = linkConfig.external ? "_blank" : void 0;
12
+ const rel = linkConfig.external ? "noopener noreferrer" : void 0;
13
+ const variantClasses = {
14
+ default: "footer-link hover:underline transition-colors",
15
+ underline: "footer-link underline",
16
+ "no-underline": "footer-link no-underline",
17
+ "button-primary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90",
18
+ "button-secondary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ "button-outline": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground"
20
+ };
21
+ const className = variantClasses[linkConfig.variant || "default"];
22
+ return /* @__PURE__ */ jsx(
23
+ "a",
24
+ {
25
+ href: linkConfig.href,
26
+ target,
27
+ rel,
28
+ className,
29
+ "aria-label": linkConfig.ariaLabel || linkConfig.text,
30
+ children: linkConfig.text
31
+ }
32
+ );
33
+ }
34
+ if (type === "button") {
35
+ const buttonConfig = config;
36
+ const target = buttonConfig.newTab ? "_blank" : void 0;
37
+ const rel = buttonConfig.newTab ? "noopener noreferrer" : void 0;
38
+ return /* @__PURE__ */ jsx(
39
+ "a",
40
+ {
41
+ href: buttonConfig.url,
42
+ target,
43
+ rel,
44
+ className: "footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors",
45
+ children: buttonConfig.text
46
+ }
47
+ );
48
+ }
49
+ if (type === "image") {
50
+ const imageConfig = config;
51
+ return /* @__PURE__ */ jsx("div", { className: "footer-block footer-image", children: /* @__PURE__ */ jsx(
52
+ "img",
53
+ {
54
+ src: imageConfig.src,
55
+ alt: imageConfig.alt || "",
56
+ width: imageConfig.width,
57
+ height: imageConfig.height
58
+ }
59
+ ) });
60
+ }
61
+ if (type === "divider") {
62
+ return /* @__PURE__ */ jsx("hr", { className: "footer-block footer-divider" });
63
+ }
64
+ return null;
65
+ };
66
+ export {
67
+ BlockRenderer
68
+ };
69
+ //# sourceMappingURL=block-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/blocks/block-renderer.tsx"],"sourcesContent":["/**\n * Block Renderer for Footer Components\n * Renders blocks within the footer context\n * This is a minimal implementation that supports the blocks commonly used in footers\n */\n\nimport { BlockInstance } from \"@otl-core/cms-types\";\nimport React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\n\ninterface BlockRendererProps {\n block: BlockInstance;\n}\n\nexport const BlockRenderer: React.FC<BlockRendererProps> = ({ block }) => {\n const { type, config } = block;\n\n // Markdown block\n if (type === \"markdown\") {\n const content = (config as { content?: string }).content || \"\";\n return (\n <div className=\"footer-block footer-markdown\">\n <ReactMarkdown>{content}</ReactMarkdown>\n </div>\n );\n }\n\n // Link block\n if (type === \"link\") {\n const linkConfig = config as {\n text?: string;\n href?: string;\n external?: boolean;\n variant?: string;\n ariaLabel?: string;\n };\n\n const target = linkConfig.external ? \"_blank\" : undefined;\n const rel = linkConfig.external ? \"noopener noreferrer\" : undefined;\n\n const variantClasses = {\n default: \"footer-link hover:underline transition-colors\",\n underline: \"footer-link underline\",\n \"no-underline\": \"footer-link no-underline\",\n \"button-primary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90\",\n \"button-secondary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n \"button-outline\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground\",\n };\n\n const className =\n variantClasses[\n (linkConfig.variant as keyof typeof variantClasses) || \"default\"\n ];\n\n return (\n <a\n href={linkConfig.href}\n target={target}\n rel={rel}\n className={className}\n aria-label={linkConfig.ariaLabel || linkConfig.text}\n >\n {linkConfig.text}\n </a>\n );\n }\n\n // Button block (existing button rendering logic)\n if (type === \"button\") {\n const buttonConfig = config as {\n text?: string;\n url?: string;\n variant?: string;\n size?: string;\n newTab?: boolean;\n };\n\n const target = buttonConfig.newTab ? \"_blank\" : undefined;\n const rel = buttonConfig.newTab ? \"noopener noreferrer\" : undefined;\n\n return (\n <a\n href={buttonConfig.url}\n target={target}\n rel={rel}\n className=\"footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors\"\n >\n {buttonConfig.text}\n </a>\n );\n }\n\n // Image block\n if (type === \"image\") {\n const imageConfig = config as {\n src?: string;\n alt?: string;\n width?: number;\n height?: number;\n };\n\n return (\n <div className=\"footer-block footer-image\">\n <img\n src={imageConfig.src}\n alt={imageConfig.alt || \"\"}\n width={imageConfig.width}\n height={imageConfig.height}\n />\n </div>\n );\n }\n\n // Divider block\n if (type === \"divider\") {\n return <hr className=\"footer-block footer-divider\" />;\n }\n\n // Fallback for unknown block types\n return null;\n};\n"],"mappings":"AAsBQ;AAdR,OAAO,mBAAmB;AAMnB,MAAM,gBAA8C,CAAC,EAAE,MAAM,MAAM;AACxE,QAAM,EAAE,MAAM,OAAO,IAAI;AAGzB,MAAI,SAAS,YAAY;AACvB,UAAM,UAAW,OAAgC,WAAW;AAC5D,WACE,oBAAC,SAAI,WAAU,gCACb,8BAAC,iBAAe,mBAAQ,GAC1B;AAAA,EAEJ;AAGA,MAAI,SAAS,QAAQ;AACnB,UAAM,aAAa;AAQnB,UAAM,SAAS,WAAW,WAAW,WAAW;AAChD,UAAM,MAAM,WAAW,WAAW,wBAAwB;AAE1D,UAAM,iBAAiB;AAAA,MACrB,SAAS;AAAA,MACT,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,kBACE;AAAA,MACF,oBACE;AAAA,MACF,kBACE;AAAA,IACJ;AAEA,UAAM,YACJ,eACG,WAAW,WAA2C,SACzD;AAEF,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,WAAW;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAY,WAAW,aAAa,WAAW;AAAA,QAE9C,qBAAW;AAAA;AAAA,IACd;AAAA,EAEJ;AAGA,MAAI,SAAS,UAAU;AACrB,UAAM,eAAe;AAQrB,UAAM,SAAS,aAAa,SAAS,WAAW;AAChD,UAAM,MAAM,aAAa,SAAS,wBAAwB;AAE1D,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,aAAa;AAAA,QACnB;AAAA,QACA;AAAA,QACA,WAAU;AAAA,QAET,uBAAa;AAAA;AAAA,IAChB;AAAA,EAEJ;AAGA,MAAI,SAAS,SAAS;AACpB,UAAM,cAAc;AAOpB,WACE,oBAAC,SAAI,WAAU,6BACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,YAAY;AAAA,QACjB,KAAK,YAAY,OAAO;AAAA,QACxB,OAAO,YAAY;AAAA,QACnB,QAAQ,YAAY;AAAA;AAAA,IACtB,GACF;AAAA,EAEJ;AAGA,MAAI,SAAS,WAAW;AACtB,WAAO,oBAAC,QAAG,WAAU,+BAA8B;AAAA,EACrD;AAGA,SAAO;AACT;","names":[]}
@@ -0,0 +1,15 @@
1
+ import { FooterSection, FooterContentSection, FooterConfig, Site } from '@otl-core/cms-types';
2
+ import React__default from 'react';
3
+ import { BlockRegistry } from '@otl-core/block-registry';
4
+
5
+ interface FooterSectionProps {
6
+ section: FooterSection | FooterContentSection;
7
+ footer: FooterConfig;
8
+ site: Site;
9
+ resolvedColors: Record<string, string | undefined>;
10
+ level?: number;
11
+ blockRegistry?: BlockRegistry;
12
+ }
13
+ declare const FooterSectionComponent: React__default.FC<FooterSectionProps>;
14
+
15
+ export { FooterSectionComponent };
@@ -0,0 +1,87 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { resolveColorToCSS } from "@otl-core/style-utils";
3
+ import { isContentSection } from "../../lib/footer.utils";
4
+ import { BlockRenderer as LocalBlockRenderer } from "../blocks/block-renderer";
5
+ import { BlockRenderer as RegistryBlockRenderer } from "@otl-core/block-registry";
6
+ const FooterSectionComponent = ({
7
+ section,
8
+ footer,
9
+ site,
10
+ resolvedColors,
11
+ level = 0,
12
+ blockRegistry
13
+ }) => {
14
+ const sectionStyle = (() => {
15
+ const style = {
16
+ display: "flex",
17
+ flexDirection: section.type === "row" ? "row" : "column",
18
+ alignItems: section.style.align || "flex-start",
19
+ justifyContent: section.style.justify || "flex-start",
20
+ gap: section.style.gap || "0"
21
+ };
22
+ if (section.style.flex) {
23
+ style.flex = section.style.flex;
24
+ }
25
+ if (section.style.background) {
26
+ style.backgroundColor = resolveColorToCSS(section.style.background);
27
+ }
28
+ if (section.style.padding) {
29
+ const padding = typeof section.style.padding === "object" && "base" in section.style.padding ? section.style.padding.base : section.style.padding;
30
+ style.padding = padding;
31
+ }
32
+ if (section.style.border) {
33
+ const border = typeof section.style.border === "object" && "base" in section.style.border ? section.style.border.base : section.style.border;
34
+ if (border) {
35
+ style.border = `${border.width || "0"} ${border.style || "solid"} ${border.color ? resolveColorToCSS(border.color) : "transparent"}`;
36
+ if (border.radius) {
37
+ style.borderRadius = border.radius;
38
+ }
39
+ }
40
+ }
41
+ return style;
42
+ })();
43
+ if (isContentSection(section)) {
44
+ return /* @__PURE__ */ jsx(
45
+ "div",
46
+ {
47
+ className: "footer-section footer-content-section",
48
+ style: sectionStyle,
49
+ "data-section-id": section.id,
50
+ children: section?.blocks?.map(
51
+ (block) => blockRegistry ? /* @__PURE__ */ jsx(
52
+ RegistryBlockRenderer,
53
+ {
54
+ block,
55
+ blockRegistry
56
+ },
57
+ block.id
58
+ ) : /* @__PURE__ */ jsx(LocalBlockRenderer, { block }, block.id)
59
+ )
60
+ }
61
+ );
62
+ }
63
+ return /* @__PURE__ */ jsx(
64
+ "div",
65
+ {
66
+ className: "footer-section footer-container-section",
67
+ style: sectionStyle,
68
+ "data-section-id": section.id,
69
+ children: section?.sections?.sort((a, b) => a.order - b.order)?.map((childSection) => /* @__PURE__ */ jsx(
70
+ FooterSectionComponent,
71
+ {
72
+ section: childSection,
73
+ footer,
74
+ site,
75
+ resolvedColors,
76
+ level: level + 1,
77
+ blockRegistry
78
+ },
79
+ childSection.id
80
+ ))
81
+ }
82
+ );
83
+ };
84
+ export {
85
+ FooterSectionComponent
86
+ };
87
+ //# sourceMappingURL=footer-section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/footer/footer-section.tsx"],"sourcesContent":["import {\n Site,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n} from \"@otl-core/cms-types\";\nimport React from \"react\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\nimport { isContentSection } from \"../../lib/footer.utils\";\nimport { BlockRenderer as LocalBlockRenderer } from \"../blocks/block-renderer\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport { BlockRenderer as RegistryBlockRenderer } from \"@otl-core/block-registry\";\n\ninterface FooterSectionProps {\n section: FooterSection | FooterContentSection;\n footer: FooterConfig;\n site: Site;\n resolvedColors: Record<string, string | undefined>;\n level?: number;\n blockRegistry?: BlockRegistry;\n}\n\nexport const FooterSectionComponent: React.FC<FooterSectionProps> = ({\n section,\n footer,\n site,\n resolvedColors,\n level = 0,\n blockRegistry,\n}) => {\n const sectionStyle: React.CSSProperties = (() => {\n const style: React.CSSProperties = {\n display: \"flex\",\n flexDirection: section.type === \"row\" ? \"row\" : \"column\",\n alignItems: section.style.align || \"flex-start\",\n justifyContent: section.style.justify || \"flex-start\",\n gap: section.style.gap || \"0\",\n };\n\n if (section.style.flex) {\n style.flex = section.style.flex;\n }\n\n // Apply section-specific background\n if (section.style.background) {\n style.backgroundColor = resolveColorToCSS(section.style.background);\n }\n\n // Apply section-specific padding\n if (section.style.padding) {\n const padding =\n typeof section.style.padding === \"object\" &&\n \"base\" in section.style.padding\n ? section.style.padding.base\n : section.style.padding;\n style.padding = padding;\n }\n\n // Apply section-specific border\n if (section.style.border) {\n const border =\n typeof section.style.border === \"object\" &&\n \"base\" in section.style.border\n ? section.style.border.base\n : section.style.border;\n\n if (border) {\n style.border = `${border.width || \"0\"} ${border.style || \"solid\"} ${\n border.color ? resolveColorToCSS(border.color) : \"transparent\"\n }`;\n if (border.radius) {\n style.borderRadius = border.radius;\n }\n }\n }\n\n return style;\n })();\n\n // Content section - render blocks\n if (isContentSection(section)) {\n return (\n <div\n className=\"footer-section footer-content-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.blocks?.map((block) =>\n blockRegistry ? (\n <RegistryBlockRenderer\n key={block.id}\n block={block}\n blockRegistry={blockRegistry}\n />\n ) : (\n <LocalBlockRenderer key={block.id} block={block} />\n ),\n )}\n </div>\n );\n }\n\n // Container section - render nested sections recursively\n return (\n <div\n className=\"footer-section footer-container-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.sections\n ?.sort((a, b) => a.order - b.order)\n ?.map((childSection) => (\n <FooterSectionComponent\n key={childSection.id}\n section={childSection}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n level={level + 1}\n blockRegistry={blockRegistry}\n />\n ))}\n </div>\n );\n};\n"],"mappings":"AAyFY;AAlFZ,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,iBAAiB,0BAA0B;AAEpD,SAAS,iBAAiB,6BAA6B;AAWhD,MAAM,yBAAuD,CAAC;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,MAAM;AACJ,QAAM,gBAAqC,MAAM;AAC/C,UAAM,QAA6B;AAAA,MACjC,SAAS;AAAA,MACT,eAAe,QAAQ,SAAS,QAAQ,QAAQ;AAAA,MAChD,YAAY,QAAQ,MAAM,SAAS;AAAA,MACnC,gBAAgB,QAAQ,MAAM,WAAW;AAAA,MACzC,KAAK,QAAQ,MAAM,OAAO;AAAA,IAC5B;AAEA,QAAI,QAAQ,MAAM,MAAM;AACtB,YAAM,OAAO,QAAQ,MAAM;AAAA,IAC7B;AAGA,QAAI,QAAQ,MAAM,YAAY;AAC5B,YAAM,kBAAkB,kBAAkB,QAAQ,MAAM,UAAU;AAAA,IACpE;AAGA,QAAI,QAAQ,MAAM,SAAS;AACzB,YAAM,UACJ,OAAO,QAAQ,MAAM,YAAY,YACjC,UAAU,QAAQ,MAAM,UACpB,QAAQ,MAAM,QAAQ,OACtB,QAAQ,MAAM;AACpB,YAAM,UAAU;AAAA,IAClB;AAGA,QAAI,QAAQ,MAAM,QAAQ;AACxB,YAAM,SACJ,OAAO,QAAQ,MAAM,WAAW,YAChC,UAAU,QAAQ,MAAM,SACpB,QAAQ,MAAM,OAAO,OACrB,QAAQ,MAAM;AAEpB,UAAI,QAAQ;AACV,cAAM,SAAS,GAAG,OAAO,SAAS,GAAG,IAAI,OAAO,SAAS,OAAO,IAC9D,OAAO,QAAQ,kBAAkB,OAAO,KAAK,IAAI,aACnD;AACA,YAAI,OAAO,QAAQ;AACjB,gBAAM,eAAe,OAAO;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG;AAGH,MAAI,iBAAiB,OAAO,GAAG;AAC7B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,QACP,mBAAiB,QAAQ;AAAA,QAExB,mBAAS,QAAQ;AAAA,UAAI,CAAC,UACrB,gBACE;AAAA,YAAC;AAAA;AAAA,cAEC;AAAA,cACA;AAAA;AAAA,YAFK,MAAM;AAAA,UAGb,IAEA,oBAAC,sBAAkC,SAAV,MAAM,EAAkB;AAAA,QAErD;AAAA;AAAA,IACF;AAAA,EAEJ;AAGA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO;AAAA,MACP,mBAAiB,QAAQ;AAAA,MAExB,mBAAS,UACN,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,GAChC,IAAI,CAAC,iBACL;AAAA,QAAC;AAAA;AAAA,UAEC,SAAS;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO,QAAQ;AAAA,UACf;AAAA;AAAA,QANK,aAAa;AAAA,MAOpB,CACD;AAAA;AAAA,EACL;AAEJ;","names":[]}
@@ -0,0 +1,14 @@
1
+ import { FooterConfig, Site } from '@otl-core/cms-types';
2
+ import { BlockRegistry } from '@otl-core/block-registry';
3
+ import React__default from 'react';
4
+
5
+ interface FooterProps {
6
+ footer: FooterConfig;
7
+ site: Site;
8
+ className?: string;
9
+ id?: string;
10
+ blockRegistry?: BlockRegistry;
11
+ }
12
+ declare const Footer: React__default.FC<FooterProps>;
13
+
14
+ export { Footer, type FooterProps };
@@ -0,0 +1,54 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { generateFooterCSS, resolveFooterColors } from "../lib/footer.utils";
3
+ import { cn } from "@otl-core/style-utils";
4
+ import { FooterSectionComponent } from "./footer/footer-section";
5
+ const Footer = ({
6
+ footer,
7
+ site,
8
+ className = "",
9
+ id = "default",
10
+ blockRegistry
11
+ }) => {
12
+ const resolvedColors = resolveFooterColors(footer.style);
13
+ const styles = generateFooterCSS(id, footer, resolvedColors);
14
+ const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);
15
+ const containerBehavior = footer.style.container || "edged";
16
+ const sectionsContent = sortedSections.map((section) => {
17
+ const sectionElement = /* @__PURE__ */ jsx(
18
+ FooterSectionComponent,
19
+ {
20
+ section,
21
+ footer,
22
+ site,
23
+ resolvedColors,
24
+ blockRegistry
25
+ },
26
+ section.id
27
+ );
28
+ if (containerBehavior === "edged") {
29
+ return /* @__PURE__ */ jsx("div", { className: "container mx-auto", children: sectionElement }, section.id);
30
+ }
31
+ return sectionElement;
32
+ });
33
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
34
+ styles && /* @__PURE__ */ jsx("style", { children: styles }),
35
+ /* @__PURE__ */ jsx(
36
+ "footer",
37
+ {
38
+ className: cn(
39
+ {
40
+ "container mx-auto": containerBehavior === "boxed"
41
+ },
42
+ className
43
+ ),
44
+ "data-footer-id": id,
45
+ "data-container": containerBehavior,
46
+ children: /* @__PURE__ */ jsx("div", { className: `footer-${id} footer-container`, children: sectionsContent })
47
+ }
48
+ )
49
+ ] });
50
+ };
51
+ export {
52
+ Footer
53
+ };
54
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/footer.tsx"],"sourcesContent":["import { Site, FooterConfig } from \"@otl-core/cms-types\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport React from \"react\";\nimport { generateFooterCSS, resolveFooterColors } from \"../lib/footer.utils\";\nimport { cn } from \"@otl-core/style-utils\";\nimport { FooterSectionComponent } from \"./footer/footer-section\";\n\nexport interface FooterProps {\n footer: FooterConfig;\n site: Site;\n className?: string;\n id?: string;\n blockRegistry?: BlockRegistry;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n footer,\n site,\n className = \"\",\n id = \"default\",\n blockRegistry,\n}) => {\n const resolvedColors = resolveFooterColors(footer.style);\n const styles = generateFooterCSS(id, footer, resolvedColors);\n const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);\n const containerBehavior = footer.style.container || \"edged\";\n\n // Render sections with container wrapping per section for \"edged\" mode\n const sectionsContent = sortedSections.map((section) => {\n const sectionElement = (\n <FooterSectionComponent\n key={section.id}\n section={section}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n blockRegistry={blockRegistry}\n />\n );\n\n // In \"edged\" mode, wrap each top-level section in its own container\n if (containerBehavior === \"edged\") {\n return (\n <div key={section.id} className=\"container mx-auto\">\n {sectionElement}\n </div>\n );\n }\n\n return sectionElement;\n });\n\n return (\n <>\n {styles && <style>{styles}</style>}\n <footer\n className={cn(\n {\n \"container mx-auto\": containerBehavior === \"boxed\",\n },\n className,\n )}\n data-footer-id={id}\n data-container={containerBehavior}\n >\n <div className={`footer-${id} footer-container`}>{sectionsContent}</div>\n </footer>\n </>\n );\n};\n"],"mappings":"AA8BM,SAuBF,UAvBE,KAuBF,YAvBE;AA3BN,SAAS,mBAAmB,2BAA2B;AACvD,SAAS,UAAU;AACnB,SAAS,8BAA8B;AAUhC,MAAM,SAAgC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,KAAK;AAAA,EACL;AACF,MAAM;AACJ,QAAM,iBAAiB,oBAAoB,OAAO,KAAK;AACvD,QAAM,SAAS,kBAAkB,IAAI,QAAQ,cAAc;AAC3D,QAAM,iBAAiB,CAAC,GAAG,OAAO,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC5E,QAAM,oBAAoB,OAAO,MAAM,aAAa;AAGpD,QAAM,kBAAkB,eAAe,IAAI,CAAC,YAAY;AACtD,UAAM,iBACJ;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,MALK,QAAQ;AAAA,IAMf;AAIF,QAAI,sBAAsB,SAAS;AACjC,aACE,oBAAC,SAAqB,WAAU,qBAC7B,4BADO,QAAQ,EAElB;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT,CAAC;AAED,SACE,iCACG;AAAA,cAAU,oBAAC,WAAO,kBAAO;AAAA,IAC1B;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,YACE,qBAAqB,sBAAsB;AAAA,UAC7C;AAAA,UACA;AAAA,QACF;AAAA,QACA,kBAAgB;AAAA,QAChB,kBAAgB;AAAA,QAEhB,8BAAC,SAAI,WAAW,UAAU,EAAE,qBAAsB,2BAAgB;AAAA;AAAA,IACpE;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as class_variance_authority_types from 'class-variance-authority/types';
3
+ import * as React from 'react';
4
+ import { VariantProps } from 'class-variance-authority';
5
+
6
+ declare const buttonVariants: (props?: ({
7
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
8
+ size?: "sm" | "md" | "lg" | null | undefined;
9
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
10
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
11
+ asChild?: boolean;
12
+ }): react_jsx_runtime.JSX.Element;
13
+
14
+ export { Button, buttonVariants };
@@ -0,0 +1,50 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "@otl-core/style-utils";
5
+ const buttonVariants = cva(
6
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
11
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
12
+ outline: "border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-gray-200 dark:hover:bg-input/50",
13
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
14
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
15
+ link: "text-primary underline-offset-4 hover:underline"
16
+ },
17
+ size: {
18
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
19
+ md: "h-9 px-4 py-2 has-[>svg]:px-3",
20
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4"
21
+ }
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ size: "md"
26
+ }
27
+ }
28
+ );
29
+ function Button({
30
+ className,
31
+ variant,
32
+ size,
33
+ asChild = false,
34
+ ...props
35
+ }) {
36
+ const Comp = asChild ? Slot : "button";
37
+ return /* @__PURE__ */ jsx(
38
+ Comp,
39
+ {
40
+ "data-slot": "button",
41
+ className: cn(buttonVariants({ variant, size, className })),
42
+ ...props
43
+ }
44
+ );
45
+ }
46
+ export {
47
+ Button,
48
+ buttonVariants
49
+ };
50
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/button.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@otl-core/style-utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-gray-200 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n md: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n"],"mappings":"AAiDI;AAhDJ,SAAS,YAAY;AACrB,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAEnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SACE;AAAA,QACF,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OACE;AAAA,QACF,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAGK;AACH,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,MACzD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,14 +1,4 @@
1
- import { FooterConfig, Site } from '@otl-core/cms-types';
2
- import { BlockRegistry } from '@otl-core/block-registry';
3
- import React from 'react';
4
-
5
- interface FooterProps {
6
- footer: FooterConfig;
7
- site: Site;
8
- className?: string;
9
- id?: string;
10
- blockRegistry?: BlockRegistry;
11
- }
12
- declare const Footer: React.FC<FooterProps>;
13
-
14
- export { Footer };
1
+ export { Footer } from './components/footer.js';
2
+ import '@otl-core/cms-types';
3
+ import '@otl-core/block-registry';
4
+ import 'react';
package/dist/index.js CHANGED
@@ -1,314 +1,5 @@
1
- "use client";
2
- import { isResponsiveConfig } from '@otl-core/cms-utils';
3
- import { cn, resolveColorToCSS } from '@otl-core/style-utils';
4
- import { useMemo } from 'react';
5
- import ReactMarkdown from 'react-markdown';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { BlockRenderer as BlockRenderer$1 } from '@otl-core/block-registry';
8
-
9
- // src/lib/footer.utils.ts
10
- function resolveFooterColors(style) {
11
- const resolved = {
12
- background: resolveColorToCSS(style.background),
13
- text: resolveColorToCSS(style.text),
14
- linkColor: resolveColorToCSS(style.link.color),
15
- linkHoverColor: resolveColorToCSS(style.link.hoverColor)
16
- };
17
- return resolved;
18
- }
19
- function getResponsiveBase(value) {
20
- if (typeof value === "object" && value !== null && "base" in value) {
21
- return value.base;
22
- }
23
- return value;
24
- }
25
- function resolveBorder(border) {
26
- if (!border) return "";
27
- const base = getResponsiveBase(border);
28
- if (!base) return "";
29
- const width = base.width || "0";
30
- const style = base.style || "solid";
31
- const color = base.color ? resolveColorToCSS(base.color) : "transparent";
32
- return `${width} ${style} ${color}`;
33
- }
34
- function generateFooterCSS(id, footer, resolvedColors) {
35
- const cssBlocks = [];
36
- const containerBehavior = footer.style.container || "edged";
37
- const baseMargin = getResponsiveBase(footer.style.layout.margin);
38
- const basePadding = getResponsiveBase(footer.style.layout.padding);
39
- const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);
40
- const borderCSS = resolveBorder(footer.style.border);
41
- const baseStyles = `
42
- .footer-${id} {
43
- display: flex;
44
- flex-direction: column;
45
- ${resolvedColors.background ? `background-color: ${resolvedColors.background};` : ""}
46
- ${resolvedColors.text ? `color: ${resolvedColors.text};` : ""}
47
- ${baseMargin ? `margin: ${baseMargin};` : ""}
48
- ${basePadding ? `padding: ${basePadding};` : ""}
49
- ${borderCSS ? `border: ${borderCSS};` : ""}
50
- ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : ""}
51
- ${baseSectionGap ? `gap: ${baseSectionGap};` : ""}
52
- }
53
- `;
54
- cssBlocks.push(baseStyles);
55
- if (containerBehavior === "edged") {
56
- cssBlocks.push(`
57
- .footer-${id} > div.container {
58
- width: 100%;
59
- }
60
- `);
61
- }
62
- cssBlocks.push(`
63
- .footer-${id} .footer-section {
64
- width: 100%;
65
- }
66
- `);
67
- cssBlocks.push(`
68
- @media (max-width: 767px) {
69
- .footer-${id} .footer-container-section {
70
- flex-wrap: wrap;
71
- }
72
- .footer-${id} .footer-container-section > .footer-content-section {
73
- flex: 1 1 100%;
74
- width: 100%;
75
- }
76
- }
77
- `);
78
- cssBlocks.push(`
79
- @media (min-width: 768px) and (max-width: 1023px) {
80
- .footer-${id} .footer-container-section {
81
- flex-wrap: wrap;
82
- }
83
- .footer-${id} .footer-container-section > .footer-content-section {
84
- flex: 1 1 calc(50% - ${baseSectionGap || "0px"} / 2);
85
- min-width: 0;
86
- }
87
- }
88
- `);
89
- cssBlocks.push(`
90
- @media (min-width: 1024px) {
91
- .footer-${id} .footer-container-section {
92
- flex-wrap: nowrap;
93
- }
94
- .footer-${id} .footer-container-section > .footer-content-section {
95
- flex: 1 1 auto;
96
- min-width: 0;
97
- }
98
- }
99
- `);
100
- if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {
101
- cssBlocks.push(`
102
- .footer-${id} a {
103
- ${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : ""}
104
- text-decoration: none;
105
- transition: color 0.2s;
106
- }
107
- .footer-${id} a:hover {
108
- ${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : ""}
109
- }
110
- `);
111
- }
112
- return minifyCSS(cssBlocks.filter(Boolean).join(""));
113
- }
114
- function formatShadow(shadow) {
115
- const insetStr = shadow.inset ? "inset " : "";
116
- return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;
117
- }
118
- function minifyCSS(css) {
119
- return css.replace(/\s+/g, " ").replace(/\s*{\s*/g, "{").replace(/\s*}\s*/g, "}").replace(/\s*:\s*/g, ":").replace(/\s*;\s*/g, ";").replace(/;\s*}/g, "}").trim();
120
- }
121
- function isContentSection(section) {
122
- return "blocks" in section && Array.isArray(section.blocks);
123
- }
124
- var BlockRenderer = ({ block }) => {
125
- const { type, config } = block;
126
- if (type === "markdown") {
127
- const content = config.content || "";
128
- return /* @__PURE__ */ jsx("div", { className: "footer-block footer-markdown", children: /* @__PURE__ */ jsx(ReactMarkdown, { children: content }) });
129
- }
130
- if (type === "link") {
131
- const linkConfig = config;
132
- const target = linkConfig.external ? "_blank" : void 0;
133
- const rel = linkConfig.external ? "noopener noreferrer" : void 0;
134
- const variantClasses = {
135
- default: "footer-link hover:underline transition-colors",
136
- underline: "footer-link underline",
137
- "no-underline": "footer-link no-underline",
138
- "button-primary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90",
139
- "button-secondary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80",
140
- "button-outline": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground"
141
- };
142
- const className = variantClasses[linkConfig.variant || "default"];
143
- return /* @__PURE__ */ jsx(
144
- "a",
145
- {
146
- href: linkConfig.href,
147
- target,
148
- rel,
149
- className,
150
- "aria-label": linkConfig.ariaLabel || linkConfig.text,
151
- children: linkConfig.text
152
- }
153
- );
154
- }
155
- if (type === "button") {
156
- const buttonConfig = config;
157
- const target = buttonConfig.newTab ? "_blank" : void 0;
158
- const rel = buttonConfig.newTab ? "noopener noreferrer" : void 0;
159
- return /* @__PURE__ */ jsx(
160
- "a",
161
- {
162
- href: buttonConfig.url,
163
- target,
164
- rel,
165
- className: "footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors",
166
- children: buttonConfig.text
167
- }
168
- );
169
- }
170
- if (type === "image") {
171
- const imageConfig = config;
172
- return /* @__PURE__ */ jsx("div", { className: "footer-block footer-image", children: /* @__PURE__ */ jsx(
173
- "img",
174
- {
175
- src: imageConfig.src,
176
- alt: imageConfig.alt || "",
177
- width: imageConfig.width,
178
- height: imageConfig.height
179
- }
180
- ) });
181
- }
182
- if (type === "divider") {
183
- return /* @__PURE__ */ jsx("hr", { className: "footer-block footer-divider" });
184
- }
185
- return null;
1
+ import { Footer } from "./components/footer";
2
+ export {
3
+ Footer
186
4
  };
187
- var FooterSectionComponent = ({
188
- section,
189
- footer,
190
- site,
191
- resolvedColors,
192
- level = 0,
193
- blockRegistry
194
- }) => {
195
- const sectionStyle = useMemo(() => {
196
- const style = {
197
- display: "flex",
198
- flexDirection: section.type === "row" ? "row" : "column",
199
- alignItems: section.style.align || "flex-start",
200
- justifyContent: section.style.justify || "flex-start",
201
- gap: section.style.gap || "0"
202
- };
203
- if (section.style.flex) {
204
- style.flex = section.style.flex;
205
- }
206
- if (section.style.background) {
207
- style.backgroundColor = resolveColorToCSS(section.style.background);
208
- }
209
- if (section.style.padding) {
210
- const padding = typeof section.style.padding === "object" && "base" in section.style.padding ? section.style.padding.base : section.style.padding;
211
- style.padding = padding;
212
- }
213
- if (section.style.border) {
214
- const border = typeof section.style.border === "object" && "base" in section.style.border ? section.style.border.base : section.style.border;
215
- if (border) {
216
- style.border = `${border.width || "0"} ${border.style || "solid"} ${border.color ? resolveColorToCSS(border.color) : "transparent"}`;
217
- if (border.radius) {
218
- style.borderRadius = border.radius;
219
- }
220
- }
221
- }
222
- return style;
223
- }, [section]);
224
- if (isContentSection(section)) {
225
- return /* @__PURE__ */ jsx(
226
- "div",
227
- {
228
- className: "footer-section footer-content-section",
229
- style: sectionStyle,
230
- "data-section-id": section.id,
231
- children: section?.blocks?.map(
232
- (block) => blockRegistry ? /* @__PURE__ */ jsx(
233
- BlockRenderer$1,
234
- {
235
- block,
236
- blockRegistry
237
- },
238
- block.id
239
- ) : /* @__PURE__ */ jsx(BlockRenderer, { block }, block.id)
240
- )
241
- }
242
- );
243
- }
244
- return /* @__PURE__ */ jsx(
245
- "div",
246
- {
247
- className: "footer-section footer-container-section",
248
- style: sectionStyle,
249
- "data-section-id": section.id,
250
- children: section?.sections?.sort((a, b) => a.order - b.order)?.map((childSection) => /* @__PURE__ */ jsx(
251
- FooterSectionComponent,
252
- {
253
- section: childSection,
254
- footer,
255
- site,
256
- resolvedColors,
257
- level: level + 1,
258
- blockRegistry
259
- },
260
- childSection.id
261
- ))
262
- }
263
- );
264
- };
265
- var Footer = ({
266
- footer,
267
- site,
268
- className = "",
269
- id = "default",
270
- blockRegistry
271
- }) => {
272
- const resolvedColors = resolveFooterColors(footer.style);
273
- const styles = generateFooterCSS(id, footer, resolvedColors);
274
- const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);
275
- const containerBehavior = footer.style.container || "edged";
276
- const sectionsContent = sortedSections.map((section) => {
277
- const sectionElement = /* @__PURE__ */ jsx(
278
- FooterSectionComponent,
279
- {
280
- section,
281
- footer,
282
- site,
283
- resolvedColors,
284
- blockRegistry
285
- },
286
- section.id
287
- );
288
- if (containerBehavior === "edged") {
289
- return /* @__PURE__ */ jsx("div", { className: "container mx-auto", children: sectionElement }, section.id);
290
- }
291
- return sectionElement;
292
- });
293
- return /* @__PURE__ */ jsxs(Fragment, { children: [
294
- styles && /* @__PURE__ */ jsx("style", { children: styles }),
295
- /* @__PURE__ */ jsx(
296
- "footer",
297
- {
298
- className: cn(
299
- {
300
- "container mx-auto": containerBehavior === "boxed"
301
- },
302
- className
303
- ),
304
- "data-footer-id": id,
305
- "data-container": containerBehavior,
306
- children: /* @__PURE__ */ jsx("div", { className: `footer-${id} footer-container`, children: sectionsContent })
307
- }
308
- )
309
- ] });
310
- };
311
-
312
- export { Footer };
313
- //# sourceMappingURL=index.js.map
314
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/footer.utils.ts","../src/components/blocks/block-renderer.tsx","../src/components/footer/footer-section.tsx","../src/components/footer.tsx"],"names":["resolveColorToCSS","jsx","RegistryBlockRenderer"],"mappings":"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","file":"index.js","sourcesContent":["import {\n BorderConfig,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n ResponsiveValue,\n ShadowConfig,\n} from \"@otl-core/cms-types\";\nimport { isResponsiveConfig } from \"@otl-core/cms-utils\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\n\n/**\n * Resolve all colors in the footer configuration to CSS values\n */\nexport function resolveFooterColors(\n style: FooterConfig[\"style\"],\n): Record<string, string | undefined> {\n const resolved: Record<string, string | undefined> = {\n background: resolveColorToCSS(style.background),\n text: resolveColorToCSS(style.text),\n linkColor: resolveColorToCSS(style.link.color),\n linkHoverColor: resolveColorToCSS(style.link.hoverColor),\n };\n\n return resolved;\n}\n\n/**\n * Resolve a responsive value to get the base value\n */\nfunction getResponsiveBase<T>(value: ResponsiveValue<T> | T): T {\n if (typeof value === \"object\" && value !== null && \"base\" in value) {\n return value.base;\n }\n return value as T;\n}\n\n/**\n * Resolve border config to CSS string\n */\nfunction resolveBorder(\n border: ResponsiveValue<BorderConfig> | undefined,\n): string {\n if (!border) return \"\";\n\n const base = getResponsiveBase(border);\n if (!base) return \"\";\n\n const width = base.width || \"0\";\n const style = base.style || \"solid\";\n const color = base.color ? resolveColorToCSS(base.color) : \"transparent\";\n\n return `${width} ${style} ${color}`;\n}\n\n/**\n * Generate CSS for the footer configuration\n */\nexport function generateFooterCSS(\n id: string,\n footer: FooterConfig,\n resolvedColors: Record<string, string | undefined>,\n): string {\n const cssBlocks: string[] = [];\n const containerBehavior = footer.style.container || \"edged\";\n\n // Base footer styles\n const baseMargin = getResponsiveBase(footer.style.layout.margin);\n const basePadding = getResponsiveBase(footer.style.layout.padding);\n const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);\n const borderCSS = resolveBorder(footer.style.border);\n\n const baseStyles = `\n .footer-${id} {\n display: flex;\n flex-direction: column;\n ${resolvedColors.background ? `background-color: ${resolvedColors.background};` : \"\"}\n ${resolvedColors.text ? `color: ${resolvedColors.text};` : \"\"}\n ${baseMargin ? `margin: ${baseMargin};` : \"\"}\n ${basePadding ? `padding: ${basePadding};` : \"\"}\n ${borderCSS ? `border: ${borderCSS};` : \"\"}\n ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : \"\"}\n ${baseSectionGap ? `gap: ${baseSectionGap};` : \"\"}\n }\n `;\n cssBlocks.push(baseStyles);\n\n // Container-specific styles\n if (containerBehavior === \"edged\") {\n // In edged mode, each container wrapper should be full width\n cssBlocks.push(`\n .footer-${id} > div.container {\n width: 100%;\n }\n `);\n }\n\n // Top-level sections (rows) should always be full width\n cssBlocks.push(`\n .footer-${id} .footer-section {\n width: 100%;\n }\n `);\n\n // Responsive layout for nested sections (columns within rows)\n // Mobile: stack all columns vertically\n cssBlocks.push(`\n @media (max-width: 767px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 100%;\n width: 100%;\n }\n }\n `);\n\n // Tablet: allow 2 columns per row\n cssBlocks.push(`\n @media (min-width: 768px) and (max-width: 1023px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 calc(50% - ${baseSectionGap || \"0px\"} / 2);\n min-width: 0;\n }\n }\n `);\n\n // Desktop: columns naturally fit based on their flex properties\n cssBlocks.push(`\n @media (min-width: 1024px) {\n .footer-${id} .footer-container-section {\n flex-wrap: nowrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 auto;\n min-width: 0;\n }\n }\n `);\n\n // Link styles\n if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {\n cssBlocks.push(`\n .footer-${id} a {\n ${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : \"\"}\n text-decoration: none;\n transition: color 0.2s;\n }\n .footer-${id} a:hover {\n ${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : \"\"}\n }\n `);\n }\n\n return minifyCSS(cssBlocks.filter(Boolean).join(\"\"));\n}\n\n/**\n * Format shadow config to CSS box-shadow value\n */\nfunction formatShadow(shadow: ShadowConfig): string {\n const insetStr = shadow.inset ? \"inset \" : \"\";\n return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;\n}\n\n/**\n * Minify CSS by removing extra whitespace\n */\nfunction minifyCSS(css: string): string {\n return css\n .replace(/\\s+/g, \" \")\n .replace(/\\s*{\\s*/g, \"{\")\n .replace(/\\s*}\\s*/g, \"}\")\n .replace(/\\s*:\\s*/g, \":\")\n .replace(/\\s*;\\s*/g, \";\")\n .replace(/;\\s*}/g, \"}\")\n .trim();\n}\n\n/**\n * Check if a section has nested sections (is a container section)\n */\nexport function isContainerSection(\n section: FooterSection | FooterContentSection,\n): section is FooterSection {\n return \"sections\" in section && Array.isArray(section.sections);\n}\n\n/**\n * Check if a section is a content section (has items)\n */\nexport function isContentSection(\n section: FooterSection | FooterContentSection,\n): section is FooterContentSection {\n return \"blocks\" in section && Array.isArray(section.blocks);\n}\n","/**\n * Block Renderer for Footer Components\n * Renders blocks within the footer context\n * This is a minimal implementation that supports the blocks commonly used in footers\n */\n\nimport { BlockInstance } from \"@otl-core/cms-types\";\nimport React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\n\ninterface BlockRendererProps {\n block: BlockInstance;\n}\n\nexport const BlockRenderer: React.FC<BlockRendererProps> = ({ block }) => {\n const { type, config } = block;\n\n // Markdown block\n if (type === \"markdown\") {\n const content = (config as { content?: string }).content || \"\";\n return (\n <div className=\"footer-block footer-markdown\">\n <ReactMarkdown>{content}</ReactMarkdown>\n </div>\n );\n }\n\n // Link block\n if (type === \"link\") {\n const linkConfig = config as {\n text?: string;\n href?: string;\n external?: boolean;\n variant?: string;\n ariaLabel?: string;\n };\n\n const target = linkConfig.external ? \"_blank\" : undefined;\n const rel = linkConfig.external ? \"noopener noreferrer\" : undefined;\n\n const variantClasses = {\n default: \"footer-link hover:underline transition-colors\",\n underline: \"footer-link underline\",\n \"no-underline\": \"footer-link no-underline\",\n \"button-primary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90\",\n \"button-secondary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n \"button-outline\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground\",\n };\n\n const className =\n variantClasses[\n (linkConfig.variant as keyof typeof variantClasses) || \"default\"\n ];\n\n return (\n <a\n href={linkConfig.href}\n target={target}\n rel={rel}\n className={className}\n aria-label={linkConfig.ariaLabel || linkConfig.text}\n >\n {linkConfig.text}\n </a>\n );\n }\n\n // Button block (existing button rendering logic)\n if (type === \"button\") {\n const buttonConfig = config as {\n text?: string;\n url?: string;\n variant?: string;\n size?: string;\n newTab?: boolean;\n };\n\n const target = buttonConfig.newTab ? \"_blank\" : undefined;\n const rel = buttonConfig.newTab ? \"noopener noreferrer\" : undefined;\n\n return (\n <a\n href={buttonConfig.url}\n target={target}\n rel={rel}\n className=\"footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors\"\n >\n {buttonConfig.text}\n </a>\n );\n }\n\n // Image block\n if (type === \"image\") {\n const imageConfig = config as {\n src?: string;\n alt?: string;\n width?: number;\n height?: number;\n };\n\n return (\n <div className=\"footer-block footer-image\">\n <img\n src={imageConfig.src}\n alt={imageConfig.alt || \"\"}\n width={imageConfig.width}\n height={imageConfig.height}\n />\n </div>\n );\n }\n\n // Divider block\n if (type === \"divider\") {\n return <hr className=\"footer-block footer-divider\" />;\n }\n\n // Fallback for unknown block types\n return null;\n};\n","import {\n Site,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n} from \"@otl-core/cms-types\";\nimport React, { useMemo } from \"react\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\nimport { isContentSection } from \"../../lib/footer.utils\";\nimport { BlockRenderer as LocalBlockRenderer } from \"../blocks/block-renderer\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport { BlockRenderer as RegistryBlockRenderer } from \"@otl-core/block-registry\";\n\ninterface FooterSectionProps {\n section: FooterSection | FooterContentSection;\n footer: FooterConfig;\n site: Site;\n resolvedColors: Record<string, string | undefined>;\n level?: number;\n blockRegistry?: BlockRegistry;\n}\n\nexport const FooterSectionComponent: React.FC<FooterSectionProps> = ({\n section,\n footer,\n site,\n resolvedColors,\n level = 0,\n blockRegistry,\n}) => {\n const sectionStyle: React.CSSProperties = useMemo(() => {\n const style: React.CSSProperties = {\n display: \"flex\",\n flexDirection: section.type === \"row\" ? \"row\" : \"column\",\n alignItems: section.style.align || \"flex-start\",\n justifyContent: section.style.justify || \"flex-start\",\n gap: section.style.gap || \"0\",\n };\n\n if (section.style.flex) {\n style.flex = section.style.flex;\n }\n\n // Apply section-specific background\n if (section.style.background) {\n style.backgroundColor = resolveColorToCSS(section.style.background);\n }\n\n // Apply section-specific padding\n if (section.style.padding) {\n const padding =\n typeof section.style.padding === \"object\" &&\n \"base\" in section.style.padding\n ? section.style.padding.base\n : section.style.padding;\n style.padding = padding;\n }\n\n // Apply section-specific border\n if (section.style.border) {\n const border =\n typeof section.style.border === \"object\" &&\n \"base\" in section.style.border\n ? section.style.border.base\n : section.style.border;\n\n if (border) {\n style.border = `${border.width || \"0\"} ${border.style || \"solid\"} ${\n border.color ? resolveColorToCSS(border.color) : \"transparent\"\n }`;\n if (border.radius) {\n style.borderRadius = border.radius;\n }\n }\n }\n\n return style;\n }, [section]);\n\n // Content section - render blocks\n if (isContentSection(section)) {\n return (\n <div\n className=\"footer-section footer-content-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.blocks?.map((block) =>\n blockRegistry ? (\n <RegistryBlockRenderer\n key={block.id}\n block={block}\n blockRegistry={blockRegistry}\n />\n ) : (\n <LocalBlockRenderer key={block.id} block={block} />\n ),\n )}\n </div>\n );\n }\n\n // Container section - render nested sections recursively\n return (\n <div\n className=\"footer-section footer-container-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.sections\n ?.sort((a, b) => a.order - b.order)\n ?.map((childSection) => (\n <FooterSectionComponent\n key={childSection.id}\n section={childSection}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n level={level + 1}\n blockRegistry={blockRegistry}\n />\n ))}\n </div>\n );\n};\n","import { Site, FooterConfig } from \"@otl-core/cms-types\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport React from \"react\";\nimport { generateFooterCSS, resolveFooterColors } from \"../lib/footer.utils\";\nimport { cn } from \"@otl-core/style-utils\";\nimport { FooterSectionComponent } from \"./footer/footer-section\";\n\nexport interface FooterProps {\n footer: FooterConfig;\n site: Site;\n className?: string;\n id?: string;\n blockRegistry?: BlockRegistry;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n footer,\n site,\n className = \"\",\n id = \"default\",\n blockRegistry,\n}) => {\n const resolvedColors = resolveFooterColors(footer.style);\n const styles = generateFooterCSS(id, footer, resolvedColors);\n const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);\n const containerBehavior = footer.style.container || \"edged\";\n\n // Render sections with container wrapping per section for \"edged\" mode\n const sectionsContent = sortedSections.map((section) => {\n const sectionElement = (\n <FooterSectionComponent\n key={section.id}\n section={section}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n blockRegistry={blockRegistry}\n />\n );\n\n // In \"edged\" mode, wrap each top-level section in its own container\n if (containerBehavior === \"edged\") {\n return (\n <div key={section.id} className=\"container mx-auto\">\n {sectionElement}\n </div>\n );\n }\n\n return sectionElement;\n });\n\n return (\n <>\n {styles && <style>{styles}</style>}\n <footer\n className={cn(\n {\n \"container mx-auto\": containerBehavior === \"boxed\",\n },\n className,\n )}\n data-footer-id={id}\n data-container={containerBehavior}\n >\n <div className={`footer-${id} footer-container`}>{sectionsContent}</div>\n </footer>\n </>\n );\n};\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Footer } from \"./components/footer\";\n"],"mappings":"AAAA,SAAS,cAAc;","names":[]}
@@ -0,0 +1,20 @@
1
+ import { FooterConfig, FooterSection, FooterContentSection } from '@otl-core/cms-types';
2
+
3
+ /**
4
+ * Resolve all colors in the footer configuration to CSS values
5
+ */
6
+ declare function resolveFooterColors(style: FooterConfig["style"]): Record<string, string | undefined>;
7
+ /**
8
+ * Generate CSS for the footer configuration
9
+ */
10
+ declare function generateFooterCSS(id: string, footer: FooterConfig, resolvedColors: Record<string, string | undefined>): string;
11
+ /**
12
+ * Check if a section has nested sections (is a container section)
13
+ */
14
+ declare function isContainerSection(section: FooterSection | FooterContentSection): section is FooterSection;
15
+ /**
16
+ * Check if a section is a content section (has items)
17
+ */
18
+ declare function isContentSection(section: FooterSection | FooterContentSection): section is FooterContentSection;
19
+
20
+ export { generateFooterCSS, isContainerSection, isContentSection, resolveFooterColors };
@@ -0,0 +1,126 @@
1
+ import { isResponsiveConfig } from "@otl-core/cms-utils";
2
+ import { resolveColorToCSS } from "@otl-core/style-utils";
3
+ function resolveFooterColors(style) {
4
+ const resolved = {
5
+ background: resolveColorToCSS(style.background),
6
+ text: resolveColorToCSS(style.text),
7
+ linkColor: resolveColorToCSS(style.link.color),
8
+ linkHoverColor: resolveColorToCSS(style.link.hoverColor)
9
+ };
10
+ return resolved;
11
+ }
12
+ function getResponsiveBase(value) {
13
+ if (typeof value === "object" && value !== null && "base" in value) {
14
+ return value.base;
15
+ }
16
+ return value;
17
+ }
18
+ function resolveBorder(border) {
19
+ if (!border) return "";
20
+ const base = getResponsiveBase(border);
21
+ if (!base) return "";
22
+ const width = base.width || "0";
23
+ const style = base.style || "solid";
24
+ const color = base.color ? resolveColorToCSS(base.color) : "transparent";
25
+ return `${width} ${style} ${color}`;
26
+ }
27
+ function generateFooterCSS(id, footer, resolvedColors) {
28
+ const cssBlocks = [];
29
+ const containerBehavior = footer.style.container || "edged";
30
+ const baseMargin = getResponsiveBase(footer.style.layout.margin);
31
+ const basePadding = getResponsiveBase(footer.style.layout.padding);
32
+ const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);
33
+ const borderCSS = resolveBorder(footer.style.border);
34
+ const baseStyles = `
35
+ .footer-${id} {
36
+ display: flex;
37
+ flex-direction: column;
38
+ ${resolvedColors.background ? `background-color: ${resolvedColors.background};` : ""}
39
+ ${resolvedColors.text ? `color: ${resolvedColors.text};` : ""}
40
+ ${baseMargin ? `margin: ${baseMargin};` : ""}
41
+ ${basePadding ? `padding: ${basePadding};` : ""}
42
+ ${borderCSS ? `border: ${borderCSS};` : ""}
43
+ ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : ""}
44
+ ${baseSectionGap ? `gap: ${baseSectionGap};` : ""}
45
+ }
46
+ `;
47
+ cssBlocks.push(baseStyles);
48
+ if (containerBehavior === "edged") {
49
+ cssBlocks.push(`
50
+ .footer-${id} > div.container {
51
+ width: 100%;
52
+ }
53
+ `);
54
+ }
55
+ cssBlocks.push(`
56
+ .footer-${id} .footer-section {
57
+ width: 100%;
58
+ }
59
+ `);
60
+ cssBlocks.push(`
61
+ @media (max-width: 767px) {
62
+ .footer-${id} .footer-container-section {
63
+ flex-wrap: wrap;
64
+ }
65
+ .footer-${id} .footer-container-section > .footer-content-section {
66
+ flex: 1 1 100%;
67
+ width: 100%;
68
+ }
69
+ }
70
+ `);
71
+ cssBlocks.push(`
72
+ @media (min-width: 768px) and (max-width: 1023px) {
73
+ .footer-${id} .footer-container-section {
74
+ flex-wrap: wrap;
75
+ }
76
+ .footer-${id} .footer-container-section > .footer-content-section {
77
+ flex: 1 1 calc(50% - ${baseSectionGap || "0px"} / 2);
78
+ min-width: 0;
79
+ }
80
+ }
81
+ `);
82
+ cssBlocks.push(`
83
+ @media (min-width: 1024px) {
84
+ .footer-${id} .footer-container-section {
85
+ flex-wrap: nowrap;
86
+ }
87
+ .footer-${id} .footer-container-section > .footer-content-section {
88
+ flex: 1 1 auto;
89
+ min-width: 0;
90
+ }
91
+ }
92
+ `);
93
+ if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {
94
+ cssBlocks.push(`
95
+ .footer-${id} a {
96
+ ${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : ""}
97
+ text-decoration: none;
98
+ transition: color 0.2s;
99
+ }
100
+ .footer-${id} a:hover {
101
+ ${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : ""}
102
+ }
103
+ `);
104
+ }
105
+ return minifyCSS(cssBlocks.filter(Boolean).join(""));
106
+ }
107
+ function formatShadow(shadow) {
108
+ const insetStr = shadow.inset ? "inset " : "";
109
+ return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;
110
+ }
111
+ function minifyCSS(css) {
112
+ return css.replace(/\s+/g, " ").replace(/\s*{\s*/g, "{").replace(/\s*}\s*/g, "}").replace(/\s*:\s*/g, ":").replace(/\s*;\s*/g, ";").replace(/;\s*}/g, "}").trim();
113
+ }
114
+ function isContainerSection(section) {
115
+ return "sections" in section && Array.isArray(section.sections);
116
+ }
117
+ function isContentSection(section) {
118
+ return "blocks" in section && Array.isArray(section.blocks);
119
+ }
120
+ export {
121
+ generateFooterCSS,
122
+ isContainerSection,
123
+ isContentSection,
124
+ resolveFooterColors
125
+ };
126
+ //# sourceMappingURL=footer.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/footer.utils.ts"],"sourcesContent":["import {\n BorderConfig,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n ResponsiveValue,\n ShadowConfig,\n} from \"@otl-core/cms-types\";\nimport { isResponsiveConfig } from \"@otl-core/cms-utils\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\n\n/**\n * Resolve all colors in the footer configuration to CSS values\n */\nexport function resolveFooterColors(\n style: FooterConfig[\"style\"],\n): Record<string, string | undefined> {\n const resolved: Record<string, string | undefined> = {\n background: resolveColorToCSS(style.background),\n text: resolveColorToCSS(style.text),\n linkColor: resolveColorToCSS(style.link.color),\n linkHoverColor: resolveColorToCSS(style.link.hoverColor),\n };\n\n return resolved;\n}\n\n/**\n * Resolve a responsive value to get the base value\n */\nfunction getResponsiveBase<T>(value: ResponsiveValue<T> | T): T {\n if (typeof value === \"object\" && value !== null && \"base\" in value) {\n return value.base;\n }\n return value as T;\n}\n\n/**\n * Resolve border config to CSS string\n */\nfunction resolveBorder(\n border: ResponsiveValue<BorderConfig> | undefined,\n): string {\n if (!border) return \"\";\n\n const base = getResponsiveBase(border);\n if (!base) return \"\";\n\n const width = base.width || \"0\";\n const style = base.style || \"solid\";\n const color = base.color ? resolveColorToCSS(base.color) : \"transparent\";\n\n return `${width} ${style} ${color}`;\n}\n\n/**\n * Generate CSS for the footer configuration\n */\nexport function generateFooterCSS(\n id: string,\n footer: FooterConfig,\n resolvedColors: Record<string, string | undefined>,\n): string {\n const cssBlocks: string[] = [];\n const containerBehavior = footer.style.container || \"edged\";\n\n // Base footer styles\n const baseMargin = getResponsiveBase(footer.style.layout.margin);\n const basePadding = getResponsiveBase(footer.style.layout.padding);\n const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);\n const borderCSS = resolveBorder(footer.style.border);\n\n const baseStyles = `\n .footer-${id} {\n display: flex;\n flex-direction: column;\n ${resolvedColors.background ? `background-color: ${resolvedColors.background};` : \"\"}\n ${resolvedColors.text ? `color: ${resolvedColors.text};` : \"\"}\n ${baseMargin ? `margin: ${baseMargin};` : \"\"}\n ${basePadding ? `padding: ${basePadding};` : \"\"}\n ${borderCSS ? `border: ${borderCSS};` : \"\"}\n ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : \"\"}\n ${baseSectionGap ? `gap: ${baseSectionGap};` : \"\"}\n }\n `;\n cssBlocks.push(baseStyles);\n\n // Container-specific styles\n if (containerBehavior === \"edged\") {\n // In edged mode, each container wrapper should be full width\n cssBlocks.push(`\n .footer-${id} > div.container {\n width: 100%;\n }\n `);\n }\n\n // Top-level sections (rows) should always be full width\n cssBlocks.push(`\n .footer-${id} .footer-section {\n width: 100%;\n }\n `);\n\n // Responsive layout for nested sections (columns within rows)\n // Mobile: stack all columns vertically\n cssBlocks.push(`\n @media (max-width: 767px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 100%;\n width: 100%;\n }\n }\n `);\n\n // Tablet: allow 2 columns per row\n cssBlocks.push(`\n @media (min-width: 768px) and (max-width: 1023px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 calc(50% - ${baseSectionGap || \"0px\"} / 2);\n min-width: 0;\n }\n }\n `);\n\n // Desktop: columns naturally fit based on their flex properties\n cssBlocks.push(`\n @media (min-width: 1024px) {\n .footer-${id} .footer-container-section {\n flex-wrap: nowrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 auto;\n min-width: 0;\n }\n }\n `);\n\n // Link styles\n if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {\n cssBlocks.push(`\n .footer-${id} a {\n ${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : \"\"}\n text-decoration: none;\n transition: color 0.2s;\n }\n .footer-${id} a:hover {\n ${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : \"\"}\n }\n `);\n }\n\n return minifyCSS(cssBlocks.filter(Boolean).join(\"\"));\n}\n\n/**\n * Format shadow config to CSS box-shadow value\n */\nfunction formatShadow(shadow: ShadowConfig): string {\n const insetStr = shadow.inset ? \"inset \" : \"\";\n return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;\n}\n\n/**\n * Minify CSS by removing extra whitespace\n */\nfunction minifyCSS(css: string): string {\n return css\n .replace(/\\s+/g, \" \")\n .replace(/\\s*{\\s*/g, \"{\")\n .replace(/\\s*}\\s*/g, \"}\")\n .replace(/\\s*:\\s*/g, \":\")\n .replace(/\\s*;\\s*/g, \";\")\n .replace(/;\\s*}/g, \"}\")\n .trim();\n}\n\n/**\n * Check if a section has nested sections (is a container section)\n */\nexport function isContainerSection(\n section: FooterSection | FooterContentSection,\n): section is FooterSection {\n return \"sections\" in section && Array.isArray(section.sections);\n}\n\n/**\n * Check if a section is a content section (has items)\n */\nexport function isContentSection(\n section: FooterSection | FooterContentSection,\n): section is FooterContentSection {\n return \"blocks\" in section && Array.isArray(section.blocks);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,19 @@
1
+ import { HeaderSection, Site, HeaderConfig, LocalizedString, HeaderDropdownContent, ShadowConfig } from '@otl-core/cms-types';
2
+
3
+ /**
4
+ * Convert ShadowConfig to CSS box-shadow string
5
+ */
6
+ declare function shadowConfigToCSS(shadow: ShadowConfig): string;
7
+ declare function calculateNavigationWidth(sections: HeaderSection[], site?: Site): number;
8
+ type Breakpoint = "sm" | "md" | "lg" | "xl" | "2xl" | null;
9
+ declare function getBreakpointForWidth(estimatedWidth: number): Breakpoint;
10
+ declare function generateNavigationCSS(id: string, navigation: HeaderConfig, resolvedColors: Record<string, string | undefined>, dropdownIds?: string[]): string;
11
+ declare function sectionsToDropdownContent(sections: HeaderSection[]): HeaderDropdownContent[];
12
+ declare function resolveDropdownColor(colorRef: {
13
+ type: string;
14
+ value: string;
15
+ } | undefined, resolvedColors: Record<string, string | undefined>, fallback?: string): string | undefined;
16
+ declare function getLocalizedString(value: string | LocalizedString | null | undefined, site?: Site): string;
17
+ declare function parseMarkdownToHTML(markdown: string): string;
18
+
19
+ export { type Breakpoint, calculateNavigationWidth, generateNavigationCSS, getBreakpointForWidth, getLocalizedString, parseMarkdownToHTML, resolveDropdownColor, sectionsToDropdownContent, shadowConfigToCSS };
@@ -0,0 +1,239 @@
1
+ import { marked } from "marked";
2
+ import {
3
+ generateDesktopDropdownAnimations,
4
+ generateMobileMenuAnimations,
5
+ generateResponsiveSpacingCSS,
6
+ generateScrollbarStyles,
7
+ generateToggleIconAnimations,
8
+ minifyCSS
9
+ } from "@otl-core/style-utils";
10
+ function shadowConfigToCSS(shadow) {
11
+ const { offsetX, offsetY, blurRadius, spreadRadius, color, inset } = shadow;
12
+ const parts = [offsetX, offsetY, blurRadius, spreadRadius, color];
13
+ if (inset) {
14
+ return `inset ${parts.join(" ")}`;
15
+ }
16
+ return parts.join(" ");
17
+ }
18
+ function calculateNavigationWidth(sections, site) {
19
+ let totalWidth = 150;
20
+ for (const section of sections) {
21
+ for (const item of section?.items || []) {
22
+ if (item.type === "logo") continue;
23
+ const label = typeof item.label === "string" ? item.label : getLocalizedString(item.label, site) || "";
24
+ const labelLength = label.length;
25
+ if (item.type === "button") {
26
+ totalWidth += labelLength * 8 + 48;
27
+ } else if (item.type === "link" || item.type === "dropdown") {
28
+ totalWidth += labelLength * 8 + 24;
29
+ }
30
+ }
31
+ }
32
+ return totalWidth;
33
+ }
34
+ function getBreakpointForWidth(estimatedWidth) {
35
+ const MAX_USABLE_WIDTH = 1400;
36
+ if (estimatedWidth > MAX_USABLE_WIDTH) return null;
37
+ if (estimatedWidth <= 640) return "sm";
38
+ if (estimatedWidth <= 768) return "md";
39
+ if (estimatedWidth <= 1024) return "lg";
40
+ if (estimatedWidth <= 1280) return "xl";
41
+ return "2xl";
42
+ }
43
+ function generateNavigationCSS(id, navigation, resolvedColors, dropdownIds = []) {
44
+ const cssBlocks = [];
45
+ if (navigation.style) {
46
+ const headerCSS = generateResponsiveSpacingCSS(`header-${id}`, {
47
+ margin: navigation.style.layout?.margin
48
+ });
49
+ if (headerCSS) cssBlocks.push(headerCSS);
50
+ }
51
+ if (navigation.style) {
52
+ const navbarCSS = generateResponsiveSpacingCSS(`navbar-${id}`, {
53
+ border: navigation.style.border,
54
+ padding: navigation.style.layout?.padding,
55
+ gap: navigation.style.layout?.sectionGap,
56
+ shadow: navigation.style.shadow
57
+ });
58
+ if (navbarCSS) cssBlocks.push(navbarCSS);
59
+ }
60
+ if (navigation.style && dropdownIds.length > 0) {
61
+ dropdownIds.forEach((dropdownId) => {
62
+ const dropdownCSS = generateResponsiveSpacingCSS(
63
+ `navigation-dropdown-${dropdownId}`,
64
+ {
65
+ padding: navigation.style?.dropdown?.padding,
66
+ border: navigation.style?.dropdown?.border
67
+ }
68
+ );
69
+ if (dropdownCSS) cssBlocks.push(dropdownCSS);
70
+ const dropdownContentCSS = generateResponsiveSpacingCSS(
71
+ `dropdown-content-${dropdownId}`,
72
+ {
73
+ gap: navigation.style?.dropdown?.sectionGap
74
+ }
75
+ );
76
+ if (dropdownContentCSS) cssBlocks.push(dropdownContentCSS);
77
+ });
78
+ }
79
+ if (resolvedColors.burgerButtonBackgroundHover) {
80
+ cssBlocks.push(
81
+ `.mobile-menu-toggle-${id}:hover{background-color:${resolvedColors.burgerButtonBackgroundHover}!important}`
82
+ );
83
+ }
84
+ if (resolvedColors.dropdownMenuLinkHoverColor || resolvedColors.dropdownMenuLinkHoverBackground) {
85
+ const hoverStyles = [];
86
+ if (resolvedColors.dropdownMenuLinkHoverBackground) {
87
+ hoverStyles.push(
88
+ `background-color:${resolvedColors.dropdownMenuLinkHoverBackground}!important`
89
+ );
90
+ }
91
+ if (resolvedColors.dropdownMenuLinkHoverColor) {
92
+ hoverStyles.push(
93
+ `color:${resolvedColors.dropdownMenuLinkHoverColor}!important`
94
+ );
95
+ }
96
+ cssBlocks.push(
97
+ `#mobile-menu-dropdown-${id} a:hover{${hoverStyles.join(";")}}`
98
+ );
99
+ }
100
+ cssBlocks.push(...generateToggleIconAnimations());
101
+ cssBlocks.push(...generateMobileMenuAnimations());
102
+ cssBlocks.push(...generateScrollbarStyles());
103
+ cssBlocks.push(...generateDesktopDropdownAnimations());
104
+ return minifyCSS(cssBlocks.filter(Boolean).join(""));
105
+ }
106
+ function sectionsToDropdownContent(sections) {
107
+ const result = [];
108
+ sections.forEach((section) => {
109
+ const items = section?.items?.filter((item) => {
110
+ if (item.type === "logo") return false;
111
+ const vis = item.visibility || (item.collapse === false ? "navbar-only" : void 0);
112
+ if (vis === "navbar-only") return false;
113
+ return true;
114
+ });
115
+ if (items?.length === 0) return;
116
+ items?.forEach((item) => {
117
+ if (item.type === "link") {
118
+ const config = item.config;
119
+ const navConfig = {
120
+ label: item.label || "",
121
+ href: config.href,
122
+ icon: config.icon,
123
+ external: config.external
124
+ };
125
+ result.push({
126
+ id: item.id,
127
+ type: "navigation-item",
128
+ config: navConfig
129
+ });
130
+ } else if (item.type === "button") {
131
+ const config = item.config;
132
+ const btnConfig = {
133
+ label: item.label || "",
134
+ href: config.href,
135
+ icon: config.icon,
136
+ external: config.external,
137
+ variant: config.variant,
138
+ size: config.size
139
+ };
140
+ result.push({
141
+ id: item.id,
142
+ type: "button",
143
+ config: btnConfig
144
+ });
145
+ } else if (item.type === "dropdown") {
146
+ const config = item.config;
147
+ result.push({
148
+ id: item.id,
149
+ type: "dropdown",
150
+ label: item.label || "",
151
+ config
152
+ });
153
+ }
154
+ });
155
+ if (sections.indexOf(section) < sections.length - 1) {
156
+ result.push({
157
+ id: `divider-${section.id}`,
158
+ type: "divider",
159
+ config: {}
160
+ });
161
+ }
162
+ });
163
+ return result;
164
+ }
165
+ function resolveDropdownColor(colorRef, resolvedColors, fallback) {
166
+ if (!colorRef) return fallback;
167
+ if (colorRef.type === "custom") {
168
+ return colorRef.value;
169
+ }
170
+ if (colorRef.type === "theme") {
171
+ return resolvedColors[colorRef.value] || fallback;
172
+ }
173
+ if (colorRef.type === "variable") {
174
+ return `var(--color-${colorRef.value})`;
175
+ }
176
+ return fallback;
177
+ }
178
+ function getBrowserPreferredLocales(options = {}) {
179
+ const defaultOptions = {
180
+ languageCodeOnly: false
181
+ };
182
+ const opt = {
183
+ ...defaultOptions,
184
+ ...options
185
+ };
186
+ const browserLocales = navigator.languages === void 0 ? [navigator.language] : navigator.languages;
187
+ if (!browserLocales) {
188
+ return void 0;
189
+ }
190
+ return browserLocales.map((locale) => {
191
+ const trimmedLocale = locale.trim();
192
+ return opt.languageCodeOnly ? trimmedLocale.split(/-|_/)[0] : trimmedLocale;
193
+ });
194
+ }
195
+ function getLocalizedString(value, site) {
196
+ if (value === null || value === void 0) return "";
197
+ if (typeof value === "string") return value;
198
+ const preferredLocale = getBrowserPreferredLocales() || [
199
+ site?.default_locale || "en"
200
+ ];
201
+ for (const locale of preferredLocale) {
202
+ if (locale in value && value[locale]) {
203
+ return value[locale];
204
+ }
205
+ }
206
+ if (site?.default_locale && site.default_locale in value && value[site.default_locale]) {
207
+ return value[site.default_locale];
208
+ }
209
+ if ("en" in value && value.en) {
210
+ return value.en;
211
+ }
212
+ if (site?.supported_locales) {
213
+ for (const locale of site.supported_locales) {
214
+ if (locale in value && value[locale]) {
215
+ return value[locale];
216
+ }
217
+ }
218
+ }
219
+ const keys = Object.keys(value);
220
+ if (keys.length > 0 && value[keys[0]]) {
221
+ return value[keys[0]];
222
+ }
223
+ return "";
224
+ }
225
+ function parseMarkdownToHTML(markdown) {
226
+ const html = marked.parse(markdown, { async: false });
227
+ return html.replace(/<h1>/g, '<div class="h1">').replace(/<\/h1>/g, "</div>").replace(/<h2>/g, '<div class="h2">').replace(/<\/h2>/g, "</div>").replace(/<h3>/g, '<div class="h3">').replace(/<\/h3>/g, "</div>").replace(/<h4>/g, '<div class="h4">').replace(/<\/h4>/g, "</div>").replace(/<h5>/g, '<div class="h5">').replace(/<\/h5>/g, "</div>").replace(/<h6>/g, '<div class="h6">').replace(/<\/h6>/g, "</div>");
228
+ }
229
+ export {
230
+ calculateNavigationWidth,
231
+ generateNavigationCSS,
232
+ getBreakpointForWidth,
233
+ getLocalizedString,
234
+ parseMarkdownToHTML,
235
+ resolveDropdownColor,
236
+ sectionsToDropdownContent,
237
+ shadowConfigToCSS
238
+ };
239
+ //# sourceMappingURL=navigation.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/navigation.utils.ts"],"sourcesContent":["import {\n Site,\n HeaderConfig,\n HeaderDropdownButtonConfig,\n HeaderDropdownContent,\n HeaderDropdownNavigationItemConfig,\n HeaderNavigationItem,\n HeaderNavigationItemButtonConfig,\n HeaderNavigationItemDropdownConfig,\n HeaderNavigationItemLinkConfig,\n HeaderSection,\n LocalizedString,\n ShadowConfig,\n} from \"@otl-core/cms-types\";\nimport { marked } from \"marked\";\nimport {\n generateDesktopDropdownAnimations,\n generateMobileMenuAnimations,\n generateResponsiveSpacingCSS,\n generateScrollbarStyles,\n generateToggleIconAnimations,\n minifyCSS,\n} from \"@otl-core/style-utils\";\n\n/**\n * Convert ShadowConfig to CSS box-shadow string\n */\nexport function shadowConfigToCSS(shadow: ShadowConfig): string {\n const { offsetX, offsetY, blurRadius, spreadRadius, color, inset } = shadow;\n const parts = [offsetX, offsetY, blurRadius, spreadRadius, color];\n if (inset) {\n return `inset ${parts.join(\" \")}`;\n }\n return parts.join(\" \");\n}\n\nexport function calculateNavigationWidth(\n sections: HeaderSection[],\n site?: Site,\n): number {\n let totalWidth = 150;\n\n for (const section of sections) {\n for (const item of section?.items || []) {\n if (item.type === \"logo\") continue;\n\n const label =\n typeof item.label === \"string\"\n ? item.label\n : getLocalizedString(item.label, site) || \"\";\n const labelLength = label.length;\n\n if (item.type === \"button\") {\n totalWidth += labelLength * 8 + 48;\n } else if (item.type === \"link\" || item.type === \"dropdown\") {\n totalWidth += labelLength * 8 + 24;\n }\n }\n }\n\n return totalWidth;\n}\n\nexport type Breakpoint = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | null;\n\nexport function getBreakpointForWidth(estimatedWidth: number): Breakpoint {\n const MAX_USABLE_WIDTH = 1400;\n\n if (estimatedWidth > MAX_USABLE_WIDTH) return null;\n\n if (estimatedWidth <= 640) return \"sm\";\n if (estimatedWidth <= 768) return \"md\";\n if (estimatedWidth <= 1024) return \"lg\";\n if (estimatedWidth <= 1280) return \"xl\";\n return \"2xl\";\n}\n\nexport function generateNavigationCSS(\n id: string,\n navigation: HeaderConfig,\n resolvedColors: Record<string, string | undefined>,\n dropdownIds: string[] = [],\n): string {\n const cssBlocks: (string | null)[] = [];\n\n if (navigation.style) {\n const headerCSS = generateResponsiveSpacingCSS(`header-${id}`, {\n margin: navigation.style.layout?.margin,\n });\n if (headerCSS) cssBlocks.push(headerCSS);\n }\n\n if (navigation.style) {\n const navbarCSS = generateResponsiveSpacingCSS(`navbar-${id}`, {\n border: navigation.style.border,\n padding: navigation.style.layout?.padding,\n gap: navigation.style.layout?.sectionGap,\n shadow: navigation.style.shadow,\n });\n if (navbarCSS) cssBlocks.push(navbarCSS);\n }\n\n if (navigation.style && dropdownIds.length > 0) {\n dropdownIds.forEach((dropdownId) => {\n const dropdownCSS = generateResponsiveSpacingCSS(\n `navigation-dropdown-${dropdownId}`,\n {\n padding: navigation.style?.dropdown?.padding,\n border: navigation.style?.dropdown?.border,\n },\n );\n if (dropdownCSS) cssBlocks.push(dropdownCSS);\n\n const dropdownContentCSS = generateResponsiveSpacingCSS(\n `dropdown-content-${dropdownId}`,\n {\n gap: navigation.style?.dropdown?.sectionGap,\n },\n );\n if (dropdownContentCSS) cssBlocks.push(dropdownContentCSS);\n });\n }\n\n if (resolvedColors.burgerButtonBackgroundHover) {\n cssBlocks.push(\n `.mobile-menu-toggle-${id}:hover{background-color:${resolvedColors.burgerButtonBackgroundHover}!important}`,\n );\n }\n\n if (\n resolvedColors.dropdownMenuLinkHoverColor ||\n resolvedColors.dropdownMenuLinkHoverBackground\n ) {\n const hoverStyles: string[] = [];\n if (resolvedColors.dropdownMenuLinkHoverBackground) {\n hoverStyles.push(\n `background-color:${resolvedColors.dropdownMenuLinkHoverBackground}!important`,\n );\n }\n if (resolvedColors.dropdownMenuLinkHoverColor) {\n hoverStyles.push(\n `color:${resolvedColors.dropdownMenuLinkHoverColor}!important`,\n );\n }\n cssBlocks.push(\n `#mobile-menu-dropdown-${id} a:hover{${hoverStyles.join(\";\")}}`,\n );\n }\n\n cssBlocks.push(...generateToggleIconAnimations());\n cssBlocks.push(...generateMobileMenuAnimations());\n cssBlocks.push(...generateScrollbarStyles());\n cssBlocks.push(...generateDesktopDropdownAnimations());\n\n return minifyCSS(cssBlocks.filter(Boolean).join(\"\"));\n}\n\nexport function sectionsToDropdownContent(\n sections: HeaderSection[],\n): HeaderDropdownContent[] {\n const result: HeaderDropdownContent[] = [];\n\n sections.forEach((section: HeaderSection) => {\n const items = section?.items?.filter((item: HeaderNavigationItem) => {\n if (item.type === \"logo\") return false;\n const vis =\n item.visibility ||\n ((item as unknown as Record<string, unknown>).collapse === false\n ? \"navbar-only\"\n : undefined);\n if (vis === \"navbar-only\") return false;\n return true;\n });\n\n if (items?.length === 0) return;\n\n items?.forEach((item: HeaderNavigationItem) => {\n if (item.type === \"link\") {\n const config = item.config as HeaderNavigationItemLinkConfig;\n const navConfig: HeaderDropdownNavigationItemConfig = {\n label: item.label || \"\",\n href: config.href,\n icon: config.icon,\n external: config.external,\n };\n result.push({\n id: item.id,\n type: \"navigation-item\",\n config: navConfig,\n });\n } else if (item.type === \"button\") {\n const config = item.config as HeaderNavigationItemButtonConfig;\n const btnConfig: HeaderDropdownButtonConfig = {\n label: item.label || \"\",\n href: config.href,\n icon: config.icon,\n external: config.external,\n variant: config.variant,\n size: config.size,\n };\n result.push({\n id: item.id,\n type: \"button\",\n config: btnConfig,\n });\n } else if (item.type === \"dropdown\") {\n const config = item.config as HeaderNavigationItemDropdownConfig;\n result.push({\n id: item.id,\n type: \"dropdown\",\n label: item.label || \"\",\n config,\n });\n }\n });\n\n if (sections.indexOf(section) < sections.length - 1) {\n result.push({\n id: `divider-${section.id}`,\n type: \"divider\",\n config: {},\n });\n }\n });\n\n return result;\n}\n\nexport function resolveDropdownColor(\n colorRef: { type: string; value: string } | undefined,\n resolvedColors: Record<string, string | undefined>,\n fallback?: string,\n): string | undefined {\n if (!colorRef) return fallback;\n\n if (colorRef.type === \"custom\") {\n return colorRef.value;\n }\n\n if (colorRef.type === \"theme\") {\n return resolvedColors[colorRef.value] || fallback;\n }\n\n if (colorRef.type === \"variable\") {\n // For variables, construct the CSS variable reference\n return `var(--color-${colorRef.value})`;\n }\n\n return fallback;\n}\n\nfunction getBrowserPreferredLocales(options = {}) {\n const defaultOptions = {\n languageCodeOnly: false,\n };\n const opt = {\n ...defaultOptions,\n ...options,\n };\n const browserLocales =\n navigator.languages === undefined\n ? [navigator.language]\n : navigator.languages;\n if (!browserLocales) {\n return undefined;\n }\n return browserLocales.map((locale) => {\n const trimmedLocale = locale.trim();\n return opt.languageCodeOnly ? trimmedLocale.split(/-|_/)[0] : trimmedLocale;\n });\n}\n\nexport function getLocalizedString(\n value: string | LocalizedString | null | undefined,\n site?: Site,\n): string {\n // Handle null/undefined\n if (value === null || value === undefined) return \"\";\n\n // If it's already a string, return it\n if (typeof value === \"string\") return value;\n\n // Get the preferred locale from options or use default fallback\n const preferredLocale = getBrowserPreferredLocales() || [\n site?.default_locale || \"en\",\n ];\n\n // Try preferred locale\n for (const locale of preferredLocale) {\n if (locale in value && value[locale]) {\n return value[locale];\n }\n }\n\n // Try default locale\n if (\n site?.default_locale &&\n site.default_locale in value &&\n value[site.default_locale]\n ) {\n return value[site.default_locale];\n }\n\n // Try 'en' as fallback\n if (\"en\" in value && value.en) {\n return value.en;\n }\n\n // Try any supported locale\n if (site?.supported_locales) {\n for (const locale of site.supported_locales) {\n if (locale in value && value[locale]) {\n return value[locale];\n }\n }\n }\n\n // Return first available value as last resort\n const keys = Object.keys(value);\n if (keys.length > 0 && value[keys[0]]) {\n return value[keys[0]];\n }\n\n return \"\";\n}\n\nexport function parseMarkdownToHTML(markdown: string): string {\n // Parse markdown to HTML\n const html = marked.parse(markdown, { async: false }) as string;\n\n // Transform h1-h6 elements to divs with corresponding classes\n return html\n .replace(/<h1>/g, '<div class=\"h1\">')\n .replace(/<\\/h1>/g, \"</div>\")\n .replace(/<h2>/g, '<div class=\"h2\">')\n .replace(/<\\/h2>/g, \"</div>\")\n .replace(/<h3>/g, '<div class=\"h3\">')\n .replace(/<\\/h3>/g, \"</div>\")\n .replace(/<h4>/g, '<div class=\"h4\">')\n .replace(/<\\/h4>/g, \"</div>\")\n .replace(/<h5>/g, '<div class=\"h5\">')\n .replace(/<\\/h5>/g, \"</div>\")\n .replace(/<h6>/g, '<div class=\"h6\">')\n .replace(/<\\/h6>/g, \"</div>\");\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otl-core/next-footer",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "type": "module",
5
5
  "description": "Reusable footer components for OTL CMS",
6
6
  "main": "./dist/index.js",
@@ -49,14 +49,14 @@
49
49
  "tailwind-merge": "^3.3.1"
50
50
  },
51
51
  "dependencies": {
52
- "@otl-core/block-registry": "^1.1.19",
53
- "@otl-core/cms-utils": "^1.1.19",
54
- "@otl-core/style-utils": "^1.1.19"
52
+ "@otl-core/block-registry": "^1.1.20",
53
+ "@otl-core/cms-utils": "^1.1.20",
54
+ "@otl-core/style-utils": "^1.1.20"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@eslint/eslintrc": "^3.3.1",
58
- "@otl-core/cms-types": "^1.1.19",
59
- "@otl-core/cms-utils": "^1.1.19",
58
+ "@otl-core/cms-types": "^1.1.20",
59
+ "@otl-core/cms-utils": "^1.1.20",
60
60
  "@radix-ui/react-focus-scope": "^1.1.7",
61
61
  "@radix-ui/react-slot": "^1.2.3",
62
62
  "@testing-library/jest-dom": "^6.9.1",