@otl-core/next-footer 1.1.37 → 1.1.39

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.
@@ -9,6 +9,7 @@ interface FooterSectionProps {
9
9
  resolvedColors: Record<string, string | undefined>;
10
10
  level?: number;
11
11
  blockRegistry?: BlockRegistry;
12
+ parentMinWidth?: string;
12
13
  }
13
14
  declare const FooterSectionComponent: React__default.FC<FooterSectionProps>;
14
15
 
@@ -9,7 +9,8 @@ const FooterSectionComponent = ({
9
9
  site,
10
10
  resolvedColors,
11
11
  level = 0,
12
- blockRegistry
12
+ blockRegistry,
13
+ parentMinWidth
13
14
  }) => {
14
15
  const sectionStyle = (() => {
15
16
  const style = {
@@ -22,6 +23,12 @@ const FooterSectionComponent = ({
22
23
  if (section.style.flex) {
23
24
  style.flex = section.style.flex;
24
25
  }
26
+ if (section.style.wrap) {
27
+ style.flexWrap = "wrap";
28
+ }
29
+ if (parentMinWidth) {
30
+ style.minWidth = parentMinWidth;
31
+ }
25
32
  if (section.style.background) {
26
33
  style.backgroundColor = resolveColorToCSS(section.style.background);
27
34
  }
@@ -32,7 +39,16 @@ const FooterSectionComponent = ({
32
39
  if (section.style.border) {
33
40
  const border = typeof section.style.border === "object" && "base" in section.style.border ? section.style.border.base : section.style.border;
34
41
  if (border) {
35
- style.border = `${border.width || "0"} ${border.style || "solid"} ${border.color ? resolveColorToCSS(border.color) : "transparent"}`;
42
+ const bw = border.width || "0";
43
+ const bs = border.style || "solid";
44
+ const bc = border.color ? resolveColorToCSS(border.color) : "transparent";
45
+ if (bw.trim().includes(" ")) {
46
+ style.borderWidth = bw;
47
+ style.borderStyle = bs;
48
+ style.borderColor = bc;
49
+ } else {
50
+ style.border = `${bw} ${bs} ${bc}`;
51
+ }
36
52
  if (border.radius) {
37
53
  style.borderRadius = border.radius;
38
54
  }
@@ -73,6 +89,7 @@ const FooterSectionComponent = ({
73
89
  footer,
74
90
  site,
75
91
  resolvedColors,
92
+ parentMinWidth: section.style.minWidth,
76
93
  level: level + 1,
77
94
  blockRegistry
78
95
  },
@@ -1 +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":[]}
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 parentMinWidth?: string;\n}\n\nexport const FooterSectionComponent: React.FC<FooterSectionProps> = ({\n section,\n footer,\n site,\n resolvedColors,\n level = 0,\n blockRegistry,\n parentMinWidth,\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 if (section.style.wrap) {\n style.flexWrap = \"wrap\";\n }\n\n if (parentMinWidth) {\n style.minWidth = parentMinWidth;\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 const bw = border.width || \"0\";\n const bs = border.style || \"solid\";\n const bc = border.color\n ? resolveColorToCSS(border.color)\n : \"transparent\";\n if (bw.trim().includes(\" \")) {\n style.borderWidth = bw;\n style.borderStyle = bs;\n style.borderColor = bc;\n } else {\n style.border = `${bw} ${bs} ${bc}`;\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 parentMinWidth={section.style.minWidth}\n level={level + 1}\n blockRegistry={blockRegistry}\n />\n ))}\n </div>\n );\n};\n"],"mappings":"AA4GY;AArGZ,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,iBAAiB,0BAA0B;AAEpD,SAAS,iBAAiB,6BAA6B;AAYhD,MAAM,yBAAuD,CAAC;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;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;AAEA,QAAI,QAAQ,MAAM,MAAM;AACtB,YAAM,WAAW;AAAA,IACnB;AAEA,QAAI,gBAAgB;AAClB,YAAM,WAAW;AAAA,IACnB;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,KAAK,OAAO,SAAS;AAC3B,cAAM,KAAK,OAAO,SAAS;AAC3B,cAAM,KAAK,OAAO,QACd,kBAAkB,OAAO,KAAK,IAC9B;AACJ,YAAI,GAAG,KAAK,EAAE,SAAS,GAAG,GAAG;AAC3B,gBAAM,cAAc;AACpB,gBAAM,cAAc;AACpB,gBAAM,cAAc;AAAA,QACtB,OAAO;AACL,gBAAM,SAAS,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;AAAA,QAClC;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,gBAAgB,QAAQ,MAAM;AAAA,UAC9B,OAAO,QAAQ;AAAA,UACf;AAAA;AAAA,QAPK,aAAa;AAAA,MAQpB,CACD;AAAA;AAAA,EACL;AAEJ;","names":[]}
@@ -22,7 +22,10 @@ function resolveBorder(border) {
22
22
  const width = base.width || "0";
23
23
  const style = base.style || "solid";
24
24
  const color = base.color ? resolveColorToCSS(base.color) : "transparent";
25
- return `${width} ${style} ${color}`;
25
+ if (width.trim().includes(" ")) {
26
+ return `border-width: ${width}; border-style: ${style}; border-color: ${color}`;
27
+ }
28
+ return `border: ${width} ${style} ${color}`;
26
29
  }
27
30
  function generateFooterCSS(id, footer, resolvedColors) {
28
31
  const cssBlocks = [];
@@ -39,7 +42,7 @@ function generateFooterCSS(id, footer, resolvedColors) {
39
42
  ${resolvedColors.text ? `color: ${resolvedColors.text};` : ""}
40
43
  ${baseMargin ? `margin: ${baseMargin};` : ""}
41
44
  ${basePadding ? `padding: ${basePadding};` : ""}
42
- ${borderCSS ? `border: ${borderCSS};` : ""}
45
+ ${borderCSS ? `${borderCSS};` : ""}
43
46
  ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : ""}
44
47
  ${baseSectionGap ? `gap: ${baseSectionGap};` : ""}
45
48
  }
@@ -1 +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":[]}
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 property string(s).\n * When width is a single value (e.g. \"1px\"), returns a `border` shorthand.\n * When width has multiple values (e.g. \"1px 0 0 0\"), returns separate properties.\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 // Multi-value width (e.g. \"1px 0 0 0\") can't use border shorthand\n if (width.trim().includes(\" \")) {\n return `border-width: ${width}; border-style: ${style}; border-color: ${color}`;\n }\n\n return `border: ${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 ? `${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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otl-core/next-footer",
3
- "version": "1.1.37",
3
+ "version": "1.1.39",
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.37",
53
- "@otl-core/cms-utils": "^1.1.37",
54
- "@otl-core/style-utils": "^1.1.37"
52
+ "@otl-core/block-registry": "^1.1.39",
53
+ "@otl-core/cms-utils": "^1.1.39",
54
+ "@otl-core/style-utils": "^1.1.39"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@eslint/eslintrc": "^3.3.1",
58
- "@otl-core/cms-types": "^1.1.37",
59
- "@otl-core/cms-utils": "^1.1.37",
58
+ "@otl-core/cms-types": "^1.1.39",
59
+ "@otl-core/cms-utils": "^1.1.39",
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",