@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
|
@@ -131,6 +131,8 @@ const HISTORY_RAIL_WIDTH = 320;
|
|
|
131
131
|
/** The v2 chat list (`MingoChatRail`, Figma `chat-sidebar`): the narrowest the v2 chat draws. */
|
|
132
132
|
export const MINGO_V2_RAIL_WIDTH = 296;
|
|
133
133
|
const CHAT_BLOCK_MIN_WIDTH = 400;
|
|
134
|
+
/** Mingo v2: the list and the chat side by side at their narrowest. */
|
|
135
|
+
export const MINGO_V2_SPLIT_WIDTH = MINGO_V2_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH;
|
|
134
136
|
const SPLIT_MIN_WIDTH = HISTORY_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH;
|
|
135
137
|
|
|
136
138
|
// Desktop drawer is this fraction of the viewport width (clamped to the
|
|
@@ -364,6 +366,12 @@ export interface EmbeddableChatProps {
|
|
|
364
366
|
* the open chat, which stays open.
|
|
365
367
|
*/
|
|
366
368
|
collapseTo?: 'list' | 'column';
|
|
369
|
+
/**
|
|
370
|
+
* v2: the |← control on the chat list while the panel shows the list alone.
|
|
371
|
+
* The host widens its panel; the chat opens the conversation open last (or
|
|
372
|
+
* the newest in the list), or a new chat when there is none.
|
|
373
|
+
*/
|
|
374
|
+
onExpand?: () => void;
|
|
367
375
|
|
|
368
376
|
/** v2: the status glyph at the end of a chat's row (working / unread / approval). */
|
|
369
377
|
dialogStatusOf?: (dialog: DialogItem) => MingoDialogStatus | undefined;
|
|
@@ -1024,6 +1032,7 @@ function EmbeddableChatInner({
|
|
|
1024
1032
|
appearance = CHAT_APPEARANCE.CLASSIC,
|
|
1025
1033
|
onCollapse,
|
|
1026
1034
|
collapseTo = 'list',
|
|
1035
|
+
onExpand,
|
|
1027
1036
|
dialogStatusOf,
|
|
1028
1037
|
userDisplayName,
|
|
1029
1038
|
userAvatarUrl,
|
|
@@ -1601,18 +1610,25 @@ function EmbeddableChatInner({
|
|
|
1601
1610
|
[handleNavigationClose],
|
|
1602
1611
|
);
|
|
1603
1612
|
|
|
1613
|
+
// "Ask Mingo" about a row (a card or source chip in the thread, or a page's
|
|
1614
|
+
// `ask-ai:open-with-ref`): asks in the open conversation. Forwarded through a
|
|
1615
|
+
// ref because what it does depends on dialog state declared further down
|
|
1616
|
+
// (`askAboutRef.current` is set next to it: an archived chat is read-only).
|
|
1617
|
+
const askAboutRef = useRef<(reference: ChatRef) => void>(discussRef);
|
|
1618
|
+
const askAbout = useCallback((reference: ChatRef) => askAboutRef.current(reference), []);
|
|
1619
|
+
|
|
1604
1620
|
// Host-provided renderer for inline entity cards — routes through the
|
|
1605
1621
|
// shared dispatcher in lib's `entity-cards/dispatch.tsx`.
|
|
1606
1622
|
const renderEntityCard = useCallback(
|
|
1607
1623
|
(reference: ChatRef): React.ReactNode =>
|
|
1608
1624
|
renderChatInlineEntityCard(reference, {
|
|
1609
|
-
onDiscuss:
|
|
1625
|
+
onDiscuss: askAbout,
|
|
1610
1626
|
onDisplay: displayRef,
|
|
1611
1627
|
baseRoute: resolvedBaseRoute,
|
|
1612
1628
|
chipBasePlatform,
|
|
1613
1629
|
extras,
|
|
1614
1630
|
}),
|
|
1615
|
-
[
|
|
1631
|
+
[askAbout, displayRef, resolvedBaseRoute, chipBasePlatform, extras],
|
|
1616
1632
|
);
|
|
1617
1633
|
|
|
1618
1634
|
// Stable assistant-icon element. `<ChatMessageList>` forwards this prop to
|
|
@@ -1832,11 +1848,11 @@ function EmbeddableChatInner({
|
|
|
1832
1848
|
).detail;
|
|
1833
1849
|
if (!detail || detail.source !== source) return;
|
|
1834
1850
|
setIsOpen(true);
|
|
1835
|
-
setTimeout(() =>
|
|
1851
|
+
setTimeout(() => askAbout(detail.ref as ChatRef), 0);
|
|
1836
1852
|
};
|
|
1837
1853
|
window.addEventListener('ask-ai:open-with-ref', handler);
|
|
1838
1854
|
return () => window.removeEventListener('ask-ai:open-with-ref', handler);
|
|
1839
|
-
}, [source,
|
|
1855
|
+
}, [source, askAbout, setIsOpen]);
|
|
1840
1856
|
|
|
1841
1857
|
// Listen for plain "open chat" events (no row context). Fired by the
|
|
1842
1858
|
// header MingoAiButton. Same strict source filter as `ask-ai:open-with-ref`
|
|
@@ -1960,12 +1976,12 @@ function EmbeddableChatInner({
|
|
|
1960
1976
|
baseRoute={resolvedBaseRoute}
|
|
1961
1977
|
chipBasePlatform={chipBasePlatform}
|
|
1962
1978
|
onClose={handleNavigationClose}
|
|
1963
|
-
onDiscuss={
|
|
1979
|
+
onDiscuss={askAbout}
|
|
1964
1980
|
/>
|
|
1965
1981
|
</div>
|
|
1966
1982
|
);
|
|
1967
1983
|
},
|
|
1968
|
-
[chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose,
|
|
1984
|
+
[chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose, askAbout],
|
|
1969
1985
|
);
|
|
1970
1986
|
|
|
1971
1987
|
// Host node for in-panel Radix portals (see the body wrapper below).
|
|
@@ -2176,6 +2192,37 @@ function EmbeddableChatInner({
|
|
|
2176
2192
|
resetToNewChat();
|
|
2177
2193
|
}, [resetToNewChat]);
|
|
2178
2194
|
|
|
2195
|
+
// "Ask Mingo" from an archived chat: the conversation is read-only (the
|
|
2196
|
+
// backend refuses writes), so the question starts a new chat instead. It is
|
|
2197
|
+
// queued until the host has let go of the archived dialog: until then its
|
|
2198
|
+
// `discussRef` still writes into the dialog it has open.
|
|
2199
|
+
const queuedAskRef = useRef<ChatRef | null>(null);
|
|
2200
|
+
useEffect(() => {
|
|
2201
|
+
askAboutRef.current = reference => {
|
|
2202
|
+
if (!isViewingArchived) {
|
|
2203
|
+
discussRef(reference);
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
queuedAskRef.current = reference;
|
|
2207
|
+
if (archiveOpen) closeArchive();
|
|
2208
|
+
resetToNewChat();
|
|
2209
|
+
setComposeOpen(true);
|
|
2210
|
+
};
|
|
2211
|
+
});
|
|
2212
|
+
useEffect(() => {
|
|
2213
|
+
const reference = queuedAskRef.current;
|
|
2214
|
+
if (!reference || activeDialogId != null) return;
|
|
2215
|
+
queuedAskRef.current = null;
|
|
2216
|
+
discussRef(reference);
|
|
2217
|
+
}, [activeDialogId, discussRef]);
|
|
2218
|
+
|
|
2219
|
+
// The conversation open last, for the |← control: going back to the list
|
|
2220
|
+
// closes it, and widening the panel again should bring it back.
|
|
2221
|
+
const lastDialogIdRef = useRef<string | null>(null);
|
|
2222
|
+
useEffect(() => {
|
|
2223
|
+
if (activeDialogId && !isViewingArchived) lastDialogIdRef.current = activeDialogId;
|
|
2224
|
+
}, [activeDialogId, isViewingArchived]);
|
|
2225
|
+
|
|
2179
2226
|
// Host prefill (`prefillDraft`) — staged, not written at once. The composer
|
|
2180
2227
|
// it goes into may only mount on the commit `resetToNewChat` +
|
|
2181
2228
|
// `setComposeOpen(true)` produce (narrow layout: list → compose; or the
|
|
@@ -2654,6 +2701,17 @@ function EmbeddableChatInner({
|
|
|
2654
2701
|
<MingoChatRail
|
|
2655
2702
|
{...mingoRailProps}
|
|
2656
2703
|
onNewChat={() => setComposeOpen(true)}
|
|
2704
|
+
onExpand={
|
|
2705
|
+
onExpand &&
|
|
2706
|
+
(() => {
|
|
2707
|
+
const last = lastDialogIdRef.current;
|
|
2708
|
+
const target = last && dialogs.some(d => d.id === last) ? last : dialogs[0]?.id;
|
|
2709
|
+
if (archiveOpen) closeArchive();
|
|
2710
|
+
if (target) handleSelectDialog(target);
|
|
2711
|
+
else handleNewChat();
|
|
2712
|
+
onExpand();
|
|
2713
|
+
})
|
|
2714
|
+
}
|
|
2657
2715
|
className="w-full border-r-0 duration-200 animate-in fade-in-0"
|
|
2658
2716
|
/>
|
|
2659
2717
|
) : stackedListView ? (
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* CaseStudyCard (pure presentation).
|
|
5
|
-
* detail)
|
|
4
|
+
* CaseStudyCard (pure presentation). Four densities — `default` (vertical
|
|
5
|
+
* detail), `sm` (compact horizontal for chat-inline), `portrait` (rails and
|
|
6
|
+
* strips) and `menu` (the site menu's card, hover-plays the highlight cut).
|
|
6
7
|
*
|
|
7
8
|
* The card writes NO click logic — callers wrap with their own anchor
|
|
8
9
|
* and pass the resolved detail URL via `href`.
|
|
@@ -12,9 +13,15 @@
|
|
|
12
13
|
* `useOgPlaceholderUrl(...)`) → `bg-ods-bg`.
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
import { useState } from 'react';
|
|
15
17
|
import Image from '../../../embed-shims/next-image';
|
|
16
|
-
import
|
|
18
|
+
import Link from '../../../embed-shims/next-link';
|
|
19
|
+
import type { CaseStudyCardData } from '../../../types/case-study';
|
|
17
20
|
import { cn } from '../../../utils/cn';
|
|
21
|
+
import { sortBitesByFeaturedAtDesc } from '../../features/video-bites-shared';
|
|
22
|
+
import { CardHoverPlay } from '../../features/video-center-badge';
|
|
23
|
+
import { VideoHoverPreviewSurface } from '../../features/video-hover-preview';
|
|
24
|
+
import { EntityIcon } from '../../icon-display';
|
|
18
25
|
import { Card } from '../../ui/card';
|
|
19
26
|
import {
|
|
20
27
|
COMPACT_CARD_IMAGE_SLOT,
|
|
@@ -36,7 +43,7 @@ import { useEntityCardLink } from './use-entity-card-link';
|
|
|
36
43
|
import { useEntityCardPlaceholder } from './use-entity-card-placeholder';
|
|
37
44
|
|
|
38
45
|
export interface CaseStudyCardProps {
|
|
39
|
-
study:
|
|
46
|
+
study: CaseStudyCardData;
|
|
40
47
|
/** Detail URL resolved by the caller. */
|
|
41
48
|
href: string;
|
|
42
49
|
/** When `_blank`, opens in a new tab. Set by chat dispatch via
|
|
@@ -46,7 +53,14 @@ export interface CaseStudyCardProps {
|
|
|
46
53
|
targetPlatform?: string | null;
|
|
47
54
|
/** OG placeholder URL, used when `study.featured_image` is missing. */
|
|
48
55
|
placeholderUrl?: string | null;
|
|
49
|
-
|
|
56
|
+
/** `menu` is the site menu's card: the story's video plays muted on hover or
|
|
57
|
+
* focus (the video bites' hover grammar), then who it is about and who tells it. */
|
|
58
|
+
size?: 'default' | 'sm' | 'portrait' | 'menu';
|
|
59
|
+
/** `menu`: mount the hover player (the host passes whether its panel is open,
|
|
60
|
+
* so a closed menu holds no player). Omitted: the card's own viewport gate. */
|
|
61
|
+
mediaMounted?: boolean;
|
|
62
|
+
/** `menu`: runs on click, before the navigation (the host closes its panel). */
|
|
63
|
+
onNavigate?: () => void;
|
|
50
64
|
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
51
65
|
showTypeBadge?: boolean;
|
|
52
66
|
className?: string;
|
|
@@ -92,6 +106,22 @@ export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' |
|
|
|
92
106
|
);
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
/**
|
|
110
|
+
* A story's HIGHLIGHT cut, the short video a card previews: its highlight
|
|
111
|
+
* video, else the bite an admin featured (the newest when several are; featuring
|
|
112
|
+
* is independent of the bite's own `published` flag, as on every featured surface). Never
|
|
113
|
+
* the full video: that plays on the story's own page. Null when it has neither.
|
|
114
|
+
*/
|
|
115
|
+
function caseStudyHighlight(study: CaseStudyCardData): { url: string; posterUrl: string | null } | null {
|
|
116
|
+
if (study.highlight_video_url) {
|
|
117
|
+
return { url: study.highlight_video_url, posterUrl: study.highlight_video_thumbnail ?? null };
|
|
118
|
+
}
|
|
119
|
+
const bite = (study.video_bites ?? [])
|
|
120
|
+
.filter(candidate => candidate.featured === true && candidate.url)
|
|
121
|
+
.sort(sortBitesByFeaturedAtDesc)[0];
|
|
122
|
+
return bite ? { url: bite.url, posterUrl: bite.thumbnail_url ?? null } : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
95
125
|
export function CaseStudyCard({
|
|
96
126
|
study,
|
|
97
127
|
href,
|
|
@@ -101,8 +131,11 @@ export function CaseStudyCard({
|
|
|
101
131
|
placeholderUrl: placeholderUrlProp,
|
|
102
132
|
size = 'default',
|
|
103
133
|
showTypeBadge = true,
|
|
134
|
+
mediaMounted,
|
|
135
|
+
onNavigate,
|
|
104
136
|
className,
|
|
105
137
|
}: CaseStudyCardProps) {
|
|
138
|
+
const [hovered, setHovered] = useState(false);
|
|
106
139
|
const { target, rel } = useEntityCardLink({
|
|
107
140
|
href,
|
|
108
141
|
targetPlatform,
|
|
@@ -149,6 +182,83 @@ export function CaseStudyCard({
|
|
|
149
182
|
);
|
|
150
183
|
}
|
|
151
184
|
|
|
185
|
+
if (size === 'menu') {
|
|
186
|
+
const preview = caseStudyHighlight(study);
|
|
187
|
+
const videoUrl = preview?.url ?? null;
|
|
188
|
+
const hasAnyVideo = !!(videoUrl || study.main_video_url);
|
|
189
|
+
// The cover is the FULL video's poster (wide, like the card); the highlight
|
|
190
|
+
// cut's own frame is usually upright and would not fill a 16:9 slot.
|
|
191
|
+
const poster = study.main_video_thumbnail || study.featured_image || preview?.posterUrl || null;
|
|
192
|
+
return (
|
|
193
|
+
<Link
|
|
194
|
+
href={href}
|
|
195
|
+
target={target}
|
|
196
|
+
rel={rel}
|
|
197
|
+
onClick={onNavigate}
|
|
198
|
+
onPointerEnter={() => setHovered(true)}
|
|
199
|
+
onPointerLeave={() => setHovered(false)}
|
|
200
|
+
onFocus={() => setHovered(true)}
|
|
201
|
+
onBlur={() => setHovered(false)}
|
|
202
|
+
className={cn(
|
|
203
|
+
// `group/card` is what the shared play glyph accents on.
|
|
204
|
+
'group/card group flex min-w-0 flex-col gap-[var(--spacing-system-sf)] rounded-xl border border-ods-border bg-ods-bg p-[var(--spacing-system-mf)] text-ods-text-primary transition-colors hover:bg-ods-bg-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-accent',
|
|
205
|
+
className,
|
|
206
|
+
)}
|
|
207
|
+
>
|
|
208
|
+
{/* The media never takes the click: the whole card is the link. */}
|
|
209
|
+
<span className="pointer-events-none relative block aspect-video w-full shrink-0 overflow-hidden rounded-md bg-ods-bg">
|
|
210
|
+
{videoUrl ? (
|
|
211
|
+
// The shared hover surface, filled the way the walkthrough card fills
|
|
212
|
+
// its own 16:9 slot (`cover`): an upright cut is cropped to the slot,
|
|
213
|
+
// never letterboxed. Sound on hover, muted when the browser blocks it.
|
|
214
|
+
<VideoHoverPreviewSurface
|
|
215
|
+
url={videoUrl}
|
|
216
|
+
posterUrl={poster}
|
|
217
|
+
active={hovered}
|
|
218
|
+
playerMounted={mediaMounted}
|
|
219
|
+
fit="cover"
|
|
220
|
+
posterSizes="360px"
|
|
221
|
+
/>
|
|
222
|
+
) : (
|
|
223
|
+
<>
|
|
224
|
+
{(poster ?? placeholderUrl) && (
|
|
225
|
+
<Image
|
|
226
|
+
src={poster ?? placeholderUrl ?? ''}
|
|
227
|
+
alt=""
|
|
228
|
+
className="object-cover"
|
|
229
|
+
fill
|
|
230
|
+
sizes="360px"
|
|
231
|
+
unoptimized
|
|
232
|
+
onError={hideOnError}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
{hasAnyVideo && <CardHoverPlay size="md" />}
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
</span>
|
|
239
|
+
{study.msp?.name && (
|
|
240
|
+
<span className="flex items-center gap-[var(--spacing-system-xsf)] text-ods-text-secondary text-h6">
|
|
241
|
+
{study.msp.icon_url && (
|
|
242
|
+
<EntityIcon
|
|
243
|
+
icon={{ url: study.msp.icon_url }}
|
|
244
|
+
size={20}
|
|
245
|
+
className="size-5 shrink-0 rounded object-contain"
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
<span className="min-w-0 truncate">{study.msp.name}</span>
|
|
249
|
+
</span>
|
|
250
|
+
)}
|
|
251
|
+
<span className="line-clamp-2 text-h6">{study.title}</span>
|
|
252
|
+
{study.user?.full_name && (
|
|
253
|
+
<span className="truncate text-ods-text-muted text-h6">
|
|
254
|
+
{study.user.job_title ? `${study.user.full_name}, ${study.user.job_title}` : study.user.full_name}
|
|
255
|
+
</span>
|
|
256
|
+
)}
|
|
257
|
+
<span className="mt-auto text-ods-accent text-h6">{hasAnyVideo ? 'Watch the story' : 'Read the story'}</span>
|
|
258
|
+
</Link>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
152
262
|
if (size === 'portrait') {
|
|
153
263
|
// Rail/strip density — shared <EntityPortraitCard> shell.
|
|
154
264
|
return (
|
|
@@ -4,6 +4,7 @@ import { type ReactNode, useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { cn } from '../../utils/cn';
|
|
5
5
|
import {
|
|
6
6
|
AlertCircleIcon,
|
|
7
|
+
Arrow02LeftIcon,
|
|
7
8
|
ChatPlusIcon,
|
|
8
9
|
ChatQuestionIcon,
|
|
9
10
|
ChatsIcon,
|
|
@@ -69,6 +70,8 @@ export interface MingoChatRailProps {
|
|
|
69
70
|
/** The list failed to load with nothing cached. */
|
|
70
71
|
loadError?: boolean;
|
|
71
72
|
onRetry?: () => void;
|
|
73
|
+
/** The |← control beside the logo: widen the panel to the list and the chat. */
|
|
74
|
+
onExpand?: () => void;
|
|
72
75
|
className?: string;
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -113,6 +116,7 @@ export function MingoChatRail({
|
|
|
113
116
|
isLoadingHistory = false,
|
|
114
117
|
loadError = false,
|
|
115
118
|
onRetry,
|
|
119
|
+
onExpand,
|
|
116
120
|
className,
|
|
117
121
|
}: MingoChatRailProps) {
|
|
118
122
|
const [searchOpen, setSearchOpen] = useState(!!searchQuery?.trim());
|
|
@@ -251,13 +255,26 @@ export function MingoChatRail({
|
|
|
251
255
|
data-accent="mingo"
|
|
252
256
|
aria-label="Mingo chats"
|
|
253
257
|
logo={
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
<div className="flex flex-1 items-center justify-between">
|
|
259
|
+
<MingoIcon
|
|
260
|
+
color="currentColor"
|
|
261
|
+
eyesColor="var(--ods-flamingo-cyan-base)"
|
|
262
|
+
cornerColor="var(--ods-flamingo-cyan-base)"
|
|
263
|
+
className="size-6 text-ods-text-primary"
|
|
264
|
+
aria-hidden
|
|
265
|
+
/>
|
|
266
|
+
{onExpand && (
|
|
267
|
+
<Button
|
|
268
|
+
variant="transparent"
|
|
269
|
+
size="icon"
|
|
270
|
+
aria-label="Expand chat"
|
|
271
|
+
onClick={onExpand}
|
|
272
|
+
className="size-6 p-0 text-ods-text-secondary hover:text-ods-text-primary md:size-6"
|
|
273
|
+
>
|
|
274
|
+
<Arrow02LeftIcon size={24} />
|
|
275
|
+
</Button>
|
|
276
|
+
)}
|
|
277
|
+
</div>
|
|
261
278
|
}
|
|
262
279
|
items={items}
|
|
263
280
|
sectionLabel="Your Current Chats"
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { Megaphone, Bell, Info, Star, Package as PackageGlyph } from 'lucide-react';
|
|
32
32
|
import type { ComponentType, CSSProperties } from 'react';
|
|
33
|
+
import { platformAccentVarName } from '../../../utils/platform-identity';
|
|
33
34
|
import * as IconsV2 from '../../icons-v2-generated';
|
|
34
35
|
import { HeadphoneIcon } from '../../icons-v2-generated/audio-and-visual/headphone-icon';
|
|
35
36
|
import { VideoRecorderIcon } from '../../icons-v2-generated/audio-and-visual/video-recorder-icon';
|
|
@@ -52,6 +53,8 @@ import { GraduationCapIcon } from '../../icons-v2-generated/school/graduation-ca
|
|
|
52
53
|
import { TicketIcon } from '../../icons-v2-generated/shopping/ticket-icon';
|
|
53
54
|
import { Rocket02Icon } from '../../icons-v2-generated/vehicles-and-delivery/rocket-02-icon';
|
|
54
55
|
import { FlamingoLogo } from '../../icons/flamingo-logo';
|
|
56
|
+
import { MiamiCyberGangLogoFaceOnly } from '../../icons/miami-cyber-gang-logo-face-only';
|
|
57
|
+
import { MlgLogo } from '../../icons/mlg-logo';
|
|
55
58
|
import { OpenFrameLogo } from '../../icons/openframe-logo';
|
|
56
59
|
import { OpenmspLogo } from '../../icons/openmsp-logo';
|
|
57
60
|
import { getIconComponent } from './icon-registry';
|
|
@@ -71,8 +74,37 @@ function sizedLogo(Logo: ComponentType<{ className?: string; style?: CSSProperti
|
|
|
71
74
|
return <Logo className={className} style={{ width: size, height: size }} />;
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
|
-
|
|
77
|
+
// A sister platform's mark is drawn in THAT platform's brand colour (the
|
|
78
|
+
// platform brand registry), never in the accent of the page showing it: on
|
|
79
|
+
// flamingo.run the OpenMSP mark stays OpenMSP yellow, not flamingo pink.
|
|
80
|
+
const brandColor = (platform: string) => `var(${platformAccentVarName(platform)})`;
|
|
81
|
+
// The OpenMSP mark's own defaults are black and white (made for a light page):
|
|
82
|
+
// on the dark surfaces every glyph sits on, the bubbles take the theme's text
|
|
83
|
+
// and background tokens and the back bubble OpenMSP's brand colour.
|
|
84
|
+
const OpenmspLogoIcon: IconComponent = ({ size = 16, className }) => (
|
|
85
|
+
<OpenmspLogo
|
|
86
|
+
className={className}
|
|
87
|
+
style={{ width: size, height: size }}
|
|
88
|
+
frontBubbleColor="var(--color-text-primary)"
|
|
89
|
+
innerFrontBubbleColor="var(--color-bg)"
|
|
90
|
+
backBubbleColor={brandColor('openmsp')}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
75
93
|
const FlamingoLogoIcon = sizedLogo(FlamingoLogo);
|
|
94
|
+
// The two community marks size themselves through a `size` prop, the resolver's own contract.
|
|
95
|
+
// Major League GitHub draws its mark in the theme's text colour on its own site
|
|
96
|
+
// (its header, hero and footer), never in an accent: so does every glyph of it.
|
|
97
|
+
const MlgLogoIcon: IconComponent = ({ size = 16, className, color }) => (
|
|
98
|
+
<MlgLogo size={size} className={className} color={color ?? 'var(--color-text-primary)'} />
|
|
99
|
+
);
|
|
100
|
+
const TmcgLogoIcon: IconComponent = ({ size = 16, className }) => (
|
|
101
|
+
<MiamiCyberGangLogoFaceOnly
|
|
102
|
+
size={size}
|
|
103
|
+
className={className}
|
|
104
|
+
originalPink={brandColor('tmcg')}
|
|
105
|
+
originalWhite="var(--color-text-primary)"
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
76
108
|
|
|
77
109
|
/**
|
|
78
110
|
* `OpenFrameLogo` from `components/icons/` predates the v2 set and:
|
|
@@ -138,6 +170,8 @@ export const ICON_ALIASES: Record<string, IconComponent> = {
|
|
|
138
170
|
// (was the separate `renderSvgIcon` map; now ONE resolver everywhere).
|
|
139
171
|
'openframe-logo': LogoOpenframeIcon,
|
|
140
172
|
'openmsp-logo': OpenmspLogoIcon,
|
|
173
|
+
'mlg-logo': MlgLogoIcon,
|
|
174
|
+
'tmcg-logo': TmcgLogoIcon,
|
|
141
175
|
flamingo: FlamingoLogoIcon,
|
|
142
176
|
'flamingo-logo': FlamingoLogoIcon,
|
|
143
177
|
megaphone: Megaphone,
|
package/src/components/index.ts
CHANGED
|
@@ -33,10 +33,8 @@ export * from './faq-accordion';
|
|
|
33
33
|
export * from './faq';
|
|
34
34
|
export * from './related-content';
|
|
35
35
|
export * from './filter-chip';
|
|
36
|
-
export * from './footer';
|
|
37
36
|
export * from './unified-filter-logic';
|
|
38
37
|
export * from './unified-pagination';
|
|
39
|
-
export * from './footer-waitlist-button';
|
|
40
38
|
export * from './hero-image-uploader';
|
|
41
39
|
export * from './icons-block';
|
|
42
40
|
export * from './image-cropper';
|
|
@@ -33,7 +33,7 @@ export function AnnouncementBarSkeleton() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Header skeleton that
|
|
36
|
+
* Header skeleton for a page that is still loading: logo, navigation and action placeholders
|
|
37
37
|
*/
|
|
38
38
|
export function HeaderSkeleton() {
|
|
39
39
|
return (
|
|
@@ -44,13 +44,23 @@ export interface AppLayoutSidePanelRenderState {
|
|
|
44
44
|
collapsesTo: 'column' | 'minimum' | null;
|
|
45
45
|
/** Step the panel back (see `collapsesTo`), animated. */
|
|
46
46
|
collapse: () => void;
|
|
47
|
+
/** Docked at its minimum with room for more: `expand` widens it. */
|
|
48
|
+
canExpand: boolean;
|
|
49
|
+
/** Widen the panel from its minimum to `defaultWidth` (as much as fits), animated. */
|
|
50
|
+
expand: () => void;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
export interface AppLayoutSidePanelConfig {
|
|
50
54
|
/** Panel body. Told its width so it can choose what fits. */
|
|
51
55
|
children: (state: AppLayoutSidePanelRenderState) => ReactNode;
|
|
52
|
-
/** Narrowest docked width
|
|
56
|
+
/** Narrowest docked width: the narrowest its content draws. */
|
|
53
57
|
minWidth: number;
|
|
58
|
+
/**
|
|
59
|
+
* Width it starts at until the user picks one (a first launch), and the
|
|
60
|
+
* width `expand` takes it back to from its minimum. As much of it as fits:
|
|
61
|
+
* with no room the panel stays narrower. Default `minWidth`.
|
|
62
|
+
*/
|
|
63
|
+
defaultWidth?: number;
|
|
54
64
|
/** Narrowest the content may get before the panel takes over. Default 400. */
|
|
55
65
|
minContentWidth?: number;
|
|
56
66
|
/** localStorage key for the chosen width. Without it the width lasts the session. */
|
|
@@ -75,6 +85,11 @@ export interface AppLayoutSidePanelConfig {
|
|
|
75
85
|
|
|
76
86
|
/** Inset of the docked card from the window edge and the header. */
|
|
77
87
|
export const SIDE_PANEL_INSET = 16;
|
|
88
|
+
/**
|
|
89
|
+
* The docked card's 1px border on both sides: its body is this much narrower
|
|
90
|
+
* than the panel's width. Add it to a content width to size the panel by it.
|
|
91
|
+
*/
|
|
92
|
+
export const SIDE_PANEL_FRAME_WIDTH = 2;
|
|
78
93
|
const MOBILE_QUERY = `not all and ${breakpoints.md}`;
|
|
79
94
|
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';
|
|
80
95
|
/** Length of the docked <-> full morph; keep in step with `duration-300` below. */
|
|
@@ -228,6 +243,8 @@ export interface AppLayoutSidePanelState extends SidePanelLayout {
|
|
|
228
243
|
endMorph: () => void;
|
|
229
244
|
collapsesTo: AppLayoutSidePanelRenderState['collapsesTo'];
|
|
230
245
|
collapse: () => void;
|
|
246
|
+
canExpand: boolean;
|
|
247
|
+
expand: () => void;
|
|
231
248
|
toggle: () => void;
|
|
232
249
|
close: () => void;
|
|
233
250
|
resize: (next: number) => void;
|
|
@@ -256,6 +273,7 @@ export function useAppLayoutSidePanel(
|
|
|
256
273
|
const minContentWidth = config?.minContentWidth ?? 400;
|
|
257
274
|
const collapsed = config?.collapsed ?? false;
|
|
258
275
|
const minimum: SidePanelSize = { width: minWidth, expanded: false };
|
|
276
|
+
const defaultWidth = Math.max(minWidth, config?.defaultWidth ?? minWidth);
|
|
259
277
|
|
|
260
278
|
const [rowWidth, setRowWidth] = useState(0);
|
|
261
279
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -271,7 +289,7 @@ export function useAppLayoutSidePanel(
|
|
|
271
289
|
const reduceMotion = useMediaQuery(REDUCED_MOTION_QUERY) === true;
|
|
272
290
|
const [morph, setMorph] = useState<SidePanelMorph | null>(null);
|
|
273
291
|
const [liveWidth, setLiveWidth] = useState<number | null>(null);
|
|
274
|
-
const [storedSize, setStoredSize] = useStoredSize(config?.storageKey,
|
|
292
|
+
const [storedSize, setStoredSize] = useStoredSize(config?.storageKey, { width: defaultWidth, expanded: false });
|
|
275
293
|
const [ownIsOpen, setOwnIsOpen] = useState(false);
|
|
276
294
|
const isOpen = config?.open ?? ownIsOpen;
|
|
277
295
|
const onOpenChange = config?.onOpenChange;
|
|
@@ -340,6 +358,8 @@ export function useAppLayoutSidePanel(
|
|
|
340
358
|
let collapsesTo: AppLayoutSidePanelRenderState['collapsesTo'] = null;
|
|
341
359
|
if (layout.mode === 'full' && layout.canDock) collapsesTo = layout.dockedWidth > minWidth ? 'column' : 'minimum';
|
|
342
360
|
else if (layout.mode === 'docked' && layout.width > minWidth) collapsesTo = 'minimum';
|
|
361
|
+
const canExpand =
|
|
362
|
+
layout.mode === 'docked' && layout.width <= minWidth && defaultWidth > minWidth && layout.maxDockedWidth > minWidth;
|
|
343
363
|
|
|
344
364
|
return {
|
|
345
365
|
...layout,
|
|
@@ -362,6 +382,12 @@ export function useAppLayoutSidePanel(
|
|
|
362
382
|
setSize({ width: minWidth, expanded: false });
|
|
363
383
|
}
|
|
364
384
|
},
|
|
385
|
+
canExpand,
|
|
386
|
+
expand: () => {
|
|
387
|
+
if (!canExpand) return;
|
|
388
|
+
animate('narrow');
|
|
389
|
+
dock(defaultWidth);
|
|
390
|
+
},
|
|
365
391
|
toggle: () => setIsOpen(!isOpen),
|
|
366
392
|
close: () => setIsOpen(false),
|
|
367
393
|
resize,
|
|
@@ -586,6 +612,8 @@ export function AppLayoutSidePanel({ config, state }: AppLayoutSidePanelProps) {
|
|
|
586
612
|
close,
|
|
587
613
|
collapsesTo: state.collapsesTo,
|
|
588
614
|
collapse: state.collapse,
|
|
615
|
+
canExpand: state.canExpand,
|
|
616
|
+
expand: state.expand,
|
|
589
617
|
})}
|
|
590
618
|
</div>
|
|
591
619
|
</div>
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
// Navigation component exports
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export {
|
|
4
|
+
// Site navigation: one data model (`SiteNav`), rendered by the header, its
|
|
5
|
+
// menu panels, the mobile sheet and the footer.
|
|
6
|
+
export { SiteHeader } from './site-header';
|
|
7
|
+
export type { SiteHeaderProps } from './site-header';
|
|
8
|
+
export { NavPanel, isMegaMenu } from './nav-panel';
|
|
9
|
+
export type { NavPanelProps } from './nav-panel';
|
|
10
|
+
export { NavItemRow } from './nav-item-row';
|
|
11
|
+
export type { NavItemRowProps, NavItemRowVariant } from './nav-item-row';
|
|
12
|
+
export { ASK_AI_OPEN_EVENT, MingoAiButton, openAskAi } from './mingo-ai-button';
|
|
8
13
|
export type { MingoAiButtonProps } from './mingo-ai-button';
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
export type {
|
|
15
|
-
|
|
16
|
-
export { MobileNavPanel, MOBILE_NAV_PANEL_ID } from './mobile-nav-panel';
|
|
17
|
-
export type { MobileNavPanelProps } from './mobile-nav-panel';
|
|
14
|
+
export { MobileNavSheet, MOBILE_NAV_SHEET_ID, siteNavHasMobileMenus } from './mobile-nav-sheet';
|
|
15
|
+
export type { MobileNavSheetProps } from './mobile-nav-sheet';
|
|
16
|
+
export { SiteFooter } from './site-footer';
|
|
17
|
+
export type { SiteFooterProps } from './site-footer';
|
|
18
|
+
export { defaultRenderSiteNavLink, navLinkLabel } from './site-nav-link';
|
|
19
|
+
export type { SiteNavLinkRenderer } from './site-nav-link';
|
|
18
20
|
|
|
19
21
|
export { SlidingSidebar } from './sliding-sidebar';
|
|
20
22
|
export type { SlidingSidebarProps } from './sliding-sidebar';
|
|
@@ -35,6 +37,7 @@ export type {
|
|
|
35
37
|
AppLayoutSidePanelMode,
|
|
36
38
|
AppLayoutSidePanelRenderState,
|
|
37
39
|
} from './app-layout-side-panel';
|
|
40
|
+
export { SIDE_PANEL_FRAME_WIDTH } from './app-layout-side-panel';
|
|
38
41
|
|
|
39
42
|
export {
|
|
40
43
|
AppLayoutDrawer,
|
|
@@ -80,10 +83,15 @@ export type { NavigationNode } from './multi-level-navigation';
|
|
|
80
83
|
|
|
81
84
|
// Re-export types from navigation types
|
|
82
85
|
export type {
|
|
83
|
-
|
|
86
|
+
NavFeature,
|
|
87
|
+
NavGroup,
|
|
88
|
+
NavLink,
|
|
89
|
+
NavMenu,
|
|
84
90
|
NavigationItem,
|
|
85
91
|
NavigationSidebarConfig,
|
|
86
92
|
NavigationSidebarItem,
|
|
93
|
+
SiteNav,
|
|
94
|
+
SiteNavPrimaryCta,
|
|
87
95
|
SlidingSidebarConfig,
|
|
88
96
|
UnifiedSidebarUser,
|
|
89
97
|
} from '../../types/navigation';
|