'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}'`) }