feat(ui): 自研 Celadon 组件族入库与 AntD 消费全量替换 - base(Kbd/StatusBadge/IconButton/ToolButton)+product(EmptyState/Callout/SegmentedControl/Checkbox/Select/Tooltip/Switch), composio 视觉基底, globals.css 覆盖清理 (ADR-0008)

This commit is contained in:
maomaochong-ai
2026-09-06 20:28:51 +08:00
parent 0dcd8e1c72
commit c3e2758736
128 changed files with 2791 additions and 737 deletions

View File

@@ -0,0 +1,114 @@
import type { ButtonHTMLAttributes, ReactNode } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* ToolButton — 工具激活态按钮(持久切换,可展开面板)。
*
* 用途:工具类按钮——画布工具 Dock、面板工具组:被按下时保持高亮(aria-pressed),
* 或展开浮层面板(expands → aria-expanded)。active 持久态与 hover/按压态区分
* 明显,配合画布 ToolDefinition(active/expands/disabled 谓词)渲染。
* token:active 态 bg-surface-active + text-foreground,常态 bg-transparent +
* text-muted-foreground,hover bg-surface-hover;焦点环 ring-ring;
* danger 态激活文字用 text-status-error。间距由 size 档位控制。
* 图标契约:`icon` 传 ReactNode(内联 JSX 或组件元素),与画布 tool-registry 的
* ToolDefinition.icon 字段一致(icons 是内联 JSX 如 <Undo2 />);尺寸由本组件
* [&_svg] 规则统一控制。禁传 icon className 改尺寸。
* 对标:接管画布工具 Dock 中按钮渲染(aria-pressed 语义);AntD Tooltip+Button
* 组合的工具按钮场景。参考 BoardUI tool 切换语义。
* 明暗:全部颜色走 token;active 用按压底语义 token,明暗自动适配。
*/
export const toolButtonVariants = cva(
[
"inline-flex select-none items-center justify-center gap-1.5 rounded-md",
"font-medium transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-45",
],
{
variants: {
variant: {
// 工具条/面板内常规(常态透明,激活常驻底)
ghost: "text-muted-foreground hover:bg-surface-hover hover:text-foreground",
// 常驻浅底(面板内分组工具)
default: "bg-surface-secondary text-muted-foreground hover:bg-surface-hover hover:text-foreground",
},
size: {
xs: "h-6 px-1.5 text-tiny [&_svg]:size-3.5",
sm: "h-7 px-2 text-caption [&_svg]:size-4",
default: "h-8 px-2.5 text-caption [&_svg]:size-4",
lg: "h-9 px-3 text-body [&_svg]:size-[18px]",
},
tone: {
default: "",
danger: "data-[active=true]:text-status-error",
},
active: {
true: "",
false: "",
},
},
compoundVariants: [
{
variant: ["ghost", "default"],
active: true,
className: "bg-surface-active text-foreground hover:text-foreground",
},
],
defaultVariants: {
variant: "ghost",
size: "default",
tone: "default",
active: false,
},
},
);
export type ToolButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof toolButtonVariants> & {
/** 图标内容:ReactNode(内联 JSX,与 tool-registry ToolDefinition.icon 对齐) */
icon?: ReactNode;
/** 持久激活态(映射 aria-pressed=true) */
active?: boolean;
/** 面板展开型工具:active 走 aria-expanded 语义而非 pressed */
expands?: boolean;
loading?: boolean;
/** 显式文本标签(提供时按带标签布局渲染) */
label?: ReactNode;
};
export function ToolButton({
variant,
size,
tone,
icon,
active = false,
expands = false,
loading = false,
label,
className,
type = "button",
disabled,
...props
}: ToolButtonProps) {
const pressed = !expands && active;
const expanded = expands && active;
return (
<button
data-slot="tool-button"
type={type}
disabled={disabled || loading}
aria-pressed={pressed || undefined}
aria-expanded={expanded || undefined}
aria-busy={loading || undefined}
data-active={active || undefined}
className={cn(toolButtonVariants({ variant, size, tone, active }), className)}
{...props}
>
{loading ? <Loader2 aria-hidden className="animate-spin" /> : icon}
{label != null ? <span>{label}</span> : null}
</button>
);
}