/** * UI 基元层 —— 全站唯一的基础交互组件来源。 * * 背景:此前只有业务级组件(Card/DataTable 等),基础交互控件全靠各页面 * 手写 className + `.btn` / `.field` 等 CSS 类维持一致。结果是: * - `btn-ghost` 被使用但从未定义,样式静默失效(与早年的 btn-outline 同型) * - Spinner 在前台/后台各复制一份 * - 弹窗焦点陷阱只在一个弹窗里有,另一个缺 * 组件化的意义在于「让错误在编译期暴露」,而不是靠人工守纪律。 * * 设计约束:视觉输出与迁移前逐字一致(使用同一批 CSS 类), * 本层只做「收口 + 类型约束」,不改设计语言。 */ import { forwardRef, useEffect, useRef } from 'react' import type { ButtonHTMLAttributes, InputHTMLAttributes, SelectHTMLAttributes, ReactNode, MouseEventHandler, } from 'react' // ── 工具:CSS 类名拼接 ──────────────────────────────────────────── export function cx(...parts: Array): string { return parts.filter(Boolean).join(' ') } // ── Button ─────────────────────────────────────────────────────── /** * 按钮变体。用联合类型约束,写错变体名会在编译期报错 * —— 这是根治 `btn-ghost` 那类「幻影变体」的关键。 */ export type ButtonVariant = 'default' | 'solid' | 'outline' | 'danger' | 'ghost' export type ButtonSize = 'default' | 'sm' const VARIANT_CLASS: Record = { default: '', solid: 'btn-solid', outline: 'btn-outline', danger: 'btn-danger', ghost: 'btn-ghost', } const SIZE_CLASS: Record = { default: '', sm: 'btn-sm', } export interface ButtonProps extends ButtonHTMLAttributes { variant?: ButtonVariant size?: ButtonSize /** 显示加载态:自动禁用并前置 Spinner */ loading?: boolean /** 占满父容器宽度 */ block?: boolean /** * 传入时渲染为 `` 而非 ` ) }) // ── Input / Select ─────────────────────────────────────────────── /** * 输入框基元:统一 `.field` 方角样式。 * * 无障碍提醒:请为输入框提供可访问名称 —— 要么传 `id` 并在外部配 * `