@flamingo-stack/openframe-frontend-core 0.0.337 → 0.0.338-snapshot.20260629235958
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-6XIB47I2.cjs → chunk-4DXRKAV6.cjs} +5 -5
- package/dist/{chunk-6XIB47I2.cjs.map → chunk-4DXRKAV6.cjs.map} +1 -1
- package/dist/{chunk-XB5LZ2YF.js → chunk-5A5SLX2F.js} +2 -2
- package/dist/{chunk-SKK3C6RE.cjs → chunk-6YWPFPKC.cjs} +25 -25
- package/dist/{chunk-SKK3C6RE.cjs.map → chunk-6YWPFPKC.cjs.map} +1 -1
- package/dist/{chunk-6G4NIF4Y.js → chunk-742ZCG3D.js} +4 -4
- package/dist/{chunk-6OA77Y55.cjs → chunk-7OPVJDX6.cjs} +15 -15
- package/dist/{chunk-6OA77Y55.cjs.map → chunk-7OPVJDX6.cjs.map} +1 -1
- package/dist/{chunk-TXVO2E5C.cjs → chunk-D5BRTQEA.cjs} +26 -26
- package/dist/{chunk-TXVO2E5C.cjs.map → chunk-D5BRTQEA.cjs.map} +1 -1
- package/dist/{chunk-HKAOAKPW.cjs → chunk-DKK3VRRM.cjs} +12 -12
- package/dist/{chunk-HKAOAKPW.cjs.map → chunk-DKK3VRRM.cjs.map} +1 -1
- package/dist/{chunk-EX5IVHLS.js → chunk-DLXHC6JS.js} +2 -2
- package/dist/{chunk-OKPYF6AI.js → chunk-DUJINSKJ.js} +2 -2
- package/dist/{chunk-LF2BC2Z6.cjs → chunk-FFNTLQZR.cjs} +60 -60
- package/dist/chunk-FFNTLQZR.cjs.map +1 -0
- package/dist/{chunk-75I2PDDE.cjs → chunk-GBE2R7R4.cjs} +7 -7
- package/dist/{chunk-75I2PDDE.cjs.map → chunk-GBE2R7R4.cjs.map} +1 -1
- package/dist/{chunk-AAPECG2D.js → chunk-GEJQRRD2.js} +8 -8
- package/dist/chunk-GEJQRRD2.js.map +1 -0
- package/dist/{chunk-X5DAKNSR.js → chunk-HHS3CJ7G.js} +2 -2
- package/dist/{chunk-KNTKGT4X.js → chunk-I54DGTLP.js} +3171 -3165
- package/dist/chunk-I54DGTLP.js.map +1 -0
- package/dist/{chunk-IZ72DTGG.js → chunk-I7NP2ZAJ.js} +4 -4
- package/dist/{chunk-43LYMONR.js → chunk-IK22LSYP.js} +4 -4
- package/dist/{chunk-7JZFRSMF.cjs → chunk-J344NBBP.cjs} +37 -37
- package/dist/{chunk-7JZFRSMF.cjs.map → chunk-J344NBBP.cjs.map} +1 -1
- package/dist/{chunk-WQZPEEFC.js → chunk-J7VJHE6O.js} +2 -2
- package/dist/{chunk-3YN5ZRHG.cjs → chunk-JOLEA7LT.cjs} +31 -31
- package/dist/{chunk-3YN5ZRHG.cjs.map → chunk-JOLEA7LT.cjs.map} +1 -1
- package/dist/{chunk-SZJLJWWO.cjs → chunk-KPR563UC.cjs} +62 -62
- package/dist/{chunk-SZJLJWWO.cjs.map → chunk-KPR563UC.cjs.map} +1 -1
- package/dist/{chunk-A3AMVJDJ.cjs → chunk-OJJCWMLT.cjs} +9 -9
- package/dist/{chunk-A3AMVJDJ.cjs.map → chunk-OJJCWMLT.cjs.map} +1 -1
- package/dist/{chunk-ASMOZBD6.cjs → chunk-PNWORHOR.cjs} +401 -395
- package/dist/chunk-PNWORHOR.cjs.map +1 -0
- package/dist/{chunk-3B6JQUCN.js → chunk-S5B3ZX6U.js} +2 -2
- package/dist/{chunk-F77UFU5A.js → chunk-YX7RIC4Z.js} +2 -2
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/entity-summary-editor.d.ts.map +1 -1
- package/dist/components/features/highlight-video-combined-section.d.ts.map +1 -1
- package/dist/components/features/highlight-video-preview.d.ts.map +1 -1
- package/dist/components/features/highlight-video-section.d.ts.map +1 -1
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/features/seo-editor-preview.d.ts.map +1 -1
- package/dist/components/features/transcript-summary-editor.d.ts.map +1 -1
- package/dist/components/features/video-source-selector.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +123 -121
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-layout.d.ts +6 -1
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/title-block.d.ts +8 -1
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/ai-generated-badge.d.ts +10 -0
- package/dist/components/ui/ai-generated-badge.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +4 -2
- 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 +3 -1
- package/dist/components/ui/info-card.d.ts.map +1 -1
- package/dist/components/ui/release-changelog-section.d.ts +2 -2
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/components/ui/table/table-skeleton.d.ts.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/features/entity-summary-editor.tsx +2 -4
- package/src/components/features/highlight-video-combined-section.tsx +2 -4
- package/src/components/features/highlight-video-preview.tsx +2 -4
- package/src/components/features/highlight-video-section.tsx +2 -4
- package/src/components/features/seo-editor-preview.tsx +4 -12
- package/src/components/features/transcript-summary-editor.tsx +3 -8
- package/src/components/features/video-source-selector.tsx +2 -4
- package/src/components/layout/page-layout.tsx +10 -1
- package/src/components/layout/title-block.tsx +56 -5
- package/src/components/shared/product-release/release-detail-page.tsx +4 -4
- package/src/components/ui/ai-generated-badge.tsx +17 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/info-card.tsx +55 -41
- package/src/components/ui/release-changelog-section.tsx +19 -14
- package/src/components/ui/table/table-skeleton.tsx +6 -2
- package/dist/chunk-AAPECG2D.js.map +0 -1
- package/dist/chunk-ASMOZBD6.cjs.map +0 -1
- package/dist/chunk-KNTKGT4X.js.map +0 -1
- package/dist/chunk-LF2BC2Z6.cjs.map +0 -1
- /package/dist/{chunk-XB5LZ2YF.js.map → chunk-5A5SLX2F.js.map} +0 -0
- /package/dist/{chunk-6G4NIF4Y.js.map → chunk-742ZCG3D.js.map} +0 -0
- /package/dist/{chunk-EX5IVHLS.js.map → chunk-DLXHC6JS.js.map} +0 -0
- /package/dist/{chunk-OKPYF6AI.js.map → chunk-DUJINSKJ.js.map} +0 -0
- /package/dist/{chunk-X5DAKNSR.js.map → chunk-HHS3CJ7G.js.map} +0 -0
- /package/dist/{chunk-IZ72DTGG.js.map → chunk-I7NP2ZAJ.js.map} +0 -0
- /package/dist/{chunk-43LYMONR.js.map → chunk-IK22LSYP.js.map} +0 -0
- /package/dist/{chunk-WQZPEEFC.js.map → chunk-J7VJHE6O.js.map} +0 -0
- /package/dist/{chunk-3B6JQUCN.js.map → chunk-S5B3ZX6U.js.map} +0 -0
- /package/dist/{chunk-F77UFU5A.js.map → chunk-YX7RIC4Z.js.map} +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
4
4
|
import { Upload, Sparkles, X, Video, Loader2 } from 'lucide-react';
|
|
5
|
+
import { AIGeneratedBadge } from '../ui/ai-generated-badge';
|
|
5
6
|
import { Button } from '../ui/button';
|
|
6
7
|
import { Input } from '../ui/input';
|
|
7
8
|
import { Label } from '../ui/label';
|
|
@@ -201,10 +202,7 @@ export function VideoSourceSelector({
|
|
|
201
202
|
<div className="flex items-center gap-2">
|
|
202
203
|
<Label>{uploadLabel}</Label>
|
|
203
204
|
{showAIBadge && isAIGenerated && (
|
|
204
|
-
<
|
|
205
|
-
<Sparkles className="h-3 w-3" />
|
|
206
|
-
AI Generated
|
|
207
|
-
</Badge>
|
|
205
|
+
<AIGeneratedBadge />
|
|
208
206
|
)}
|
|
209
207
|
</div>
|
|
210
208
|
<Button
|
|
@@ -58,6 +58,11 @@ export interface PageLayoutProps {
|
|
|
58
58
|
/** Title typography size, forwarded to `TitleBlock`. Default `'h2'` (frozen
|
|
59
59
|
* baseline). Pass `'h1'` for the unified Help Center pages. */
|
|
60
60
|
titleSize?: 'h1' | 'h2'
|
|
61
|
+
/** Optional node rendered inline next to the title (e.g. a status `Tag`), forwarded to `TitleBlock`. */
|
|
62
|
+
titleAdornment?: React.ReactNode
|
|
63
|
+
/** When true, the title/subtitle render as line-box-accurate skeleton bars (forwarded to
|
|
64
|
+
* `TitleBlock`). Header height stays identical to the loaded state — for page skeletons. */
|
|
65
|
+
loading?: boolean
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -80,10 +85,12 @@ export function PageLayout({
|
|
|
80
85
|
contentClassName,
|
|
81
86
|
showHeader = true,
|
|
82
87
|
titleSize,
|
|
88
|
+
titleAdornment,
|
|
89
|
+
loading,
|
|
83
90
|
}: PageLayoutProps) {
|
|
84
91
|
const hasActions = actions && actions.length > 0
|
|
85
92
|
const needsBottomPadding = hasActions && actionsVariant === 'primary-buttons'
|
|
86
|
-
const hasHeader = showHeader && (title || subtitle || image || backButton || hasActions || selector)
|
|
93
|
+
const hasHeader = showHeader && (title || subtitle || image || backButton || hasActions || selector || loading)
|
|
87
94
|
|
|
88
95
|
return (
|
|
89
96
|
<div className={cn('flex flex-col w-full', className)}>
|
|
@@ -99,6 +106,8 @@ export function PageLayout({
|
|
|
99
106
|
selector={selector}
|
|
100
107
|
variant={headerVariant}
|
|
101
108
|
titleSize={titleSize}
|
|
109
|
+
titleAdornment={titleAdornment}
|
|
110
|
+
loading={loading}
|
|
102
111
|
/>
|
|
103
112
|
)}
|
|
104
113
|
|
|
@@ -29,6 +29,15 @@
|
|
|
29
29
|
* opt the title typography up to `text-h1` (used by the unified Help Center
|
|
30
30
|
* pages). This is additive and default-preserving; do NOT change the default or
|
|
31
31
|
* touch anything else here.
|
|
32
|
+
*
|
|
33
|
+
* SANCTIONED EXCEPTION (2026-06, explicit human sign-off): the OPTIONAL
|
|
34
|
+
* `titleAdornment` and `loading` props. `titleAdornment` renders a node inline
|
|
35
|
+
* after the title (e.g. a status `Tag`). `loading` swaps ONLY the title/subtitle
|
|
36
|
+
* TEXT for line-box-accurate skeleton bars (the surrounding `h1`/`p` and their
|
|
37
|
+
* typography are untouched, so a loading header is pixel-identical in height to
|
|
38
|
+
* the loaded one — used by page skeletons that render through `PageLayout`).
|
|
39
|
+
* Both are additive + default-preserving: omit them and every existing caller is
|
|
40
|
+
* byte-identical. Do NOT change defaults or touch anything else here.
|
|
32
41
|
* ========================================================================== */
|
|
33
42
|
|
|
34
43
|
import React from 'react'
|
|
@@ -70,6 +79,30 @@ export interface TitleBlockProps {
|
|
|
70
79
|
* opt the title up to `text-h1` (the unified Help Center pages). Subtitle stays
|
|
71
80
|
* `text-h6` either way. */
|
|
72
81
|
titleSize?: 'h1' | 'h2'
|
|
82
|
+
/** Optional node rendered inline, immediately after the title (e.g. a status `Tag`).
|
|
83
|
+
* Additive + default-preserving: omit it and every existing caller is byte-identical. */
|
|
84
|
+
titleAdornment?: React.ReactNode
|
|
85
|
+
/** When true, the title/subtitle TEXT is replaced by line-box-accurate skeleton bars
|
|
86
|
+
* (typography + surrounding markup unchanged, so header height is identical to loaded).
|
|
87
|
+
* Additive + default-preserving: omit it and every existing caller is byte-identical. */
|
|
88
|
+
loading?: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Inline text skeleton — used only in `loading` mode, placed directly inside the real
|
|
93
|
+
* `h1`/`p`. It is a single `inline-block` bar whose height is intentionally SHORTER than the
|
|
94
|
+
* typography line-height, and it uses the default baseline alignment. That way the element's
|
|
95
|
+
* own line-box STRUT (text-h2 → 40px, text-h6 → 20px) sets the height exactly as it would for
|
|
96
|
+
* real text — the bar fits within the ascent and never inflates the line. So a loading header
|
|
97
|
+
* is pixel-identical in height to the loaded one. Phrasing-valid (`span` only).
|
|
98
|
+
*/
|
|
99
|
+
function TitleTextSkeleton({ widthClass, heightClass }: { widthClass: string; heightClass: string }) {
|
|
100
|
+
return (
|
|
101
|
+
<span
|
|
102
|
+
aria-hidden
|
|
103
|
+
className={cn('inline-block max-w-full rounded-md bg-ods-border animate-pulse', widthClass, heightClass)}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
73
106
|
}
|
|
74
107
|
|
|
75
108
|
export function TitleBlock({
|
|
@@ -84,6 +117,8 @@ export function TitleBlock({
|
|
|
84
117
|
variant = 'plain',
|
|
85
118
|
className,
|
|
86
119
|
titleSize = 'h2',
|
|
120
|
+
titleAdornment,
|
|
121
|
+
loading,
|
|
87
122
|
}: TitleBlockProps) {
|
|
88
123
|
const hasActions = actions && actions.length > 0
|
|
89
124
|
const hasMenuActions = !!menuActions && menuActions.some(g => g.items.length > 0)
|
|
@@ -115,7 +150,7 @@ export function TitleBlock({
|
|
|
115
150
|
className="hidden md:inline-flex"
|
|
116
151
|
/>
|
|
117
152
|
)}
|
|
118
|
-
{(image || subtitle) ? (
|
|
153
|
+
{(image || subtitle || loading) ? (
|
|
119
154
|
<div className="flex items-center gap-[var(--spacing-system-m)] min-w-0 w-full">
|
|
120
155
|
{image && (
|
|
121
156
|
<EntityImage
|
|
@@ -125,16 +160,32 @@ export function TitleBlock({
|
|
|
125
160
|
/>
|
|
126
161
|
)}
|
|
127
162
|
<div className="flex flex-col justify-center min-w-0 flex-1">
|
|
128
|
-
{title && (
|
|
129
|
-
|
|
163
|
+
{(loading || title) && (
|
|
164
|
+
titleAdornment ? (
|
|
165
|
+
<div className="flex items-center gap-[var(--spacing-system-m)] min-w-0 w-full">
|
|
166
|
+
<h1 className={cn(titleClass, 'text-ods-text-primary truncate min-w-0')} title={title}>{loading ? <TitleTextSkeleton widthClass="w-48 md:w-72" heightClass="h-4 md:h-6" /> : title}</h1>
|
|
167
|
+
<span className="shrink-0">{titleAdornment}</span>
|
|
168
|
+
</div>
|
|
169
|
+
) : (
|
|
170
|
+
<h1 className={cn(titleClass, 'text-ods-text-primary truncate')} title={title}>{loading ? <TitleTextSkeleton widthClass="w-48 md:w-72" heightClass="h-4 md:h-6" /> : title}</h1>
|
|
171
|
+
)
|
|
130
172
|
)}
|
|
131
173
|
{subtitle && (
|
|
132
|
-
<p className="text-h6 text-ods-text-secondary truncate" title={subtitle}>{subtitle}</p>
|
|
174
|
+
<p className="text-h6 text-ods-text-secondary truncate" title={subtitle}>{loading ? <TitleTextSkeleton widthClass="w-28 md:w-36" heightClass="h-2.5 md:h-3" /> : subtitle}</p>
|
|
133
175
|
)}
|
|
134
176
|
</div>
|
|
135
177
|
</div>
|
|
136
178
|
) : (
|
|
137
|
-
title &&
|
|
179
|
+
title && (
|
|
180
|
+
titleAdornment ? (
|
|
181
|
+
<div className="flex items-center gap-[var(--spacing-system-m)] min-w-0 w-full">
|
|
182
|
+
<h1 className={cn(titleClass, 'text-ods-text-primary truncate min-w-0')} title={title}>{title}</h1>
|
|
183
|
+
<span className="shrink-0">{titleAdornment}</span>
|
|
184
|
+
</div>
|
|
185
|
+
) : (
|
|
186
|
+
<h1 className={cn(titleClass, 'text-ods-text-primary')}>{title}</h1>
|
|
187
|
+
)
|
|
188
|
+
)
|
|
138
189
|
)}
|
|
139
190
|
</div>
|
|
140
191
|
|
|
@@ -402,7 +402,7 @@ export function ReleaseDetailPage({
|
|
|
402
402
|
isBreaking
|
|
403
403
|
hideTitle
|
|
404
404
|
icon={<AlertTriangle className="h-6 w-6" />}
|
|
405
|
-
|
|
405
|
+
MarkdownRenderer={MarkdownRenderer}
|
|
406
406
|
/>
|
|
407
407
|
{/* Features / Bugs / Improvements use `previewFirst` — same
|
|
408
408
|
progressive-disclosure pattern as the investor-update detail
|
|
@@ -415,21 +415,21 @@ export function ReleaseDetailPage({
|
|
|
415
415
|
entries={featuresAdded || []}
|
|
416
416
|
icon={<Sparkles className="h-6 w-6" />}
|
|
417
417
|
previewFirst
|
|
418
|
-
|
|
418
|
+
MarkdownRenderer={MarkdownRenderer}
|
|
419
419
|
/>
|
|
420
420
|
<ReleaseChangelogSection
|
|
421
421
|
title="Bugs Fixed"
|
|
422
422
|
entries={bugFixed || []}
|
|
423
423
|
icon={<Wrench className="h-6 w-6" />}
|
|
424
424
|
previewFirst
|
|
425
|
-
|
|
425
|
+
MarkdownRenderer={MarkdownRenderer}
|
|
426
426
|
/>
|
|
427
427
|
<ReleaseChangelogSection
|
|
428
428
|
title="Improvements"
|
|
429
429
|
entries={improvements || []}
|
|
430
430
|
icon={<TrendingUp className="h-6 w-6" />}
|
|
431
431
|
previewFirst
|
|
432
|
-
|
|
432
|
+
MarkdownRenderer={MarkdownRenderer}
|
|
433
433
|
/>
|
|
434
434
|
|
|
435
435
|
{/* Video Bites Section - Only when VideoDisplaySection is not handling it */}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Badge } from './badge';
|
|
2
|
+
import { Sparkles } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical "AI Generated" chip (Sparkles + label). Single source of truth for
|
|
6
|
+
* every AI-generated indicator across the app — replaces the byte-identical
|
|
7
|
+
* inline `<Badge variant="secondary"><Sparkles/> AI Generated</Badge>` that was
|
|
8
|
+
* copy-pasted across the summary / video / SEO editors.
|
|
9
|
+
*/
|
|
10
|
+
export function AIGeneratedBadge({ className }: { className?: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<Badge variant="secondary" className={`flex items-center gap-1${className ? ` ${className}` : ''}`}>
|
|
13
|
+
<Sparkles className="h-3 w-3" />
|
|
14
|
+
AI Generated
|
|
15
|
+
</Badge>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -52,6 +52,7 @@ export * from './tab-navigation'
|
|
|
52
52
|
// Feedback components
|
|
53
53
|
export * from './alert'
|
|
54
54
|
export * from './badge'
|
|
55
|
+
export * from './ai-generated-badge'
|
|
55
56
|
export * from './progress'
|
|
56
57
|
export * from './release-changelog-section'
|
|
57
58
|
export * from './status-badge'
|
|
@@ -55,52 +55,66 @@ export function InfoCard({ data, className = '' }: InfoCardProps) {
|
|
|
55
55
|
className,
|
|
56
56
|
)}
|
|
57
57
|
>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{data.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
{/* Header (title + subtitle) and body (rows + progress) are two groups separated by
|
|
59
|
+
`gap-l`; the title/subtitle stack tightly and the rows use `gap-xs` — matching the ODS
|
|
60
|
+
"Info-card" design. With no title/subtitle, the body is the only group. */}
|
|
61
|
+
<div className="p-[var(--spacing-system-m)] flex flex-col gap-[var(--spacing-system-l)] items-start w-full">
|
|
62
|
+
{(data.title || data.subtitle) && (
|
|
63
|
+
<div className="flex flex-col items-start self-stretch w-full">
|
|
64
|
+
{data.title && (
|
|
65
|
+
<div className="flex items-center gap-[var(--spacing-system-xsf)] self-stretch">
|
|
66
|
+
<span className="text-h4 text-ods-text-primary truncate min-w-0" title={data.title}>
|
|
67
|
+
{data.title}
|
|
68
|
+
</span>
|
|
69
|
+
{data.icon}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
{data.subtitle && (
|
|
73
|
+
<div className="flex items-center gap-[var(--spacing-system-xsf)] self-stretch">
|
|
74
|
+
<span className="text-h4 text-ods-text-secondary truncate min-w-0" title={data.subtitle}>
|
|
75
|
+
{data.subtitle}
|
|
76
|
+
</span>
|
|
77
|
+
{/* Icon lives with the title when present; otherwise it falls to the subtitle so
|
|
78
|
+
subtitle-only cards still render it. */}
|
|
79
|
+
{!data.title && data.icon}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
72
82
|
</div>
|
|
73
83
|
)}
|
|
74
84
|
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
{(data.items.length > 0 || data.progress) && (
|
|
86
|
+
<div className="flex flex-col gap-[var(--spacing-system-xs)] items-start self-stretch w-full">
|
|
87
|
+
{/* Info items */}
|
|
88
|
+
{data.items.map((item, index) => {
|
|
89
|
+
const values = Array.isArray(item.value) ? item.value : [item.value]
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
return (
|
|
92
|
+
<React.Fragment key={index}>
|
|
93
|
+
{values.map((val, valIndex) => (
|
|
94
|
+
<InfoCardValueRow
|
|
95
|
+
key={`${index}-${valIndex}`}
|
|
96
|
+
label={item.label}
|
|
97
|
+
value={val}
|
|
98
|
+
showLabel={valIndex === 0}
|
|
99
|
+
copyable={item.copyable}
|
|
100
|
+
icon={valIndex === 0 ? item.icon : undefined}
|
|
101
|
+
copyAriaLabel={`Copy ${item.label ?? 'value'} ${valIndex + 1}`}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</React.Fragment>
|
|
105
|
+
)
|
|
106
|
+
})}
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
{/* Progress bar */}
|
|
109
|
+
{data.progress && (
|
|
110
|
+
<ProgressBar
|
|
111
|
+
progress={data.progress.value}
|
|
112
|
+
warningThreshold={data.progress.warningThreshold}
|
|
113
|
+
criticalThreshold={data.progress.criticalThreshold}
|
|
114
|
+
inverted={data.progress.inverted}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
104
118
|
)}
|
|
105
119
|
</div>
|
|
106
120
|
|
|
@@ -36,7 +36,7 @@ interface ReleaseChangelogSectionProps {
|
|
|
36
36
|
* the lib's `RichMarkdownRenderer` so changelog rich-link previews
|
|
37
37
|
* (YouTube, OG cards, etc.) work out of the box. Hosts that already
|
|
38
38
|
* wrap with a Supabase-aware preset can keep passing their own. */
|
|
39
|
-
|
|
39
|
+
MarkdownRenderer?: React.ComponentType<{ content: string }>;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Collapsed height for the preview-first mode. ~120px shows the first
|
|
@@ -52,7 +52,7 @@ export function ReleaseChangelogSection({
|
|
|
52
52
|
defaultCollapsed = true,
|
|
53
53
|
previewFirst = false,
|
|
54
54
|
icon,
|
|
55
|
-
|
|
55
|
+
MarkdownRenderer = RichMarkdownRenderer,
|
|
56
56
|
}: ReleaseChangelogSectionProps) {
|
|
57
57
|
const [collapsed, setCollapsed] = useState(collapsible ? defaultCollapsed : false);
|
|
58
58
|
const [previewExpanded, setPreviewExpanded] = useState(false);
|
|
@@ -126,7 +126,7 @@ export function ReleaseChangelogSection({
|
|
|
126
126
|
} : {}),
|
|
127
127
|
}}
|
|
128
128
|
>
|
|
129
|
-
<ChangelogEntryList entries={entries}
|
|
129
|
+
<ChangelogEntryList entries={entries} MarkdownRenderer={MarkdownRenderer} />
|
|
130
130
|
</div>
|
|
131
131
|
{previewNeedsFade && (
|
|
132
132
|
<button
|
|
@@ -142,7 +142,7 @@ export function ReleaseChangelogSection({
|
|
|
142
142
|
)}
|
|
143
143
|
</div>
|
|
144
144
|
) : (
|
|
145
|
-
<ChangelogEntryList entries={entries}
|
|
145
|
+
<ChangelogEntryList entries={entries} MarkdownRenderer={MarkdownRenderer} />
|
|
146
146
|
)
|
|
147
147
|
)}
|
|
148
148
|
</div>
|
|
@@ -156,31 +156,36 @@ export function ReleaseChangelogSection({
|
|
|
156
156
|
*/
|
|
157
157
|
function ChangelogEntryList({
|
|
158
158
|
entries,
|
|
159
|
-
|
|
159
|
+
MarkdownRenderer,
|
|
160
160
|
}: {
|
|
161
161
|
entries: ChangelogEntry[];
|
|
162
|
-
|
|
162
|
+
MarkdownRenderer: React.ComponentType<{ content: string }>;
|
|
163
163
|
}) {
|
|
164
164
|
return (
|
|
165
165
|
<ul className="space-y-6">
|
|
166
166
|
{entries.map((entry, index) => (
|
|
167
167
|
<li key={index} className="border-l-2 border-ods-border pl-4 ml-0">
|
|
168
|
-
{/* Entry title —
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
{/* Entry title — run through the SAME markdown renderer as the
|
|
169
|
+
description so inline markdown (links like `[label](url)`,
|
|
170
|
+
emphasis) renders instead of showing as raw text, then pinned
|
|
171
|
+
back to the `text-h3` body+BOLD scale (per ODS tokens:
|
|
172
|
+
`--font-h3-weight: var(--font-weight-bold)`, 14/18px responsive)
|
|
173
|
+
so plain-text titles look exactly as before. `[&_p]:!my-0`
|
|
174
|
+
strips the renderer's paragraph margins; `mb-2` keeps the gap to
|
|
175
|
+
the description. */}
|
|
176
|
+
<div className="text-h3 text-ods-text-primary mb-2 [&_p]:!text-[length:var(--font-size-h3-body)] [&_p]:!leading-[var(--font-line-space-h3-body)] [&_p]:!font-bold [&_p]:!my-0 [&_p+p]:!mt-2">
|
|
177
|
+
<MarkdownRenderer content={entry.title} />
|
|
178
|
+
</div>
|
|
174
179
|
{entry.description && (
|
|
175
180
|
/* Entry description — body text matches the main release
|
|
176
181
|
summary at the SAME 14/18px responsive `text-h4` scale.
|
|
177
|
-
The `
|
|
182
|
+
The `MarkdownRenderer` forces its own `<p>` typography
|
|
178
183
|
which would override `text-h4` on `lg+` viewports and
|
|
179
184
|
inflate the changelog body to 20px. The `[&_p]:!` overrides
|
|
180
185
|
pin every descendant `<p>` back to the h4 responsive tokens
|
|
181
186
|
so the breakpoints stay aligned with the rest of the page. */
|
|
182
187
|
<div className="text-h4 text-ods-text-primary [&_p]:!text-[length:var(--font-size-h4-body)] [&_p]:!leading-[var(--font-line-space-h4-body)] [&_p]:!font-medium">
|
|
183
|
-
<
|
|
188
|
+
<MarkdownRenderer content={entry.description} />
|
|
184
189
|
</div>
|
|
185
190
|
)}
|
|
186
191
|
</li>
|
|
@@ -16,6 +16,10 @@ export function TableCardSkeleton({
|
|
|
16
16
|
className,
|
|
17
17
|
rowClassName
|
|
18
18
|
}: TableCardSkeletonProps) {
|
|
19
|
+
// The multi-line placeholder belongs to the primary text column — the wide,
|
|
20
|
+
// GROWING one (`flex-1` / `flex-[n]`), not necessarily the first column, which
|
|
21
|
+
// may be a narrow leading action/icon column. Falls back to the first column.
|
|
22
|
+
const primaryKey = (columns.find((c) => /flex-(1|\[)/.test(c.width || '')) ?? columns[0])?.key
|
|
19
23
|
return (
|
|
20
24
|
<>
|
|
21
25
|
{Array.from({ length: rows }).map((_, index) => (
|
|
@@ -41,8 +45,8 @@ export function TableCardSkeleton({
|
|
|
41
45
|
)}
|
|
42
46
|
>
|
|
43
47
|
<div className="h-5 bg-ods-bg-surface rounded w-3/4 mb-1" />
|
|
44
|
-
{/*
|
|
45
|
-
{index % 2 === 0 && column.key ===
|
|
48
|
+
{/* second line simulates multi-line content on the primary column */}
|
|
49
|
+
{index % 2 === 0 && column.key === primaryKey && (
|
|
46
50
|
<div className="h-4 bg-ods-bg-surface rounded w-1/2 opacity-60" />
|
|
47
51
|
)}
|
|
48
52
|
</div>
|