/** * Loading / empty-state feedback components (Part 3 polish primitives). * * PanelSkeleton replaces spinner-style loading with content-shaped skeletons; * IllustratedEmpty renders a small inline-SVG illustration + a "how to * produce data" action so empty screens teach instead of dead-ending. */ import type { ReactNode } from 'react'; import Box from '@mui/material/Box'; import Skeleton from '@mui/material/Skeleton'; import Typography from '@mui/material/Typography'; import { tokens } from '../../theme'; // ---------------------------------------------------------------- skeletons export function PanelSkeleton({ rows = 4, height = 22, header = true }: { rows?: number; height?: number; header?: boolean }) { return ( {header ? : null} {Array.from({ length: rows }, (_, i) => ( ))} ); } export function ChartSkeleton({ height = 240 }: { height?: number }) { return ( {[0.5, 0.8, 0.35, 0.65, 0.9, 0.45, 0.7, 0.55].map((f, i) => ( ))} ); } export function TileSkeleton({ n = 4 }: { n?: number }) { return ( {Array.from({ length: n }, (_, i) => ( ))} ); } // ---------------------------------------------------------------- empty states type EmptyArt = 'data' | 'search' | 'canvas' | 'gauge' | 'map'; function Art({ variant }: { variant: EmptyArt }) { const stroke = tokens.color.textFaint; const accent = tokens.color.info; const common = { fill: 'none', strokeWidth: 1.6, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const }; switch (variant) { case 'search': return ( ); case 'canvas': return ( ); case 'gauge': return ( ); case 'map': return ( ); case 'data': default: return ( ); } } export function IllustratedEmpty({ art = 'data', title, message, action, }: { art?: EmptyArt; title: string; message: string; action?: ReactNode; }) { return ( {title} {message} {action ? {action} : null} ); }