@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
@@ -1,241 +0,0 @@
1
- 'use client';
2
-
3
- import { usePreventScroll } from '@react-aria/overlays';
4
- import type React from 'react';
5
- import { useRef, useState } from 'react';
6
- import { resolveActiveSection } from '../../hooks/ui/use-active-section';
7
- import { useFocusTrap } from '../../hooks/ui/use-focus-trap';
8
- import type { NavLink, NavMenu, SiteNav } from '../../types/navigation';
9
- import { cn } from '../../utils/cn';
10
- import { Chevron02DownIcon } from '../icons-v2-generated/arrows/chevron-02-down-icon';
11
- import { Chevron02RightIcon } from '../icons-v2-generated/arrows/chevron-02-right-icon';
12
- import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon';
13
- import { Button } from '../ui/button';
14
- import { MingoAiButton } from './mingo-ai-button';
15
- import { MOBILE_NAV_SHEET_ID } from './mobile-nav-sheet-id';
16
- import { NavItemRow } from './nav-item-row';
17
- import { defaultRenderSiteNavLink, NAV_FOCUS_CLASS, type SiteNavLinkRenderer } from './site-nav-link';
18
-
19
- export { MOBILE_NAV_SHEET_ID } from './mobile-nav-sheet-id';
20
-
21
- export interface MobileNavSheetProps {
22
- nav: SiteNav;
23
- isOpen: boolean;
24
- onClose: () => void;
25
- /** The host router's pathname: the group holding the current page starts open. */
26
- pathname: string;
27
- logo: React.ReactNode;
28
- renderLink?: SiteNavLinkRenderer;
29
- /** The primary CTA node when `nav.primaryCta` is `'trial'` or `'waitlist'`. */
30
- cta?: React.ReactNode;
31
- /** Host nodes shown above the pinned CTA (a sign-up button). */
32
- actions?: React.ReactNode;
33
- /** The Mingo row at the top (`MingoAiButton variant="field"`): the assistant's
34
- * name and icon; `source` scopes the `ask-ai:open` event. Omitted: no row. */
35
- askAI?: { source?: string; icon?: React.ReactNode; label?: string };
36
- /** Host-added groups after the site's own (Profile, Admin). */
37
- extraMenus?: NavMenu[];
38
- /** Values for `NavLink.badgeKey`. */
39
- badges?: Record<string, number>;
40
- /** Rendered under the links of a menu with `showSocial`. */
41
- social?: React.ReactNode;
42
- /** Opaque ODS background class. Default `bg-ods-bg`. */
43
- backgroundClassName?: string;
44
- }
45
-
46
- /**
47
- * Would the sheet list anything from this navigation? The header's menu button
48
- * and the sheet read this one answer (plus whatever the host adds: a profile
49
- * entry, a sign-up button), so a button never opens an empty sheet.
50
- */
51
- export function siteNavHasMobileMenus(nav: Pick<SiteNav, 'menus' | 'mobileMenus'>): boolean {
52
- return (nav.mobileMenus ?? nav.menus).length > 0;
53
- }
54
-
55
- const GROUP_ROW_CLASS =
56
- 'flex min-h-14 w-full items-center justify-between gap-[var(--spacing-system-sf)] text-left text-ods-text-primary text-h4';
57
-
58
- const menuLinks = (menu: NavMenu): NavLink[] => [
59
- ...(menu.columns ?? []).flatMap(group => group.links),
60
- ...(menu.sideLinks ?? []),
61
- ];
62
-
63
- /**
64
- * The site navigation below `lg`: a full-screen sheet drawn from the same
65
- * `SiteNav` as the header. Each menu with links is an accordion that expands
66
- * in place; the primary CTA and Sign in stay pinned at the bottom while the
67
- * body scrolls.
68
- *
69
- * Unmounted while closed (the header's panels are what crawlers read).
70
- */
71
- export function MobileNavSheet(props: MobileNavSheetProps) {
72
- // The open groups are seeded from the page the sheet opens on, so the body
73
- // mounts with the sheet: every open starts from the current section.
74
- if (!props.isOpen) return null;
75
- return <MobileNavSheetBody {...props} />;
76
- }
77
-
78
- function MobileNavSheetBody({
79
- nav,
80
- onClose,
81
- pathname,
82
- logo,
83
- renderLink = defaultRenderSiteNavLink,
84
- cta,
85
- actions,
86
- askAI,
87
- extraMenus,
88
- badges,
89
- social,
90
- backgroundClassName,
91
- }: MobileNavSheetProps) {
92
- const sheetRef = useRef<HTMLDivElement>(null);
93
- const menus = [...(nav.mobileMenus ?? nav.menus), ...(extraMenus ?? [])];
94
-
95
- // Shared ref-counted, iOS-aware scroll lock (react-aria): one counter with
96
- // the modals and chat overlays, restores prior styles on release.
97
- usePreventScroll();
98
- // Initial focus, Tab containment, Escape-to-close, guarded focus restore.
99
- useFocusTrap(sheetRef, true, { onEscape: onClose });
100
-
101
- const [openGroups, setOpenGroups] = useState<Record<string, boolean>>(() => {
102
- const active = resolveActiveSection(menus, pathname);
103
- return active ? { [active]: true } : {};
104
- });
105
-
106
- const primaryCta =
107
- typeof nav.primaryCta === 'object' ? (
108
- <Button variant="accent" href={nav.primaryCta.href} onClick={onClose} className="w-full">
109
- {nav.primaryCta.label}
110
- </Button>
111
- ) : nav.primaryCta === 'none' ? null : (
112
- (cta ?? null)
113
- );
114
- const hasFooter = !!actions || !!primaryCta || !!nav.signIn;
115
-
116
- return (
117
- <div
118
- ref={sheetRef}
119
- id={MOBILE_NAV_SHEET_ID}
120
- role="dialog"
121
- aria-modal="true"
122
- aria-label="Navigation menu"
123
- tabIndex={-1}
124
- // Small-viewport height: 100vh overflowed under mobile browser chrome,
125
- // leaving the pinned CTA unreachable while the body was scroll-locked.
126
- className={cn(
127
- 'fixed inset-0 z-[9999] flex h-[100svh] flex-col text-ods-text-primary outline-none',
128
- backgroundClassName ?? 'bg-ods-bg',
129
- )}
130
- >
131
- <div className="flex h-14 shrink-0 items-center justify-between border-b border-ods-border pl-5 pr-[var(--spacing-system-xsf)]">
132
- <div className="flex min-w-0 items-center">{logo}</div>
133
- <Button
134
- type="button"
135
- variant="transparent"
136
- size="icon"
137
- aria-label="Close menu"
138
- onClick={onClose}
139
- className={cn('h-11 w-11 shrink-0 md:h-11 md:w-11', NAV_FOCUS_CLASS)}
140
- >
141
- <XmarkIcon aria-hidden="true" className="!h-6 !w-6" />
142
- </Button>
143
- </div>
144
-
145
- <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-5">
146
- {askAI && (
147
- <MingoAiButton
148
- variant="field"
149
- source={askAI.source}
150
- icon={askAI.icon}
151
- label={askAI.label}
152
- onClick={onClose}
153
- />
154
- )}
155
-
156
- <nav aria-label="Main">
157
- {menus.map(menu => {
158
- const links = menuLinks(menu);
159
- if (links.length === 0) {
160
- if (!menu.href) return null;
161
- return (
162
- <div key={menu.id} className="border-b border-ods-border">
163
- {renderLink({
164
- link: { href: menu.href },
165
- className: cn(GROUP_ROW_CLASS, NAV_FOCUS_CLASS),
166
- onClick: onClose,
167
- children: (
168
- <>
169
- {menu.label}
170
- <Chevron02RightIcon aria-hidden="true" className="h-5 w-5 shrink-0 text-ods-text-muted" />
171
- </>
172
- ),
173
- })}
174
- </div>
175
- );
176
- }
177
-
178
- const open = !!openGroups[menu.id];
179
- const listId = `${MOBILE_NAV_SHEET_ID}-${menu.id}`;
180
- return (
181
- <div key={menu.id} className="border-b border-ods-border">
182
- <Button
183
- type="button"
184
- variant="glyph"
185
- size="wrap"
186
- font="regular"
187
- aria-expanded={open}
188
- aria-controls={open ? listId : undefined}
189
- onClick={() => setOpenGroups(prev => ({ ...prev, [menu.id]: !prev[menu.id] }))}
190
- className={cn(GROUP_ROW_CLASS, NAV_FOCUS_CLASS)}
191
- >
192
- {menu.label}
193
- <Chevron02DownIcon
194
- aria-hidden="true"
195
- className={cn(
196
- 'h-5 w-5 shrink-0 text-ods-text-muted transition-transform duration-150',
197
- open && 'rotate-180',
198
- )}
199
- />
200
- </Button>
201
- {open && (
202
- <div id={listId} className="flex flex-col pb-[var(--spacing-system-sf)]">
203
- {links.map(link => (
204
- <NavItemRow
205
- key={link.id}
206
- link={link}
207
- variant="sheet"
208
- renderLink={renderLink}
209
- badge={link.badgeKey ? badges?.[link.badgeKey] : undefined}
210
- onNavigate={onClose}
211
- />
212
- ))}
213
- {menu.showSocial && social && <div className="pt-[var(--spacing-system-xsf)]">{social}</div>}
214
- </div>
215
- )}
216
- </div>
217
- );
218
- })}
219
- </nav>
220
- </div>
221
-
222
- {/* Pinned: stays visible while the body scrolls, clears the iOS home indicator. */}
223
- {hasFooter && (
224
- <div className="flex shrink-0 flex-col gap-[var(--spacing-system-xxs)] border-t border-ods-border px-5 pb-[max(1.75rem,env(safe-area-inset-bottom))] pt-[var(--spacing-system-mf)] [&_a]:w-full [&_button]:w-full">
225
- {actions}
226
- {primaryCta}
227
- {nav.signIn &&
228
- renderLink({
229
- link: nav.signIn,
230
- className: cn(
231
- 'flex h-12 items-center justify-center rounded-md text-ods-text-primary text-h4',
232
- NAV_FOCUS_CLASS,
233
- ),
234
- onClick: onClose,
235
- children: nav.signIn.label,
236
- })}
237
- </div>
238
- )}
239
- </div>
240
- );
241
- }
@@ -1,129 +0,0 @@
1
- 'use client';
2
-
3
- import type React from 'react';
4
- import type { NavLink } from '../../types/navigation';
5
- import { cn } from '../../utils/cn';
6
- import { EntityIcon } from '../icon-display';
7
- import { defaultRenderSiteNavLink, NAV_FOCUS_CLASS, navLinkLabel, type SiteNavLinkRenderer } from './site-nav-link';
8
- import { UnreadCountBadge } from './unread-dot';
9
-
10
- export type NavItemRowVariant = 'panel' | 'sheet' | 'side' | 'label';
11
-
12
- export interface NavItemRowProps {
13
- link: NavLink;
14
- /**
15
- * - `panel` (default): a desktop menu row, 36px icon tile, label, description.
16
- * - `sheet`: the mobile sheet's row, 32px tile, at least 52px tall.
17
- * - `side`: a mega menu's side list, a small bare icon and the label.
18
- * - `label`: the label only (footer columns).
19
- */
20
- variant?: NavItemRowVariant;
21
- renderLink?: SiteNavLinkRenderer;
22
- /** The live counter for `link.badgeKey`; nothing renders at 0 or undefined. */
23
- badge?: number;
24
- /** Called on activation, so the menu or sheet holding the row can close. */
25
- onNavigate?: () => void;
26
- className?: string;
27
- }
28
-
29
- // Box and colour per variant; every text step is an ODS composite.
30
- const ROW_CLASSES: Record<NavItemRowVariant, string> = {
31
- panel:
32
- 'flex items-start gap-[var(--spacing-system-sf)] rounded-lg p-[var(--spacing-system-sf)] text-ods-text-primary transition-colors hover:bg-ods-bg-hover',
33
- sheet: 'flex min-h-[52px] items-center gap-[var(--spacing-system-sf)] py-1.5 text-ods-text-primary',
34
- side: 'flex items-center gap-[var(--spacing-system-xsf)] rounded-md px-[var(--spacing-system-sf)] py-2.5 text-ods-text-secondary transition-colors text-h6 hover:bg-ods-bg-hover hover:text-ods-text-primary',
35
- label:
36
- 'inline-flex items-center gap-[var(--spacing-system-xsf)] text-ods-text-secondary transition-colors text-h6 hover:text-ods-text-primary',
37
- };
38
-
39
- function RowGlyph({ link, box, glyph }: { link: NavLink; box: string; glyph: number }) {
40
- // A server-configured icon (an agent's avatar) is drawn round and whole.
41
- if (link.icon) {
42
- return (
43
- <span
44
- aria-hidden="true"
45
- className={cn('flex shrink-0 items-center justify-center overflow-hidden rounded-full', box)}
46
- >
47
- <EntityIcon icon={link.icon} className="h-full w-full" />
48
- </span>
49
- );
50
- }
51
- if (!link.iconName) return null;
52
- return (
53
- <span
54
- aria-hidden="true"
55
- className={cn(
56
- 'flex shrink-0 items-center justify-center rounded-lg border border-ods-border bg-ods-bg text-ods-text-primary',
57
- box,
58
- )}
59
- >
60
- <EntityIcon icon={{ name: link.iconName }} size={glyph} />
61
- </span>
62
- );
63
- }
64
-
65
- /**
66
- * One navigation row, shared by the desktop panels, the mobile sheet and the
67
- * footer columns: icon tile or agent avatar, label, description, the
68
- * "Built on ..." line and an optional live counter.
69
- */
70
- export function NavItemRow({
71
- link,
72
- variant = 'panel',
73
- renderLink = defaultRenderSiteNavLink,
74
- badge,
75
- onNavigate,
76
- className,
77
- }: NavItemRowProps) {
78
- const label = navLinkLabel(link);
79
- const counter = badge ? <UnreadCountBadge count={badge} className="static shrink-0" /> : null;
80
- // A row with one line of text centres it on its icon; with a description or
81
- // a "Built on" line the label stays level with the icon's top.
82
- const singleLine = !link.description && !link.builtOn;
83
- const rowClassName = cn(
84
- ROW_CLASSES[variant],
85
- variant === 'panel' && singleLine && 'items-center',
86
- NAV_FOCUS_CLASS,
87
- className,
88
- );
89
- const onClick = onNavigate ? () => onNavigate() : undefined;
90
-
91
- let children: React.ReactNode;
92
- if (variant === 'label') {
93
- children = (
94
- <>
95
- {label}
96
- {counter}
97
- </>
98
- );
99
- } else if (variant === 'side') {
100
- children = (
101
- <>
102
- {link.iconName && (
103
- <span aria-hidden="true" className="flex shrink-0">
104
- <EntityIcon icon={{ name: link.iconName }} size={18} />
105
- </span>
106
- )}
107
- <span className="min-w-0 flex-1">{label}</span>
108
- {counter}
109
- </>
110
- );
111
- } else {
112
- const sheet = variant === 'sheet';
113
- children = (
114
- <>
115
- <RowGlyph link={link} box={sheet ? 'h-8 w-8' : 'h-9 w-9'} glyph={sheet ? 18 : 20} />
116
- <span className="flex min-w-0 flex-1 flex-col">
117
- <span className="flex items-center gap-[var(--spacing-system-xsf)] text-h6">
118
- <span className="min-w-0">{label}</span>
119
- {counter}
120
- </span>
121
- {link.description && <span className="text-ods-text-muted text-h6">{link.description}</span>}
122
- {link.builtOn && <span className="text-ods-text-muted text-h6">Built on {link.builtOn}</span>}
123
- </span>
124
- </>
125
- );
126
- }
127
-
128
- return <>{renderLink({ link, className: rowClassName, children, onClick })}</>;
129
- }
@@ -1,196 +0,0 @@
1
- 'use client';
2
-
3
- import type React from 'react';
4
- import { forwardRef, type HTMLAttributes } from 'react';
5
- import type { NavGroup, NavMenu } from '../../types/navigation';
6
- import { cn } from '../../utils/cn';
7
- import { CaseStudyCard } from '../chat/entity-cards/case-study-card';
8
- import { NavItemRow } from './nav-item-row';
9
- import { defaultRenderSiteNavLink, type SiteNavLinkRenderer } from './site-nav-link';
10
-
11
- /** A column with more links than this lays them out in two columns. */
12
- const TWO_COLUMN_OVER = 4;
13
-
14
- /** True when a menu opens the full-width mega panel instead of a dropdown list. */
15
- export function isMegaMenu(menu: Pick<NavMenu, 'columns' | 'features' | 'sideLinks'>): boolean {
16
- return (menu.columns?.length ?? 0) > 1 || !!menu.features?.length || !!menu.sideLinks?.length;
17
- }
18
-
19
- export interface NavPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
20
- menu: NavMenu;
21
- open: boolean;
22
- renderLink?: SiteNavLinkRenderer;
23
- /** Values for `NavLink.badgeKey`. */
24
- badges?: Record<string, number>;
25
- /** Rendered under the links of a menu with `showSocial`. */
26
- social?: React.ReactNode;
27
- /** Called when a link is activated, so the owner can close the menu. */
28
- onNavigate?: () => void;
29
- /**
30
- * How an OPEN mega menu arrived: `open` from a closed bar (the panel fades
31
- * in), `from-start` / `from-end` from the mega menu before or after it in
32
- * the row (the surface stays put and only the content slides in from that
33
- * side). Default `open`.
34
- */
35
- motion?: NavPanelMotion;
36
- /**
37
- * Another mega menu is open: this closed one leaves without a fade, so the
38
- * two panels never show through each other mid-switch.
39
- */
40
- handoff?: boolean;
41
- }
42
-
43
- export type NavPanelMotion = 'open' | 'from-start' | 'from-end';
44
-
45
- const HEADING_CLASS = 'mb-[var(--spacing-system-xsf)] ml-[var(--spacing-system-sf)] text-ods-text-muted text-h5';
46
-
47
- function Column({
48
- group,
49
- wide,
50
- rowProps,
51
- }: {
52
- group: NavGroup;
53
- wide: boolean;
54
- rowProps: Pick<NavPanelProps, 'renderLink' | 'badges' | 'onNavigate'>;
55
- }) {
56
- const twoColumns = wide && group.links.length > TWO_COLUMN_OVER;
57
- return (
58
- <div className={cn('flex min-w-0 flex-col', wide && (twoColumns ? 'flex-[2]' : 'flex-1'))}>
59
- {group.title && <div className={HEADING_CLASS}>{group.title}</div>}
60
- <div
61
- className={
62
- twoColumns
63
- ? 'grid grid-cols-2 gap-x-[var(--spacing-system-mf)] gap-y-[var(--spacing-system-xxs)]'
64
- : 'flex flex-col gap-[var(--spacing-system-xxs)]'
65
- }
66
- >
67
- {group.links.map(link => (
68
- <NavItemRow
69
- key={link.id}
70
- link={link}
71
- renderLink={rowProps.renderLink}
72
- onNavigate={rowProps.onNavigate}
73
- badge={link.badgeKey ? rowProps.badges?.[link.badgeKey] : undefined}
74
- />
75
- ))}
76
- </div>
77
- </div>
78
- );
79
- }
80
-
81
- /**
82
- * The panel a header menu opens, in the one shape its data asks for: a single
83
- * column is a compact dropdown under its trigger; more than one column, or
84
- * features, or side links make the full-width mega menu under the bar.
85
- *
86
- * Always in the DOM, so crawlers see every link; closed, it is `inert` (out of
87
- * the tab order and the accessibility tree, which `aria-hidden` alone would
88
- * not do) and invisible. Plain `<div>`s on purpose: these are disclosures of
89
- * links, not an ARIA menu, so there is no `role="menu"` and Tab moves through
90
- * the links in DOM order.
91
- */
92
- export const NavPanel = forwardRef<HTMLDivElement, NavPanelProps>(function NavPanelImpl(
93
- {
94
- menu,
95
- open,
96
- renderLink = defaultRenderSiteNavLink,
97
- badges,
98
- social,
99
- onNavigate,
100
- motion = 'open',
101
- handoff = false,
102
- className,
103
- ...panelProps
104
- }: NavPanelProps,
105
- ref,
106
- ) {
107
- const columns = menu.columns ?? [];
108
- const mega = isMegaMenu(menu);
109
- const rowProps = { renderLink, badges, onNavigate };
110
- const socialRow = menu.showSocial && social ? social : null;
111
-
112
- return (
113
- <div
114
- inert={!open}
115
- data-state={open ? 'open' : 'closed'}
116
- {...panelProps}
117
- ref={ref}
118
- className={cn(
119
- 'absolute top-full z-[60]',
120
- // Between two mega menus the surface is handed over in one frame (same
121
- // place, same size, same colour) and only the content moves; a fade
122
- // here would let the page show through both for its whole length.
123
- mega && (open ? motion !== 'open' : handoff) ? 'transition-none' : 'transition-opacity duration-150',
124
- mega
125
- ? 'left-0 right-0 border-b border-ods-border bg-ods-card shadow-xl'
126
- : 'left-0 mt-[var(--spacing-system-xxs)] min-w-[260px] rounded-lg border border-ods-border bg-ods-card p-[var(--spacing-system-xsf)] shadow-xl',
127
- open ? 'visible opacity-100' : 'pointer-events-none invisible opacity-0',
128
- className,
129
- )}
130
- >
131
- {mega ? (
132
- // ONE height for every mega menu, so moving between menus (and a
133
- // card's cover loading) never resizes the panel under the pointer.
134
- <div
135
- data-motion={open ? motion : undefined}
136
- className="nav-panel-content flex h-[400px] max-h-[calc(100svh-var(--top-nav-height,72px))] flex-col gap-[var(--spacing-system-mf)] overflow-y-auto px-[var(--spacing-system-lf)] pb-[var(--spacing-system-xlf)] pt-8"
137
- >
138
- <div className="flex min-h-0 flex-1 gap-[var(--spacing-system-xlf)]">
139
- {columns.map(group => (
140
- <Column key={group.id} group={group} wide rowProps={rowProps} />
141
- ))}
142
- {!!menu.features?.length && (
143
- // One row of cards, as many as the bar's width holds: a card that
144
- // would wrap lands on a zero-height row, where its cell clips it.
145
- <div className="grid min-w-0 flex-[3] auto-rows-[0px] grid-cols-[repeat(auto-fit,minmax(240px,1fr))] grid-rows-[minmax(0,1fr)] gap-x-[var(--spacing-system-mf)]">
146
- {menu.features.map(feature => (
147
- <div key={feature.id} className="min-h-0 min-w-0 overflow-hidden">
148
- <CaseStudyCard
149
- size="menu"
150
- study={feature.study}
151
- href={feature.href}
152
- mediaMounted={open}
153
- onNavigate={onNavigate}
154
- className="h-full"
155
- />
156
- </div>
157
- ))}
158
- </div>
159
- )}
160
- {!!menu.sideLinks?.length && (
161
- <div className="flex w-[260px] shrink-0 flex-col gap-0.5 border-l border-ods-border pl-8 pt-5">
162
- {menu.sideTitle && <div className={HEADING_CLASS}>{menu.sideTitle}</div>}
163
- {menu.sideLinks.map(link => (
164
- <NavItemRow
165
- key={link.id}
166
- link={link}
167
- variant="side"
168
- {...rowProps}
169
- badge={link.badgeKey ? badges?.[link.badgeKey] : undefined}
170
- />
171
- ))}
172
- </div>
173
- )}
174
- </div>
175
- {socialRow && (
176
- <div className="border-t border-ods-border px-[var(--spacing-system-sf)] pt-[var(--spacing-system-mf)]">
177
- {socialRow}
178
- </div>
179
- )}
180
- </div>
181
- ) : (
182
- <>
183
- {columns.map(group => (
184
- <Column key={group.id} group={group} wide={false} rowProps={rowProps} />
185
- ))}
186
- {socialRow && (
187
- <div className="mt-[var(--spacing-system-xsf)] border-t border-ods-border px-[var(--spacing-system-sf)] pt-[var(--spacing-system-sf)]">
188
- {socialRow}
189
- </div>
190
- )}
191
- </>
192
- )}
193
- </div>
194
- );
195
- });
196
- NavPanel.displayName = 'NavPanel';