@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,180 +1,104 @@
|
|
|
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';
|
|
6
4
|
import { cn } from '../../utils';
|
|
7
|
-
import { shortcutLabel, usesCommandKey } from '../../utils/visitor-os';
|
|
8
5
|
import { MingoIcon } from '../icons';
|
|
9
|
-
import { Button } from '../ui/button';
|
|
10
6
|
|
|
11
7
|
export interface MingoAiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
12
8
|
source?: string;
|
|
13
|
-
/** The platform's Mingo identity glyph
|
|
9
|
+
/** The platform's Mingo identity glyph — pass the SAME server-configured
|
|
14
10
|
* icon the chat panel renders (host-side: `EntityIcon` fed by the admin
|
|
15
11
|
* `assistantIcon`), so the launcher and the panel can never diverge.
|
|
16
12
|
* Falls back to the packaged Mingo mark when the server has none. */
|
|
17
13
|
icon?: React.ReactNode;
|
|
18
|
-
/** The launcher's wordmark + aria-label
|
|
14
|
+
/** The launcher's wordmark + aria-label — pass the server-configured
|
|
19
15
|
* assistant name (same `assistantName` the chat panel shows) so the
|
|
20
16
|
* launcher never hardcodes an identity the admin has renamed. */
|
|
21
17
|
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 ?? '' } }));
|
|
44
18
|
}
|
|
45
19
|
|
|
46
20
|
/**
|
|
47
|
-
*
|
|
48
|
-
* the
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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.
|
|
21
|
+
* Marketing-header Mingo AI launcher: the flush, full-height button rendered
|
|
22
|
+
* at the far right edge of the public `Header` (`config.mingo`). Stateless:
|
|
23
|
+
* clicking dispatches an `ask-ai:open` CustomEvent (source-filtered) that the
|
|
24
|
+
* mounted `EmbeddableChat` panel listens for.
|
|
57
25
|
*
|
|
58
26
|
* Distinct from `header-mingo-button.tsx` (`HeaderMingoButton`), the
|
|
59
27
|
* dashboard/AppHeader controlled toggle; different surface and contract, do
|
|
60
28
|
* not merge them.
|
|
61
29
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
30
|
+
* Deliberately a raw `<button>` rather than the ui-kit `Button`: it needs
|
|
31
|
+
* full-height flush cell layout, an absolutely-positioned animated ring, and
|
|
32
|
+
* icon-only collapse that `Button` cannot express (same precedent as
|
|
33
|
+
* `header-mingo-button.tsx`).
|
|
64
34
|
*/
|
|
65
|
-
|
|
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]);
|
|
35
|
+
const MINGO_ACCENT = 'var(--ods-flamingo-cyan-base)';
|
|
97
36
|
|
|
37
|
+
export function MingoAiButton({ source, icon, label = 'Mingo AI', className, onClick, ...props }: MingoAiButtonProps) {
|
|
98
38
|
return (
|
|
99
|
-
<
|
|
39
|
+
<button
|
|
100
40
|
{...props}
|
|
101
41
|
type="button"
|
|
102
|
-
variant={field ? 'glyph' : 'transparent'}
|
|
103
|
-
size="wrap"
|
|
104
|
-
font="regular"
|
|
105
42
|
aria-label={label}
|
|
106
|
-
aria-keyshortcuts={shortcutHint && visitor.known ? (commandKey ? 'Meta+K' : 'Control+K') : undefined}
|
|
107
43
|
onClick={e => {
|
|
108
|
-
|
|
44
|
+
// Coalesce to '' so a source-less mount still matches EmbeddableChat's
|
|
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 ?? '' } }));
|
|
109
48
|
onClick?.(e);
|
|
110
49
|
}}
|
|
111
50
|
className={cn(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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',
|
|
51
|
+
// Unified ODS top-navigation cell (Figma 2797-6808 desktop /
|
|
52
|
+
// 2797-7275 mobile): full-height cell with a leading divider,
|
|
53
|
+
// transparent at rest so it inherits the bar's background.
|
|
54
|
+
// No hover background on the cell: the animated ring + shimmer are
|
|
55
|
+
// the ONLY hover treatment (a bg flip on top read as a double
|
|
56
|
+
// animation).
|
|
57
|
+
'group/mingo relative flex h-full shrink-0 items-center border-l border-ods-border bg-transparent px-[var(--spacing-system-m)] focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',
|
|
120
58
|
className,
|
|
121
59
|
)}
|
|
122
60
|
>
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<span
|
|
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}
|
|
61
|
+
{/* Inner box = 1:1 twin of the DEFAULT-size Button geometry (rounded-md,
|
|
62
|
+
h-11 md:h-12, px-m) — the same footprint as the header's "Try for
|
|
63
|
+
Free" CTA — so the animated ring traces exactly where a full button
|
|
64
|
+
border would sit inside the 72px bar. */}
|
|
65
|
+
<span className="relative flex h-11 items-center gap-[var(--spacing-system-xsf)] rounded-md px-[var(--spacing-system-m)] md:h-12">
|
|
66
|
+
{/* AI edge light (Apple-Intelligence-style): a rotating accent-
|
|
67
|
+
gradient arc clipped to a 3px ring on the box outline via CSS
|
|
68
|
+
mask (.mingo-edge-frame) — NO opaque cover, so the launcher
|
|
69
|
+
inherits whatever background sits behind it. Platform-tinted via
|
|
70
|
+
the accent token. */}
|
|
71
|
+
<span aria-hidden="true" className="mingo-edge-frame pointer-events-none absolute inset-0 rounded-md">
|
|
72
|
+
<span className="mingo-edge" />
|
|
153
73
|
</span>
|
|
154
|
-
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
74
|
+
{/* One-shot light-streak shimmer on hover, clipped to the box. */}
|
|
75
|
+
<span aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden rounded-md">
|
|
76
|
+
<span
|
|
77
|
+
className="mingo-shimmer absolute inset-y-0 left-0 w-1/2"
|
|
78
|
+
style={{
|
|
79
|
+
background:
|
|
80
|
+
'linear-gradient(105deg, transparent, color-mix(in srgb, var(--ods-system-greys-white) 12%, transparent), transparent)',
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
</span>
|
|
84
|
+
{icon ? (
|
|
85
|
+
<span className="relative inline-flex size-6 shrink-0 items-center justify-center">{icon}</span>
|
|
86
|
+
) : (
|
|
87
|
+
<MingoIcon
|
|
88
|
+
color="currentColor"
|
|
89
|
+
eyesColor={MINGO_ACCENT}
|
|
90
|
+
cornerColor={MINGO_ACCENT}
|
|
91
|
+
className="relative h-6 w-6 shrink-0 text-ods-text-primary"
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
{/* Wordmark collapses below md — the mobile cell is icon-only per
|
|
95
|
+
spec. h3 bold, matching the default-size CTA label next to it
|
|
96
|
+
(Figma 2936-6825). */}
|
|
97
|
+
{/* Bare `text-h3` — the exact composite the default-size Button label
|
|
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>
|
|
168
100
|
</span>
|
|
169
|
-
|
|
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>
|
|
101
|
+
</button>
|
|
178
102
|
);
|
|
179
103
|
}
|
|
180
104
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePreventScroll } from '@react-aria/overlays';
|
|
4
|
+
import { X } from 'lucide-react';
|
|
5
|
+
import type React from 'react';
|
|
6
|
+
import { useRef } from 'react';
|
|
7
|
+
import { useFocusTrap } from '../../hooks/ui/use-focus-trap';
|
|
8
|
+
import { useHeaderHeight } from '../../hooks/ui/use-header-height';
|
|
9
|
+
import type { MobileNavConfig, NavigationItem } from '../../types/navigation';
|
|
10
|
+
import { cn } from '../../utils';
|
|
11
|
+
import { Button } from '../ui/button';
|
|
12
|
+
import { OVERLAY_BACKDROP_CLASS } from '../ui/drawer';
|
|
13
|
+
|
|
14
|
+
/** DOM id of the panel — referenced by the header hamburger's `aria-controls`. */
|
|
15
|
+
export const MOBILE_NAV_PANEL_ID = 'mobile-nav-panel';
|
|
16
|
+
|
|
17
|
+
export interface MobileNavPanelProps {
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
config: MobileNavConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
|
|
23
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
24
|
+
|
|
25
|
+
// Shared ref-counted, iOS-aware scroll lock (react-aria) — one counter with
|
|
26
|
+
// the modals and chat overlays, restores prior styles on release.
|
|
27
|
+
usePreventScroll({ isDisabled: !isOpen });
|
|
28
|
+
// Initial focus, Tab containment, Escape-to-close, guarded focus restore.
|
|
29
|
+
useFocusTrap(panelRef, isOpen, { onEscape: config.onClose });
|
|
30
|
+
// The panel is layout-mounted on every page — observers only while open.
|
|
31
|
+
const headerHeight = useHeaderHeight(64, { enabled: isOpen });
|
|
32
|
+
|
|
33
|
+
if (!isOpen) return null;
|
|
34
|
+
|
|
35
|
+
const renderNavigationItem = (item: NavigationItem) => {
|
|
36
|
+
if (item.element) {
|
|
37
|
+
return <div key={item.id}>{item.element}</div>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleClick = () => {
|
|
41
|
+
if (item.onClick) {
|
|
42
|
+
item.onClick();
|
|
43
|
+
}
|
|
44
|
+
config.onClose?.();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (item.href) {
|
|
48
|
+
return (
|
|
49
|
+
<Button
|
|
50
|
+
key={item.id}
|
|
51
|
+
variant="outline"
|
|
52
|
+
size="small-legacy"
|
|
53
|
+
href={item.href}
|
|
54
|
+
onClick={handleClick}
|
|
55
|
+
leftIcon={item.icon}
|
|
56
|
+
rightIcon={item.badge}
|
|
57
|
+
className={cn(
|
|
58
|
+
'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',
|
|
59
|
+
item.isActive ? 'bg-ods-bg-hover' : '',
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{item.label}
|
|
63
|
+
</Button>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Button
|
|
69
|
+
key={item.id}
|
|
70
|
+
variant="outline"
|
|
71
|
+
size="small-legacy"
|
|
72
|
+
onClick={handleClick}
|
|
73
|
+
leftIcon={item.icon}
|
|
74
|
+
className={cn(
|
|
75
|
+
'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',
|
|
76
|
+
item.isActive ? 'bg-ods-bg-hover' : '',
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<span className="flex-1 text-left">{item.label}</span>
|
|
80
|
+
{item.badge !== undefined && <span className="ml-auto">{item.badge}</span>}
|
|
81
|
+
</Button>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
{/* Backdrop - closes nav when clicked outside */}
|
|
88
|
+
<div className={cn('fixed inset-0 z-[9998]', OVERLAY_BACKDROP_CLASS)} onClick={config.onClose} />
|
|
89
|
+
|
|
90
|
+
{/* Navigation Panel - top-anchored floating card */}
|
|
91
|
+
<div
|
|
92
|
+
ref={panelRef}
|
|
93
|
+
id={MOBILE_NAV_PANEL_ID}
|
|
94
|
+
role="dialog"
|
|
95
|
+
aria-modal="true"
|
|
96
|
+
aria-label="Navigation menu"
|
|
97
|
+
tabIndex={-1}
|
|
98
|
+
style={{ '--mobile-nav-top': `${headerHeight + 16}px` } as React.CSSProperties}
|
|
99
|
+
className={cn(
|
|
100
|
+
'fixed z-[9999] rounded-lg shadow-xl',
|
|
101
|
+
config.className ? '' : 'border border-ods-border bg-ods-card',
|
|
102
|
+
// Responsive positioning and sizing — LEFT-anchored from md, flush
|
|
103
|
+
// under the burger cell at the left edge of the unified
|
|
104
|
+
// top-navigation (a larger inset read as a stray gap between the
|
|
105
|
+
// burger and its panel).
|
|
106
|
+
'left-2 right-2 md:right-auto',
|
|
107
|
+
'md:left-2 md:w-[400px] md:max-w-[calc(100vw-1rem)]',
|
|
108
|
+
// Small-viewport (svh) sizing anchored below the measured header —
|
|
109
|
+
// 100vh overflowed under mobile browser chrome, leaving the bottom
|
|
110
|
+
// entries and footer unreachable while the body was scroll-locked.
|
|
111
|
+
'top-[var(--mobile-nav-top)]',
|
|
112
|
+
'max-h-[calc(100svh-var(--mobile-nav-top)-50px)] md:max-h-[calc(100svh-var(--mobile-nav-top)-8px)]',
|
|
113
|
+
'flex flex-col',
|
|
114
|
+
config.className || '',
|
|
115
|
+
)}
|
|
116
|
+
onClick={e => e.stopPropagation()} // Prevent closing when clicking inside nav
|
|
117
|
+
>
|
|
118
|
+
{/* Header with close button - fixed at top */}
|
|
119
|
+
<div className="flex flex-shrink-0 justify-end border-b border-ods-border p-2">
|
|
120
|
+
<Button
|
|
121
|
+
aria-label="Close menu"
|
|
122
|
+
size="icon"
|
|
123
|
+
variant="transparent"
|
|
124
|
+
leftIcon={<X className="h-4 w-4 text-ods-text-primary" />}
|
|
125
|
+
onClick={config.onClose}
|
|
126
|
+
className="hover:bg-ods-bg-hover"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Scrollable content area */}
|
|
131
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-2 py-2">
|
|
132
|
+
<div className="flex flex-col space-y-1">
|
|
133
|
+
{config.sections.map((section, index) => (
|
|
134
|
+
<div key={index}>
|
|
135
|
+
{section.title && (
|
|
136
|
+
<div className="px-4 pb-1 pt-2 font-semibold uppercase text-ods-text-secondary text-h6">
|
|
137
|
+
{section.title}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
<div className="flex flex-col gap-1">{section.items.map(renderNavigationItem)}</div>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Footer with action button - fixed at bottom, clears the iOS home indicator */}
|
|
147
|
+
{config.footer && (
|
|
148
|
+
<div className="flex-shrink-0 border-t border-ods-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
149
|
+
{config.footer}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
</>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -155,7 +155,8 @@ export const NavigationSidebarItemButton = memo(function NavigationSidebarItemBu
|
|
|
155
155
|
*
|
|
156
156
|
* Deliberately in this file, beside `NavigationSidebarItemButton`: the two must
|
|
157
157
|
* agree on `h-14`, the `p-[var(--spacing-system-m)]` inset, the 24px icon box and
|
|
158
|
-
* the label column, or the nav resizes on the handoff.
|
|
158
|
+
* the label column, or the nav resizes on the handoff. Same reason
|
|
159
|
+
* `header-skeleton.tsx` sits beside `header.tsx`.
|
|
159
160
|
*
|
|
160
161
|
* `showLabel` is the sidebar's own minimized state, so the caller passes what it
|
|
161
162
|
* already passes the real row — no separate width handling.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* TicketAlertsButton — the unified header affordance for support-ticket
|
|
5
|
-
* unread indication. Mounted by BOTH header shells (
|
|
6
|
-
* `
|
|
5
|
+
* unread indication. Mounted by BOTH header shells (hub `Header` via
|
|
6
|
+
* `HeaderConfig.tickets`, console `AppHeader` via `showTicketAlerts`).
|
|
7
7
|
*
|
|
8
8
|
* ATTENTION-ONLY element, deliberately unlike the always-on header cells:
|
|
9
9
|
* - renders NOTHING unless there are unread support replies (and a
|
|
@@ -59,7 +59,7 @@ export interface TopNavigationProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
59
59
|
sideActions?: React.ReactNode;
|
|
60
60
|
/** Opaque ODS background class. Defaults to `bg-ods-card`. Keep it opaque —
|
|
61
61
|
* translucent backgrounds under a sticky bar need a deliberate
|
|
62
|
-
* backdrop-blur treatment (see the note in `
|
|
62
|
+
* backdrop-blur treatment (see the note in `header.tsx`). */
|
|
63
63
|
backgroundClassName?: string;
|
|
64
64
|
/** Top border on mobile (the ODS spec bar shows it below md). Default true. */
|
|
65
65
|
mobileTopBorder?: boolean;
|
|
@@ -73,7 +73,7 @@ const hasContent = (node: React.ReactNode): boolean => node !== null && node !==
|
|
|
73
73
|
/**
|
|
74
74
|
* Unified ODS top-navigation bar (Figma `[UPD] top-navigation`, node
|
|
75
75
|
* 2797-5978): the single 48px/56px (mobile / md+) cell-based header shell
|
|
76
|
-
* shared by the console `AppHeader` and the `
|
|
76
|
+
* shared by the console `AppHeader` and the marketing `Header` across all
|
|
77
77
|
* platforms.
|
|
78
78
|
*
|
|
79
79
|
* The shell owns the bar geometry only — height, borders, background, and the
|
package/src/hooks/index.ts
CHANGED
|
@@ -39,7 +39,6 @@ export * from './use-claude-mirror-src';
|
|
|
39
39
|
// Deep-link "scroll to URL hash" after data loads. Pairs with URL
|
|
40
40
|
// composers that emit `?<filter>=<id>#<prefix>-<id>` — the filter
|
|
41
41
|
// narrows the list, the hash scrolls the matching DOM id.
|
|
42
|
-
export * from './use-location-hash';
|
|
43
42
|
export * from './use-scroll-to-hash';
|
|
44
43
|
|
|
45
44
|
// Invisible bot-protection client primitive (honeypot ref + submit-timing).
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -31,6 +31,3 @@ export { usePreventScroll } from '@react-aria/overlays';
|
|
|
31
31
|
export * from './use-prefers-reduced-motion';
|
|
32
32
|
export * from './use-in-view';
|
|
33
33
|
export * from './use-autoplay';
|
|
34
|
-
export * from './use-active-section';
|
|
35
|
-
export * from './use-nav-menus';
|
|
36
|
-
export * from './use-visitor-os';
|
|
@@ -134,12 +134,12 @@
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/* Ring mask: clips the rotating conic layer to a
|
|
137
|
+
/* Ring mask: clips the rotating conic layer to a 3px band along the frame's
|
|
138
138
|
rounded outline (padding-box XOR), so the interior stays fully transparent
|
|
139
139
|
and the host background shows through — no opaque cover element. */
|
|
140
140
|
.mingo-edge-frame {
|
|
141
141
|
overflow: hidden;
|
|
142
|
-
padding:
|
|
142
|
+
padding: 3px;
|
|
143
143
|
-webkit-mask:
|
|
144
144
|
linear-gradient(#fff 0 0) content-box,
|
|
145
145
|
linear-gradient(#fff 0 0);
|
|
@@ -198,58 +198,3 @@
|
|
|
198
198
|
opacity: 0;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
/* -----------------------------------------------------------------------------
|
|
203
|
-
* Site header mega menus: how a panel's CONTENT arrives.
|
|
204
|
-
* open from a closed bar: a short fade and settle.
|
|
205
|
-
* from-start from the menu before it in the row: slides in from the left.
|
|
206
|
-
* from-end from the menu after it: slides in from the right.
|
|
207
|
-
* Between two mega menus the surface itself does not animate (NavPanel hands it
|
|
208
|
-
* over in one frame), so only the content moves, in the direction the pointer
|
|
209
|
-
* travelled. Short and ease-out: it must never make the menu feel slower.
|
|
210
|
-
* Reduced motion: no movement, a plain fade.
|
|
211
|
-
* ---------------------------------------------------------------------------*/
|
|
212
|
-
@keyframes nav-panel-open {
|
|
213
|
-
from {
|
|
214
|
-
opacity: 0;
|
|
215
|
-
transform: translateY(-4px);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
@keyframes nav-panel-from-start {
|
|
220
|
-
from {
|
|
221
|
-
opacity: 0;
|
|
222
|
-
transform: translateX(-24px);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@keyframes nav-panel-from-end {
|
|
227
|
-
from {
|
|
228
|
-
opacity: 0;
|
|
229
|
-
transform: translateX(24px);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@keyframes nav-panel-fade {
|
|
234
|
-
from {
|
|
235
|
-
opacity: 0;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.nav-panel-content[data-motion='open'] {
|
|
240
|
-
animation: nav-panel-open 180ms cubic-bezier(0.2, 0, 0, 1);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.nav-panel-content[data-motion='from-start'] {
|
|
244
|
-
animation: nav-panel-from-start 220ms cubic-bezier(0.2, 0, 0, 1);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.nav-panel-content[data-motion='from-end'] {
|
|
248
|
-
animation: nav-panel-from-end 220ms cubic-bezier(0.2, 0, 0, 1);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@media (prefers-reduced-motion: reduce) {
|
|
252
|
-
.nav-panel-content[data-motion] {
|
|
253
|
-
animation: nav-panel-fade 120ms linear;
|
|
254
|
-
}
|
|
255
|
-
}
|
package/src/types/case-study.ts
CHANGED
|
@@ -82,22 +82,6 @@ export interface CaseStudy {
|
|
|
82
82
|
case_study_tags?: TagAssoc[];
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
* What a case study card reads: the story, its cover and video, whose story it
|
|
87
|
-
* is and who tells it. A full `CaseStudy` row satisfies it; a surface that ships
|
|
88
|
-
* the card's data to the browser (the site menu) sends only this.
|
|
89
|
-
*/
|
|
90
|
-
export type CaseStudyCardData = Pick<CaseStudy, 'id' | 'title' | 'summary' | 'featured_image'> &
|
|
91
|
-
Partial<
|
|
92
|
-
Pick<
|
|
93
|
-
CaseStudy,
|
|
94
|
-
'main_video_url' | 'main_video_thumbnail' | 'highlight_video_url' | 'highlight_video_thumbnail' | 'video_bites'
|
|
95
|
-
>
|
|
96
|
-
> & {
|
|
97
|
-
user?: Pick<UserProfile, 'full_name' | 'job_title'> & Partial<Pick<UserProfile, 'avatar_url'>>;
|
|
98
|
-
msp?: Pick<MSP, 'name' | 'icon_url'>;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
85
|
export interface CreateCaseStudyData {
|
|
102
86
|
title: string;
|
|
103
87
|
slug: string;
|