@otl-core/next-navigation 1.1.36 → 1.1.38

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.
@@ -218,7 +218,7 @@ function NavigationDropdown({
218
218
  return /* @__PURE__ */ jsx(
219
219
  "nav",
220
220
  {
221
- className: `flex flex-col p-2 dropdown-content-${dropdownId}`,
221
+ className: `flex flex-col ${useSameLayerMode ? "" : "p-2"} dropdown-content-${dropdownId}`,
222
222
  style: {
223
223
  ...linkHoverStyle,
224
224
  width: "100%",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/navigation/dropdown.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n Site,\n HeaderConfig,\n HeaderDropdownGroupConfig,\n HeaderNavigationItemConfig,\n HeaderNavigationItemDropdownConfig,\n} from \"@otl-core/cms-types\";\nimport { HeaderDropdownContent } from \"@otl-core/cms-types\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { CSSTransition } from \"react-transition-group\";\nimport { useNavigation } from \"../../context/navigation-context\";\nimport { resolveBorderToCSS, resolveColorsToCSS } from \"@otl-core/style-utils\";\nimport { DropdownContentItem } from \"../items/dropdown-content-item\";\n\nfunction getDropdownTimeout(isMobileMenu: boolean): number {\n return isMobileMenu ? 300 : 200;\n}\n\nfunction getDropdownClassNames(isMobileMenu: boolean): string {\n return isMobileMenu ? \"mobile-menu\" : \"desktop-dropdown\";\n}\n\nfunction getDropdownZIndex(\n isSameLayer: boolean,\n layer?: string,\n): number | undefined {\n if (isSameLayer) return undefined;\n return layer === \"above\" ? 9999 : undefined;\n}\n\ninterface NavigationDropdownProps {\n itemId: string;\n config: HeaderNavigationItemConfig;\n navigation: HeaderConfig;\n resolvedColors: Record<string, string | undefined>;\n styles: React.CSSProperties;\n linkStyle: React.CSSProperties;\n dropdownId: string;\n site: Site;\n containerContent?: boolean;\n isSameLayer?: boolean;\n}\nexport function NavigationDropdown({\n itemId,\n config,\n navigation,\n resolvedColors,\n dropdownId,\n site,\n containerContent = false,\n isSameLayer = false,\n}: NavigationDropdownProps) {\n const { activeDropdown } = useNavigation();\n const [navigationStack, setNavigationStack] = useState<string[]>([]);\n const nodeRef = useRef(null);\n const backButtonRef = useRef<HTMLButtonElement>(null);\n\n const isMobileMenu = useMemo(\n () => itemId.startsWith(\"mobile-menu\"),\n [itemId],\n );\n const isOpen = useMemo(\n () => activeDropdown === itemId,\n [activeDropdown, itemId],\n );\n const content: HeaderDropdownContent[] = useMemo(\n () => (config as HeaderNavigationItemDropdownConfig).content || [],\n [config],\n );\n\n // Reset nested content when dropdown closes (derived during render, no effect needed)\n const prevIsOpen = useRef(isOpen);\n if (prevIsOpen.current && !isOpen) {\n setNavigationStack([]);\n }\n prevIsOpen.current = isOpen;\n\n // Focus back button when navigating to nested content (DOM side effect, needs useEffect)\n useEffect(() => {\n if (navigationStack.length > 0 && backButtonRef.current) {\n backButtonRef.current.focus();\n }\n }, [navigationStack.length]);\n\n const handleNavigate = (contentId: string) => {\n setNavigationStack((prev) => [...prev, contentId]);\n };\n\n const handleBack = () => {\n setNavigationStack((prev) => prev.slice(0, -1));\n };\n\n // Helper to recursively find a dropdown by ID in content (including inside sections)\n const findDropdownById = (\n contentArray: HeaderDropdownContent[],\n id: string,\n ): HeaderDropdownContent | undefined => {\n for (const item of contentArray) {\n if (item.id === id && item.type === \"dropdown\") {\n return item;\n }\n // Search inside sections\n if (item.type === \"section\") {\n const config = item.config as HeaderDropdownGroupConfig;\n const found = findDropdownById(config.content, id);\n if (found) return found;\n }\n }\n return undefined;\n };\n\n // Build the navigation panels based on the stack\n const buildNavigationPanels = () => {\n const panels: Array<{\n content: HeaderDropdownContent[];\n depth: number;\n }> = [];\n\n // Root panel\n panels.push({ content: content, depth: 0 });\n\n // Build nested panels\n let currentContent = content;\n for (let i = 0; i < navigationStack.length; i++) {\n const contentId = navigationStack[i];\n const dropdownItem = findDropdownById(currentContent, contentId);\n\n if (dropdownItem && dropdownItem.config) {\n const nestedContent = (\n dropdownItem.config as HeaderNavigationItemDropdownConfig\n ).content;\n panels.push({ content: nestedContent, depth: i + 1 });\n currentContent = nestedContent;\n } else {\n break;\n }\n }\n\n return panels;\n };\n\n const panels = buildNavigationPanels();\n\n // Use the same color resolution as the rest of the system\n const dropdownColors = resolveColorsToCSS({\n dropdownMenuBackground: navigation.style?.dropdown?.background,\n dropdownMenuLinkColor: navigation.style?.dropdown?.link?.color,\n dropdownMenuLinkHoverColor: navigation.style?.dropdown?.link?.hoverColor,\n dropdownMenuLinkHoverBackground:\n navigation.style?.dropdown?.link?.hoverBackground,\n dropdownMenuTextColor: navigation.style?.dropdown?.textColor,\n });\n\n const dropdownMenuBackground = dropdownColors.dropdownMenuBackground;\n const dropdownMenuLinkColor = dropdownColors.dropdownMenuLinkColor;\n const dropdownMenuLinkHoverColor = dropdownColors.dropdownMenuLinkHoverColor;\n const dropdownMenuLinkHoverBackground =\n dropdownColors.dropdownMenuLinkHoverBackground || \"transparent\";\n const dropdownMenuTextColor = dropdownColors.dropdownMenuTextColor;\n\n const dropdownMenuBorder = useMemo(() => {\n if (!navigation.style?.dropdown?.border) return undefined;\n return resolveBorderToCSS(navigation.style.dropdown.border);\n }, [navigation.style?.dropdown?.border]);\n\n // Shadow is now handled via responsive CSS generation (generateResponsiveSpacingCSS)\n\n // Early return AFTER all hooks\n if (content.length === 0) return null;\n\n // Enhanced resolved colors for dropdown, using dropdown-specific text color if available\n const dropdownResolvedColors = {\n ...resolvedColors,\n text: dropdownMenuTextColor || resolvedColors.text,\n dropdownMenuLinkColor,\n };\n\n const useSameLayerMode = isSameLayer && !isMobileMenu;\n\n const dropdownStyles: React.CSSProperties = {\n backgroundColor: useSameLayerMode ? undefined : dropdownMenuBackground,\n color: dropdownMenuTextColor || dropdownMenuLinkColor,\n ...(useSameLayerMode ? {} : dropdownMenuBorder),\n };\n\n const linkHoverStyle = {\n \"--dropdown-link-hover-color\": dropdownMenuLinkHoverColor,\n \"--dropdown-link-hover-background\": dropdownMenuLinkHoverBackground,\n } as React.CSSProperties;\n\n return (\n <>\n <CSSTransition\n in={isOpen}\n nodeRef={nodeRef}\n timeout={useSameLayerMode ? 250 : getDropdownTimeout(isMobileMenu)}\n classNames={\n useSameLayerMode\n ? \"same-layer-dropdown\"\n : getDropdownClassNames(isMobileMenu)\n }\n unmountOnExit={!useSameLayerMode}\n >\n <div\n ref={nodeRef}\n className={`relative w-full navigation-dropdown-${dropdownId}`}\n style={{\n ...dropdownStyles,\n ...(useSameLayerMode\n ? { display: \"grid\", overflow: \"hidden\" }\n : { overflow: \"hidden\" }),\n zIndex: getDropdownZIndex(\n useSameLayerMode,\n navigation.style?.dropdown?.layer,\n ),\n }}\n >\n <div\n style={\n useSameLayerMode\n ? { overflow: \"hidden\", minHeight: 0 }\n : undefined\n }\n >\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n transform: `translateX(-${navigationStack.length * 100}%)`,\n transition: \"transform 300ms ease-in-out\",\n }}\n >\n {panels.map((panel, panelIndex) => {\n const isActive = panelIndex === navigationStack.length;\n const isToRight = panelIndex > navigationStack.length;\n\n const panelContent = (\n <>\n {panel.depth > 0 && (\n <button\n ref={\n panelIndex === navigationStack.length\n ? backButtonRef\n : null\n }\n onClick={(e) => {\n e.preventDefault();\n handleBack();\n }}\n type=\"button\"\n data-navigation-internal=\"true\"\n className=\"flex items-center px-3 py-2 text-sm rounded-md transition-colors mb-2\"\n style={{\n color:\n resolvedColors.dropdownMenuLinkColor ||\n resolvedColors.linkColor,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n className=\"mr-2\"\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n Back\n </button>\n )}\n\n <div className=\"flex flex-wrap gap-4\">\n {panel.content?.map((item: HeaderDropdownContent) => {\n if (item.type === \"section\") {\n return (\n <div key={item.id} className=\"flex-1 min-w-[200px]\">\n <DropdownContentItem\n content={item}\n resolvedColors={dropdownResolvedColors}\n onNavigate={handleNavigate}\n navigation={navigation}\n site={site}\n />\n </div>\n );\n }\n return (\n <div key={item.id} className=\"w-full\">\n <DropdownContentItem\n content={item}\n resolvedColors={dropdownResolvedColors}\n onNavigate={handleNavigate}\n navigation={navigation}\n site={site}\n />\n </div>\n );\n })}\n </div>\n </>\n );\n\n return (\n <nav\n key={panelIndex}\n className={`flex flex-col p-2 dropdown-content-${dropdownId}`}\n style={{\n ...linkHoverStyle,\n width: \"100%\",\n flexShrink: 0,\n transform: isToRight\n ? \"translateX(30px)\"\n : \"translateX(0)\",\n opacity: isActive ? 1 : 0.95,\n transition:\n \"transform 300ms ease-in-out, opacity 300ms ease-in-out\",\n }}\n inert={!isActive ? true : undefined}\n >\n {containerContent ? (\n <div className=\"container mx-auto\">{panelContent}</div>\n ) : (\n panelContent\n )}\n </nav>\n );\n })}\n </div>\n </div>\n </div>\n </CSSTransition>\n </>\n );\n}\n"],"mappings":"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","names":["config","panels"]}
1
+ {"version":3,"sources":["../../../src/components/navigation/dropdown.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n Site,\n HeaderConfig,\n HeaderDropdownGroupConfig,\n HeaderNavigationItemConfig,\n HeaderNavigationItemDropdownConfig,\n} from \"@otl-core/cms-types\";\nimport { HeaderDropdownContent } from \"@otl-core/cms-types\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { CSSTransition } from \"react-transition-group\";\nimport { useNavigation } from \"../../context/navigation-context\";\nimport { resolveBorderToCSS, resolveColorsToCSS } from \"@otl-core/style-utils\";\nimport { DropdownContentItem } from \"../items/dropdown-content-item\";\n\nfunction getDropdownTimeout(isMobileMenu: boolean): number {\n return isMobileMenu ? 300 : 200;\n}\n\nfunction getDropdownClassNames(isMobileMenu: boolean): string {\n return isMobileMenu ? \"mobile-menu\" : \"desktop-dropdown\";\n}\n\nfunction getDropdownZIndex(\n isSameLayer: boolean,\n layer?: string,\n): number | undefined {\n if (isSameLayer) return undefined;\n return layer === \"above\" ? 9999 : undefined;\n}\n\ninterface NavigationDropdownProps {\n itemId: string;\n config: HeaderNavigationItemConfig;\n navigation: HeaderConfig;\n resolvedColors: Record<string, string | undefined>;\n styles: React.CSSProperties;\n linkStyle: React.CSSProperties;\n dropdownId: string;\n site: Site;\n containerContent?: boolean;\n isSameLayer?: boolean;\n}\nexport function NavigationDropdown({\n itemId,\n config,\n navigation,\n resolvedColors,\n dropdownId,\n site,\n containerContent = false,\n isSameLayer = false,\n}: NavigationDropdownProps) {\n const { activeDropdown } = useNavigation();\n const [navigationStack, setNavigationStack] = useState<string[]>([]);\n const nodeRef = useRef(null);\n const backButtonRef = useRef<HTMLButtonElement>(null);\n\n const isMobileMenu = useMemo(\n () => itemId.startsWith(\"mobile-menu\"),\n [itemId],\n );\n const isOpen = useMemo(\n () => activeDropdown === itemId,\n [activeDropdown, itemId],\n );\n const content: HeaderDropdownContent[] = useMemo(\n () => (config as HeaderNavigationItemDropdownConfig).content || [],\n [config],\n );\n\n // Reset nested content when dropdown closes (derived during render, no effect needed)\n const prevIsOpen = useRef(isOpen);\n if (prevIsOpen.current && !isOpen) {\n setNavigationStack([]);\n }\n prevIsOpen.current = isOpen;\n\n // Focus back button when navigating to nested content (DOM side effect, needs useEffect)\n useEffect(() => {\n if (navigationStack.length > 0 && backButtonRef.current) {\n backButtonRef.current.focus();\n }\n }, [navigationStack.length]);\n\n const handleNavigate = (contentId: string) => {\n setNavigationStack((prev) => [...prev, contentId]);\n };\n\n const handleBack = () => {\n setNavigationStack((prev) => prev.slice(0, -1));\n };\n\n // Helper to recursively find a dropdown by ID in content (including inside sections)\n const findDropdownById = (\n contentArray: HeaderDropdownContent[],\n id: string,\n ): HeaderDropdownContent | undefined => {\n for (const item of contentArray) {\n if (item.id === id && item.type === \"dropdown\") {\n return item;\n }\n // Search inside sections\n if (item.type === \"section\") {\n const config = item.config as HeaderDropdownGroupConfig;\n const found = findDropdownById(config.content, id);\n if (found) return found;\n }\n }\n return undefined;\n };\n\n // Build the navigation panels based on the stack\n const buildNavigationPanels = () => {\n const panels: Array<{\n content: HeaderDropdownContent[];\n depth: number;\n }> = [];\n\n // Root panel\n panels.push({ content: content, depth: 0 });\n\n // Build nested panels\n let currentContent = content;\n for (let i = 0; i < navigationStack.length; i++) {\n const contentId = navigationStack[i];\n const dropdownItem = findDropdownById(currentContent, contentId);\n\n if (dropdownItem && dropdownItem.config) {\n const nestedContent = (\n dropdownItem.config as HeaderNavigationItemDropdownConfig\n ).content;\n panels.push({ content: nestedContent, depth: i + 1 });\n currentContent = nestedContent;\n } else {\n break;\n }\n }\n\n return panels;\n };\n\n const panels = buildNavigationPanels();\n\n // Use the same color resolution as the rest of the system\n const dropdownColors = resolveColorsToCSS({\n dropdownMenuBackground: navigation.style?.dropdown?.background,\n dropdownMenuLinkColor: navigation.style?.dropdown?.link?.color,\n dropdownMenuLinkHoverColor: navigation.style?.dropdown?.link?.hoverColor,\n dropdownMenuLinkHoverBackground:\n navigation.style?.dropdown?.link?.hoverBackground,\n dropdownMenuTextColor: navigation.style?.dropdown?.textColor,\n });\n\n const dropdownMenuBackground = dropdownColors.dropdownMenuBackground;\n const dropdownMenuLinkColor = dropdownColors.dropdownMenuLinkColor;\n const dropdownMenuLinkHoverColor = dropdownColors.dropdownMenuLinkHoverColor;\n const dropdownMenuLinkHoverBackground =\n dropdownColors.dropdownMenuLinkHoverBackground || \"transparent\";\n const dropdownMenuTextColor = dropdownColors.dropdownMenuTextColor;\n\n const dropdownMenuBorder = useMemo(() => {\n if (!navigation.style?.dropdown?.border) return undefined;\n return resolveBorderToCSS(navigation.style.dropdown.border);\n }, [navigation.style?.dropdown?.border]);\n\n // Shadow is now handled via responsive CSS generation (generateResponsiveSpacingCSS)\n\n // Early return AFTER all hooks\n if (content.length === 0) return null;\n\n // Enhanced resolved colors for dropdown, using dropdown-specific text color if available\n const dropdownResolvedColors = {\n ...resolvedColors,\n text: dropdownMenuTextColor || resolvedColors.text,\n dropdownMenuLinkColor,\n };\n\n const useSameLayerMode = isSameLayer && !isMobileMenu;\n\n const dropdownStyles: React.CSSProperties = {\n backgroundColor: useSameLayerMode ? undefined : dropdownMenuBackground,\n color: dropdownMenuTextColor || dropdownMenuLinkColor,\n ...(useSameLayerMode ? {} : dropdownMenuBorder),\n };\n\n const linkHoverStyle = {\n \"--dropdown-link-hover-color\": dropdownMenuLinkHoverColor,\n \"--dropdown-link-hover-background\": dropdownMenuLinkHoverBackground,\n } as React.CSSProperties;\n\n return (\n <>\n <CSSTransition\n in={isOpen}\n nodeRef={nodeRef}\n timeout={useSameLayerMode ? 250 : getDropdownTimeout(isMobileMenu)}\n classNames={\n useSameLayerMode\n ? \"same-layer-dropdown\"\n : getDropdownClassNames(isMobileMenu)\n }\n unmountOnExit={!useSameLayerMode}\n >\n <div\n ref={nodeRef}\n className={`relative w-full navigation-dropdown-${dropdownId}`}\n style={{\n ...dropdownStyles,\n ...(useSameLayerMode\n ? { display: \"grid\", overflow: \"hidden\" }\n : { overflow: \"hidden\" }),\n zIndex: getDropdownZIndex(\n useSameLayerMode,\n navigation.style?.dropdown?.layer,\n ),\n }}\n >\n <div\n style={\n useSameLayerMode\n ? { overflow: \"hidden\", minHeight: 0 }\n : undefined\n }\n >\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n transform: `translateX(-${navigationStack.length * 100}%)`,\n transition: \"transform 300ms ease-in-out\",\n }}\n >\n {panels.map((panel, panelIndex) => {\n const isActive = panelIndex === navigationStack.length;\n const isToRight = panelIndex > navigationStack.length;\n\n const panelContent = (\n <>\n {panel.depth > 0 && (\n <button\n ref={\n panelIndex === navigationStack.length\n ? backButtonRef\n : null\n }\n onClick={(e) => {\n e.preventDefault();\n handleBack();\n }}\n type=\"button\"\n data-navigation-internal=\"true\"\n className=\"flex items-center px-3 py-2 text-sm rounded-md transition-colors mb-2\"\n style={{\n color:\n resolvedColors.dropdownMenuLinkColor ||\n resolvedColors.linkColor,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n className=\"mr-2\"\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n Back\n </button>\n )}\n\n <div className=\"flex flex-wrap gap-4\">\n {panel.content?.map((item: HeaderDropdownContent) => {\n if (item.type === \"section\") {\n return (\n <div key={item.id} className=\"flex-1 min-w-[200px]\">\n <DropdownContentItem\n content={item}\n resolvedColors={dropdownResolvedColors}\n onNavigate={handleNavigate}\n navigation={navigation}\n site={site}\n />\n </div>\n );\n }\n return (\n <div key={item.id} className=\"w-full\">\n <DropdownContentItem\n content={item}\n resolvedColors={dropdownResolvedColors}\n onNavigate={handleNavigate}\n navigation={navigation}\n site={site}\n />\n </div>\n );\n })}\n </div>\n </>\n );\n\n return (\n <nav\n key={panelIndex}\n className={`flex flex-col ${useSameLayerMode ? \"\" : \"p-2\"} dropdown-content-${dropdownId}`}\n style={{\n ...linkHoverStyle,\n width: \"100%\",\n flexShrink: 0,\n transform: isToRight\n ? \"translateX(30px)\"\n : \"translateX(0)\",\n opacity: isActive ? 1 : 0.95,\n transition:\n \"transform 300ms ease-in-out, opacity 300ms ease-in-out\",\n }}\n inert={!isActive ? true : undefined}\n >\n {containerContent ? (\n <div className=\"container mx-auto\">{panelContent}</div>\n ) : (\n panelContent\n )}\n </nav>\n );\n })}\n </div>\n </div>\n </div>\n </CSSTransition>\n </>\n );\n}\n"],"mappings":"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","names":["config","panels"]}
@@ -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 = [];
@@ -36,7 +39,7 @@ function generateFooterCSS(id, footer, resolvedColors) {
36
39
  ${resolvedColors.text ? `color: ${resolvedColors.text};` : ""}
37
40
  ${baseMargin ? `margin: ${baseMargin};` : ""}
38
41
  ${basePadding ? `padding: ${basePadding};` : ""}
39
- ${borderCSS ? `border: ${borderCSS};` : ""}
42
+ ${borderCSS ? `${borderCSS};` : ""}
40
43
  ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : ""}
41
44
  }
42
45
  `;
@@ -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\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 ${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 }\n `;\n cssBlocks.push(baseStyles);\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 // Section gap\n if (baseSectionGap) {\n cssBlocks.push(`\n .footer-${id} > .footer-section {\n gap: ${baseSectionGap};\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 \"items\" in section && Array.isArray(section.items);\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\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 ${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 }\n `;\n cssBlocks.push(baseStyles);\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 // Section gap\n if (baseSectionGap) {\n cssBlocks.push(`\n .footer-${id} > .footer-section {\n gap: ${baseSectionGap};\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 \"items\" in section && Array.isArray(section.items);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otl-core/next-navigation",
3
- "version": "1.1.36",
3
+ "version": "1.1.38",
4
4
  "type": "module",
5
5
  "description": "Reusable navigation components for OTL CMS",
6
6
  "main": "./dist/index.js",
@@ -48,13 +48,13 @@
48
48
  "tailwind-merge": "^3.3.1"
49
49
  },
50
50
  "dependencies": {
51
- "@otl-core/cms-utils": "^1.1.36",
52
- "@otl-core/style-utils": "^1.1.36"
51
+ "@otl-core/cms-utils": "^1.1.38",
52
+ "@otl-core/style-utils": "^1.1.38"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@eslint/eslintrc": "^3.3.1",
56
- "@otl-core/cms-types": "^1.1.36",
57
- "@otl-core/cms-utils": "^1.1.36",
56
+ "@otl-core/cms-types": "^1.1.38",
57
+ "@otl-core/cms-utils": "^1.1.38",
58
58
  "@radix-ui/react-focus-scope": "^1.1.7",
59
59
  "@radix-ui/react-slot": "^1.2.3",
60
60
  "@testing-library/jest-dom": "^6.9.1",