@flamingo-stack/openframe-frontend-core 0.0.215 → 0.0.216
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-2V4SACHE.js +302 -0
- package/dist/chunk-2V4SACHE.js.map +1 -0
- package/dist/chunk-572WQWIX.cjs +348 -0
- package/dist/chunk-572WQWIX.cjs.map +1 -0
- package/dist/{chunk-WT5JV2GS.cjs → chunk-5V6MSE3B.cjs} +39 -39
- package/dist/chunk-5V6MSE3B.cjs.map +1 -0
- package/dist/{chunk-WQZP3JIZ.js → chunk-CDLYRFDE.js} +1894 -1472
- package/dist/chunk-CDLYRFDE.js.map +1 -0
- package/dist/chunk-GVNQAGXB.js +232 -0
- package/dist/chunk-GVNQAGXB.js.map +1 -0
- package/dist/{chunk-P5EE2VJX.cjs → chunk-HOHDXYPR.cjs} +1 -1
- package/dist/chunk-HOHDXYPR.cjs.map +1 -0
- package/dist/chunk-IH76P5R6.cjs +232 -0
- package/dist/chunk-IH76P5R6.cjs.map +1 -0
- package/dist/{chunk-24KCAECR.cjs → chunk-JJR27M56.cjs} +3 -3
- package/dist/{chunk-24KCAECR.cjs.map → chunk-JJR27M56.cjs.map} +1 -1
- package/dist/chunk-K4DFAVSO.cjs +302 -0
- package/dist/chunk-K4DFAVSO.cjs.map +1 -0
- package/dist/{chunk-HICZPTRR.js → chunk-LCLTCCXS.js} +14 -14
- package/dist/chunk-LCLTCCXS.js.map +1 -0
- package/dist/{chunk-VFKQMAUF.cjs → chunk-OB45JHDY.cjs} +3 -3
- package/dist/{chunk-VFKQMAUF.cjs.map → chunk-OB45JHDY.cjs.map} +1 -1
- package/dist/{chunk-4XLJWX2N.js → chunk-ORJREQ2W.js} +4 -4
- package/dist/{chunk-7PCP7YQR.js → chunk-QTKU6ULP.js} +6 -6
- package/dist/{chunk-CIPO6DXK.js → chunk-QY75VKAS.js} +5 -5
- package/dist/{chunk-ZG2YY5E7.js → chunk-RFONYT63.js} +1 -1
- package/dist/chunk-RFONYT63.js.map +1 -0
- package/dist/{chunk-NGFP4RVL.cjs → chunk-SMCG2CCC.cjs} +30 -30
- package/dist/{chunk-NGFP4RVL.cjs.map → chunk-SMCG2CCC.cjs.map} +1 -1
- package/dist/{chunk-MX5MIFWA.js → chunk-UEBM4PC4.js} +5 -5
- package/dist/chunk-VC3ND5RB.js +348 -0
- package/dist/chunk-VC3ND5RB.js.map +1 -0
- package/dist/{chunk-UXZ3ZJ3M.cjs → chunk-XDPSSE4O.cjs} +4 -4
- package/dist/{chunk-UXZ3ZJ3M.cjs.map → chunk-XDPSSE4O.cjs.map} +1 -1
- package/dist/{chunk-D4MNFY67.cjs → chunk-ZGTDUPTW.cjs} +1316 -894
- package/dist/chunk-ZGTDUPTW.cjs.map +1 -0
- package/dist/components/chat/entity-cards/blog-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/case-study-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/investor-update-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/program-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/use-entity-card-link.d.ts +14 -0
- package/dist/components/chat/entity-cards/use-entity-card-link.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts +13 -0
- package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +11 -11
- package/dist/components/chat/index.js +10 -10
- package/dist/components/contact/index.cjs +12 -12
- package/dist/components/contact/index.js +11 -11
- package/dist/components/features/captions-url.d.ts +18 -0
- package/dist/components/features/captions-url.d.ts.map +1 -0
- package/dist/components/features/index.cjs +23 -11
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +2 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +24 -12
- package/dist/components/features/mux-origins.cjs +10 -0
- package/dist/components/features/mux-origins.cjs.map +1 -0
- package/dist/components/features/mux-origins.d.ts +26 -0
- package/dist/components/features/mux-origins.d.ts.map +1 -0
- package/dist/components/features/mux-origins.js +7 -0
- package/dist/components/features/mux-origins.js.map +1 -0
- package/dist/components/features/notifications/index.d.ts +2 -0
- package/dist/components/features/notifications/index.d.ts.map +1 -1
- package/dist/components/features/notifications/notification-drawer.d.ts +2 -1
- package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
- package/dist/components/features/notifications/notification-popups.d.ts +10 -0
- package/dist/components/features/notifications/notification-popups.d.ts.map +1 -0
- package/dist/components/features/notifications/notifications-context.d.ts +8 -1
- package/dist/components/features/notifications/notifications-context.d.ts.map +1 -1
- package/dist/components/features/notifications/types.d.ts +1 -0
- package/dist/components/features/notifications/types.d.ts.map +1 -1
- package/dist/components/features/use-video-warmup.d.ts +53 -0
- package/dist/components/features/use-video-warmup.d.ts.map +1 -0
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/icons-v2-generated/index.cjs +2 -2
- package/dist/components/icons-v2-generated/index.cjs.map +1 -1
- package/dist/components/icons-v2-generated/index.js +4 -4
- package/dist/components/index.cjs +132 -102
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +94 -64
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +11 -11
- package/dist/components/navigation/index.js +10 -10
- package/dist/components/onboarding-guides/build-default-href.d.ts +15 -0
- package/dist/components/onboarding-guides/build-default-href.d.ts.map +1 -0
- package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts +28 -0
- package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts.map +1 -0
- package/dist/components/onboarding-guides/index.cjs +373 -0
- package/dist/components/onboarding-guides/index.cjs.map +1 -0
- package/dist/components/onboarding-guides/index.d.ts +25 -0
- package/dist/components/onboarding-guides/index.d.ts.map +1 -0
- package/dist/components/onboarding-guides/index.js +373 -0
- package/dist/components/onboarding-guides/index.js.map +1 -0
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +52 -0
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -0
- package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts +17 -0
- package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts.map +1 -0
- package/dist/components/onboarding-guides/onboarding-guides-catalog-view.d.ts +43 -0
- package/dist/components/onboarding-guides/onboarding-guides-catalog-view.d.ts.map +1 -0
- package/dist/components/shared/doc-search/doc-search-bar.d.ts +59 -0
- package/dist/components/shared/doc-search/doc-search-bar.d.ts.map +1 -0
- package/dist/components/shared/doc-search/doc-search-result-row.d.ts +18 -0
- package/dist/components/shared/doc-search/doc-search-result-row.d.ts.map +1 -0
- package/dist/components/shared/doc-search/format-relative-path.d.ts +10 -0
- package/dist/components/shared/doc-search/format-relative-path.d.ts.map +1 -0
- package/dist/components/shared/doc-search/index.d.ts +8 -0
- package/dist/components/shared/doc-search/index.d.ts.map +1 -0
- package/dist/components/shared/doc-search/map-doc-search-results.d.ts +15 -0
- package/dist/components/shared/doc-search/map-doc-search-results.d.ts.map +1 -0
- package/dist/components/shared/doc-search/resolve-search-result-action.d.ts +37 -0
- package/dist/components/shared/doc-search/resolve-search-result-action.d.ts.map +1 -0
- package/dist/components/shared/doc-search/types.d.ts +29 -0
- package/dist/components/shared/doc-search/types.d.ts.map +1 -0
- package/dist/components/shared/doc-search/use-doc-search.d.ts +46 -0
- package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -0
- package/dist/components/tickets/help-center-card.d.ts +5 -1
- package/dist/components/tickets/help-center-card.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-ticket-actions.d.ts +8 -0
- package/dist/components/tickets/hooks/use-ticket-actions.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +316 -145
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +237 -66
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/tickets/ticket-detail-drawer.d.ts +11 -2
- package/dist/components/tickets/ticket-detail-drawer.d.ts.map +1 -1
- package/dist/components/tickets/types.d.ts +50 -1
- package/dist/components/tickets/types.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +51 -51
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +2 -2
- package/dist/components/ui/filter-pill-row.d.ts +20 -0
- package/dist/components/ui/filter-pill-row.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +16 -14
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +21 -19
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +42 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +5 -5
- package/dist/hooks/index.cjs +6 -6
- package/dist/hooks/index.js +5 -5
- package/dist/index.cjs +28 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +59 -45
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts +2 -2
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts.map +1 -1
- package/dist/utils/index.cjs +11 -5
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +11 -5
- package/dist/utils/index.js.map +1 -1
- package/package.json +13 -1
- package/src/components/chat/entity-cards/blog-card.tsx +17 -5
- package/src/components/chat/entity-cards/case-study-card.tsx +23 -1
- package/src/components/chat/entity-cards/customer-interview-card.tsx +23 -1
- package/src/components/chat/entity-cards/dispatch.tsx +21 -0
- package/src/components/chat/entity-cards/investor-update-card.tsx +23 -1
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +30 -4
- package/src/components/chat/entity-cards/program-card.tsx +17 -3
- package/src/components/chat/entity-cards/use-entity-card-link.ts +66 -0
- package/src/components/chat/entity-cards/use-entity-card-placeholder.ts +50 -0
- package/src/components/features/captions-url.ts +25 -0
- package/src/components/features/index.ts +2 -0
- package/src/components/features/mux-origins.ts +27 -0
- package/src/components/features/notifications/index.ts +2 -0
- package/src/components/features/notifications/notification-drawer.tsx +100 -16
- package/src/components/features/notifications/notification-popups.tsx +105 -0
- package/src/components/features/notifications/notifications-context.tsx +16 -0
- package/src/components/features/notifications/types.ts +1 -0
- package/src/components/features/use-video-warmup.ts +176 -0
- package/src/components/index.ts +5 -0
- package/src/components/onboarding-guides/build-default-href.ts +16 -0
- package/src/components/onboarding-guides/hooks/use-onboarding-guides.ts +90 -0
- package/src/components/onboarding-guides/index.ts +39 -0
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +215 -0
- package/src/components/onboarding-guides/onboarding-guides-catalog-skeleton.tsx +62 -0
- package/src/components/onboarding-guides/onboarding-guides-catalog-view.tsx +230 -0
- package/src/components/shared/doc-search/doc-search-bar.tsx +100 -0
- package/src/components/shared/doc-search/doc-search-result-row.tsx +73 -0
- package/src/components/shared/doc-search/format-relative-path.ts +17 -0
- package/src/components/shared/doc-search/index.ts +24 -0
- package/src/components/shared/doc-search/map-doc-search-results.ts +113 -0
- package/src/components/shared/doc-search/resolve-search-result-action.ts +68 -0
- package/src/components/shared/doc-search/types.ts +28 -0
- package/src/components/shared/doc-search/use-doc-search.ts +263 -0
- package/src/components/tickets/help-center-card.tsx +8 -0
- package/src/components/tickets/help-center-list.tsx +17 -3
- package/src/components/tickets/hooks/use-ticket-actions.ts +210 -14
- package/src/components/tickets/ticket-detail-drawer.tsx +145 -5
- package/src/components/tickets/types.ts +55 -0
- package/src/components/ui/filter-pill-row.tsx +72 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/simple-markdown-renderer.tsx +24 -1
- package/src/components/ui/toaster.tsx +3 -3
- package/src/contexts/chat-runtime-context.tsx +41 -0
- package/src/stories/NotificationDrawer.stories.tsx +18 -2
- package/src/utils/dev-sections/openframe-dev-sections.ts +12 -5
- package/dist/chunk-2G3NXF6J.cjs +0 -521
- package/dist/chunk-2G3NXF6J.cjs.map +0 -1
- package/dist/chunk-D4MNFY67.cjs.map +0 -1
- package/dist/chunk-HICZPTRR.js.map +0 -1
- package/dist/chunk-P5EE2VJX.cjs.map +0 -1
- package/dist/chunk-R6MLPU4A.js +0 -521
- package/dist/chunk-R6MLPU4A.js.map +0 -1
- package/dist/chunk-WQZP3JIZ.js.map +0 -1
- package/dist/chunk-WT5JV2GS.cjs.map +0 -1
- package/dist/chunk-ZG2YY5E7.js.map +0 -1
- /package/dist/{chunk-4XLJWX2N.js.map → chunk-ORJREQ2W.js.map} +0 -0
- /package/dist/{chunk-7PCP7YQR.js.map → chunk-QTKU6ULP.js.map} +0 -0
- /package/dist/{chunk-CIPO6DXK.js.map → chunk-QY75VKAS.js.map} +0 -0
- /package/dist/{chunk-MX5MIFWA.js.map → chunk-UEBM4PC4.js.map} +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { KeyboardEvent, MouseEvent } from 'react'
|
|
4
|
+
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useOptionalNotifications } from './notifications-context'
|
|
6
|
+
import { NotificationTile } from './notification-tile'
|
|
7
|
+
import type { Notification } from './types'
|
|
8
|
+
|
|
9
|
+
export type NotificationPopupsPosition =
|
|
10
|
+
| 'top-right'
|
|
11
|
+
| 'top-left'
|
|
12
|
+
| 'bottom-right'
|
|
13
|
+
| 'bottom-left'
|
|
14
|
+
|
|
15
|
+
export interface NotificationPopupsProps {
|
|
16
|
+
className?: string
|
|
17
|
+
liveDurationMs?: number
|
|
18
|
+
maxVisible?: number
|
|
19
|
+
position?: NotificationPopupsPosition
|
|
20
|
+
hideWhenDrawerOpen?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const positionClasses: Record<NotificationPopupsPosition, string> = {
|
|
24
|
+
'top-right': 'top-4 right-4',
|
|
25
|
+
'top-left': 'top-4 left-4',
|
|
26
|
+
'bottom-right': 'bottom-4 right-4 flex-col-reverse',
|
|
27
|
+
'bottom-left': 'bottom-4 left-4 flex-col-reverse',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function NotificationPopups({
|
|
31
|
+
className,
|
|
32
|
+
liveDurationMs = 4000,
|
|
33
|
+
maxVisible = 4,
|
|
34
|
+
position = 'top-right',
|
|
35
|
+
hideWhenDrawerOpen = true,
|
|
36
|
+
}: NotificationPopupsProps) {
|
|
37
|
+
const ctx = useOptionalNotifications()
|
|
38
|
+
if (!ctx) return null
|
|
39
|
+
|
|
40
|
+
const { notifications, showPopups, isOpen, open, markRead, markSettled } = ctx
|
|
41
|
+
|
|
42
|
+
if (!showPopups) return null
|
|
43
|
+
if (hideWhenDrawerOpen && isOpen) return null
|
|
44
|
+
|
|
45
|
+
const now = Date.now()
|
|
46
|
+
const live = notifications
|
|
47
|
+
.filter((n) => !n.read && !n.settled && now - n.createdAt < liveDurationMs)
|
|
48
|
+
.slice(0, maxVisible)
|
|
49
|
+
|
|
50
|
+
if (live.length === 0) return null
|
|
51
|
+
|
|
52
|
+
const activate = (notification: Notification) => {
|
|
53
|
+
if (notification.onClick) {
|
|
54
|
+
notification.onClick()
|
|
55
|
+
markRead(notification.id)
|
|
56
|
+
markSettled(notification.id)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
open()
|
|
60
|
+
markSettled(notification.id)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleBodyClick = (notification: Notification) => (event: MouseEvent<HTMLDivElement>) => {
|
|
64
|
+
// Nested interactive controls (X, check button) handle their own actions.
|
|
65
|
+
if ((event.target as HTMLElement).closest('button')) return
|
|
66
|
+
activate(notification)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleBodyKeyDown = (notification: Notification) => (event: KeyboardEvent<HTMLDivElement>) => {
|
|
70
|
+
if ((event.target as HTMLElement).closest('button')) return
|
|
71
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
activate(notification)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
aria-live="polite"
|
|
79
|
+
className={cn(
|
|
80
|
+
'pointer-events-none fixed z-[9999] flex w-[26rem] max-w-[calc(100vw-2rem)] flex-col gap-[var(--spacing-system-xs)]',
|
|
81
|
+
positionClasses[position],
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{live.map((n) => (
|
|
86
|
+
// biome-ignore lint/a11y/useSemanticElements: nested interactive elements forbid <button>
|
|
87
|
+
<div
|
|
88
|
+
key={n.id}
|
|
89
|
+
role="button"
|
|
90
|
+
tabIndex={0}
|
|
91
|
+
onClick={handleBodyClick(n)}
|
|
92
|
+
onKeyDown={handleBodyKeyDown(n)}
|
|
93
|
+
className="pointer-events-auto cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
|
|
94
|
+
>
|
|
95
|
+
<NotificationTile
|
|
96
|
+
notification={n}
|
|
97
|
+
liveDurationMs={liveDurationMs}
|
|
98
|
+
onComplete={markRead}
|
|
99
|
+
onSettle={markSettled}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -21,6 +21,9 @@ interface NotificationsContextValue {
|
|
|
21
21
|
toggle: () => void
|
|
22
22
|
setShowPopups: (value: boolean) => void
|
|
23
23
|
onHistoryClick?: () => void
|
|
24
|
+
hasMore: boolean
|
|
25
|
+
isLoadingMore: boolean
|
|
26
|
+
loadMore?: () => void
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const NotificationsContext = React.createContext<NotificationsContextValue | null>(null)
|
|
@@ -44,6 +47,10 @@ export interface NotificationsProviderProps {
|
|
|
44
47
|
onShowPopupsChange?: (value: boolean) => void
|
|
45
48
|
onHistoryClick?: () => void
|
|
46
49
|
actions?: NotificationsActions
|
|
50
|
+
/** Pagination — when omitted, the drawer hides its load-more sentinel. */
|
|
51
|
+
hasMore?: boolean
|
|
52
|
+
isLoadingMore?: boolean
|
|
53
|
+
onLoadMore?: () => void
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
type Action =
|
|
@@ -130,6 +137,9 @@ export function NotificationsProvider({
|
|
|
130
137
|
onShowPopupsChange,
|
|
131
138
|
onHistoryClick,
|
|
132
139
|
actions,
|
|
140
|
+
hasMore = false,
|
|
141
|
+
isLoadingMore = false,
|
|
142
|
+
onLoadMore,
|
|
133
143
|
}: NotificationsProviderProps) {
|
|
134
144
|
const [notifications, dispatch] = React.useReducer(reducer, initialNotifications)
|
|
135
145
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
@@ -231,6 +241,9 @@ export function NotificationsProvider({
|
|
|
231
241
|
toggle,
|
|
232
242
|
setShowPopups,
|
|
233
243
|
onHistoryClick,
|
|
244
|
+
hasMore,
|
|
245
|
+
isLoadingMore,
|
|
246
|
+
loadMore: onLoadMore,
|
|
234
247
|
}),
|
|
235
248
|
[
|
|
236
249
|
notifications,
|
|
@@ -250,6 +263,9 @@ export function NotificationsProvider({
|
|
|
250
263
|
toggle,
|
|
251
264
|
setShowPopups,
|
|
252
265
|
onHistoryClick,
|
|
266
|
+
hasMore,
|
|
267
|
+
isLoadingMore,
|
|
268
|
+
onLoadMore,
|
|
253
269
|
],
|
|
254
270
|
)
|
|
255
271
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `useVideoWarmup` — single-source-of-truth hook for warming the
|
|
5
|
+
* network path to a public entity's main video so click→first-frame
|
|
6
|
+
* lands in sub-second on Fast 4G.
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Preconnect on every render** (`ReactDOM.preconnect`) — buys
|
|
11
|
+
* the TCP / TLS handshakes to the video-bearing origins. React
|
|
12
|
+
* 19 de-dupes identical preconnects, so this is safe to call
|
|
13
|
+
* on every render.
|
|
14
|
+
*
|
|
15
|
+
* 2. **Preload the video bytes** (`<link rel="preload" as="video">`)
|
|
16
|
+
* ONLY when:
|
|
17
|
+
* - the consumer's container scrolls within `nearMargin` of
|
|
18
|
+
* the viewport (gated via the lib's IO singleton hook), AND
|
|
19
|
+
* - `navigator.connection?.saveData !== true`, AND
|
|
20
|
+
* - the URL is on the Supabase storage origin (Mux HLS warms
|
|
21
|
+
* via its own manifest fetch when MuxPlayer mounts; YouTube
|
|
22
|
+
* has its own origin pool, no preload benefit).
|
|
23
|
+
*
|
|
24
|
+
* Origin configuration:
|
|
25
|
+
* - Mux origins (`stream.mux.com` / `image.mux.com`) are public
|
|
26
|
+
* Mux CDN hostnames and stable across the Mux API contract —
|
|
27
|
+
* hardcoded here.
|
|
28
|
+
* - Supabase storage origin varies per-deployment (different
|
|
29
|
+
* project per env). Threaded via the `supabaseStorageOrigin`
|
|
30
|
+
* argument so the lib stays env-agnostic; hub callers pass
|
|
31
|
+
* `getSupabaseStorageOrigin()` from their env config, or read
|
|
32
|
+
* it from `ChatRuntime.endpoints.supabaseStorageOrigin`.
|
|
33
|
+
*
|
|
34
|
+
* Lifted from hub `hooks/use-video-warmup.ts`. The Mux constants
|
|
35
|
+
* and the IO-gated preload semantics are byte-equivalent.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { useEffect } from 'react'
|
|
39
|
+
import ReactDOM from 'react-dom'
|
|
40
|
+
import { useNearViewport } from '../../hooks/use-near-viewport'
|
|
41
|
+
import { useChatRuntime } from '../../contexts/chat-runtime-context'
|
|
42
|
+
// Re-export from the server-safe `mux-origins.ts` module so the
|
|
43
|
+
// constants are NOT bound to this `'use client'` file. See the
|
|
44
|
+
// JSDoc in `mux-origins.ts` for the bug history. Backward-compat:
|
|
45
|
+
// existing imports that read `MUX_STREAM_ORIGIN` from
|
|
46
|
+
// `@flamingo-stack/openframe-frontend-core/components/features`
|
|
47
|
+
// continue to resolve through this re-export.
|
|
48
|
+
export { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'
|
|
49
|
+
import { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Preconnect-only variant — fires the three video-bearing origin
|
|
53
|
+
* preconnects (Supabase Storage + Mux stream + Mux image) without
|
|
54
|
+
* setting up the IntersectionObserver subscription or the preload
|
|
55
|
+
* `<link>` injection.
|
|
56
|
+
*
|
|
57
|
+
* Use this when the consumer can't attach a `ref` to the video
|
|
58
|
+
* container (e.g. release detail page, which delegates the player
|
|
59
|
+
* render to a sibling component). Calling the full `useVideoWarmup`
|
|
60
|
+
* from there would subscribe to a never-mounted ref and ship dead
|
|
61
|
+
* preload machinery in the bundle.
|
|
62
|
+
*
|
|
63
|
+
* For consumers that own the video container, use `useVideoWarmup`
|
|
64
|
+
* (which composes this hook + the IO-gated preload step).
|
|
65
|
+
*
|
|
66
|
+
* Reads `supabaseStorageOrigin` from `ChatRuntime.endpoints` by
|
|
67
|
+
* default — callers in hosts that mount `HubRuntimeProvider` (or
|
|
68
|
+
* any equivalent provider that wires the field) get the origin
|
|
69
|
+
* automatically. The explicit `supabaseStorageOrigin` argument
|
|
70
|
+
* overrides the runtime value when set.
|
|
71
|
+
*/
|
|
72
|
+
export function useVideoOriginPreconnect({
|
|
73
|
+
supabaseStorageOrigin,
|
|
74
|
+
}: { supabaseStorageOrigin?: string } = {}): void {
|
|
75
|
+
const runtime = useChatRuntime()
|
|
76
|
+
const resolvedOrigin =
|
|
77
|
+
supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin
|
|
78
|
+
try {
|
|
79
|
+
ReactDOM.preconnect(MUX_STREAM_ORIGIN, { crossOrigin: 'anonymous' })
|
|
80
|
+
ReactDOM.preconnect(MUX_IMAGE_ORIGIN, { crossOrigin: 'anonymous' })
|
|
81
|
+
if (resolvedOrigin) {
|
|
82
|
+
ReactDOM.preconnect(resolvedOrigin, { crossOrigin: 'anonymous' })
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.warn('[useVideoOriginPreconnect] preconnect failed:', err)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface UseVideoWarmupOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Effective video URL the page renders. Pass null/undefined when
|
|
95
|
+
* there's no video yet (the hook still preconnects). Only URLs on
|
|
96
|
+
* `supabaseStorageOrigin` are preloaded — Mux HLS and YouTube are
|
|
97
|
+
* no-ops on the preload side.
|
|
98
|
+
*/
|
|
99
|
+
videoUrl?: string | null
|
|
100
|
+
/**
|
|
101
|
+
* Supabase storage origin (e.g. `https://xyz.supabase.co`). When
|
|
102
|
+
* omitted, falls back to `ChatRuntime.endpoints.supabaseStorageOrigin`
|
|
103
|
+
* — hosts that mount `HubRuntimeProvider` (or any equivalent
|
|
104
|
+
* provider) get the origin automatically. When neither is set, the
|
|
105
|
+
* preload step is skipped (preconnect to Mux still fires).
|
|
106
|
+
*/
|
|
107
|
+
supabaseStorageOrigin?: string
|
|
108
|
+
/**
|
|
109
|
+
* IO root margin gate for the preload step. Default `'1000px'` —
|
|
110
|
+
* about one viewport's worth of lookahead on desktop.
|
|
111
|
+
*/
|
|
112
|
+
nearMargin?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface UseVideoWarmupResult<T extends Element = HTMLDivElement> {
|
|
116
|
+
ref: (node: T | null) => void
|
|
117
|
+
isNear: boolean
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function useVideoWarmup<T extends Element = HTMLDivElement>({
|
|
121
|
+
videoUrl,
|
|
122
|
+
supabaseStorageOrigin,
|
|
123
|
+
nearMargin = '1000px',
|
|
124
|
+
}: UseVideoWarmupOptions = {}): UseVideoWarmupResult<T> {
|
|
125
|
+
// Resolve origin once — runtime fallback so callers in hosts that
|
|
126
|
+
// mount `HubRuntimeProvider` don't need to thread it themselves.
|
|
127
|
+
const runtime = useChatRuntime()
|
|
128
|
+
const resolvedOrigin =
|
|
129
|
+
supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin
|
|
130
|
+
|
|
131
|
+
// Preconnect on every render — React 19 dedupes. Delegates to the
|
|
132
|
+
// shared preconnect-only variant so the origin list is a single
|
|
133
|
+
// source of truth.
|
|
134
|
+
useVideoOriginPreconnect({ supabaseStorageOrigin: resolvedOrigin })
|
|
135
|
+
|
|
136
|
+
const { ref, isNear } = useNearViewport<T>(nearMargin)
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!isNear || !videoUrl || !resolvedOrigin) return
|
|
140
|
+
|
|
141
|
+
// Save-Data gate — metered connections skip preload.
|
|
142
|
+
type Connection = { saveData?: boolean }
|
|
143
|
+
const conn = (navigator as Navigator & { connection?: Connection }).connection
|
|
144
|
+
if (conn?.saveData === true) return
|
|
145
|
+
|
|
146
|
+
// Origin gate: only preload Supabase-hosted MP4s. Mux HLS warms
|
|
147
|
+
// via the manifest fetch when MuxPlayer mounts; YouTube has no
|
|
148
|
+
// preload benefit.
|
|
149
|
+
let videoOrigin: string
|
|
150
|
+
try {
|
|
151
|
+
videoOrigin = new URL(videoUrl, 'http://placeholder.local').origin
|
|
152
|
+
} catch {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (videoOrigin !== resolvedOrigin) return
|
|
156
|
+
|
|
157
|
+
const link = document.createElement('link')
|
|
158
|
+
link.rel = 'preload'
|
|
159
|
+
link.as = 'video'
|
|
160
|
+
link.href = videoUrl
|
|
161
|
+
link.crossOrigin = 'anonymous'
|
|
162
|
+
// `fetchPriority='low'` matches the plan — the hint should not
|
|
163
|
+
// steal network from the LCP image; the click→first-frame win is
|
|
164
|
+
// in milliseconds, not the first paint.
|
|
165
|
+
if ('fetchPriority' in link) {
|
|
166
|
+
;(link as HTMLLinkElement & { fetchPriority?: string }).fetchPriority = 'low'
|
|
167
|
+
}
|
|
168
|
+
document.head.appendChild(link)
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
link.remove()
|
|
172
|
+
}
|
|
173
|
+
}, [isNear, videoUrl, resolvedOrigin])
|
|
174
|
+
|
|
175
|
+
return { ref, isNear }
|
|
176
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -62,6 +62,11 @@ export * from './chat'
|
|
|
62
62
|
// Onboarding components
|
|
63
63
|
export * from './shared/onboarding'
|
|
64
64
|
|
|
65
|
+
// Doc-search bar — unified RAG-search dropdown used by the data-room
|
|
66
|
+
// sidebar AND the onboarding-guide catalog. Pure presentation; hosts
|
|
67
|
+
// own the `useDocSearch` hook and pass results in as props.
|
|
68
|
+
export * from './shared/doc-search'
|
|
69
|
+
|
|
65
70
|
// Product Release components
|
|
66
71
|
export * from './shared/product-release'
|
|
67
72
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lib-internal helper — default href shape for onboarding-guide cards
|
|
3
|
+
* when `runtime.composeContentUrl` is not wired (single-platform
|
|
4
|
+
* embedders without cross-platform topology).
|
|
5
|
+
*
|
|
6
|
+
* Shared between `OnboardingGuidesCatalogView` (per-card href) and
|
|
7
|
+
* `OnboardingGuideDetailView` (per-related-card href) so both views
|
|
8
|
+
* compose hrefs from the same `basePath`-derived shape — no parallel
|
|
9
|
+
* 4-line helper to drift apart.
|
|
10
|
+
*/
|
|
11
|
+
export function buildDefaultHref(
|
|
12
|
+
basePath: string,
|
|
13
|
+
slug: string,
|
|
14
|
+
): { href: string; targetPlatform: string | null } {
|
|
15
|
+
return { href: `${basePath}/${slug}`, targetPlatform: null }
|
|
16
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* React Query hooks for Onboarding Guides — PUBLIC reads only.
|
|
5
|
+
*
|
|
6
|
+
* Admin mutations (create/update/delete/publish/stats/save-highlight)
|
|
7
|
+
* stay HUB-side in `hooks/use-onboarding-guides.ts` and import the
|
|
8
|
+
* `onboardingGuideKeys.admin` sub-namespace from this module so the
|
|
9
|
+
* cache namespace `['onboarding-guides']` has a single source of truth.
|
|
10
|
+
*
|
|
11
|
+
* Endpoints (`/api/onboarding-guides*`) are host-supplied — non-Next.js
|
|
12
|
+
* embedders must reverse-proxy the same routes. Same precedent as the
|
|
13
|
+
* tickets list hook.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useQuery } from '@tanstack/react-query'
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
OnboardingGuide,
|
|
20
|
+
OnboardingGuideFilters,
|
|
21
|
+
OnboardingGuideListResponse,
|
|
22
|
+
OnboardingGuideSectionSummary,
|
|
23
|
+
} from '../../chat/types/entities/onboarding-guide'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cache key builder for the `['onboarding-guides']` namespace.
|
|
27
|
+
*
|
|
28
|
+
* Includes BOTH public-read sub-keys (`lists`, `list`, `details`,
|
|
29
|
+
* `detail`, `sections`) AND the `admin` sub-namespace. Admin mutations
|
|
30
|
+
* live hub-side but invalidate against this same builder so a single
|
|
31
|
+
* `qc.invalidateQueries({ queryKey: onboardingGuideKeys.all })` from
|
|
32
|
+
* an admin hook also clears the public read cache.
|
|
33
|
+
*/
|
|
34
|
+
export const onboardingGuideKeys = {
|
|
35
|
+
all: ['onboarding-guides'] as const,
|
|
36
|
+
lists: () => [...onboardingGuideKeys.all, 'list'] as const,
|
|
37
|
+
list: (filters: OnboardingGuideFilters) =>
|
|
38
|
+
[...onboardingGuideKeys.lists(), filters] as const,
|
|
39
|
+
details: () => [...onboardingGuideKeys.all, 'detail'] as const,
|
|
40
|
+
detail: (slug: string) => [...onboardingGuideKeys.details(), slug] as const,
|
|
41
|
+
sections: () => [...onboardingGuideKeys.all, 'sections'] as const,
|
|
42
|
+
admin: {
|
|
43
|
+
all: ['admin', 'onboarding-guides'] as const,
|
|
44
|
+
lists: () => ['admin', 'onboarding-guides', 'list'] as const,
|
|
45
|
+
detail: (slug: string) =>
|
|
46
|
+
['admin', 'onboarding-guide', slug] as const,
|
|
47
|
+
stats: () => ['admin', 'onboarding-guides', 'stats'] as const,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useOnboardingGuides(filters?: OnboardingGuideFilters) {
|
|
52
|
+
return useQuery({
|
|
53
|
+
queryKey: onboardingGuideKeys.list(filters || {}),
|
|
54
|
+
queryFn: async (): Promise<OnboardingGuideListResponse> => {
|
|
55
|
+
const params = new URLSearchParams()
|
|
56
|
+
if (filters?.search) params.set('search', filters.search)
|
|
57
|
+
if (filters?.section) params.set('section', filters.section)
|
|
58
|
+
if (filters?.limit) params.set('limit', filters.limit.toString())
|
|
59
|
+
if (filters?.offset) params.set('offset', filters.offset.toString())
|
|
60
|
+
const res = await fetch(`/api/onboarding-guides?${params}`)
|
|
61
|
+
if (!res.ok) throw new Error('Failed to fetch onboarding guides')
|
|
62
|
+
return res.json()
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useOnboardingGuide(slug: string | undefined) {
|
|
68
|
+
return useQuery({
|
|
69
|
+
queryKey: onboardingGuideKeys.detail(slug || ''),
|
|
70
|
+
queryFn: async (): Promise<OnboardingGuide> => {
|
|
71
|
+
const res = await fetch(`/api/onboarding-guides/${slug}`)
|
|
72
|
+
if (!res.ok) throw new Error('Failed to fetch onboarding guide')
|
|
73
|
+
return res.json()
|
|
74
|
+
},
|
|
75
|
+
enabled: !!slug,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useOnboardingGuideSections() {
|
|
80
|
+
return useQuery({
|
|
81
|
+
queryKey: onboardingGuideKeys.sections(),
|
|
82
|
+
queryFn: async (): Promise<OnboardingGuideSectionSummary[]> => {
|
|
83
|
+
const res = await fetch('/api/onboarding-guides/sections')
|
|
84
|
+
if (!res.ok)
|
|
85
|
+
throw new Error('Failed to fetch onboarding-guide sections')
|
|
86
|
+
return res.json()
|
|
87
|
+
},
|
|
88
|
+
staleTime: 0,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Guides surface barrel.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `components/tickets/` — top-level openframe-route-specific
|
|
5
|
+
* product surface (NOT under `shared/`, which is reserved for
|
|
6
|
+
* cross-platform marketing).
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: this barrel MUST NOT re-export `OnboardingGuide`,
|
|
9
|
+
* `OnboardingGuideFilters`, `OnboardingGuideListResponse`, or
|
|
10
|
+
* `OnboardingGuideSectionSummary`. Those types already flow via
|
|
11
|
+
* `components/chat/types/entities/onboarding-guide.ts` (re-exported
|
|
12
|
+
* from `components/chat` through `./types/*`). A duplicate path
|
|
13
|
+
* triggers TypeScript's TS2308 ambiguous re-export at the top-level
|
|
14
|
+
* `components/index.ts` barrel — same gotcha documented for
|
|
15
|
+
* `RoadmapItem` at `shared/roadmap/index.ts:1-14`.
|
|
16
|
+
*
|
|
17
|
+
* Consumers needing the row type:
|
|
18
|
+
* import type { OnboardingGuide } from
|
|
19
|
+
* '@flamingo-stack/openframe-frontend-core/components/chat'
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
OnboardingGuidesCatalogView,
|
|
24
|
+
type OnboardingGuidesCatalogViewProps,
|
|
25
|
+
} from './onboarding-guides-catalog-view'
|
|
26
|
+
export {
|
|
27
|
+
OnboardingGuideDetailView,
|
|
28
|
+
type OnboardingGuideDetailViewProps,
|
|
29
|
+
} from './onboarding-guide-detail-view'
|
|
30
|
+
export {
|
|
31
|
+
OnboardingGuidesCatalogSkeleton,
|
|
32
|
+
} from './onboarding-guides-catalog-skeleton'
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
useOnboardingGuides,
|
|
36
|
+
useOnboardingGuide,
|
|
37
|
+
useOnboardingGuideSections,
|
|
38
|
+
onboardingGuideKeys,
|
|
39
|
+
} from './hooks/use-onboarding-guides'
|