Files
UHD_Media_Tool/apps/web/app/components/ui/button.tsx
T
rootandClaude Opus 5.5 09a2adb637 feat(web): 设计规范与 uhd_web 同步(截至 2026-10-08)
- 材质统一边光:slab / float / pill / pill-selected / seg-thumb / chrome-side 改为左上亮右下暗的 1px 边光 + 外侧暗线;新增 lens / field / chip 参数与 pg-float-modal
- 主按钮:转动光谱环改为静态左上光谱高光,悬停沿边扫过一小段;次按钮同为 lens 玻璃
- Input / Textarea / SelectTrigger 挂 .field-glass,原生下拉与实体选择器同步;对话框按钮行 gap-3
- 提示条改 pg-float(状态色只在图标、展开不叠放);新增 GlassTooltip 接管 title 提示
- 触屏判断 input-modality.ts、touch / hover 自定义变体;animate-pg-pop 改 backwards;scroll-fade-end
- 控制台外壳:侧栏无硬分隔线、折叠开关进标志行;顶栏去磨砂,右侧控件收进玻璃胶囊
- 登录页居中单卡 auth-card;详情头图「订阅」改回普通主按钮,删除 pill-solid;发现页海报角标改 glass-chip-media
- 引入 simple-liquid-glass(liquid-surface.tsx);更新 docs/design 三份规范与 CHANGELOG

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-08 02:29:19 +08:00

69 lines
2.9 KiB
TypeScript

import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils.ts";
const buttonVariants = cva(
// 全站只有这一套按钮(与动漫站同一思路):形状一律胶囊 / 正圆,颜色与材质只由 variant 决定,
// 调用处只给布局(宽度、高度、间距),不改圆角、底色、边框、投影和字色。
// 不是 <button> 的元素(Link、a)用 buttonVariants() 拿同一套类名
"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full text-sm font-medium whitespace-nowrap transition-[transform,background-color,border-color,color,box-shadow,opacity] duration-base ease-spring outline-none active:scale-[0.96] focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
// 主操作:lens 玻璃胶囊 + 左上棱边的光谱高光(静止,悬停沿边扫过一小段)。样式见 app.css 的 .pill-primary(--mat-lens-*、--prism-*)
default: "pill-primary",
// 次操作:lens 玻璃胶囊(边光 + 外侧细线)。outline 与 secondary 是同一个样子,保留两个名字只为兼容旧调用
outline: "pill-secondary",
secondary: "pill-secondary",
// 危险操作:同一胶囊,淡红底红字
destructive:
"border border-destructive/35 bg-destructive/10 text-destructive hover:bg-destructive/15 focus-visible:ring-destructive/25 dark:focus-visible:ring-destructive/40",
// 只有文字 / 图标,悬停出淡底
ghost: "text-muted-foreground hover:bg-foreground/[0.06] hover:text-foreground",
link: "text-primary underline-offset-4 hover:underline active:scale-100",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-11 px-6 has-[>svg]:px-5",
icon: "size-9",
"icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot.Root : "button";
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };