import { useState, type ComponentType, type ReactNode } from 'react';
import { AlertCircle, Info, Loader2, SearchX, X } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import { cn } from '../../lib/cn';
export type SurfaceIcon = ComponentType<{ size?: number; className?: string }>;
export function PageFrame({
title,
subtitle,
icon: Icon,
eyebrow,
meta,
actions,
children,
maxWidth = 'max-w-6xl',
compactInfo = false,
}: {
title: string;
subtitle?: string;
icon?: SurfaceIcon;
eyebrow?: string;
meta?: ReactNode;
actions?: ReactNode;
children: ReactNode;
maxWidth?: string;
compactInfo?: boolean;
}) {
const [infoOpen, setInfoOpen] = useState(false);
return (
{compactInfo ? (
<>
{Icon ? (
) : null}
{eyebrow ? {eyebrow} : null}
{meta ? {meta} : null}
{title}
{actions ?
{actions}
: null}
{subtitle ? (
) : null}
{infoOpen && subtitle ? (
{subtitle}
) : null}
>
) : (
{Icon ? (
) : null}
{eyebrow ? {eyebrow} : null}
{meta ? {meta} : null}
{title}
{subtitle ?
{subtitle}
: null}
{actions ?
{actions}
: null}
)}
{children}
);
}
export function SurfaceCard({
title,
subtitle,
actions,
children,
className,
}: {
title?: string;
subtitle?: string;
actions?: ReactNode;
children: ReactNode;
className?: string;
}) {
return (
{(title || subtitle || actions) && (
{title ?
{title}
: null}
{subtitle ?
{subtitle}
: null}
{actions ?
{actions}
: null}
)}
{children}
);
}
export function MetricTile({
label,
value,
unit,
helper,
icon: Icon,
tone = 'blue',
}: {
label: string;
value: ReactNode;
unit?: string;
helper?: string;
icon?: SurfaceIcon;
tone?: 'blue' | 'emerald' | 'amber' | 'rose' | 'slate';
}) {
const toneClass = {
blue: 'bg-blue-50 text-blue-600 ring-blue-100',
emerald: 'bg-emerald-50 text-emerald-600 ring-emerald-100',
amber: 'bg-amber-50 text-amber-600 ring-amber-100',
rose: 'bg-rose-50 text-rose-600 ring-rose-100',
slate: 'bg-slate-100 text-slate-600 ring-slate-200',
}[tone];
return (
{label}
{Icon ? (
) : null}
{value}
{unit ? {unit} : null}
{helper ?
{helper}
: null}
);
}
export function SegmentedNav({
tabs,
active,
onChange,
className,
}: {
tabs: readonly { id: T; label: string; icon?: SurfaceIcon }[];
active: T;
onChange: (id: T) => void;
className?: string;
}) {
return (
{tabs.map(({ id, label, icon: Icon }) => {
const isActive = active === id;
return (
);
})}
);
}
export function SkeletonBlock({ className }: { className?: string }) {
return ;
}
export function LoadingState({ label = '数据加载中' }: { label?: string }) {
return (
);
}
export function EmptyState({
title = '暂无数据',
description = '换个筛选条件或稍后再试',
}: {
title?: string;
description?: string;
}) {
return (
);
}
export function ErrorState({ message }: { message: string }) {
return (
);
}
export function FadeIn({ children, className }: { children: ReactNode; className?: string }) {
return (
{children}
);
}