@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.216",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
"require": "./dist/components/tickets/index.cjs",
|
|
49
49
|
"default": "./dist/components/tickets/index.js"
|
|
50
50
|
},
|
|
51
|
+
"./components/onboarding-guides": {
|
|
52
|
+
"types": "./dist/components/onboarding-guides/index.d.ts",
|
|
53
|
+
"import": "./dist/components/onboarding-guides/index.js",
|
|
54
|
+
"require": "./dist/components/onboarding-guides/index.cjs",
|
|
55
|
+
"default": "./dist/components/onboarding-guides/index.js"
|
|
56
|
+
},
|
|
51
57
|
"./components/contact": {
|
|
52
58
|
"types": "./dist/components/contact/index.d.ts",
|
|
53
59
|
"import": "./dist/components/contact/index.js",
|
|
@@ -72,6 +78,12 @@
|
|
|
72
78
|
"require": "./dist/components/features/index.cjs",
|
|
73
79
|
"default": "./dist/components/features/index.js"
|
|
74
80
|
},
|
|
81
|
+
"./components/features/mux-origins": {
|
|
82
|
+
"types": "./dist/components/features/mux-origins.d.ts",
|
|
83
|
+
"import": "./dist/components/features/mux-origins.js",
|
|
84
|
+
"require": "./dist/components/features/mux-origins.cjs",
|
|
85
|
+
"default": "./dist/components/features/mux-origins.js"
|
|
86
|
+
},
|
|
75
87
|
"./components/toast": {
|
|
76
88
|
"types": "./dist/components/toast/index.d.ts",
|
|
77
89
|
"import": "./dist/components/toast/index.js",
|
|
@@ -24,6 +24,8 @@ import Image from '../../../embed-shims/next-image'
|
|
|
24
24
|
import { StatusBadge } from '../../ui/status-badge'
|
|
25
25
|
import { cn } from '../../../utils/cn'
|
|
26
26
|
import type { BlogPostSummary } from '../../../types/blog'
|
|
27
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
28
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
27
29
|
import {
|
|
28
30
|
COMPACT_CARD_IMAGE_SLOT,
|
|
29
31
|
COMPACT_CARD_META_ROW_BOX,
|
|
@@ -102,16 +104,26 @@ export function BlogCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm'
|
|
|
102
104
|
export function BlogCard({
|
|
103
105
|
post,
|
|
104
106
|
href,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
placeholderUrl,
|
|
107
|
+
target: targetProp,
|
|
108
|
+
rel: relProp,
|
|
109
|
+
targetPlatform,
|
|
110
|
+
placeholderUrl: placeholderUrlProp,
|
|
110
111
|
size = 'default',
|
|
111
112
|
className,
|
|
112
113
|
hasEmbeddedVideo = false,
|
|
113
114
|
priority = false,
|
|
114
115
|
}: BlogCardProps) {
|
|
116
|
+
const { target, rel } = useEntityCardLink({
|
|
117
|
+
href,
|
|
118
|
+
targetPlatform,
|
|
119
|
+
target: targetProp,
|
|
120
|
+
rel: relProp,
|
|
121
|
+
})
|
|
122
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
123
|
+
title: post.title,
|
|
124
|
+
placeholderUrl: placeholderUrlProp,
|
|
125
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
126
|
+
})
|
|
115
127
|
const [imageError, setImageError] = useState(false)
|
|
116
128
|
const displayImage = (post.featured_image && !imageError) ? post.featured_image : placeholderUrl
|
|
117
129
|
|
|
@@ -17,6 +17,8 @@ import Image from '../../../embed-shims/next-image'
|
|
|
17
17
|
import { Card } from '../../ui/card'
|
|
18
18
|
import { cn } from '../../../utils/cn'
|
|
19
19
|
import type { CaseStudy } from '../../../types/case-study'
|
|
20
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
21
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
20
22
|
import {
|
|
21
23
|
COMPACT_CARD_IMAGE_SLOT,
|
|
22
24
|
COMPACT_CARD_META_ROW_BOX,
|
|
@@ -88,7 +90,27 @@ export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' |
|
|
|
88
90
|
)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
export function CaseStudyCard({
|
|
93
|
+
export function CaseStudyCard({
|
|
94
|
+
study,
|
|
95
|
+
href,
|
|
96
|
+
target: targetProp,
|
|
97
|
+
rel: relProp,
|
|
98
|
+
targetPlatform,
|
|
99
|
+
placeholderUrl: placeholderUrlProp,
|
|
100
|
+
size = 'default',
|
|
101
|
+
className,
|
|
102
|
+
}: CaseStudyCardProps) {
|
|
103
|
+
const { target, rel } = useEntityCardLink({
|
|
104
|
+
href,
|
|
105
|
+
targetPlatform,
|
|
106
|
+
target: targetProp,
|
|
107
|
+
rel: relProp,
|
|
108
|
+
})
|
|
109
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
110
|
+
title: study.title,
|
|
111
|
+
placeholderUrl: placeholderUrlProp,
|
|
112
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
113
|
+
})
|
|
92
114
|
const coverImage = study.featured_image || placeholderUrl || null
|
|
93
115
|
|
|
94
116
|
if (size === 'sm') {
|
|
@@ -13,6 +13,8 @@ import { Card } from '../../ui/card'
|
|
|
13
13
|
import { cn } from '../../../utils/cn'
|
|
14
14
|
import { Video } from 'lucide-react'
|
|
15
15
|
import type { CustomerInterview } from '../../../types/customer-interview'
|
|
16
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
17
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
16
18
|
import {
|
|
17
19
|
COMPACT_CARD_IMAGE_SLOT,
|
|
18
20
|
COMPACT_CARD_META_ROW_BOX,
|
|
@@ -88,7 +90,27 @@ export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'de
|
|
|
88
90
|
)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
export function CustomerInterviewCard({
|
|
93
|
+
export function CustomerInterviewCard({
|
|
94
|
+
interview,
|
|
95
|
+
href,
|
|
96
|
+
target: targetProp,
|
|
97
|
+
rel: relProp,
|
|
98
|
+
targetPlatform,
|
|
99
|
+
placeholderUrl: placeholderUrlProp,
|
|
100
|
+
size = 'default',
|
|
101
|
+
className,
|
|
102
|
+
}: CustomerInterviewCardProps) {
|
|
103
|
+
const { target, rel } = useEntityCardLink({
|
|
104
|
+
href,
|
|
105
|
+
targetPlatform,
|
|
106
|
+
target: targetProp,
|
|
107
|
+
rel: relProp,
|
|
108
|
+
})
|
|
109
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
110
|
+
title: interview.title,
|
|
111
|
+
placeholderUrl: placeholderUrlProp,
|
|
112
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
113
|
+
})
|
|
92
114
|
const thumbnailUrl = interview.featured_image || placeholderUrl || null
|
|
93
115
|
|
|
94
116
|
if (size === 'sm') {
|
|
@@ -1016,6 +1016,27 @@ export function ChatCardLoader({
|
|
|
1016
1016
|
</ChatCardNavWrap>
|
|
1017
1017
|
)
|
|
1018
1018
|
if (entry.mode === 'no-fetch') {
|
|
1019
|
+
// Synthetic-ref gate. `chat-message-enhanced.tsx` builds a minimal
|
|
1020
|
+
// `{ type, id, title: cardId, url: null }` ChatRef when the LLM
|
|
1021
|
+
// emits `[card://<type>:<id>]` for an id the server did NOT
|
|
1022
|
+
// surface (refs map miss) — typically an LLM hallucination of a
|
|
1023
|
+
// composite/invented UUID. EVERY real ref carries `sourceRepo`
|
|
1024
|
+
// (set by `buildChatRefFromRow` via `config.id` AND by
|
|
1025
|
+
// `synthesizeVideoRefs` via `EMBEDDED_VIDEO_SOURCE_REPO`), so a
|
|
1026
|
+
// missing `sourceRepo` is a reliable synthetic-ref signal.
|
|
1027
|
+
//
|
|
1028
|
+
// Returning null here triggers the bare-cardId fallback span in
|
|
1029
|
+
// chat-message-enhanced's `<a card://...>` override — the
|
|
1030
|
+
// documented "VISIBLE breakage" behavior. Without this gate, a
|
|
1031
|
+
// hallucinated marker like `[card://markdown:f18945f8-<real-uuid>]`
|
|
1032
|
+
// renders a `DataRoomDocChatCard` with the generic "Document"
|
|
1033
|
+
// badge AND the fake id as the title — which the user can't tell
|
|
1034
|
+
// apart from a real card (the entire point of the fallback
|
|
1035
|
+
// comment block in chat-message-enhanced.tsx).
|
|
1036
|
+
//
|
|
1037
|
+
// Fetch-mode types already handle this gracefully: a synthetic
|
|
1038
|
+
// id leads to a fetch miss → `!item` → null at line ~1034.
|
|
1039
|
+
if (!finalChatRef.sourceRepo) return null
|
|
1019
1040
|
if (entry.bareInline) {
|
|
1020
1041
|
return navWrap(entry.render(finalChatRef, renderOpts))
|
|
1021
1042
|
}
|
|
@@ -12,6 +12,8 @@ import React from 'react'
|
|
|
12
12
|
import { Calendar } from 'lucide-react'
|
|
13
13
|
import { AdminContentCard } from './admin-content-card'
|
|
14
14
|
import { formatInvestorUpdatePeriod, type InvestorUpdate } from '../types/entities/investor-update'
|
|
15
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
16
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
15
17
|
import {
|
|
16
18
|
COMPACT_CARD_IMAGE_SLOT,
|
|
17
19
|
COMPACT_CARD_META_ROW_BOX,
|
|
@@ -73,7 +75,27 @@ export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'defau
|
|
|
73
75
|
)
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
export function InvestorUpdateCard({
|
|
78
|
+
export function InvestorUpdateCard({
|
|
79
|
+
update,
|
|
80
|
+
href,
|
|
81
|
+
target: targetProp,
|
|
82
|
+
rel: relProp,
|
|
83
|
+
targetPlatform,
|
|
84
|
+
placeholderUrl: placeholderUrlProp,
|
|
85
|
+
size = 'default',
|
|
86
|
+
className,
|
|
87
|
+
}: InvestorUpdateCardProps) {
|
|
88
|
+
const { target, rel } = useEntityCardLink({
|
|
89
|
+
href,
|
|
90
|
+
targetPlatform,
|
|
91
|
+
target: targetProp,
|
|
92
|
+
rel: relProp,
|
|
93
|
+
})
|
|
94
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
95
|
+
title: update.title ?? `Update #${update.update_number ?? ''}`,
|
|
96
|
+
placeholderUrl: placeholderUrlProp,
|
|
97
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
98
|
+
})
|
|
77
99
|
const coverImage = update.featured_image || placeholderUrl || null
|
|
78
100
|
|
|
79
101
|
if (size === 'sm') {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* OnboardingGuideCard (pure presentation).
|
|
4
|
+
* OnboardingGuideCard (pure presentation + runtime-derived link attrs).
|
|
5
5
|
*
|
|
6
6
|
* Three variants:
|
|
7
7
|
* - `catalog`: rich detail card (hero + author grid) for the public catalog
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* - `default`: horizontal step-numbered card for "More in {section}" rail.
|
|
10
10
|
* - `sm`: compact horizontal card for chat-inline rendering.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Link semantics: the card derives `target`/`rel` from `ChatRuntime.navigation
|
|
13
|
+
* .decideNewTab` (hub-wired via `HubRuntimeProvider`) and the placeholder
|
|
14
|
+
* image from `ChatRuntime.resolvePlaceholderUrl`. Explicit `target` / `rel`
|
|
15
|
+
* / `placeholderUrl` props always WIN — chat dispatch and tests can
|
|
16
|
+
* pre-resolve. No runtime mounted → same-tab + no placeholder.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import React from 'react'
|
|
@@ -21,6 +24,8 @@ import { BlogImagePlaceholder } from './blog-image-placeholder'
|
|
|
21
24
|
import { EntityAuthorCard } from './entity-author-card'
|
|
22
25
|
import { formatDurationMMSS } from '../../../utils/format'
|
|
23
26
|
import type { OnboardingGuide } from '../types/entities/onboarding-guide'
|
|
27
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
28
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
24
29
|
import {
|
|
25
30
|
COMPACT_CARD_OUTER,
|
|
26
31
|
COMPACT_CARD_IMAGE_SLOT,
|
|
@@ -134,7 +139,28 @@ export function OnboardingGuideCardSkeleton({ size = 'default' }: { size?: 'cata
|
|
|
134
139
|
)
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
export function OnboardingGuideCard({
|
|
142
|
+
export function OnboardingGuideCard({
|
|
143
|
+
guide,
|
|
144
|
+
href,
|
|
145
|
+
target: targetProp,
|
|
146
|
+
rel: relProp,
|
|
147
|
+
targetPlatform,
|
|
148
|
+
placeholderUrl: placeholderUrlProp,
|
|
149
|
+
size = 'default',
|
|
150
|
+
className,
|
|
151
|
+
}: OnboardingGuideCardProps) {
|
|
152
|
+
const { target, rel } = useEntityCardLink({
|
|
153
|
+
href,
|
|
154
|
+
targetPlatform,
|
|
155
|
+
target: targetProp,
|
|
156
|
+
rel: relProp,
|
|
157
|
+
})
|
|
158
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
159
|
+
title: guide.title,
|
|
160
|
+
placeholderUrl: placeholderUrlProp,
|
|
161
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
162
|
+
})
|
|
163
|
+
|
|
138
164
|
if (size === 'catalog') {
|
|
139
165
|
const coverImage =
|
|
140
166
|
guide.featured_image ||
|
|
@@ -40,6 +40,8 @@ import type {
|
|
|
40
40
|
ProgramMedia,
|
|
41
41
|
ProgramHost,
|
|
42
42
|
} from '../types/entities/program-types'
|
|
43
|
+
import { useEntityCardLink } from './use-entity-card-link'
|
|
44
|
+
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
43
45
|
|
|
44
46
|
type CardSize = 'default' | 'sm'
|
|
45
47
|
|
|
@@ -198,12 +200,24 @@ export function ProgramCard<T extends BaseProgramItem>({
|
|
|
198
200
|
renderMeta,
|
|
199
201
|
size = 'default',
|
|
200
202
|
href,
|
|
201
|
-
target,
|
|
202
|
-
rel,
|
|
203
|
-
|
|
203
|
+
target: targetProp,
|
|
204
|
+
rel: relProp,
|
|
205
|
+
targetPlatform,
|
|
206
|
+
placeholderUrl: placeholderUrlProp,
|
|
204
207
|
wholeCardClickable = false,
|
|
205
208
|
className,
|
|
206
209
|
}: ProgramCardProps<T>) {
|
|
210
|
+
const { target, rel } = useEntityCardLink({
|
|
211
|
+
href,
|
|
212
|
+
targetPlatform,
|
|
213
|
+
target: targetProp,
|
|
214
|
+
rel: relProp,
|
|
215
|
+
})
|
|
216
|
+
const placeholderUrl = useEntityCardPlaceholder({
|
|
217
|
+
title: item.title,
|
|
218
|
+
placeholderUrl: placeholderUrlProp,
|
|
219
|
+
aspect: size === 'sm' ? 'square' : 'wide',
|
|
220
|
+
})
|
|
207
221
|
const coverImage = item.cover_url
|
|
208
222
|
const images = media.filter((m) => m.media_type === 'image')
|
|
209
223
|
const hosts = getHosts(item.hosts)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared link-attribute resolver for every entity card.
|
|
5
|
+
*
|
|
6
|
+
* Pure-presentation cards (BlogCard, OnboardingGuideCard, etc.)
|
|
7
|
+
* historically required callers to pre-compute `target`/`rel` via a
|
|
8
|
+
* hub-side `useNavLink` wrapper because the same-tab-vs-new-tab
|
|
9
|
+
* decision depends on `currentPlatform()` and the cross-platform
|
|
10
|
+
* URL topology — neither of which the lib knows.
|
|
11
|
+
*
|
|
12
|
+
* This hook moves that decision INTO the card via the
|
|
13
|
+
* `ChatRuntime.navigation.decideNewTab` callback already wired by
|
|
14
|
+
* `HubRuntimeProvider` (and overridable per-embedder). The wrapping
|
|
15
|
+
* `*-card-item.tsx` files in the hub become unnecessary — every card
|
|
16
|
+
* derives its own `target`/`rel` from runtime.
|
|
17
|
+
*
|
|
18
|
+
* Backwards compat: explicit `target` / `rel` props always WIN over
|
|
19
|
+
* runtime-derived values. Chat dispatcher callsites that already pass
|
|
20
|
+
* pre-resolved attributes are unaffected.
|
|
21
|
+
*
|
|
22
|
+
* No runtime mounted? Returns `{ target: undefined, rel: undefined }`
|
|
23
|
+
* (same-tab) — matches the documented embed-shim fallback.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { useMemo } from 'react'
|
|
27
|
+
import { useChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
28
|
+
|
|
29
|
+
export interface UseEntityCardLinkArgs {
|
|
30
|
+
href: string
|
|
31
|
+
targetPlatform?: string | null
|
|
32
|
+
/** Explicit override. When set, runtime decision is skipped. */
|
|
33
|
+
target?: '_blank'
|
|
34
|
+
/** Explicit override. When set, runtime decision is skipped. */
|
|
35
|
+
rel?: 'noopener noreferrer'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface EntityCardLinkProps {
|
|
39
|
+
target: '_blank' | undefined
|
|
40
|
+
rel: 'noopener noreferrer' | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useEntityCardLink({
|
|
44
|
+
href,
|
|
45
|
+
targetPlatform,
|
|
46
|
+
target,
|
|
47
|
+
rel,
|
|
48
|
+
}: UseEntityCardLinkArgs): EntityCardLinkProps {
|
|
49
|
+
const runtime = useChatRuntime()
|
|
50
|
+
return useMemo(() => {
|
|
51
|
+
// Explicit prop wins — preserves the chat-dispatcher path that
|
|
52
|
+
// pre-computes attrs from `computeIsNewTab`. When `target='_blank'`
|
|
53
|
+
// is passed without `rel`, auto-pair with `noopener noreferrer`
|
|
54
|
+
// to close the tabnabbing vector (window.opener access from the
|
|
55
|
+
// new tab back to the parent).
|
|
56
|
+
if (target !== undefined || rel !== undefined) {
|
|
57
|
+
const safeRel =
|
|
58
|
+
rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined)
|
|
59
|
+
return { target, rel: safeRel }
|
|
60
|
+
}
|
|
61
|
+
const newTab = runtime?.navigation.decideNewTab?.({ href, targetPlatform }) ?? false
|
|
62
|
+
return newTab
|
|
63
|
+
? { target: '_blank' as const, rel: 'noopener noreferrer' as const }
|
|
64
|
+
: { target: undefined, rel: undefined }
|
|
65
|
+
}, [target, rel, href, targetPlatform, runtime])
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared OG-placeholder resolver for every entity card.
|
|
5
|
+
*
|
|
6
|
+
* Pure-presentation cards historically required callers to
|
|
7
|
+
* pre-compute `placeholderUrl` via a hub-side `useOgPlaceholder`
|
|
8
|
+
* wrapper that injected the hub's `buildOgPlaceholderUrl` (resolves
|
|
9
|
+
* CSS-var ODS colors to hex via the static map).
|
|
10
|
+
*
|
|
11
|
+
* This hook moves that resolution INTO the card via the
|
|
12
|
+
* `ChatRuntime.resolvePlaceholderUrl` callback. Embedders that don't
|
|
13
|
+
* wire the callback get no placeholder (the card's empty-state path
|
|
14
|
+
* activates) — same fallback semantics as before.
|
|
15
|
+
*
|
|
16
|
+
* Backwards compat: explicit `placeholderUrl` prop ALWAYS wins over
|
|
17
|
+
* runtime-derived value. Callers that pre-resolve (chat dispatch,
|
|
18
|
+
* tests) are unaffected.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
22
|
+
import { useOgPlaceholder } from '../../../hooks/use-og-placeholder'
|
|
23
|
+
|
|
24
|
+
export interface UseEntityCardPlaceholderArgs {
|
|
25
|
+
/** Entity title — used as the placeholder label. */
|
|
26
|
+
title: string | undefined | null
|
|
27
|
+
/** Explicit override. When set, runtime resolver is skipped. */
|
|
28
|
+
placeholderUrl?: string | null
|
|
29
|
+
/** Site name shown under the title. Optional. */
|
|
30
|
+
siteName?: string
|
|
31
|
+
/** Output aspect ratio. `'wide'` (default) for catalog cards,
|
|
32
|
+
* `'square'` for compact chat-inline cards. */
|
|
33
|
+
aspect?: 'wide' | 'square'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const NO_OP_BUILDER = () => ''
|
|
37
|
+
|
|
38
|
+
export function useEntityCardPlaceholder({
|
|
39
|
+
title,
|
|
40
|
+
placeholderUrl,
|
|
41
|
+
siteName = '',
|
|
42
|
+
aspect = 'wide',
|
|
43
|
+
}: UseEntityCardPlaceholderArgs): string | null {
|
|
44
|
+
const runtime = useChatRuntime()
|
|
45
|
+
const builder = runtime?.resolvePlaceholderUrl ?? NO_OP_BUILDER
|
|
46
|
+
const enabled = placeholderUrl === undefined && !!runtime?.resolvePlaceholderUrl
|
|
47
|
+
const derived = useOgPlaceholder(builder, title, siteName, enabled, aspect)
|
|
48
|
+
// Explicit prop (including explicit null) wins; `undefined` falls back to derived.
|
|
49
|
+
return placeholderUrl !== undefined ? placeholderUrl : derived
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the captions API URL for a video entity.
|
|
3
|
+
*
|
|
4
|
+
* Returns the HTTPS URL to the `/api/captions/[entityType]/[entityId]` endpoint
|
|
5
|
+
* which serves VTT content for iOS native fullscreen subtitles.
|
|
6
|
+
* Returns undefined if entity has no srt_content.
|
|
7
|
+
*
|
|
8
|
+
* Cache-busting hash derived from the srt_content length so iOS Safari
|
|
9
|
+
* fetches fresh VTT when subtitles are regenerated (Safari aggressively caches
|
|
10
|
+
* <track> src URLs even with short Cache-Control max-age).
|
|
11
|
+
*
|
|
12
|
+
* Lifted from hub `lib/utils/captions-url.ts`. The hub's hard-coded
|
|
13
|
+
* `VideoEnabledEntityType` enum is widened to `string` here — embedders
|
|
14
|
+
* pass whatever entity-type discriminator their reverse-proxied
|
|
15
|
+
* `/api/captions/...` route expects.
|
|
16
|
+
*/
|
|
17
|
+
export function getCaptionsUrl(
|
|
18
|
+
entityType: string,
|
|
19
|
+
entityId: string | number,
|
|
20
|
+
srtContent?: string | null,
|
|
21
|
+
): string | undefined {
|
|
22
|
+
if (!srtContent) return undefined
|
|
23
|
+
const hash = `${srtContent.length}-${srtContent.slice(0, 8).replace(/\s/g, '')}`
|
|
24
|
+
return `/api/captions/${entityType}/${entityId}?v=${hash}`
|
|
25
|
+
}
|
|
@@ -56,6 +56,8 @@ export * from './video'
|
|
|
56
56
|
export * from './video-ratio-tabs'
|
|
57
57
|
export * from './video-bites-display'
|
|
58
58
|
export * from './entity-video-section'
|
|
59
|
+
export * from './use-video-warmup'
|
|
60
|
+
export * from './captions-url'
|
|
59
61
|
export * from './video-source-selector'
|
|
60
62
|
export * from './transcript-summary-editor'
|
|
61
63
|
export * from './highlight-video-section'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mux CDN origin constants — single source of truth, server-safe.
|
|
3
|
+
*
|
|
4
|
+
* Lives in its own NON-`'use client'` module so server-side hub
|
|
5
|
+
* modules (webhook handlers, URL builders, hostname comparisons in
|
|
6
|
+
* `lib/config/mux-config.ts`) can import these strings without
|
|
7
|
+
* tripping Next.js's client-reference poisoning. Re-exported from
|
|
8
|
+
* `use-video-warmup.ts` for backward-compat with client-side callers.
|
|
9
|
+
*
|
|
10
|
+
* Bug history (2026-05-29): when these constants lived in
|
|
11
|
+
* `use-video-warmup.ts` (which is `'use client'`), the hub's
|
|
12
|
+
* server-side `new URL(MUX_STREAM_ORIGIN).hostname` evaluation crashed
|
|
13
|
+
* at Vercel build with `TypeError: Invalid URL` — Next.js had
|
|
14
|
+
* replaced the constant with a client-function stub that throws
|
|
15
|
+
* "Attempted to call ... from the server" when stringified. Splitting
|
|
16
|
+
* the constants into this module restores the server-safe path.
|
|
17
|
+
*
|
|
18
|
+
* These hostnames are part of Mux's public API contract and are
|
|
19
|
+
* stable. A future change to the Mux CDN architecture (extremely
|
|
20
|
+
* unlikely) would be a single-line edit here.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** HLS playback (`/{playback_id}.m3u8` + segments + per-asset MP4 renditions). */
|
|
24
|
+
export const MUX_STREAM_ORIGIN = 'https://stream.mux.com'
|
|
25
|
+
|
|
26
|
+
/** Server-generated thumbnails (`/{playback_id}/thumbnail.jpg`). */
|
|
27
|
+
export const MUX_IMAGE_ORIGIN = 'https://image.mux.com'
|
|
@@ -8,6 +8,8 @@ export { NotificationDrawer } from './notification-drawer'
|
|
|
8
8
|
export type { NotificationDrawerProps } from './notification-drawer'
|
|
9
9
|
export { NotificationTile } from './notification-tile'
|
|
10
10
|
export type { NotificationTileProps } from './notification-tile'
|
|
11
|
+
export { NotificationPopups } from './notification-popups'
|
|
12
|
+
export type { NotificationPopupsProps, NotificationPopupsPosition } from './notification-popups'
|
|
11
13
|
export type {
|
|
12
14
|
Notification,
|
|
13
15
|
NotificationVariant,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
4
|
+
import { useEffect, useRef } from 'react'
|
|
4
5
|
import { BellOffIcon } from '../../icons-v2-generated/interface/bell-off-icon'
|
|
5
6
|
import { ClockHistoryIcon } from '../../icons-v2-generated/date-and-time/clock-history-icon'
|
|
6
7
|
import { Button } from '../../ui/button/button'
|
|
@@ -9,13 +10,15 @@ import { Switch } from '../../ui/switch'
|
|
|
9
10
|
import { cn } from '../../../utils/cn'
|
|
10
11
|
import { useOptionalNotifications } from './notifications-context'
|
|
11
12
|
import { NotificationTile } from './notification-tile'
|
|
13
|
+
import type { Notification } from './types'
|
|
12
14
|
|
|
13
15
|
export interface NotificationDrawerProps {
|
|
14
16
|
className?: string
|
|
15
17
|
liveDurationMs?: number
|
|
18
|
+
loadMoreRootMargin?: string
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
export function NotificationDrawer({ className, liveDurationMs }: NotificationDrawerProps) {
|
|
21
|
+
export function NotificationDrawer({ className, liveDurationMs, loadMoreRootMargin = '200px' }: NotificationDrawerProps) {
|
|
19
22
|
const ctx = useOptionalNotifications()
|
|
20
23
|
if (!ctx) return null
|
|
21
24
|
|
|
@@ -31,6 +34,9 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
|
|
|
31
34
|
markSettled,
|
|
32
35
|
setShowPopups,
|
|
33
36
|
onHistoryClick,
|
|
37
|
+
hasMore,
|
|
38
|
+
isLoadingMore,
|
|
39
|
+
loadMore,
|
|
34
40
|
} = ctx
|
|
35
41
|
|
|
36
42
|
const unreadNotifications = notifications.filter((n) => !n.read)
|
|
@@ -59,21 +65,16 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
|
|
|
59
65
|
</button>
|
|
60
66
|
</div>
|
|
61
67
|
|
|
62
|
-
<
|
|
63
|
-
{unreadNotifications
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
onSettle={markSettled}
|
|
73
|
-
/>
|
|
74
|
-
))
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
68
|
+
<DrawerScrollList
|
|
69
|
+
unreadNotifications={unreadNotifications}
|
|
70
|
+
liveDurationMs={liveDurationMs}
|
|
71
|
+
onComplete={markRead}
|
|
72
|
+
onSettle={markSettled}
|
|
73
|
+
hasMore={hasMore}
|
|
74
|
+
isLoadingMore={isLoadingMore}
|
|
75
|
+
loadMore={loadMore}
|
|
76
|
+
loadMoreRootMargin={loadMoreRootMargin}
|
|
77
|
+
/>
|
|
77
78
|
|
|
78
79
|
<div className="flex flex-col gap-[var(--spacing-system-xs)] px-[var(--spacing-system-m)] pb-[var(--spacing-system-m)]">
|
|
79
80
|
<ShowNotificationsToggleRow checked={showPopups} onChange={setShowPopups} />
|
|
@@ -86,6 +87,89 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
|
|
|
86
87
|
)
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
interface DrawerScrollListProps {
|
|
91
|
+
unreadNotifications: Notification[]
|
|
92
|
+
liveDurationMs?: number
|
|
93
|
+
onComplete: (id: string) => void
|
|
94
|
+
onSettle: (id: string) => void
|
|
95
|
+
hasMore: boolean
|
|
96
|
+
isLoadingMore: boolean
|
|
97
|
+
loadMore?: () => void
|
|
98
|
+
loadMoreRootMargin: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function DrawerScrollList({
|
|
102
|
+
unreadNotifications,
|
|
103
|
+
liveDurationMs,
|
|
104
|
+
onComplete,
|
|
105
|
+
onSettle,
|
|
106
|
+
hasMore,
|
|
107
|
+
isLoadingMore,
|
|
108
|
+
loadMore,
|
|
109
|
+
loadMoreRootMargin,
|
|
110
|
+
}: DrawerScrollListProps) {
|
|
111
|
+
const scrollRef = useRef<HTMLDivElement>(null)
|
|
112
|
+
const sentinelRef = useRef<HTMLDivElement>(null)
|
|
113
|
+
const loadMoreRef = useRef(loadMore)
|
|
114
|
+
loadMoreRef.current = loadMore
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!loadMore || !hasMore || isLoadingMore) return
|
|
118
|
+
const sentinel = sentinelRef.current
|
|
119
|
+
const root = scrollRef.current
|
|
120
|
+
if (!sentinel || !root) return
|
|
121
|
+
const observer = new IntersectionObserver(
|
|
122
|
+
entries => {
|
|
123
|
+
if (entries[0]?.isIntersecting) loadMoreRef.current?.()
|
|
124
|
+
},
|
|
125
|
+
{ root, rootMargin: loadMoreRootMargin },
|
|
126
|
+
)
|
|
127
|
+
observer.observe(sentinel)
|
|
128
|
+
return () => observer.disconnect()
|
|
129
|
+
}, [hasMore, isLoadingMore, loadMore, loadMoreRootMargin])
|
|
130
|
+
|
|
131
|
+
const isEmpty = unreadNotifications.length === 0
|
|
132
|
+
return (
|
|
133
|
+
<div
|
|
134
|
+
ref={scrollRef}
|
|
135
|
+
className="flex flex-1 flex-col gap-[var(--spacing-system-xs)] overflow-y-auto px-[var(--spacing-system-m)]"
|
|
136
|
+
>
|
|
137
|
+
{isEmpty && !isLoadingMore ? (
|
|
138
|
+
<EmptyState />
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
{unreadNotifications.map((n) => (
|
|
142
|
+
<NotificationTile
|
|
143
|
+
key={n.id}
|
|
144
|
+
notification={n}
|
|
145
|
+
liveDurationMs={liveDurationMs}
|
|
146
|
+
onComplete={onComplete}
|
|
147
|
+
onSettle={onSettle}
|
|
148
|
+
/>
|
|
149
|
+
))}
|
|
150
|
+
{isLoadingMore && <DrawerLoadingTiles />}
|
|
151
|
+
{loadMore && hasMore && <div ref={sentinelRef} className="h-1" aria-hidden="true" />}
|
|
152
|
+
</>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function DrawerLoadingTiles() {
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<div
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
className="h-16 w-full shrink-0 animate-pulse rounded-md border border-ods-border bg-ods-card"
|
|
164
|
+
/>
|
|
165
|
+
<div
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
className="h-16 w-full shrink-0 animate-pulse rounded-md border border-ods-border bg-ods-card"
|
|
168
|
+
/>
|
|
169
|
+
</>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
89
173
|
function EmptyState() {
|
|
90
174
|
return (
|
|
91
175
|
<div className="flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-xs)] py-[var(--spacing-system-l)]">
|