import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react"; /** Miuix-style primitives: Button / Tag / Switch / Spinner / Surface / Empty. * Springs are applied only to interactive elements (cubic-bezier(0.34,1.56,0.64,1)). */ type ButtonVariant = "primary" | "soft" | "ghost" | "outline" | "danger"; export interface ButtonProps extends ButtonHTMLAttributes { variant?: ButtonVariant; size?: "sm" | "md"; children: ReactNode; } const buttonVariant: Record = { primary: "bg-[var(--mu-accent)] text-white hover:bg-[var(--mu-accent-hi)] shadow-[var(--mu-shadow-2)]", soft: "bg-[var(--mu-surface-4)] text-[var(--mu-text-hi)] hover:bg-[var(--mu-surface-5)]", ghost: "text-[var(--mu-text-mid)] hover:bg-[var(--mu-surface-4)]", outline: "border border-[var(--mu-glass-border)] text-[var(--mu-text-hi)] hover:bg-[var(--mu-surface-4)]", danger: "bg-[var(--mu-err)] text-white", }; export function Button({ variant = "soft", size = "md", className = "", children, ...rest }: ButtonProps): React.ReactNode { const pad = size === "sm" ? "px-2.5 py-1 text-xs" : "px-4 py-2 text-sm"; return ( ); } export type TagTone = "accent" | "ok" | "warn" | "err" | "neutral"; export interface TagProps { tone?: TagTone; children: ReactNode; className?: string; /** 原生 title:给标签补充「为什么」的悬停说明(如无 id 条目的后果)。 */ title?: string; } const tagTone: Record = { accent: "bg-[var(--mu-accent)]/15 text-[var(--mu-accent-hi)]", ok: "bg-[var(--mu-ok)]/15 text-[var(--mu-ok)]", warn: "bg-[var(--mu-warn)]/15 text-[var(--mu-warn)]", err: "bg-[var(--mu-err)]/15 text-[var(--mu-err)]", neutral: "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]", }; export function Tag({ tone = "neutral", children, className = "", title }: TagProps): React.ReactNode { return ( {children} ); } export interface SwitchProps { checked: boolean; onChange: (next: boolean) => void; disabled?: boolean; label?: string; } export function Switch({ checked, onChange, disabled, label }: SwitchProps): React.ReactNode { return ( ); } export interface SpinnerProps { size?: number; label?: string; } export function Spinner({ size = 18, label }: SpinnerProps): React.ReactNode { return ( {label ? {label} : null} ); } export interface SurfaceProps extends HTMLAttributes { glass?: boolean; liquid?: boolean; gap?: "none" | "sm" | "md"; children: ReactNode; } export function Surface({ glass = false, liquid = false, gap = "md", className = "", children, ...rest }: SurfaceProps): React.ReactNode { const pad = gap === "none" ? "" : gap === "sm" ? "p-3" : "p-5"; const base = glass ? "glass" : liquid ? "liquid-glass" : "bg-[var(--mu-surface-3)] border border-[var(--mu-glass-border)] shadow-[var(--mu-shadow-1)]"; return (
{children}
); } export interface EmptyStateProps { icon?: ReactNode; title: string; hint?: string; } export function EmptyState({ icon, title, hint }: EmptyStateProps): React.ReactNode { return (
{icon ?
{icon}
: null}
{title}
{hint ?
{hint}
: null}
); }