"use client" import { cn } from "../../utils/cn" interface ChatMessageSkeletonProps { className?: string showAvatar?: boolean isUser?: boolean /** How many body lines to draw under the name row. Default 3 (paragraph-ish * reply). Pass 1 for surfaces whose real messages are single-line (e.g. the * short scripted hero-demo turns), so the skeleton isn't ~3x too tall. */ bodyLines?: number } // Body-line width palette: full-width lines then a short trailing line, so any // `bodyLines` count ends on a short line like a real wrapped message. const BODY_LINE_WIDTHS = ["w-full", "w-11/12", "w-4/5", "w-5/6"] const bodyLineWidth = (index: number, total: number) => index === total - 1 ? "w-3/5" : BODY_LINE_WIDTHS[index % BODY_LINE_WIDTHS.length] /** * One message-row skeleton. Mirrors `ChatMessageEnhanced`'s real layout: * full panel width, no card background, and an INLINE avatar in the name row * (the 2025 chat pattern — Claude.ai / ChatGPT / Gemini), NOT the legacy * `absolute -left-16` hanging avatar. Body lines use percentage widths so they * reflow cleanly as the panel resizes. */ export function ChatMessageSkeleton({ className, showAvatar = true, isUser = false, bodyLines = 3, }: ChatMessageSkeletonProps) { const lines = Math.max(1, bodyLines) return (
{/* Name row — inline avatar (assistant only) + name + timestamp, matching ChatMessageEnhanced's inline-avatar header. Neutral `bg-ods-border` so the placeholders are actually visible (a tinted low-alpha fill reads as blank on the dark surface). */}
{showAvatar && !isUser && (
)}
{/* Body lines — percentage widths so they reflow on panel resize. */}
{Array.from({ length: lines }, (_, i) => (
))}
) } interface ChatMessageListSkeletonProps { className?: string messageCount?: number showAvatars?: boolean /** * Match `ChatMessageList`'s `fullWidth`: drop the centered * `max-w-ods-content-narrow` column so the skeleton fills the panel and * reflows on resize (instead of staying a fixed narrow column with large * side gutters). Same semantics as the real list. */ fullWidth?: boolean /** Extra classes on the content column (e.g. host-provided padding). The * host wrapper is expected to own the panel padding; this stays empty by * default so the skeleton sits flush. */ contentClassName?: string /** Body lines per message row (forwarded to `ChatMessageSkeleton`). Default 3; * pass 1 for single-line message surfaces. */ bodyLines?: number /** * FILL mode: top-anchor the rows and CLIP overflow (instead of bottom-anchoring * + inner scroll). Combined with a generous `messageCount`, the rows cover the * container top-to-bottom at ANY height — no per-container measuring, no empty * band at the top. Use for loading surfaces that should read as "the whole * panel is loading" rather than "a short thread pinned to the bottom". */ fill?: boolean } export function ChatMessageListSkeleton({ className, messageCount = 6, showAvatars = true, fullWidth = false, contentClassName, bodyLines, fill = false, }: ChatMessageListSkeletonProps) { const messages = Array.from({ length: messageCount }, (_, index) => ({ id: index, isUser: index % 3 === 0, })) return (
{/* Bottom-anchor the rows like the real list — unless FILL mode wants them top-anchored + clipped to cover the whole panel. */} {!fill &&
} {messages.map((message) => ( ))}
) }