@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,215 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public-facing detail view for `/onboarding-guides/<slug>` on
|
|
5
|
+
* openframe.
|
|
6
|
+
*
|
|
7
|
+
* Self-contained: every concern that used to require a hub-side
|
|
8
|
+
* wrapper now flows through lib primitives + the ChatRuntime context.
|
|
9
|
+
*
|
|
10
|
+
* - Markdown: lib `<SimpleMarkdownRenderer>` (already lib-resident).
|
|
11
|
+
* - Video warmup: lib `useVideoWarmup` (reads Supabase storage
|
|
12
|
+
* origin from `runtime.endpoints.supabaseStorageOrigin`; Mux
|
|
13
|
+
* origins are hardcoded public CDN hosts).
|
|
14
|
+
* - Captions URL: lib `getCaptionsUrl` (pure URL builder).
|
|
15
|
+
* - Video poster: inline priority chain
|
|
16
|
+
* (`main_video_thumbnail || featured_image || og_image_url`) —
|
|
17
|
+
* same fallbacks the card uses.
|
|
18
|
+
* - Related card hrefs: `runtime.composeContentUrl?.(
|
|
19
|
+
* 'onboarding_guide', slug, platforms)` — falls back to same-
|
|
20
|
+
* origin relative path when no composer is wired.
|
|
21
|
+
*
|
|
22
|
+
* No hub-side wrapper file required. Optional `MarkdownRenderer`
|
|
23
|
+
* prop lets hosts swap in a renderer with extra plugins (Reddit /
|
|
24
|
+
* Twitter / X embeds) when needed.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { type ComponentType, type ReactNode } from 'react'
|
|
28
|
+
import { ArrowLeft } from 'lucide-react'
|
|
29
|
+
|
|
30
|
+
import { Link } from '../../embed-shims'
|
|
31
|
+
import { ArticleDetailLayout } from '../layout/article-detail-layout'
|
|
32
|
+
import { EntityVideoSection } from '../features/entity-video-section'
|
|
33
|
+
import { VideoBitesDisplay } from '../features/video-bites-display'
|
|
34
|
+
import { useVideoWarmup } from '../features/use-video-warmup'
|
|
35
|
+
import { getCaptionsUrl } from '../features/captions-url'
|
|
36
|
+
import { SimpleMarkdownRenderer } from '../ui/simple-markdown-renderer'
|
|
37
|
+
import { EntityAuthorCard } from '../chat/entity-cards/entity-author-card'
|
|
38
|
+
import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
|
|
39
|
+
import { useChatRuntime } from '../../contexts/chat-runtime-context'
|
|
40
|
+
import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide'
|
|
41
|
+
import type { VideoTeaser } from '../../types/video-processing'
|
|
42
|
+
import { buildDefaultHref } from './build-default-href'
|
|
43
|
+
|
|
44
|
+
export interface OnboardingGuideDetailViewProps {
|
|
45
|
+
initialData: OnboardingGuide
|
|
46
|
+
related?: OnboardingGuide[]
|
|
47
|
+
/** Optional markdown renderer override. Defaults to lib
|
|
48
|
+
* `<SimpleMarkdownRenderer>`. Hosts override when they need extra
|
|
49
|
+
* plugins (Reddit / Twitter / X / code-block enhancements). */
|
|
50
|
+
MarkdownRenderer?: ComponentType<{ content: string }>
|
|
51
|
+
/** Optional per-row related-card renderer override. When omitted,
|
|
52
|
+
* lib renders `<OnboardingGuideCard>` with runtime-composed href. */
|
|
53
|
+
renderRelatedCard?: (guide: OnboardingGuide) => ReactNode
|
|
54
|
+
/** Back-link target. Defaults to `basePath` so the link returns to
|
|
55
|
+
* the catalog the embedder is hosting (no drift when `basePath`
|
|
56
|
+
* is overridden). The admin preview explicitly overrides to
|
|
57
|
+
* `/admin/onboarding-guides`. */
|
|
58
|
+
backHref?: string
|
|
59
|
+
/** Back-link label. Defaults to "Back to Getting Started". */
|
|
60
|
+
backLabel?: string
|
|
61
|
+
/** Base path the related-card hrefs default to when
|
|
62
|
+
* `runtime.composeContentUrl` is not wired. Embedders mounting at
|
|
63
|
+
* `/docs/onboarding/` instead of `/onboarding-guides/` should
|
|
64
|
+
* override. Default `/onboarding-guides`. */
|
|
65
|
+
basePath?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function OnboardingGuideDetailView({
|
|
69
|
+
initialData: guide,
|
|
70
|
+
related = [],
|
|
71
|
+
MarkdownRenderer = SimpleMarkdownRenderer,
|
|
72
|
+
renderRelatedCard,
|
|
73
|
+
backHref,
|
|
74
|
+
backLabel = 'Back to Getting Started',
|
|
75
|
+
basePath = '/onboarding-guides',
|
|
76
|
+
}: OnboardingGuideDetailViewProps) {
|
|
77
|
+
// Resolve `backHref` from `basePath` when not explicitly set — so
|
|
78
|
+
// an embedder overriding `basePath="/docs/onboarding"` automatically
|
|
79
|
+
// gets the right back link without remembering to thread `backHref`
|
|
80
|
+
// too. Admin preview still wins via its explicit `backHref` override.
|
|
81
|
+
const resolvedBackHref = backHref ?? basePath
|
|
82
|
+
const runtime = useChatRuntime()
|
|
83
|
+
|
|
84
|
+
// Video warmup — preconnect always fires; preload only when the
|
|
85
|
+
// container scrolls within ~1 viewport AND the URL is on the
|
|
86
|
+
// configured Supabase storage origin. No origin in runtime ⇒
|
|
87
|
+
// preconnect-only path (Mux/YouTube unaffected).
|
|
88
|
+
const { ref: videoWarmupRef } = useVideoWarmup<HTMLDivElement>({
|
|
89
|
+
videoUrl: guide.main_video_url,
|
|
90
|
+
supabaseStorageOrigin: runtime?.endpoints.supabaseStorageOrigin,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const captionsUrl = getCaptionsUrl(
|
|
94
|
+
'onboarding_guide',
|
|
95
|
+
guide.id,
|
|
96
|
+
guide.srt_content,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Video poster — fallback chain:
|
|
100
|
+
// 1. Entity-owned thumbnails (main_video_thumbnail / featured_image / og_image_url)
|
|
101
|
+
// 2. Branded OG placeholder from `runtime.resolvePlaceholderUrl(title)`
|
|
102
|
+
// — restores the hub's prior behavior where a video without a
|
|
103
|
+
// thumbnail still showed a branded poster instead of a black
|
|
104
|
+
// frame.
|
|
105
|
+
// 3. `undefined` (player picks its own default poster — usually
|
|
106
|
+
// first-frame extraction).
|
|
107
|
+
const videoPoster =
|
|
108
|
+
guide.main_video_thumbnail ||
|
|
109
|
+
guide.featured_image ||
|
|
110
|
+
guide.og_image_url ||
|
|
111
|
+
runtime?.resolvePlaceholderUrl?.(guide.title, { aspect: 'wide' }) ||
|
|
112
|
+
undefined
|
|
113
|
+
|
|
114
|
+
// Default related-card renderer — runtime-composed cross-platform href.
|
|
115
|
+
const defaultRenderRelatedCard = (g: OnboardingGuide) => {
|
|
116
|
+
const cta = runtime?.composeContentUrl
|
|
117
|
+
? runtime.composeContentUrl(
|
|
118
|
+
'onboarding_guide',
|
|
119
|
+
g.slug,
|
|
120
|
+
g.onboarding_guide_platforms,
|
|
121
|
+
)
|
|
122
|
+
: buildDefaultHref(basePath, g.slug)
|
|
123
|
+
return (
|
|
124
|
+
<OnboardingGuideCard
|
|
125
|
+
guide={g}
|
|
126
|
+
href={cta.href}
|
|
127
|
+
targetPlatform={cta.targetPlatform}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
const renderRelatedCardFn = renderRelatedCard ?? defaultRenderRelatedCard
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ArticleDetailLayout>
|
|
135
|
+
<div className="space-y-6 md:space-y-8">
|
|
136
|
+
{/* Back link */}
|
|
137
|
+
<Link
|
|
138
|
+
href={resolvedBackHref}
|
|
139
|
+
className="inline-flex items-center gap-2 text-ods-text-secondary hover:text-ods-accent transition-colors"
|
|
140
|
+
>
|
|
141
|
+
<ArrowLeft className="h-4 w-4" />
|
|
142
|
+
<span className="text-h5">{backLabel}</span>
|
|
143
|
+
</Link>
|
|
144
|
+
|
|
145
|
+
<h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary">
|
|
146
|
+
{guide.title}
|
|
147
|
+
</h1>
|
|
148
|
+
|
|
149
|
+
{/* Metadata grid — Section · Step | Published | Author. */}
|
|
150
|
+
<EntityAuthorCard
|
|
151
|
+
author={guide.author}
|
|
152
|
+
publishedAt={guide.published_at}
|
|
153
|
+
extraCells={[
|
|
154
|
+
{
|
|
155
|
+
value: `${guide.section} · Step ${guide.step_order}`,
|
|
156
|
+
label: 'Section',
|
|
157
|
+
uppercase: false,
|
|
158
|
+
},
|
|
159
|
+
]}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
{/* Video. `main_video_url` (Mux/MP4) and `youtube_url` are
|
|
163
|
+
independent columns — either one populated should render
|
|
164
|
+
the player. `EntityVideoSection` routes accordingly. */}
|
|
165
|
+
{(guide.main_video_url || guide.youtube_url) && (
|
|
166
|
+
<div ref={videoWarmupRef}>
|
|
167
|
+
<EntityVideoSection
|
|
168
|
+
mainVideoUrl={guide.main_video_url}
|
|
169
|
+
youtubeUrl={guide.youtube_url || undefined}
|
|
170
|
+
highlightVideoUrl={guide.highlight_video_url}
|
|
171
|
+
mainVideoPoster={videoPoster}
|
|
172
|
+
highlightVideoThumbnail={guide.highlight_video_thumbnail || undefined}
|
|
173
|
+
videoSummary={undefined}
|
|
174
|
+
videoBites={undefined}
|
|
175
|
+
title={guide.title}
|
|
176
|
+
srtContent={guide.srt_content}
|
|
177
|
+
captionsUrl={captionsUrl}
|
|
178
|
+
MarkdownRenderer={MarkdownRenderer}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{/* Markdown body */}
|
|
184
|
+
{guide.content && (
|
|
185
|
+
<div className="space-y-4">
|
|
186
|
+
<MarkdownRenderer content={guide.content} />
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Video Bites */}
|
|
191
|
+
{guide.video_bites && guide.video_bites.length > 0 && (
|
|
192
|
+
<VideoBitesDisplay
|
|
193
|
+
bites={guide.video_bites as VideoTeaser[]}
|
|
194
|
+
filterPublished={true}
|
|
195
|
+
showTitle={false}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* Related — same-section, ordered by step. */}
|
|
200
|
+
{related.length > 0 && (
|
|
201
|
+
<div className="space-y-4 pt-8 border-t border-ods-border">
|
|
202
|
+
<h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary">
|
|
203
|
+
More in {guide.section}
|
|
204
|
+
</h2>
|
|
205
|
+
<ul className="flex flex-col gap-3">
|
|
206
|
+
{related.map((r) => (
|
|
207
|
+
<li key={r.id}>{renderRelatedCardFn(r)}</li>
|
|
208
|
+
))}
|
|
209
|
+
</ul>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</ArticleDetailLayout>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { DevSectionPage } from '../shared/dev-section'
|
|
4
|
+
import { OnboardingGuideCardSkeleton } from '../chat/entity-cards/onboarding-guide-card'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Page-level Suspense fallback for `/onboarding-guides`.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the loaded `<OnboardingGuidesCatalogView>` shape — both
|
|
10
|
+
* mount the same `<DevSectionPage sectionKey="onboarding">` so the
|
|
11
|
+
* hero, back button, and overall page scaffold render identically.
|
|
12
|
+
*
|
|
13
|
+
* The `preControls` slot reserves space for the search bar (h-12)
|
|
14
|
+
* plus the section pill row (~74px including padding) so the
|
|
15
|
+
* Suspense → loaded transition doesn't shift vertically.
|
|
16
|
+
*
|
|
17
|
+
* Card distribution `4 + 3 + 3 = 10` matches the typical openframe
|
|
18
|
+
* onboarding dataset; per-card height (288 px) is byte-identical to
|
|
19
|
+
* the loaded card so per-card shifts on resolve are zero.
|
|
20
|
+
*/
|
|
21
|
+
export function OnboardingGuidesCatalogSkeleton() {
|
|
22
|
+
return (
|
|
23
|
+
<DevSectionPage
|
|
24
|
+
sectionKey="onboarding"
|
|
25
|
+
preControls={
|
|
26
|
+
<div className="space-y-4 animate-pulse">
|
|
27
|
+
{/* Search input placeholder — matches `<SearchInput>` h-12. */}
|
|
28
|
+
<div className="h-12 w-full bg-ods-card border border-ods-border rounded-md" />
|
|
29
|
+
{/* Section pill row placeholder — same wrapper class set the
|
|
30
|
+
hub-side `<FilterSection>` uses (~74 px). */}
|
|
31
|
+
<div className="flex flex-wrap items-center gap-3 p-4 bg-ods-card border border-ods-border rounded-lg">
|
|
32
|
+
<div className="h-4 w-14 bg-ods-border/60 rounded" />
|
|
33
|
+
{[0, 1, 2, 3].map((i) => (
|
|
34
|
+
<div
|
|
35
|
+
key={i}
|
|
36
|
+
className="h-10 w-24 bg-ods-card border border-ods-border rounded-md"
|
|
37
|
+
/>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
}
|
|
42
|
+
>
|
|
43
|
+
<div className="space-y-10 animate-pulse">
|
|
44
|
+
{[4, 3, 3].map((cardCount, sectionIdx) => (
|
|
45
|
+
<section key={sectionIdx} className="space-y-4">
|
|
46
|
+
<h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary flex items-center gap-2">
|
|
47
|
+
<span className="h-6 w-40 bg-ods-border/70 rounded" />
|
|
48
|
+
<span className="h-5 w-8 bg-ods-text-secondary/20 rounded-full" />
|
|
49
|
+
</h2>
|
|
50
|
+
<ul className="flex flex-col gap-4">
|
|
51
|
+
{Array.from({ length: cardCount }).map((_, cardIdx) => (
|
|
52
|
+
<li key={cardIdx}>
|
|
53
|
+
<OnboardingGuideCardSkeleton size="catalog" />
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
</section>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</DevSectionPage>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public-facing catalog view for `/onboarding-guides` on openframe.
|
|
5
|
+
*
|
|
6
|
+
* Self-contained: every concern that used to require a hub-side
|
|
7
|
+
* wrapper now flows through lib primitives + the ChatRuntime context.
|
|
8
|
+
*
|
|
9
|
+
* - Chrome: `<DevSectionPage sectionKey="onboarding">` (same lib
|
|
10
|
+
* primitive every other dev-center surface uses).
|
|
11
|
+
* - Search bar: lib `<DocSearchBar>` + `useDocSearch` directly,
|
|
12
|
+
* pre-scoped to `tableIds: ['onboarding-guides']`. The chat
|
|
13
|
+
* runtime's `source` discriminates the RAG namespace.
|
|
14
|
+
* - Section filter: lib `<FilterSection>` + URL push via embed-shim
|
|
15
|
+
* `useRouter`/`useSearchParams`.
|
|
16
|
+
* - Cards: lib `<OnboardingGuideCard>` with hrefs composed via
|
|
17
|
+
* `runtime.composeContentUrl?.('onboarding_guide', slug, platforms)`.
|
|
18
|
+
* Falls back to a same-origin relative path when no composer is
|
|
19
|
+
* wired (single-platform embedders).
|
|
20
|
+
*
|
|
21
|
+
* No hub-side wrapper file required.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { useMemo, useTransition, type ReactNode } from 'react'
|
|
25
|
+
import { GraduationCap } from 'lucide-react'
|
|
26
|
+
|
|
27
|
+
import { useRouter, useSearchParams } from '../../embed-shims'
|
|
28
|
+
import { DevSectionPage } from '../shared/dev-section'
|
|
29
|
+
import { DocSearchBar, useDocSearch } from '../shared/doc-search'
|
|
30
|
+
import { FilterPillRow } from '../ui/filter-pill-row'
|
|
31
|
+
import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
|
|
32
|
+
import { useChatRuntime } from '../../contexts/chat-runtime-context'
|
|
33
|
+
import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide'
|
|
34
|
+
import { buildDefaultHref } from './build-default-href'
|
|
35
|
+
|
|
36
|
+
export interface OnboardingGuidesCatalogViewProps {
|
|
37
|
+
initialGuides: OnboardingGuide[]
|
|
38
|
+
initialSections: Array<{
|
|
39
|
+
section: string
|
|
40
|
+
section_order: number
|
|
41
|
+
count: number
|
|
42
|
+
}>
|
|
43
|
+
initialSection?: string
|
|
44
|
+
/** Optional per-row card renderer override. When omitted, lib
|
|
45
|
+
* renders `<OnboardingGuideCard>` with runtime-composed href.
|
|
46
|
+
* Embedders only override to swap the card shape entirely. */
|
|
47
|
+
renderCard?: (guide: OnboardingGuide) => ReactNode
|
|
48
|
+
/** Base path the catalog is mounted under. Used as the fallback
|
|
49
|
+
* `href` prefix for card hrefs when `runtime.composeContentUrl` is
|
|
50
|
+
* not wired. Embedders mounting at `/docs/onboarding/` instead of
|
|
51
|
+
* `/onboarding-guides/` should override. Also used by `setSection`
|
|
52
|
+
* for the `?section=` URL push. Default `/onboarding-guides`. */
|
|
53
|
+
basePath?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function OnboardingGuidesCatalogView({
|
|
57
|
+
initialGuides,
|
|
58
|
+
initialSections,
|
|
59
|
+
initialSection = '',
|
|
60
|
+
renderCard,
|
|
61
|
+
basePath = '/onboarding-guides',
|
|
62
|
+
}: OnboardingGuidesCatalogViewProps) {
|
|
63
|
+
const router = useRouter()
|
|
64
|
+
const searchParams = useSearchParams()
|
|
65
|
+
const [isPending, startTransition] = useTransition()
|
|
66
|
+
const runtime = useChatRuntime()
|
|
67
|
+
const activeSection = initialSection || 'all'
|
|
68
|
+
|
|
69
|
+
// Section grouping. Data arrives already filtered server-side via
|
|
70
|
+
// `?section=`; this just buckets the visible rows for the section-
|
|
71
|
+
// header layout — no client-side `.filter()`.
|
|
72
|
+
const grouped = useMemo(() => {
|
|
73
|
+
const map = new Map<
|
|
74
|
+
string,
|
|
75
|
+
{ section_order: number; guides: OnboardingGuide[] }
|
|
76
|
+
>()
|
|
77
|
+
for (const g of initialGuides) {
|
|
78
|
+
const existing = map.get(g.section)
|
|
79
|
+
if (existing) {
|
|
80
|
+
if (g.section_order < existing.section_order)
|
|
81
|
+
existing.section_order = g.section_order
|
|
82
|
+
existing.guides.push(g)
|
|
83
|
+
} else {
|
|
84
|
+
map.set(g.section, { section_order: g.section_order, guides: [g] })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const entry of map.values()) {
|
|
88
|
+
entry.guides.sort(
|
|
89
|
+
(a, b) =>
|
|
90
|
+
a.step_order - b.step_order || a.title.localeCompare(b.title),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
return Array.from(map.entries())
|
|
94
|
+
.map(([section, info]) => ({ section, ...info }))
|
|
95
|
+
.sort(
|
|
96
|
+
(a, b) =>
|
|
97
|
+
a.section_order - b.section_order ||
|
|
98
|
+
a.section.localeCompare(b.section),
|
|
99
|
+
)
|
|
100
|
+
}, [initialGuides])
|
|
101
|
+
|
|
102
|
+
// Section-filter options for the lib `<FilterSection>` row.
|
|
103
|
+
const sectionFilterOptions = useMemo(
|
|
104
|
+
() => [
|
|
105
|
+
{ value: 'all', label: `All (${initialGuides.length})` },
|
|
106
|
+
...initialSections.map((s) => ({
|
|
107
|
+
value: s.section,
|
|
108
|
+
label: `${s.section} (${s.count})`,
|
|
109
|
+
})),
|
|
110
|
+
],
|
|
111
|
+
[initialGuides.length, initialSections],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Section pill change → push `?section=X` so the host RSC re-
|
|
115
|
+
// fetches against the DAL. Wrapped in `useTransition` so the
|
|
116
|
+
// results grid dims while the new payload is in flight.
|
|
117
|
+
const setSection = (value: string) => {
|
|
118
|
+
const params = new URLSearchParams(searchParams.toString())
|
|
119
|
+
if (value === 'all') {
|
|
120
|
+
params.delete('section')
|
|
121
|
+
} else {
|
|
122
|
+
params.set('section', value)
|
|
123
|
+
}
|
|
124
|
+
const qs = params.toString()
|
|
125
|
+
startTransition(() => {
|
|
126
|
+
router.push(qs ? `${basePath}?${qs}` : basePath)
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Search bar — scoped to onboarding-guides only via the RAG-search
|
|
131
|
+
// `tableIds` parameter. The hook calls `/api/docs/search` directly;
|
|
132
|
+
// hub or embedder must expose that endpoint (reverse-proxy on
|
|
133
|
+
// non-Next.js hosts).
|
|
134
|
+
const source = runtime?.source ?? 'openframe'
|
|
135
|
+
const docSearch = useDocSearch({
|
|
136
|
+
source,
|
|
137
|
+
baseRoute: basePath,
|
|
138
|
+
onNavigate: (path) => router.push(path),
|
|
139
|
+
tableIds: ['onboarding-guides'],
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Per-row card renderer — uses runtime-composed href for cross-
|
|
143
|
+
// platform navigation. Falls back to a same-origin relative URL
|
|
144
|
+
// when no composer is wired.
|
|
145
|
+
const defaultRenderCard = (guide: OnboardingGuide) => {
|
|
146
|
+
const cta = runtime?.composeContentUrl
|
|
147
|
+
? runtime.composeContentUrl(
|
|
148
|
+
'onboarding_guide',
|
|
149
|
+
guide.slug,
|
|
150
|
+
guide.onboarding_guide_platforms,
|
|
151
|
+
)
|
|
152
|
+
: buildDefaultHref(basePath, guide.slug)
|
|
153
|
+
return (
|
|
154
|
+
<OnboardingGuideCard
|
|
155
|
+
guide={guide}
|
|
156
|
+
href={cta.href}
|
|
157
|
+
targetPlatform={cta.targetPlatform}
|
|
158
|
+
size="catalog"
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
const renderCardFn = renderCard ?? defaultRenderCard
|
|
163
|
+
|
|
164
|
+
const preControls = (
|
|
165
|
+
<div className="space-y-4">
|
|
166
|
+
<DocSearchBar
|
|
167
|
+
placeholder="Search onboarding guides, releases, case studies…"
|
|
168
|
+
query={docSearch.query}
|
|
169
|
+
onQueryChange={docSearch.setQuery}
|
|
170
|
+
results={docSearch.results}
|
|
171
|
+
isLoading={docSearch.isLoading}
|
|
172
|
+
onResultSelect={docSearch.handleResultSelect}
|
|
173
|
+
showDropdown={docSearch.keepDropdownOpen}
|
|
174
|
+
/>
|
|
175
|
+
{initialSections.length > 0 && (
|
|
176
|
+
<FilterPillRow
|
|
177
|
+
label="Section"
|
|
178
|
+
selectedValue={activeSection}
|
|
179
|
+
onValueChange={setSection}
|
|
180
|
+
options={sectionFilterOptions}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<DevSectionPage sectionKey="onboarding" preControls={preControls}>
|
|
188
|
+
{initialGuides.length === 0 ? (
|
|
189
|
+
<div className="text-center py-16">
|
|
190
|
+
<GraduationCap className="h-12 w-12 text-ods-text-secondary mx-auto mb-4" />
|
|
191
|
+
<h2 className="text-ods-text-primary font-['DM_Sans'] text-[20px] font-semibold mb-2">
|
|
192
|
+
No onboarding guides found
|
|
193
|
+
</h2>
|
|
194
|
+
<p className="text-ods-text-secondary font-['DM_Sans'] text-[14px]">
|
|
195
|
+
{activeSection !== 'all'
|
|
196
|
+
? 'No guides in this section yet.'
|
|
197
|
+
: "We're working on the onboarding library. Check back soon."}
|
|
198
|
+
</p>
|
|
199
|
+
</div>
|
|
200
|
+
) : (
|
|
201
|
+
<div
|
|
202
|
+
className={
|
|
203
|
+
isPending
|
|
204
|
+
? 'opacity-60 transition-opacity space-y-10'
|
|
205
|
+
: 'space-y-10'
|
|
206
|
+
}
|
|
207
|
+
>
|
|
208
|
+
{grouped.map((sec) => (
|
|
209
|
+
<section key={sec.section} className="space-y-4">
|
|
210
|
+
<h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary flex items-center gap-2">
|
|
211
|
+
{sec.section}
|
|
212
|
+
<span className="inline-flex items-center justify-center rounded-full bg-ods-text-secondary/20 text-ods-text-secondary text-xs font-medium px-2 py-0.5">
|
|
213
|
+
{sec.guides.length}
|
|
214
|
+
</span>
|
|
215
|
+
</h2>
|
|
216
|
+
{/* HORIZONTAL catalog list — single column so consecutive
|
|
217
|
+
steps read top-to-bottom (Step 1 above Step 2 above
|
|
218
|
+
Step 3); a grid would visually reorder them. */}
|
|
219
|
+
<ul className="flex flex-col gap-4">
|
|
220
|
+
{sec.guides.map((guide) => (
|
|
221
|
+
<li key={guide.id}>{renderCardFn(guide)}</li>
|
|
222
|
+
))}
|
|
223
|
+
</ul>
|
|
224
|
+
</section>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</DevSectionPage>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<DocSearchBar>` — the canonical RAG-search dropdown surface.
|
|
5
|
+
*
|
|
6
|
+
* Mounted by every doc-search consumer (data-room sidebar, onboarding-
|
|
7
|
+
* guide catalog, and any future surface that needs typeahead against
|
|
8
|
+
* `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard
|
|
9
|
+
* `<DocSearchResultRow>` so the dropdown looks identical everywhere.
|
|
10
|
+
*
|
|
11
|
+
* ## Why a presentation component, not a "search bar that owns its
|
|
12
|
+
* own hook"
|
|
13
|
+
*
|
|
14
|
+
* The data-fetching hook (`useDocSearch`) lives hub-side because it
|
|
15
|
+
* depends on hub-only context (`useDocNavigation`, the rag-table-
|
|
16
|
+
* config registry, the hub's `decideNewTab` helper). Moving the hook
|
|
17
|
+
* would cascade ~5 more file migrations into the lib.
|
|
18
|
+
*
|
|
19
|
+
* Instead, the hook stays hub-side and callers pass its result into
|
|
20
|
+
* this component as plain props. Both consumers shrink to ~5 lines.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { ReactNode } from 'react'
|
|
24
|
+
import { SearchInput, type SearchResult } from '../../ui/search-input'
|
|
25
|
+
import { DocSearchResultRow } from './doc-search-result-row'
|
|
26
|
+
|
|
27
|
+
export interface DocSearchBarProps {
|
|
28
|
+
placeholder: string
|
|
29
|
+
query: string
|
|
30
|
+
onQueryChange: (value: string) => void
|
|
31
|
+
/** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`
|
|
32
|
+
* shape directly so callers don't translate. */
|
|
33
|
+
results: SearchResult[]
|
|
34
|
+
isLoading: boolean
|
|
35
|
+
/** Result selection handler. Mirrors `<SearchInput>` — the second
|
|
36
|
+
* `modifiers` argument is preserved so cmd-click / shift-click on
|
|
37
|
+
* a result row still forces new-tab behavior. Hub `useDocSearch`
|
|
38
|
+
* reads these to short-circuit to `window.open()`. */
|
|
39
|
+
onResultSelect: (
|
|
40
|
+
result: SearchResult,
|
|
41
|
+
modifiers?: {
|
|
42
|
+
metaKey?: boolean
|
|
43
|
+
ctrlKey?: boolean
|
|
44
|
+
shiftKey?: boolean
|
|
45
|
+
altKey?: boolean
|
|
46
|
+
button?: number
|
|
47
|
+
},
|
|
48
|
+
) => void
|
|
49
|
+
/** Lets the caller's hook force the dropdown open after a recent
|
|
50
|
+
* internal action (e.g. result navigation). `undefined` falls back
|
|
51
|
+
* to `<SearchInput>`'s built-in focus/hover heuristics. */
|
|
52
|
+
showDropdown?: boolean
|
|
53
|
+
/** Defaults to 2 — matches the existing data-room and onboarding-
|
|
54
|
+
* guide consumers. Override only if a surface needs different
|
|
55
|
+
* typeahead semantics. */
|
|
56
|
+
minQueryLength?: number
|
|
57
|
+
/** Defaults to 0 — both existing consumers debounce inside the
|
|
58
|
+
* hook, not the input. */
|
|
59
|
+
debounceMs?: number
|
|
60
|
+
className?: string
|
|
61
|
+
/** Optional row-renderer override. Defaults to the lib's standard
|
|
62
|
+
* `<DocSearchResultRow>` (source icon + title + path breadcrumb).
|
|
63
|
+
* Override only when a surface needs custom row chrome. */
|
|
64
|
+
renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function DocSearchBar({
|
|
68
|
+
placeholder,
|
|
69
|
+
query,
|
|
70
|
+
onQueryChange,
|
|
71
|
+
results,
|
|
72
|
+
isLoading,
|
|
73
|
+
onResultSelect,
|
|
74
|
+
showDropdown,
|
|
75
|
+
minQueryLength = 2,
|
|
76
|
+
debounceMs = 0,
|
|
77
|
+
className = 'w-full',
|
|
78
|
+
renderResult,
|
|
79
|
+
}: DocSearchBarProps) {
|
|
80
|
+
return (
|
|
81
|
+
<SearchInput
|
|
82
|
+
placeholder={placeholder}
|
|
83
|
+
value={query}
|
|
84
|
+
onChange={onQueryChange}
|
|
85
|
+
results={results}
|
|
86
|
+
isLoading={isLoading}
|
|
87
|
+
onResultSelect={onResultSelect}
|
|
88
|
+
showDropdown={showDropdown || undefined}
|
|
89
|
+
debounceMs={debounceMs}
|
|
90
|
+
minQueryLength={minQueryLength}
|
|
91
|
+
className={className}
|
|
92
|
+
renderResult={
|
|
93
|
+
renderResult ??
|
|
94
|
+
((result, isHighlighted) => (
|
|
95
|
+
<DocSearchResultRow result={result} isHighlighted={isHighlighted} />
|
|
96
|
+
))
|
|
97
|
+
}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single row in the `<SearchInput>` dropdown — the standard layout
|
|
5
|
+
* used by every doc-search-backed surface (company-hub data-room
|
|
6
|
+
* search bar, onboarding-guide catalog search, …). Single source of
|
|
7
|
+
* truth for the row appearance so search dropdowns are visually
|
|
8
|
+
* identical everywhere.
|
|
9
|
+
*
|
|
10
|
+
* Resolves the source icon via the same `resolveSourceIcon()`
|
|
11
|
+
* registry the inline chat-card refs use, so a row pointing at e.g.
|
|
12
|
+
* an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the
|
|
13
|
+
* chat card surfaces — no cross-surface drift.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { resolveSourceIcon } from '../../chat/utils/source-row-cta'
|
|
17
|
+
import { formatRelativePath } from './format-relative-path'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Minimal result shape this row renders. Compatible with any
|
|
21
|
+
* doc-search hook whose result type exposes `{ title?, path?,
|
|
22
|
+
* metadata? }`. The two hub consumers (onboarding-guide catalog,
|
|
23
|
+
* data-room sidebar) both satisfy this shape via their
|
|
24
|
+
* `useDocSearch` hook result.
|
|
25
|
+
*/
|
|
26
|
+
export interface DocSearchResultRowEntry {
|
|
27
|
+
title?: string
|
|
28
|
+
path?: string
|
|
29
|
+
metadata?: Record<string, unknown>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DocSearchResultRowProps {
|
|
33
|
+
result: DocSearchResultRowEntry
|
|
34
|
+
isHighlighted: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DocSearchResultRow({
|
|
38
|
+
result,
|
|
39
|
+
isHighlighted,
|
|
40
|
+
}: DocSearchResultRowProps) {
|
|
41
|
+
const docType = (result.metadata?.documentType as string) || undefined
|
|
42
|
+
const sourceRepo = (result.metadata?.sourceRepo as string) || undefined
|
|
43
|
+
const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({
|
|
44
|
+
sourceRepo,
|
|
45
|
+
documentType: docType,
|
|
46
|
+
})
|
|
47
|
+
const isGroup = result.metadata?.isGroup as boolean | undefined
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex items-center gap-3 w-full min-w-0">
|
|
51
|
+
<span
|
|
52
|
+
className="flex-shrink-0 text-ods-text-secondary"
|
|
53
|
+
title={iconLabel}
|
|
54
|
+
>
|
|
55
|
+
<SourceIcon className="size-4" />
|
|
56
|
+
</span>
|
|
57
|
+
<div className="min-w-0 flex-1">
|
|
58
|
+
<div
|
|
59
|
+
className={`text-sm font-medium leading-5 truncate ${
|
|
60
|
+
isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'
|
|
61
|
+
}`}
|
|
62
|
+
>
|
|
63
|
+
{result.title || result.path}
|
|
64
|
+
</div>
|
|
65
|
+
{!isGroup && result.path?.includes('/') && (
|
|
66
|
+
<div className="text-xs leading-4 text-ods-text-secondary truncate mt-0.5">
|
|
67
|
+
{formatRelativePath(result.path)}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|