Files
dudu/design/index.html
T
wangjia 911ceca079
ci / server (push) Failing after 11s
ci / design-system (push) Failing after 12s
feat(design+desktop): Select 真相源组件 + 设置窗口无边框(12A 实测反馈)
Select 组件(用户拍板:下拉框全端共用一个真相源):
- design/components/forms/Select.jsx:自绘下拉——触发器同 Input
  (control-sm/radius-sm/border-1 + chevronDown),弹层同菜单
  (radius-md/shadow-menu、选中项前置对勾、hover surface-2),
  外点/Esc 收起;登记簿加卡;icons.js 新增 chevronDown 并登记 icon-map
- 原型 SettingsTray 与桌面 SettingsApp 同步换用(替换原生 select——
  其系统弹层样式/位置不可控,即 12A 截图问题)

原型组件运行时可再生(修复单源缺口):
- design-pipeline/bundle-components.mjs:用 desktop 现成 esbuild 重建
  _ds_bundle.js(此前为 Claude 导出产物、仓库内无法再生,组件一改即
  过期);import 'react' 经全局 shim,命名空间不变;已实测原型页正常
- _ds_bundle.js 重建(15 个组件,含 Select/Icon)

设置窗口无边框:
- tauri.conf settings 窗口 titleBarStyle=Overlay + hiddenTitle,
  系统红绿灯悬浮左上;SettingsApp 自绘顶条(surface-2 + 下边框 +
  data-tauri-drag-region 可拖拽),对齐原型 MacWindow 规格

合并 feat/design-truth-source(真相源与闸体系)进本分支

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 01:00:29 +08:00

