Files
UHD_Media_Tool/docs/design/ui-controls.md
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

7.2 KiB

控件使用标准

迁入自 /opt/uhd_web docs/features/(2026-09-27),2026-10-08 与 uhd_web 同步。文中流媒体、播放器、落地页、套餐等段落是 uhd_web 的原始上下文,本项目没有这些页面,只作材质与 token 用法参考;以本目录 README 的约定为准。

配合设计 token与界面材质。开发态的陈列页 /dev/ui(只在开发构建注册,routes.ts)把三块壳下的 token、材质和控件排在一页,换皮或改 token 先看它。

按钮只有一套

全站按钮(门户、公共页、测速、流媒体、播放器浮层)是同一套外观,与动漫站同一思路:形状一律胶囊或正圆,颜色与材质只由角色决定。

角色 门户写法 流媒体写法 外观
主操作 <Button> btn-glass btn-glass-primary lens 玻璃胶囊 + 左上棱边的光谱高光(静止,悬停沿边扫过一小段),不做实心
次操作 <Button variant="outline">(secondary 同样子) btn-glass lens 玻璃胶囊(边光 + 外侧细线)
图标钮 <Button variant="outline" size="icon*"> btn-glass-icon lens 玻璃正圆
只有文字 / 图标 <Button variant="ghost"> 各自的 ghost 行 悬停出淡底
危险操作 <Button variant="destructive"> — 淡红底红字胶囊

一屏(或一张卡片)只放一个主操作,并列的其余动作退为描边;前置条件没满足时,真正该做的那一步是主操作(例如团队页没有团队套餐时,「创建团队套餐」是主操作,「添加成员」退为描边)。

两边的底、边、字色、投影都引用 --mat-lens-* / --mat-pill-*,改外观只改参数。弹窗底部的按钮行间距 12px(gap-3):按钮带外侧细线与光谱高光,8px 会挤在一起。放进玻璃胶囊里的按钮(控制台顶栏的通知、日夜切换、账户)用 ghost,不再自带玻璃。调用处只给布局(宽度、高度、间距、定位),不改圆角、底色、边框、投影和字色;不是 <button> 的元素(Link、a)用 buttonVariants() 拿同一套类名。分段标签、菜单行、列表行不算按钮,按各自控件规范;分页当前页、角标、选中的季 / 集 / 选项、日历选中日用静态的 pill-selected,不借主按钮样式;分段控件(侧栏、SegmentedTabs、求片页的分段)的滑块用 seg-thumb。放胶囊或分段控件的横向滚动容器加 scroll-bleed:overflow-x-auto 会连带裁掉纵向溢出,不加的话胶囊的阴影和描边会被切成直边。

右上角账户模块全站共用 account-popover.tsx:用户中心用头像 + 名字的胶囊,流媒体与测速用圆头像;菜单里是另外两块的跳转(当前所在的不列,流媒体与测速需有生效订阅)与退出登录。

其余控件仍分两处实现:门户原语在 app/components/ui/(shadcn 语义 token),流媒体控件在 app/routes/stream/_sub/、app/routes/stream/player/controls.tsx(浏览页 content / wash,播放器 on-media 族)。需要相同行为时各自实现。

尺寸与圆角

  • 图标钮:桌面 36px,触屏 40px(touch: 变体,见下文「触屏判断」);播放键等主钮 44 / 48。
  • 圆角角色:控件 rounded-md、小按钮 rounded-lg、输入框与下拉选择 rounded-xl(由 .field-glass 的原语给)、卡片 rounded-xl、面板 rounded-2xl、抽屉与大卡 rounded-3xl、药丸 rounded-full、徽章与骨架屏 rounded-tight。
  • 字号:控件文字 text-control(15),菜单项 text-body / text-caption,面板小标签 text-label 大写 tracking-[0.18em]。

状态反馈

  • hover:洗层 hover:bg-wash/[0.06](播放器 on-media-wash/[0.07]),文字提一档(secondary → strong)。
  • 按压:active:scale-[0.95](圆形主钮 0.92),transition-[transform,background-color,color] duration-micro。
  • 选中:pill-selected(静态半透明底 + 稍深描边);单选列表项左侧 Check 标记。进度用细线表示,不做实心填充。
  • 打开中的触发钮:bg-wash/[0.08] 保持高亮。
  • 焦点:门户走 shadcn 的 focus-visible:ring,玻璃族 focus-visible:ring-2 ring-brand/50。
  • 禁用:opacity-50 cursor-not-allowed,不改颜色。
  • 危险:移除、删除这类动作平时是中性描边,悬停时换成 destructive 的边框与文字。

动效

duration-micro 微反馈、duration-base 颜色透明度、duration-layout 尺寸位置、duration-slow 及以上只给入场与装饰;缓动默认 ease-standard,滑块与按压回弹 ease-spring,抽屉 ease-sheet,弹层 animate-pg-pop。尊重 reduced motion。

大写小标签

SectionLabel(section-label.tsx),三种口味:portal(门户表单字段名与分组,text-xs font-semibold uppercase text-muted-foreground)、page(浏览页,text-label uppercase tracking-[0.18em] text-content-muted)、media(播放器弹层,同上换 on-media)。as 指定元素(label / span / p)。

还有几种写法没收进来,值不同、收进来就是像素变化,待设计决定:门户里 text-xs font-medium uppercase tracking-wider(团队订单弹窗)、text-micro … tracking-[0.15em](侧栏分组、邀请页)、tracking-widest 一处;落地页 hero 的 tracking-[0.32em] text-white/45 与分区标题的 text-meta font-bold tracking-[.22em] text-brand-strong 是刻意的装饰用法,单列为 hero kicker 与 section kicker。

弹层结构

触发钮 + 面板,面板由 usePopover 管开关(点外面 / Esc 关闭)。面板顶部一行大写小标签说明这是什么,下面是列表;列表项分两种:单选行(左勾选 + 标签 + 右侧提示)与动作行(左图标 + 文字)。四件在 player/controls.tsx:MenuTrigger、MenuPanel、MenuOption、MenuButton。MenuPanel 通吃三处:placement 上方(播放器)/ 下方(浏览页 header),tone 决定表头口味,role 给 listbox,内边距、宽度、z 与偏移经 className 传入,tailwind-merge 让它们覆盖默认值,所以三处的 class 集合与各自收拢前完全一致(用 react-dom/server 渲出来逐项核对过)。账户菜单的头像表头与排序菜单的单选行样式不同,仍各自写在页面里。

收拢原则

  • 同一份 class 串出现在两处以上就抽原语,抽的时候 class 串逐字不改,渲染不变。
  • 参数只到必要:尺寸档、是否激活、标签、图标;不把整页状态传给控件。
  • 只有一处用的局部组件留在页面文件里,不为了"以后复用"提前抽。
  • 两个页面展示同一种信息时共用零件:套餐卡用 plan-card-parts.tsx(名称价格、规格表、线路提醒、共有权益),电影与剧集详情用 DetailExtras.tsx(规格徽章、影片信息、可播放版本、更多推荐)。

触屏判断

触屏还是鼠标由 input-modality.ts 统一判断,写在 <html data-input> 上:样式用 touch: 变体(代替 [@media(pointer:coarse)]:),组件用 useTouchInput(),处理指针事件用 pointerKind(e),不要直接比较 pointerType。车机、触屏一体机常把触屏报告成鼠标,只看 (pointer: coarse) 会按电脑处理。hover: 在触屏模式下不生效,触屏上必须能用的操作不要只靠悬停出现。界面上不给用户选;排障时可在地址加 ?input=touch|mouse|auto 手动指定。