@flamingo-stack/openframe-frontend-core 0.0.705-snapshot.20261006160157 → 0.0.705

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.
Files changed (194) hide show
  1. package/dist/{chunk-WCVZCGK5.cjs → chunk-2QEUFXNS.cjs} +71 -312
  2. package/dist/{chunk-N45D4ZXQ.js → chunk-2YXVWZVF.js} +715 -959
  3. package/dist/{chunk-7OFPU7WE.cjs → chunk-3MX5S4CF.cjs} +2547 -877
  4. package/dist/{chunk-MB4PNY7C.cjs → chunk-4I45Y3DN.cjs} +6 -6
  5. package/dist/{chunk-K6O43WDL.cjs → chunk-4KX6OED2.cjs} +46 -46
  6. package/dist/{chunk-JPG7OHEG.js → chunk-5HBHO5TB.js} +1 -1
  7. package/dist/{chunk-3PLDQDE6.cjs → chunk-6NJKKO73.cjs} +15 -15
  8. package/dist/{chunk-O2RSAA5M.js → chunk-6SEANMOU.js} +11 -11
  9. package/dist/{chunk-WMX26HB5.js → chunk-6XIF2Z7I.js} +3 -3
  10. package/dist/{chunk-TQ4LB2DS.cjs → chunk-7LS22BZU.cjs} +14 -14
  11. package/dist/{chunk-7GEBFJT2.js → chunk-7VVFKJY5.js} +3429 -1759
  12. package/dist/{chunk-DDVBX42E.cjs → chunk-A75QY6CD.cjs} +21 -21
  13. package/dist/{chunk-OF6GEI5A.cjs → chunk-ABU3TGXA.cjs} +3 -3
  14. package/dist/{chunk-4CJMQLB5.js → chunk-AQWZNP6A.js} +4 -4
  15. package/dist/{chunk-FI6GMDO7.js → chunk-CH5232HV.js} +662 -2455
  16. package/dist/{chunk-Y2GSOUCS.cjs → chunk-D7BTSVMS.cjs} +2 -2
  17. package/dist/{chunk-4ILM7W2R.js → chunk-DENB2XJC.js} +2 -2
  18. package/dist/{chunk-6232S532.js → chunk-DW7G4KG3.js} +3 -3
  19. package/dist/{chunk-3NCM4ZQB.js → chunk-F6AFPNDF.js} +15 -15
  20. package/dist/{chunk-PXOQIA5K.js → chunk-F6I6XZMN.js} +5 -5
  21. package/dist/{chunk-G6UMNAVI.js → chunk-FCGC42TS.js} +61 -57
  22. package/dist/{chunk-BHI6GVC4.js → chunk-G5A3PSPV.js} +691 -1080
  23. package/dist/{chunk-HZ7CBXPL.cjs → chunk-HPWWNJE7.cjs} +89 -89
  24. package/dist/{chunk-SW5ZV2BN.cjs → chunk-IHERL5V4.cjs} +34 -34
  25. package/dist/{chunk-UJ33ZXDT.cjs → chunk-IKTQ3ZAG.cjs} +6 -6
  26. package/dist/{chunk-SEKYEWQG.cjs → chunk-JEZKGCZI.cjs} +26 -26
  27. package/dist/{chunk-7SCNMP2B.cjs → chunk-KOWVJD4V.cjs} +56 -56
  28. package/dist/chunk-LMBC5FFP.js +43 -0
  29. package/dist/{chunk-F57ECE2I.js → chunk-MFFCUVWG.js} +1 -1
  30. package/dist/{chunk-KUC673HM.js → chunk-MKXO4Q5P.js} +142 -383
  31. package/dist/{chunk-6NTPY5QA.cjs → chunk-MMOODW57.cjs} +94 -1887
  32. package/dist/{chunk-CUME3VTQ.js → chunk-MP2C3LJ4.js} +1 -1
  33. package/dist/{chunk-VX4E6YSB.cjs → chunk-N3COXDBH.cjs} +5 -41
  34. package/dist/{chunk-FXJ3WWUB.js → chunk-NDESEPW5.js} +0 -68
  35. package/dist/chunk-O43V3EOS.js +252 -0
  36. package/dist/{chunk-PLYEKV3N.cjs → chunk-OHFS7VOU.cjs} +2 -2
  37. package/dist/{chunk-M75XEEGT.cjs → chunk-OUY3HHW6.cjs} +539 -928
  38. package/dist/{chunk-CZ7Y7UG4.js → chunk-PDWVPI3S.js} +72 -4
  39. package/dist/{chunk-TE773HEH.js → chunk-PN2RKPDF.js} +10 -10
  40. package/dist/{chunk-3EZ4HHAP.js → chunk-QFXRPRFT.js} +3 -3
  41. package/dist/chunk-QZVSJF2K.cjs +252 -0
  42. package/dist/chunk-RALRPFDW.cjs +43 -0
  43. package/dist/{chunk-YGJKKDDM.js → chunk-RBDP4WDE.js} +6 -6
  44. package/dist/{chunk-LM7TXMLU.cjs → chunk-RKV5B7EU.cjs} +4 -72
  45. package/dist/{chunk-UVTD52MD.cjs → chunk-TYWLNCNH.cjs} +82 -14
  46. package/dist/{chunk-IKJA3Y7Z.js → chunk-VA5T32XA.js} +2 -2
  47. package/dist/{chunk-GWQEYPYM.cjs → chunk-VESKITRQ.cjs} +732 -728
  48. package/dist/{chunk-RQRX4YC3.cjs → chunk-VHCACDKP.cjs} +39 -39
  49. package/dist/{chunk-4IBPRIQH.js → chunk-VR2ZLFCP.js} +1 -1
  50. package/dist/{chunk-RTIPR53L.cjs → chunk-WA6GUYQ4.cjs} +104 -104
  51. package/dist/{chunk-T5TMCHWM.js → chunk-X5MQ7JRP.js} +9 -9
  52. package/dist/{chunk-LLSUVPQC.cjs → chunk-X72J4ACL.cjs} +8 -252
  53. package/dist/{chunk-UDFRUKLC.cjs → chunk-XOYH2YQE.cjs} +2 -2
  54. package/dist/{chunk-LVSFKENJ.cjs → chunk-YFBM2K6X.cjs} +4 -4
  55. package/dist/{chunk-DWPAAWMR.js → chunk-YHM5AWTT.js} +6 -6
  56. package/dist/{chunk-NCPTID44.js → chunk-Z2VNQQ74.js} +942 -978
  57. package/dist/{chunk-STHZBOQI.cjs → chunk-ZSAGLQOD.cjs} +17 -17
  58. package/dist/{chunk-VZICIQW2.js → chunk-ZSAO2HBW.js} +15 -15
  59. package/dist/components/case-studies/index.cjs +32 -30
  60. package/dist/components/case-studies/index.js +16 -14
  61. package/dist/components/chat/entity-cards/case-study-card.d.ts +4 -11
  62. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  63. package/dist/components/chat/index.cjs +11 -9
  64. package/dist/components/chat/index.js +16 -14
  65. package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
  66. package/dist/components/contact/index.cjs +14 -12
  67. package/dist/components/contact/index.js +13 -11
  68. package/dist/components/docs/index.cjs +17 -15
  69. package/dist/components/docs/index.js +16 -14
  70. package/dist/components/embeds/index.cjs +14 -12
  71. package/dist/components/embeds/index.js +13 -11
  72. package/dist/components/faq/index.cjs +15 -13
  73. package/dist/components/faq/index.js +14 -12
  74. package/dist/components/features/index.cjs +14 -12
  75. package/dist/components/features/index.js +23 -21
  76. package/dist/components/footer-waitlist-button.d.ts +29 -0
  77. package/dist/components/footer-waitlist-button.d.ts.map +1 -0
  78. package/dist/components/footer.d.ts +47 -0
  79. package/dist/components/footer.d.ts.map +1 -0
  80. package/dist/components/help-center-pages/index.cjs +120 -118
  81. package/dist/components/help-center-pages/index.js +35 -33
  82. package/dist/components/icons/index.cjs +5 -3
  83. package/dist/components/icons/index.js +11 -9
  84. package/dist/components/icons-v2-generated/index.cjs +5 -3
  85. package/dist/components/icons-v2-generated/index.js +4 -2
  86. package/dist/components/index.cjs +159 -86
  87. package/dist/components/index.d.ts +2 -0
  88. package/dist/components/index.d.ts.map +1 -1
  89. package/dist/components/index.js +402 -329
  90. package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
  91. package/dist/components/meeting-scheduler/index.cjs +64 -62
  92. package/dist/components/meeting-scheduler/index.js +14 -12
  93. package/dist/components/navigation/client-only-header.d.ts +8 -0
  94. package/dist/components/navigation/client-only-header.d.ts.map +1 -0
  95. package/dist/components/navigation/header-skeleton.d.ts +12 -0
  96. package/dist/components/navigation/header-skeleton.d.ts.map +1 -0
  97. package/dist/components/navigation/header.d.ts +9 -0
  98. package/dist/components/navigation/header.d.ts.map +1 -0
  99. package/dist/components/navigation/index.cjs +16 -28
  100. package/dist/components/navigation/index.d.ts +10 -14
  101. package/dist/components/navigation/index.d.ts.map +1 -1
  102. package/dist/components/navigation/index.js +26 -38
  103. package/dist/components/navigation/mingo-ai-button.d.ts +3 -38
  104. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  105. package/dist/components/navigation/mobile-nav-panel.d.ts +10 -0
  106. package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -0
  107. package/dist/components/navigation/navigation-sidebar-item.d.ts +2 -1
  108. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  109. package/dist/components/navigation/top-navigation.d.ts +2 -2
  110. package/dist/components/navigation/top-navigation.d.ts.map +1 -1
  111. package/dist/components/onboarding-guides/index.cjs +17 -15
  112. package/dist/components/onboarding-guides/index.js +16 -14
  113. package/dist/components/related-content/index.cjs +8 -10
  114. package/dist/components/related-content/index.js +7 -9
  115. package/dist/components/tickets/index.cjs +18 -16
  116. package/dist/components/tickets/index.js +17 -15
  117. package/dist/components/ui/index.cjs +14 -12
  118. package/dist/components/ui/index.js +19 -17
  119. package/dist/hooks/index.cjs +11 -19
  120. package/dist/hooks/index.d.ts +0 -1
  121. package/dist/hooks/index.d.ts.map +1 -1
  122. package/dist/hooks/index.js +11 -19
  123. package/dist/hooks/ui/index.d.ts +0 -3
  124. package/dist/hooks/ui/index.d.ts.map +1 -1
  125. package/dist/index.cjs +22 -55
  126. package/dist/index.js +196 -229
  127. package/dist/types/case-study.d.ts +0 -9
  128. package/dist/types/case-study.d.ts.map +1 -1
  129. package/dist/types/navigation.d.ts +65 -95
  130. package/dist/types/navigation.d.ts.map +1 -1
  131. package/dist/utils/index.cjs +0 -55
  132. package/dist/utils/index.d.ts +0 -1
  133. package/dist/utils/index.d.ts.map +1 -1
  134. package/dist/utils/index.js +1 -50
  135. package/dist/utils/source-icons.d.ts.map +1 -1
  136. package/package.json +1 -1
  137. package/src/components/chat/entity-cards/case-study-card.tsx +5 -115
  138. package/src/components/chat/utils/icon-library.tsx +1 -35
  139. package/src/components/footer-waitlist-button.tsx +53 -0
  140. package/src/components/footer.tsx +195 -0
  141. package/src/components/index.ts +2 -0
  142. package/src/components/loading/page-layout-skeleton.tsx +1 -1
  143. package/src/components/navigation/client-only-header.tsx +22 -0
  144. package/src/components/navigation/header-skeleton.tsx +74 -0
  145. package/src/components/navigation/header.tsx +429 -0
  146. package/src/components/navigation/index.ts +14 -21
  147. package/src/components/navigation/mingo-ai-button.tsx +63 -139
  148. package/src/components/navigation/mobile-nav-panel.tsx +155 -0
  149. package/src/components/navigation/navigation-sidebar-item.tsx +2 -1
  150. package/src/components/navigation/ticket-alerts-button.tsx +2 -2
  151. package/src/components/navigation/top-navigation.tsx +2 -2
  152. package/src/hooks/index.ts +0 -1
  153. package/src/hooks/ui/index.ts +0 -3
  154. package/src/styles/chat-animations.css +2 -57
  155. package/src/types/case-study.ts +0 -16
  156. package/src/types/navigation.ts +66 -80
  157. package/src/utils/index.ts +0 -1
  158. package/src/utils/source-icons.ts +0 -13
  159. package/dist/components/navigation/mobile-nav-sheet-id.d.ts +0 -3
  160. package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +0 -1
  161. package/dist/components/navigation/mobile-nav-sheet.d.ts +0 -48
  162. package/dist/components/navigation/mobile-nav-sheet.d.ts.map +0 -1
  163. package/dist/components/navigation/nav-item-row.d.ts +0 -27
  164. package/dist/components/navigation/nav-item-row.d.ts.map +0 -1
  165. package/dist/components/navigation/nav-panel.d.ts +0 -43
  166. package/dist/components/navigation/nav-panel.d.ts.map +0 -1
  167. package/dist/components/navigation/site-footer.d.ts +0 -28
  168. package/dist/components/navigation/site-footer.d.ts.map +0 -1
  169. package/dist/components/navigation/site-header.d.ts +0 -61
  170. package/dist/components/navigation/site-header.d.ts.map +0 -1
  171. package/dist/components/navigation/site-nav-link.d.ts +0 -26
  172. package/dist/components/navigation/site-nav-link.d.ts.map +0 -1
  173. package/dist/hooks/ui/use-active-section.d.ts +0 -10
  174. package/dist/hooks/ui/use-active-section.d.ts.map +0 -1
  175. package/dist/hooks/ui/use-nav-menus.d.ts +0 -48
  176. package/dist/hooks/ui/use-nav-menus.d.ts.map +0 -1
  177. package/dist/hooks/ui/use-visitor-os.d.ts +0 -15
  178. package/dist/hooks/ui/use-visitor-os.d.ts.map +0 -1
  179. package/dist/hooks/use-location-hash.d.ts +0 -9
  180. package/dist/hooks/use-location-hash.d.ts.map +0 -1
  181. package/dist/utils/visitor-os.d.ts +0 -45
  182. package/dist/utils/visitor-os.d.ts.map +0 -1
  183. package/src/components/navigation/mobile-nav-sheet-id.ts +0 -2
  184. package/src/components/navigation/mobile-nav-sheet.tsx +0 -241
  185. package/src/components/navigation/nav-item-row.tsx +0 -129
  186. package/src/components/navigation/nav-panel.tsx +0 -196
  187. package/src/components/navigation/site-footer.tsx +0 -256
  188. package/src/components/navigation/site-header.tsx +0 -339
  189. package/src/components/navigation/site-nav-link.tsx +0 -41
  190. package/src/hooks/ui/use-active-section.ts +0 -44
  191. package/src/hooks/ui/use-nav-menus.ts +0 -200
  192. package/src/hooks/ui/use-visitor-os.ts +0 -33
  193. package/src/hooks/use-location-hash.ts +0 -34
  194. package/src/utils/visitor-os.ts +0 -84