215 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dudu 设计系统 · 登记簿</title>
<link rel="stylesheet" href="./styles.css">
<style>
/* 登记簿版式(色值一律 var(--*),本页也受 check-ds 闸约束) */
body { margin: 0; background: var(--bg-app); color: var(--text-1); font-family: var(--font-sans); line-height: var(--leading-normal); }
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px 80px; }
header.hero { padding: 40px 0 8px; display: flex; align-items: center; justify-content: space-between; }
header.hero h1 { font-size: var(--text-2xl); margin: 0; }
header.hero p { color: var(--text-2); margin: 4px 0 0; font-size: var(--text-sm); }
.theme-btn { height: var(--control-sm); padding: 0 14px; border-radius: var(--radius-sm); border: 1px solid var(--border-2); background: var(--surface-card); color: var(--text-1); cursor: pointer; font-size: var(--text-sm); }
section { background: var(--surface-card); border: 1px solid var(--border-1); border-radius: var(--radius-lg); padding: 22px 26px; margin-top: 20px; box-shadow: var(--shadow-card); }
h2 { font-size: var(--text-lg); margin: 0 0 12px; }
h3 { font-size: var(--text-base); margin: 18px 0 8px; color: var(--accent-text); }
.rule { border-left: 3px solid var(--accent); background: var(--accent-soft); padding: 8px 14px; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; font-size: var(--text-sm); margin: 8px 0; }
.sw-row { display: flex; flex-wrap: wrap; gap: 8px; }
.sw { width: 88px; font-size: var(--text-xs); color: var(--text-2); }
.sw i { display: block; height: 40px; border-radius: var(--radius-sm); border: 1px solid var(--border-1); margin-bottom: 3px; }
table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); margin-top: 8px; }
th, td { border: 1px solid var(--border-1); padding: 6px 10px; text-align: left; }
th { background: var(--surface-2); }
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
code { background: var(--surface-2); padding: 1px 6px; border-radius: var(--radius-xs); font-family: var(--font-mono); font-size: var(--text-xs); }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 10px; }
.entry { border: 1px solid var(--border-1); border-radius: var(--radius-md); padding: 12px 14px; }
.entry b { font-size: var(--text-base); }
.entry .sub { color: var(--text-2); font-size: var(--text-xs); margin: 3px 0 6px; }
.entry .meta { font-size: var(--text-xs); color: var(--text-3); }
#icon-grid { display: flex; flex-wrap: wrap; gap: 14px; }
#icon-grid .ic { width: 96px; text-align: center; font-size: var(--text-xs); color: var(--text-2); }
#icon-grid .ic .box { height: 48px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--border-1); border-radius: var(--radius-sm); margin-bottom: 4px; color: var(--text-1); }
.note { color: var(--text-3); font-size: var(--text-xs); }
</style>
</head>
<body>
<div class="wrap">
<header class="hero">
<div>
<h1>dudu 设计系统 · 登记簿</h1>
<p>单一真相源活文档 —— 所有 token / 组件 / 图标必须在本页登记(check-ds 闸强制)。评审预览:<code>node design/serve.mjs</code></p>
</div>
<button class="theme-btn" onclick="const r=document.documentElement; r.dataset.theme = r.dataset.theme==='dark'?'':'dark'">切换 Light / Dark</button>
</header>
<section>
<h2>规则速览(L1 · 设计元素单源)</h2>
<div class="rule">新增<b>颜色/字号/间距</b>:先登记 <code>tokens/*.css</code><code>node design-pipeline/export-tokens.mjs</code> 再生成各端 → 业务代码只引 token。</div>
<div class="rule">新增<b>组件</b>:先建 <code>components/&lt;&gt;/X.jsx</code>(+ .d.ts)并在本页「组件登记」加卡,再到各端实现。</div>
<div class="rule">新增<b>图标</b>:先登记 <code>icons.js</code>Lucide 24 网格 2px 圆头)+ <code>icon-map.json</code> 跨端映射,业务代码禁止内联 &lt;path&gt;</div>
<div class="rule">违规谁拦:<code>design-pipeline/check-ds.mjs</code>(真相源自检)/ <code>check-code.mjs</code>(代码侧单源)/ <code>export-tokens.mjs --check</code>(产物零 diff),pre-commit 与 CI 双挂。</div>
<h3>各端消费方式</h3>
<table>
<tr><th></th><th>消费方式</th><th>令牌文件</th></tr>
<tr><td>桌面(Tauri web 层)</td><td>vite alias <code>@dudu/design</code> 直引 CSS 变量与组件</td><td><code>design/tokens/*.css</code>(源)</td></tr>
<tr><td>iOSSwiftUI</td><td><code>DuduTheme.*</code> 常量(codegen 直写)</td><td><code>ios/dudu/Shared/DuduTheme.swift</code></td></tr>
<tr><td>AndroidCompose</td><td><code>DuduTheme.Light/Dark</code><code>DuduPalette</code> 注入</td><td><code>android/.../design/DuduTheme.kt</code></td></tr>
<tr><td>官网(web/</td><td><code>&lt;link href="tokens.css"&gt;</code>,页面只允许 var(--*)</td><td><code>web/tokens.css</code>codegen,含 Outfit 内嵌)</td></tr>
</table>
</section>
<section>
<h2>Tokens 色板</h2>
<p class="note">色块直接渲染 var(--token),与 tokens/colors.css 永远同步;右上角切主题查看 dark 值。完整定义见 <a href="./tokens/colors.css">colors.css</a> · <a href="./tokens/typography.css">typography.css</a> · <a href="./tokens/spacing.css">spacing.css</a> · <a href="./tokens/effects.css">effects.css</a> · <a href="./tokens/fonts.css">fonts.css</a></p>
<h3>品牌蓝阶梯</h3>
<div class="sw-row">
<div class="sw"><i style="background:var(--blue-50)"></i>blue-50</div>
<div class="sw"><i style="background:var(--blue-100)"></i>blue-100</div>
<div class="sw"><i style="background:var(--blue-200)"></i>blue-200</div>
<div class="sw"><i style="background:var(--blue-300)"></i>blue-300</div>
<div class="sw"><i style="background:var(--blue-400)"></i>blue-400</div>
<div class="sw"><i style="background:var(--blue-500)"></i>blue-500 ★</div>
<div class="sw"><i style="background:var(--blue-600)"></i>blue-600</div>
<div class="sw"><i style="background:var(--blue-700)"></i>blue-700</div>
<div class="sw"><i style="background:var(--blue-800)"></i>blue-800</div>
<div class="sw"><i style="background:var(--blue-900)"></i>blue-900</div>
</div>
<h3>中性阶梯(冷灰)</h3>
<div class="sw-row">
<div class="sw"><i style="background:var(--gray-0)"></i>gray-0</div>
<div class="sw"><i style="background:var(--gray-25)"></i>gray-25</div>
<div class="sw"><i style="background:var(--gray-50)"></i>gray-50</div>
<div class="sw"><i style="background:var(--gray-100)"></i>gray-100</div>
<div class="sw"><i style="background:var(--gray-200)"></i>gray-200</div>
<div class="sw"><i style="background:var(--gray-300)"></i>gray-300</div>
<div class="sw"><i style="background:var(--gray-400)"></i>gray-400</div>
<div class="sw"><i style="background:var(--gray-500)"></i>gray-500</div>
<div class="sw"><i style="background:var(--gray-600)"></i>gray-600</div>
<div class="sw"><i style="background:var(--gray-700)"></i>gray-700</div>
<div class="sw"><i style="background:var(--gray-800)"></i>gray-800</div>
<div class="sw"><i style="background:var(--gray-900)"></i>gray-900</div>
<div class="sw"><i style="background:var(--gray-950)"></i>gray-950</div>
</div>
<h3>语义与功能</h3>
<div class="sw-row">
<div class="sw"><i style="background:var(--accent)"></i>accent</div>
<div class="sw"><i style="background:var(--accent-hover)"></i>accent-hover</div>
<div class="sw"><i style="background:var(--accent-press)"></i>accent-press</div>
<div class="sw"><i style="background:var(--accent-soft)"></i>accent-soft</div>
<div class="sw"><i style="background:var(--positive)"></i>positive</div>
<div class="sw"><i style="background:var(--warning)"></i>warning</div>
<div class="sw"><i style="background:var(--danger)"></i>danger</div>
<div class="sw"><i style="background:var(--bg-app)"></i>bg-app</div>
<div class="sw"><i style="background:var(--surface-card)"></i>surface-card</div>
<div class="sw"><i style="background:var(--surface-2)"></i>surface-2</div>
<div class="sw"><i style="background:var(--surface-3)"></i>surface-3</div>
<div class="sw"><i style="background:var(--border-1)"></i>border-1</div>
</div>
<h3>浮层与第三方品牌</h3>
<div class="sw-row">
<div class="sw"><i style="background:var(--overlay-bg)"></i>overlay-bg</div>
<div class="sw"><i style="background:var(--overlay-text)"></i>overlay-text</div>
<div class="sw"><i style="background:var(--overlay-text-2)"></i>overlay-text-2</div>
<div class="sw"><i style="background:var(--overlay-text-3)"></i>overlay-text-3</div>
<div class="sw"><i style="background:var(--overlay-wave)"></i>overlay-wave</div>
<div class="sw"><i style="background:var(--brand-wechat)"></i>brand-wechat</div>
<div class="sw"><i style="background:var(--brand-wechat-press)"></i>brand-wechat-press</div>
</div>
</section>
<section>
<h2>组件登记</h2>
<p class="note">每个 <code>components/**/*.jsx</code> 组件必须在此有登记卡(check-ds 第④道)。演示卡:<a href="./components/core/core.card.html">core</a> · <a href="./components/forms/forms.card.html">forms</a> · <a href="./components/voice/voice.card.html">voice</a></p>
<h3>core</h3>
<div class="grid">
<div class="entry"><b>Button</b><div class="sub">主/次/幽灵/危险按钮,三种尺寸</div><div class="meta">variant · size · block — components/core/Button.jsx</div></div>
<div class="entry"><b>IconButton</b><div class="sub">方形图标按钮</div><div class="meta">size · label(无障碍必填)— components/core/IconButton.jsx</div></div>
<div class="entry"><b>Badge</b><div class="sub">状态徽标</div><div class="meta">tone: green/blue/orange/red/neutral — components/core/Badge.jsx</div></div>
<div class="entry"><b>Kbd</b> / <b>HotkeyCombo</b><div class="sub">键帽与快捷键组合(⌘ ⇧ Space</div><div class="meta">keys=['⌘','⇧','Space'] — components/core/Kbd.jsx</div></div>
<div class="entry"><b>Card</b><div class="sub">标准卡片容器</div><div class="meta">padding — components/core/Card.jsx</div></div>
<div class="entry"><b>ProgressBar</b><div class="sub">用量进度条,超阈自动警告色</div><div class="meta">value/max · warnAt=0.9 — components/core/ProgressBar.jsx</div></div>
<div class="entry"><b>Icon</b><div class="sub">icons.js 单源图标渲染器</div><div class="meta">name · size · sw — components/core/Icon.jsx</div></div>
</div>
<h3>forms</h3>
<div class="grid">
<div class="entry"><b>Input</b><div class="sub">文本输入框</div><div class="meta">size: md/lg — components/forms/Input.jsx</div></div>
<div class="entry"><b>Switch</b><div class="sub">开关</div><div class="meta">checked · onChange — components/forms/Switch.jsx</div></div>
<div class="entry"><b>SettingRow</b><div class="sub">设置行(标签 + 描述 + 右侧控件)</div><div class="meta">label · description — components/forms/SettingRow.jsx</div></div>
<div class="entry"><b>Select</b><div class="sub">自绘下拉选择器:触发器同 Input,弹层同菜单(radius-md + shadow-menu),选中项前置对勾。全端下拉外观基准,禁用原生 select</div><div class="meta">value · onChange · options · placeholder — components/forms/Select.jsx</div></div>
</div>
<h3>voice</h3>
<div class="grid">
<div class="entry"><b>RecognitionOverlay</b><div class="sub">桌面识别浮层 · 聆听/流式两态</div><div class="meta">state · text — components/voice/RecognitionOverlay.jsx</div></div>
<div class="entry"><b>MicBar</b><div class="sub">移动端按住说话 · 四种状态</div><div class="meta">state — components/voice/MicBar.jsx</div></div>
<div class="entry"><b>Waveform</b><div class="sub">波形条(唯一循环动画,仅录音中)</div><div class="meta">active · bars · height · color — components/voice/Waveform.jsx</div></div>
</div>
</section>
<section>
<h2>图标登记(icons.js + icon-map.json</h2>
<p class="note">Lucide 线条 · 24 网格 · 2px 圆头 · currentColor。下方由 <code>icons.js</code> 实时渲染(需经 serve.mjs 访问;file:// 打开时仅显示映射表)。</p>
<div id="icon-grid"></div>
<div id="icon-map-table"></div>
</section>
<section>
<h2>Guidelines 规范卡</h2>
<div class="grid">
<div class="entry"><b><a href="./guidelines/brand-logo.card.html">Logo</a></b><div class="sub">圆形嘴巴 + 声波线条 · 线性风格</div></div>
<div class="entry"><b><a href="./guidelines/brand-wordmark.card.html">Wordmark</a></b><div class="sub">dudu 几何线条字标</div></div>
<div class="entry"><b><a href="./guidelines/colors-brand.card.html">品牌蓝阶梯</a></b><div class="sub">blue-50 → 900</div></div>
<div class="entry"><b><a href="./guidelines/colors-neutrals.card.html">中性阶梯</a></b><div class="sub">gray-0 → 950 冷灰</div></div>
<div class="entry"><b><a href="./guidelines/colors-surfaces.card.html">表面与文字</a></b><div class="sub">bg → surface → textLight/Dark</div></div>
<div class="entry"><b><a href="./guidelines/colors-semantic.card.html">功能色</a></b><div class="sub">绿=录音/成功 · 琥珀=警告 · 红=错误</div></div>
<div class="entry"><b><a href="./guidelines/colors-overlay.card.html">识别浮层色</a></b><div class="sub">双模式深色玻璃</div></div>
<div class="entry"><b><a href="./guidelines/type-families.card.html">字体家族</a></b><div class="sub">Outfit(自托管)+ 系统中文栈</div></div>
<div class="entry"><b><a href="./guidelines/type-scale.card.html">字号阶梯</a></b><div class="sub">12 → 44</div></div>
<div class="entry"><b><a href="./guidelines/type-recognition.card.html">识别文本规范</a></b><div class="sub">final 实色 · partial 弱色</div></div>
<div class="entry"><b><a href="./guidelines/spacing-grid.card.html">间距网格</a></b><div class="sub">4px 基准</div></div>
<div class="entry"><b><a href="./guidelines/spacing-radius.card.html">圆角阶梯</a></b><div class="sub">xs 4 → full 999</div></div>
<div class="entry"><b><a href="./guidelines/spacing-controls.card.html">控件高度</a></b><div class="sub">桌面 28/36 · 移动 44/56</div></div>
<div class="entry"><b><a href="./guidelines/spacing-elevation.card.html">层级与阴影</a></b><div class="sub">card → menu → window → overlay</div></div>
<div class="entry"><b><a href="./guidelines/motion.card.html">动效</a></b><div class="sub">快入慢出 · 120/200/320ms · 无弹跳</div></div>
</div>
</section>
<section>
<h2>UI Kits(屏级原型)</h2>
<div class="grid">
<div class="entry"><b><a href="./ui_kits/desktop/index.html">桌面端 UI Kit</a></b><div class="sub">识别浮层 · 托盘菜单 · 设置 · 登录购买(可交互,Light/Dark)</div></div>
<div class="entry"><b><a href="./ui_kits/mobile/index.html">移动端 UI Kit</a></b><div class="sub">语音键盘(按住说话)+ 主 App 四屏(可交互,Light/Dark</div></div>
</div>
<p class="note">已知滞后:ui_kits 预览页仍经 unpkg CDN 加载 React/Babel,离线/无代理网络下不可用(治理项见 doc/frontend-overview.html)。</p>
</section>
<script type="module">
// 图标区:从单源渲染(serve.mjs 下可用;file:// 因模块 CORS 限制自动降级为仅映射表)
try {
const { icons } = await import('./icons.js');
const map = await (await fetch('./icon-map.json')).json();
const grid = document.getElementById('icon-grid');
grid.innerHTML = Object.entries(icons).map(([k, body]) =>
`<div class="ic"><div class="box"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${body}</svg></div>${k}</div>`
).join('');
const rows = Object.entries(map).filter(([k]) => k !== '$schema').map(([k, v]) =>
`<tr><td><code>${k}</code></td><td>${v.web ?? '—'}</td><td>${v.ios ?? '—'}</td><td>${v.android ?? '—'}</td></tr>`
).join('');
document.getElementById('icon-map-table').innerHTML =
`<table><tr><th>语义名</th><th>webicons.js</th><th>iOSSF Symbol</th><th>Android</th></tr>${rows}</table>`;
} catch {
document.getElementById('icon-map-table').innerHTML =
'<p class="note">file:// 模式无法加载 icons.js / icon-map.json,请 <code>node design/serve.mjs</code> 后访问。</p>';
}
</script>
</div>
</body>
</html>