@flamingo-stack/openframe-frontend-core 0.0.704 → 0.0.705-snapshot.20261006160157
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-7LFIKJX2.js → chunk-3EZ4HHAP.js} +3 -3
- package/dist/{chunk-I7DFUQMN.js → chunk-3NCM4ZQB.js} +17 -18
- package/dist/{chunk-4DQMTBMK.cjs → chunk-3PLDQDE6.cjs} +15 -15
- package/dist/{chunk-VTASHWER.js → chunk-4CJMQLB5.js} +4 -4
- package/dist/{chunk-VR2ZLFCP.js → chunk-4IBPRIQH.js} +1 -1
- package/dist/{chunk-ZQFZSFQI.js → chunk-4ILM7W2R.js} +2 -2
- package/dist/{chunk-FXENTYE5.js → chunk-6232S532.js} +3 -3
- package/dist/{chunk-MMOODW57.cjs → chunk-6NTPY5QA.cjs} +1887 -94
- package/dist/{chunk-2JDVL7UV.js → chunk-7GEBFJT2.js} +1829 -3447
- package/dist/{chunk-LDPEQGKT.cjs → chunk-7OFPU7WE.cjs} +942 -2560
- package/dist/{chunk-L3IC42IF.cjs → chunk-7SCNMP2B.cjs} +57 -58
- package/dist/{chunk-2G546OND.js → chunk-BHI6GVC4.js} +1094 -693
- package/dist/{chunk-MP2C3LJ4.js → chunk-CUME3VTQ.js} +1 -1
- package/dist/{chunk-WUJNYGO2.js → chunk-CZ7Y7UG4.js} +4 -76
- package/dist/{chunk-7JIL3WOH.cjs → chunk-DDVBX42E.cjs} +21 -21
- package/dist/{chunk-6WQIUQUC.js → chunk-DWPAAWMR.js} +6 -6
- package/dist/{chunk-MFFCUVWG.js → chunk-F57ECE2I.js} +1 -1
- package/dist/{chunk-CH5232HV.js → chunk-FI6GMDO7.js} +2455 -662
- package/dist/{chunk-NDESEPW5.js → chunk-FXJ3WWUB.js} +68 -0
- package/dist/{chunk-4KGXLVCF.js → chunk-G6UMNAVI.js} +57 -61
- package/dist/{chunk-LTSTC3N5.cjs → chunk-GWQEYPYM.cjs} +728 -732
- package/dist/{chunk-N3TO7JV3.cjs → chunk-HZ7CBXPL.cjs} +89 -89
- package/dist/{chunk-XATTBO5X.js → chunk-IKJA3Y7Z.js} +2 -2
- package/dist/{chunk-5HBHO5TB.js → chunk-JPG7OHEG.js} +1 -1
- package/dist/{chunk-AMYY3PNL.cjs → chunk-K6O43WDL.cjs} +46 -46
- package/dist/{chunk-IHU5ANUY.js → chunk-KUC673HM.js} +383 -142
- package/dist/{chunk-X72J4ACL.cjs → chunk-LLSUVPQC.cjs} +252 -8
- package/dist/{chunk-RKV5B7EU.cjs → chunk-LM7TXMLU.cjs} +72 -4
- package/dist/{chunk-XJKZ4IKT.cjs → chunk-LVSFKENJ.cjs} +4 -4
- package/dist/{chunk-IZGDG5X6.cjs → chunk-M75XEEGT.cjs} +940 -539
- package/dist/{chunk-UK62YTH6.cjs → chunk-MB4PNY7C.cjs} +6 -6
- package/dist/{chunk-2YXVWZVF.js → chunk-N45D4ZXQ.js} +959 -715
- package/dist/{chunk-Z2VNQQ74.js → chunk-NCPTID44.js} +978 -942
- package/dist/{chunk-JEYRXT5L.js → chunk-O2RSAA5M.js} +11 -11
- package/dist/{chunk-ABU3TGXA.cjs → chunk-OF6GEI5A.cjs} +3 -3
- package/dist/{chunk-OHFS7VOU.cjs → chunk-PLYEKV3N.cjs} +2 -2
- package/dist/{chunk-5QJDM6UV.js → chunk-PXOQIA5K.js} +5 -5
- package/dist/{chunk-VHCACDKP.cjs → chunk-RQRX4YC3.cjs} +39 -39
- package/dist/{chunk-HFJW24JJ.cjs → chunk-RTIPR53L.cjs} +104 -104
- package/dist/{chunk-262YQFOS.cjs → chunk-SEKYEWQG.cjs} +26 -26
- package/dist/{chunk-SYXQLETR.cjs → chunk-STHZBOQI.cjs} +17 -17
- package/dist/{chunk-MI3JUJ75.cjs → chunk-SW5ZV2BN.cjs} +34 -34
- package/dist/{chunk-QVSFBSWV.js → chunk-T5TMCHWM.js} +9 -9
- package/dist/{chunk-ERCV6HHJ.js → chunk-TE773HEH.js} +10 -10
- package/dist/{chunk-C7XGRLT5.cjs → chunk-TQ4LB2DS.cjs} +14 -14
- package/dist/{chunk-XOYH2YQE.cjs → chunk-UDFRUKLC.cjs} +2 -2
- package/dist/{chunk-XIFOBQRQ.cjs → chunk-UJ33ZXDT.cjs} +6 -6
- package/dist/{chunk-C3BWIU7R.cjs → chunk-UVTD52MD.cjs} +14 -86
- package/dist/{chunk-N3COXDBH.cjs → chunk-VX4E6YSB.cjs} +41 -5
- package/dist/{chunk-HJAYVKWQ.js → chunk-VZICIQW2.js} +15 -15
- package/dist/{chunk-MGGGBHYD.cjs → chunk-WCVZCGK5.cjs} +312 -71
- package/dist/{chunk-6XIF2Z7I.js → chunk-WMX26HB5.js} +3 -3
- package/dist/{chunk-D7BTSVMS.cjs → chunk-Y2GSOUCS.cjs} +2 -2
- package/dist/{chunk-NRLJUBCS.js → chunk-YGJKKDDM.js} +6 -6
- package/dist/components/case-studies/index.cjs +30 -32
- package/dist/components/case-studies/index.js +14 -16
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +8 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/case-study-card.d.ts +11 -4
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +11 -11
- package/dist/components/chat/index.d.ts +1 -1
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +16 -16
- package/dist/components/chat/mingo-chat-rail.d.ts +3 -1
- package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -1
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +12 -14
- package/dist/components/contact/index.js +11 -13
- package/dist/components/docs/index.cjs +15 -17
- package/dist/components/docs/index.js +14 -16
- package/dist/components/embeds/index.cjs +12 -14
- package/dist/components/embeds/index.js +11 -13
- package/dist/components/faq/index.cjs +13 -15
- package/dist/components/faq/index.js +12 -14
- package/dist/components/features/index.cjs +12 -14
- package/dist/components/features/index.js +21 -23
- package/dist/components/help-center-pages/index.cjs +118 -120
- package/dist/components/help-center-pages/index.js +33 -35
- package/dist/components/icons/index.cjs +3 -5
- package/dist/components/icons/index.js +9 -11
- package/dist/components/icons-v2-generated/index.cjs +3 -5
- package/dist/components/icons-v2-generated/index.js +2 -4
- package/dist/components/index.cjs +89 -158
- package/dist/components/index.d.ts +0 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +333 -402
- package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
- package/dist/components/meeting-scheduler/index.cjs +62 -64
- package/dist/components/meeting-scheduler/index.js +12 -14
- package/dist/components/navigation/app-layout-side-panel.d.ts +18 -1
- package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +30 -16
- package/dist/components/navigation/index.d.ts +15 -10
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +40 -26
- package/dist/components/navigation/mingo-ai-button.d.ts +38 -3
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/navigation/mobile-nav-sheet-id.d.ts +3 -0
- package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +1 -0
- package/dist/components/navigation/mobile-nav-sheet.d.ts +48 -0
- package/dist/components/navigation/mobile-nav-sheet.d.ts.map +1 -0
- package/dist/components/navigation/nav-item-row.d.ts +27 -0
- package/dist/components/navigation/nav-item-row.d.ts.map +1 -0
- package/dist/components/navigation/nav-panel.d.ts +43 -0
- package/dist/components/navigation/nav-panel.d.ts.map +1 -0
- package/dist/components/navigation/navigation-sidebar-item.d.ts +1 -2
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/navigation/site-footer.d.ts +28 -0
- package/dist/components/navigation/site-footer.d.ts.map +1 -0
- package/dist/components/navigation/site-header.d.ts +61 -0
- package/dist/components/navigation/site-header.d.ts.map +1 -0
- package/dist/components/navigation/site-nav-link.d.ts +26 -0
- package/dist/components/navigation/site-nav-link.d.ts.map +1 -0
- 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 +15 -17
- package/dist/components/onboarding-guides/index.js +14 -16
- package/dist/components/related-content/index.cjs +10 -8
- package/dist/components/related-content/index.js +9 -7
- package/dist/components/tickets/index.cjs +16 -18
- package/dist/components/tickets/index.js +15 -17
- package/dist/components/ui/index.cjs +14 -14
- package/dist/components/ui/index.js +19 -19
- package/dist/hooks/index.cjs +19 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +19 -11
- package/dist/hooks/ui/index.d.ts +3 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/use-active-section.d.ts +10 -0
- package/dist/hooks/ui/use-active-section.d.ts.map +1 -0
- package/dist/hooks/ui/use-nav-menus.d.ts +48 -0
- package/dist/hooks/ui/use-nav-menus.d.ts.map +1 -0
- package/dist/hooks/ui/use-visitor-os.d.ts +15 -0
- package/dist/hooks/ui/use-visitor-os.d.ts.map +1 -0
- package/dist/hooks/use-location-hash.d.ts +9 -0
- package/dist/hooks/use-location-hash.d.ts.map +1 -0
- package/dist/index.cjs +58 -21
- package/dist/index.js +233 -196
- package/dist/types/case-study.d.ts +9 -0
- package/dist/types/case-study.d.ts.map +1 -1
- package/dist/types/navigation.d.ts +95 -65
- package/dist/types/navigation.d.ts.map +1 -1
- package/dist/utils/index.cjs +55 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +50 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/dist/utils/visitor-os.d.ts +45 -0
- package/dist/utils/visitor-os.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/chat/chat-input.tsx +5 -1
- package/src/components/chat/chat-message-enhanced.tsx +4 -3
- package/src/components/chat/embeddable-chat.tsx +64 -6
- package/src/components/chat/entity-cards/case-study-card.tsx +115 -5
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/mingo-chat-rail.tsx +24 -7
- package/src/components/chat/utils/icon-library.tsx +35 -1
- package/src/components/index.ts +0 -2
- package/src/components/loading/page-layout-skeleton.tsx +1 -1
- package/src/components/navigation/app-layout-side-panel.tsx +30 -2
- package/src/components/navigation/index.ts +22 -14
- package/src/components/navigation/mingo-ai-button.tsx +139 -63
- package/src/components/navigation/mobile-nav-sheet-id.ts +2 -0
- package/src/components/navigation/mobile-nav-sheet.tsx +241 -0
- package/src/components/navigation/nav-item-row.tsx +129 -0
- package/src/components/navigation/nav-panel.tsx +196 -0
- package/src/components/navigation/navigation-sidebar-item.tsx +1 -2
- package/src/components/navigation/site-footer.tsx +256 -0
- package/src/components/navigation/site-header.tsx +339 -0
- package/src/components/navigation/site-nav-link.tsx +41 -0
- package/src/components/navigation/ticket-alerts-button.tsx +2 -2
- package/src/components/navigation/top-navigation.tsx +2 -2
- package/src/components/platform/SoftwareSourceBadge.tsx +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/ui/index.ts +3 -0
- package/src/hooks/ui/use-active-section.ts +44 -0
- package/src/hooks/ui/use-nav-menus.ts +200 -0
- package/src/hooks/ui/use-visitor-os.ts +33 -0
- package/src/hooks/use-location-hash.ts +34 -0
- package/src/styles/app-globals.css +13 -0
- package/src/styles/chat-animations.css +57 -2
- package/src/types/case-study.ts +16 -0
- package/src/types/navigation.ts +80 -66
- package/src/utils/index.ts +1 -0
- package/src/utils/source-icons.ts +13 -0
- package/src/utils/visitor-os.ts +84 -0
- package/dist/chunk-LMBC5FFP.js +0 -43
- package/dist/chunk-O43V3EOS.js +0 -252
- package/dist/chunk-QZVSJF2K.cjs +0 -252
- package/dist/chunk-RALRPFDW.cjs +0 -43
- package/dist/components/footer-waitlist-button.d.ts +0 -29
- package/dist/components/footer-waitlist-button.d.ts.map +0 -1
- package/dist/components/footer.d.ts +0 -47
- package/dist/components/footer.d.ts.map +0 -1
- package/dist/components/navigation/client-only-header.d.ts +0 -8
- package/dist/components/navigation/client-only-header.d.ts.map +0 -1
- package/dist/components/navigation/header-skeleton.d.ts +0 -12
- package/dist/components/navigation/header-skeleton.d.ts.map +0 -1
- package/dist/components/navigation/header.d.ts +0 -9
- package/dist/components/navigation/header.d.ts.map +0 -1
- package/dist/components/navigation/mobile-nav-panel.d.ts +0 -10
- package/dist/components/navigation/mobile-nav-panel.d.ts.map +0 -1
- package/src/components/footer-waitlist-button.tsx +0 -53
- package/src/components/footer.tsx +0 -195
- package/src/components/navigation/client-only-header.tsx +0 -22
- package/src/components/navigation/header-skeleton.tsx +0 -74
- package/src/components/navigation/header.tsx +0 -429
- package/src/components/navigation/mobile-nav-panel.tsx +0 -155
|
@@ -1,104 +1,180 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import { useVisitorOs } from '../../hooks/ui/use-visitor-os';
|
|
4
6
|
import { cn } from '../../utils';
|
|
7
|
+
import { shortcutLabel, usesCommandKey } from '../../utils/visitor-os';
|
|
5
8
|
import { MingoIcon } from '../icons';
|
|
9
|
+
import { Button } from '../ui/button';
|
|
6
10
|
|
|
7
11
|
export interface MingoAiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
8
12
|
source?: string;
|
|
9
|
-
/** The platform's Mingo identity glyph
|
|
13
|
+
/** The platform's Mingo identity glyph: pass the SAME server-configured
|
|
10
14
|
* icon the chat panel renders (host-side: `EntityIcon` fed by the admin
|
|
11
15
|
* `assistantIcon`), so the launcher and the panel can never diverge.
|
|
12
16
|
* Falls back to the packaged Mingo mark when the server has none. */
|
|
13
17
|
icon?: React.ReactNode;
|
|
14
|
-
/** The launcher's wordmark + aria-label
|
|
18
|
+
/** The launcher's wordmark + aria-label: pass the server-configured
|
|
15
19
|
* assistant name (same `assistantName` the chat panel shows) so the
|
|
16
20
|
* launcher never hardcodes an identity the admin has renamed. */
|
|
17
21
|
label?: string;
|
|
22
|
+
/** Bind Cmd+K (Ctrl+K off Apple platforms) to open the chat, and show the
|
|
23
|
+
* key cap beside the label. Default false. */
|
|
24
|
+
shortcutHint?: boolean;
|
|
25
|
+
/** `inline` (default): the header launcher, the height and type of the menu
|
|
26
|
+
* items beside it. `field`: the full-width row that opens the mobile menu
|
|
27
|
+
* (the name reads as the field's prompt). ONE component, so every Mingo
|
|
28
|
+
* launcher carries the same identity, ring and event. */
|
|
29
|
+
variant?: 'inline' | 'field';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MINGO_ACCENT = 'var(--ods-flamingo-cyan-base)';
|
|
33
|
+
|
|
34
|
+
/** The event the mounted chat panel (`EmbeddableChat`) opens on. */
|
|
35
|
+
export const ASK_AI_OPEN_EVENT = 'ask-ai:open';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Open the chat of `source`. Coalesced to '' so a source-less call still
|
|
39
|
+
* matches the panel's own `runtime.source ?? ''` comparison (undefined !== ''
|
|
40
|
+
* would make the panel silently ignore the event).
|
|
41
|
+
*/
|
|
42
|
+
export function openAskAi(source?: string): void {
|
|
43
|
+
window.dispatchEvent(new CustomEvent(ASK_AI_OPEN_EVENT, { detail: { source: source ?? '' } }));
|
|
18
44
|
}
|
|
19
45
|
|
|
20
46
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
47
|
+
* THE Mingo AI launcher, in the site header's right cluster and at the top of
|
|
48
|
+
* the mobile menu (`variant="field"`): the round identity glyph, the
|
|
49
|
+
* assistant's name and an optional shortcut hint, sized like the menu items
|
|
50
|
+
* beside it. Stateless: clicking dispatches an `ask-ai:open`
|
|
51
|
+
* CustomEvent (source-filtered) that the mounted `EmbeddableChat` panel
|
|
52
|
+
* listens for.
|
|
53
|
+
*
|
|
54
|
+
* It carries the AI edge light (`.mingo-edge-frame` / `.mingo-edge` in
|
|
55
|
+
* `styles/chat-animations.css`): an accent arc travelling around its outline,
|
|
56
|
+
* with a one-shot shimmer on hover. Static under reduced motion.
|
|
25
57
|
*
|
|
26
58
|
* Distinct from `header-mingo-button.tsx` (`HeaderMingoButton`), the
|
|
27
59
|
* dashboard/AppHeader controlled toggle; different surface and contract, do
|
|
28
60
|
* not merge them.
|
|
29
61
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* icon-only collapse that `Button` cannot express (same precedent as
|
|
33
|
-
* `header-mingo-button.tsx`).
|
|
62
|
+
* It is the lib `Button` (`transparent`, or `glyph` for the menu row, at the
|
|
63
|
+
* `wrap` size): the launcher's own box, ring and collapse are classes on it.
|
|
34
64
|
*/
|
|
35
|
-
|
|
65
|
+
export function MingoAiButton({
|
|
66
|
+
source,
|
|
67
|
+
icon,
|
|
68
|
+
label = 'Mingo AI',
|
|
69
|
+
shortcutHint = false,
|
|
70
|
+
variant = 'inline',
|
|
71
|
+
className,
|
|
72
|
+
onClick,
|
|
73
|
+
...props
|
|
74
|
+
}: MingoAiButtonProps) {
|
|
75
|
+
// Known after hydration only: until then the hint keeps its space, empty.
|
|
76
|
+
const visitor = useVisitorOs();
|
|
77
|
+
const field = variant === 'field';
|
|
78
|
+
|
|
79
|
+
const commandKey = visitor.known && usesCommandKey(visitor.os);
|
|
80
|
+
|
|
81
|
+
// The shortcut the key cap shows opens the same chat the click opens.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!shortcutHint) return undefined;
|
|
84
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
85
|
+
// Something on the page already took the shortcut (an editor's link shortcut): leave it alone.
|
|
86
|
+
if (event.defaultPrevented || event.altKey || event.shiftKey) return;
|
|
87
|
+
// The modifier the key cap shows, and only that one: on a Mac Ctrl+K is
|
|
88
|
+
// the text fields' own "delete to the end of the line".
|
|
89
|
+
const modifier = commandKey ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
90
|
+
if (event.key.toLowerCase() !== 'k' || !modifier) return;
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
openAskAi(source);
|
|
93
|
+
};
|
|
94
|
+
window.addEventListener('keydown', onKeyDown);
|
|
95
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
96
|
+
}, [shortcutHint, source, commandKey]);
|
|
36
97
|
|
|
37
|
-
export function MingoAiButton({ source, icon, label = 'Mingo AI', className, onClick, ...props }: MingoAiButtonProps) {
|
|
38
98
|
return (
|
|
39
|
-
<
|
|
99
|
+
<Button
|
|
40
100
|
{...props}
|
|
41
101
|
type="button"
|
|
102
|
+
variant={field ? 'glyph' : 'transparent'}
|
|
103
|
+
size="wrap"
|
|
104
|
+
font="regular"
|
|
42
105
|
aria-label={label}
|
|
106
|
+
aria-keyshortcuts={shortcutHint && visitor.known ? (commandKey ? 'Meta+K' : 'Control+K') : undefined}
|
|
43
107
|
onClick={e => {
|
|
44
|
-
|
|
45
|
-
// own `runtime.source ?? ''` comparison (undefined !== '' would make
|
|
46
|
-
// the panel silently ignore the event).
|
|
47
|
-
window.dispatchEvent(new CustomEvent('ask-ai:open', { detail: { source: source ?? '' } }));
|
|
108
|
+
openAskAi(source);
|
|
48
109
|
onClick?.(e);
|
|
49
110
|
}}
|
|
50
111
|
className={cn(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
'group/mingo flex shrink-0 justify-start focus-visible:ring-ods-accent',
|
|
113
|
+
field
|
|
114
|
+
? // A row of the mobile menu like the groups under it: same height,
|
|
115
|
+
// type and divider, no surface and no ring of its own.
|
|
116
|
+
'min-h-14 w-full gap-[var(--spacing-system-sf)] rounded-none border-b border-ods-border text-left text-h4 [&_svg]:h-6 [&_svg]:w-6'
|
|
117
|
+
: // Transparent at rest so it inherits the bar's background; the hover
|
|
118
|
+
// wash is the menu items' own.
|
|
119
|
+
'h-10 gap-[var(--spacing-system-xsf)] rounded-md pl-[var(--spacing-system-xsf)] pr-2.5 text-ods-text-secondary hover:text-ods-text-primary',
|
|
58
120
|
className,
|
|
59
121
|
)}
|
|
60
122
|
>
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<span className="
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
123
|
+
{!field && (
|
|
124
|
+
<>
|
|
125
|
+
{/* AI edge light (Apple-Intelligence-style): a rotating accent-gradient
|
|
126
|
+
arc clipped to a hairline ring on the outline via CSS mask
|
|
127
|
+
(.mingo-edge-frame). NO opaque cover, so the launcher inherits
|
|
128
|
+
whatever background sits behind it. Platform-tinted via the accent
|
|
129
|
+
token. */}
|
|
130
|
+
<span aria-hidden="true" className="mingo-edge-frame pointer-events-none absolute inset-0 rounded-md">
|
|
131
|
+
<span className="mingo-edge" />
|
|
132
|
+
</span>
|
|
133
|
+
{/* One-shot light-streak shimmer on hover, clipped to the outline. */}
|
|
134
|
+
<span aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden rounded-md">
|
|
135
|
+
<span
|
|
136
|
+
className="mingo-shimmer absolute inset-y-0 left-0 w-1/2"
|
|
137
|
+
style={{
|
|
138
|
+
background:
|
|
139
|
+
'linear-gradient(105deg, transparent, color-mix(in srgb, var(--ods-system-greys-white) 12%, transparent), transparent)',
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
</span>
|
|
143
|
+
</>
|
|
144
|
+
)}
|
|
145
|
+
{icon ? (
|
|
146
|
+
<span
|
|
147
|
+
className={cn(
|
|
148
|
+
'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full',
|
|
149
|
+
field ? 'size-6' : 'size-5',
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
{icon}
|
|
83
153
|
</span>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
uses (bold 700 and -0.02em tracking are built into the utility). */}
|
|
99
|
-
<span className="relative hidden whitespace-nowrap text-ods-text-primary text-h3 md:inline">{label}</span>
|
|
154
|
+
) : (
|
|
155
|
+
<MingoIcon
|
|
156
|
+
color="currentColor"
|
|
157
|
+
eyesColor={MINGO_ACCENT}
|
|
158
|
+
cornerColor={MINGO_ACCENT}
|
|
159
|
+
className={cn('relative shrink-0 text-ods-text-primary', field ? 'size-6' : 'size-5')}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
162
|
+
{/* In the header the name collapses below lg, with the menus: on a phone
|
|
163
|
+
the launcher is its glyph alone (the name stays the accessible name). */}
|
|
164
|
+
<span
|
|
165
|
+
className={cn('relative whitespace-nowrap', field ? 'min-w-0 flex-1 truncate' : 'hidden text-h6 lg:inline')}
|
|
166
|
+
>
|
|
167
|
+
{label}
|
|
100
168
|
</span>
|
|
101
|
-
|
|
169
|
+
{shortcutHint && !field && (
|
|
170
|
+
// A key cap in the label's own type (never smaller: a hint nobody can
|
|
171
|
+
// read is decoration) and the label's colour, which clears 4.5:1 on
|
|
172
|
+
// the bar. It keeps its width before the platform is known.
|
|
173
|
+
<kbd className="relative hidden h-6 min-w-9 items-center justify-center rounded border border-ods-border px-[var(--spacing-system-xxs)] text-h6 lg:inline-flex">
|
|
174
|
+
{visitor.known ? shortcutLabel(visitor.os, 'K') : ''}
|
|
175
|
+
</kbd>
|
|
176
|
+
)}
|
|
177
|
+
</Button>
|
|
102
178
|
);
|
|
103
179
|
}
|
|
104
180
|
|
|
@@ -0,0 +1,241 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
}
|