/** * SkeletonCalendar — shimmer loading placeholder. * * UI-SPEC §SkeletonCalendar: * - Month variant: 6×7 grid of rounded rect placeholders, animated shimmer * - Agenda variant: 4 date-group blocks, 2–3 rows each, varying widths (60–90%) * - No spinner; shimmer only (matches Fantastical-style) * - aria-busy="true", aria-label="Loading calendar" on root * * Shimmer keyframe declared in tokens.css (@keyframes shimmer). * The shimmer animation uses the gradient from tokens.css: * background: linear-gradient(90deg, --color-surface-dim, --color-border-subtle, --color-surface-dim) */ type SkeletonVariant = 'month' | 'agenda'; interface SkeletonCalendarProps { variant?: SkeletonVariant; } /** Shimmer inline style — gradient + animation referencing the keyframe in tokens.css */ const shimmerStyle: React.CSSProperties = { background: 'linear-gradient(90deg, var(--color-surface-dim), var(--color-border-subtle), var(--color-surface-dim))', backgroundSize: '200% 100%', animation: 'shimmer 1.5s infinite', borderRadius: 'var(--space-1)', }; export function SkeletonCalendar({ variant = 'month' }: SkeletonCalendarProps) { return (
{variant === 'month' ? : }
); } /** 6×7 month grid of shimmer cells */ function MonthSkeleton() { return (
{/* Day-of-week header row */}
{Array.from({ length: 7 }).map((_, i) => (
))}
{/* 6 rows × 7 columns */}
{Array.from({ length: 42 }).map((_, i) => (
))}
); } /** 4 date-group blocks, 2–3 event rows each, varying widths */ function AgendaSkeleton() { // Each group has: a date header + 2–3 event rows with varying widths const groups = [ { rows: [80, 65, 90] }, { rows: [75, 60] }, { rows: [85, 70, 65] }, { rows: [60, 80] }, ]; return (
{groups.map((group, gi) => (
{/* Date group header placeholder */}
{/* Event rows */}
{group.rows.map((widthPct, ri) => (
))}
))}
); }