@flamingo-stack/openframe-frontend-core 0.0.203 → 0.0.204-snapshot.20260523020255
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-27APPAJN.cjs +24 -0
- package/dist/chunk-27APPAJN.cjs.map +1 -0
- package/dist/{chunk-XGL5FKIK.js → chunk-D6BNDYZK.js} +114 -4
- package/dist/chunk-D6BNDYZK.js.map +1 -0
- package/dist/chunk-MJNXIEV2.js +24 -0
- package/dist/chunk-MJNXIEV2.js.map +1 -0
- package/dist/{chunk-25LVV26X.cjs → chunk-NLXY4QZE.cjs} +114 -4
- package/dist/chunk-NLXY4QZE.cjs.map +1 -0
- package/dist/{chunk-E6Q6UGDK.js → chunk-OFCRX24Y.js} +383 -569
- package/dist/chunk-OFCRX24Y.js.map +1 -0
- package/dist/{chunk-3YH2M76N.cjs → chunk-RDXOQWBY.cjs} +556 -742
- package/dist/chunk-RDXOQWBY.cjs.map +1 -0
- package/dist/components/announcement-bar.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -3
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +3 -2
- package/dist/components/index.cjs +4 -3
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +3 -2
- package/dist/components/navigation/index.cjs +4 -3
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +3 -2
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts.map +1 -1
- package/dist/components/shared/product-release/product-release-card.d.ts +19 -12
- package/dist/components/shared/product-release/product-release-card.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -2
- package/dist/contexts/endpoints-runtime-context.d.ts +28 -0
- package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -0
- package/dist/contexts/index.cjs +13 -0
- package/dist/contexts/index.cjs.map +1 -0
- package/dist/contexts/index.d.ts +24 -0
- package/dist/contexts/index.d.ts.map +1 -0
- package/dist/contexts/index.js +13 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/hooks/index.cjs +5 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -1
- package/dist/hooks/use-access-code-integration.d.ts +48 -0
- package/dist/hooks/use-access-code-integration.d.ts.map +1 -0
- package/dist/hooks/use-contact-submission.d.ts.map +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -6
- package/dist/utils/access-code-client.d.ts +21 -37
- package/dist/utils/access-code-client.d.ts.map +1 -1
- package/dist/utils/index.cjs +10 -51
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +11 -47
- package/dist/utils/index.js.map +1 -1
- package/package.json +7 -1
- package/src/components/announcement-bar.tsx +25 -3
- package/src/components/navigation/navigation-sidebar.tsx +3 -1
- package/src/components/shared/product-release/product-release-card-skeleton.tsx +66 -70
- package/src/components/shared/product-release/product-release-card.tsx +194 -242
- package/src/contexts/endpoints-runtime-context.tsx +68 -0
- package/src/contexts/index.ts +29 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/use-access-code-integration.ts +107 -0
- package/src/hooks/use-contact-submission.ts +6 -2
- package/src/utils/access-code-client.ts +32 -75
- package/dist/chunk-25LVV26X.cjs.map +0 -1
- package/dist/chunk-3YH2M76N.cjs.map +0 -1
- package/dist/chunk-E6Q6UGDK.js.map +0 -1
- package/dist/chunk-XGL5FKIK.js.map +0 -1
|
@@ -7,7 +7,6 @@ import { StatusBadge } from '../../ui/status-badge'
|
|
|
7
7
|
import { SquareAvatar } from '../../ui/square-avatar'
|
|
8
8
|
import {
|
|
9
9
|
AlertTriangle,
|
|
10
|
-
ChevronRight,
|
|
11
10
|
Eye,
|
|
12
11
|
Package,
|
|
13
12
|
Play,
|
|
@@ -18,21 +17,28 @@ import {
|
|
|
18
17
|
import { cn } from '../../../utils/cn'
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
|
-
* Card density
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - `
|
|
25
|
-
*
|
|
20
|
+
* Card density. Two variants, both actively used across openframe + flamingo
|
|
21
|
+
* apps and the related-content rail:
|
|
22
|
+
*
|
|
23
|
+
* - `lg`: rich large card used everywhere a product release is the focal
|
|
24
|
+
* item — openframe's `/releases` catalog row, flamingo's DevCenter tab,
|
|
25
|
+
* investor-update related-content section. Three zones — hero (16:9 cover
|
|
26
|
+
* + version pill + title + summary), changelog stats strip (icons +
|
|
27
|
+
* counts), metadata grid footer (Type · Status · Released · Author). The
|
|
28
|
+
* grid mirrors the hub's `<EntityAuthorCard>` byte-for-byte (see lg
|
|
29
|
+
* branch comment).
|
|
30
|
+
*
|
|
31
|
+
* - `sm`: compact horizontal layout (~80px tall) for inline rendering inside
|
|
32
|
+
* chat messages and other tight surfaces. Drops `<h3>` (block-only,
|
|
26
33
|
* illegal inside markdown `<p>`) for `<span>` text, swaps the outer
|
|
27
34
|
* `InteractiveCard` for a `<span>`-anchored link, and collapses to:
|
|
28
35
|
* 56px icon + 1-line title + 1-line meta (version · date).
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* branch comment).
|
|
36
|
+
*
|
|
37
|
+
* A previous `default` variant (vertical title+description / version+date
|
|
38
|
+
* column) was deleted in the 2026-05 DRY pass — it had a single consumer
|
|
39
|
+
* (the hub's RelatedContentSection) that has since moved to `lg`.
|
|
34
40
|
*/
|
|
35
|
-
export type ProductReleaseCardSize = '
|
|
41
|
+
export type ProductReleaseCardSize = 'lg' | 'sm'
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Minimal structural `<a>` prop bundle the consumer composes (typically
|
|
@@ -80,7 +86,7 @@ export interface ProductReleaseCardProps {
|
|
|
80
86
|
anchorProps?: ProductReleaseCardAnchorProps
|
|
81
87
|
/** Additional CSS classes */
|
|
82
88
|
className?: string
|
|
83
|
-
/** Card density. Defaults to `'
|
|
89
|
+
/** Card density. Defaults to `'lg'` (the canonical large card). */
|
|
84
90
|
size?: ProductReleaseCardSize
|
|
85
91
|
|
|
86
92
|
// ─── Catalog-only props (ignored by `default` / `sm` branches) ─────────
|
|
@@ -125,7 +131,7 @@ export function ProductReleaseCard({
|
|
|
125
131
|
onClick,
|
|
126
132
|
anchorProps,
|
|
127
133
|
className,
|
|
128
|
-
size = '
|
|
134
|
+
size = 'lg',
|
|
129
135
|
coverImage,
|
|
130
136
|
hasVideoCover,
|
|
131
137
|
releaseType,
|
|
@@ -135,7 +141,7 @@ export function ProductReleaseCard({
|
|
|
135
141
|
author,
|
|
136
142
|
changelogCounts,
|
|
137
143
|
}: ProductReleaseCardProps) {
|
|
138
|
-
// -----
|
|
144
|
+
// ----- LG branch (rich /releases catalog row + related-content rail) -----
|
|
139
145
|
// The card has THREE zones:
|
|
140
146
|
// 1. Hero — cover image LEFT, version pill + title + summary RIGHT.
|
|
141
147
|
// 2. Changelog strip — icons + counts (hidden when total === 0).
|
|
@@ -145,11 +151,11 @@ export function ProductReleaseCard({
|
|
|
145
151
|
// author-cell shapes, byte-for-byte). The OSS lib has zero hub
|
|
146
152
|
// coupling by design; we cannot import the hub's
|
|
147
153
|
// <EntityAuthorCard>. This is the SAME inline-duplication policy
|
|
148
|
-
// documented for the COMPACT_CARD_* string set
|
|
149
|
-
// If the hub's <EntityAuthorCard> visual changes (cell
|
|
150
|
-
// divider styles, avatar size, etc.), update this branch
|
|
151
|
-
// lockstep.
|
|
152
|
-
if (size === '
|
|
154
|
+
// documented for the COMPACT_CARD_* string set in the chat-card
|
|
155
|
+
// file. If the hub's <EntityAuthorCard> visual changes (cell
|
|
156
|
+
// padding, divider styles, avatar size, etc.), update this branch
|
|
157
|
+
// in lockstep.
|
|
158
|
+
if (size === 'lg') {
|
|
153
159
|
const totalChangelog =
|
|
154
160
|
(changelogCounts?.features ?? 0) +
|
|
155
161
|
(changelogCounts?.fixes ?? 0) +
|
|
@@ -157,46 +163,60 @@ export function ProductReleaseCard({
|
|
|
157
163
|
(changelogCounts?.breaking ?? 0)
|
|
158
164
|
|
|
159
165
|
// Build the metadata-grid cell array — mirrors the hub's
|
|
160
|
-
// EntityAuthorCard composition
|
|
161
|
-
//
|
|
162
|
-
//
|
|
166
|
+
// EntityAuthorCard composition. ALWAYS render all 3 value cells
|
|
167
|
+
// (Type / Status / Released) — missing values render as a plain
|
|
168
|
+
// em-dash + label so the grid keeps a fixed 4-cell shape (matching
|
|
169
|
+
// the skeleton). The Author cell is also always rendered below
|
|
170
|
+
// (effectiveAuthor falls back to a placeholder shape). This is
|
|
171
|
+
// load-to-resolve baseline parity: any conditional cell would
|
|
172
|
+
// introduce a reflow when the skeleton resolves.
|
|
173
|
+
//
|
|
174
|
+
// Plan note: em-dash placeholders read as plain text (NOT a colored
|
|
175
|
+
// StatusBadge for the Type cell) so empty badges don't look broken
|
|
176
|
+
// next to populated badges.
|
|
163
177
|
type ValueCell = {
|
|
164
178
|
value: string
|
|
165
179
|
label: string
|
|
166
180
|
uppercase: boolean
|
|
167
181
|
colorScheme?: 'error' | 'cyan' | 'success' | 'warning'
|
|
168
182
|
}
|
|
169
|
-
const valueCells: ValueCell[] = [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
183
|
+
const valueCells: ValueCell[] = [
|
|
184
|
+
releaseType && releaseTypeBadgeColor
|
|
185
|
+
? {
|
|
186
|
+
value: releaseType.toUpperCase(),
|
|
187
|
+
label: 'Type',
|
|
188
|
+
uppercase: true,
|
|
189
|
+
colorScheme: releaseTypeBadgeColor,
|
|
190
|
+
}
|
|
191
|
+
: { value: '—', label: 'Type', uppercase: false },
|
|
192
|
+
releaseStatus
|
|
193
|
+
? {
|
|
194
|
+
value: releaseStatus.toUpperCase(),
|
|
195
|
+
label: 'Status',
|
|
196
|
+
uppercase: true,
|
|
197
|
+
}
|
|
198
|
+
: { value: '—', label: 'Status', uppercase: false },
|
|
199
|
+
formattedDate
|
|
200
|
+
? {
|
|
201
|
+
value: formattedDate,
|
|
202
|
+
label: 'Released',
|
|
203
|
+
uppercase: false,
|
|
204
|
+
}
|
|
205
|
+
: { value: '—', label: 'Released', uppercase: false },
|
|
206
|
+
]
|
|
207
|
+
// EMPTY_AUTHOR_PLACEHOLDER shape — mirrors the hub's
|
|
208
|
+
// EMPTY_AUTHOR_PLACEHOLDER constant exported from
|
|
209
|
+
// components/shared/entity-author-card.tsx (hub can't be imported
|
|
210
|
+
// here; the two are kept in lockstep per the inline-duplication
|
|
211
|
+
// policy documented in the catalog branch comment above).
|
|
212
|
+
const effectiveAuthor = author?.full_name
|
|
213
|
+
? author
|
|
214
|
+
: { full_name: '—', avatar_url: null, job_title: 'Unknown' }
|
|
215
|
+
// Fixed 4-cell grid (Type / Status / Released / Author) so the
|
|
216
|
+
// skeleton's shape matches the loaded card exactly. The earlier
|
|
217
|
+
// dynamic `gridColsClass` ternary collapsed missing cells and
|
|
218
|
+
// caused 28-56px reflow on resolve.
|
|
219
|
+
const gridColsClass = 'md:grid-cols-4'
|
|
200
220
|
const dividerClass = 'border-b md:border-b-0 md:border-r border-ods-border'
|
|
201
221
|
|
|
202
222
|
const frameClass = cn(
|
|
@@ -242,107 +262,130 @@ export function ProductReleaseCard({
|
|
|
242
262
|
v{version}
|
|
243
263
|
</span>
|
|
244
264
|
</div>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
{/* Title — reserve a fixed 2-line height so cards with
|
|
266
|
+
1-line titles don't shrink and the catalog skeleton-to-
|
|
267
|
+
content transition is shift-free. Mirrors the
|
|
268
|
+
onboarding-guide catalog card. */}
|
|
269
|
+
<div className="min-h-[60px] md:min-h-[72px] flex items-start mb-3">
|
|
270
|
+
<h3 className="font-['Azeret_Mono'] font-semibold text-xl md:text-2xl text-ods-text-primary leading-tight line-clamp-2">
|
|
271
|
+
{title}
|
|
272
|
+
</h3>
|
|
273
|
+
</div>
|
|
274
|
+
{/* Summary — fixed 3-line height. `line-clamp-3` caps long
|
|
275
|
+
summaries at 3 lines; `min-h` reserves the same vertical
|
|
276
|
+
space when content is shorter, so the catalog grid stays
|
|
277
|
+
row-consistent regardless of per-card content length.
|
|
278
|
+
Heights derived from text-sm md:text-base × leading-relaxed
|
|
279
|
+
(1.625): 14×1.625×3 ≈ 68 px mobile, 16×1.625×3 ≈ 78 px desktop. */}
|
|
280
|
+
<div className="min-h-[68px] md:min-h-[78px]">
|
|
281
|
+
<p className="font-['DM_Sans'] text-sm md:text-base text-ods-text-secondary leading-relaxed line-clamp-3">
|
|
282
|
+
{summary ?? ''}
|
|
251
283
|
</p>
|
|
252
|
-
|
|
284
|
+
</div>
|
|
253
285
|
</div>
|
|
254
286
|
</div>
|
|
255
287
|
|
|
256
|
-
{/* CHANGELOG STRIP —
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
288
|
+
{/* CHANGELOG STRIP — ALWAYS rendered so the skeleton's
|
|
289
|
+
always-on changelog placeholder matches the loaded shape
|
|
290
|
+
(zero reflow on resolve). When `totalChangelog === 0`, an
|
|
291
|
+
empty-state line takes the same vertical space as the
|
|
292
|
+
populated row. */}
|
|
293
|
+
<div className="border-t border-ods-border pt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 font-['DM_Sans'] text-sm text-ods-text-secondary">
|
|
294
|
+
{totalChangelog > 0 && changelogCounts ? (
|
|
295
|
+
<>
|
|
296
|
+
{changelogCounts.features > 0 && (
|
|
297
|
+
<span className="inline-flex items-center gap-1.5">
|
|
298
|
+
<Sparkles className="w-3.5 h-3.5" />
|
|
299
|
+
{changelogCounts.features} {changelogCounts.features === 1 ? 'feature' : 'features'}
|
|
300
|
+
</span>
|
|
301
|
+
)}
|
|
302
|
+
{changelogCounts.fixes > 0 && (
|
|
303
|
+
<span className="inline-flex items-center gap-1.5">
|
|
304
|
+
<Wrench className="w-3.5 h-3.5" />
|
|
305
|
+
{changelogCounts.fixes} {changelogCounts.fixes === 1 ? 'fix' : 'fixes'}
|
|
306
|
+
</span>
|
|
307
|
+
)}
|
|
308
|
+
{changelogCounts.improvements > 0 && (
|
|
309
|
+
<span className="inline-flex items-center gap-1.5">
|
|
310
|
+
<TrendingUp className="w-3.5 h-3.5" />
|
|
311
|
+
{changelogCounts.improvements} {changelogCounts.improvements === 1 ? 'improvement' : 'improvements'}
|
|
312
|
+
</span>
|
|
313
|
+
)}
|
|
314
|
+
{changelogCounts.breaking > 0 && (
|
|
315
|
+
<span className="inline-flex items-center gap-1.5 text-[var(--ods-attention-yellow-warning)]">
|
|
316
|
+
<AlertTriangle className="w-3.5 h-3.5" />
|
|
317
|
+
{changelogCounts.breaking} breaking
|
|
318
|
+
</span>
|
|
319
|
+
)}
|
|
320
|
+
</>
|
|
321
|
+
) : (
|
|
322
|
+
<span className="text-sm text-ods-text-secondary">No changelog entries yet</span>
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
285
325
|
|
|
286
|
-
{/* METADATA GRID FOOTER —
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
</div>
|
|
324
|
-
))}
|
|
325
|
-
{hasAuthorCell && author && (
|
|
326
|
-
<div className="bg-ods-card p-4 flex items-center gap-3">
|
|
327
|
-
<SquareAvatar
|
|
328
|
-
src={author.avatar_url ?? undefined}
|
|
329
|
-
alt={author.full_name}
|
|
330
|
-
fallback={author.full_name.charAt(0).toUpperCase()}
|
|
331
|
-
size="md"
|
|
332
|
-
variant="round"
|
|
333
|
-
/>
|
|
334
|
-
<div className="flex flex-col gap-0 flex-1 min-w-0">
|
|
335
|
-
<p className="text-h3 tracking-[-0.36px] text-ods-text-primary truncate">
|
|
336
|
-
{author.full_name}
|
|
337
|
-
</p>
|
|
338
|
-
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary">
|
|
339
|
-
{author.job_title || 'Author'}
|
|
326
|
+
{/* METADATA GRID FOOTER — fixed 4-cell shape (Type / Status /
|
|
327
|
+
Released / Author) so the skeleton mirrors the loaded card
|
|
328
|
+
exactly. Empty value cells render em-dash + label (plain
|
|
329
|
+
text, no colored badge — em-dash badges read as broken next
|
|
330
|
+
to populated ones); the Author cell falls back to the
|
|
331
|
+
EMPTY_AUTHOR_PLACEHOLDER shape declared above. */}
|
|
332
|
+
<div
|
|
333
|
+
className={cn(
|
|
334
|
+
'grid grid-cols-1',
|
|
335
|
+
gridColsClass,
|
|
336
|
+
'border border-ods-border rounded-md overflow-hidden w-full',
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
{valueCells.map((cell, i) => (
|
|
340
|
+
<div
|
|
341
|
+
key={`${cell.label}-${i}`}
|
|
342
|
+
className={cn('bg-ods-card p-4 flex flex-col gap-3', dividerClass)}
|
|
343
|
+
>
|
|
344
|
+
<div className="flex flex-col gap-0">
|
|
345
|
+
{cell.colorScheme ? (
|
|
346
|
+
<StatusBadge
|
|
347
|
+
text={cell.value}
|
|
348
|
+
variant="card"
|
|
349
|
+
colorScheme={cell.colorScheme}
|
|
350
|
+
singleLine
|
|
351
|
+
className="self-start"
|
|
352
|
+
/>
|
|
353
|
+
) : (
|
|
354
|
+
<p
|
|
355
|
+
className={cn(
|
|
356
|
+
'text-h4',
|
|
357
|
+
// Em-dash placeholder reads as secondary text;
|
|
358
|
+
// populated values stay primary.
|
|
359
|
+
cell.value === '—' ? 'text-ods-text-secondary' : 'text-ods-text-primary',
|
|
360
|
+
)}
|
|
361
|
+
>
|
|
362
|
+
{cell.uppercase ? cell.value.toLocaleUpperCase() : cell.value}
|
|
340
363
|
</p>
|
|
341
|
-
|
|
364
|
+
)}
|
|
365
|
+
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary">
|
|
366
|
+
{cell.label}
|
|
367
|
+
</p>
|
|
342
368
|
</div>
|
|
343
|
-
|
|
369
|
+
</div>
|
|
370
|
+
))}
|
|
371
|
+
<div className="bg-ods-card p-4 flex items-center gap-3">
|
|
372
|
+
<SquareAvatar
|
|
373
|
+
src={effectiveAuthor.avatar_url ?? undefined}
|
|
374
|
+
alt={effectiveAuthor.full_name}
|
|
375
|
+
fallback={effectiveAuthor.full_name.charAt(0).toUpperCase()}
|
|
376
|
+
size="md"
|
|
377
|
+
variant="round"
|
|
378
|
+
/>
|
|
379
|
+
<div className="flex flex-col gap-0 flex-1 min-w-0">
|
|
380
|
+
<p className="text-h3 tracking-[-0.36px] text-ods-text-primary truncate">
|
|
381
|
+
{effectiveAuthor.full_name}
|
|
382
|
+
</p>
|
|
383
|
+
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary">
|
|
384
|
+
{effectiveAuthor.job_title || 'Author'}
|
|
385
|
+
</p>
|
|
386
|
+
</div>
|
|
344
387
|
</div>
|
|
345
|
-
|
|
388
|
+
</div>
|
|
346
389
|
|
|
347
390
|
{typeof viewCount === 'number' && viewCount > 0 && (
|
|
348
391
|
<div className="flex items-center gap-1.5 text-xs text-ods-text-secondary">
|
|
@@ -519,99 +562,8 @@ export function ProductReleaseCard({
|
|
|
519
562
|
)
|
|
520
563
|
}
|
|
521
564
|
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
// → soft RSC nav, modifier-click → browser default). The
|
|
527
|
-
// `<InteractiveCard onClick>` form remains the back-compat path for
|
|
528
|
-
// the public `/releases` tab caller which still routes via
|
|
529
|
-
// `router.push()` directly.
|
|
530
|
-
if (anchorProps) {
|
|
531
|
-
return (
|
|
532
|
-
<a
|
|
533
|
-
{...anchorProps}
|
|
534
|
-
className={cn(
|
|
535
|
-
'bg-ods-card border border-ods-border rounded-[6px]',
|
|
536
|
-
'flex flex-col md:flex-row',
|
|
537
|
-
'items-start md:items-center',
|
|
538
|
-
'gap-3 md:gap-4',
|
|
539
|
-
'p-4 no-underline',
|
|
540
|
-
'transition-colors hover:border-ods-text-secondary/40',
|
|
541
|
-
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-offset-1 focus-visible:ring-offset-ods-card',
|
|
542
|
-
className,
|
|
543
|
-
)}
|
|
544
|
-
>
|
|
545
|
-
{/* Left column - content */}
|
|
546
|
-
<div className="flex-1 w-full md:w-auto min-w-0 flex flex-col justify-center gap-2">
|
|
547
|
-
<div className="min-h-[48px] flex items-center">
|
|
548
|
-
<h3 className="text-h3 text-ods-text-primary tracking-[-0.36px] line-clamp-2" title={title}>
|
|
549
|
-
{title}
|
|
550
|
-
</h3>
|
|
551
|
-
</div>
|
|
552
|
-
<p className="text-h4 text-ods-text-secondary line-clamp-3" title={summary || ' '}>
|
|
553
|
-
{summary || ' '}
|
|
554
|
-
</p>
|
|
555
|
-
</div>
|
|
556
|
-
<div className="flex items-center gap-2 w-full md:w-auto justify-start md:justify-end shrink-0">
|
|
557
|
-
<div className="w-[200px] flex flex-col justify-center gap-2">
|
|
558
|
-
<p className="text-h3 text-ods-text-primary tracking-[-0.36px] truncate">
|
|
559
|
-
{version}
|
|
560
|
-
</p>
|
|
561
|
-
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate">
|
|
562
|
-
{formattedDate}
|
|
563
|
-
</p>
|
|
564
|
-
</div>
|
|
565
|
-
<div className="flex items-center justify-center p-3 shrink-0">
|
|
566
|
-
<ChevronRight className="h-6 w-6 text-ods-text-primary" />
|
|
567
|
-
</div>
|
|
568
|
-
</div>
|
|
569
|
-
</a>
|
|
570
|
-
)
|
|
571
|
-
}
|
|
572
|
-
return (
|
|
573
|
-
<InteractiveCard
|
|
574
|
-
clickable={true}
|
|
575
|
-
onClick={onClick}
|
|
576
|
-
className={cn(
|
|
577
|
-
'bg-ods-card border border-ods-border rounded-[6px]',
|
|
578
|
-
'flex flex-col md:flex-row',
|
|
579
|
-
'items-start md:items-center',
|
|
580
|
-
'gap-3 md:gap-4',
|
|
581
|
-
'p-4',
|
|
582
|
-
className
|
|
583
|
-
)}
|
|
584
|
-
>
|
|
585
|
-
{/* Left column - content */}
|
|
586
|
-
<div className="flex-1 w-full md:w-auto min-w-0 flex flex-col justify-center gap-2">
|
|
587
|
-
<div className="min-h-[48px] flex items-center">
|
|
588
|
-
<h3 className="text-h3 text-ods-text-primary tracking-[-0.36px] line-clamp-2" title={title}>
|
|
589
|
-
{title}
|
|
590
|
-
</h3>
|
|
591
|
-
</div>
|
|
592
|
-
<p className="text-h4 text-ods-text-secondary line-clamp-3" title={summary || ' '}>
|
|
593
|
-
{summary || ' '}
|
|
594
|
-
</p>
|
|
595
|
-
</div>
|
|
596
|
-
|
|
597
|
-
{/* Right column - version + date */}
|
|
598
|
-
<div
|
|
599
|
-
className="flex items-center gap-2 w-full md:w-auto justify-start md:justify-end shrink-0"
|
|
600
|
-
onClick={(e) => e.stopPropagation()}
|
|
601
|
-
>
|
|
602
|
-
<div className="w-[200px] flex flex-col justify-center gap-2">
|
|
603
|
-
<p className="text-h3 text-ods-text-primary tracking-[-0.36px] truncate">
|
|
604
|
-
{version}
|
|
605
|
-
</p>
|
|
606
|
-
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate">
|
|
607
|
-
{formattedDate}
|
|
608
|
-
</p>
|
|
609
|
-
</div>
|
|
610
|
-
{/* Icon column */}
|
|
611
|
-
<div className="flex items-center justify-center p-3 shrink-0">
|
|
612
|
-
<ChevronRight className="h-6 w-6 text-ods-text-primary" />
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
</InteractiveCard>
|
|
616
|
-
)
|
|
565
|
+
// Unreachable — `size` is typed `'lg' | 'sm'` and both branches return
|
|
566
|
+
// above. Kept as a defensive throw so a future variant addition that
|
|
567
|
+
// forgets to return doesn't silently render `undefined`.
|
|
568
|
+
throw new Error(`ProductReleaseCard: unsupported size '${size as string}'`)
|
|
617
569
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Endpoints runtime — sibling of ChatRuntime. Carries the API path
|
|
5
|
+
* literals consumed by oss-lib components/hooks/utils so a host
|
|
6
|
+
* application can override them (e.g. when running behind a reverse
|
|
7
|
+
* proxy as `user1.openframe.ai` → `/api/mingo-guide/*`).
|
|
8
|
+
*
|
|
9
|
+
* The hub mounts `<HubRuntimeProvider>` at root with the
|
|
10
|
+
* canonical hub paths; an embedded app mounts its own provider with
|
|
11
|
+
* remapped paths. The pattern mirrors ChatRuntimeContext exactly:
|
|
12
|
+
*
|
|
13
|
+
* - `useEndpointsRuntime()` returns null when no provider is mounted.
|
|
14
|
+
* For optional consumers that should gracefully no-op without one.
|
|
15
|
+
* - `useRequiredEndpointsRuntime()` throws on missing provider — for
|
|
16
|
+
* hooks/components that cannot function without endpoints.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT for embedders: memoize the value passed to
|
|
19
|
+
* `<EndpointsRuntimeContext.Provider value={...}>` (e.g. React.useMemo).
|
|
20
|
+
* Reference changes invalidate downstream effect dependency arrays and
|
|
21
|
+
* trigger unnecessary re-fetches.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createContext, useContext } from 'react'
|
|
25
|
+
|
|
26
|
+
export interface EndpointsRuntime {
|
|
27
|
+
/** GET active announcement (used by `<AnnouncementBar>` polling). */
|
|
28
|
+
announcementsUrl: string
|
|
29
|
+
accessCode: {
|
|
30
|
+
/** POST validate access code. */
|
|
31
|
+
validateUrl: string
|
|
32
|
+
/** POST consume / redeem access code after registration. */
|
|
33
|
+
consumeUrl: string
|
|
34
|
+
}
|
|
35
|
+
/** POST contact-form submission. */
|
|
36
|
+
contactUrl: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const EndpointsRuntimeContext = createContext<EndpointsRuntime | null>(null)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Optional read — returns null when no provider is mounted. Use for
|
|
43
|
+
* surfaces that should silently skip the fetch (e.g. announcement
|
|
44
|
+
* polling on a page rendered outside the provider tree).
|
|
45
|
+
*/
|
|
46
|
+
export function useEndpointsRuntime(): EndpointsRuntime | null {
|
|
47
|
+
return useContext(EndpointsRuntimeContext)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Strict variant — throws on missing provider. Use for consumers that
|
|
52
|
+
* cannot function without an endpoint (form submission, code
|
|
53
|
+
* validation). In tests/Storybook, wrap with the hub's
|
|
54
|
+
* `<HubRuntimeProvider>` or a stub
|
|
55
|
+
* `<EndpointsRuntimeContext.Provider value={mockedEndpoints}>`.
|
|
56
|
+
*/
|
|
57
|
+
export function useRequiredEndpointsRuntime(): EndpointsRuntime {
|
|
58
|
+
const v = useContext(EndpointsRuntimeContext)
|
|
59
|
+
if (!v) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'[endpoints-runtime] hook called outside an <EndpointsRuntimeContext.Provider>. ' +
|
|
62
|
+
'Hub: mount <HubRuntimeProvider> in your providers tree. ' +
|
|
63
|
+
'Embedded app: mount your own provider with proxied URLs at the tree root. ' +
|
|
64
|
+
'Tests/Storybook: wrap render() in <EndpointsRuntimeContext.Provider value={mocked}>.',
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
return v
|
|
68
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime contexts for embeddable oss-lib components.
|
|
3
|
+
*
|
|
4
|
+
* BUNDLE-SPLIT CONVENTION — read before adding new files here:
|
|
5
|
+
* Every module in `src/contexts/` calls `createContext()` at module
|
|
6
|
+
* top level. The tsup config builds this directory under the CLIENT
|
|
7
|
+
* bundle (with `"use client"` banner). Importing anything from here
|
|
8
|
+
* into a SERVER-bundled module (`utils/index.ts` and its `export *`
|
|
9
|
+
* transitive closure) crashes SSR with
|
|
10
|
+
* `createContext is not a function`.
|
|
11
|
+
*
|
|
12
|
+
* Rule of thumb when adding a runtime-aware helper:
|
|
13
|
+
* - Pure functions that take endpoints/runtime as args → `utils/`.
|
|
14
|
+
* - React hooks that READ the runtime via useContext → `hooks/`
|
|
15
|
+
* (with `'use client'` at the top of the file).
|
|
16
|
+
* - Never re-export from `utils/*` anything that pulls
|
|
17
|
+
* `src/contexts/*` transitively.
|
|
18
|
+
*
|
|
19
|
+
* The split between `utils/access-code-client.ts` (pure) and
|
|
20
|
+
* `hooks/use-access-code-integration.ts` (runtime-aware) is the
|
|
21
|
+
* reference example.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
EndpointsRuntimeContext,
|
|
26
|
+
useEndpointsRuntime,
|
|
27
|
+
useRequiredEndpointsRuntime,
|
|
28
|
+
type EndpointsRuntime,
|
|
29
|
+
} from './endpoints-runtime-context'
|
package/src/hooks/index.ts
CHANGED
|
@@ -18,3 +18,9 @@ export * from './nats/use-nats-client'
|
|
|
18
18
|
|
|
19
19
|
// Viewport / lazy-mount primitive (shared IO singleton)
|
|
20
20
|
export * from './use-near-viewport'
|
|
21
|
+
|
|
22
|
+
// Access code integration — pairs with the standalone helpers in
|
|
23
|
+
// `utils/access-code-client`. Lives in `hooks/` so the createContext
|
|
24
|
+
// pulled in via EndpointsRuntimeContext doesn't end up in the
|
|
25
|
+
// server-safe utils bundle.
|
|
26
|
+
export * from './use-access-code-integration'
|