@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.
- package/dist/{chunk-WCVZCGK5.cjs → chunk-2QEUFXNS.cjs} +71 -312
- package/dist/{chunk-N45D4ZXQ.js → chunk-2YXVWZVF.js} +715 -959
- package/dist/{chunk-7OFPU7WE.cjs → chunk-3MX5S4CF.cjs} +2547 -877
- package/dist/{chunk-MB4PNY7C.cjs → chunk-4I45Y3DN.cjs} +6 -6
- package/dist/{chunk-K6O43WDL.cjs → chunk-4KX6OED2.cjs} +46 -46
- package/dist/{chunk-JPG7OHEG.js → chunk-5HBHO5TB.js} +1 -1
- package/dist/{chunk-3PLDQDE6.cjs → chunk-6NJKKO73.cjs} +15 -15
- package/dist/{chunk-O2RSAA5M.js → chunk-6SEANMOU.js} +11 -11
- package/dist/{chunk-WMX26HB5.js → chunk-6XIF2Z7I.js} +3 -3
- package/dist/{chunk-TQ4LB2DS.cjs → chunk-7LS22BZU.cjs} +14 -14
- package/dist/{chunk-7GEBFJT2.js → chunk-7VVFKJY5.js} +3429 -1759
- package/dist/{chunk-DDVBX42E.cjs → chunk-A75QY6CD.cjs} +21 -21
- package/dist/{chunk-OF6GEI5A.cjs → chunk-ABU3TGXA.cjs} +3 -3
- package/dist/{chunk-4CJMQLB5.js → chunk-AQWZNP6A.js} +4 -4
- package/dist/{chunk-FI6GMDO7.js → chunk-CH5232HV.js} +662 -2455
- package/dist/{chunk-Y2GSOUCS.cjs → chunk-D7BTSVMS.cjs} +2 -2
- package/dist/{chunk-4ILM7W2R.js → chunk-DENB2XJC.js} +2 -2
- package/dist/{chunk-6232S532.js → chunk-DW7G4KG3.js} +3 -3
- package/dist/{chunk-3NCM4ZQB.js → chunk-F6AFPNDF.js} +15 -15
- package/dist/{chunk-PXOQIA5K.js → chunk-F6I6XZMN.js} +5 -5
- package/dist/{chunk-G6UMNAVI.js → chunk-FCGC42TS.js} +61 -57
- package/dist/{chunk-BHI6GVC4.js → chunk-G5A3PSPV.js} +691 -1080
- package/dist/{chunk-HZ7CBXPL.cjs → chunk-HPWWNJE7.cjs} +89 -89
- package/dist/{chunk-SW5ZV2BN.cjs → chunk-IHERL5V4.cjs} +34 -34
- package/dist/{chunk-UJ33ZXDT.cjs → chunk-IKTQ3ZAG.cjs} +6 -6
- package/dist/{chunk-SEKYEWQG.cjs → chunk-JEZKGCZI.cjs} +26 -26
- package/dist/{chunk-7SCNMP2B.cjs → chunk-KOWVJD4V.cjs} +56 -56
- package/dist/chunk-LMBC5FFP.js +43 -0
- package/dist/{chunk-F57ECE2I.js → chunk-MFFCUVWG.js} +1 -1
- package/dist/{chunk-KUC673HM.js → chunk-MKXO4Q5P.js} +142 -383
- package/dist/{chunk-6NTPY5QA.cjs → chunk-MMOODW57.cjs} +94 -1887
- package/dist/{chunk-CUME3VTQ.js → chunk-MP2C3LJ4.js} +1 -1
- package/dist/{chunk-VX4E6YSB.cjs → chunk-N3COXDBH.cjs} +5 -41
- package/dist/{chunk-FXJ3WWUB.js → chunk-NDESEPW5.js} +0 -68
- package/dist/chunk-O43V3EOS.js +252 -0
- package/dist/{chunk-PLYEKV3N.cjs → chunk-OHFS7VOU.cjs} +2 -2
- package/dist/{chunk-M75XEEGT.cjs → chunk-OUY3HHW6.cjs} +539 -928
- package/dist/{chunk-CZ7Y7UG4.js → chunk-PDWVPI3S.js} +72 -4
- package/dist/{chunk-TE773HEH.js → chunk-PN2RKPDF.js} +10 -10
- package/dist/{chunk-3EZ4HHAP.js → chunk-QFXRPRFT.js} +3 -3
- package/dist/chunk-QZVSJF2K.cjs +252 -0
- package/dist/chunk-RALRPFDW.cjs +43 -0
- package/dist/{chunk-YGJKKDDM.js → chunk-RBDP4WDE.js} +6 -6
- package/dist/{chunk-LM7TXMLU.cjs → chunk-RKV5B7EU.cjs} +4 -72
- package/dist/{chunk-UVTD52MD.cjs → chunk-TYWLNCNH.cjs} +82 -14
- package/dist/{chunk-IKJA3Y7Z.js → chunk-VA5T32XA.js} +2 -2
- package/dist/{chunk-GWQEYPYM.cjs → chunk-VESKITRQ.cjs} +732 -728
- package/dist/{chunk-RQRX4YC3.cjs → chunk-VHCACDKP.cjs} +39 -39
- package/dist/{chunk-4IBPRIQH.js → chunk-VR2ZLFCP.js} +1 -1
- package/dist/{chunk-RTIPR53L.cjs → chunk-WA6GUYQ4.cjs} +104 -104
- package/dist/{chunk-T5TMCHWM.js → chunk-X5MQ7JRP.js} +9 -9
- package/dist/{chunk-LLSUVPQC.cjs → chunk-X72J4ACL.cjs} +8 -252
- package/dist/{chunk-UDFRUKLC.cjs → chunk-XOYH2YQE.cjs} +2 -2
- package/dist/{chunk-LVSFKENJ.cjs → chunk-YFBM2K6X.cjs} +4 -4
- package/dist/{chunk-DWPAAWMR.js → chunk-YHM5AWTT.js} +6 -6
- package/dist/{chunk-NCPTID44.js → chunk-Z2VNQQ74.js} +942 -978
- package/dist/{chunk-STHZBOQI.cjs → chunk-ZSAGLQOD.cjs} +17 -17
- package/dist/{chunk-VZICIQW2.js → chunk-ZSAO2HBW.js} +15 -15
- package/dist/components/case-studies/index.cjs +32 -30
- package/dist/components/case-studies/index.js +16 -14
- package/dist/components/chat/entity-cards/case-study-card.d.ts +4 -11
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +11 -9
- package/dist/components/chat/index.js +16 -14
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +14 -12
- package/dist/components/contact/index.js +13 -11
- package/dist/components/docs/index.cjs +17 -15
- package/dist/components/docs/index.js +16 -14
- package/dist/components/embeds/index.cjs +14 -12
- package/dist/components/embeds/index.js +13 -11
- package/dist/components/faq/index.cjs +15 -13
- package/dist/components/faq/index.js +14 -12
- package/dist/components/features/index.cjs +14 -12
- package/dist/components/features/index.js +23 -21
- package/dist/components/footer-waitlist-button.d.ts +29 -0
- package/dist/components/footer-waitlist-button.d.ts.map +1 -0
- package/dist/components/footer.d.ts +47 -0
- package/dist/components/footer.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +120 -118
- package/dist/components/help-center-pages/index.js +35 -33
- package/dist/components/icons/index.cjs +5 -3
- package/dist/components/icons/index.js +11 -9
- package/dist/components/icons-v2-generated/index.cjs +5 -3
- package/dist/components/icons-v2-generated/index.js +4 -2
- package/dist/components/index.cjs +159 -86
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +402 -329
- package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
- package/dist/components/meeting-scheduler/index.cjs +64 -62
- package/dist/components/meeting-scheduler/index.js +14 -12
- package/dist/components/navigation/client-only-header.d.ts +8 -0
- package/dist/components/navigation/client-only-header.d.ts.map +1 -0
- package/dist/components/navigation/header-skeleton.d.ts +12 -0
- package/dist/components/navigation/header-skeleton.d.ts.map +1 -0
- package/dist/components/navigation/header.d.ts +9 -0
- package/dist/components/navigation/header.d.ts.map +1 -0
- package/dist/components/navigation/index.cjs +16 -28
- package/dist/components/navigation/index.d.ts +10 -14
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +26 -38
- package/dist/components/navigation/mingo-ai-button.d.ts +3 -38
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/navigation/mobile-nav-panel.d.ts +10 -0
- package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -0
- package/dist/components/navigation/navigation-sidebar-item.d.ts +2 -1
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/navigation/top-navigation.d.ts +2 -2
- package/dist/components/navigation/top-navigation.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +17 -15
- package/dist/components/onboarding-guides/index.js +16 -14
- package/dist/components/related-content/index.cjs +8 -10
- package/dist/components/related-content/index.js +7 -9
- package/dist/components/tickets/index.cjs +18 -16
- package/dist/components/tickets/index.js +17 -15
- package/dist/components/ui/index.cjs +14 -12
- package/dist/components/ui/index.js +19 -17
- package/dist/hooks/index.cjs +11 -19
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +11 -19
- package/dist/hooks/ui/index.d.ts +0 -3
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/index.cjs +22 -55
- package/dist/index.js +196 -229
- package/dist/types/case-study.d.ts +0 -9
- package/dist/types/case-study.d.ts.map +1 -1
- package/dist/types/navigation.d.ts +65 -95
- package/dist/types/navigation.d.ts.map +1 -1
- package/dist/utils/index.cjs +0 -55
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -50
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/case-study-card.tsx +5 -115
- package/src/components/chat/utils/icon-library.tsx +1 -35
- package/src/components/footer-waitlist-button.tsx +53 -0
- package/src/components/footer.tsx +195 -0
- package/src/components/index.ts +2 -0
- package/src/components/loading/page-layout-skeleton.tsx +1 -1
- package/src/components/navigation/client-only-header.tsx +22 -0
- package/src/components/navigation/header-skeleton.tsx +74 -0
- package/src/components/navigation/header.tsx +429 -0
- package/src/components/navigation/index.ts +14 -21
- package/src/components/navigation/mingo-ai-button.tsx +63 -139
- package/src/components/navigation/mobile-nav-panel.tsx +155 -0
- package/src/components/navigation/navigation-sidebar-item.tsx +2 -1
- package/src/components/navigation/ticket-alerts-button.tsx +2 -2
- package/src/components/navigation/top-navigation.tsx +2 -2
- package/src/hooks/index.ts +0 -1
- package/src/hooks/ui/index.ts +0 -3
- package/src/styles/chat-animations.css +2 -57
- package/src/types/case-study.ts +0 -16
- package/src/types/navigation.ts +66 -80
- package/src/utils/index.ts +0 -1
- package/src/utils/source-icons.ts +0 -13
- package/dist/components/navigation/mobile-nav-sheet-id.d.ts +0 -3
- package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +0 -1
- package/dist/components/navigation/mobile-nav-sheet.d.ts +0 -48
- package/dist/components/navigation/mobile-nav-sheet.d.ts.map +0 -1
- package/dist/components/navigation/nav-item-row.d.ts +0 -27
- package/dist/components/navigation/nav-item-row.d.ts.map +0 -1
- package/dist/components/navigation/nav-panel.d.ts +0 -43
- package/dist/components/navigation/nav-panel.d.ts.map +0 -1
- package/dist/components/navigation/site-footer.d.ts +0 -28
- package/dist/components/navigation/site-footer.d.ts.map +0 -1
- package/dist/components/navigation/site-header.d.ts +0 -61
- package/dist/components/navigation/site-header.d.ts.map +0 -1
- package/dist/components/navigation/site-nav-link.d.ts +0 -26
- package/dist/components/navigation/site-nav-link.d.ts.map +0 -1
- package/dist/hooks/ui/use-active-section.d.ts +0 -10
- package/dist/hooks/ui/use-active-section.d.ts.map +0 -1
- package/dist/hooks/ui/use-nav-menus.d.ts +0 -48
- package/dist/hooks/ui/use-nav-menus.d.ts.map +0 -1
- package/dist/hooks/ui/use-visitor-os.d.ts +0 -15
- package/dist/hooks/ui/use-visitor-os.d.ts.map +0 -1
- package/dist/hooks/use-location-hash.d.ts +0 -9
- package/dist/hooks/use-location-hash.d.ts.map +0 -1
- package/dist/utils/visitor-os.d.ts +0 -45
- package/dist/utils/visitor-os.d.ts.map +0 -1
- package/src/components/navigation/mobile-nav-sheet-id.ts +0 -2
- package/src/components/navigation/mobile-nav-sheet.tsx +0 -241
- package/src/components/navigation/nav-item-row.tsx +0 -129
- package/src/components/navigation/nav-panel.tsx +0 -196
- package/src/components/navigation/site-footer.tsx +0 -256
- package/src/components/navigation/site-header.tsx +0 -339
- package/src/components/navigation/site-nav-link.tsx +0 -41
- package/src/hooks/ui/use-active-section.ts +0 -44
- package/src/hooks/ui/use-nav-menus.ts +0 -200
- package/src/hooks/ui/use-visitor-os.ts +0 -33
- package/src/hooks/use-location-hash.ts +0 -34
- 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';
|