@flamingo-stack/openframe-frontend-core 0.0.219 → 0.0.220-snapshot.20260602171504
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-EDW2NVRV.js → chunk-4WZOFD46.js} +37 -37
- package/dist/{chunk-EDW2NVRV.js.map → chunk-4WZOFD46.js.map} +1 -1
- package/dist/{chunk-ZGBXHK26.cjs → chunk-5GN7TXHY.cjs} +12 -12
- package/dist/{chunk-ZGBXHK26.cjs.map → chunk-5GN7TXHY.cjs.map} +1 -1
- package/dist/{chunk-F3FO2ZZZ.cjs → chunk-BAKZF4GU.cjs} +7 -7
- package/dist/{chunk-F3FO2ZZZ.cjs.map → chunk-BAKZF4GU.cjs.map} +1 -1
- package/dist/{chunk-MPHDM2VZ.cjs → chunk-BCL24DFU.cjs} +30 -30
- package/dist/{chunk-MPHDM2VZ.cjs.map → chunk-BCL24DFU.cjs.map} +1 -1
- package/dist/{chunk-65CPJ4SX.cjs → chunk-C6ASEPZL.cjs} +30 -30
- package/dist/{chunk-65CPJ4SX.cjs.map → chunk-C6ASEPZL.cjs.map} +1 -1
- package/dist/{chunk-SZXKKEUH.cjs → chunk-E6B4B7GM.cjs} +46 -30
- package/dist/chunk-E6B4B7GM.cjs.map +1 -0
- package/dist/{chunk-SRA2QYK6.js → chunk-HUA4XG4S.js} +4 -4
- package/dist/{chunk-A3PL6ZCF.js → chunk-KXF3WCPH.js} +6397 -5128
- package/dist/chunk-KXF3WCPH.js.map +1 -0
- package/dist/{chunk-SL3RGBPX.cjs → chunk-QNYH3WUU.cjs} +9 -9
- package/dist/{chunk-SL3RGBPX.cjs.map → chunk-QNYH3WUU.cjs.map} +1 -1
- package/dist/{chunk-24Q2WLIU.js → chunk-QYRV6MKX.js} +2 -2
- package/dist/{chunk-XG7DFRJL.js → chunk-RCECOGMI.js} +3 -3
- package/dist/{chunk-7UZLRI7W.cjs → chunk-SEECETJY.cjs} +3301 -2032
- package/dist/chunk-SEECETJY.cjs.map +1 -0
- package/dist/{chunk-ZII7TNVA.js → chunk-T5MEXJD5.js} +3 -3
- package/dist/{chunk-YX3YQNC4.cjs → chunk-W23DRJAA.cjs} +13 -13
- package/dist/{chunk-YX3YQNC4.cjs.map → chunk-W23DRJAA.cjs.map} +1 -1
- package/dist/{chunk-DRPECAXO.js → chunk-WR32ZE63.js} +2 -2
- package/dist/{chunk-Y3MXGCOW.js → chunk-YZDUOUMB.js} +46 -30
- package/dist/chunk-YZDUOUMB.js.map +1 -0
- package/dist/components/chat/chat-archive-page.d.ts +25 -0
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -0
- package/dist/components/chat/chat-composer.d.ts +29 -0
- package/dist/components/chat/chat-composer.d.ts.map +1 -0
- package/dist/components/chat/chat-header-icon-button.d.ts +14 -0
- package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -0
- package/dist/components/chat/chat-panel-header.d.ts +32 -0
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +18 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/guide-mode-banner.d.ts +16 -0
- package/dist/components/chat/guide-mode-banner.d.ts.map +1 -0
- package/dist/components/chat/guide-welcome.d.ts +40 -0
- package/dist/components/chat/guide-welcome.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +58 -0
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +26 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +29 -5
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +9 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +28 -4
- package/dist/components/chat/mingo-chat-history.d.ts +37 -0
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -0
- package/dist/components/chat/mingo-chat-modals.d.ts +50 -0
- package/dist/components/chat/mingo-chat-modals.d.ts.map +1 -0
- package/dist/components/chat/mingo-onboarding-card.d.ts.map +1 -1
- package/dist/components/chat/mingo-welcome.d.ts +78 -0
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts +6 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +6 -6
- package/dist/components/contact/index.js +5 -5
- package/dist/components/features/index.cjs +5 -5
- package/dist/components/features/index.js +4 -4
- package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/index.cjs +2 -2
- package/dist/components/icons-v2-generated/index.js +1 -1
- package/dist/components/index.cjs +128 -104
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +31 -7
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-heading.d.ts +7 -6
- package/dist/components/layout/page-heading.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts +4 -2
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +5 -5
- package/dist/components/navigation/index.js +4 -4
- package/dist/components/onboarding-guides/index.cjs +28 -28
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/tickets/index.cjs +88 -88
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +29 -5
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +28 -4
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/more-actions-menu.d.ts +8 -1
- package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
- package/dist/components/ui/portal-container.d.ts +21 -0
- package/dist/components/ui/portal-container.d.ts.map +1 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +29 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +28 -4
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +93 -0
- package/src/components/chat/chat-composer.tsx +99 -0
- package/src/components/chat/chat-header-icon-button.tsx +36 -0
- package/src/components/chat/chat-panel-header.tsx +114 -0
- package/src/components/chat/embeddable-chat.tsx +386 -311
- package/src/components/chat/guide-mode-banner.tsx +75 -0
- package/src/components/chat/guide-welcome.tsx +207 -0
- package/src/components/chat/hooks/use-chat-dialog-manager.ts +227 -0
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +85 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +8 -0
- package/src/components/chat/hooks/use-unified-chat.ts +12 -0
- package/src/components/chat/index.ts +9 -0
- package/src/components/chat/mingo-chat-history.tsx +308 -0
- package/src/components/chat/mingo-chat-modals.tsx +223 -0
- package/src/components/chat/mingo-onboarding-card.tsx +5 -8
- package/src/components/chat/mingo-welcome.tsx +396 -0
- package/src/components/chat/types/unified-chat-state.types.ts +8 -0
- package/src/components/icons-v2/brand-logos/fleet-mdm-logo-grey.svg +6 -6
- package/src/components/icons-v2/brand-logos/fleet-mdm-logo.svg +6 -6
- package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.tsx +2 -22
- package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.tsx +22 -2
- package/src/components/layout/page-heading.tsx +13 -7
- package/src/components/navigation/app-header.tsx +12 -12
- package/src/components/navigation/app-layout-drawer.tsx +25 -15
- package/src/components/navigation/header-mingo-button.tsx +22 -7
- package/src/components/ui/dropdown-menu.tsx +9 -3
- package/src/components/ui/modal-v2.tsx +33 -3
- package/src/components/ui/more-actions-menu.tsx +15 -2
- package/src/components/ui/portal-container.tsx +28 -0
- package/src/components/ui/tooltip.tsx +9 -3
- package/src/stories/AppLayoutSidebar.stories.tsx +184 -0
- package/src/stories/EmbeddableChat.stories.tsx +114 -0
- package/src/stories/GuideWelcome.stories.tsx +102 -0
- package/src/stories/MingoChatModals.stories.tsx +82 -0
- package/src/stories/MingoWelcome.stories.tsx +119 -0
- package/dist/chunk-7UZLRI7W.cjs.map +0 -1
- package/dist/chunk-A3PL6ZCF.js.map +0 -1
- package/dist/chunk-SZXKKEUH.cjs.map +0 -1
- package/dist/chunk-Y3MXGCOW.js.map +0 -1
- /package/dist/{chunk-SRA2QYK6.js.map → chunk-HUA4XG4S.js.map} +0 -0
- /package/dist/{chunk-24Q2WLIU.js.map → chunk-QYRV6MKX.js.map} +0 -0
- /package/dist/{chunk-XG7DFRJL.js.map → chunk-RCECOGMI.js.map} +0 -0
- /package/dist/{chunk-ZII7TNVA.js.map → chunk-T5MEXJD5.js.map} +0 -0
- /package/dist/{chunk-DRPECAXO.js.map → chunk-WR32ZE63.js.map} +0 -0
|
@@ -105,16 +105,14 @@ const appLayoutDrawerVariants = cva(
|
|
|
105
105
|
},
|
|
106
106
|
},
|
|
107
107
|
compoundVariants: [
|
|
108
|
-
{ side: "right", flush: false, class: "pr-
|
|
109
|
-
{ side: "left", flush: false, class: "pl-
|
|
110
|
-
{ side: "top", flush: false, class: "pt-
|
|
111
|
-
{ side: "bottom", flush: false, class: "pb-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{ side: "
|
|
115
|
-
{ side: "
|
|
116
|
-
{ side: "top", flush: true, class: "md:pt-4 md:px-4" },
|
|
117
|
-
{ side: "bottom", flush: true, class: "md:pb-4 md:px-4" },
|
|
108
|
+
{ side: "right", flush: false, class: "pr-[var(--spacing-system-m)] py-[var(--spacing-system-m)]" },
|
|
109
|
+
{ side: "left", flush: false, class: "pl-[var(--spacing-system-m)] py-[var(--spacing-system-m)]" },
|
|
110
|
+
{ side: "top", flush: false, class: "pt-[var(--spacing-system-m)] px-[var(--spacing-system-m)]" },
|
|
111
|
+
{ side: "bottom", flush: false, class: "pb-[var(--spacing-system-m)] px-[var(--spacing-system-m)]" },
|
|
112
|
+
{ side: "right", flush: true, class: "md:pr-[var(--spacing-system-m)] md:py-[var(--spacing-system-m)]" },
|
|
113
|
+
{ side: "left", flush: true, class: "md:pl-[var(--spacing-system-m)] md:py-[var(--spacing-system-m)]" },
|
|
114
|
+
{ side: "top", flush: true, class: "md:pt-[var(--spacing-system-m)] md:px-[var(--spacing-system-m)]" },
|
|
115
|
+
{ side: "bottom", flush: true, class: "md:pb-[var(--spacing-system-m)] md:px-[var(--spacing-system-m)]" },
|
|
118
116
|
],
|
|
119
117
|
defaultVariants: {
|
|
120
118
|
side: "right",
|
|
@@ -192,11 +190,11 @@ function useContainedResizableSize({
|
|
|
192
190
|
|
|
193
191
|
const clampToContainer = React.useCallback(
|
|
194
192
|
(value: number) => {
|
|
195
|
-
// Reserve
|
|
196
|
-
// matching gap on the inside edge) so the panel sits symmetrically
|
|
193
|
+
// Reserve 40px (the `system-m` outside-edge padding from the wrapper
|
|
194
|
+
// plus a matching gap on the inside edge) so the panel sits symmetrically
|
|
197
195
|
// inside the container. This also keeps the resize grip on-screen at
|
|
198
196
|
// maximum extent.
|
|
199
|
-
const effectiveMax = available > 0 ? Math.min(maxSize, available -
|
|
197
|
+
const effectiveMax = available > 0 ? Math.min(maxSize, available - 40) : maxSize
|
|
200
198
|
return clamp(value, minSize, Math.max(minSize, effectiveMax))
|
|
201
199
|
},
|
|
202
200
|
[available, minSize, maxSize],
|
|
@@ -547,13 +545,19 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
547
545
|
aria-hidden
|
|
548
546
|
data-state={open ? "open" : "closed"}
|
|
549
547
|
className={cn(
|
|
550
|
-
"absolute inset-0 z-[40] bg-
|
|
548
|
+
"absolute inset-0 z-[40] bg-[color-mix(in_srgb,var(--ods-system-greys-background)_50%,transparent)] outline-none data-[state=open]:pointer-events-auto data-[state=closed]:pointer-events-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
551
549
|
overlayClassName,
|
|
552
550
|
)}
|
|
553
551
|
/>
|
|
554
552
|
<DialogPrimitive.Content
|
|
555
553
|
ref={ref}
|
|
556
|
-
className={cn(
|
|
554
|
+
className={cn(
|
|
555
|
+
appLayoutDrawerVariants({ side, flush }),
|
|
556
|
+
// Mobile: span the whole container and drop the outer gap so the
|
|
557
|
+
// panel renders edge-to-edge full-screen. (`p-0` overrides the
|
|
558
|
+
// variant's wrapper padding via tailwind-merge.)
|
|
559
|
+
isMobile && "inset-0 p-0",
|
|
560
|
+
)}
|
|
557
561
|
style={style}
|
|
558
562
|
onInteractOutside={(event) => {
|
|
559
563
|
onInteractOutside?.(event)
|
|
@@ -585,6 +589,12 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
585
589
|
<div
|
|
586
590
|
className={cn(
|
|
587
591
|
appLayoutDrawerPanelVariants({ side, flush }),
|
|
592
|
+
// Mobile: fill the container (the side already pins one axis) and
|
|
593
|
+
// drop the card chrome so the panel is edge-to-edge full-screen.
|
|
594
|
+
// Inner padding/gap is preserved so content isn't glued to the
|
|
595
|
+
// edges. tailwind-merge lets these override the variant classes.
|
|
596
|
+
isMobile && (isHorizontal ? "w-full" : "h-full"),
|
|
597
|
+
isMobile && "rounded-none border-0",
|
|
588
598
|
className,
|
|
589
599
|
panelClassName,
|
|
590
600
|
)}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import { cn } from '../../utils/cn'
|
|
5
5
|
import { MingoIcon } from '../icons'
|
|
6
|
+
import { XmarkIcon } from '../icons-v2-generated'
|
|
6
7
|
|
|
7
8
|
export interface HeaderMingoButtonProps
|
|
8
9
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -18,8 +19,10 @@ export interface HeaderMingoButtonProps
|
|
|
18
19
|
/**
|
|
19
20
|
* "Mingo AI" launcher button for `AppHeader`. Mirrors the `HeaderButton`
|
|
20
21
|
* visual contract (sticky header height, `ods-card` rest / `ods-bg-hover`
|
|
21
|
-
* hover
|
|
22
|
-
*
|
|
22
|
+
* hover, divider via `AppHeader`'s `divide-x`), but carries both the Mingo
|
|
23
|
+
* logo and the bold "Mingo AI" wordmark. The open/active state is exposed via
|
|
24
|
+
* `aria-pressed` (and, in `iconOnly` mode, by swapping the logo for a close
|
|
25
|
+
* "X"); there is no distinct active background.
|
|
23
26
|
*
|
|
24
27
|
* Figma: 7532:222103 — `button-full`.
|
|
25
28
|
*/
|
|
@@ -32,20 +35,32 @@ export function HeaderMingoButton({
|
|
|
32
35
|
return (
|
|
33
36
|
<button
|
|
34
37
|
type="button"
|
|
35
|
-
aria-label=
|
|
38
|
+
aria-label={iconOnly && isActive ? 'Close Mingo AI' : 'Mingo AI'}
|
|
36
39
|
aria-pressed={isActive}
|
|
37
40
|
className={cn(
|
|
38
41
|
'flex items-center shrink-0 h-full gap-2 px-4',
|
|
39
42
|
'transition-colors duration-200',
|
|
40
43
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',
|
|
41
|
-
|
|
42
|
-
? 'text-ods-text-primary bg-ods-bg-active'
|
|
43
|
-
: 'text-ods-text-primary bg-ods-card hover:bg-ods-bg-hover',
|
|
44
|
+
'text-ods-text-primary bg-ods-card hover:bg-ods-bg-hover',
|
|
44
45
|
className,
|
|
45
46
|
)}
|
|
46
47
|
{...props}
|
|
47
48
|
>
|
|
48
|
-
|
|
49
|
+
{iconOnly && isActive ? (
|
|
50
|
+
// Mobile, drawer open: the icon-only button doubles as the close
|
|
51
|
+
// affordance, so swap the Mingo logo for an X. Match the other header
|
|
52
|
+
// icons (e.g. notifications): secondary color, w-4 h-4 → md:w-6 h-6.
|
|
53
|
+
<XmarkIcon className="w-4 h-4 md:w-6 md:h-6 shrink-0 text-ods-text-secondary" />
|
|
54
|
+
) : (
|
|
55
|
+
// Outer frame follows the button's text color (currentColor); the eyes
|
|
56
|
+
// and corner block are ODS cyan.
|
|
57
|
+
<MingoIcon
|
|
58
|
+
color="currentColor"
|
|
59
|
+
eyesColor="var(--ods-flamingo-cyan-base)"
|
|
60
|
+
cornerColor="var(--ods-flamingo-cyan-base)"
|
|
61
|
+
className="w-4 h-4 md:w-6 md:h-6 shrink-0"
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
49
64
|
{!iconOnly && (
|
|
50
65
|
<span className="text-h3 font-bold tracking-[-0.36px] whitespace-nowrap">
|
|
51
66
|
Mingo AI
|
|
@@ -5,6 +5,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
|
5
5
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
6
6
|
|
|
7
7
|
import { cn } from "../../utils/cn"
|
|
8
|
+
import { usePortalContainer } from "./portal-container"
|
|
8
9
|
|
|
9
10
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
10
11
|
|
|
@@ -59,8 +60,12 @@ DropdownMenuSubContent.displayName =
|
|
|
59
60
|
const DropdownMenuContent = React.forwardRef<
|
|
60
61
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
61
62
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
62
|
-
>(({ className, sideOffset = 4, ...props }, ref) =>
|
|
63
|
-
|
|
63
|
+
>(({ className, sideOffset = 4, ...props }, ref) => {
|
|
64
|
+
// Portal into the active container (e.g. a drawer) so the menu inherits its
|
|
65
|
+
// stacking context; falls back to `document.body` when none is provided.
|
|
66
|
+
const container = usePortalContainer()
|
|
67
|
+
return (
|
|
68
|
+
<DropdownMenuPrimitive.Portal container={container ?? undefined}>
|
|
64
69
|
<DropdownMenuPrimitive.Content
|
|
65
70
|
ref={ref}
|
|
66
71
|
sideOffset={sideOffset}
|
|
@@ -71,7 +76,8 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
71
76
|
{...props}
|
|
72
77
|
/>
|
|
73
78
|
</DropdownMenuPrimitive.Portal>
|
|
74
|
-
)
|
|
79
|
+
)
|
|
80
|
+
})
|
|
75
81
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
76
82
|
|
|
77
83
|
const DropdownMenuItem = React.forwardRef<
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import { useEffect } from "react"
|
|
4
|
+
import { useEffect, useState } from "react"
|
|
5
5
|
import { XmarkIcon } from "../icons-v2-generated"
|
|
6
6
|
import { cn } from "../../utils/cn"
|
|
7
7
|
|
|
8
|
+
// Duration of the open/close animation in ms — keep in sync with the
|
|
9
|
+
// `duration-200` utilities applied to the backdrop and panel below.
|
|
10
|
+
const ANIMATION_DURATION = 200
|
|
11
|
+
|
|
8
12
|
const ModalContext = React.createContext<{ onClose?: () => void }>({})
|
|
9
13
|
|
|
10
14
|
interface ModalProps {
|
|
@@ -36,6 +40,18 @@ interface ModalFooterProps {
|
|
|
36
40
|
|
|
37
41
|
const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
|
|
38
42
|
({ isOpen, onClose, children, className }, ref) => {
|
|
43
|
+
// Keep the modal mounted while the exit animation plays.
|
|
44
|
+
const [isMounted, setIsMounted] = useState(isOpen)
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (isOpen) {
|
|
48
|
+
setIsMounted(true)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
const timeout = setTimeout(() => setIsMounted(false), ANIMATION_DURATION)
|
|
52
|
+
return () => clearTimeout(timeout)
|
|
53
|
+
}, [isOpen])
|
|
54
|
+
|
|
39
55
|
useEffect(() => {
|
|
40
56
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
41
57
|
if (event.key === 'Escape') {
|
|
@@ -54,17 +70,26 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
|
|
|
54
70
|
}
|
|
55
71
|
}, [isOpen, onClose])
|
|
56
72
|
|
|
57
|
-
if (!
|
|
73
|
+
if (!isMounted) return null
|
|
74
|
+
|
|
75
|
+
const state = isOpen ? "open" : "closed"
|
|
58
76
|
|
|
59
77
|
return (
|
|
60
78
|
<div className="fixed inset-0 z-[1300] flex items-end md:items-center justify-center">
|
|
61
79
|
<div
|
|
62
|
-
|
|
80
|
+
data-state={state}
|
|
81
|
+
className={cn(
|
|
82
|
+
"absolute inset-0 bg-black/50 backdrop-blur-[2px] md:backdrop-blur-none",
|
|
83
|
+
"duration-200",
|
|
84
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
85
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0"
|
|
86
|
+
)}
|
|
63
87
|
onClick={onClose}
|
|
64
88
|
aria-hidden="true"
|
|
65
89
|
/>
|
|
66
90
|
<div
|
|
67
91
|
ref={ref}
|
|
92
|
+
data-state={state}
|
|
68
93
|
className={cn(
|
|
69
94
|
"relative z-10 w-full max-w-md flex flex-col",
|
|
70
95
|
"mx-4 mb-4 md:mb-0",
|
|
@@ -72,6 +97,11 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
|
|
|
72
97
|
"bg-ods-bg md:bg-ods-card",
|
|
73
98
|
"border border-ods-border rounded-md shadow-xl",
|
|
74
99
|
"p-[var(--spacing-system-xl)] gap-[var(--spacing-system-l)]",
|
|
100
|
+
"duration-200",
|
|
101
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
102
|
+
"data-[state=open]:slide-in-from-bottom-4 md:data-[state=open]:slide-in-from-bottom-0 md:data-[state=open]:zoom-in-95",
|
|
103
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
104
|
+
"data-[state=closed]:slide-out-to-bottom-4 md:data-[state=closed]:slide-out-to-bottom-0 md:data-[state=closed]:zoom-out-95",
|
|
75
105
|
className
|
|
76
106
|
)}
|
|
77
107
|
role="dialog"
|
|
@@ -31,6 +31,10 @@ export interface MoreActionsMenuProps {
|
|
|
31
31
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
32
32
|
sideOffset?: number
|
|
33
33
|
className?: string
|
|
34
|
+
/** Appended to the dropdown content. To render the menu above a high-z
|
|
35
|
+
* surface (drawer, modal), prefer wrapping that surface in a
|
|
36
|
+
* `PortalContainerContext` provider rather than escalating z-index here. */
|
|
37
|
+
contentClassName?: string
|
|
34
38
|
ariaLabel?: string
|
|
35
39
|
/** Custom trigger element. When provided, replaces the default ellipsis icon button. */
|
|
36
40
|
trigger?: React.ReactNode
|
|
@@ -38,6 +42,9 @@ export interface MoreActionsMenuProps {
|
|
|
38
42
|
open?: boolean
|
|
39
43
|
/** Called when the open state changes — use together with `open`. */
|
|
40
44
|
onOpenChange?: (open: boolean) => void
|
|
45
|
+
/** Forwarded to the dropdown content. Call `e.preventDefault()` to stop
|
|
46
|
+
* Radix returning focus (and its focus ring) to the trigger on close. */
|
|
47
|
+
onCloseAutoFocus?: (event: Event) => void
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/**
|
|
@@ -50,10 +57,12 @@ export function MoreActionsMenu({
|
|
|
50
57
|
side = 'bottom',
|
|
51
58
|
sideOffset = 6,
|
|
52
59
|
className,
|
|
60
|
+
contentClassName,
|
|
53
61
|
ariaLabel = 'More actions',
|
|
54
62
|
trigger,
|
|
55
63
|
open,
|
|
56
|
-
onOpenChange
|
|
64
|
+
onOpenChange,
|
|
65
|
+
onCloseAutoFocus
|
|
57
66
|
}: MoreActionsMenuProps) {
|
|
58
67
|
return (
|
|
59
68
|
<DropdownMenu open={open} onOpenChange={onOpenChange}>
|
|
@@ -73,7 +82,11 @@ export function MoreActionsMenu({
|
|
|
73
82
|
align={align}
|
|
74
83
|
side={side}
|
|
75
84
|
sideOffset={sideOffset}
|
|
76
|
-
|
|
85
|
+
onCloseAutoFocus={onCloseAutoFocus}
|
|
86
|
+
className={cn(
|
|
87
|
+
'bg-ods-card border border-ods-border p-0 rounded-[4px] min-w-[200px]',
|
|
88
|
+
contentClassName,
|
|
89
|
+
)}
|
|
77
90
|
>
|
|
78
91
|
{items.map((item, idx) => {
|
|
79
92
|
const itemClassName =
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The DOM node that Radix overlays (dropdown menus, tooltips) should portal
|
|
7
|
+
* into, instead of the default `document.body`.
|
|
8
|
+
*
|
|
9
|
+
* Portaling to `document.body` lifts content out of every local stacking
|
|
10
|
+
* context, which forces overlays opened *inside* a high-z surface (e.g. the
|
|
11
|
+
* chat drawer) to escalate their own z-index to compete at the document
|
|
12
|
+
* root. Pointing the portal at a node *inside* that surface instead
|
|
13
|
+
* lets the content inherit the surface's stacking context — small, local
|
|
14
|
+
* z-indices then "just work" and no escalation is needed. Radix positions
|
|
15
|
+
* content with `strategy: "fixed"`, so it still escapes ancestor
|
|
16
|
+
* `overflow: hidden` clipping regardless of where it is portaled.
|
|
17
|
+
*
|
|
18
|
+
* Default `null` → Radix falls back to `document.body` (unchanged behaviour
|
|
19
|
+
* everywhere a provider isn't present).
|
|
20
|
+
*/
|
|
21
|
+
export const PortalContainerContext = React.createContext<HTMLElement | null>(
|
|
22
|
+
null,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
/** Read the active portal container (or `null` to use `document.body`). */
|
|
26
|
+
export function usePortalContainer(): HTMLElement | null {
|
|
27
|
+
return React.useContext(PortalContainerContext)
|
|
28
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
5
|
import { cn } from "../../utils/cn"
|
|
6
|
+
import { usePortalContainer } from "./portal-container"
|
|
6
7
|
|
|
7
8
|
const TooltipProvider = TooltipPrimitive.Provider
|
|
8
9
|
|
|
@@ -13,8 +14,12 @@ const TooltipTrigger = TooltipPrimitive.Trigger
|
|
|
13
14
|
const TooltipContent = React.forwardRef<
|
|
14
15
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
15
16
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
16
|
-
>(({ className, sideOffset = 4, ...props }, ref) =>
|
|
17
|
-
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => {
|
|
18
|
+
// Portal into the active container (e.g. a drawer) so the tooltip inherits
|
|
19
|
+
// its stacking context; falls back to `document.body` when none is provided.
|
|
20
|
+
const container = usePortalContainer()
|
|
21
|
+
return (
|
|
22
|
+
<TooltipPrimitive.Portal container={container ?? undefined}>
|
|
18
23
|
<TooltipPrimitive.Content
|
|
19
24
|
ref={ref}
|
|
20
25
|
sideOffset={sideOffset}
|
|
@@ -25,7 +30,8 @@ const TooltipContent = React.forwardRef<
|
|
|
25
30
|
{...props}
|
|
26
31
|
/>
|
|
27
32
|
</TooltipPrimitive.Portal>
|
|
28
|
-
)
|
|
33
|
+
)
|
|
34
|
+
})
|
|
29
35
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
30
36
|
|
|
31
37
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { fn } from 'storybook/test'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
BracketCurlyIcon,
|
|
7
|
+
ChartDonutIcon,
|
|
8
|
+
IdCardIcon,
|
|
9
|
+
MonitorIcon,
|
|
10
|
+
Settings02Icon,
|
|
11
|
+
} from '../components/icons-v2-generated'
|
|
12
|
+
import {
|
|
13
|
+
AppLayout,
|
|
14
|
+
AppLayoutDrawer,
|
|
15
|
+
AppLayoutDrawerBody,
|
|
16
|
+
AppLayoutDrawerContent,
|
|
17
|
+
AppLayoutDrawerDescription,
|
|
18
|
+
AppLayoutDrawerHeader,
|
|
19
|
+
AppLayoutDrawerTitle,
|
|
20
|
+
} from '../components/navigation'
|
|
21
|
+
import { NavigationSidebarConfig } from '../types/navigation'
|
|
22
|
+
|
|
23
|
+
const navigationItems: NavigationSidebarConfig['items'] = [
|
|
24
|
+
{ id: 'dashboard', label: 'Dashboard', icon: <ChartDonutIcon size={24} />, path: '/dashboard', isActive: true },
|
|
25
|
+
{ id: 'customers', label: 'Customers', icon: <IdCardIcon size={24} />, path: '/customers' },
|
|
26
|
+
{ id: 'devices', label: 'Devices', icon: <MonitorIcon size={24} />, path: '/devices' },
|
|
27
|
+
{ id: 'scripts', label: 'Scripts', icon: <BracketCurlyIcon size={24} />, path: '/scripts' },
|
|
28
|
+
{ id: 'settings', label: 'Settings', icon: <Settings02Icon size={24} />, path: '/settings', section: 'secondary' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const meta = {
|
|
32
|
+
title: 'Navigation/AppLayoutSidebar',
|
|
33
|
+
component: AppLayout,
|
|
34
|
+
parameters: {
|
|
35
|
+
layout: 'fullscreen',
|
|
36
|
+
docs: {
|
|
37
|
+
description: {
|
|
38
|
+
component: `
|
|
39
|
+
\`AppLayout\` wired so the **Mingo AI** header button toggles a right-side,
|
|
40
|
+
in-layout sidebar (an \`AppLayoutDrawer\`). The sidebar renders inside the
|
|
41
|
+
main content area — the navigation sidebar and header stay visible and
|
|
42
|
+
interactive while it is open.
|
|
43
|
+
|
|
44
|
+
The header button reflects the open state via \`isMingoAIActive\`, and the
|
|
45
|
+
sidebar is persistent: clicks on the header/sidebar/overlay do not close it
|
|
46
|
+
(use the X button or Escape).
|
|
47
|
+
`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
tags: ['autodocs'],
|
|
52
|
+
} satisfies Meta<typeof AppLayout>
|
|
53
|
+
|
|
54
|
+
export default meta
|
|
55
|
+
type Story = StoryObj<typeof meta>
|
|
56
|
+
|
|
57
|
+
function DashboardChildren() {
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-6 p-6">
|
|
60
|
+
<div>
|
|
61
|
+
<h1 className="text-2xl font-semibold text-ods-text-primary">Devices Overview</h1>
|
|
62
|
+
<p className="text-ods-text-secondary mt-1">8,250 Devices in Total</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
65
|
+
{[
|
|
66
|
+
{ label: 'Active Devices', value: '6,930' },
|
|
67
|
+
{ label: 'Active Tickets', value: '136' },
|
|
68
|
+
{ label: 'Resolved Tickets', value: '825' },
|
|
69
|
+
].map((stat) => (
|
|
70
|
+
<div key={stat.label} className="p-4 bg-ods-card rounded-lg border border-ods-border">
|
|
71
|
+
<p className="text-sm text-ods-text-secondary">{stat.label}</p>
|
|
72
|
+
<p className="text-2xl font-semibold text-ods-text-primary mt-1">{stat.value}</p>
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
<p className="text-ods-text-secondary">
|
|
77
|
+
Click <span className="font-medium text-ods-text-primary">Mingo AI</span> in the header to open the chat sidebar.
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mingo AI header button toggles the right-side chat sidebar.
|
|
85
|
+
*/
|
|
86
|
+
export const Default: Story = {
|
|
87
|
+
render: function Render() {
|
|
88
|
+
const [open, setOpen] = useState(false)
|
|
89
|
+
return (
|
|
90
|
+
<AppLayout
|
|
91
|
+
sidebarConfig={{ items: navigationItems, onNavigate: fn(), onToggleMinimized: fn() }}
|
|
92
|
+
headerProps={{
|
|
93
|
+
showNotifications: true,
|
|
94
|
+
showMingoAI: true,
|
|
95
|
+
onMingoAI: () => setOpen((prev) => !prev),
|
|
96
|
+
isMingoAIActive: open,
|
|
97
|
+
showUser: true,
|
|
98
|
+
userName: 'Alex Developer',
|
|
99
|
+
userEmail: 'alex@openframe.dev',
|
|
100
|
+
onProfile: fn(),
|
|
101
|
+
onLogout: fn(),
|
|
102
|
+
}}
|
|
103
|
+
mobileBurgerMenuProps={{
|
|
104
|
+
user: {
|
|
105
|
+
userName: 'Alex Developer',
|
|
106
|
+
userEmail: 'alex@openframe.dev',
|
|
107
|
+
userRole: 'Admin',
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
drawer={
|
|
111
|
+
<AppLayoutDrawer open={open} onOpenChange={setOpen}>
|
|
112
|
+
<AppLayoutDrawerContent side="right">
|
|
113
|
+
<AppLayoutDrawerHeader>
|
|
114
|
+
<AppLayoutDrawerTitle>Mingo AI</AppLayoutDrawerTitle>
|
|
115
|
+
<AppLayoutDrawerDescription>
|
|
116
|
+
Ask Mingo about your devices, tickets, and scripts.
|
|
117
|
+
</AppLayoutDrawerDescription>
|
|
118
|
+
</AppLayoutDrawerHeader>
|
|
119
|
+
<AppLayoutDrawerBody>
|
|
120
|
+
<p className="text-sm text-ods-text-secondary">Chat content goes here.</p>
|
|
121
|
+
</AppLayoutDrawerBody>
|
|
122
|
+
</AppLayoutDrawerContent>
|
|
123
|
+
</AppLayoutDrawer>
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
<DashboardChildren />
|
|
127
|
+
</AppLayout>
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resizable chat sidebar — drag the inside edge to resize. Starts open.
|
|
134
|
+
*/
|
|
135
|
+
export const Resizable: Story = {
|
|
136
|
+
render: function Render() {
|
|
137
|
+
const [open, setOpen] = useState(true)
|
|
138
|
+
return (
|
|
139
|
+
<AppLayout
|
|
140
|
+
sidebarConfig={{ items: navigationItems, onNavigate: fn(), onToggleMinimized: fn() }}
|
|
141
|
+
headerProps={{
|
|
142
|
+
showNotifications: true,
|
|
143
|
+
showMingoAI: true,
|
|
144
|
+
onMingoAI: () => setOpen((prev) => !prev),
|
|
145
|
+
isMingoAIActive: open,
|
|
146
|
+
showUser: true,
|
|
147
|
+
userName: 'Alex Developer',
|
|
148
|
+
userEmail: 'alex@openframe.dev',
|
|
149
|
+
onProfile: fn(),
|
|
150
|
+
onLogout: fn(),
|
|
151
|
+
}}
|
|
152
|
+
mobileBurgerMenuProps={{
|
|
153
|
+
user: {
|
|
154
|
+
userName: 'Alex Developer',
|
|
155
|
+
userEmail: 'alex@openframe.dev',
|
|
156
|
+
userRole: 'Admin',
|
|
157
|
+
},
|
|
158
|
+
}}
|
|
159
|
+
drawer={
|
|
160
|
+
<AppLayoutDrawer open={open} onOpenChange={setOpen}>
|
|
161
|
+
<AppLayoutDrawerContent
|
|
162
|
+
side="right"
|
|
163
|
+
resizable
|
|
164
|
+
minSize={360}
|
|
165
|
+
defaultSize={560}
|
|
166
|
+
storageKey="storybook:app-layout-sidebar:mingo"
|
|
167
|
+
>
|
|
168
|
+
<AppLayoutDrawerHeader>
|
|
169
|
+
<AppLayoutDrawerTitle>Mingo AI</AppLayoutDrawerTitle>
|
|
170
|
+
</AppLayoutDrawerHeader>
|
|
171
|
+
<AppLayoutDrawerBody>
|
|
172
|
+
<p className="text-sm text-ods-text-secondary">
|
|
173
|
+
Drag the grip on the left edge to resize the chat sidebar.
|
|
174
|
+
</p>
|
|
175
|
+
</AppLayoutDrawerBody>
|
|
176
|
+
</AppLayoutDrawerContent>
|
|
177
|
+
</AppLayoutDrawer>
|
|
178
|
+
}
|
|
179
|
+
>
|
|
180
|
+
<DashboardChildren />
|
|
181
|
+
</AppLayout>
|
|
182
|
+
)
|
|
183
|
+
},
|
|
184
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { EmbeddableChat } from '../components/chat/embeddable-chat'
|
|
9
9
|
import type { UseNatsChatAdapterConfig } from '../components/chat/hooks/use-nats-chat-adapter'
|
|
10
10
|
import type { SlashCommandSummary } from '../components/chat/hooks/use-slash-commands'
|
|
11
|
+
import type { DialogItem } from '../components/chat/types/component.types'
|
|
11
12
|
|
|
12
13
|
// =============================================================================
|
|
13
14
|
// Stub commands endpoint — module-level constant referenced both by the
|
|
@@ -59,6 +60,92 @@ function createMockMingoConfig(): UseNatsChatAdapterConfig {
|
|
|
59
60
|
// eslint-disable-next-line no-console
|
|
60
61
|
console.log('[story] mingo publish', { text, options })
|
|
61
62
|
},
|
|
63
|
+
// Selecting a dialog loads its history via this callback — without it
|
|
64
|
+
// `hasMessages` stays false and the panel never switches to the
|
|
65
|
+
// conversation view. Return a tiny canned thread so clicks "open" a chat.
|
|
66
|
+
fetchDialogMessages: ({ dialogId }) =>
|
|
67
|
+
Promise.resolve({
|
|
68
|
+
messages: [
|
|
69
|
+
{
|
|
70
|
+
id: `${dialogId}-u1`,
|
|
71
|
+
createdAt: new Date().toISOString(),
|
|
72
|
+
owner: { type: 'CLIENT' },
|
|
73
|
+
messageData: {
|
|
74
|
+
type: 'TEXT',
|
|
75
|
+
text: 'Which devices have not sent any logs in the last 24 hours?',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: `${dialogId}-a1`,
|
|
80
|
+
createdAt: new Date().toISOString(),
|
|
81
|
+
owner: { type: 'ASSISTANT' },
|
|
82
|
+
messageData: {
|
|
83
|
+
type: 'TEXT',
|
|
84
|
+
text: 'Found 3 devices that have not reported in over 24 hours. Want me to run a full diagnostic?',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
nextCursor: null,
|
|
89
|
+
}),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Sample dialog history (Figma node 7532:223950) — supplied via `fetchDialogs`
|
|
94
|
+
// so EmbeddableChat reports `dialogs.length > 0` and renders MingoWelcome's
|
|
95
|
+
// returning-user variation (outline "Start Guide Chat" chip, no promo).
|
|
96
|
+
const SAMPLE_DIALOGS: ReadonlyArray<DialogItem> = [
|
|
97
|
+
{ id: 'd1', title: 'PowerShell script for bulk user creation', unreadMessagesCount: 1 },
|
|
98
|
+
{ id: 'd2', title: 'Setting up automated backup verification', unreadMessagesCount: 1 },
|
|
99
|
+
{ id: 'd3', title: 'Network segmentation best practices for client' },
|
|
100
|
+
{ id: 'd4', title: 'Creating GPO for software deployment' },
|
|
101
|
+
{ id: 'd5', title: 'WSUS patching strategy optimization' },
|
|
102
|
+
{ id: 'd6', title: 'Office 365 license assignment automation' },
|
|
103
|
+
{ id: 'd7', title: 'Firewall rule configuration for new application' },
|
|
104
|
+
{ id: 'd8', title: 'SQL Server maintenance plan troubleshooting' },
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
/** Mingo config whose `fetchDialogs` resolves a single page of history (with
|
|
108
|
+
* timestamps so the list splits into Today / Yesterday) plus working
|
|
109
|
+
* rename/archive callbacks — used by the returning-user story. */
|
|
110
|
+
function createMockMingoConfigWithDialogs(): UseNatsChatAdapterConfig {
|
|
111
|
+
return {
|
|
112
|
+
...createMockMingoConfig(),
|
|
113
|
+
fetchDialogs: () => {
|
|
114
|
+
const now = Date.now()
|
|
115
|
+
const day = 24 * 60 * 60 * 1000
|
|
116
|
+
// First six → Today, the rest → Yesterday.
|
|
117
|
+
const dialogs = SAMPLE_DIALOGS.map((d, i) => ({
|
|
118
|
+
...d,
|
|
119
|
+
timestamp: new Date(now - (i < 6 ? 0 : day)),
|
|
120
|
+
}))
|
|
121
|
+
return Promise.resolve({ dialogs, nextCursor: null })
|
|
122
|
+
},
|
|
123
|
+
renameDialog: (id, title) => {
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.log('[story] mingo rename', { id, title })
|
|
126
|
+
return Promise.resolve()
|
|
127
|
+
},
|
|
128
|
+
archiveDialog: (id) => {
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.log('[story] mingo archive', { id })
|
|
131
|
+
return Promise.resolve()
|
|
132
|
+
},
|
|
133
|
+
fetchArchivedDialogs: () => {
|
|
134
|
+
const now = Date.now()
|
|
135
|
+
const day = 24 * 60 * 60 * 1000
|
|
136
|
+
const dialogs: DialogItem[] = [
|
|
137
|
+
{ id: 'a1', title: 'Exchange hybrid migration planning', timestamp: new Date(now) },
|
|
138
|
+
{ id: 'a2', title: 'VPN split-tunnel configuration review', timestamp: new Date(now) },
|
|
139
|
+
{ id: 'a3', title: 'Decommissioning legacy file server', timestamp: new Date(now - day) },
|
|
140
|
+
{ id: 'a4', title: 'Intune compliance policy rollout', timestamp: new Date(now - day) },
|
|
141
|
+
]
|
|
142
|
+
return Promise.resolve({ dialogs, nextCursor: null })
|
|
143
|
+
},
|
|
144
|
+
unarchiveDialog: (id) => {
|
|
145
|
+
// eslint-disable-next-line no-console
|
|
146
|
+
console.log('[story] mingo unarchive', { id })
|
|
147
|
+
return Promise.resolve()
|
|
148
|
+
},
|
|
62
149
|
}
|
|
63
150
|
}
|
|
64
151
|
|
|
@@ -354,3 +441,30 @@ export const BothModes: Story = {
|
|
|
354
441
|
),
|
|
355
442
|
args: {},
|
|
356
443
|
}
|
|
444
|
+
|
|
445
|
+
// =============================================================================
|
|
446
|
+
// 4. Returning user — Mingo with existing chats (Figma node 7532:223950)
|
|
447
|
+
// =============================================================================
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Returning-user variation. The mock `fetchDialogs` resolves a page of dialog
|
|
451
|
+
* history, so `dialogs.length > 0` and the empty state switches to its
|
|
452
|
+
* returning-user form: the "New to OpenFrame?" notification is hidden and the
|
|
453
|
+
* "Start Guide Chat" chip drops from the accent yellow to the muted outline
|
|
454
|
+
* style. The dialog list itself renders inline via MingoChatHistory.
|
|
455
|
+
*/
|
|
456
|
+
export const ReturningUser: Story = {
|
|
457
|
+
render: (args) => (
|
|
458
|
+
<EmbeddableChat
|
|
459
|
+
{...args}
|
|
460
|
+
modes={{
|
|
461
|
+
guide: {},
|
|
462
|
+
mingo: createMockMingoConfigWithDialogs(),
|
|
463
|
+
}}
|
|
464
|
+
defaultActiveMode="mingo"
|
|
465
|
+
defaultOpen
|
|
466
|
+
showInternalTrigger={false}
|
|
467
|
+
/>
|
|
468
|
+
),
|
|
469
|
+
args: {},
|
|
470
|
+
}
|