/**
* 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}
);
}