@@ -0,0 +1,429 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useRef, useState } from 'react';
4
+ import Link from '../../embed-shims/next-link';
5
+ import type { HeaderConfig, NavigationItem } from '../../types/navigation';
6
+ import { cn } from '../../utils';
7
+ import { Menu01Icon, XmarkIcon } from '../icons-v2-generated';
8
+ import { Button } from '../ui/button';
9
+ import { HeaderButton } from './header-button';
10
+ import { MingoAiButton } from './mingo-ai-button';
11
+ import { MOBILE_NAV_PANEL_ID } from './mobile-nav-panel';
12
+ import { TicketAlertsButton } from './ticket-alerts-button';
13
+ import { TopNavigation } from './top-navigation';
14
+
15
+ export interface HeaderProps {
16
+ config: HeaderConfig;
17
+ platform?: string;
18
+ }
19
+
20
+ // Re-export from types for convenience
21
+ export type { HeaderConfig } from '../../types/navigation';
22
+
23
+ // Top-level nav-link typography (Figma 2936-6815): the compact h6 step
24
+ // (DM Sans 500, 14/20) with 24px icons. Overrides the `font="regular"` h4
25
+ // label and the Button base's 20px svg cap via cn()'s tailwind-merge.
26
+ const NAV_ITEM_CLASSES = 'text-h6 [&_svg]:h-6 [&_svg]:w-6';
27
+
28
+ export function Header({ config }: HeaderProps) {
29
+ const [show, setShow] = useState(true);
30
+ // Not state: nothing renders the previous offset, and as `useState` it
31
+ // re-rendered the whole header on every scroll event just to store a number.
32
+ const lastScrollY = useRef(0);
33
+ const [openDropdowns, setOpenDropdowns] = useState<Record<string, boolean>>({});
34
+ const dropdownRefs = useRef<Record<string, HTMLDivElement | null>>({});
35
+ const triggerRefs = useRef<Record<string, HTMLButtonElement | null>>({});
36
+
37
+ // Handle click outside and escape key for custom dropdown
38
+ useEffect(() => {
39
+ const handleClickOutside = (event: MouseEvent) => {
40
+ const target = event.target as HTMLElement;
41
+ if (!target) return;
42
+
43
+ // Check if click is outside all dropdowns
44
+ const isOutsideAllDropdowns = Object.keys(openDropdowns).every(id => {
45
+ const dropdown = dropdownRefs.current[id];
46
+ const trigger = triggerRefs.current[id];
47
+
48
+ if (!dropdown || !trigger) return true;
49
+
50
+ return !dropdown.contains(target) && !trigger.contains(target);
51
+ });
52
+
53
+ if (isOutsideAllDropdowns) {
54
+ setOpenDropdowns({});
55
+ }
56
+ };
57
+
58
+ const handleEscapeKey = (event: KeyboardEvent) => {
59
+ if (event.key === 'Escape') {
60
+ setOpenDropdowns({});
61
+ }
62
+ };
63
+
64
+ // Only add listeners if any dropdown is open
65
+ const hasOpenDropdowns = Object.values(openDropdowns).some(Boolean);
66
+ if (hasOpenDropdowns) {
67
+ document.addEventListener('mousedown', handleClickOutside);
68
+ document.addEventListener('keydown', handleEscapeKey);
69
+ }
70
+
71
+ return () => {
72
+ document.removeEventListener('mousedown', handleClickOutside);
73
+ document.removeEventListener('keydown', handleEscapeKey);
74
+ };
75
+ }, [openDropdowns]);
76
+
77
+ // Force close all dropdowns and cleanup on unmount
78
+ useEffect(() => {
79
+ return () => {
80
+ // Close all dropdowns before unmounting to prevent focus errors
81
+ setOpenDropdowns({});
82
+ // Clear any stored refs
83
+ dropdownRefs.current = {};
84
+ triggerRefs.current = {};
85
+ };
86
+ }, []);
87
+
88
+ useEffect(() => {
89
+ // Only add scroll listener if autoHide is enabled. Nothing to reset here:
90
+ // `show` is masked by `config.autoHide` where it is read (see `headerShown`
91
+ // below), so turning auto-hide off can never leave the header stuck
92
+ // off-screen, and it does not cost the extra render pass that writing
93
+ // `true` back into state from this effect did.
94
+ if (!config.autoHide) return undefined;
95
+
96
+ const handleScroll = () => {
97
+ const currentScrollY = window.scrollY;
98
+
99
+ const prevScrollY = lastScrollY.current;
100
+ lastScrollY.current = currentScrollY;
101
+
102
+ // Determine if we should show or hide the header
103
+ const shouldHide = currentScrollY > prevScrollY && currentScrollY > 50;
104
+ const shouldShow = currentScrollY < prevScrollY || currentScrollY <= 10;
105
+
106
+ if (shouldHide) {
107
+ setShow(false);
108
+ } else if (shouldShow) {
109
+ setShow(true);
110
+ }
111
+ };
112
+
113
+ window.addEventListener('scroll', handleScroll, { passive: true });
114
+
115
+ return () => {
116
+ window.removeEventListener('scroll', handleScroll);
117
+ };
118
+ }, [config.autoHide]);
119
+
120
+ const renderNavigationItem = (item: NavigationItem) => {
121
+ // If custom element provided, use it
122
+ if (item.element) {
123
+ return <React.Fragment key={item.id}>{item.element}</React.Fragment>;
124
+ }
125
+
126
+ // If it has children, render as custom dropdown
127
+ if (item.children && item.children.length > 0) {
128
+ const isOpen = openDropdowns[item.id] || false;
129
+
130
+ return (
131
+ <div key={item.id} className="relative">
132
+ <Button
133
+ ref={el => {
134
+ triggerRefs.current[item.id] = el;
135
+ }}
136
+ variant="transparent"
137
+ leftIcon={item.icon}
138
+ rightIcon={item.badge}
139
+ onClick={() => {
140
+ // Single-open: opening a dropdown closes any other open one —
141
+ // only one nav dropdown may be expanded at a time.
142
+ setOpenDropdowns(prev => ({
143
+ [item.id]: !prev[item.id],
144
+ }));
145
+ }}
146
+ size="default"
147
+ font="regular"
148
+ className={cn(
149
+ // Top-level nav links (Figma 2936-6815): compact h6 label
150
+ // (DM Sans 500, 14/20) with 24px icons — overrides the
151
+ // font="regular" h4 step and the button's 20px svg cap.
152
+ NAV_ITEM_CLASSES,
153
+ item.isActive && 'bg-ods-bg-hover', // Active items get subtle gray background
154
+ isOpen && 'bg-ods-bg-hover', // Open dropdowns get gray background
155
+ item.className,
156
+ )}
157
+ >
158
+ {item.label}
159
+ </Button>
160
+
161
+ {/* Always render dropdown in DOM so crawlers see child <a> links;
162
+ toggle visibility via CSS + `inert`.
163
+
164
+ Why `inert` (not `aria-hidden`):
165
+ - `aria-hidden=true` HIDES from screen readers but DOES NOT
166
+ remove focusable descendants from the tab order. If a child
167
+ retains focus (e.g., the dropdown closes while a child was
168
+ hovered/focused), the browser correctly flags "Blocked
169
+ aria-hidden on an element because its descendant retained
170
+ focus" — a real WAI-ARIA violation.
171
+ - `inert` is the HTML-standard attribute that does BOTH:
172
+ removes from a11y tree + blocks focus + prevents click +
173
+ removes from tab order. Native React 19 support. */}
174
+ <div
175
+ ref={el => {
176
+ dropdownRefs.current[item.id] = el;
177
+ }}
178
+ inert={!isOpen}
179
+ className={cn(
180
+ 'absolute left-0 top-full mt-1',
181
+ item.dropdownClassName ? '' : 'border border-ods-border bg-ods-card',
182
+ 'z-[9999] rounded-lg shadow-xl',
183
+ item.id === 'community' ? 'min-w-[240px]' : 'min-w-[220px]',
184
+ 'transition-opacity duration-150',
185
+ isOpen ? 'pointer-events-auto visible opacity-100' : 'pointer-events-none invisible opacity-0',
186
+ item.dropdownClassName || '',
187
+ )}
188
+ >
189
+ <div className="p-2">
190
+ {item.children.map((child, index) => (
191
+ <Button
192
+ key={child.id}
193
+ variant="transparent"
194
+ size="small-legacy"
195
+ href={child.href} // Use href for navigation
196
+ leftIcon={child.icon}
197
+ rightIcon={child.badge}
198
+ onClick={() => {
199
+ // Always close dropdown when any item is clicked
200
+ setOpenDropdowns(prev => ({ ...prev, [item.id]: false }));
201
+ // If there's a custom onClick, call it too
202
+ if (child.onClick) {
203
+ child.onClick();
204
+ }
205
+ }}
206
+ className={cn(
207
+ 'flex w-full justify-start',
208
+ // Same caption step as the top-level nav links (designer:
209
+ // dropdowns follow the h6 caption size/weight too). The
210
+ // explicit font-medium beats small-legacy's font-bold via
211
+ // tailwind-merge instead of stylesheet order.
212
+ 'font-medium text-h6',
213
+ index < (item.children?.length ?? 0) - 1 && 'mb-1',
214
+ 'text-ods-text-primary', // All dropdown items use primary text color
215
+ child.isActive && 'bg-ods-bg-hover', // Active dropdown items get gray background
216
+ )}
217
+ {...(child.isExternal && { isExternal: true })}
218
+ >
219
+ {child.label}
220
+ </Button>
221
+ ))}
222
+ </div>
223
+ {item.dropdownContent && (
224
+ <>
225
+ {item.showDropdownDivider !== false && <div className="mx-2 my-2 h-px bg-ods-border" />}
226
+ <div className="px-2 pb-2">{item.dropdownContent}</div>
227
+ </>
228
+ )}
229
+ </div>
230
+ </div>
231
+ );
232
+ }
233
+
234
+ // Regular navigation item
235
+ if (item.href || item.onClick) {
236
+ return (
237
+ <Button
238
+ key={item.id}
239
+ variant="transparent"
240
+ href={item.href} // Use href for navigation
241
+ onClick={item.onClick} // Only for non-navigation actions
242
+ leftIcon={item.icon}
243
+ rightIcon={item.badge}
244
+ size="default"
245
+ font="regular"
246
+ className={cn(
247
+ NAV_ITEM_CLASSES,
248
+ 'hover:bg-ods-bg-hover focus:bg-ods-bg-hover',
249
+ 'whitespace-nowrap',
250
+ 'text-ods-text-primary', // All items use primary text color
251
+ item.isActive && 'bg-ods-bg-hover', // Active items get subtle gray background
252
+ item.className,
253
+ )}
254
+ {...(item.isExternal && { isExternal: true })}
255
+ >
256
+ {item.label}
257
+ </Button>
258
+ );
259
+ }
260
+
261
+ // Button with onClick
262
+ return (
263
+ <Button
264
+ key={item.id}
265
+ variant="transparent"
266
+ onClick={item.onClick}
267
+ leftIcon={item.icon}
268
+ rightIcon={item.badge}
269
+ size="default"
270
+ font="regular"
271
+ className={cn(
272
+ NAV_ITEM_CLASSES,
273
+ 'hover:bg-ods-bg-hover focus:bg-ods-bg-hover',
274
+ 'whitespace-nowrap',
275
+ 'text-ods-text-primary', // All items use primary text color
276
+ item.isActive && 'bg-ods-bg-hover', // Active items get gray background
277
+ item.className,
278
+ )}
279
+ >
280
+ {item.label}
281
+ </Button>
282
+ );
283
+ };
284
+
285
+ // `show` only means anything while auto-hide is on — with it off the header
286
+ // is always up, regardless of whatever the scroll listener last recorded.
287
+ const headerShown = !config.autoHide || show;
288
+
289
+ const hasNav = !!config.navigation && config.navigation.items.length > 0;
290
+ const hasCta =
291
+ !!config.actions?.right?.length || !!(config.actions?.persistent && config.actions.persistent.length > 0);
292
+
293
+ return (
294
+ <div
295
+ className={cn('sticky top-0 z-[50] w-full transition-transform duration-300 ease-in-out')}
296
+ style={{
297
+ transform: !headerShown ? 'translateY(-100%)' : 'translateY(0)',
298
+ }}
299
+ >
300
+ {/* Unified ODS top-navigation shell (Figma 2797-5978): 48px mobile /
301
+ 56px md+, cell model with per-cell dividers. NOTE: no `backdrop-blur`
302
+ anywhere in this bar. Every platform ships an OPAQUE header
303
+ background (`backgroundColor` resolves to an opaque ODS token), so a
304
+ backdrop-filter would blur a backdrop that is then fully painted
305
+ over: zero visual effect, but the browser still re-rasterizes the
306
+ strip behind this sticky bar every scroll frame and content flickers
307
+ as it passes under the header. If a consumer ever wants a
308
+ translucent "glass" header, add the blur together with a translucent
309
+ `backgroundColor` deliberately. */}
310
+ <TopNavigation
311
+ // `relative` anchors the absolutely-centered nav (position 'center')
312
+ // to the bar itself, so the links sit at the true viewport center
313
+ // regardless of the asymmetric CTA/Mingo cluster on the right.
314
+ className={cn('relative', config.className)}
315
+ style={config.style}
316
+ backgroundClassName={config.backgroundColor}
317
+ centerBreakpoint="lg"
318
+ size="big"
319
+ leading={
320
+ <>
321
+ {/* Length-guarded: platform configs often pass `left: []`, and an
322
+ empty array is truthy — without the guard the cell would render
323
+ as a bare divider + padding. */}
324
+ {/* No padding on the wrapper: leading cells (HeaderButton-based
325
+ admin toggles) size themselves to the bar's square cell. */}
326
+ {!!config.actions?.left?.length && (
327
+ <div className="flex h-full items-center border-r border-ods-border">{config.actions.left}</div>
328
+ )}
329
+ {/* Mobile/tablet menu toggle — a leading cell per the ODS spec
330
+ (banded with the nav breakpoint `lg` so the desktop nav and the
331
+ toggle never co-show). */}
332
+ {config.mobile?.enabled && (
333
+ <HeaderButton
334
+ className="border-r border-ods-border lg:hidden"
335
+ onClick={() => {
336
+ config.mobile?.onToggle?.();
337
+ }}
338
+ isActive={config.mobile?.isOpen ?? false}
339
+ aria-label={config.mobile?.isOpen ? 'Close menu' : 'Open menu'}
340
+ aria-expanded={config.mobile?.isOpen ?? false}
341
+ // Conditional: the panel unmounts when closed, so an unconditional
342
+ // reference would dangle (axe aria-valid-attr-value).
343
+ aria-controls={config.mobile?.isOpen ? MOBILE_NAV_PANEL_ID : undefined}
344
+ icon={
345
+ config.mobile?.isOpen
346
+ ? config.mobile?.closeIcon || <XmarkIcon className="h-6 w-6" />
347
+ : config.mobile?.menuIcon || <Menu01Icon className="h-6 w-6" />
348
+ }
349
+ />
350
+ )}
351
+ </>
352
+ }
353
+ logo={
354
+ config.logo.href ? (
355
+ <Link href={config.logo.href} className="transition-opacity duration-200 hover:opacity-80">
356
+ {config.logo.element}
357
+ </Link>
358
+ ) : (
359
+ config.logo.element
360
+ )
361
+ }
362
+ // Big-bar rule (Figma 2936-6812): 24px fixed left inset on the logo
363
+ // zone at every breakpoint — the same 24px also reads as the gap
364
+ // between a leading cell (burger / admin toggle) and the logo.
365
+ logoClassName="pl-[var(--spacing-system-lf)] md:pl-[var(--spacing-system-lf)] lg:pl-[var(--spacing-system-lf)]"
366
+ center={
367
+ hasNav ? (
368
+ <nav
369
+ className={cn(
370
+ 'flex items-center gap-2',
371
+ // True centering relative to the bar (not the leftover flex
372
+ // space) — same treatment the pre-unification header had.
373
+ config.navigation?.position === 'center' && 'absolute left-1/2 -translate-x-1/2',
374
+ )}
375
+ role="navigation"
376
+ aria-label="Main navigation"
377
+ >
378
+ {config.navigation?.items.map(renderNavigationItem)}
379
+ </nav>
380
+ ) : undefined
381
+ }
382
+ centerClassName={cn(
383
+ config.navigation?.position === 'left' && 'justify-start',
384
+ config.navigation?.position === 'right' && 'justify-end pr-[var(--spacing-system-m)]',
385
+ )}
386
+ cta={
387
+ hasCta ? (
388
+ <>
389
+ {/* Desktop actions — banded with the nav/burger breakpoint (lg)
390
+ so the desktop right-cluster and the mobile toggle never
391
+ co-show. */}
392
+ {!!config.actions?.right?.length && (
393
+ <div className="hidden items-center gap-3 lg:flex">{config.actions.right}</div>
394
+ )}
395
+ {config.actions?.persistent && config.actions.persistent.length > 0 && (
396
+ <div className="flex items-center">{config.actions.persistent}</div>
397
+ )}
398
+ </>
399
+ ) : undefined
400
+ }
401
+ ctaClassName="gap-3"
402
+ sideActions={
403
+ config.tickets || config.mingo?.enabled ? (
404
+ <>
405
+ {/* Support-ticket alerts cell — before Mingo, flush cell row.
406
+ Attention-only: renders nothing unless there are unread
407
+ replies (and the host mounted <TicketLiveProvider>). */}
408
+ {config.tickets && (
409
+ <TicketAlertsButton
410
+ href={config.tickets.href}
411
+ onNavigate={config.tickets.onClick}
412
+ className="border-l border-ods-border"
413
+ />
414
+ )}
415
+ {config.mingo?.enabled && (
416
+ <MingoAiButton
417
+ source={config.mingo.source}
418
+ icon={config.mingo.icon}
419
+ label={config.mingo.label}
420
+ className={config.mingo.className}
421
+ />
422
+ )}
423
+ </>
424
+ ) : undefined
425
+ }
426
+ />
427
+ </div>
428
+ );
429
+ }
@@ -1,22 +1,20 @@
1
1
  'use client';
