/** * 设计系统 UI 基元(纯展示,无副作用):页头 / 卡片 / 指标卡 / 徽标 / * 按钮 / 表单字段 / 提示条 / 空态 / 骨架 / 进度 / 回测结果区块。 */ import { useId } from "react"; import type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from "react"; import type { BacktestSummary } from "@/lib/types"; import { Icon, type IconName } from "@/components/icons"; export function cx(...parts: Array): string { return parts.filter(Boolean).join(" "); } /* ---------- 页头 ---------- */ export function PageHeader({ title, sub, actions, }: { title: string; sub?: ReactNode; actions?: ReactNode; }) { return (

{title}

{sub ?

{sub}

: null}
{actions ?
{actions}
: null}
); } /* ---------- 卡片 ---------- */ export function Card({ title, sub, icon, tools, children, flush = false, className, }: { title?: ReactNode; sub?: ReactNode; icon?: IconName; tools?: ReactNode; children: ReactNode; flush?: boolean; className?: string; }) { const hasHead = title != null || tools != null; return (
{hasHead ? (
{icon ? : null} {title} {sub ?  {sub} : null}
{tools ?
{tools}
: null}
) : null}
{children}
); } /* ---------- 指标卡 ---------- */ export function Metric({ label, icon, value, sub, tone = "plain", small = false, }: { label: ReactNode; icon?: IconName; value: ReactNode; sub?: ReactNode; tone?: "plain" | "pos" | "neg" | "warn" | "accent"; small?: boolean; }) { return (
{icon ? : null} {label}
{value}
{sub ?
{sub}
: null}
); } /* ---------- 徽标 ---------- */ export function Pill({ tone = "default", children, icon, }: { tone?: "default" | "accent" | "pos" | "neg" | "warn" | "violet"; children: ReactNode; icon?: IconName; }) { return ( {icon ? : null} {children} ); } /* ---------- 按钮 ---------- */ interface BtnProps extends ButtonHTMLAttributes { variant?: "primary" | "ghost" | "danger"; size?: "sm" | "md"; icon?: IconName; loading?: boolean; children?: ReactNode; } export function Btn({ variant = "ghost", size = "md", icon, loading = false, children, className, ...rest }: BtnProps) { return ( ); } /* ---------- 表单 ---------- */ export function Field({ label, hint, htmlFor, children, className, }: { label: ReactNode; hint?: ReactNode; htmlFor?: string; children: ReactNode; className?: string; }) { return (
{children} {hint ? {hint} : null}
); } interface TextInputProps extends InputHTMLAttributes { mono?: boolean; } /** 统一样式的 input(含原生控件),mono 用于代码/数值输入。 */ export function TextInput({ className, mono, ...rest }: TextInputProps) { return ; } export function SearchBox({ value, onChange, onClear, placeholder, label, }: { value: string; onChange: (v: string) => void; onClear?: () => void; placeholder?: string; label?: string; }) { const inputId = useId(); return (
onChange(e.target.value)} /> {value && onClear ? ( ) : null}
); } /* ---------- 提示条 / 空态 / 加载 ---------- */ export function Banner({ tone = "info", children, icon, }: { tone?: "error" | "warn" | "info" | "muted"; children: ReactNode; icon?: IconName; }) { const ic: IconName = icon ?? (tone === "error" || tone === "warn" ? "alert" : tone === "muted" ? "info" : "info"); return (
{children}
); } export function Empty({ icon = "archive", title, hint, children, }: { icon?: IconName; title: ReactNode; hint?: ReactNode; children?: ReactNode; }) { return (
{title} {hint ?

{hint}

: null} {children ?
{children}
: null}
); } export function SkeletonLines({ n = 3, height = 14 }: { n?: number; height?: number }) { return (