@otl-core/next-navigation 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.
- package/dist/components/icons/chevron-icon.d.ts +10 -0
- package/dist/components/icons/chevron-icon.js +30 -0
- package/dist/components/icons/chevron-icon.js.map +1 -0
- package/dist/components/icons/grid-icon.d.ts +12 -0
- package/dist/components/icons/grid-icon.js +95 -0
- package/dist/components/icons/grid-icon.js.map +1 -0
- package/dist/components/icons/hamburger-icon.d.ts +12 -0
- package/dist/components/icons/hamburger-icon.js +78 -0
- package/dist/components/icons/hamburger-icon.js.map +1 -0
- package/dist/components/icons/kebab-icon.d.ts +12 -0
- package/dist/components/icons/kebab-icon.js +81 -0
- package/dist/components/icons/kebab-icon.js.map +1 -0
- package/dist/components/icons/meatballs-icon.d.ts +12 -0
- package/dist/components/icons/meatballs-icon.js +81 -0
- package/dist/components/icons/meatballs-icon.js.map +1 -0
- package/dist/components/icons/plus-icon.d.ts +12 -0
- package/dist/components/icons/plus-icon.js +64 -0
- package/dist/components/icons/plus-icon.js.map +1 -0
- package/dist/components/icons/toggle-icon-map.d.ts +14 -0
- package/dist/components/icons/toggle-icon-map.js +22 -0
- package/dist/components/icons/toggle-icon-map.js.map +1 -0
- package/dist/components/icons/toggle-icon.d.ts +14 -0
- package/dist/components/icons/toggle-icon.js +39 -0
- package/dist/components/icons/toggle-icon.js.map +1 -0
- package/dist/components/items/animated-toggle-icon.d.ts +15 -0
- package/dist/components/items/animated-toggle-icon.js +39 -0
- package/dist/components/items/animated-toggle-icon.js.map +1 -0
- package/dist/components/items/dropdown-content-item.d.ts +12 -0
- package/dist/components/items/dropdown-content-item.js +223 -0
- package/dist/components/items/dropdown-content-item.js.map +1 -0
- package/dist/components/items/logo.d.ts +11 -0
- package/dist/components/items/logo.js +42 -0
- package/dist/components/items/logo.js.map +1 -0
- package/dist/components/mobile/mobile-menu-toggle.d.ts +12 -0
- package/dist/components/mobile/mobile-menu-toggle.js +53 -0
- package/dist/components/mobile/mobile-menu-toggle.js.map +1 -0
- package/dist/components/mobile/navigation-header-wrapper.d.ts +11 -0
- package/dist/components/mobile/navigation-header-wrapper.js +15 -0
- package/dist/components/mobile/navigation-header-wrapper.js.map +1 -0
- package/dist/components/navigation/dropdown.d.ts +19 -0
- package/dist/components/navigation/dropdown.js +258 -0
- package/dist/components/navigation/dropdown.js.map +1 -0
- package/dist/components/navigation/header.d.ts +13 -0
- package/dist/components/navigation/header.js +305 -0
- package/dist/components/navigation/header.js.map +1 -0
- package/dist/components/navigation/navbar.d.ts +21 -0
- package/dist/components/navigation/navbar.js +66 -0
- package/dist/components/navigation/navbar.js.map +1 -0
- package/dist/components/sections/navbar-sections.d.ts +23 -0
- package/dist/components/sections/navbar-sections.js +103 -0
- package/dist/components/sections/navbar-sections.js.map +1 -0
- package/dist/components/sections/navigation-item.d.ts +13 -0
- package/dist/components/sections/navigation-item.js +171 -0
- package/dist/components/sections/navigation-item.js.map +1 -0
- package/dist/components/ui/button.d.ts +14 -0
- package/dist/components/ui/button.js +51 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/context/navigation-context.d.ts +17 -0
- package/dist/context/navigation-context.js +93 -0
- package/dist/context/navigation-context.js.map +1 -0
- package/dist/index.d.ts +12 -86
- package/dist/index.js +19 -2077
- package/dist/index.js.map +1 -1
- package/dist/lib/footer.utils.d.ts +20 -0
- package/dist/lib/footer.utils.js +84 -0
- package/dist/lib/footer.utils.js.map +1 -0
- package/dist/lib/navigation.utils.d.ts +34 -0
- package/dist/lib/navigation.utils.js +387 -0
- package/dist/lib/navigation.utils.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HeaderSection, HeaderConfig, Site } from '@otl-core/cms-types';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface NavbarProps {
|
|
5
|
+
id: string;
|
|
6
|
+
className: string;
|
|
7
|
+
headerStyles: React.CSSProperties;
|
|
8
|
+
sortedSections: HeaderSection[];
|
|
9
|
+
navigation: HeaderConfig;
|
|
10
|
+
resolvedColors: Record<string, string | undefined>;
|
|
11
|
+
itemsShowClass: string;
|
|
12
|
+
togglerHideClass: string;
|
|
13
|
+
togglerSectionId: string;
|
|
14
|
+
site: Site;
|
|
15
|
+
mobileMenuId?: string;
|
|
16
|
+
containerContent?: boolean;
|
|
17
|
+
isSameLayer?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function Navbar({ id, className, headerStyles, sortedSections, navigation, resolvedColors, itemsShowClass, togglerHideClass, togglerSectionId, site, mobileMenuId, containerContent, isSameLayer, }: NavbarProps): ReactNode;
|
|
20
|
+
|
|
21
|
+
export { Navbar };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "@otl-core/style-utils";
|
|
3
|
+
import { NavbarSections } from "../sections/navbar-sections";
|
|
4
|
+
function Navbar({
|
|
5
|
+
id,
|
|
6
|
+
className,
|
|
7
|
+
headerStyles,
|
|
8
|
+
sortedSections,
|
|
9
|
+
navigation,
|
|
10
|
+
resolvedColors,
|
|
11
|
+
itemsShowClass,
|
|
12
|
+
togglerHideClass,
|
|
13
|
+
togglerSectionId,
|
|
14
|
+
site,
|
|
15
|
+
mobileMenuId,
|
|
16
|
+
containerContent = false,
|
|
17
|
+
isSameLayer = false
|
|
18
|
+
}) {
|
|
19
|
+
const dropdownLayer = navigation.style?.dropdown?.layer || "below";
|
|
20
|
+
function getNavbarZ() {
|
|
21
|
+
if (isSameLayer) return "";
|
|
22
|
+
return dropdownLayer === "above" ? "z-[1]" : "z-[9999]";
|
|
23
|
+
}
|
|
24
|
+
const navbarZ = getNavbarZ();
|
|
25
|
+
const sectionsContent = sortedSections.map((section) => /* @__PURE__ */ jsx(
|
|
26
|
+
NavbarSections,
|
|
27
|
+
{
|
|
28
|
+
section,
|
|
29
|
+
site,
|
|
30
|
+
navigation,
|
|
31
|
+
resolvedColors,
|
|
32
|
+
itemsShowClass,
|
|
33
|
+
togglerHideClass,
|
|
34
|
+
isTogglerSection: section.id === togglerSectionId,
|
|
35
|
+
mobileMenuId
|
|
36
|
+
},
|
|
37
|
+
section.id
|
|
38
|
+
));
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
"div",
|
|
41
|
+
{
|
|
42
|
+
className: cn(
|
|
43
|
+
`navbar-${id}`,
|
|
44
|
+
`relative flex items-center justify-between`,
|
|
45
|
+
navbarZ,
|
|
46
|
+
!containerContent && `navbar-inner-${id}`,
|
|
47
|
+
className
|
|
48
|
+
),
|
|
49
|
+
style: isSameLayer ? void 0 : headerStyles,
|
|
50
|
+
children: containerContent ? /* @__PURE__ */ jsx(
|
|
51
|
+
"div",
|
|
52
|
+
{
|
|
53
|
+
className: cn(
|
|
54
|
+
"container mx-auto flex items-center justify-between w-full",
|
|
55
|
+
`navbar-inner-${id}`
|
|
56
|
+
),
|
|
57
|
+
children: sectionsContent
|
|
58
|
+
}
|
|
59
|
+
) : sectionsContent
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
Navbar
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=navbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/navigation/navbar.tsx"],"sourcesContent":["import { Site, HeaderConfig, HeaderSection } from \"@otl-core/cms-types\";\nimport { cn } from \"@otl-core/style-utils\";\nimport { ReactNode } from \"react\";\nimport { NavbarSections } from \"../sections/navbar-sections\";\n\ninterface NavbarProps {\n id: string;\n className: string;\n headerStyles: React.CSSProperties;\n sortedSections: HeaderSection[];\n navigation: HeaderConfig;\n resolvedColors: Record<string, string | undefined>;\n itemsShowClass: string;\n togglerHideClass: string;\n togglerSectionId: string;\n site: Site;\n mobileMenuId?: string;\n containerContent?: boolean;\n isSameLayer?: boolean;\n}\n\nexport function Navbar({\n id,\n className,\n headerStyles,\n sortedSections,\n navigation,\n resolvedColors,\n itemsShowClass,\n togglerHideClass,\n togglerSectionId,\n site,\n mobileMenuId,\n containerContent = false,\n isSameLayer = false,\n}: NavbarProps): ReactNode {\n const dropdownLayer = navigation.style?.dropdown?.layer || \"below\";\n\n function getNavbarZ(): string {\n if (isSameLayer) return \"\";\n return dropdownLayer === \"above\" ? \"z-[1]\" : \"z-[9999]\";\n }\n const navbarZ = getNavbarZ();\n\n const sectionsContent = sortedSections.map((section) => (\n <NavbarSections\n key={section.id}\n section={section}\n site={site}\n navigation={navigation}\n resolvedColors={resolvedColors}\n itemsShowClass={itemsShowClass}\n togglerHideClass={togglerHideClass}\n isTogglerSection={section.id === togglerSectionId}\n mobileMenuId={mobileMenuId}\n />\n ));\n\n return (\n <div\n className={cn(\n `navbar-${id}`,\n `relative flex items-center justify-between`,\n navbarZ,\n !containerContent && `navbar-inner-${id}`,\n className,\n )}\n style={isSameLayer ? undefined : headerStyles}\n >\n {containerContent ? (\n <div\n className={cn(\n \"container mx-auto flex items-center justify-between w-full\",\n `navbar-inner-${id}`,\n )}\n >\n {sectionsContent}\n </div>\n ) : (\n sectionsContent\n )}\n </div>\n );\n}\n"],"mappings":"AA6CI;AA5CJ,SAAS,UAAU;AAEnB,SAAS,sBAAsB;AAkBxB,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB,cAAc;AAChB,GAA2B;AACzB,QAAM,gBAAgB,WAAW,OAAO,UAAU,SAAS;AAE3D,WAAS,aAAqB;AAC5B,QAAI,YAAa,QAAO;AACxB,WAAO,kBAAkB,UAAU,UAAU;AAAA,EAC/C;AACA,QAAM,UAAU,WAAW;AAE3B,QAAM,kBAAkB,eAAe,IAAI,CAAC,YAC1C;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,kBAAkB,QAAQ,OAAO;AAAA,MACjC;AAAA;AAAA,IARK,QAAQ;AAAA,EASf,CACD;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT,UAAU,EAAE;AAAA,QACZ;AAAA,QACA;AAAA,QACA,CAAC,oBAAoB,gBAAgB,EAAE;AAAA,QACvC;AAAA,MACF;AAAA,MACA,OAAO,cAAc,SAAY;AAAA,MAEhC,6BACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,gBAAgB,EAAE;AAAA,UACpB;AAAA,UAEC;AAAA;AAAA,MACH,IAEA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HeaderSection, HeaderConfig, Site } from '@otl-core/cms-types';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
interface NavbarSectionProps {
|
|
5
|
+
section: HeaderSection;
|
|
6
|
+
logo?: {
|
|
7
|
+
text?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
};
|
|
13
|
+
navigation?: HeaderConfig;
|
|
14
|
+
resolvedColors: Record<string, string | undefined>;
|
|
15
|
+
itemsShowClass?: string;
|
|
16
|
+
togglerHideClass?: string;
|
|
17
|
+
isTogglerSection?: boolean;
|
|
18
|
+
site: Site;
|
|
19
|
+
mobileMenuId?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const NavbarSections: React__default.FC<NavbarSectionProps>;
|
|
22
|
+
|
|
23
|
+
export { NavbarSections };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { resolveItemVisibility } from "../../lib/navigation.utils";
|
|
4
|
+
import { MobileMenuToggle } from "../mobile/mobile-menu-toggle";
|
|
5
|
+
import { NavigationItem } from "./navigation-item";
|
|
6
|
+
const NavbarSections = ({
|
|
7
|
+
section,
|
|
8
|
+
navigation,
|
|
9
|
+
resolvedColors,
|
|
10
|
+
itemsShowClass,
|
|
11
|
+
togglerHideClass,
|
|
12
|
+
isTogglerSection,
|
|
13
|
+
site,
|
|
14
|
+
mobileMenuId
|
|
15
|
+
}) => {
|
|
16
|
+
const hasLogo = useMemo(() => {
|
|
17
|
+
return section?.items?.some(
|
|
18
|
+
(item) => item.type === "logo"
|
|
19
|
+
);
|
|
20
|
+
}, [section.items]);
|
|
21
|
+
const sectionStyle = useMemo(() => {
|
|
22
|
+
let flexValue = section.flex;
|
|
23
|
+
if (flexValue === "0" || flexValue === void 0) {
|
|
24
|
+
if (hasLogo) {
|
|
25
|
+
flexValue = "0 0 auto";
|
|
26
|
+
} else {
|
|
27
|
+
flexValue = "0 1 auto";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
flex: flexValue,
|
|
32
|
+
justifyContent: section.justify || "flex-start",
|
|
33
|
+
alignItems: section.align || "center",
|
|
34
|
+
gap: section.gap || "0",
|
|
35
|
+
// For non-logo sections, ensure they can shrink below content width
|
|
36
|
+
...!hasLogo ? { minWidth: 0 } : {}
|
|
37
|
+
};
|
|
38
|
+
}, [section, hasLogo]);
|
|
39
|
+
const shouldHide = useMemo(() => {
|
|
40
|
+
if (!section.hideWhenEmpty) return false;
|
|
41
|
+
const hasLogo2 = section.items?.some(
|
|
42
|
+
(item) => item.type === "logo"
|
|
43
|
+
);
|
|
44
|
+
if (hasLogo2) return false;
|
|
45
|
+
const hasAlwaysVisibleItems = section.items?.some(
|
|
46
|
+
(item) => resolveItemVisibility(item) === "navbar-only" || resolveItemVisibility(item) === "both"
|
|
47
|
+
);
|
|
48
|
+
if (hasAlwaysVisibleItems) return false;
|
|
49
|
+
const collapsibleItems = section.items?.filter(
|
|
50
|
+
(item) => item.type !== "logo" && resolveItemVisibility(item) !== "navbar-only" && resolveItemVisibility(item) !== "both"
|
|
51
|
+
);
|
|
52
|
+
if (collapsibleItems && collapsibleItems.length > 0) return false;
|
|
53
|
+
if (isTogglerSection) return false;
|
|
54
|
+
return true;
|
|
55
|
+
}, [section.hideWhenEmpty, section.items, isTogglerSection]);
|
|
56
|
+
const sectionVisibilityClass = useMemo(() => {
|
|
57
|
+
if (!section.hideWhenEmpty) return "";
|
|
58
|
+
const alwaysVisibleItems = section.items?.filter(
|
|
59
|
+
(item) => item.type === "logo" || resolveItemVisibility(item) === "navbar-only" || resolveItemVisibility(item) === "both"
|
|
60
|
+
);
|
|
61
|
+
if (alwaysVisibleItems && alwaysVisibleItems.length > 0) return "";
|
|
62
|
+
return itemsShowClass;
|
|
63
|
+
}, [section.hideWhenEmpty, section.items, itemsShowClass]);
|
|
64
|
+
if (shouldHide) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return /* @__PURE__ */ jsxs(
|
|
68
|
+
"div",
|
|
69
|
+
{
|
|
70
|
+
className: `flex flex-row ${sectionVisibilityClass}`,
|
|
71
|
+
style: sectionStyle,
|
|
72
|
+
"data-section-id": section.id,
|
|
73
|
+
children: [
|
|
74
|
+
section.items?.map((item) => {
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
NavigationItem,
|
|
77
|
+
{
|
|
78
|
+
item,
|
|
79
|
+
navigation,
|
|
80
|
+
resolvedColors,
|
|
81
|
+
itemsShowClass,
|
|
82
|
+
site
|
|
83
|
+
},
|
|
84
|
+
item.id
|
|
85
|
+
);
|
|
86
|
+
}),
|
|
87
|
+
isTogglerSection && navigation && mobileMenuId && /* @__PURE__ */ jsx("div", { className: `flex flex-shrink-0 ${togglerHideClass}`, children: /* @__PURE__ */ jsx(
|
|
88
|
+
MobileMenuToggle,
|
|
89
|
+
{
|
|
90
|
+
navigation,
|
|
91
|
+
resolvedColors,
|
|
92
|
+
toggleId: `toggler-${section.id}`,
|
|
93
|
+
mobileMenuId
|
|
94
|
+
}
|
|
95
|
+
) })
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
export {
|
|
101
|
+
NavbarSections
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=navbar-sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/sections/navbar-sections.tsx"],"sourcesContent":["import {\n Site,\n HeaderConfig,\n HeaderNavigationItem,\n HeaderSection,\n} from \"@otl-core/cms-types\";\nimport React, { useMemo } from \"react\";\nimport { resolveItemVisibility } from \"../../lib/navigation.utils\";\nimport { MobileMenuToggle } from \"../mobile/mobile-menu-toggle\";\nimport { NavigationItem } from \"./navigation-item\";\n\ninterface NavbarSectionProps {\n section: HeaderSection;\n logo?: {\n text?: string;\n url?: string;\n alt?: string;\n width?: number;\n height?: number;\n };\n navigation?: HeaderConfig;\n resolvedColors: Record<string, string | undefined>;\n itemsShowClass?: string;\n togglerHideClass?: string;\n isTogglerSection?: boolean;\n site: Site;\n mobileMenuId?: string;\n}\n\nexport const NavbarSections: React.FC<NavbarSectionProps> = ({\n section,\n navigation,\n resolvedColors,\n itemsShowClass,\n togglerHideClass,\n isTogglerSection,\n site,\n mobileMenuId,\n}) => {\n // Check if this section contains a logo\n const hasLogo = useMemo(() => {\n return section?.items?.some(\n (item: HeaderNavigationItem) => item.type === \"logo\",\n );\n }, [section.items]);\n\n const sectionStyle: React.CSSProperties = useMemo(() => {\n let flexValue = section.flex;\n\n // Smart flex handling for better logo and navigation behavior\n if (flexValue === \"0\" || flexValue === undefined) {\n if (hasLogo) {\n // Logo section: Don't shrink, take natural size\n flexValue = \"0 0 auto\";\n } else {\n // Other sections with flex: 0 - allow shrinking if needed\n flexValue = \"0 1 auto\";\n }\n }\n\n return {\n flex: flexValue,\n justifyContent: section.justify || \"flex-start\",\n alignItems: section.align || \"center\",\n gap: section.gap || \"0\",\n // For non-logo sections, ensure they can shrink below content width\n ...(!hasLogo ? { minWidth: 0 } : {}),\n };\n }, [section, hasLogo]);\n\n // Check if section should be hidden when empty\n const shouldHide = useMemo(() => {\n if (!section.hideWhenEmpty) return false;\n\n // Never hide if there's a logo - logos are always visible\n const hasLogo = section.items?.some(\n (item: HeaderNavigationItem) => item.type === \"logo\",\n );\n if (hasLogo) return false;\n\n // Never hide if there are items with visibility: navbar-only or both - they're always visible\n const hasAlwaysVisibleItems = section.items?.some(\n (item: HeaderNavigationItem) =>\n resolveItemVisibility(item) === \"navbar-only\" ||\n resolveItemVisibility(item) === \"both\",\n );\n if (hasAlwaysVisibleItems) return false;\n\n // Check if there are any collapsible items (responsive or mobile-only)\n const collapsibleItems = section.items?.filter(\n (item: HeaderNavigationItem) =>\n item.type !== \"logo\" &&\n resolveItemVisibility(item) !== \"navbar-only\" &&\n resolveItemVisibility(item) !== \"both\",\n );\n\n // If there are collapsible items, section is not empty\n if (collapsibleItems && collapsibleItems.length > 0) return false;\n\n // Don't hide if this is the toggler section (toggler button should show)\n if (isTogglerSection) return false;\n\n return true;\n }, [section.hideWhenEmpty, section.items, isTogglerSection]);\n\n // Apply the same visibility class as items if hideWhenEmpty is enabled\n const sectionVisibilityClass = useMemo(() => {\n if (!section.hideWhenEmpty) return \"\";\n\n // Check if there are any items that are always visible (logos or navbar-only/both)\n const alwaysVisibleItems = section.items?.filter(\n (item: HeaderNavigationItem) =>\n item.type === \"logo\" ||\n resolveItemVisibility(item) === \"navbar-only\" ||\n resolveItemVisibility(item) === \"both\",\n );\n\n // If there are always visible items, don't apply visibility class\n if (alwaysVisibleItems && alwaysVisibleItems.length > 0) return \"\";\n\n // If all items collapse, apply the same visibility class\n return itemsShowClass;\n }, [section.hideWhenEmpty, section.items, itemsShowClass]);\n\n if (shouldHide) {\n return null;\n }\n\n return (\n <div\n className={`flex flex-row ${sectionVisibilityClass}`}\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section.items?.map((item: HeaderNavigationItem) => {\n return (\n <NavigationItem\n key={item.id}\n item={item}\n navigation={navigation}\n resolvedColors={resolvedColors}\n itemsShowClass={itemsShowClass}\n site={site}\n />\n );\n })}\n {isTogglerSection && navigation && mobileMenuId && (\n <div className={`flex flex-shrink-0 ${togglerHideClass}`}>\n <MobileMenuToggle\n navigation={navigation}\n resolvedColors={resolvedColors}\n toggleId={`toggler-${section.id}`}\n mobileMenuId={mobileMenuId}\n />\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"AAiII,SAOM,KAPN;AA3HJ,SAAgB,eAAe;AAC/B,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AACjC,SAAS,sBAAsB;AAoBxB,MAAM,iBAA+C,CAAC;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AAEJ,QAAM,UAAU,QAAQ,MAAM;AAC5B,WAAO,SAAS,OAAO;AAAA,MACrB,CAAC,SAA+B,KAAK,SAAS;AAAA,IAChD;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,QAAM,eAAoC,QAAQ,MAAM;AACtD,QAAI,YAAY,QAAQ;AAGxB,QAAI,cAAc,OAAO,cAAc,QAAW;AAChD,UAAI,SAAS;AAEX,oBAAY;AAAA,MACd,OAAO;AAEL,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,gBAAgB,QAAQ,WAAW;AAAA,MACnC,YAAY,QAAQ,SAAS;AAAA,MAC7B,KAAK,QAAQ,OAAO;AAAA;AAAA,MAEpB,GAAI,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,SAAS,OAAO,CAAC;AAGrB,QAAM,aAAa,QAAQ,MAAM;AAC/B,QAAI,CAAC,QAAQ,cAAe,QAAO;AAGnC,UAAMA,WAAU,QAAQ,OAAO;AAAA,MAC7B,CAAC,SAA+B,KAAK,SAAS;AAAA,IAChD;AACA,QAAIA,SAAS,QAAO;AAGpB,UAAM,wBAAwB,QAAQ,OAAO;AAAA,MAC3C,CAAC,SACC,sBAAsB,IAAI,MAAM,iBAChC,sBAAsB,IAAI,MAAM;AAAA,IACpC;AACA,QAAI,sBAAuB,QAAO;AAGlC,UAAM,mBAAmB,QAAQ,OAAO;AAAA,MACtC,CAAC,SACC,KAAK,SAAS,UACd,sBAAsB,IAAI,MAAM,iBAChC,sBAAsB,IAAI,MAAM;AAAA,IACpC;AAGA,QAAI,oBAAoB,iBAAiB,SAAS,EAAG,QAAO;AAG5D,QAAI,iBAAkB,QAAO;AAE7B,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,eAAe,QAAQ,OAAO,gBAAgB,CAAC;AAG3D,QAAM,yBAAyB,QAAQ,MAAM;AAC3C,QAAI,CAAC,QAAQ,cAAe,QAAO;AAGnC,UAAM,qBAAqB,QAAQ,OAAO;AAAA,MACxC,CAAC,SACC,KAAK,SAAS,UACd,sBAAsB,IAAI,MAAM,iBAChC,sBAAsB,IAAI,MAAM;AAAA,IACpC;AAGA,QAAI,sBAAsB,mBAAmB,SAAS,EAAG,QAAO;AAGhE,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,eAAe,QAAQ,OAAO,cAAc,CAAC;AAEzD,MAAI,YAAY;AACd,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,iBAAiB,sBAAsB;AAAA,MAClD,OAAO;AAAA,MACP,mBAAiB,QAAQ;AAAA,MAExB;AAAA,gBAAQ,OAAO,IAAI,CAAC,SAA+B;AAClD,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA;AAAA,YALK,KAAK;AAAA,UAMZ;AAAA,QAEJ,CAAC;AAAA,QACA,oBAAoB,cAAc,gBACjC,oBAAC,SAAI,WAAW,sBAAsB,gBAAgB,IACpD;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA,UAAU,WAAW,QAAQ,EAAE;AAAA,YAC/B;AAAA;AAAA,QACF,GACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":["hasLogo"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HeaderNavigationItem, HeaderConfig, Site } from '@otl-core/cms-types';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
interface NavigationItemProps {
|
|
5
|
+
item: HeaderNavigationItem;
|
|
6
|
+
navigation?: HeaderConfig;
|
|
7
|
+
resolvedColors: Record<string, string | undefined>;
|
|
8
|
+
itemsShowClass?: string;
|
|
9
|
+
site: Site;
|
|
10
|
+
}
|
|
11
|
+
declare const NavigationItem: React__default.FC<NavigationItemProps>;
|
|
12
|
+
|
|
13
|
+
export { NavigationItem };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useNavigation } from "../../context/navigation-context";
|
|
5
|
+
import {
|
|
6
|
+
getLocalizedString,
|
|
7
|
+
getVisibilityClass,
|
|
8
|
+
parseMarkdownToHTML
|
|
9
|
+
} from "../../lib/navigation.utils";
|
|
10
|
+
import { ChevronIcon } from "../icons/chevron-icon";
|
|
11
|
+
import { Logo } from "../items/logo";
|
|
12
|
+
import { Button } from "../ui/button";
|
|
13
|
+
const variantMap = {
|
|
14
|
+
primary: "default",
|
|
15
|
+
secondary: "secondary",
|
|
16
|
+
outline: "outline",
|
|
17
|
+
ghost: "ghost"
|
|
18
|
+
};
|
|
19
|
+
const sizeMap = {
|
|
20
|
+
sm: "sm",
|
|
21
|
+
md: "default",
|
|
22
|
+
lg: "lg"
|
|
23
|
+
};
|
|
24
|
+
const NavigationItem = ({
|
|
25
|
+
item,
|
|
26
|
+
navigation,
|
|
27
|
+
resolvedColors,
|
|
28
|
+
itemsShowClass,
|
|
29
|
+
site
|
|
30
|
+
}) => {
|
|
31
|
+
const label = getLocalizedString(item.label, site);
|
|
32
|
+
const visibilityClass = getVisibilityClass(item, itemsShowClass);
|
|
33
|
+
if (item.type === "logo") {
|
|
34
|
+
if (!navigation?.logo) return null;
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
Logo,
|
|
37
|
+
{
|
|
38
|
+
navigation,
|
|
39
|
+
siteName: navigation.logo.alt || "Logo",
|
|
40
|
+
logoTextColor: resolvedColors.logoText
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (item.type === "link") {
|
|
45
|
+
const config = item.config;
|
|
46
|
+
const linkStyle = {
|
|
47
|
+
color: resolvedColors.linkColor
|
|
48
|
+
};
|
|
49
|
+
return /* @__PURE__ */ jsxs(
|
|
50
|
+
Link,
|
|
51
|
+
{
|
|
52
|
+
href: config.href,
|
|
53
|
+
style: linkStyle,
|
|
54
|
+
target: config.external ? "_blank" : void 0,
|
|
55
|
+
rel: config.external ? "noopener noreferrer" : void 0,
|
|
56
|
+
className: `${visibilityClass} items-center whitespace-nowrap`,
|
|
57
|
+
children: [
|
|
58
|
+
config.icon && /* @__PURE__ */ jsx("span", { children: config.icon }),
|
|
59
|
+
label
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (item.type === "button") {
|
|
65
|
+
const config = item.config;
|
|
66
|
+
return /* @__PURE__ */ jsx("div", { className: `${visibilityClass} items-center`, children: /* @__PURE__ */ jsx(
|
|
67
|
+
Button,
|
|
68
|
+
{
|
|
69
|
+
asChild: true,
|
|
70
|
+
variant: config.variant ? variantMap[config.variant] : "default",
|
|
71
|
+
size: config.size ? sizeMap[config.size] : "default",
|
|
72
|
+
className: "whitespace-nowrap",
|
|
73
|
+
style: {
|
|
74
|
+
fontSize: `var(--btn-font-${config.size || "md"})`
|
|
75
|
+
},
|
|
76
|
+
children: /* @__PURE__ */ jsxs(
|
|
77
|
+
Link,
|
|
78
|
+
{
|
|
79
|
+
href: config.href,
|
|
80
|
+
target: config.external ? "_blank" : void 0,
|
|
81
|
+
rel: config.external ? "noopener noreferrer" : void 0,
|
|
82
|
+
children: [
|
|
83
|
+
config.icon && /* @__PURE__ */ jsx("span", { children: config.icon }),
|
|
84
|
+
label
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
) });
|
|
90
|
+
}
|
|
91
|
+
if (item.type === "dropdown") {
|
|
92
|
+
const { toggleDropdown, activeDropdown } = useNavigation();
|
|
93
|
+
const isOpen = activeDropdown === item.id;
|
|
94
|
+
const linkStyle = {
|
|
95
|
+
color: resolvedColors.linkColor
|
|
96
|
+
};
|
|
97
|
+
return /* @__PURE__ */ jsxs(
|
|
98
|
+
"button",
|
|
99
|
+
{
|
|
100
|
+
onClick: () => toggleDropdown(item.id),
|
|
101
|
+
style: linkStyle,
|
|
102
|
+
"aria-expanded": isOpen,
|
|
103
|
+
"aria-haspopup": "true",
|
|
104
|
+
className: `${visibilityClass} items-center gap-1 whitespace-nowrap`,
|
|
105
|
+
children: [
|
|
106
|
+
label,
|
|
107
|
+
/* @__PURE__ */ jsx(ChevronIcon, { isOpen, size: 16 })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (item.type === "markdown") {
|
|
113
|
+
const config = item.config;
|
|
114
|
+
const content = getLocalizedString(config.content, site);
|
|
115
|
+
const html = parseMarkdownToHTML(content);
|
|
116
|
+
const textStyle = {};
|
|
117
|
+
if (config.color) {
|
|
118
|
+
if (typeof config.color === "string") {
|
|
119
|
+
textStyle.color = config.color;
|
|
120
|
+
} else if (config.color.type === "custom" && config.color.value) {
|
|
121
|
+
textStyle.color = typeof config.color.value === "string" ? config.color.value : config.color.value.background;
|
|
122
|
+
} else if (config.color.type === "variable" && config.color.value) {
|
|
123
|
+
textStyle.color = `var(${config.color.value})`;
|
|
124
|
+
} else if (config.color.type === "theme" && config.color.value) {
|
|
125
|
+
textStyle.color = `var(--${config.color.value})`;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
textStyle.color = resolvedColors.text;
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
134
|
+
className: `${visibilityClass} items-center whitespace-nowrap [&_*]:text-inherit`,
|
|
135
|
+
style: textStyle
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (item.type === "image") {
|
|
140
|
+
const config = item.config;
|
|
141
|
+
if (!config.src) return null;
|
|
142
|
+
const imgElement = /* @__PURE__ */ jsx(
|
|
143
|
+
"img",
|
|
144
|
+
{
|
|
145
|
+
src: config.src,
|
|
146
|
+
alt: config.alt || "",
|
|
147
|
+
style: {
|
|
148
|
+
display: "block",
|
|
149
|
+
width: config.width || "auto",
|
|
150
|
+
height: config.height || "auto",
|
|
151
|
+
objectFit: config.objectFit || "contain"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
const wrapped = config.href ? /* @__PURE__ */ jsx(
|
|
156
|
+
Link,
|
|
157
|
+
{
|
|
158
|
+
href: config.href,
|
|
159
|
+
target: config.external ? "_blank" : void 0,
|
|
160
|
+
rel: config.external ? "noopener noreferrer" : void 0,
|
|
161
|
+
children: imgElement
|
|
162
|
+
}
|
|
163
|
+
) : imgElement;
|
|
164
|
+
return /* @__PURE__ */ jsx("div", { className: `${visibilityClass} items-center`, children: wrapped });
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
};
|
|
168
|
+
export {
|
|
169
|
+
NavigationItem
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=navigation-item.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/sections/navigation-item.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Site,\n HeaderConfig,\n HeaderNavigationItem,\n HeaderNavigationItemButtonConfig,\n HeaderNavigationItemImageConfig,\n HeaderNavigationItemLinkConfig,\n HeaderNavigationItemMarkdownConfig,\n} from \"@otl-core/cms-types\";\nimport Link from \"next/link\";\nimport React from \"react\";\nimport { useNavigation } from \"../../context/navigation-context\";\nimport {\n getLocalizedString,\n getVisibilityClass,\n parseMarkdownToHTML,\n} from \"../../lib/navigation.utils\";\nimport { ChevronIcon } from \"../icons/chevron-icon\";\nimport { Logo } from \"../items/logo\";\nimport { Button } from \"../ui/button\";\n\ninterface NavigationItemProps {\n item: HeaderNavigationItem;\n navigation?: HeaderConfig;\n resolvedColors: Record<string, string | undefined>;\n itemsShowClass?: string;\n site: Site;\n}\n\n// Map button variants to Button component variants\nconst variantMap: Record<\n string,\n \"default\" | \"secondary\" | \"outline\" | \"ghost\"\n> = {\n primary: \"default\",\n secondary: \"secondary\",\n outline: \"outline\",\n ghost: \"ghost\",\n};\n\n// Map button sizes to Button component sizes\nconst sizeMap: Record<string, \"default\" | \"sm\" | \"lg\"> = {\n sm: \"sm\",\n md: \"default\",\n lg: \"lg\",\n};\n\nexport const NavigationItem: React.FC<NavigationItemProps> = ({\n item,\n navigation,\n resolvedColors,\n itemsShowClass,\n site,\n}) => {\n const label = getLocalizedString(item.label, site);\n\n const visibilityClass = getVisibilityClass(item, itemsShowClass);\n\n // Render logo\n if (item.type === \"logo\") {\n if (!navigation?.logo) return null;\n return (\n <Logo\n navigation={navigation}\n siteName={navigation.logo.alt || \"Logo\"}\n logoTextColor={resolvedColors.logoText}\n />\n );\n }\n\n if (item.type === \"link\") {\n const config = item.config as HeaderNavigationItemLinkConfig;\n const linkStyle: React.CSSProperties = {\n color: resolvedColors.linkColor,\n };\n\n return (\n <Link\n href={config.href}\n style={linkStyle}\n target={config.external ? \"_blank\" : undefined}\n rel={config.external ? \"noopener noreferrer\" : undefined}\n className={`${visibilityClass} items-center whitespace-nowrap`}\n >\n {config.icon && <span>{config.icon}</span>}\n {label}\n </Link>\n );\n }\n\n // Render button — wrapped in a div to avoid CSS specificity conflict\n // between Button's base `inline-flex` and visibility `hidden` classes\n if (item.type === \"button\") {\n const config = item.config as HeaderNavigationItemButtonConfig;\n\n return (\n <div className={`${visibilityClass} items-center`}>\n <Button\n asChild\n variant={config.variant ? variantMap[config.variant] : \"default\"}\n size={config.size ? sizeMap[config.size] : \"default\"}\n className=\"whitespace-nowrap\"\n style={{\n fontSize: `var(--btn-font-${config.size || \"md\"})`,\n }}\n >\n <Link\n href={config.href}\n target={config.external ? \"_blank\" : undefined}\n rel={config.external ? \"noopener noreferrer\" : undefined}\n >\n {config.icon && <span>{config.icon}</span>}\n {label}\n </Link>\n </Button>\n </div>\n );\n }\n\n // Render dropdown trigger\n if (item.type === \"dropdown\") {\n const { toggleDropdown, activeDropdown } = useNavigation();\n const isOpen = activeDropdown === item.id;\n\n const linkStyle: React.CSSProperties = {\n color: resolvedColors.linkColor,\n };\n\n return (\n <button\n onClick={() => toggleDropdown(item.id)}\n style={linkStyle}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n className={`${visibilityClass} items-center gap-1 whitespace-nowrap`}\n >\n {label}\n <ChevronIcon isOpen={isOpen} size={16} />\n </button>\n );\n }\n\n // Render markdown\n if (item.type === \"markdown\") {\n const config = item.config as HeaderNavigationItemMarkdownConfig;\n const content = getLocalizedString(config.content, site);\n const html = parseMarkdownToHTML(content);\n\n const textStyle: React.CSSProperties = {};\n if (config.color) {\n if (typeof config.color === \"string\") {\n textStyle.color = config.color;\n } else if (config.color.type === \"custom\" && config.color.value) {\n textStyle.color =\n typeof config.color.value === \"string\"\n ? config.color.value\n : config.color.value.background;\n } else if (config.color.type === \"variable\" && config.color.value) {\n textStyle.color = `var(${config.color.value})`;\n } else if (config.color.type === \"theme\" && config.color.value) {\n textStyle.color = `var(--${config.color.value})`;\n }\n } else {\n textStyle.color = resolvedColors.text;\n }\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: html }}\n className={`${visibilityClass} items-center whitespace-nowrap [&_*]:text-inherit`}\n style={textStyle}\n />\n );\n }\n\n // Render image\n if (item.type === \"image\") {\n const config = item.config as HeaderNavigationItemImageConfig;\n if (!config.src) return null;\n\n const imgElement = (\n <img\n src={config.src}\n alt={config.alt || \"\"}\n style={{\n display: \"block\",\n width: config.width || \"auto\",\n height: config.height || \"auto\",\n objectFit: config.objectFit || \"contain\",\n }}\n />\n );\n\n const wrapped = config.href ? (\n <Link\n href={config.href}\n target={config.external ? \"_blank\" : undefined}\n rel={config.external ? \"noopener noreferrer\" : undefined}\n >\n {imgElement}\n </Link>\n ) : (\n imgElement\n );\n\n return <div className={`${visibilityClass} items-center`}>{wrapped}</div>;\n }\n\n return null;\n};\n"],"mappings":"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","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?: "link" | "default" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
|
|
8
|
+
size?: "default" | "sm" | "lg" | "icon" | 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,51 @@
|
|
|
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-surface 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
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
19
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
20
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
21
|
+
icon: "size-9"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
size: "default"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
function Button({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
asChild = false,
|
|
35
|
+
...props
|
|
36
|
+
}) {
|
|
37
|
+
const Comp = asChild ? Slot : "button";
|
|
38
|
+
return /* @__PURE__ */ jsx(
|
|
39
|
+
Comp,
|
|
40
|
+
{
|
|
41
|
+
"data-slot": "button",
|
|
42
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
43
|
+
...props
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
Button,
|
|
49
|
+
buttonVariants
|
|
50
|
+
};
|
|
51
|
+
//# 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-surface 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 default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\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":"AAkDI;AAjDJ,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,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;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":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
interface NavigationContextType {
|
|
5
|
+
headerRef: React__default.RefObject<HTMLElement | null>;
|
|
6
|
+
isDropdownOpen: boolean;
|
|
7
|
+
activeDropdown: string | null;
|
|
8
|
+
openDropdown: (dropdownId: string) => void;
|
|
9
|
+
closeDropdown: () => void;
|
|
10
|
+
toggleDropdown: (dropdownId: string) => void;
|
|
11
|
+
}
|
|
12
|
+
declare function NavigationProvider({ children, }: {
|
|
13
|
+
children: React__default.ReactNode;
|
|
14
|
+
}): react_jsx_runtime.JSX.Element;
|
|
15
|
+
declare function useNavigation(): NavigationContextType;
|
|
16
|
+
|
|
17
|
+
export { NavigationProvider, useNavigation };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { usePathname } from "next/navigation";
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
const NavigationContext = createContext(
|
|
14
|
+
void 0
|
|
15
|
+
);
|
|
16
|
+
function NavigationProvider({
|
|
17
|
+
children
|
|
18
|
+
}) {
|
|
19
|
+
const [activeDropdown, setActiveDropdown] = useState(null);
|
|
20
|
+
const headerRef = useRef(null);
|
|
21
|
+
const pathname = usePathname();
|
|
22
|
+
const openDropdown = useCallback(
|
|
23
|
+
(dropdownId) => setActiveDropdown(dropdownId),
|
|
24
|
+
[]
|
|
25
|
+
);
|
|
26
|
+
const closeDropdown = useCallback(() => setActiveDropdown(null), []);
|
|
27
|
+
const toggleDropdown = useCallback(
|
|
28
|
+
(dropdownId) => setActiveDropdown((prev) => prev === dropdownId ? null : dropdownId),
|
|
29
|
+
[]
|
|
30
|
+
);
|
|
31
|
+
const prevPathname = useRef(pathname);
|
|
32
|
+
if (pathname !== prevPathname.current) {
|
|
33
|
+
prevPathname.current = pathname;
|
|
34
|
+
if (activeDropdown) {
|
|
35
|
+
setActiveDropdown(null);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const handleEscape = (e) => {
|
|
40
|
+
if (e.key === "Escape" && activeDropdown) {
|
|
41
|
+
closeDropdown();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
document.addEventListener("keydown", handleEscape);
|
|
45
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
46
|
+
}, [activeDropdown, closeDropdown]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!activeDropdown) return;
|
|
49
|
+
const handleClickOutside = (e) => {
|
|
50
|
+
const target = e.target;
|
|
51
|
+
if (target.closest("[data-navigation-internal]")) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!headerRef.current?.contains(target)) {
|
|
55
|
+
closeDropdown();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
document.addEventListener("click", handleClickOutside, true);
|
|
59
|
+
return () => {
|
|
60
|
+
document.removeEventListener("click", handleClickOutside, true);
|
|
61
|
+
};
|
|
62
|
+
}, [activeDropdown, closeDropdown]);
|
|
63
|
+
const isDropdownOpen = useMemo(
|
|
64
|
+
() => activeDropdown !== null,
|
|
65
|
+
[activeDropdown]
|
|
66
|
+
);
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
NavigationContext.Provider,
|
|
69
|
+
{
|
|
70
|
+
value: {
|
|
71
|
+
headerRef,
|
|
72
|
+
isDropdownOpen,
|
|
73
|
+
activeDropdown,
|
|
74
|
+
openDropdown,
|
|
75
|
+
closeDropdown,
|
|
76
|
+
toggleDropdown
|
|
77
|
+
},
|
|
78
|
+
children
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function useNavigation() {
|
|
83
|
+
const context = useContext(NavigationContext);
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error("useNavigation must be used within NavigationProvider");
|
|
86
|
+
}
|
|
87
|
+
return context;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
NavigationProvider,
|
|
91
|
+
useNavigation
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=navigation-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/context/navigation-context.tsx"],"sourcesContent":["\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\ninterface NavigationContextType {\n headerRef: React.RefObject<HTMLElement | null>;\n isDropdownOpen: boolean;\n activeDropdown: string | null;\n openDropdown: (dropdownId: string) => void;\n closeDropdown: () => void;\n toggleDropdown: (dropdownId: string) => void;\n}\n\nconst NavigationContext = createContext<NavigationContextType | undefined>(\n undefined,\n);\n\nexport function NavigationProvider({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const [activeDropdown, setActiveDropdown] = useState<string | null>(null);\n const headerRef = useRef<HTMLElement | null>(null);\n const pathname = usePathname();\n\n const openDropdown = useCallback(\n (dropdownId: string) => setActiveDropdown(dropdownId),\n [],\n );\n const closeDropdown = useCallback(() => setActiveDropdown(null), []);\n const toggleDropdown = useCallback(\n (dropdownId: string) =>\n setActiveDropdown((prev) => (prev === dropdownId ? null : dropdownId)),\n [],\n );\n\n // Close dropdown on route change (render-time derivation, not an effect)\n const prevPathname = useRef(pathname);\n if (pathname !== prevPathname.current) {\n prevPathname.current = pathname;\n if (activeDropdown) {\n setActiveDropdown(null);\n }\n }\n\n useEffect(() => {\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && activeDropdown) {\n closeDropdown();\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [activeDropdown, closeDropdown]);\n\n useEffect(() => {\n if (!activeDropdown) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n\n // Check if click is on an internal navigation element (back button, nested dropdown triggers)\n if (target.closest(\"[data-navigation-internal]\")) {\n return;\n }\n\n // Check if click is outside the header\n if (!headerRef.current?.contains(target)) {\n closeDropdown();\n }\n };\n\n // Use capture phase to handle clicks before they bubble\n document.addEventListener(\"click\", handleClickOutside, true);\n\n return () => {\n document.removeEventListener(\"click\", handleClickOutside, true);\n };\n }, [activeDropdown, closeDropdown]);\n\n const isDropdownOpen = useMemo(\n () => activeDropdown !== null,\n [activeDropdown],\n );\n\n return (\n <NavigationContext.Provider\n value={{\n headerRef,\n isDropdownOpen,\n activeDropdown,\n openDropdown,\n closeDropdown,\n toggleDropdown,\n }}\n >\n {children}\n </NavigationContext.Provider>\n );\n}\n\nexport function useNavigation() {\n const context = useContext(NavigationContext);\n if (!context) {\n throw new Error(\"useNavigation must be used within NavigationProvider\");\n }\n return context;\n}\n"],"mappings":";AAiGI;AA/FJ,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAWP,MAAM,oBAAoB;AAAA,EACxB;AACF;AAEO,SAAS,mBAAmB;AAAA,EACjC;AACF,GAEG;AACD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB,IAAI;AACxE,QAAM,YAAY,OAA2B,IAAI;AACjD,QAAM,WAAW,YAAY;AAE7B,QAAM,eAAe;AAAA,IACnB,CAAC,eAAuB,kBAAkB,UAAU;AAAA,IACpD,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,YAAY,MAAM,kBAAkB,IAAI,GAAG,CAAC,CAAC;AACnE,QAAM,iBAAiB;AAAA,IACrB,CAAC,eACC,kBAAkB,CAAC,SAAU,SAAS,aAAa,OAAO,UAAW;AAAA,IACvE,CAAC;AAAA,EACH;AAGA,QAAM,eAAe,OAAO,QAAQ;AACpC,MAAI,aAAa,aAAa,SAAS;AACrC,iBAAa,UAAU;AACvB,QAAI,gBAAgB;AAClB,wBAAkB,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,YAAU,MAAM;AACd,UAAM,eAAe,CAAC,MAAqB;AACzC,UAAI,EAAE,QAAQ,YAAY,gBAAgB;AACxC,sBAAc;AAAA,MAChB;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,YAAY;AACjD,WAAO,MAAM,SAAS,oBAAoB,WAAW,YAAY;AAAA,EACnE,GAAG,CAAC,gBAAgB,aAAa,CAAC;AAElC,YAAU,MAAM;AACd,QAAI,CAAC,eAAgB;AAErB,UAAM,qBAAqB,CAAC,MAAkB;AAC5C,YAAM,SAAS,EAAE;AAGjB,UAAI,OAAO,QAAQ,4BAA4B,GAAG;AAChD;AAAA,MACF;AAGA,UAAI,CAAC,UAAU,SAAS,SAAS,MAAM,GAAG;AACxC,sBAAc;AAAA,MAChB;AAAA,IACF;AAGA,aAAS,iBAAiB,SAAS,oBAAoB,IAAI;AAE3D,WAAO,MAAM;AACX,eAAS,oBAAoB,SAAS,oBAAoB,IAAI;AAAA,IAChE;AAAA,EACF,GAAG,CAAC,gBAAgB,aAAa,CAAC;AAElC,QAAM,iBAAiB;AAAA,IACrB,MAAM,mBAAmB;AAAA,IACzB,CAAC,cAAc;AAAA,EACjB;AAEA,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEO,SAAS,gBAAgB;AAC9B,QAAM,UAAU,WAAW,iBAAiB;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,SAAO;AACT;","names":[]}
|