2
2
 
3
3
  // Navigation component exports
4
- // Site navigation: one data model (`SiteNav`), rendered by the header, its
5
- // menu panels, the mobile sheet and the footer.
6
- export { SiteHeader } from './site-header';
7
- export type { SiteHeaderProps } from './site-header';
8
- export { NavPanel, isMegaMenu } from './nav-panel';
9
- export type { NavPanelProps } from './nav-panel';
10
- export { NavItemRow } from './nav-item-row';
11
- export type { NavItemRowProps, NavItemRowVariant } from './nav-item-row';
12
- export { ASK_AI_OPEN_EVENT, MingoAiButton, openAskAi } from './mingo-ai-button';
4
+ export { Header } from './header';
5
+ export type { HeaderConfig, HeaderProps } from './header';
6
+
7
+ export { MingoAiButton } from './mingo-ai-button';
13
8
  export type { MingoAiButtonProps } from './mingo-ai-button';
14
- export { MobileNavSheet, MOBILE_NAV_SHEET_ID, siteNavHasMobileMenus } from './mobile-nav-sheet';
15
- export type { MobileNavSheetProps } from './mobile-nav-sheet';
16
- export { SiteFooter } from './site-footer';
17
- export type { SiteFooterProps } from './site-footer';
18
- export { defaultRenderSiteNavLink, navLinkLabel } from './site-nav-link';
19
- export type { SiteNavLinkRenderer } from './site-nav-link';
9
+
10
+ export { ClientOnlyHeader } from './client-only-header';
11
+ export type { ClientOnlyHeaderProps } from './client-only-header';
12
+
13
+ export { HeaderSkeleton } from './header-skeleton';
14
+ export type { HeaderSkeletonProps } from './header-skeleton';
15
+
16
+ export { MobileNavPanel, MOBILE_NAV_PANEL_ID } from './mobile-nav-panel';
17
+ export type { MobileNavPanelProps } from './mobile-nav-panel';
20
18
 
21
19
  export { SlidingSidebar } from './sliding-sidebar';
22
20
  export type { SlidingSidebarProps } from './sliding-sidebar';
@@ -83,15 +81,10 @@ export type { NavigationNode } from './multi-level-navigation';
83
81
 
84
82
  // Re-export types from navigation types
85
83
  export type {
86
- NavFeature,
87
- NavGroup,
88
- NavLink,
89
- NavMenu,
84
+ MobileNavConfig,
90
85
  NavigationItem,
91
86
  NavigationSidebarConfig,
92
87
  NavigationSidebarItem,
93
- SiteNav,
94
- SiteNavPrimaryCta,
95
88
  SlidingSidebarConfig,
96
89
  UnifiedSidebarUser,
97
90
  } from '../../types/navigation';