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