'use client'
import React from 'react'
import { cn } from '../../../utils/cn'
export interface ProductReleaseCardSkeletonProps {
/** Additional CSS classes */
className?: string
/** Card density. Must match the loaded card's `size` prop so the loading
* height matches the resolved height (no layout shift on resolve). */
size?: 'lg' | 'sm'
}
export function ProductReleaseCardSkeleton({ className, size = 'lg' }: ProductReleaseCardSkeletonProps) {
// ----- LG branch — must match ProductReleaseCard size='lg'.
// Same outer frame (`bg-ods-card border border-ods-border …
// p-6 gap-4`). Inner: hero (16:9 cover + version pill + title + summary),
// changelog strip placeholder, metadata-grid footer (4 cells via grid).
// Heights chosen to match the loaded card's rendered metrics so the
// 5-card slot grid in `ReleasesList` doesn't jump on resolve. The
// loaded card ALSO always renders the changelog strip + a fixed 4-cell
// grid (with em-dash placeholders for missing values), so this
// skeleton's shape matches exactly with zero load-to-resolve reflow.
if (size === 'lg') {
return (
{/* HERO — placeholders use `bg-ods-border` (#3a3a3a) so they
contrast against the card's `bg-ods-card`
(#212121) container. The metadata grid cells below use
`bg-ods-card` containers so `bg-ods-bg` placeholders work
there, but in the hero the card IS `bg-ods-card`-equivalent —
`bg-ods-bg` (#161616) is only 6 hex points darker than the
card and renders nearly invisible.
CRITICAL: title + summary use the SAME min-h containers as
the loaded card so total card height is byte-identical
between skeleton state and loaded state. Without this,
individual placeholder heights underrun the loaded card's
min-h reservations and the page jumps on resolve. */}
{/* Version pill — mirrors `flex items-center gap-3 mb-3` in
the loaded card. The loaded `
` renders at
line-height 28 px (Tailwind text-lg = 18 px / 28 px LH);
placeholder uses `h-7` (28 px) to match exactly. */}
{/* Title container — SAME min-h as the loaded card so the
card height contributed by this region matches exactly. */}
{/* Summary container — SAME min-h as the loaded card. The
3 placeholder lines mirror the rendered 3-line clamp;
`bg-ods-border/70` keeps summary placeholders slightly
dimmer than title placeholders (primary vs secondary
text hierarchy). */}
{/* CHANGELOG strip placeholder — always rendered. Inner
placeholder `h-5` mirrors the loaded strip's `text-sm`
line-height (20 px) so total height is consistent with the
loaded `border-t pt-3 + text content` (~32-33 px). */}
{/* METADATA GRID — 4-cell placeholder. The grid cells use
`bg-ods-card` containers and `bg-ods-bg` placeholders, which
DO contrast correctly because the cells are brighter than
the placeholders. Inner content heights mirror the loaded
cells (`text-h4` ≈ 28 px + `DM_Sans 14px leading-20`) so
total grid height matches the loaded ~86 px. */}
{[0, 1, 2].map((i) => (
))}
{/* Author cell */}
)
}
// ----- COMPACT branch — must match ProductReleaseCard size='sm' exactly.
// Same outer: span + items-start + gap-3 + p-2 + my-1.5 (no border to keep
// the skeleton 1px lighter than the resolved card is fine — but we mirror
// the border too so width is byte-identical). Inner row 1 = title +
// version-pill; row 2 = date; row 3 = summary line. Heights/widths chosen
// to match the rendered card line-heights (text-sm = 14px line-height-20,
// text-[11px] = 11/16). The total height comes out the same as the loaded
// card so the chat message height does NOT jump on resolve.
if (size === 'sm') {
return (
{/* Text column: 3 rows with FIXED heights matching the loaded
card (h-5 title, h-4 + h-4 meta + summary). Skeleton bars
sit centered inside each row container so a placeholder
occupies the SAME pixel position as the loaded text will
on resolve. */}
)
}
// Unreachable — `size` is typed `'lg' | 'sm'` and both branches return
// above. Kept as a defensive throw so a future variant addition that
// forgets to return doesn't silently render `undefined`.
throw new Error(`ProductReleaseCardSkeleton: unsupported size '${size as string}'`)
}