@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,200 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
useCallback,
|
|
5
|
-
useEffect,
|
|
6
|
-
useId,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
type MouseEvent as ReactMouseEvent,
|
|
10
|
-
type RefCallback,
|
|
11
|
-
} from 'react';
|
|
12
|
-
|
|
13
|
-
export interface UseNavMenusOptions {
|
|
14
|
-
/** The host router's pathname: any change closes the open menu. */
|
|
15
|
-
pathname: string;
|
|
16
|
-
/** Hover intent: how long the pointer rests on a trigger before it opens. */
|
|
17
|
-
hoverOpenMs?: number;
|
|
18
|
-
/** How long after the pointer left the trigger and the panel the menu closes. */
|
|
19
|
-
hoverCloseMs?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface NavMenuTriggerProps {
|
|
23
|
-
ref: RefCallback<HTMLElement>;
|
|
24
|
-
'aria-expanded': boolean;
|
|
25
|
-
'aria-controls': string;
|
|
26
|
-
onClick: (event: ReactMouseEvent) => void;
|
|
27
|
-
onMouseEnter: () => void;
|
|
28
|
-
onMouseLeave: () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface NavMenuPanelProps {
|
|
32
|
-
ref: RefCallback<HTMLElement>;
|
|
33
|
-
id: string;
|
|
34
|
-
inert: boolean;
|
|
35
|
-
'data-state': 'open' | 'closed';
|
|
36
|
-
onMouseEnter: () => void;
|
|
37
|
-
onMouseLeave: () => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface UseNavMenusResult {
|
|
41
|
-
/** The id of the open menu, or `null`. One menu is open at a time. */
|
|
42
|
-
openId: string | null;
|
|
43
|
-
close: () => void;
|
|
44
|
-
getTriggerProps: (id: string) => NavMenuTriggerProps;
|
|
45
|
-
getPanelProps: (id: string) => NavMenuPanelProps;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Open state for a row of disclosure menus (the site header's dropdowns and
|
|
50
|
-
* mega menus).
|
|
51
|
-
*
|
|
52
|
-
* - Click toggles; the trigger is a `<button>`, so Enter and Space arrive as
|
|
53
|
-
* that same click.
|
|
54
|
-
* - Hover on a trigger opens after `hoverOpenMs`; with a menu already open,
|
|
55
|
-
* hovering another trigger switches to it at once. Leaving the trigger and the
|
|
56
|
-
* panel closes after `hoverCloseMs`; entering the panel or any trigger
|
|
57
|
-
* cancels that close, so the pointer can cross the gap between them.
|
|
58
|
-
* - Escape closes and returns focus to the trigger. A pointer-down outside
|
|
59
|
-
* every trigger and panel closes. A `pathname` change closes.
|
|
60
|
-
* - No arrow-key roving: these are plain disclosures, Tab moves in DOM order.
|
|
61
|
-
*/
|
|
62
|
-
export function useNavMenus({
|
|
63
|
-
pathname,
|
|
64
|
-
hoverOpenMs = 150,
|
|
65
|
-
hoverCloseMs = 300,
|
|
66
|
-
}: UseNavMenusOptions): UseNavMenusResult {
|
|
67
|
-
const baseId = useId();
|
|
68
|
-
// The open menu is remembered WITH the pathname it was opened on, so a
|
|
69
|
-
// navigation closes it during render: no effect, no frame of a stale panel.
|
|
70
|
-
// The stale entry is dropped in the same render, or coming back to that
|
|
71
|
-
// pathname would reopen it.
|
|
72
|
-
const [opened, setOpened] = useState<{ id: string; pathname: string } | null>(null);
|
|
73
|
-
const stale = opened !== null && opened.pathname !== pathname;
|
|
74
|
-
if (stale) setOpened(null);
|
|
75
|
-
const openId = opened && !stale ? opened.id : null;
|
|
76
|
-
|
|
77
|
-
const triggers = useRef(new Map<string, HTMLElement>());
|
|
78
|
-
const panels = useRef(new Map<string, HTMLElement>());
|
|
79
|
-
const openTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
80
|
-
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
81
|
-
// The menu a HOVER opened, until a click claims it (see `onClick`).
|
|
82
|
-
const openedByHover = useRef<string | null>(null);
|
|
83
|
-
|
|
84
|
-
const clearTimers = useCallback(() => {
|
|
85
|
-
if (openTimer.current) clearTimeout(openTimer.current);
|
|
86
|
-
if (closeTimer.current) clearTimeout(closeTimer.current);
|
|
87
|
-
openTimer.current = null;
|
|
88
|
-
closeTimer.current = null;
|
|
89
|
-
}, []);
|
|
90
|
-
|
|
91
|
-
useEffect(() => clearTimers, [clearTimers]);
|
|
92
|
-
|
|
93
|
-
const close = useCallback(() => {
|
|
94
|
-
clearTimers();
|
|
95
|
-
setOpened(null);
|
|
96
|
-
}, [clearTimers]);
|
|
97
|
-
|
|
98
|
-
const cancelClose = useCallback(() => {
|
|
99
|
-
if (closeTimer.current) clearTimeout(closeTimer.current);
|
|
100
|
-
closeTimer.current = null;
|
|
101
|
-
}, []);
|
|
102
|
-
|
|
103
|
-
const scheduleClose = useCallback(() => {
|
|
104
|
-
if (openTimer.current) clearTimeout(openTimer.current);
|
|
105
|
-
openTimer.current = null;
|
|
106
|
-
cancelClose();
|
|
107
|
-
closeTimer.current = setTimeout(() => {
|
|
108
|
-
closeTimer.current = null;
|
|
109
|
-
setOpened(null);
|
|
110
|
-
}, hoverCloseMs);
|
|
111
|
-
}, [cancelClose, hoverCloseMs]);
|
|
112
|
-
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (!openId) return undefined;
|
|
115
|
-
|
|
116
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
117
|
-
if (event.key !== 'Escape') return;
|
|
118
|
-
const trigger = triggers.current.get(openId);
|
|
119
|
-
close();
|
|
120
|
-
trigger?.focus({ preventScroll: true });
|
|
121
|
-
};
|
|
122
|
-
const handlePointerDown = (event: PointerEvent) => {
|
|
123
|
-
const target = event.target;
|
|
124
|
-
if (!(target instanceof Node)) return;
|
|
125
|
-
for (const el of triggers.current.values()) if (el.contains(target)) return;
|
|
126
|
-
for (const el of panels.current.values()) if (el.contains(target)) return;
|
|
127
|
-
close();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
131
|
-
document.addEventListener('pointerdown', handlePointerDown);
|
|
132
|
-
return () => {
|
|
133
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
134
|
-
document.removeEventListener('pointerdown', handlePointerDown);
|
|
135
|
-
};
|
|
136
|
-
}, [openId, close]);
|
|
137
|
-
|
|
138
|
-
const getTriggerProps = useCallback(
|
|
139
|
-
(id: string): NavMenuTriggerProps => ({
|
|
140
|
-
ref: el => {
|
|
141
|
-
if (el) triggers.current.set(id, el);
|
|
142
|
-
else triggers.current.delete(id);
|
|
143
|
-
},
|
|
144
|
-
'aria-expanded': openId === id,
|
|
145
|
-
'aria-controls': `${baseId}-panel-${id}`,
|
|
146
|
-
onClick: () => {
|
|
147
|
-
// A touch tap fires mouseenter first: drop its pending open, or it
|
|
148
|
-
// would reopen the menu this click just closed.
|
|
149
|
-
clearTimers();
|
|
150
|
-
// A click on a menu the pointer's hover already opened means "open
|
|
151
|
-
// it": it keeps the menu open instead of toggling it shut (a mouse
|
|
152
|
-
// user hovers, then clicks; a touch tap fires mouseenter, then click).
|
|
153
|
-
// The click claims the menu, so the next click on it closes it.
|
|
154
|
-
const hovered = openedByHover.current === id;
|
|
155
|
-
openedByHover.current = null;
|
|
156
|
-
if (hovered) {
|
|
157
|
-
setOpened({ id, pathname });
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
setOpened(prev => (prev && prev.id === id && prev.pathname === pathname ? null : { id, pathname }));
|
|
161
|
-
},
|
|
162
|
-
onMouseEnter: () => {
|
|
163
|
-
clearTimers();
|
|
164
|
-
if (openId === id) return;
|
|
165
|
-
// A menu is already open: the visitor is browsing the row, so the next
|
|
166
|
-
// one opens at once. The hover-intent wait is only for the FIRST open
|
|
167
|
-
// (it filters a pointer merely crossing the bar).
|
|
168
|
-
if (openId !== null) {
|
|
169
|
-
openedByHover.current = id;
|
|
170
|
-
setOpened({ id, pathname });
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
openTimer.current = setTimeout(() => {
|
|
174
|
-
openTimer.current = null;
|
|
175
|
-
openedByHover.current = id;
|
|
176
|
-
setOpened({ id, pathname });
|
|
177
|
-
}, hoverOpenMs);
|
|
178
|
-
},
|
|
179
|
-
onMouseLeave: scheduleClose,
|
|
180
|
-
}),
|
|
181
|
-
[baseId, openId, pathname, hoverOpenMs, clearTimers, scheduleClose],
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
const getPanelProps = useCallback(
|
|
185
|
-
(id: string): NavMenuPanelProps => ({
|
|
186
|
-
ref: el => {
|
|
187
|
-
if (el) panels.current.set(id, el);
|
|
188
|
-
else panels.current.delete(id);
|
|
189
|
-
},
|
|
190
|
-
id: `${baseId}-panel-${id}`,
|
|
191
|
-
inert: openId !== id,
|
|
192
|
-
'data-state': openId === id ? 'open' : 'closed',
|
|
193
|
-
onMouseEnter: cancelClose,
|
|
194
|
-
onMouseLeave: scheduleClose,
|
|
195
|
-
}),
|
|
196
|
-
[baseId, openId, cancelClose, scheduleClose],
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return { openId, close, getTriggerProps, getPanelProps };
|
|
200
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
4
|
-
import { detectVisitorOs, type VisitorOs } from '../../utils/visitor-os';
|
|
5
|
-
|
|
6
|
-
export type VisitorOsState = { known: false; os: null } | { known: true; os: VisitorOs | null };
|
|
7
|
-
|
|
8
|
-
const UNKNOWN: VisitorOsState = { known: false, os: null };
|
|
9
|
-
// One state object per answer, so the snapshot is stable between reads.
|
|
10
|
-
const KNOWN = new Map<VisitorOs | null, VisitorOsState>();
|
|
11
|
-
|
|
12
|
-
const subscribe = () => () => {};
|
|
13
|
-
const getServerSnapshot = () => UNKNOWN;
|
|
14
|
-
function getSnapshot(): VisitorOsState {
|
|
15
|
-
// `userAgentData` is the Client Hints API (Chromium only, so not in every DOM typing).
|
|
16
|
-
const stated = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform;
|
|
17
|
-
const os = detectVisitorOs(navigator.userAgent, { platform: stated, maxTouchPoints: navigator.maxTouchPoints });
|
|
18
|
-
let state = KNOWN.get(os);
|
|
19
|
-
if (!state) {
|
|
20
|
-
state = { known: true, os };
|
|
21
|
-
KNOWN.set(os, state);
|
|
22
|
-
}
|
|
23
|
-
return state;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The visitor's system in the browser, through the one detection rule
|
|
28
|
-
* (`utils/visitor-os`). `known` is false on the server and while React
|
|
29
|
-
* hydrates: the server cannot see the browser, and a guess would mismatch.
|
|
30
|
-
*/
|
|
31
|
-
export function useVisitorOs(): VisitorOsState {
|
|
32
|
-
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
33
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
4
|
-
import { normalizeHashFragment } from '../utils/same-page-hash-nav';
|
|
5
|
-
|
|
6
|
-
/** `navigateSamePageHash` dispatches `hashchange` itself; `popstate` covers back and forward. */
|
|
7
|
-
const HASH_EVENTS = ['hashchange', 'popstate'] as const;
|
|
8
|
-
|
|
9
|
-
function subscribe(onChange: () => void): () => void {
|
|
10
|
-
for (const event of HASH_EVENTS) window.addEventListener(event, onChange);
|
|
11
|
-
return () => {
|
|
12
|
-
for (const event of HASH_EVENTS) window.removeEventListener(event, onChange);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function readHash(): string {
|
|
17
|
-
const raw = normalizeHashFragment(window.location.hash).replace(/^#/, '');
|
|
18
|
-
try {
|
|
19
|
-
return decodeURIComponent(raw);
|
|
20
|
-
} catch {
|
|
21
|
-
return raw;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The URL's hash without its `#`, or `''`: the READ side of same-page anchor
|
|
27
|
-
* navigation (`navigateSamePageHash` writes it, `useScrollToHash` scrolls to
|
|
28
|
-
* it). For a component whose state follows the anchor: a tab group that opens
|
|
29
|
-
* the tab a link names. Empty on the server and during hydration (the server
|
|
30
|
-
* never sees a hash), then the real one.
|
|
31
|
-
*/
|
|
32
|
-
export function useLocationHash(): string {
|
|
33
|
-
return useSyncExternalStore(subscribe, readHash, () => '');
|
|
34
|
-
}
|
package/src/utils/visitor-os.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE detection of the system a visitor is on. One rule, read by everything
|
|
3
|
-
* that depends on it: which installer the desktop app's download offers, and
|
|
4
|
-
* which shortcut key a hint shows. Never test `navigator.platform` or a
|
|
5
|
-
* User-Agent anywhere else.
|
|
6
|
-
*
|
|
7
|
-
* Pure and server-safe: the server passes the request's User-Agent, the browser
|
|
8
|
-
* goes through `useVisitorOs` (hooks), which also passes the touch-point count.
|
|
9
|
-
*/
|
|
10
|
-
export type VisitorOs = 'mac' | 'windows' | 'linux' | 'ios' | 'android';
|
|
11
|
-
|
|
12
|
-
/** The systems the desktop app installs on. */
|
|
13
|
-
export const DESKTOP_OSES = ['mac', 'windows', 'linux'] as const;
|
|
14
|
-
export type DesktopOs = (typeof DESKTOP_OSES)[number];
|
|
15
|
-
|
|
16
|
-
export interface VisitorOsHints {
|
|
17
|
-
/**
|
|
18
|
-
* The platform the browser STATES through User-Agent Client Hints:
|
|
19
|
-
* `navigator.userAgentData.platform` in the browser, the `Sec-CH-UA-Platform`
|
|
20
|
-
* request header on the server (Chromium browsers send it on every request).
|
|
21
|
-
* It is the current standard and is read first: browsers are freezing the
|
|
22
|
-
* User-Agent string, so the hint is the value they keep accurate. Safari and
|
|
23
|
-
* Firefox send none, which is why the User-Agent stays the fallback.
|
|
24
|
-
*/
|
|
25
|
-
platform?: string | null;
|
|
26
|
-
/**
|
|
27
|
-
* `navigator.maxTouchPoints`, when the caller is a browser. An iPad asks for
|
|
28
|
-
* the desktop site by default and then calls itself a Macintosh; a Macintosh
|
|
29
|
-
* with a touch screen does not exist, so touch points tell the two apart. The
|
|
30
|
-
* server has no such signal and reads that iPad as a Mac.
|
|
31
|
-
*/
|
|
32
|
-
maxTouchPoints?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Client Hints platform names (the header quotes them) to ours. Chrome OS runs Linux apps. */
|
|
36
|
-
const HINT_PLATFORMS: Record<string, VisitorOs> = {
|
|
37
|
-
macos: 'mac',
|
|
38
|
-
windows: 'windows',
|
|
39
|
-
linux: 'linux',
|
|
40
|
-
'chrome os': 'linux',
|
|
41
|
-
'chromium os': 'linux',
|
|
42
|
-
android: 'android',
|
|
43
|
-
ios: 'ios',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The visitor's system, or null when nothing names one. Order: the Client Hints
|
|
48
|
-
* platform, then the User-Agent, with touch points separating an iPad from a Mac.
|
|
49
|
-
*/
|
|
50
|
-
export function detectVisitorOs(userAgent: string | null | undefined, hints: VisitorOsHints = {}): VisitorOs | null {
|
|
51
|
-
const touchMac = (os: VisitorOs) => (os === 'mac' && (hints.maxTouchPoints ?? 0) > 1 ? 'ios' : os);
|
|
52
|
-
const hinted = HINT_PLATFORMS[(hints.platform ?? '').replace(/"/g, '').trim().toLowerCase()];
|
|
53
|
-
if (hinted) return touchMac(hinted);
|
|
54
|
-
|
|
55
|
-
const ua = userAgent ?? '';
|
|
56
|
-
if (!ua) return null;
|
|
57
|
-
// Phones and tablets first: Android's User-Agent also says "Linux".
|
|
58
|
-
if (/iPhone|iPad|iPod/i.test(ua)) return 'ios';
|
|
59
|
-
if (/Android/i.test(ua)) return 'android';
|
|
60
|
-
if (/Windows NT|Win64|Win32/i.test(ua)) return 'windows';
|
|
61
|
-
if (/Macintosh|Mac OS X/i.test(ua)) return touchMac('mac');
|
|
62
|
-
if (/Linux|X11|CrOS/i.test(ua)) return 'linux';
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** The system as a desktop one, or null for a phone, a tablet or an unknown system. */
|
|
67
|
-
export function desktopOsOf(os: VisitorOs | null): DesktopOs | null {
|
|
68
|
-
return os !== null && (DESKTOP_OSES as readonly string[]).includes(os) ? (os as DesktopOs) : null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** The desktop system a User-Agent comes from: `desktopOsOf(detectVisitorOs(...))`. */
|
|
72
|
-
export function detectDesktopOs(userAgent: string | null | undefined, hints?: VisitorOsHints): DesktopOs | null {
|
|
73
|
-
return desktopOsOf(detectVisitorOs(userAgent, hints));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Apple systems use the Command key for shortcuts; every other system uses Control. */
|
|
77
|
-
export function usesCommandKey(os: VisitorOs | null): boolean {
|
|
78
|
-
return os === 'mac' || os === 'ios';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** A shortcut as its hint reads on the visitor's system: `⌘K` or `Ctrl K`. */
|
|
82
|
-
export function shortcutLabel(os: VisitorOs | null, key: string): string {
|
|
83
|
-
return usesCommandKey(os) ? `⌘${key}` : `Ctrl ${key}`;
|
|
84
|
-
}
|