import clsx from 'clsx';
// Skeleton loading previews. The base block uses Tailwind's animate-pulse and
// the light-gray surface tokens so placeholders match real card/table styling.
// Composites below mirror the layouts they stand in for (event cards, admin
// tables, FAQ accordions, article bodies) to avoid layout shift when data lands.
interface SkeletonProps {
className?: string;
}
export function Skeleton({ className }: SkeletonProps) {
return (
);
}
// Screen-reader announcement wrapper for a group of skeleton blocks.
export function SkeletonGroup({ className, children }: SkeletonProps & { children: React.ReactNode }) {
return (
Loading…
{children}
);
}
// Paragraph-style stack of text lines with a shorter last line.
export function SkeletonText({ lines = 3, className }: SkeletonProps & { lines?: number }) {
return (
{Array.from({ length: lines }).map((_, i) => (
))}
);
}
// Mirrors the event card in EventsClient: banner, title, meta rows, price + button.
export function EventCardSkeleton() {
return (
);
}
export function EventGridSkeleton({ count = 6 }: { count?: number }) {
return (
{Array.from({ length: count }).map((_, i) => (
))}
);
}
// Mirrors the featured "next event" hero card on the homepage: side banner + info column.
export function FeaturedEventSkeleton() {
return (
);
}
// Accordion-style rows for FAQ lists. `compact` matches the homepage variant
// (rounded-btn, px-5); the default matches the /faq page cards (px-6, more spacing).
export function FaqListSkeleton({ count = 5, compact = false }: { count?: number; compact?: boolean }) {
return (
{Array.from({ length: count }).map((_, i) => (
))}
);
}
// Long-form content: heading, meta line, then paragraph blocks.
export function ArticleSkeleton({ withBanner = false }: { withBanner?: boolean }) {
return (
{withBanner && }
);
}
// Stacked list of card rows (dashboard tickets/payments, contact lists).
export function CardListSkeleton({ count = 3 }: { count?: number }) {
return (
{Array.from({ length: count }).map((_, i) => (
))}
);
}
// Admin data tables: desktop table inside a Card, mobile stacked cards —
// mirrors the two-breakpoint layout every admin list page renders.
export function TableSkeleton({ rows = 6, cols = 5 }: { rows?: number; cols?: number }) {
return (
{/* Desktop: table */}
{Array.from({ length: cols }).map((_, i) => (
))}
{Array.from({ length: rows }).map((_, r) => (
{Array.from({ length: cols - 1 }).map((_, c) => (
))}
))}
{/* Mobile: card list */}
{Array.from({ length: Math.min(rows, 4) }).map((_, i) => (
))}
);
}
// Full admin page placeholder: title bar + table. Used by the `if (loading)`
// gates that replace the entire page, so it includes the header row.
export function AdminPageSkeleton({ rows = 6, cols = 5 }: { rows?: number; cols?: number }) {
return (
);
}
// Thumbnail grid (admin gallery/media pages).
export function ImageGridSkeleton({ count = 12 }: { count?: number }) {
return (
{Array.from({ length: count }).map((_, i) => (
))}
);
}