@deernova/style 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/bin/cli.mjs +32 -0
- package/package.json +18 -4
- package/skill/SKILL.md +36 -0
- package/skill/assets/design/README.md +170 -0
- package/skill/assets/design/components/Banner/README.md +33 -0
- package/skill/assets/design/components/Banner/preview.html +26 -0
- package/skill/assets/design/components/Button/README.md +59 -0
- package/skill/assets/design/components/Button/preview.html +27 -0
- package/skill/assets/design/components/Card/README.md +31 -0
- package/skill/assets/design/components/Card/preview.html +24 -0
- package/skill/assets/design/components/CodeBlock/README.md +34 -0
- package/skill/assets/design/components/CodeBlock/preview.html +16 -0
- package/skill/assets/design/components/Composer/README.md +42 -0
- package/skill/assets/design/components/Composer/preview.html +28 -0
- package/skill/assets/design/components/Cover/preview.html +53 -0
- package/skill/assets/design/components/EmptyState/README.md +28 -0
- package/skill/assets/design/components/EmptyState/preview.html +16 -0
- package/skill/assets/design/components/GlassControls/README.md +43 -0
- package/skill/assets/design/components/GlassControls/preview.html +40 -0
- package/skill/assets/design/components/ListGroup/README.md +39 -0
- package/skill/assets/design/components/ListGroup/preview.html +22 -0
- package/skill/assets/design/components/MacWindow/README.md +27 -0
- package/skill/assets/design/components/MacWindow/preview.html +70 -0
- package/skill/assets/design/components/Message/README.md +31 -0
- package/skill/assets/design/components/Message/preview.html +21 -0
- package/skill/assets/design/components/PhoneScreen/README.md +25 -0
- package/skill/assets/design/components/PhoneScreen/preview.html +74 -0
- package/skill/assets/design/components/Segmented/README.md +21 -0
- package/skill/assets/design/components/Segmented/preview.html +21 -0
- package/skill/assets/design/components/Sheet/README.md +29 -0
- package/skill/assets/design/components/Sheet/preview.html +31 -0
- package/skill/assets/design/components/Sidebar/README.md +36 -0
- package/skill/assets/design/components/Sidebar/preview.html +20 -0
- package/skill/assets/design/components/StepRow/README.md +35 -0
- package/skill/assets/design/components/StepRow/preview.html +17 -0
- package/skill/assets/design/components/Tag/README.md +36 -0
- package/skill/assets/design/components/Tag/preview.html +22 -0
- package/skill/assets/design/components/TextField/README.md +29 -0
- package/skill/assets/design/components/TextField/preview.html +28 -0
- package/skill/assets/design/components/bundle.css +189 -0
- package/skill/assets/design/design-system.json +25 -0
- package/skill/assets/design/images/Banner-dark.png +0 -0
- package/skill/assets/design/images/Banner-light.png +0 -0
- package/skill/assets/design/images/Button-dark.png +0 -0
- package/skill/assets/design/images/Button-light.png +0 -0
- package/skill/assets/design/images/Card-dark.png +0 -0
- package/skill/assets/design/images/Card-light.png +0 -0
- package/skill/assets/design/images/CodeBlock-dark.png +0 -0
- package/skill/assets/design/images/CodeBlock-light.png +0 -0
- package/skill/assets/design/images/Composer-dark.png +0 -0
- package/skill/assets/design/images/Composer-light.png +0 -0
- package/skill/assets/design/images/Cover-dark.png +0 -0
- package/skill/assets/design/images/Cover-light.png +0 -0
- package/skill/assets/design/images/EmptyState-dark.png +0 -0
- package/skill/assets/design/images/EmptyState-light.png +0 -0
- package/skill/assets/design/images/Example-iOSButtonsInPhone-dark.png +0 -0
- package/skill/assets/design/images/Example-iOSButtonsInPhone-light.png +0 -0
- package/skill/assets/design/images/GlassControls-dark.png +0 -0
- package/skill/assets/design/images/GlassControls-light.png +0 -0
- package/skill/assets/design/images/ListGroup-dark.png +0 -0
- package/skill/assets/design/images/ListGroup-light.png +0 -0
- package/skill/assets/design/images/MacWindow-dark.png +0 -0
- package/skill/assets/design/images/MacWindow-light.png +0 -0
- package/skill/assets/design/images/Message-dark.png +0 -0
- package/skill/assets/design/images/Message-light.png +0 -0
- package/skill/assets/design/images/PhoneScreen-dark.png +0 -0
- package/skill/assets/design/images/PhoneScreen-light.png +0 -0
- package/skill/assets/design/images/Segmented-dark.png +0 -0
- package/skill/assets/design/images/Segmented-light.png +0 -0
- package/skill/assets/design/images/Sheet-dark.png +0 -0
- package/skill/assets/design/images/Sheet-light.png +0 -0
- package/skill/assets/design/images/Sidebar-dark.png +0 -0
- package/skill/assets/design/images/Sidebar-light.png +0 -0
- package/skill/assets/design/images/StepRow-dark.png +0 -0
- package/skill/assets/design/images/StepRow-light.png +0 -0
- package/skill/assets/design/images/Tag-dark.png +0 -0
- package/skill/assets/design/images/Tag-light.png +0 -0
- package/skill/assets/design/images/TextField-dark.png +0 -0
- package/skill/assets/design/images/TextField-light.png +0 -0
- package/skill/assets/design/tokens.css +187 -0
- package/skill/assets/design/tokens.json +142 -0
- package/skill/manifest.json +24 -0
- package/skill/references/components.md +648 -0
- package/skill/references/foundations.md +170 -0
- package/skill/references/native.md +7 -0
- package/skill/references/review.md +13 -0
- package/skill/references/tokens.md +74 -0
- package/skill/references/web.md +11 -0
- package/template/app.js +7 -0
- package/template/index.html +1290 -0
- package/template/manifest.json +24 -0
- package/template/project/README.md +170 -0
- package/template/project/components/Banner/README.md +33 -0
- package/template/project/components/Banner/preview.html +26 -0
- package/template/project/components/Button/README.md +59 -0
- package/template/project/components/Button/preview.html +27 -0
- package/template/project/components/Card/README.md +31 -0
- package/template/project/components/Card/preview.html +24 -0
- package/template/project/components/CodeBlock/README.md +34 -0
- package/template/project/components/CodeBlock/preview.html +16 -0
- package/template/project/components/Composer/README.md +42 -0
- package/template/project/components/Composer/preview.html +28 -0
- package/template/project/components/Cover/preview.html +53 -0
- package/template/project/components/EmptyState/README.md +28 -0
- package/template/project/components/EmptyState/preview.html +16 -0
- package/template/project/components/GlassControls/README.md +43 -0
- package/template/project/components/GlassControls/preview.html +40 -0
- package/template/project/components/ListGroup/README.md +39 -0
- package/template/project/components/ListGroup/preview.html +22 -0
- package/template/project/components/MacWindow/README.md +27 -0
- package/template/project/components/MacWindow/preview.html +70 -0
- package/template/project/components/Message/README.md +31 -0
- package/template/project/components/Message/preview.html +21 -0
- package/template/project/components/PhoneScreen/README.md +25 -0
- package/template/project/components/PhoneScreen/preview.html +74 -0
- package/template/project/components/Segmented/README.md +21 -0
- package/template/project/components/Segmented/preview.html +21 -0
- package/template/project/components/Sheet/README.md +29 -0
- package/template/project/components/Sheet/preview.html +31 -0
- package/template/project/components/Sidebar/README.md +36 -0
- package/template/project/components/Sidebar/preview.html +20 -0
- package/template/project/components/StepRow/README.md +35 -0
- package/template/project/components/StepRow/preview.html +17 -0
- package/template/project/components/Tag/README.md +36 -0
- package/template/project/components/Tag/preview.html +22 -0
- package/template/project/components/TextField/README.md +29 -0
- package/template/project/components/TextField/preview.html +28 -0
- package/template/project/components/bundle.css +189 -0
- package/template/project/design-system.json +25 -0
- package/template/project/images/Banner-dark.png +0 -0
- package/template/project/images/Banner-light.png +0 -0
- package/template/project/images/Button-dark.png +0 -0
- package/template/project/images/Button-light.png +0 -0
- package/template/project/images/Card-dark.png +0 -0
- package/template/project/images/Card-light.png +0 -0
- package/template/project/images/CodeBlock-dark.png +0 -0
- package/template/project/images/CodeBlock-light.png +0 -0
- package/template/project/images/Composer-dark.png +0 -0
- package/template/project/images/Composer-light.png +0 -0
- package/template/project/images/Cover-dark.png +0 -0
- package/template/project/images/Cover-light.png +0 -0
- package/template/project/images/EmptyState-dark.png +0 -0
- package/template/project/images/EmptyState-light.png +0 -0
- package/template/project/images/Example-iOSButtonsInPhone-dark.png +0 -0
- package/template/project/images/Example-iOSButtonsInPhone-light.png +0 -0
- package/template/project/images/GlassControls-dark.png +0 -0
- package/template/project/images/GlassControls-light.png +0 -0
- package/template/project/images/ListGroup-dark.png +0 -0
- package/template/project/images/ListGroup-light.png +0 -0
- package/template/project/images/MacWindow-dark.png +0 -0
- package/template/project/images/MacWindow-light.png +0 -0
- package/template/project/images/Message-dark.png +0 -0
- package/template/project/images/Message-light.png +0 -0
- package/template/project/images/PhoneScreen-dark.png +0 -0
- package/template/project/images/PhoneScreen-light.png +0 -0
- package/template/project/images/Segmented-dark.png +0 -0
- package/template/project/images/Segmented-light.png +0 -0
- package/template/project/images/Sheet-dark.png +0 -0
- package/template/project/images/Sheet-light.png +0 -0
- package/template/project/images/Sidebar-dark.png +0 -0
- package/template/project/images/Sidebar-light.png +0 -0
- package/template/project/images/StepRow-dark.png +0 -0
- package/template/project/images/StepRow-light.png +0 -0
- package/template/project/images/Tag-dark.png +0 -0
- package/template/project/images/Tag-light.png +0 -0
- package/template/project/images/TextField-dark.png +0 -0
- package/template/project/images/TextField-light.png +0 -0
- package/template/project/tokens.css +187 -0
- package/template/project/tokens.json +142 -0
- package/template/style.css +1 -0
|
@@ -0,0 +1,1290 @@
|
|
|
1
|
+
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Deernova 系列统一设计语言、组件规范与 AI skill。"><title>Deernova — Design language</title><link rel="stylesheet" href="project/tokens.css"><link rel="stylesheet" href="style.css"><script src="app.js" defer></script></head><body>
|
|
2
|
+
<a class="skip" href="#main">跳到内容</a><aside><a class="brand" href="#overview">deernova<span>DESIGN LANGUAGE</span></a><div class="edition">规范与工具 / v0.1.0</div><label class="search">查找组件<input id="search" type="search" placeholder="名称或关键词…"></label><nav aria-label="文档导航"><a href="#overview">概览 <span>01</span></a><a href="#foundations">设计原则 <span>02</span></a><a href="#tokens">Design tokens <span>03</span></a><a href="#components">组件与布局 <span>04</span></a><div class="component-nav"><a href="#c-Banner">Banner</a><a href="#c-Button">Button</a><a href="#c-Card">Card</a><a href="#c-CodeBlock">CodeBlock</a><a href="#c-Composer">Composer</a><a href="#c-Cover">Cover</a><a href="#c-EmptyState">EmptyState</a><a href="#c-GlassControls">GlassControls</a><a href="#c-ListGroup">ListGroup</a><a href="#c-MacWindow">MacWindow</a><a href="#c-Message">Message</a><a href="#c-PhoneScreen">PhoneScreen</a><a href="#c-Segmented">Segmented</a><a href="#c-Sheet">Sheet</a><a href="#c-Sidebar">Sidebar</a><a href="#c-StepRow">StepRow</a><a href="#c-Tag">Tag</a><a href="#c-TextField">TextField</a></div><a href="#install">安装与使用 <span>05</span></a></nav><div class="aside-bottom"><label for="theme">外观</label><select id="theme"><option value="system">跟随系统</option><option value="light">浅色</option><option value="dark">深色</option></select><p>内容在纸面上。<br>操作在玻璃上。</p></div></aside>
|
|
3
|
+
<main id="main"><header><span>DEERNOVA / SYSTEM</span><span>macOS · iOS · Web</span></header><section id="overview" class="hero"><p class="eyebrow">一套语言,贯穿每一件工具</p><h1>让内容清晰。<br><em>让界面安静。</em></h1><p class="lede">Deernova 系列的设计基础。用纸面的秩序、墨色的对比与恰到好处的玻璃,让工具退到背景,让想法留在眼前。</p><div class="actions"><a class="primary" href="#components">浏览组件 ↗</a><a href="#install">接入你的项目 →</a></div><div class="specimen"><div class="specimen-bar"><span>deernova / workspace</span><span>设计示例</span></div><div class="specimen-inner"><div class="mini-nav">工作空间<br><b>所有笔记</b><span>最近编辑</span><span>已归档</span></div><div class="paper"><small>随手记 / 今天</small><h2>好的工具,<br>让想法自然发生。</h2><p>记录、整理、继续创造。<br>把注意力留给真正重要的事。</p><div class="paper-rule"></div><span class="status">✓ 已同步</span></div><div class="floating">+ <span>新建笔记</span></div></div></div><div class="stats"><div><b>18</b><span>组件与布局</span></div><div><b>Light / Dark</b><span>两套语义主题</span></div><div><b>One source</b><span>网页、样式与 skill 同步生成</span></div></div></section>
|
|
4
|
+
<section id="foundations" class="section"><div class="section-title"><span>02 / FOUNDATIONS</span><h2>从原则开始</h2><p>紧凑、克制、原生优先。每一个决定都有依据。</p></div><div class="prose foundations"><p>Deernova 是给 macOS 和 iOS 应用用的一套设计语言:只用黑、白、灰。纸面承载内容,液态玻璃(Liquid Glass)承载操作,强调靠明暗对比,不靠彩色。整体紧凑、安静、克制,像一件趁手的工具。</p>
|
|
5
|
+
<h2>原则</h2>
|
|
6
|
+
<ol>
|
|
7
|
+
<li><strong>纸面与玻璃分层。</strong> 文字、列表、卡片、代码这些内容,放在不透明的纸面上:<code>canvas</code>、<code>surface</code>、<code>surface-raised</code>、<code>surface-sunken</code>。工具栏、标签栏、浮动按钮、弹出层这些操作,浮在内容之上,用系统的 Liquid Glass。玻璃上不放内容,内容也不做成玻璃。</li>
|
|
8
|
+
<li><strong>黑白灰,强调靠对比。</strong> <code>accent</code> 就是墨色本身:浅色主题下主按钮是黑底白字,深色主题下是白底黑字。界面里唯一的彩色是状态色(成功、警告、错误、提示),只在表达状态时出现。</li>
|
|
9
|
+
<li><strong>用线条代替阴影。</strong> 区块之间用 <code>line</code> 细线分隔(<code>stroke-hairline</code>)。只有真正浮起来的东西(菜单、弹窗、浮动面板)才加阴影 <code>shadow-float</code>。</li>
|
|
10
|
+
<li><strong>衬线用来"说",无衬线用来"做"。</strong> 标题、空状态、长文阅读(文章、AI 回答)用衬线 <code>--font-serif</code>;按钮、标签、列表、表单这些界面文字,用系统无衬线 <code>--font-sans</code>。</li>
|
|
11
|
+
<li><strong>紧凑。</strong> Mac 正文 13pt、行高约 1.4 倍,控件 24pt、列表行 26pt;靠细线和少量留白分组,不靠大块空白。iOS 保留系统的 17pt 正文和 44pt 点击区域,但按钮看起来只有 36pt。</li>
|
|
12
|
+
<li><strong>原生优先。</strong> 系统已有的控件(Toggle、Picker、Menu、DatePicker、Alert)直接用,只通过 <code>tint</code> 和字体让它们融入。自定义组件只做系统没有的东西:Composer、Message、StepRow。</li>
|
|
13
|
+
</ol>
|
|
14
|
+
<h2>文案</h2>
|
|
15
|
+
<ul>
|
|
16
|
+
<li>用"你"称呼用户。语气平实直接,不用感叹号,不用 emoji。</li>
|
|
17
|
+
<li>按钮以动词开头,说清点了会发生什么:「保存」「新建笔记」「删除 3 个文件」。不用「确定」「OK」。操作完成后的提示用完成态:「已保存」「已复制」。</li>
|
|
18
|
+
<li>错误提示要说明出了什么问题、怎么解决:「网络连接已断开,恢复后会自动重试。」不道歉,不含糊。</li>
|
|
19
|
+
<li>中文与英文、中文与数字之间加一个半角空格:「同步到 iCloud」「剩余 12 次」。数字和单位用半角。</li>
|
|
20
|
+
<li>标题、按钮、标签不加句号;完整的句子加句号。</li>
|
|
21
|
+
<li>空状态写法:标题说明现在的状态,正文告诉用户下一步做什么。例如「还没有笔记」/「写下第一条想法,它会同步到你所有的设备。」</li>
|
|
22
|
+
</ul>
|
|
23
|
+
<h2>颜色</h2>
|
|
24
|
+
<p>所有颜色都是语义 token,分 Light 和 Dark 两套主题,两套都验算过对比度。灰阶全部是中性灰,不偏暖也不偏冷。</p>
|
|
25
|
+
<ul>
|
|
26
|
+
<li><strong>纸面层级</strong>,从下到上:窗口底色 <code>canvas</code> → 内容区和卡片 <code>surface</code> → 输入框、Composer、弹出层 <code>surface-raised</code>。代码块、用户消息气泡、展开的详情用 <code>surface-sunken</code>。</li>
|
|
27
|
+
<li><strong>文字</strong>:正文用 <code>ink</code>,次要信息用 <code>ink-secondary</code>,占位符、时间戳、计数用 <code>ink-tertiary</code>。三者在四种纸面上都不低于 4.5:1。</li>
|
|
28
|
+
<li><strong>线条</strong>:分隔线和卡片描边用 <code>line</code>,属于装饰。输入框边框用 <code>line-control</code>,不低于 3:1,让人看得出这里可以输入。</li>
|
|
29
|
+
<li><strong>强调</strong>:<code>accent</code> 等于 <code>ink</code>,用于主按钮填充、选中指示、焦点环。主按钮上的文字用 <code>on-accent</code>,不要写死白色,深色主题下它是黑色。浅灰强调底用 <code>accent-soft</code>,上面的文字用 <code>ink</code>。链接用 <code>ink</code> 加下划线,不用彩色。</li>
|
|
30
|
+
<li><strong>焦点</strong>:<code>focus-ring</code> 等于 <code>accent</code>,2pt 实线,外偏移 2pt。</li>
|
|
31
|
+
<li><strong>状态色</strong>:<code>success</code>(青绿)、<code>warning</code>(赭黄)、<code>danger</code>(莓红)、<code>info</code>(蓝),各自配一个 <code>-soft</code> 浅底。这是界面里仅有的彩色,只用在状态上,不拿来装饰。状态必须同时带图标或文字,不能只靠颜色区分。<code>success</code> 特意偏青,和 <code>danger</code> 拉开距离,色弱用户也能分辨。开关打开时用 <code>success</code>,因为黑白开关在深色主题下看不清圆钮。</li>
|
|
32
|
+
<li><strong>交互底色</strong>:悬停用 <code>fill-hover</code>,选中用 <code>fill-selected</code>。它们是半透明的,叠在任何纸面上都成立。</li>
|
|
33
|
+
<li><strong>仅限网页预览</strong>:<code>glass-fill</code>、<code>glass-stroke</code>、<code>shadow-glass</code> 只是在网页里模拟玻璃效果。App 里一律用系统的 <code>.glassEffect()</code>,不要用这些值自己画玻璃。</li>
|
|
34
|
+
</ul>
|
|
35
|
+
<p>在 SwiftUI 里落地:在 Asset Catalog 里按 token 名建 Color Set,分别填入 Any 和 Dark 两套值,再写一个扩展来引用。扩展写在 <code>ShapeStyle</code> 上,这样 <code>.foregroundStyle(.inkSecondary)</code>、<code>.background(.surface, in: …)</code> 都能直接用点语法。组件说明里的代码都按这套命名书写:</p>
|
|
36
|
+
<pre><code class="language-swift">extension ShapeStyle where Self == Color {
|
|
37
|
+
static var canvas: Color { Color("canvas") }
|
|
38
|
+
static var surface: Color { Color("surface") }
|
|
39
|
+
static var surfaceRaised: Color { Color("surface-raised") }
|
|
40
|
+
static var surfaceSunken: Color { Color("surface-sunken") }
|
|
41
|
+
static var ink: Color { Color("ink") }
|
|
42
|
+
static var inkSecondary: Color { Color("ink-secondary") }
|
|
43
|
+
static var inkTertiary: Color { Color("ink-tertiary") }
|
|
44
|
+
static var line: Color { Color("line") }
|
|
45
|
+
static var brandAccent: Color { Color("accent") } // 等于 ink;避开系统的 .accentColor
|
|
46
|
+
// success、successSoft、warning、danger、fillSelected 等其余 token 同理
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 在 App 入口统一着色,系统控件会自动跟随(按钮、选中态变成黑 / 白)
|
|
50
|
+
WindowGroup {
|
|
51
|
+
RootView().tint(.brandAccent)
|
|
52
|
+
}
|
|
53
|
+
// 开关单独着色
|
|
54
|
+
Toggle("iCloud 同步", isOn: $sync).tint(.success)
|
|
55
|
+
</code></pre>
|
|
56
|
+
<h2>字体</h2>
|
|
57
|
+
<p>全部使用系统字体,不打包字体文件:App 体积不增加,也自动支持动态字体(Dynamic Type)。</p>
|
|
58
|
+
<table>
|
|
59
|
+
<thead>
|
|
60
|
+
<tr>
|
|
61
|
+
<th>角色</th>
|
|
62
|
+
<th>西文</th>
|
|
63
|
+
<th>中文</th>
|
|
64
|
+
<th>SwiftUI</th>
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody><tr>
|
|
68
|
+
<td>衬线:标题、长文</td>
|
|
69
|
+
<td>New York</td>
|
|
70
|
+
<td>苹方</td>
|
|
71
|
+
<td><code>.system(.title, design: .serif)</code></td>
|
|
72
|
+
</tr>
|
|
73
|
+
<tr>
|
|
74
|
+
<td>无衬线:界面</td>
|
|
75
|
+
<td>SF Pro</td>
|
|
76
|
+
<td>苹方</td>
|
|
77
|
+
<td><code>.body</code>、<code>.headline</code> 等</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td>等宽:代码、数据</td>
|
|
81
|
+
<td>SF Mono</td>
|
|
82
|
+
<td>苹方</td>
|
|
83
|
+
<td><code>.system(.body, design: .monospaced)</code></td>
|
|
84
|
+
</tr>
|
|
85
|
+
</tbody></table>
|
|
86
|
+
<ul>
|
|
87
|
+
<li>macOS 和 iOS 各有一套字号,见 Typography。Mac 正文 13pt、行高 18(<code>mac-body</code>),长文 14pt、行高 22(<code>mac-prose</code>);iOS 正文 17pt、行高 23(<code>ios-body</code>)。</li>
|
|
88
|
+
<li>iOS 一律用文本样式(<code>.body</code>、<code>.headline</code>),不要写死字号,这样动态字体才会生效。需要衬线时用 <code>.system(.title2, design: .serif)</code> 这种写法,同样能随系统缩放。</li>
|
|
89
|
+
<li>界面文字行高约为字号的 1.4 倍;长文阅读放宽到 1.5–1.6 倍,中文读起来不挤。</li>
|
|
90
|
+
<li>如果想让中文标题也有衬线的气质,可以打包思源宋体(Source Han Serif,开源可商用)的子集,只用在大标题和营销页上。界面文字始终用苹方。</li>
|
|
91
|
+
<li>需要上下对齐的数字(计数、价格、时间)用等宽数字:<code>.monospacedDigit()</code>。</li>
|
|
92
|
+
</ul>
|
|
93
|
+
<h2>间距与尺寸</h2>
|
|
94
|
+
<ul>
|
|
95
|
+
<li>采用 4pt 网格:<code>space-1</code>(4)、<code>space-2</code>(8)、<code>space-3</code>(12)、<code>space-4</code>(16)、<code>space-5</code>(20)、<code>space-6</code>(24)、<code>space-8</code>(32)、<code>space-10</code>(40)。日常布局基本只用到 <code>space-1</code> 到 <code>space-4</code>。</li>
|
|
96
|
+
<li><strong>macOS 紧凑</strong>:控件高 <code>control-mac</code>(24),侧边栏和列表行 <code>row-mac</code>(26),内容区边距 <code>space-4</code>,卡片内边距 <code>space-3</code>,消息之间 <code>space-4</code>,侧边栏宽 <code>sidebar-mac</code>(220)。</li>
|
|
97
|
+
<li><strong>iOS</strong>:可点区域至少 <code>control-ios</code>(44)。按钮的可见高度:默认 <code>button-ios</code>(36),小号 <code>button-ios-sm</code>(30),通栏主按钮 <code>control-ios-lg</code>(46)。页面左右边距 <code>space-4</code>,卡片之间 <code>space-2</code>,分组之间 <code>space-5</code>。</li>
|
|
98
|
+
<li>长文阅读的栏宽不超过 <code>content-max</code>(680),14pt 下大约每行 45 个汉字。</li>
|
|
99
|
+
<li>组与组之间的间距至少是组内间距的 2 倍。层次靠间距建立,不靠加更多的线和框。</li>
|
|
100
|
+
</ul>
|
|
101
|
+
<h2>圆角</h2>
|
|
102
|
+
<ul>
|
|
103
|
+
<li><code>radius-xs</code>(4):标签、复选框。</li>
|
|
104
|
+
<li><code>radius-sm</code>(6):Mac 按钮、输入框、侧边栏选中块。</li>
|
|
105
|
+
<li><code>radius-md</code>(8):Mac 卡片、横幅、代码块、菜单。</li>
|
|
106
|
+
<li><code>radius-lg</code>(12):iOS 卡片、分组列表、Composer、消息气泡、Mac 侧边栏面板。</li>
|
|
107
|
+
<li><code>radius-xl</code>(24):iOS 底部弹窗、单行 Composer。</li>
|
|
108
|
+
<li><code>radius-full</code>:胶囊和圆形,用于玻璃按钮、iOS 主按钮、发送按钮。</li>
|
|
109
|
+
<li>嵌套时保持同心:内圆角 = 外圆角 − 内边距。iOS 26 / macOS 26 起可以直接用 <code>ConcentricRectangle()</code>,内层会自动和外层容器同心。</li>
|
|
110
|
+
</ul>
|
|
111
|
+
<h2>线条</h2>
|
|
112
|
+
<ul>
|
|
113
|
+
<li><code>stroke-hairline</code>:1 个物理像素,在 Retina 屏上是 0.5pt,用于分隔线和卡片描边。SwiftUI 里写成 <code>1 / displayScale</code>,<code>displayScale</code> 从 <code>@Environment(\.displayScale)</code> 读取。</li>
|
|
114
|
+
<li><code>stroke-control</code>:1pt,用于输入框边框。</li>
|
|
115
|
+
<li><code>stroke-focus</code>:2pt,用于焦点环。</li>
|
|
116
|
+
<li>列表分隔线从文字起点开始,不贯穿到左边缘。</li>
|
|
117
|
+
</ul>
|
|
118
|
+
<h2>层级与阴影</h2>
|
|
119
|
+
<p>从下到上分三层:</p>
|
|
120
|
+
<ol>
|
|
121
|
+
<li>纸面:<code>canvas</code> 和 <code>surface</code>。</li>
|
|
122
|
+
<li>用细线分隔的内容块:没有阴影。</li>
|
|
123
|
+
<li>浮层:玻璃控件、菜单、弹窗,带 <code>shadow-float</code> 或系统玻璃自带的阴影。</li>
|
|
124
|
+
</ol>
|
|
125
|
+
<p>卡片不加阴影。</p>
|
|
126
|
+
<h2>Liquid Glass</h2>
|
|
127
|
+
<p>目标系统设为 iOS 26+ / macOS 26+,直接用系统 API,不写兼容层。</p>
|
|
128
|
+
<p><strong>用玻璃的地方</strong>(大部分由系统自动提供):</p>
|
|
129
|
+
<ul>
|
|
130
|
+
<li>导航栏、工具栏、标签栏、Mac 侧边栏:使用标准的 <code>NavigationStack</code>、<code>NavigationSplitView</code>、<code>TabView</code>、<code>.toolbar</code>,系统会自动渲染成玻璃。</li>
|
|
131
|
+
<li>浮动操作按钮、浮动工具条:用 <code>.glassEffect()</code>。相邻的几个玻璃元素放进同一个 <code>GlassEffectContainer</code>,它们会互相融合,切换时也会跟着变形。</li>
|
|
132
|
+
<li>底部弹窗、弹出菜单、popover:系统自动处理。</li>
|
|
133
|
+
<li>玻璃层上的主操作用 <code>.buttonStyle(.glassProminent)</code> 加 <code>.tint(.brandAccent)</code>,次要操作用 <code>.buttonStyle(.glass)</code>。</li>
|
|
134
|
+
</ul>
|
|
135
|
+
<p><strong>不用玻璃的地方</strong>:卡片、列表行、消息、输入框、代码块,以及所有需要长时间阅读的内容。</p>
|
|
136
|
+
<p><strong>规则</strong>:</p>
|
|
137
|
+
<ul>
|
|
138
|
+
<li>玻璃上不叠玻璃。</li>
|
|
139
|
+
<li>玻璃上只放图标和很短的标签,不放段落。</li>
|
|
140
|
+
<li>只有主操作的玻璃才用实色(黑底白图标),一屏最多一个。</li>
|
|
141
|
+
<li>iOS 上可点击的自定义玻璃加 <code>.interactive()</code>,按下时会有形变反馈。</li>
|
|
142
|
+
<li>让内容从工具栏下面滚过去,保留系统的滚动边缘效果。不要给工具栏加实色背景。</li>
|
|
143
|
+
<li>iOS 标签栏在向下滚动时收起:<code>.tabBarMinimizeBehavior(.onScrollDown)</code>。</li>
|
|
144
|
+
<li>Mac 上侧边栏旁边的大图,用 <code>.backgroundExtensionEffect()</code> 延伸到侧边栏下方。</li>
|
|
145
|
+
<li>用户开启「降低透明度」后,系统会自动把玻璃换成实色。自己写的半透明效果也要读取 <code>accessibilityReduceTransparency</code>,并做同样的处理。</li>
|
|
146
|
+
</ul>
|
|
147
|
+
<pre><code class="language-swift">// 浮动工具条:两个玻璃按钮融合成一组
|
|
148
|
+
GlassEffectContainer(spacing: 12) {
|
|
149
|
+
HStack(spacing: 12) {
|
|
150
|
+
Button("新建", systemImage: "plus") { createNote() }
|
|
151
|
+
.buttonStyle(.glassProminent)
|
|
152
|
+
Button("搜索", systemImage: "magnifyingglass") { search() }
|
|
153
|
+
.buttonStyle(.glass)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
.tint(.brandAccent)
|
|
157
|
+
</code></pre>
|
|
158
|
+
<h2>动效</h2>
|
|
159
|
+
<ul>
|
|
160
|
+
<li>默认用弹簧动画:<code>.spring(response: 0.35, dampingFraction: 0.85)</code>。悬停、按压这类即时反馈用 0.12s,展开、切换用 0.2s,弹窗的动画交给系统。</li>
|
|
161
|
+
<li>动效用来说明因果:东西从哪里来、到哪里去。不做装饰性的循环动画。</li>
|
|
162
|
+
<li>用户开启「减弱动态效果」(<code>accessibilityReduceMotion</code>)后,位移和缩放动画换成淡入淡出。</li>
|
|
163
|
+
</ul>
|
|
164
|
+
<h2>图标</h2>
|
|
165
|
+
<ul>
|
|
166
|
+
<li>只用 SF Symbols,和系统保持一致。不用 emoji 当图标。</li>
|
|
167
|
+
<li>图标粗细跟随旁边的文字。Mac 上图标 14pt,13pt 文字旁的图标用 <code>.medium</code>,看起来更清楚。</li>
|
|
168
|
+
<li>默认颜色 <code>ink-secondary</code>,选中态和主操作用 <code>ink</code>。图标不用彩色,状态图标除外。多层符号用 <code>.symbolRenderingMode(.hierarchical)</code>。</li>
|
|
169
|
+
<li>状态图标固定为:成功 <code>checkmark.circle.fill</code>,警告 <code>exclamationmark.triangle.fill</code>,错误 <code>xmark.octagon.fill</code>,提示 <code>info.circle.fill</code>。</li>
|
|
170
|
+
<li>目前还没有品牌标志,界面里先用文字名称。做好标志后,再新增一个 Logos 资产组。</li>
|
|
171
|
+
<li>组件预览里的图标是近似 SF Symbols 的线稿,App 里换成对应的 SF Symbols。</li>
|
|
172
|
+
</ul>
|
|
173
|
+
<h2>无障碍</h2>
|
|
174
|
+
<ul>
|
|
175
|
+
<li>每个文字 token 在它说明里注明的纸面上不低于 4.5:1,两套主题都验算过。界面紧凑不等于字小:Mac 正文不低于 13pt,说明文字不低于 11pt。</li>
|
|
176
|
+
<li>iOS 全面支持动态字体,在最大字号下也不截断文字:允许换行,横排放不下就改成竖排。</li>
|
|
177
|
+
<li>iOS 上每个可点元素的点击区域至少 44×44pt,看起来可以更小(按钮 36pt、小按钮 30pt),差的部分用透明的内边距补足;Mac 上的图标按钮至少 24×24pt。</li>
|
|
178
|
+
<li>只靠颜色传达的信息,一律补上图标或文字。</li>
|
|
179
|
+
</ul>
|
|
180
|
+
<h2>组件</h2>
|
|
181
|
+
<p>组件页里的预览是网页上的近似效果,用来对齐尺寸、颜色和层级。App 里用 SwiftUI 实现,每个组件的说明里都写了对应的 SwiftUI 写法,以及需要传入哪些内容。Layouts 分组里有一个 Mac 窗口示例和一个 iPhone 界面示例,展示各个组件组合在一起的样子。</p>
|
|
182
|
+
</div></section>
|
|
183
|
+
<section id="tokens" class="section"><div class="section-title"><span>03 / DESIGN TOKENS</span><h2>同一种墨色,不同的层次</h2><p>点击 token 复制变量。切换主题查看语义颜色的变化。</p></div><h3>color</h3><div class="token-grid"><button class="token" data-copy="var(--canvas)" title="复制 CSS 变量"><span class="swatch" style="background:var(--canvas)"></span><strong>canvas</strong><code>#f5f5f5</code><small>窗口底色、iOS 分组列表的底色。最底层的纸面。</small></button><button class="token" data-copy="var(--surface)" title="复制 CSS 变量"><span class="swatch" style="background:var(--surface)"></span><strong>surface</strong><code>#fafafa</code><small>内容区、卡片。比 canvas 略亮一层。</small></button><button class="token" data-copy="var(--surface-raised)" title="复制 CSS 变量"><span class="swatch" style="background:var(--surface-raised)"></span><strong>surface-raised</strong><code>#ffffff</code><small>输入框、Composer、iOS 分组列表、弹出层。最亮的纸面。</small></button><button class="token" data-copy="var(--surface-sunken)" title="复制 CSS 变量"><span class="swatch" style="background:var(--surface-sunken)"></span><strong>surface-sunken</strong><code>#ebebeb</code><small>代码块、用户消息气泡、展开的步骤详情。凹下去的纸面。</small></button><button class="token" data-copy="var(--ink)" title="复制 CSS 变量"><span class="swatch" style="background:var(--ink)"></span><strong>ink</strong><code>#171717</code><small>正文与标题,用在四种纸面上(最低 13:1)。</small></button><button class="token" data-copy="var(--ink-secondary)" title="复制 CSS 变量"><span class="swatch" style="background:var(--ink-secondary)"></span><strong>ink-secondary</strong><code>#525252</code><small>次要文字、说明、列表的值、未选中图标,用在四种纸面上(最低 6:1)。</small></button><button class="token" data-copy="var(--ink-tertiary)" title="复制 CSS 变量"><span class="swatch" style="background:var(--ink-tertiary)"></span><strong>ink-tertiary</strong><code>#666666</code><small>占位符、时间戳、计数、分组标题,用在四种纸面上(最低 4.5:1)。</small></button><button class="token" data-copy="var(--line)" title="复制 CSS 变量"><span class="swatch" style="background:var(--line)"></span><strong>line</strong><code>#e3e3e3</code><small>分隔线、卡片描边、按钮描边。装饰性,不承载信息。</small></button><button class="token" data-copy="var(--line-control)" title="复制 CSS 变量"><span class="swatch" style="background:var(--line-control)"></span><strong>line-control</strong><code>#8a8a8a</code><small>输入框边框、拖拽条。在 canvas、surface、surface-raised 上不低于 3:1,让人一眼看出可输入。</small></button><button class="token" data-copy="var(--accent)" title="复制 CSS 变量"><span class="swatch" style="background:var(--accent)"></span><strong>accent</strong><code>var(--ink)</code><small>强调:主按钮填充、选中指示、焦点环。就是墨色本身,浅色主题为黑、深色主题为白;强调靠明暗对比,不靠彩色。</small></button><button class="token" data-copy="var(--accent-soft)" title="复制 CSS 变量"><span class="swatch" style="background:var(--accent-soft)"></span><strong>accent-soft</strong><code>#e8e8e8</code><small>强调的浅底:强调标签、列表图标底、输入框聚焦光晕。上面的文字用 ink。</small></button><button class="token" data-copy="var(--on-accent)" title="复制 CSS 变量"><span class="swatch" style="background:var(--on-accent)"></span><strong>on-accent</strong><code>#ffffff</code><small>accent 填充上的文字和图标:浅色主题白字,深色主题黑字。</small></button><button class="token" data-copy="var(--focus-ring)" title="复制 CSS 变量"><span class="swatch" style="background:var(--focus-ring)"></span><strong>focus-ring</strong><code>var(--accent)</code><small>键盘焦点环:2pt 实线,外偏移 2pt。在所有纸面上不低于 13:1。</small></button><button class="token" data-copy="var(--success)" title="复制 CSS 变量"><span class="swatch" style="background:var(--success)"></span><strong>success</strong><code>#1f6b66</code><small>成功、已完成、已同步。界面里只有状态色是彩色,只在表达状态时出现。作为文字用在纸面和 success-soft 上。</small></button><button class="token" data-copy="var(--success-soft)" title="复制 CSS 变量"><span class="swatch" style="background:var(--success-soft)"></span><strong>success-soft</strong><code>#ddeeec</code><small>成功状态的浅底(标签、横幅)。</small></button><button class="token" data-copy="var(--warning)" title="复制 CSS 变量"><span class="swatch" style="background:var(--warning)"></span><strong>warning</strong><code>#8a5a0b</code><small>需要注意但不阻断:额度将用完、网络较慢。作为文字用在纸面和 warning-soft 上。</small></button><button class="token" data-copy="var(--warning-soft)" title="复制 CSS 变量"><span class="swatch" style="background:var(--warning-soft)"></span><strong>warning-soft</strong><code>#f5ead3</code><small>警告状态的浅底。</small></button><button class="token" data-copy="var(--danger)" title="复制 CSS 变量"><span class="swatch" style="background:var(--danger)"></span><strong>danger</strong><code>#b0283f</code><small>错误、失败、删除。永远同时配图标或文字。作为文字用在纸面和 danger-soft 上。</small></button><button class="token" data-copy="var(--danger-soft)" title="复制 CSS 变量"><span class="swatch" style="background:var(--danger-soft)"></span><strong>danger-soft</strong><code>#f7e1e4</code><small>错误状态的浅底、危险按钮的底色。</small></button><button class="token" data-copy="var(--info)" title="复制 CSS 变量"><span class="swatch" style="background:var(--info)"></span><strong>info</strong><code>#2c5f86</code><small>中性提示。作为文字用在纸面和 info-soft 上。</small></button><button class="token" data-copy="var(--info-soft)" title="复制 CSS 变量"><span class="swatch" style="background:var(--info-soft)"></span><strong>info-soft</strong><code>#e1ebf3</code><small>提示状态的浅底。</small></button><button class="token" data-copy="var(--fill-hover)" title="复制 CSS 变量"><span class="swatch" style="background:var(--fill-hover)"></span><strong>fill-hover</strong><code>#0000000a</code><small>悬停底色,叠在任何纸面上(列表行、文字按钮、侧边栏项)。</small></button><button class="token" data-copy="var(--fill-selected)" title="复制 CSS 变量"><span class="swatch" style="background:var(--fill-selected)"></span><strong>fill-selected</strong><code>#00000012</code><small>选中底色(侧边栏当前项、分段控件底槽、中性标签底)。</small></button><button class="token" data-copy="var(--knob)" title="复制 CSS 变量"><span class="swatch" style="background:var(--knob)"></span><strong>knob</strong><code>#ffffff</code><small>开关的圆钮,两套主题都是白色,与系统一致。</small></button><button class="token" data-copy="var(--scrim)" title="复制 CSS 变量"><span class="swatch" style="background:var(--scrim)"></span><strong>scrim</strong><code>#00000052</code><small>弹窗背后压暗内容的遮罩。</small></button><button class="token" data-copy="var(--glass-fill)" title="复制 CSS 变量"><span class="swatch" style="background:var(--glass-fill)"></span><strong>glass-fill</strong><code>#ffffffa6</code><small>仅用于网页预览模拟 Liquid Glass。App 里用系统 .glassEffect(),不要用这个值自己画玻璃。</small></button><button class="token" data-copy="var(--glass-stroke)" title="复制 CSS 变量"><span class="swatch" style="background:var(--glass-stroke)"></span><strong>glass-stroke</strong><code>#ffffffcc</code><small>仅用于网页预览:玻璃边缘的高光描边。</small></button></div><h3>shadow</h3><div class="token-grid"><button class="token" data-copy="var(--shadow-control)" title="复制 CSS 变量"><strong>shadow-control</strong><code>0 1px 1px rgba(0, 0, 0, 0.05)</code><small>次按钮、分段控件的选中块:一点点厚度。</small></button><button class="token" data-copy="var(--shadow-float)" title="复制 CSS 变量"><strong>shadow-float</strong><code>0 1px 2px rgba(0, 0, 0, 0.05), 0 6px 20px rgba(0, 0, 0, 0.08)</code><small>Composer、弹出菜单、弹窗、浮动面板。</small></button><button class="token" data-copy="var(--shadow-glass)" title="复制 CSS 变量"><strong>shadow-glass</strong><code>0 4px 14px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.70)</code><small>仅用于网页预览模拟玻璃。App 里系统玻璃自带阴影。</small></button></div><h3>spacing</h3><div class="token-grid"><button class="token" data-copy="var(--space-1)" title="复制 CSS 变量"><strong>space-1</strong><code>4px</code><small>图标与文字之间、卡片内标题与摘要之间。</small></button><button class="token" data-copy="var(--space-2)" title="复制 CSS 变量"><strong>space-2</strong><code>8px</code><small>Mac 列表项内边距、横幅上下内边距、Composer 内边距。</small></button><button class="token" data-copy="var(--space-3)" title="复制 CSS 变量"><strong>space-3</strong><code>12px</code><small>Mac 按钮左右内边距、卡片内边距、横幅左右内边距。</small></button><button class="token" data-copy="var(--space-4)" title="复制 CSS 变量"><strong>space-4</strong><code>16px</code><small>Mac 内容区边距、对话中消息之间、iOS 页面左右边距。</small></button><button class="token" data-copy="var(--space-5)" title="复制 CSS 变量"><strong>space-5</strong><code>20px</code><small>iOS 分组之间、Mac 区块之间。</small></button><button class="token" data-copy="var(--space-6)" title="复制 CSS 变量"><strong>space-6</strong><code>24px</code><small>空状态内边距、大区块之间。</small></button><button class="token" data-copy="var(--space-8)" title="复制 CSS 变量"><strong>space-8</strong><code>32px</code><small>页面级留白。</small></button><button class="token" data-copy="var(--space-10)" title="复制 CSS 变量"><strong>space-10</strong><code>40px</code><small>引导页、首屏的大留白。</small></button></div><h3>radius</h3><div class="token-grid"><button class="token" data-copy="var(--radius-xs)" title="复制 CSS 变量"><strong>radius-xs</strong><code>4px</code><small>标签、复选框、分段控件里的选中块(外层 radius-sm、内边距 2)。</small></button><button class="token" data-copy="var(--radius-sm)" title="复制 CSS 变量"><strong>radius-sm</strong><code>6px</code><small>Mac 按钮、输入框、侧边栏选中块、iOS 列表图标底。</small></button><button class="token" data-copy="var(--radius-md)" title="复制 CSS 变量"><strong>radius-md</strong><code>8px</code><small>Mac 卡片、横幅、代码块、弹出菜单。</small></button><button class="token" data-copy="var(--radius-lg)" title="复制 CSS 变量"><strong>radius-lg</strong><code>12px</code><small>iOS 卡片、iOS 分组列表、Composer、用户消息气泡、Mac 侧边栏面板。</small></button><button class="token" data-copy="var(--radius-xl)" title="复制 CSS 变量"><strong>radius-xl</strong><code>24px</code><small>iOS 底部弹窗、iOS 单行 Composer。</small></button><button class="token" data-copy="var(--radius-full)" title="复制 CSS 变量"><strong>radius-full</strong><code>999px</code><small>胶囊与圆形:玻璃按钮、iOS 主按钮、发送按钮、状态圆点。</small></button></div><h3>stroke</h3><div class="token-grid"><button class="token" data-copy="var(--stroke-hairline)" title="复制 CSS 变量"><strong>stroke-hairline</strong><code>0.5px</code><small>分隔线、卡片描边:1 个物理像素。SwiftUI: 1 / displayScale。</small></button><button class="token" data-copy="var(--stroke-control)" title="复制 CSS 变量"><strong>stroke-control</strong><code>1px</code><small>输入框边框。</small></button><button class="token" data-copy="var(--stroke-focus)" title="复制 CSS 变量"><strong>stroke-focus</strong><code>2px</code><small>焦点环。</small></button></div><h3>size</h3><div class="token-grid"><button class="token" data-copy="var(--control-mac-sm)" title="复制 CSS 变量"><strong>control-mac-sm</strong><code>20px</code><small>Mac 小按钮、行内操作、分段控件。</small></button><button class="token" data-copy="var(--control-mac)" title="复制 CSS 变量"><strong>control-mac</strong><code>24px</code><small>Mac 默认按钮、输入框、图标按钮、步骤行。</small></button><button class="token" data-copy="var(--control-mac-lg)" title="复制 CSS 变量"><strong>control-mac-lg</strong><code>28px</code><small>Mac 大按钮(引导页、空状态)。</small></button><button class="token" data-copy="var(--row-mac)" title="复制 CSS 变量"><strong>row-mac</strong><code>26px</code><small>Mac 侧边栏项、列表行。</small></button><button class="token" data-copy="var(--control-ios)" title="复制 CSS 变量"><strong>control-ios</strong><code>44px</code><small>iOS 最小可点区域(按钮看起来可以更小,点击区域仍补足到 44)、列表行、输入框、导航栏玻璃按钮。</small></button><button class="token" data-copy="var(--button-ios-sm)" title="复制 CSS 变量"><strong>button-ios-sm</strong><code>30px</code><small>iOS 小按钮的可见高度:卡片和列表里的「获取」「关注」这类行内操作。</small></button><button class="token" data-copy="var(--button-ios)" title="复制 CSS 变量"><strong>button-ios</strong><code>36px</code><small>iOS 默认按钮的可见高度。</small></button><button class="token" data-copy="var(--control-ios-lg)" title="复制 CSS 变量"><strong>control-ios-lg</strong><code>46px</code><small>iOS 通栏主按钮(页面底部、弹窗里的唯一主操作)。</small></button><button class="token" data-copy="var(--sidebar-mac)" title="复制 CSS 变量"><strong>sidebar-mac</strong><code>220px</code><small>Mac 侧边栏默认宽度。</small></button><button class="token" data-copy="var(--content-max)" title="复制 CSS 变量"><strong>content-max</strong><code>680px</code><small>长文阅读栏的最大宽度,14pt 下约每行 45 个汉字。</small></button></div><h3>Typography</h3><h4>macOS</h4><div class="type-row"><code>mac-display · 22px/28px</code><p style="font-family:var(--font-serif);font-size:22px;line-height:28px;font-weight:600">还没有笔记</p><small>空状态、引导页的大标题。每屏最多一个。SwiftUI: .system(size: 22, weight: .semibold, design: .serif)</small></div><div class="type-row"><code>mac-title · 17px/22px</code><p style="font-family:var(--font-serif);font-size:17px;line-height:22px;font-weight:600">本周回顾</p><small>文章标题、设置页标题。SwiftUI: .system(.title3, design: .serif).weight(.semibold)</small></div><div class="type-row"><code>mac-headline · 13px/17px</code><p style="font-family:var(--font-sans);font-size:13px;line-height:17px;font-weight:600">最近项目</p><small>窗口标题、区块标题、横幅标题。SwiftUI: .headline</small></div><div class="type-row"><code>mac-body · 13px/18px</code><p style="font-family:var(--font-sans);font-size:13px;line-height:18px;font-weight:400">同步到 iCloud 后,所有设备上都能看到这条笔记。</p><small>界面正文、按钮、列表、表单、用户消息。SwiftUI: .body</small></div><div class="type-row"><code>mac-prose · 14px/22px</code><p style="font-family:var(--font-serif);font-size:14px;line-height:22px;font-weight:400">长文和 AI 的回答用衬线排,读起来更像一页纸,而不是一块界面。</p><small>长文阅读、AI 回答。栏宽不超过 content-max。</small></div><div class="type-row"><code>mac-callout · 12px/16px</code><p style="font-family:var(--font-sans);font-size:12px;line-height:16px;font-weight:400">上次同步:今天 09:41</p><small>表单说明、卡片摘要、次要信息。SwiftUI: .callout</small></div><div class="type-row"><code>mac-caption · 11px/13px</code><p style="font-family:var(--font-sans);font-size:11px;line-height:13px;font-weight:500">3 个文件 · 12 KB</p><small>时间戳、计数、侧边栏分组标题、标签。SwiftUI: .caption</small></div><h4>iOS</h4><div class="type-row"><code>ios-large-title · 30px/36px</code><p style="font-family:var(--font-serif);font-size:30px;line-height:36px;font-weight:600">笔记</p><small>页面大标题。SwiftUI: .system(.largeTitle, design: .serif).weight(.semibold)</small></div><div class="type-row"><code>ios-title · 20px/25px</code><p style="font-family:var(--font-serif);font-size:20px;line-height:25px;font-weight:600">本周回顾</p><small>弹窗标题、文章标题、空状态标题。SwiftUI: .system(.title3, design: .serif)</small></div><div class="type-row"><code>ios-headline · 17px/22px</code><p style="font-family:var(--font-sans);font-size:17px;line-height:22px;font-weight:600">最近项目</p><small>列表行标题、卡片标题、按钮。SwiftUI: .headline</small></div><div class="type-row"><code>ios-body · 17px/23px</code><p style="font-family:var(--font-sans);font-size:17px;line-height:23px;font-weight:400">同步到 iCloud 后,所有设备上都能看到这条笔记。</p><small>界面正文、列表行。SwiftUI: .body</small></div><div class="type-row"><code>ios-prose · 17px/26px</code><p style="font-family:var(--font-serif);font-size:17px;line-height:26px;font-weight:400">长文和 AI 的回答用衬线排,读起来更像一页纸。</p><small>长文阅读、AI 回答。SwiftUI: .system(.body, design: .serif),行距 .lineSpacing(4)</small></div><div class="type-row"><code>ios-callout · 15px/20px</code><p style="font-family:var(--font-sans);font-size:15px;line-height:20px;font-weight:400">上次同步:今天 09:41</p><small>说明文字、弹窗正文、用户消息。SwiftUI: .callout</small></div><div class="type-row"><code>ios-footnote · 13px/17px</code><p style="font-family:var(--font-sans);font-size:13px;line-height:17px;font-weight:400">关闭后,新笔记只保存在这台设备上。</p><small>分组列表的页头页脚、卡片摘要。SwiftUI: .footnote</small></div><div class="type-row"><code>ios-caption · 12px/15px</code><p style="font-family:var(--font-sans);font-size:12px;line-height:15px;font-weight:500">3 个文件 · 12 KB</p><small>时间戳、计数、标签。SwiftUI: .caption</small></div><h4>Code</h4><div class="type-row"><code>code-mac · 12px/17px</code><p style="font-family:var(--font-mono);font-size:12px;line-height:17px;font-weight:400">let total = items.reduce(0, +)</p><small>Mac 上的代码块、步骤摘要。SwiftUI: .system(.callout, design: .monospaced)</small></div><div class="type-row"><code>code-ios · 13px/18px</code><p style="font-family:var(--font-mono);font-size:13px;line-height:18px;font-weight:400">let total = items.reduce(0, +)</p><small>iOS 上的代码块。SwiftUI: .system(.footnote, design: .monospaced)</small></div></section>
|
|
184
|
+
<section id="components" class="section"><div class="section-title"><span>04 / COMPONENT LIBRARY</span><h2>从细节,构成整体</h2><p>网页预览用于对齐视觉;原生 App 使用文档中的 SwiftUI 实现。</p></div><p id="results" role="status"></p><section class="component" id="c-Banner" data-search="Banner 页面内提示与轻提示 # Banner
|
|
185
|
+
|
|
186
|
+
页面内的提示条,告诉用户一件需要知道的事,必要时附带一个解决动作。轻提示(Toast)是它的短暂版本。
|
|
187
|
+
|
|
188
|
+
**需要提供**:语气(中性 / success / warning / danger)、标题(一句话说明状态)、正文(后果或解决办法)、可选的一个动作按钮;能关闭的横幅还要提供关闭动作。
|
|
189
|
+
|
|
190
|
+
**规格**
|
|
191
|
+
|
|
192
|
+
- 中性横幅:`surface-raised` 底、`line` 描边、`ink-secondary` 图标。有语气的横幅:对应的 `-soft` 底、无描边、图标用语气色。文字始终是 `ink` 和 `ink-secondary`。
|
|
193
|
+
- `radius-md`,内边距上下 `space-2`、左右 `space-3`,图标 16pt;标题 13pt 半粗,正文 12pt。图标统一用 README「图标」一节里的状态符号。
|
|
194
|
+
- 动作按钮用小号次按钮,放在右侧,一个横幅最多一个。
|
|
195
|
+
- 轻提示:底部居中的玻璃胶囊,高 40pt,2 秒后自动消失,只用来确认刚刚完成的操作(「已复制」「已保存」),文字用完成态,不放按钮。
|
|
196
|
+
|
|
197
|
+
```swift
|
|
198
|
+
HStack(alignment: .top, spacing: 10) {
|
|
199
|
+
Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.warning)
|
|
200
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
201
|
+
Text("本月 AI 次数还剩 10%").font(.headline)
|
|
202
|
+
Text("用完后可以继续写笔记,AI 功能会在 11 月 1 日恢复。")
|
|
203
|
+
.foregroundStyle(.inkSecondary)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
.padding(.vertical, 8).padding(.horizontal, 12)
|
|
207
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
208
|
+
.background(.warningSoft, in: .rect(cornerRadius: 8))
|
|
209
|
+
|
|
210
|
+
// 轻提示
|
|
211
|
+
Label("已复制到剪贴板", systemImage: "checkmark.circle.fill")
|
|
212
|
+
.padding(.horizontal, 16).frame(height: 40)
|
|
213
|
+
.glassEffect(in: .capsule)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**不要**:一个页面同时出现多个横幅;用横幅打广告;错误信息只写「出错了」;轻提示里放需要用户处理的事情(那应该用横幅或弹窗)。
|
|
217
|
+
"><div class="component-head"><h3>Banner</h3><span>页面内提示与轻提示</span><a href="project/components/Banner/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Banner 组件预览" src="project/components/Banner/preview.html" style="height:312px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Banner</h1>
|
|
218
|
+
<p>页面内的提示条,告诉用户一件需要知道的事,必要时附带一个解决动作。轻提示(Toast)是它的短暂版本。</p>
|
|
219
|
+
<p><strong>需要提供</strong>:语气(中性 / success / warning / danger)、标题(一句话说明状态)、正文(后果或解决办法)、可选的一个动作按钮;能关闭的横幅还要提供关闭动作。</p>
|
|
220
|
+
<p><strong>规格</strong></p>
|
|
221
|
+
<ul>
|
|
222
|
+
<li>中性横幅:<code>surface-raised</code> 底、<code>line</code> 描边、<code>ink-secondary</code> 图标。有语气的横幅:对应的 <code>-soft</code> 底、无描边、图标用语气色。文字始终是 <code>ink</code> 和 <code>ink-secondary</code>。</li>
|
|
223
|
+
<li><code>radius-md</code>,内边距上下 <code>space-2</code>、左右 <code>space-3</code>,图标 16pt;标题 13pt 半粗,正文 12pt。图标统一用 README「图标」一节里的状态符号。</li>
|
|
224
|
+
<li>动作按钮用小号次按钮,放在右侧,一个横幅最多一个。</li>
|
|
225
|
+
<li>轻提示:底部居中的玻璃胶囊,高 40pt,2 秒后自动消失,只用来确认刚刚完成的操作(「已复制」「已保存」),文字用完成态,不放按钮。</li>
|
|
226
|
+
</ul>
|
|
227
|
+
<pre><code class="language-swift">HStack(alignment: .top, spacing: 10) {
|
|
228
|
+
Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.warning)
|
|
229
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
230
|
+
Text("本月 AI 次数还剩 10%").font(.headline)
|
|
231
|
+
Text("用完后可以继续写笔记,AI 功能会在 11 月 1 日恢复。")
|
|
232
|
+
.foregroundStyle(.inkSecondary)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
.padding(.vertical, 8).padding(.horizontal, 12)
|
|
236
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
237
|
+
.background(.warningSoft, in: .rect(cornerRadius: 8))
|
|
238
|
+
|
|
239
|
+
// 轻提示
|
|
240
|
+
Label("已复制到剪贴板", systemImage: "checkmark.circle.fill")
|
|
241
|
+
.padding(.horizontal, 16).frame(height: 40)
|
|
242
|
+
.glassEffect(in: .capsule)
|
|
243
|
+
</code></pre>
|
|
244
|
+
<p><strong>不要</strong>:一个页面同时出现多个横幅;用横幅打广告;错误信息只写「出错了」;轻提示里放需要用户处理的事情(那应该用横幅或弹窗)。</p>
|
|
245
|
+
</div></details></section>
|
|
246
|
+
<section class="component" id="c-Button" data-search="Button 主按钮每屏最多一个 # Button
|
|
247
|
+
|
|
248
|
+
按钮执行一个动作;每个视图最多一个主按钮(`accent` 填充,浅色主题黑底白字、深色主题白底黑字),用来完成这一屏存在的理由。
|
|
249
|
+
|
|
250
|
+
**变体**
|
|
251
|
+
|
|
252
|
+
- 主按钮:`accent` 底、`on-accent` 字。用于保存、发送、购买等关键动作。
|
|
253
|
+
- 次按钮:`surface-raised` 底、`line` 描边、`ink` 字,带 `shadow-control`。
|
|
254
|
+
- 文字按钮:无底,`ink-secondary` 字,悬停时出现 `fill-hover`。用于工具栏和卡片内的次要操作。
|
|
255
|
+
- 危险按钮:`danger-soft` 底、`danger` 字。只有在最终确认弹窗里,才用系统的 `.destructive` 样式。
|
|
256
|
+
|
|
257
|
+
**尺寸**
|
|
258
|
+
|
|
259
|
+
| | macOS | iOS |
|
|
260
|
+
| --- | --- | --- |
|
|
261
|
+
| 小 | `control-mac-sm`(20),`radius-xs`,11pt | `button-ios-sm`(30),胶囊,13pt 半粗,用于卡片和列表里的行内操作 |
|
|
262
|
+
| 默认 | `control-mac`(24),`radius-sm`,13pt 中粗 | `button-ios`(36),胶囊,15pt 半粗 |
|
|
263
|
+
| 大 | `control-mac-lg`(28) | `control-ios-lg`(46),通栏胶囊,16pt 半粗,只用于页面底部或弹窗里唯一的主操作 |
|
|
264
|
+
|
|
265
|
+
iOS 按钮看起来可以比 44pt 小,但点击区域必须补足到 44pt(`control-ios`):在按钮外面加透明的内边距,或者用 `.contentShape` 扩大可点范围。
|
|
266
|
+
|
|
267
|
+
**iOS 怎么选尺寸**:不是随意挑,按按钮所在的位置定。
|
|
268
|
+
|
|
269
|
+
- 列表行、卡片角落里的行内操作(获取、关注、打开)→ 小号 30pt。
|
|
270
|
+
- 页面和卡片里的普通按钮,包括并排的两个按钮(以后再说 / 开启)→ 默认 36pt。同一行里的按钮用同一个尺寸。
|
|
271
|
+
- 整页唯一的主操作,放在页面底部(登录、订阅、开始使用)→ 通栏 46pt,一屏最多一个。
|
|
272
|
+
- 导航栏、标签栏、工具栏里的按钮 → 用系统控件,不自己设尺寸。
|
|
273
|
+
|
|
274
|
+
iOS 按钮比 Mac 大是有意的:手指比鼠标指针粗,Mac 默认 24pt,iOS 默认 36pt。在 Mac 屏幕上看 iOS 设计图会比真机上大一些,因为 Mac 上 1pt 的物理尺寸比 iPhone 上大,判断大小时以真机为准。
|
|
275
|
+
|
|
276
|
+
**需要提供**:标签(动词开头)、可选的 SF Symbol、点击动作。加载中时保持按钮宽度不变,把标签换成 `ProgressView()`。
|
|
277
|
+
|
|
278
|
+
```swift
|
|
279
|
+
Button("保存") { save() }
|
|
280
|
+
.buttonStyle(.borderedProminent) // 主按钮,颜色跟随 .tint(.brandAccent)
|
|
281
|
+
Button("取消", role: .cancel) { dismiss() }
|
|
282
|
+
.buttonStyle(.bordered)
|
|
283
|
+
Button("删除", role: .destructive) { delete() }
|
|
284
|
+
|
|
285
|
+
// iOS 默认按钮:看起来 36pt 高,点击区域补足到 44pt
|
|
286
|
+
Button("继续") { next() }
|
|
287
|
+
.font(.subheadline.weight(.semibold))
|
|
288
|
+
.buttonStyle(.borderedProminent)
|
|
289
|
+
.buttonBorderShape(.capsule)
|
|
290
|
+
.controlSize(.regular)
|
|
291
|
+
.padding(.vertical, 4) // 透明的外边距,把点击区域撑到 44pt
|
|
292
|
+
.contentShape(.rect)
|
|
293
|
+
|
|
294
|
+
// iOS 底部通栏主按钮(46pt)
|
|
295
|
+
Button { start() } label: {
|
|
296
|
+
Text("开始使用").font(.callout.weight(.semibold)).frame(maxWidth: .infinity, minHeight: 46)
|
|
297
|
+
}
|
|
298
|
+
.buttonStyle(.borderedProminent)
|
|
299
|
+
.buttonBorderShape(.capsule)
|
|
300
|
+
|
|
301
|
+
// 放在玻璃层上时改用 .glassProminent / .glass,见 GlassControls
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**不要**:一个视图里放两个主按钮;用「确定」「OK」做标签;按钮里写一整句话;为了强调把次要按钮也做成黑底。
|
|
305
|
+
"><div class="component-head"><h3>Button</h3><span>主按钮每屏最多一个</span><a href="project/components/Button/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Button 组件预览" src="project/components/Button/preview.html" style="height:202px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Button</h1>
|
|
306
|
+
<p>按钮执行一个动作;每个视图最多一个主按钮(<code>accent</code> 填充,浅色主题黑底白字、深色主题白底黑字),用来完成这一屏存在的理由。</p>
|
|
307
|
+
<p><strong>变体</strong></p>
|
|
308
|
+
<ul>
|
|
309
|
+
<li>主按钮:<code>accent</code> 底、<code>on-accent</code> 字。用于保存、发送、购买等关键动作。</li>
|
|
310
|
+
<li>次按钮:<code>surface-raised</code> 底、<code>line</code> 描边、<code>ink</code> 字,带 <code>shadow-control</code>。</li>
|
|
311
|
+
<li>文字按钮:无底,<code>ink-secondary</code> 字,悬停时出现 <code>fill-hover</code>。用于工具栏和卡片内的次要操作。</li>
|
|
312
|
+
<li>危险按钮:<code>danger-soft</code> 底、<code>danger</code> 字。只有在最终确认弹窗里,才用系统的 <code>.destructive</code> 样式。</li>
|
|
313
|
+
</ul>
|
|
314
|
+
<p><strong>尺寸</strong></p>
|
|
315
|
+
<table>
|
|
316
|
+
<thead>
|
|
317
|
+
<tr>
|
|
318
|
+
<th></th>
|
|
319
|
+
<th>macOS</th>
|
|
320
|
+
<th>iOS</th>
|
|
321
|
+
</tr>
|
|
322
|
+
</thead>
|
|
323
|
+
<tbody><tr>
|
|
324
|
+
<td>小</td>
|
|
325
|
+
<td><code>control-mac-sm</code>(20),<code>radius-xs</code>,11pt</td>
|
|
326
|
+
<td><code>button-ios-sm</code>(30),胶囊,13pt 半粗,用于卡片和列表里的行内操作</td>
|
|
327
|
+
</tr>
|
|
328
|
+
<tr>
|
|
329
|
+
<td>默认</td>
|
|
330
|
+
<td><code>control-mac</code>(24),<code>radius-sm</code>,13pt 中粗</td>
|
|
331
|
+
<td><code>button-ios</code>(36),胶囊,15pt 半粗</td>
|
|
332
|
+
</tr>
|
|
333
|
+
<tr>
|
|
334
|
+
<td>大</td>
|
|
335
|
+
<td><code>control-mac-lg</code>(28)</td>
|
|
336
|
+
<td><code>control-ios-lg</code>(46),通栏胶囊,16pt 半粗,只用于页面底部或弹窗里唯一的主操作</td>
|
|
337
|
+
</tr>
|
|
338
|
+
</tbody></table>
|
|
339
|
+
<p>iOS 按钮看起来可以比 44pt 小,但点击区域必须补足到 44pt(<code>control-ios</code>):在按钮外面加透明的内边距,或者用 <code>.contentShape</code> 扩大可点范围。</p>
|
|
340
|
+
<p><strong>iOS 怎么选尺寸</strong>:不是随意挑,按按钮所在的位置定。</p>
|
|
341
|
+
<ul>
|
|
342
|
+
<li>列表行、卡片角落里的行内操作(获取、关注、打开)→ 小号 30pt。</li>
|
|
343
|
+
<li>页面和卡片里的普通按钮,包括并排的两个按钮(以后再说 / 开启)→ 默认 36pt。同一行里的按钮用同一个尺寸。</li>
|
|
344
|
+
<li>整页唯一的主操作,放在页面底部(登录、订阅、开始使用)→ 通栏 46pt,一屏最多一个。</li>
|
|
345
|
+
<li>导航栏、标签栏、工具栏里的按钮 → 用系统控件,不自己设尺寸。</li>
|
|
346
|
+
</ul>
|
|
347
|
+
<p>iOS 按钮比 Mac 大是有意的:手指比鼠标指针粗,Mac 默认 24pt,iOS 默认 36pt。在 Mac 屏幕上看 iOS 设计图会比真机上大一些,因为 Mac 上 1pt 的物理尺寸比 iPhone 上大,判断大小时以真机为准。</p>
|
|
348
|
+
<p><strong>需要提供</strong>:标签(动词开头)、可选的 SF Symbol、点击动作。加载中时保持按钮宽度不变,把标签换成 <code>ProgressView()</code>。</p>
|
|
349
|
+
<pre><code class="language-swift">Button("保存") { save() }
|
|
350
|
+
.buttonStyle(.borderedProminent) // 主按钮,颜色跟随 .tint(.brandAccent)
|
|
351
|
+
Button("取消", role: .cancel) { dismiss() }
|
|
352
|
+
.buttonStyle(.bordered)
|
|
353
|
+
Button("删除", role: .destructive) { delete() }
|
|
354
|
+
|
|
355
|
+
// iOS 默认按钮:看起来 36pt 高,点击区域补足到 44pt
|
|
356
|
+
Button("继续") { next() }
|
|
357
|
+
.font(.subheadline.weight(.semibold))
|
|
358
|
+
.buttonStyle(.borderedProminent)
|
|
359
|
+
.buttonBorderShape(.capsule)
|
|
360
|
+
.controlSize(.regular)
|
|
361
|
+
.padding(.vertical, 4) // 透明的外边距,把点击区域撑到 44pt
|
|
362
|
+
.contentShape(.rect)
|
|
363
|
+
|
|
364
|
+
// iOS 底部通栏主按钮(46pt)
|
|
365
|
+
Button { start() } label: {
|
|
366
|
+
Text("开始使用").font(.callout.weight(.semibold)).frame(maxWidth: .infinity, minHeight: 46)
|
|
367
|
+
}
|
|
368
|
+
.buttonStyle(.borderedProminent)
|
|
369
|
+
.buttonBorderShape(.capsule)
|
|
370
|
+
|
|
371
|
+
// 放在玻璃层上时改用 .glassProminent / .glass,见 GlassControls
|
|
372
|
+
</code></pre>
|
|
373
|
+
<p><strong>不要</strong>:一个视图里放两个主按钮;用「确定」「OK」做标签;按钮里写一整句话;为了强调把次要按钮也做成黑底。</p>
|
|
374
|
+
</div></details></section>
|
|
375
|
+
<section class="component" id="c-Card" data-search="Card 细线描边,不加阴影 # Card
|
|
376
|
+
|
|
377
|
+
卡片把一条内容的摘要装进一个可以点开的块里,用于网格和看板。
|
|
378
|
+
|
|
379
|
+
**需要提供**:标题、一到两行摘要、元信息(时间、分类、状态标签),点击后打开详情。
|
|
380
|
+
|
|
381
|
+
**规格**
|
|
382
|
+
|
|
383
|
+
- `surface` 底、`stroke-hairline` 宽的 `line` 描边,不加阴影。Mac 用 `radius-md`,iOS 用 `radius-lg`。Mac 内边距 `space-3`,iOS 内边距 14pt × `space-4`,内部间距 `space-1`。
|
|
384
|
+
- 标题用衬线 15pt 半粗(Mac)或 17pt(iOS);摘要用 `mac-callout` 或 `ios-footnote`、`ink-secondary`,最多 3 行;元信息 11pt `ink-tertiary`。
|
|
385
|
+
- 悬停时描边加深为 `line-control`。选中时描边变成 1pt 的 `ink`。
|
|
386
|
+
- 网格间距 `space-2`(Mac)或 `space-3`(iOS);每张卡片高度由内容决定,同一行顶部对齐。
|
|
387
|
+
|
|
388
|
+
```swift
|
|
389
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
390
|
+
Text(note.title).font(.system(.headline, design: .serif))
|
|
391
|
+
Text(note.summary).font(.callout).foregroundStyle(.inkSecondary).lineLimit(3)
|
|
392
|
+
Text(note.date, format: .relative(presentation: .named))
|
|
393
|
+
.font(.caption).foregroundStyle(.inkTertiary)
|
|
394
|
+
}
|
|
395
|
+
.padding(12)
|
|
396
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
397
|
+
.background(.surface, in: .rect(cornerRadius: 8))
|
|
398
|
+
.overlay(
|
|
399
|
+
RoundedRectangle(cornerRadius: 8)
|
|
400
|
+
.strokeBorder(isSelected ? .ink : .line,
|
|
401
|
+
lineWidth: isSelected ? 1 : 1 / displayScale)
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**不要**:给卡片加阴影或玻璃效果;在卡片左侧加一条彩色竖条;卡片里套卡片;一张卡片里放多个主按钮。
|
|
406
|
+
"><div class="component-head"><h3>Card</h3><span>细线描边,不加阴影</span><a href="project/components/Card/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Card 组件预览" src="project/components/Card/preview.html" style="height:202px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Card</h1>
|
|
407
|
+
<p>卡片把一条内容的摘要装进一个可以点开的块里,用于网格和看板。</p>
|
|
408
|
+
<p><strong>需要提供</strong>:标题、一到两行摘要、元信息(时间、分类、状态标签),点击后打开详情。</p>
|
|
409
|
+
<p><strong>规格</strong></p>
|
|
410
|
+
<ul>
|
|
411
|
+
<li><code>surface</code> 底、<code>stroke-hairline</code> 宽的 <code>line</code> 描边,不加阴影。Mac 用 <code>radius-md</code>,iOS 用 <code>radius-lg</code>。Mac 内边距 <code>space-3</code>,iOS 内边距 14pt × <code>space-4</code>,内部间距 <code>space-1</code>。</li>
|
|
412
|
+
<li>标题用衬线 15pt 半粗(Mac)或 17pt(iOS);摘要用 <code>mac-callout</code> 或 <code>ios-footnote</code>、<code>ink-secondary</code>,最多 3 行;元信息 11pt <code>ink-tertiary</code>。</li>
|
|
413
|
+
<li>悬停时描边加深为 <code>line-control</code>。选中时描边变成 1pt 的 <code>ink</code>。</li>
|
|
414
|
+
<li>网格间距 <code>space-2</code>(Mac)或 <code>space-3</code>(iOS);每张卡片高度由内容决定,同一行顶部对齐。</li>
|
|
415
|
+
</ul>
|
|
416
|
+
<pre><code class="language-swift">VStack(alignment: .leading, spacing: 4) {
|
|
417
|
+
Text(note.title).font(.system(.headline, design: .serif))
|
|
418
|
+
Text(note.summary).font(.callout).foregroundStyle(.inkSecondary).lineLimit(3)
|
|
419
|
+
Text(note.date, format: .relative(presentation: .named))
|
|
420
|
+
.font(.caption).foregroundStyle(.inkTertiary)
|
|
421
|
+
}
|
|
422
|
+
.padding(12)
|
|
423
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
424
|
+
.background(.surface, in: .rect(cornerRadius: 8))
|
|
425
|
+
.overlay(
|
|
426
|
+
RoundedRectangle(cornerRadius: 8)
|
|
427
|
+
.strokeBorder(isSelected ? .ink : .line,
|
|
428
|
+
lineWidth: isSelected ? 1 : 1 / displayScale)
|
|
429
|
+
)
|
|
430
|
+
</code></pre>
|
|
431
|
+
<p><strong>不要</strong>:给卡片加阴影或玻璃效果;在卡片左侧加一条彩色竖条;卡片里套卡片;一张卡片里放多个主按钮。</p>
|
|
432
|
+
</div></details></section>
|
|
433
|
+
<section class="component" id="c-CodeBlock" data-search="CodeBlock 代码与命令 # CodeBlock
|
|
434
|
+
|
|
435
|
+
展示代码、命令或结构化数据,带语言标签和复制按钮。
|
|
436
|
+
|
|
437
|
+
**需要提供**:代码文本、语言名称;可选的文件名(替代语言名显示在表头)。
|
|
438
|
+
|
|
439
|
+
**规格**
|
|
440
|
+
|
|
441
|
+
- `surface-sunken` 底、`line` 细线描边、`radius-md`。表头高 28pt,左边是语言名或文件名(11pt 等宽、`ink-tertiary`),右边是一个小号文字按钮「复制」;点击后显示「已复制」1.5 秒。
|
|
442
|
+
- 代码字体 `code-mac` 或 `code-ios`,内边距上下 `space-2`、左右 `space-3`,过长时横向滚动,不自动换行。
|
|
443
|
+
- 语法高亮只用灰阶:关键字 `ink` 加粗,字符串 `ink-secondary`,注释 `ink-tertiary` 加斜体,其余 `ink`。这些颜色在 `surface-sunken` 上都不低于 4.5:1。
|
|
444
|
+
|
|
445
|
+
```swift
|
|
446
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
447
|
+
HStack {
|
|
448
|
+
Text(language).font(.caption.monospaced()).foregroundStyle(.inkTertiary)
|
|
449
|
+
Spacer()
|
|
450
|
+
Button(copied ? "已复制" : "复制", systemImage: copied ? "checkmark" : "doc.on.doc") { copy() }
|
|
451
|
+
.buttonStyle(.borderless).controlSize(.small)
|
|
452
|
+
}
|
|
453
|
+
.padding(.horizontal, 12).frame(height: 28)
|
|
454
|
+
Divider()
|
|
455
|
+
ScrollView(.horizontal) {
|
|
456
|
+
Text(highlighted) // 用 AttributedString 按上面的颜色着色
|
|
457
|
+
.font(.system(.callout, design: .monospaced))
|
|
458
|
+
.textSelection(.enabled)
|
|
459
|
+
.padding(.vertical, 8).padding(.horizontal, 12)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 8))
|
|
463
|
+
.overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(.line, lineWidth: 1 / displayScale))
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**不要**:用彩色的编辑器配色主题(和黑白灰的整体风格冲突);给代码块加阴影;自动换行打乱缩进。
|
|
467
|
+
"><div class="component-head"><h3>CodeBlock</h3><span>代码与命令</span><a href="project/components/CodeBlock/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="CodeBlock 组件预览" src="project/components/CodeBlock/preview.html" style="height:202px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>CodeBlock</h1>
|
|
468
|
+
<p>展示代码、命令或结构化数据,带语言标签和复制按钮。</p>
|
|
469
|
+
<p><strong>需要提供</strong>:代码文本、语言名称;可选的文件名(替代语言名显示在表头)。</p>
|
|
470
|
+
<p><strong>规格</strong></p>
|
|
471
|
+
<ul>
|
|
472
|
+
<li><code>surface-sunken</code> 底、<code>line</code> 细线描边、<code>radius-md</code>。表头高 28pt,左边是语言名或文件名(11pt 等宽、<code>ink-tertiary</code>),右边是一个小号文字按钮「复制」;点击后显示「已复制」1.5 秒。</li>
|
|
473
|
+
<li>代码字体 <code>code-mac</code> 或 <code>code-ios</code>,内边距上下 <code>space-2</code>、左右 <code>space-3</code>,过长时横向滚动,不自动换行。</li>
|
|
474
|
+
<li>语法高亮只用灰阶:关键字 <code>ink</code> 加粗,字符串 <code>ink-secondary</code>,注释 <code>ink-tertiary</code> 加斜体,其余 <code>ink</code>。这些颜色在 <code>surface-sunken</code> 上都不低于 4.5:1。</li>
|
|
475
|
+
</ul>
|
|
476
|
+
<pre><code class="language-swift">VStack(alignment: .leading, spacing: 0) {
|
|
477
|
+
HStack {
|
|
478
|
+
Text(language).font(.caption.monospaced()).foregroundStyle(.inkTertiary)
|
|
479
|
+
Spacer()
|
|
480
|
+
Button(copied ? "已复制" : "复制", systemImage: copied ? "checkmark" : "doc.on.doc") { copy() }
|
|
481
|
+
.buttonStyle(.borderless).controlSize(.small)
|
|
482
|
+
}
|
|
483
|
+
.padding(.horizontal, 12).frame(height: 28)
|
|
484
|
+
Divider()
|
|
485
|
+
ScrollView(.horizontal) {
|
|
486
|
+
Text(highlighted) // 用 AttributedString 按上面的颜色着色
|
|
487
|
+
.font(.system(.callout, design: .monospaced))
|
|
488
|
+
.textSelection(.enabled)
|
|
489
|
+
.padding(.vertical, 8).padding(.horizontal, 12)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 8))
|
|
493
|
+
.overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(.line, lineWidth: 1 / displayScale))
|
|
494
|
+
</code></pre>
|
|
495
|
+
<p><strong>不要</strong>:用彩色的编辑器配色主题(和黑白灰的整体风格冲突);给代码块加阴影;自动换行打乱缩进。</p>
|
|
496
|
+
</div></details></section>
|
|
497
|
+
<section class="component" id="c-Composer" data-search="Composer 多行输入与发送 # Composer
|
|
498
|
+
|
|
499
|
+
多行输入框加一条工具栏,用于发消息、写笔记、向 AI 提问。
|
|
500
|
+
|
|
501
|
+
**需要提供**:绑定的文本、占位符、发送动作、是否可以发送(内容为空或正在生成时禁用)。可选:附件列表、左侧的工具按钮(添加附件、模式选择)、右侧的状态文字(剩余次数、字数)。
|
|
502
|
+
|
|
503
|
+
**规格**
|
|
504
|
+
|
|
505
|
+
- 容器:`surface-raised` 底、`line` 细线描边、`radius-lg`、`shadow-float`;内边距上、右、下 8,左 12。获得焦点时描边加深为 `line-control`。
|
|
506
|
+
- 文字:Mac 13pt(`mac-body`),iOS 17pt(`ios-body`)。高度随内容增长,最多 8 行,超出后内部滚动。
|
|
507
|
+
- 附件:放在输入区上方,`surface-sunken` 底、`radius-sm` 的文件条。
|
|
508
|
+
- 工具条:左边是 24pt 的图标按钮和 20pt 高的胶囊形选择器(`line` 描边),右边是状态文字和发送按钮。
|
|
509
|
+
- 发送按钮:24pt 圆形,`accent` 底、`on-accent` 箭头(浅色主题黑底白箭头)。不能发送时换成 `fill-selected` 底、`ink-tertiary` 箭头。正在生成时变成「停止」方块。
|
|
510
|
+
- Mac 上 Return 发送,Shift-Return 换行;iOS 上换行键就是换行,只能点按钮发送。
|
|
511
|
+
|
|
512
|
+
```swift
|
|
513
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
514
|
+
TextField("写点什么,或拖入文件", text: $draft, axis: .vertical)
|
|
515
|
+
.lineLimit(1...8)
|
|
516
|
+
.textFieldStyle(.plain)
|
|
517
|
+
.onSubmit(send) // Mac:Return 发送
|
|
518
|
+
HStack(spacing: 4) {
|
|
519
|
+
Button("添加附件", systemImage: "plus") { attach() }
|
|
520
|
+
.labelStyle(.iconOnly).buttonStyle(.borderless)
|
|
521
|
+
Spacer()
|
|
522
|
+
Button(action: send) {
|
|
523
|
+
Image(systemName: "arrow.up")
|
|
524
|
+
.fontWeight(.semibold)
|
|
525
|
+
.frame(width: 24, height: 24)
|
|
526
|
+
}
|
|
527
|
+
.buttonStyle(.borderedProminent)
|
|
528
|
+
.buttonBorderShape(.circle)
|
|
529
|
+
.disabled(draft.isEmpty)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
.padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 8))
|
|
533
|
+
.background(.surfaceRaised, in: .rect(cornerRadius: 12))
|
|
534
|
+
.overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.line, lineWidth: 1 / displayScale))
|
|
535
|
+
.shadow(color: .black.opacity(0.08), radius: 10, y: 6)
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**不要**:把 Composer 做成玻璃(它承载的是内容);把发送按钮放到框外;禁用时把按钮直接隐藏,让布局跳动。
|
|
539
|
+
"><div class="component-head"><h3>Composer</h3><span>多行输入与发送</span><a href="project/components/Composer/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Composer 组件预览" src="project/components/Composer/preview.html" style="height:272px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Composer</h1>
|
|
540
|
+
<p>多行输入框加一条工具栏,用于发消息、写笔记、向 AI 提问。</p>
|
|
541
|
+
<p><strong>需要提供</strong>:绑定的文本、占位符、发送动作、是否可以发送(内容为空或正在生成时禁用)。可选:附件列表、左侧的工具按钮(添加附件、模式选择)、右侧的状态文字(剩余次数、字数)。</p>
|
|
542
|
+
<p><strong>规格</strong></p>
|
|
543
|
+
<ul>
|
|
544
|
+
<li>容器:<code>surface-raised</code> 底、<code>line</code> 细线描边、<code>radius-lg</code>、<code>shadow-float</code>;内边距上、右、下 8,左 12。获得焦点时描边加深为 <code>line-control</code>。</li>
|
|
545
|
+
<li>文字:Mac 13pt(<code>mac-body</code>),iOS 17pt(<code>ios-body</code>)。高度随内容增长,最多 8 行,超出后内部滚动。</li>
|
|
546
|
+
<li>附件:放在输入区上方,<code>surface-sunken</code> 底、<code>radius-sm</code> 的文件条。</li>
|
|
547
|
+
<li>工具条:左边是 24pt 的图标按钮和 20pt 高的胶囊形选择器(<code>line</code> 描边),右边是状态文字和发送按钮。</li>
|
|
548
|
+
<li>发送按钮:24pt 圆形,<code>accent</code> 底、<code>on-accent</code> 箭头(浅色主题黑底白箭头)。不能发送时换成 <code>fill-selected</code> 底、<code>ink-tertiary</code> 箭头。正在生成时变成「停止」方块。</li>
|
|
549
|
+
<li>Mac 上 Return 发送,Shift-Return 换行;iOS 上换行键就是换行,只能点按钮发送。</li>
|
|
550
|
+
</ul>
|
|
551
|
+
<pre><code class="language-swift">VStack(alignment: .leading, spacing: 4) {
|
|
552
|
+
TextField("写点什么,或拖入文件", text: $draft, axis: .vertical)
|
|
553
|
+
.lineLimit(1...8)
|
|
554
|
+
.textFieldStyle(.plain)
|
|
555
|
+
.onSubmit(send) // Mac:Return 发送
|
|
556
|
+
HStack(spacing: 4) {
|
|
557
|
+
Button("添加附件", systemImage: "plus") { attach() }
|
|
558
|
+
.labelStyle(.iconOnly).buttonStyle(.borderless)
|
|
559
|
+
Spacer()
|
|
560
|
+
Button(action: send) {
|
|
561
|
+
Image(systemName: "arrow.up")
|
|
562
|
+
.fontWeight(.semibold)
|
|
563
|
+
.frame(width: 24, height: 24)
|
|
564
|
+
}
|
|
565
|
+
.buttonStyle(.borderedProminent)
|
|
566
|
+
.buttonBorderShape(.circle)
|
|
567
|
+
.disabled(draft.isEmpty)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
.padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 8))
|
|
571
|
+
.background(.surfaceRaised, in: .rect(cornerRadius: 12))
|
|
572
|
+
.overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.line, lineWidth: 1 / displayScale))
|
|
573
|
+
.shadow(color: .black.opacity(0.08), radius: 10, y: 6)
|
|
574
|
+
</code></pre>
|
|
575
|
+
<p><strong>不要</strong>:把 Composer 做成玻璃(它承载的是内容);把发送按钮放到框外;禁用时把按钮直接隐藏,让布局跳动。</p>
|
|
576
|
+
</div></details></section>
|
|
577
|
+
<section class="component" id="c-Cover" data-search="Cover 组合预览。用于对齐布局、尺寸与层级。"><div class="component-head"><h3>Cover</h3><span></span><a href="project/components/Cover/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Cover 组件预览" src="project/components/Cover/preview.html" style="height:320px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><p>组合预览。用于对齐布局、尺寸与层级。</p>
|
|
578
|
+
</div></details></section>
|
|
579
|
+
<section class="component" id="c-EmptyState" data-search="EmptyState 没有内容时告诉用户下一步 # EmptyState
|
|
580
|
+
|
|
581
|
+
列表、搜索结果或整个页面没有内容时显示:说明现在是什么状态,再给出下一步。
|
|
582
|
+
|
|
583
|
+
**需要提供**:SF Symbol、标题(描述状态)、一句正文(告诉用户怎么开始),以及最多一个主操作和一个次操作。
|
|
584
|
+
|
|
585
|
+
**规格**
|
|
586
|
+
|
|
587
|
+
- 在可用区域里居中。图标 28pt、细线、`ink-tertiary` 色;标题用 `mac-display` 或 `ios-title`(衬线);正文 `ink-secondary`,最大宽度 320pt。
|
|
588
|
+
- 按钮用大号:Mac 是 `control-mac-lg`,iOS 是 `control-ios`。
|
|
589
|
+
- 搜索没有结果时不放按钮,标题写成「没有找到"关键词"」,正文给出修改建议。
|
|
590
|
+
|
|
591
|
+
```swift
|
|
592
|
+
ContentUnavailableView {
|
|
593
|
+
Label("还没有笔记", systemImage: "doc.text")
|
|
594
|
+
.font(.system(.title2, design: .serif))
|
|
595
|
+
} description: {
|
|
596
|
+
Text("写下第一条想法,它会同步到你所有的设备。")
|
|
597
|
+
} actions: {
|
|
598
|
+
Button("新建笔记") { create() }.buttonStyle(.borderedProminent)
|
|
599
|
+
Button("从文件导入") { importFile() }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 搜索没有结果
|
|
603
|
+
ContentUnavailableView.search(text: query)
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
**不要**:用插画或吉祥物填满空白;标题写「空空如也」这种玩笑话;出现三个以上的按钮。
|
|
607
|
+
"><div class="component-head"><h3>EmptyState</h3><span>没有内容时告诉用户下一步</span><a href="project/components/EmptyState/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="EmptyState 组件预览" src="project/components/EmptyState/preview.html" style="height:262px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>EmptyState</h1>
|
|
608
|
+
<p>列表、搜索结果或整个页面没有内容时显示:说明现在是什么状态,再给出下一步。</p>
|
|
609
|
+
<p><strong>需要提供</strong>:SF Symbol、标题(描述状态)、一句正文(告诉用户怎么开始),以及最多一个主操作和一个次操作。</p>
|
|
610
|
+
<p><strong>规格</strong></p>
|
|
611
|
+
<ul>
|
|
612
|
+
<li>在可用区域里居中。图标 28pt、细线、<code>ink-tertiary</code> 色;标题用 <code>mac-display</code> 或 <code>ios-title</code>(衬线);正文 <code>ink-secondary</code>,最大宽度 320pt。</li>
|
|
613
|
+
<li>按钮用大号:Mac 是 <code>control-mac-lg</code>,iOS 是 <code>control-ios</code>。</li>
|
|
614
|
+
<li>搜索没有结果时不放按钮,标题写成「没有找到"关键词"」,正文给出修改建议。</li>
|
|
615
|
+
</ul>
|
|
616
|
+
<pre><code class="language-swift">ContentUnavailableView {
|
|
617
|
+
Label("还没有笔记", systemImage: "doc.text")
|
|
618
|
+
.font(.system(.title2, design: .serif))
|
|
619
|
+
} description: {
|
|
620
|
+
Text("写下第一条想法,它会同步到你所有的设备。")
|
|
621
|
+
} actions: {
|
|
622
|
+
Button("新建笔记") { create() }.buttonStyle(.borderedProminent)
|
|
623
|
+
Button("从文件导入") { importFile() }
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// 搜索没有结果
|
|
627
|
+
ContentUnavailableView.search(text: query)
|
|
628
|
+
</code></pre>
|
|
629
|
+
<p><strong>不要</strong>:用插画或吉祥物填满空白;标题写「空空如也」这种玩笑话;出现三个以上的按钮。</p>
|
|
630
|
+
</div></details></section>
|
|
631
|
+
<section class="component" id="c-GlassControls" data-search="GlassControls 浮在内容之上的操作层 # GlassControls
|
|
632
|
+
|
|
633
|
+
浮在内容之上的玻璃操作层:工具栏按钮组、标签栏、浮动操作按钮。
|
|
634
|
+
|
|
635
|
+
**什么时候用**:用户在浏览内容时随时要用到的操作,比如新建、搜索、切换页面、分享。内容本身永远不放在玻璃上。
|
|
636
|
+
|
|
637
|
+
**需要提供**:每个按钮的 SF Symbol 和无障碍标签(`accessibilityLabel`)、点击动作;如果是标签栏,还要提供当前选中项。
|
|
638
|
+
|
|
639
|
+
**规格**
|
|
640
|
+
|
|
641
|
+
- 单个玻璃按钮:圆形,`control-ios`(44)。
|
|
642
|
+
- 按钮组:胶囊形,内部按钮 36pt,间距 2pt。相邻的玻璃元素放进同一个 `GlassEffectContainer`,间距 `space-3`。
|
|
643
|
+
- 选中项:图标从 `ink-secondary` 变成 `ink`,底色 `fill-selected`。
|
|
644
|
+
- 实色玻璃(`accent` 底、`on-accent` 图标,也就是黑底白图标)一屏只用一次,留给主操作,比如「新建」。
|
|
645
|
+
|
|
646
|
+
```swift
|
|
647
|
+
// 标准标签栏与工具栏,系统自动渲染为玻璃
|
|
648
|
+
TabView {
|
|
649
|
+
Tab("笔记", systemImage: "doc.text") { NotesView() }
|
|
650
|
+
Tab("收藏", systemImage: "star") { FavoritesView() }
|
|
651
|
+
Tab(role: .search) { SearchView() } // 搜索单独成为右侧的圆形玻璃按钮
|
|
652
|
+
}
|
|
653
|
+
.tabBarMinimizeBehavior(.onScrollDown)
|
|
654
|
+
.tint(.brandAccent)
|
|
655
|
+
|
|
656
|
+
// 自定义浮动按钮
|
|
657
|
+
GlassEffectContainer(spacing: 12) {
|
|
658
|
+
HStack(spacing: 12) {
|
|
659
|
+
Button("搜索", systemImage: "magnifyingglass") { search() }
|
|
660
|
+
.buttonStyle(.glass)
|
|
661
|
+
Button("新建", systemImage: "plus") { create() }
|
|
662
|
+
.buttonStyle(.glassProminent)
|
|
663
|
+
}
|
|
664
|
+
.labelStyle(.iconOnly)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 完全自定义形状时
|
|
668
|
+
Image(systemName: "mic")
|
|
669
|
+
.frame(width: 44, height: 44)
|
|
670
|
+
.glassEffect(.regular.interactive(), in: .circle)
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**不要**:在玻璃上放段落文字或列表;玻璃叠玻璃;给工具栏加实色背景挡住滚动边缘效果;同一屏出现两个实色玻璃。
|
|
674
|
+
"><div class="component-head"><h3>GlassControls</h3><span>浮在内容之上的操作层</span><a href="project/components/GlassControls/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="GlassControls 组件预览" src="project/components/GlassControls/preview.html" style="height:292px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>GlassControls</h1>
|
|
675
|
+
<p>浮在内容之上的玻璃操作层:工具栏按钮组、标签栏、浮动操作按钮。</p>
|
|
676
|
+
<p><strong>什么时候用</strong>:用户在浏览内容时随时要用到的操作,比如新建、搜索、切换页面、分享。内容本身永远不放在玻璃上。</p>
|
|
677
|
+
<p><strong>需要提供</strong>:每个按钮的 SF Symbol 和无障碍标签(<code>accessibilityLabel</code>)、点击动作;如果是标签栏,还要提供当前选中项。</p>
|
|
678
|
+
<p><strong>规格</strong></p>
|
|
679
|
+
<ul>
|
|
680
|
+
<li>单个玻璃按钮:圆形,<code>control-ios</code>(44)。</li>
|
|
681
|
+
<li>按钮组:胶囊形,内部按钮 36pt,间距 2pt。相邻的玻璃元素放进同一个 <code>GlassEffectContainer</code>,间距 <code>space-3</code>。</li>
|
|
682
|
+
<li>选中项:图标从 <code>ink-secondary</code> 变成 <code>ink</code>,底色 <code>fill-selected</code>。</li>
|
|
683
|
+
<li>实色玻璃(<code>accent</code> 底、<code>on-accent</code> 图标,也就是黑底白图标)一屏只用一次,留给主操作,比如「新建」。</li>
|
|
684
|
+
</ul>
|
|
685
|
+
<pre><code class="language-swift">// 标准标签栏与工具栏,系统自动渲染为玻璃
|
|
686
|
+
TabView {
|
|
687
|
+
Tab("笔记", systemImage: "doc.text") { NotesView() }
|
|
688
|
+
Tab("收藏", systemImage: "star") { FavoritesView() }
|
|
689
|
+
Tab(role: .search) { SearchView() } // 搜索单独成为右侧的圆形玻璃按钮
|
|
690
|
+
}
|
|
691
|
+
.tabBarMinimizeBehavior(.onScrollDown)
|
|
692
|
+
.tint(.brandAccent)
|
|
693
|
+
|
|
694
|
+
// 自定义浮动按钮
|
|
695
|
+
GlassEffectContainer(spacing: 12) {
|
|
696
|
+
HStack(spacing: 12) {
|
|
697
|
+
Button("搜索", systemImage: "magnifyingglass") { search() }
|
|
698
|
+
.buttonStyle(.glass)
|
|
699
|
+
Button("新建", systemImage: "plus") { create() }
|
|
700
|
+
.buttonStyle(.glassProminent)
|
|
701
|
+
}
|
|
702
|
+
.labelStyle(.iconOnly)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 完全自定义形状时
|
|
706
|
+
Image(systemName: "mic")
|
|
707
|
+
.frame(width: 44, height: 44)
|
|
708
|
+
.glassEffect(.regular.interactive(), in: .circle)
|
|
709
|
+
</code></pre>
|
|
710
|
+
<p><strong>不要</strong>:在玻璃上放段落文字或列表;玻璃叠玻璃;给工具栏加实色背景挡住滚动边缘效果;同一屏出现两个实色玻璃。</p>
|
|
711
|
+
</div></details></section>
|
|
712
|
+
<section class="component" id="c-ListGroup" data-search="ListGroup iOS 分组列表与设置页 # ListGroup
|
|
713
|
+
|
|
714
|
+
iOS 分组列表,用于设置页、详情页和各种表单。
|
|
715
|
+
|
|
716
|
+
**需要提供**:分组,每个分组可以有页头和页脚说明;每一行提供标题,以及可选的图标、右侧的值、开关或箭头,点击后跳转或执行动作。
|
|
717
|
+
|
|
718
|
+
**规格**
|
|
719
|
+
|
|
720
|
+
- 页面底色 `canvas`,分组底色 `surface-raised`,`radius-lg`,左右边距 `space-4`,分组之间 `space-5`。
|
|
721
|
+
- 每行最小高度 `control-ios`(44),左右内边距 `space-4`,文字 17pt `ink`;右侧的值用 `ink-secondary`,箭头用 `ink-tertiary`。
|
|
722
|
+
- 图标放在 28pt 的方块里,`accent-soft` 浅灰底、`ink` 图标、`radius-sm`。整个列表统一用灰色,不要每行一个颜色。
|
|
723
|
+
- 分隔线 `stroke-hairline`、`line` 色,从文字起点开始。
|
|
724
|
+
- 页头 13pt `ink-secondary`,页脚 13pt `ink-tertiary`,页脚写清楚这个设置会带来什么后果。
|
|
725
|
+
- 开关打开时用 `success` 色。不要用黑白色调的开关:深色主题下白色轨道会把白色圆钮吞掉。
|
|
726
|
+
|
|
727
|
+
```swift
|
|
728
|
+
Form {
|
|
729
|
+
Section("账号") {
|
|
730
|
+
NavigationLink { ProfileView() } label: {
|
|
731
|
+
LabeledContent { Text("林一") } label: { Label("个人资料", systemImage: "person") }
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
Section {
|
|
735
|
+
Toggle(isOn: $iCloudSync) { Label("iCloud 同步", systemImage: "icloud") }
|
|
736
|
+
.tint(.success)
|
|
737
|
+
} header: {
|
|
738
|
+
Text("同步")
|
|
739
|
+
} footer: {
|
|
740
|
+
Text("关闭 iCloud 同步后,新笔记只保存在这台设备上。")
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
.scrollContentBackground(.hidden)
|
|
744
|
+
.background(.canvas)
|
|
745
|
+
.tint(.brandAccent)
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Mac 上的设置窗口同样用 `Form` 加 `.formStyle(.grouped)`,系统会给出对应的 Mac 样式。
|
|
749
|
+
|
|
750
|
+
**不要**:每行用不同颜色的图标;页脚写成一大段;在列表行里放主按钮(主按钮放在页面底部)。
|
|
751
|
+
"><div class="component-head"><h3>ListGroup</h3><span>iOS 分组列表与设置页</span><a href="project/components/ListGroup/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="ListGroup 组件预览" src="project/components/ListGroup/preview.html" style="height:452px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>ListGroup</h1>
|
|
752
|
+
<p>iOS 分组列表,用于设置页、详情页和各种表单。</p>
|
|
753
|
+
<p><strong>需要提供</strong>:分组,每个分组可以有页头和页脚说明;每一行提供标题,以及可选的图标、右侧的值、开关或箭头,点击后跳转或执行动作。</p>
|
|
754
|
+
<p><strong>规格</strong></p>
|
|
755
|
+
<ul>
|
|
756
|
+
<li>页面底色 <code>canvas</code>,分组底色 <code>surface-raised</code>,<code>radius-lg</code>,左右边距 <code>space-4</code>,分组之间 <code>space-5</code>。</li>
|
|
757
|
+
<li>每行最小高度 <code>control-ios</code>(44),左右内边距 <code>space-4</code>,文字 17pt <code>ink</code>;右侧的值用 <code>ink-secondary</code>,箭头用 <code>ink-tertiary</code>。</li>
|
|
758
|
+
<li>图标放在 28pt 的方块里,<code>accent-soft</code> 浅灰底、<code>ink</code> 图标、<code>radius-sm</code>。整个列表统一用灰色,不要每行一个颜色。</li>
|
|
759
|
+
<li>分隔线 <code>stroke-hairline</code>、<code>line</code> 色,从文字起点开始。</li>
|
|
760
|
+
<li>页头 13pt <code>ink-secondary</code>,页脚 13pt <code>ink-tertiary</code>,页脚写清楚这个设置会带来什么后果。</li>
|
|
761
|
+
<li>开关打开时用 <code>success</code> 色。不要用黑白色调的开关:深色主题下白色轨道会把白色圆钮吞掉。</li>
|
|
762
|
+
</ul>
|
|
763
|
+
<pre><code class="language-swift">Form {
|
|
764
|
+
Section("账号") {
|
|
765
|
+
NavigationLink { ProfileView() } label: {
|
|
766
|
+
LabeledContent { Text("林一") } label: { Label("个人资料", systemImage: "person") }
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
Section {
|
|
770
|
+
Toggle(isOn: $iCloudSync) { Label("iCloud 同步", systemImage: "icloud") }
|
|
771
|
+
.tint(.success)
|
|
772
|
+
} header: {
|
|
773
|
+
Text("同步")
|
|
774
|
+
} footer: {
|
|
775
|
+
Text("关闭 iCloud 同步后,新笔记只保存在这台设备上。")
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
.scrollContentBackground(.hidden)
|
|
779
|
+
.background(.canvas)
|
|
780
|
+
.tint(.brandAccent)
|
|
781
|
+
</code></pre>
|
|
782
|
+
<p>Mac 上的设置窗口同样用 <code>Form</code> 加 <code>.formStyle(.grouped)</code>,系统会给出对应的 Mac 样式。</p>
|
|
783
|
+
<p><strong>不要</strong>:每行用不同颜色的图标;页脚写成一大段;在列表行里放主按钮(主按钮放在页面底部)。</p>
|
|
784
|
+
</div></details></section>
|
|
785
|
+
<section class="component" id="c-MacWindow" data-search="MacWindow 组合示例:Mac 窗口 # MacWindow
|
|
786
|
+
|
|
787
|
+
Mac 应用窗口的标准结构:左边是悬浮的玻璃侧边栏,右边内容区的上方浮着玻璃工具栏,下方浮着 Composer。
|
|
788
|
+
|
|
789
|
+
**结构**
|
|
790
|
+
|
|
791
|
+
- 窗口底色 `canvas`。侧边栏是 `NavigationSplitView` 的 sidebar 列,系统会渲染成悬浮的玻璃面板。
|
|
792
|
+
- 工具栏用 `.toolbar` 声明,标题用 `mac-headline`;右侧的按钮组由系统渲染成玻璃胶囊。内容从工具栏下面滚过去。
|
|
793
|
+
- 内容区居中,阅读栏宽不超过 `content-max`,左右边距 `space-4`。
|
|
794
|
+
- 底部的 Composer 悬浮在内容之上,它承载输入的内容,所以是纸面,不是玻璃。用 `.safeAreaInset(edge: .bottom)` 放置,让内容可以滚到它的上方。
|
|
795
|
+
|
|
796
|
+
```swift
|
|
797
|
+
NavigationSplitView {
|
|
798
|
+
SidebarView()
|
|
799
|
+
} detail: {
|
|
800
|
+
ScrollView { ThreadView().frame(maxWidth: 680) }
|
|
801
|
+
.safeAreaInset(edge: .bottom) { Composer().padding(16) }
|
|
802
|
+
.navigationTitle(chat.title)
|
|
803
|
+
.toolbar {
|
|
804
|
+
ToolbarItemGroup {
|
|
805
|
+
ShareLink(item: chat.url)
|
|
806
|
+
Menu("更多", systemImage: "ellipsis") { … }
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
.background(.canvas)
|
|
811
|
+
```
|
|
812
|
+
"><div class="component-head"><h3>MacWindow</h3><span>组合示例:Mac 窗口</span><a href="project/components/MacWindow/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="MacWindow 组件预览" src="project/components/MacWindow/preview.html" style="height:622px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>MacWindow</h1>
|
|
813
|
+
<p>Mac 应用窗口的标准结构:左边是悬浮的玻璃侧边栏,右边内容区的上方浮着玻璃工具栏,下方浮着 Composer。</p>
|
|
814
|
+
<p><strong>结构</strong></p>
|
|
815
|
+
<ul>
|
|
816
|
+
<li>窗口底色 <code>canvas</code>。侧边栏是 <code>NavigationSplitView</code> 的 sidebar 列,系统会渲染成悬浮的玻璃面板。</li>
|
|
817
|
+
<li>工具栏用 <code>.toolbar</code> 声明,标题用 <code>mac-headline</code>;右侧的按钮组由系统渲染成玻璃胶囊。内容从工具栏下面滚过去。</li>
|
|
818
|
+
<li>内容区居中,阅读栏宽不超过 <code>content-max</code>,左右边距 <code>space-4</code>。</li>
|
|
819
|
+
<li>底部的 Composer 悬浮在内容之上,它承载输入的内容,所以是纸面,不是玻璃。用 <code>.safeAreaInset(edge: .bottom)</code> 放置,让内容可以滚到它的上方。</li>
|
|
820
|
+
</ul>
|
|
821
|
+
<pre><code class="language-swift">NavigationSplitView {
|
|
822
|
+
SidebarView()
|
|
823
|
+
} detail: {
|
|
824
|
+
ScrollView { ThreadView().frame(maxWidth: 680) }
|
|
825
|
+
.safeAreaInset(edge: .bottom) { Composer().padding(16) }
|
|
826
|
+
.navigationTitle(chat.title)
|
|
827
|
+
.toolbar {
|
|
828
|
+
ToolbarItemGroup {
|
|
829
|
+
ShareLink(item: chat.url)
|
|
830
|
+
Menu("更多", systemImage: "ellipsis") { … }
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
.background(.canvas)
|
|
835
|
+
</code></pre>
|
|
836
|
+
</div></details></section>
|
|
837
|
+
<section class="component" id="c-Message" data-search="Message 对话:用户气泡与衬线回答 # Message
|
|
838
|
+
|
|
839
|
+
对话里的一条消息。用户说的话放在凹下去的气泡里,AI 的回答直接排在纸面上,用衬线字体,像一页文章。
|
|
840
|
+
|
|
841
|
+
**需要提供**:发送者(用户或 AI)、内容(Markdown,可以包含代码块、列表、链接)、AI 消息的操作(复制、重新生成),以及生成中的状态。
|
|
842
|
+
|
|
843
|
+
**规格**
|
|
844
|
+
|
|
845
|
+
- 用户消息:靠右,最大宽度 80%,`surface-sunken` 底,`radius-lg`,内边距上下 6pt、左右 `space-3`;Mac 用 `mac-body`,iOS 用 `ios-callout`。
|
|
846
|
+
- AI 消息:靠左,不加气泡,正文用 `mac-prose` 或 `ios-prose`,栏宽不超过 `content-max`。代码块用 CodeBlock,执行步骤用 StepRow。
|
|
847
|
+
- 消息之间间距 `space-4`,段落之间 `space-2`。
|
|
848
|
+
- 操作按钮放在 AI 消息下方,22pt 图标按钮,`ink-tertiary` 色,Mac 上只在悬停时显示,iOS 上一直显示。
|
|
849
|
+
- 链接用 `ink` 色,加 1pt 的 `line-control` 色下划线,下偏移 3pt。
|
|
850
|
+
- 生成中:回答末尾显示一个闪烁的光标,Composer 的发送按钮变成「停止」。
|
|
851
|
+
|
|
852
|
+
```swift
|
|
853
|
+
// 用户消息
|
|
854
|
+
Text(message.text)
|
|
855
|
+
.padding(.vertical, 6).padding(.horizontal, 12)
|
|
856
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 12))
|
|
857
|
+
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
858
|
+
|
|
859
|
+
// AI 消息:用 AttributedString(markdown:) 渲染,或接入 Markdown 渲染库
|
|
860
|
+
Text(try AttributedString(markdown: message.text))
|
|
861
|
+
.font(.system(.body, design: .serif))
|
|
862
|
+
.lineSpacing(4)
|
|
863
|
+
.textSelection(.enabled)
|
|
864
|
+
.frame(maxWidth: 680, alignment: .leading)
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
**不要**:给 AI 回答也加气泡;用头像区分谁在说话(靠位置和字体就够了);在正文里用彩色强调文字(强调用加粗)。
|
|
868
|
+
"><div class="component-head"><h3>Message</h3><span>对话:用户气泡与衬线回答</span><a href="project/components/Message/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Message 组件预览" src="project/components/Message/preview.html" style="height:322px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Message</h1>
|
|
869
|
+
<p>对话里的一条消息。用户说的话放在凹下去的气泡里,AI 的回答直接排在纸面上,用衬线字体,像一页文章。</p>
|
|
870
|
+
<p><strong>需要提供</strong>:发送者(用户或 AI)、内容(Markdown,可以包含代码块、列表、链接)、AI 消息的操作(复制、重新生成),以及生成中的状态。</p>
|
|
871
|
+
<p><strong>规格</strong></p>
|
|
872
|
+
<ul>
|
|
873
|
+
<li>用户消息:靠右,最大宽度 80%,<code>surface-sunken</code> 底,<code>radius-lg</code>,内边距上下 6pt、左右 <code>space-3</code>;Mac 用 <code>mac-body</code>,iOS 用 <code>ios-callout</code>。</li>
|
|
874
|
+
<li>AI 消息:靠左,不加气泡,正文用 <code>mac-prose</code> 或 <code>ios-prose</code>,栏宽不超过 <code>content-max</code>。代码块用 CodeBlock,执行步骤用 StepRow。</li>
|
|
875
|
+
<li>消息之间间距 <code>space-4</code>,段落之间 <code>space-2</code>。</li>
|
|
876
|
+
<li>操作按钮放在 AI 消息下方,22pt 图标按钮,<code>ink-tertiary</code> 色,Mac 上只在悬停时显示,iOS 上一直显示。</li>
|
|
877
|
+
<li>链接用 <code>ink</code> 色,加 1pt 的 <code>line-control</code> 色下划线,下偏移 3pt。</li>
|
|
878
|
+
<li>生成中:回答末尾显示一个闪烁的光标,Composer 的发送按钮变成「停止」。</li>
|
|
879
|
+
</ul>
|
|
880
|
+
<pre><code class="language-swift">// 用户消息
|
|
881
|
+
Text(message.text)
|
|
882
|
+
.padding(.vertical, 6).padding(.horizontal, 12)
|
|
883
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 12))
|
|
884
|
+
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
885
|
+
|
|
886
|
+
// AI 消息:用 AttributedString(markdown:) 渲染,或接入 Markdown 渲染库
|
|
887
|
+
Text(try AttributedString(markdown: message.text))
|
|
888
|
+
.font(.system(.body, design: .serif))
|
|
889
|
+
.lineSpacing(4)
|
|
890
|
+
.textSelection(.enabled)
|
|
891
|
+
.frame(maxWidth: 680, alignment: .leading)
|
|
892
|
+
</code></pre>
|
|
893
|
+
<p><strong>不要</strong>:给 AI 回答也加气泡;用头像区分谁在说话(靠位置和字体就够了);在正文里用彩色强调文字(强调用加粗)。</p>
|
|
894
|
+
</div></details></section>
|
|
895
|
+
<section class="component" id="c-PhoneScreen" data-search="PhoneScreen 组合示例:iPhone 列表页与对话页 # PhoneScreen
|
|
896
|
+
|
|
897
|
+
iPhone 上的两种典型页面:一个列表页,一个对话页。展示纸面和玻璃在手机上怎么分层。
|
|
898
|
+
|
|
899
|
+
**列表页**
|
|
900
|
+
|
|
901
|
+
- 衬线大标题(`ios-large-title`),内容是 `radius-lg` 的卡片,左右边距 `space-4`,卡片之间 `space-2`。
|
|
902
|
+
- 底部是玻璃标签栏,搜索单独成为右侧的圆形玻璃按钮(`Tab(role: .search)`)。向下滚动时标签栏收起。
|
|
903
|
+
|
|
904
|
+
**对话页**
|
|
905
|
+
|
|
906
|
+
- 顶部的返回和更多按钮是系统导航栏提供的玻璃圆形按钮,标题用 17pt 半粗。
|
|
907
|
+
- 对话内容:AI 回答用 `ios-prose`,用户消息用 `ios-callout`。
|
|
908
|
+
- 底部的 Composer 收成单行胶囊(`radius-xl`),用 `.safeAreaInset(edge: .bottom)` 悬浮在内容上方;输入多行时向上增长。
|
|
909
|
+
|
|
910
|
+
```swift
|
|
911
|
+
NavigationStack {
|
|
912
|
+
ScrollView { ThreadView().padding(.horizontal, 16) }
|
|
913
|
+
.safeAreaInset(edge: .bottom) { Composer().padding(.horizontal, 12) }
|
|
914
|
+
.navigationTitle("周报整理")
|
|
915
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
916
|
+
.toolbar { ToolbarItem { Menu("更多", systemImage: "ellipsis") { … } } }
|
|
917
|
+
}
|
|
918
|
+
.background(.canvas)
|
|
919
|
+
```
|
|
920
|
+
"><div class="component-head"><h3>PhoneScreen</h3><span>组合示例:iPhone 列表页与对话页</span><a href="project/components/PhoneScreen/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="PhoneScreen 组件预览" src="project/components/PhoneScreen/preview.html" style="height:812px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>PhoneScreen</h1>
|
|
921
|
+
<p>iPhone 上的两种典型页面:一个列表页,一个对话页。展示纸面和玻璃在手机上怎么分层。</p>
|
|
922
|
+
<p><strong>列表页</strong></p>
|
|
923
|
+
<ul>
|
|
924
|
+
<li>衬线大标题(<code>ios-large-title</code>),内容是 <code>radius-lg</code> 的卡片,左右边距 <code>space-4</code>,卡片之间 <code>space-2</code>。</li>
|
|
925
|
+
<li>底部是玻璃标签栏,搜索单独成为右侧的圆形玻璃按钮(<code>Tab(role: .search)</code>)。向下滚动时标签栏收起。</li>
|
|
926
|
+
</ul>
|
|
927
|
+
<p><strong>对话页</strong></p>
|
|
928
|
+
<ul>
|
|
929
|
+
<li>顶部的返回和更多按钮是系统导航栏提供的玻璃圆形按钮,标题用 17pt 半粗。</li>
|
|
930
|
+
<li>对话内容:AI 回答用 <code>ios-prose</code>,用户消息用 <code>ios-callout</code>。</li>
|
|
931
|
+
<li>底部的 Composer 收成单行胶囊(<code>radius-xl</code>),用 <code>.safeAreaInset(edge: .bottom)</code> 悬浮在内容上方;输入多行时向上增长。</li>
|
|
932
|
+
</ul>
|
|
933
|
+
<pre><code class="language-swift">NavigationStack {
|
|
934
|
+
ScrollView { ThreadView().padding(.horizontal, 16) }
|
|
935
|
+
.safeAreaInset(edge: .bottom) { Composer().padding(.horizontal, 12) }
|
|
936
|
+
.navigationTitle("周报整理")
|
|
937
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
938
|
+
.toolbar { ToolbarItem { Menu("更多", systemImage: "ellipsis") { … } } }
|
|
939
|
+
}
|
|
940
|
+
.background(.canvas)
|
|
941
|
+
</code></pre>
|
|
942
|
+
</div></details></section>
|
|
943
|
+
<section class="component" id="c-Segmented" data-search="Segmented 在 2–4 个视图之间切换 # Segmented
|
|
944
|
+
|
|
945
|
+
分段控件,在同一份数据的 2–4 种视图或范围之间切换。
|
|
946
|
+
|
|
947
|
+
**需要提供**:选项列表(每项 2–4 个字,或者一个 SF Symbol)和绑定的选中值。
|
|
948
|
+
|
|
949
|
+
**规格**:底槽 `fill-selected`;选中块 `surface-raised` 加 `shadow-control`;未选中文字 `ink-secondary`,选中文字 `ink`。Mac 上外层 `radius-sm`、内边距 2,内层 `radius-xs`,两层同心;高度 `control-mac-sm`。iOS 上是胶囊形,高 32pt。
|
|
950
|
+
|
|
951
|
+
```swift
|
|
952
|
+
Picker("视图", selection: $mode) {
|
|
953
|
+
Text("列表").tag(Mode.list)
|
|
954
|
+
Text("卡片").tag(Mode.cards)
|
|
955
|
+
Text("日历").tag(Mode.calendar)
|
|
956
|
+
}
|
|
957
|
+
.pickerStyle(.segmented)
|
|
958
|
+
.labelsHidden()
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
直接用系统控件就能得到上面的效果,不需要自己画。
|
|
962
|
+
|
|
963
|
+
**不要**:选项超过 4 个(改用 Menu 或 Picker);拿它当执行动作的按钮组;文字长短差别太大。
|
|
964
|
+
"><div class="component-head"><h3>Segmented</h3><span>在 2–4 个视图之间切换</span><a href="project/components/Segmented/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Segmented 组件预览" src="project/components/Segmented/preview.html" style="height:162px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Segmented</h1>
|
|
965
|
+
<p>分段控件,在同一份数据的 2–4 种视图或范围之间切换。</p>
|
|
966
|
+
<p><strong>需要提供</strong>:选项列表(每项 2–4 个字,或者一个 SF Symbol)和绑定的选中值。</p>
|
|
967
|
+
<p><strong>规格</strong>:底槽 <code>fill-selected</code>;选中块 <code>surface-raised</code> 加 <code>shadow-control</code>;未选中文字 <code>ink-secondary</code>,选中文字 <code>ink</code>。Mac 上外层 <code>radius-sm</code>、内边距 2,内层 <code>radius-xs</code>,两层同心;高度 <code>control-mac-sm</code>。iOS 上是胶囊形,高 32pt。</p>
|
|
968
|
+
<pre><code class="language-swift">Picker("视图", selection: $mode) {
|
|
969
|
+
Text("列表").tag(Mode.list)
|
|
970
|
+
Text("卡片").tag(Mode.cards)
|
|
971
|
+
Text("日历").tag(Mode.calendar)
|
|
972
|
+
}
|
|
973
|
+
.pickerStyle(.segmented)
|
|
974
|
+
.labelsHidden()
|
|
975
|
+
</code></pre>
|
|
976
|
+
<p>直接用系统控件就能得到上面的效果,不需要自己画。</p>
|
|
977
|
+
<p><strong>不要</strong>:选项超过 4 个(改用 Menu 或 Picker);拿它当执行动作的按钮组;文字长短差别太大。</p>
|
|
978
|
+
</div></details></section>
|
|
979
|
+
<section class="component" id="c-Sheet" data-search="Sheet iOS 底部弹窗,系统玻璃 # Sheet
|
|
980
|
+
|
|
981
|
+
从底部弹出的面板,用于一个短小、独立的任务:确认购买、编辑一个字段、选择分享方式。
|
|
982
|
+
|
|
983
|
+
**需要提供**:标题、一两句说明、内容(短表单或选项)、主操作和取消操作,以及要支持的高度档位。
|
|
984
|
+
|
|
985
|
+
**规格**
|
|
986
|
+
|
|
987
|
+
- iOS 26 上,系统会把中等高度的弹窗渲染成悬浮的玻璃面板,四周留出间距;拉到全高后变成不透明。直接使用系统样式,不要自己画背景。
|
|
988
|
+
- 顶部拖拽条 36×5pt,`line-control` 色。
|
|
989
|
+
- 标题用 `ios-title`(衬线),说明用 `ios-callout` `ink-secondary`,下方留 `space-4`。
|
|
990
|
+
- 主操作是通栏 46pt 胶囊主按钮(`control-ios-lg`),下面是同尺寸的文字按钮「暂不需要」,两者间距 `space-1`。
|
|
991
|
+
- 内容超过一屏,或需要长时间阅读时,改成全高弹窗或推入新页面,背景用 `canvas`。
|
|
992
|
+
- Mac 上对应的是附着在窗口上的 sheet 和系统 Alert,同样直接使用系统样式。
|
|
993
|
+
|
|
994
|
+
```swift
|
|
995
|
+
.sheet(isPresented: $showPaywall) {
|
|
996
|
+
PaywallView()
|
|
997
|
+
.presentationDetents([.medium, .large])
|
|
998
|
+
.presentationDragIndicator(.visible)
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// 确认删除这类二选一的决定,用系统对话框
|
|
1002
|
+
.confirmationDialog("删除这条笔记?", isPresented: $confirmDelete) {
|
|
1003
|
+
Button("删除", role: .destructive) { delete() }
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**不要**:在弹窗里再弹一个弹窗;把长篇文章放进玻璃弹窗;没有取消方式;把价格之类的关键信息藏在小字里。
|
|
1008
|
+
"><div class="component-head"><h3>Sheet</h3><span>iOS 底部弹窗,系统玻璃</span><a href="project/components/Sheet/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Sheet 组件预览" src="project/components/Sheet/preview.html" style="height:452px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Sheet</h1>
|
|
1009
|
+
<p>从底部弹出的面板,用于一个短小、独立的任务:确认购买、编辑一个字段、选择分享方式。</p>
|
|
1010
|
+
<p><strong>需要提供</strong>:标题、一两句说明、内容(短表单或选项)、主操作和取消操作,以及要支持的高度档位。</p>
|
|
1011
|
+
<p><strong>规格</strong></p>
|
|
1012
|
+
<ul>
|
|
1013
|
+
<li>iOS 26 上,系统会把中等高度的弹窗渲染成悬浮的玻璃面板,四周留出间距;拉到全高后变成不透明。直接使用系统样式,不要自己画背景。</li>
|
|
1014
|
+
<li>顶部拖拽条 36×5pt,<code>line-control</code> 色。</li>
|
|
1015
|
+
<li>标题用 <code>ios-title</code>(衬线),说明用 <code>ios-callout</code> <code>ink-secondary</code>,下方留 <code>space-4</code>。</li>
|
|
1016
|
+
<li>主操作是通栏 46pt 胶囊主按钮(<code>control-ios-lg</code>),下面是同尺寸的文字按钮「暂不需要」,两者间距 <code>space-1</code>。</li>
|
|
1017
|
+
<li>内容超过一屏,或需要长时间阅读时,改成全高弹窗或推入新页面,背景用 <code>canvas</code>。</li>
|
|
1018
|
+
<li>Mac 上对应的是附着在窗口上的 sheet 和系统 Alert,同样直接使用系统样式。</li>
|
|
1019
|
+
</ul>
|
|
1020
|
+
<pre><code class="language-swift">.sheet(isPresented: $showPaywall) {
|
|
1021
|
+
PaywallView()
|
|
1022
|
+
.presentationDetents([.medium, .large])
|
|
1023
|
+
.presentationDragIndicator(.visible)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// 确认删除这类二选一的决定,用系统对话框
|
|
1027
|
+
.confirmationDialog("删除这条笔记?", isPresented: $confirmDelete) {
|
|
1028
|
+
Button("删除", role: .destructive) { delete() }
|
|
1029
|
+
}
|
|
1030
|
+
</code></pre>
|
|
1031
|
+
<p><strong>不要</strong>:在弹窗里再弹一个弹窗;把长篇文章放进玻璃弹窗;没有取消方式;把价格之类的关键信息藏在小字里。</p>
|
|
1032
|
+
</div></details></section>
|
|
1033
|
+
<section class="component" id="c-Sidebar" data-search="Sidebar macOS 侧边栏 # Sidebar
|
|
1034
|
+
|
|
1035
|
+
macOS 应用的主导航:顶部放全局操作,下面按分组列出各个位置。
|
|
1036
|
+
|
|
1037
|
+
**需要提供**:分组(标题可选)、每一项的 SF Symbol 和名称、可选的计数或快捷键、绑定的选中项。
|
|
1038
|
+
|
|
1039
|
+
**规格**
|
|
1040
|
+
|
|
1041
|
+
- 宽度 `sidebar-mac`(220),用户可以拖动调整。macOS 26 上侧边栏是悬浮的玻璃面板,由 `NavigationSplitView` 自动提供;预览里用 `surface-sunken` 模拟。
|
|
1042
|
+
- 每项高 `row-mac`(26),`radius-sm`,左右内边距 `space-2`,图标 14pt、颜色 `ink-secondary`,文字 13pt `ink`。
|
|
1043
|
+
- 悬停时底色 `fill-hover`。选中时底色 `fill-selected`、文字中粗、图标变 `ink`。选中块不用黑色填满,保持安静。
|
|
1044
|
+
- 分组标题 11pt 半粗 `ink-tertiary`,上方留 `space-3`、下方留 `space-1`。
|
|
1045
|
+
- 计数和快捷键放在右侧,11pt `ink-tertiary`,用等宽数字。
|
|
1046
|
+
- 名称太长时在末尾截断,悬停时显示完整内容。
|
|
1047
|
+
|
|
1048
|
+
```swift
|
|
1049
|
+
NavigationSplitView {
|
|
1050
|
+
List(selection: $selection) {
|
|
1051
|
+
Section("资料库") {
|
|
1052
|
+
Label("全部笔记", systemImage: "doc.text").badge(128).tag(Item.all)
|
|
1053
|
+
Label("收藏", systemImage: "star").badge(9).tag(Item.favorites)
|
|
1054
|
+
}
|
|
1055
|
+
Section("最近") {
|
|
1056
|
+
ForEach(recent) { chat in
|
|
1057
|
+
Label(chat.title, systemImage: "bubble.left").tag(Item.chat(chat.id))
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
|
|
1062
|
+
} detail: {
|
|
1063
|
+
DetailView(selection: selection)
|
|
1064
|
+
}
|
|
1065
|
+
.tint(.brandAccent)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
**不要**:给侧边栏加实色背景(会盖掉系统玻璃);一级导航超过两层;用彩色图标区分每一项(图标一律灰色)。
|
|
1069
|
+
"><div class="component-head"><h3>Sidebar</h3><span>macOS 侧边栏</span><a href="project/components/Sidebar/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Sidebar 组件预览" src="project/components/Sidebar/preview.html" style="height:352px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Sidebar</h1>
|
|
1070
|
+
<p>macOS 应用的主导航:顶部放全局操作,下面按分组列出各个位置。</p>
|
|
1071
|
+
<p><strong>需要提供</strong>:分组(标题可选)、每一项的 SF Symbol 和名称、可选的计数或快捷键、绑定的选中项。</p>
|
|
1072
|
+
<p><strong>规格</strong></p>
|
|
1073
|
+
<ul>
|
|
1074
|
+
<li>宽度 <code>sidebar-mac</code>(220),用户可以拖动调整。macOS 26 上侧边栏是悬浮的玻璃面板,由 <code>NavigationSplitView</code> 自动提供;预览里用 <code>surface-sunken</code> 模拟。</li>
|
|
1075
|
+
<li>每项高 <code>row-mac</code>(26),<code>radius-sm</code>,左右内边距 <code>space-2</code>,图标 14pt、颜色 <code>ink-secondary</code>,文字 13pt <code>ink</code>。</li>
|
|
1076
|
+
<li>悬停时底色 <code>fill-hover</code>。选中时底色 <code>fill-selected</code>、文字中粗、图标变 <code>ink</code>。选中块不用黑色填满,保持安静。</li>
|
|
1077
|
+
<li>分组标题 11pt 半粗 <code>ink-tertiary</code>,上方留 <code>space-3</code>、下方留 <code>space-1</code>。</li>
|
|
1078
|
+
<li>计数和快捷键放在右侧,11pt <code>ink-tertiary</code>,用等宽数字。</li>
|
|
1079
|
+
<li>名称太长时在末尾截断,悬停时显示完整内容。</li>
|
|
1080
|
+
</ul>
|
|
1081
|
+
<pre><code class="language-swift">NavigationSplitView {
|
|
1082
|
+
List(selection: $selection) {
|
|
1083
|
+
Section("资料库") {
|
|
1084
|
+
Label("全部笔记", systemImage: "doc.text").badge(128).tag(Item.all)
|
|
1085
|
+
Label("收藏", systemImage: "star").badge(9).tag(Item.favorites)
|
|
1086
|
+
}
|
|
1087
|
+
Section("最近") {
|
|
1088
|
+
ForEach(recent) { chat in
|
|
1089
|
+
Label(chat.title, systemImage: "bubble.left").tag(Item.chat(chat.id))
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
|
|
1094
|
+
} detail: {
|
|
1095
|
+
DetailView(selection: selection)
|
|
1096
|
+
}
|
|
1097
|
+
.tint(.brandAccent)
|
|
1098
|
+
</code></pre>
|
|
1099
|
+
<p><strong>不要</strong>:给侧边栏加实色背景(会盖掉系统玻璃);一级导航超过两层;用彩色图标区分每一项(图标一律灰色)。</p>
|
|
1100
|
+
</div></details></section>
|
|
1101
|
+
<section class="component" id="c-StepRow" data-search="StepRow AI 或后台任务的执行步骤 # StepRow
|
|
1102
|
+
|
|
1103
|
+
一行显示一个执行步骤:做了什么、结果如何、用了多久。点开可以看到细节。用于 AI 的工具调用、导入导出、批量处理这类任务。
|
|
1104
|
+
|
|
1105
|
+
**需要提供**:状态(进行中 / 成功 / 失败)、动作名(2–4 个字的动词)、对象摘要(文件名、参数、结果)、耗时或说明;可选的详细内容,点击后展开。
|
|
1106
|
+
|
|
1107
|
+
**规格**
|
|
1108
|
+
|
|
1109
|
+
- 高 `control-mac`,`radius-sm`,悬停时底色 `fill-hover`。整行默认 `ink-secondary`,让位于正文内容。
|
|
1110
|
+
- 状态图标 14pt:进行中是 `ink` 色的转圈,成功是 `success` 色,失败是 `danger` 色。失败时摘要要写清楚原因。
|
|
1111
|
+
- 动作名 12pt 半粗 `ink`;摘要用 11pt 等宽,单行,在末尾截断;右侧耗时 11pt `ink-tertiary`。
|
|
1112
|
+
- 展开后的详情缩进到和文字对齐,`surface-sunken` 底,`radius-sm`,12pt 等宽。
|
|
1113
|
+
- 连续 3 步以上全部成功时,合并成一行「完成 5 个步骤」,点击后展开。
|
|
1114
|
+
|
|
1115
|
+
```swift
|
|
1116
|
+
DisclosureGroup(isExpanded: $expanded) {
|
|
1117
|
+
Text(step.detail)
|
|
1118
|
+
.font(.system(.callout, design: .monospaced))
|
|
1119
|
+
.foregroundStyle(.inkSecondary)
|
|
1120
|
+
.padding(.vertical, 6).padding(.horizontal, 8)
|
|
1121
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1122
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 6))
|
|
1123
|
+
} label: {
|
|
1124
|
+
HStack(spacing: 7) {
|
|
1125
|
+
StatusGlyph(step.status) // ProgressView / checkmark.circle / xmark.circle
|
|
1126
|
+
Text(step.action).fontWeight(.semibold).foregroundStyle(.ink)
|
|
1127
|
+
Text(step.summary).font(.callout.monospaced()).lineLimit(1)
|
|
1128
|
+
Spacer()
|
|
1129
|
+
Text(step.duration).font(.caption).monospacedDigit().foregroundStyle(.inkTertiary)
|
|
1130
|
+
}
|
|
1131
|
+
.foregroundStyle(.inkSecondary)
|
|
1132
|
+
}
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
**不要**:把每一步都做成卡片;成功的步骤也用醒目的颜色;把原始日志直接铺在对话里(放进折叠详情)。
|
|
1136
|
+
"><div class="component-head"><h3>StepRow</h3><span>AI 或后台任务的执行步骤</span><a href="project/components/StepRow/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="StepRow 组件预览" src="project/components/StepRow/preview.html" style="height:222px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>StepRow</h1>
|
|
1137
|
+
<p>一行显示一个执行步骤:做了什么、结果如何、用了多久。点开可以看到细节。用于 AI 的工具调用、导入导出、批量处理这类任务。</p>
|
|
1138
|
+
<p><strong>需要提供</strong>:状态(进行中 / 成功 / 失败)、动作名(2–4 个字的动词)、对象摘要(文件名、参数、结果)、耗时或说明;可选的详细内容,点击后展开。</p>
|
|
1139
|
+
<p><strong>规格</strong></p>
|
|
1140
|
+
<ul>
|
|
1141
|
+
<li>高 <code>control-mac</code>,<code>radius-sm</code>,悬停时底色 <code>fill-hover</code>。整行默认 <code>ink-secondary</code>,让位于正文内容。</li>
|
|
1142
|
+
<li>状态图标 14pt:进行中是 <code>ink</code> 色的转圈,成功是 <code>success</code> 色,失败是 <code>danger</code> 色。失败时摘要要写清楚原因。</li>
|
|
1143
|
+
<li>动作名 12pt 半粗 <code>ink</code>;摘要用 11pt 等宽,单行,在末尾截断;右侧耗时 11pt <code>ink-tertiary</code>。</li>
|
|
1144
|
+
<li>展开后的详情缩进到和文字对齐,<code>surface-sunken</code> 底,<code>radius-sm</code>,12pt 等宽。</li>
|
|
1145
|
+
<li>连续 3 步以上全部成功时,合并成一行「完成 5 个步骤」,点击后展开。</li>
|
|
1146
|
+
</ul>
|
|
1147
|
+
<pre><code class="language-swift">DisclosureGroup(isExpanded: $expanded) {
|
|
1148
|
+
Text(step.detail)
|
|
1149
|
+
.font(.system(.callout, design: .monospaced))
|
|
1150
|
+
.foregroundStyle(.inkSecondary)
|
|
1151
|
+
.padding(.vertical, 6).padding(.horizontal, 8)
|
|
1152
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1153
|
+
.background(.surfaceSunken, in: .rect(cornerRadius: 6))
|
|
1154
|
+
} label: {
|
|
1155
|
+
HStack(spacing: 7) {
|
|
1156
|
+
StatusGlyph(step.status) // ProgressView / checkmark.circle / xmark.circle
|
|
1157
|
+
Text(step.action).fontWeight(.semibold).foregroundStyle(.ink)
|
|
1158
|
+
Text(step.summary).font(.callout.monospaced()).lineLimit(1)
|
|
1159
|
+
Spacer()
|
|
1160
|
+
Text(step.duration).font(.caption).monospacedDigit().foregroundStyle(.inkTertiary)
|
|
1161
|
+
}
|
|
1162
|
+
.foregroundStyle(.inkSecondary)
|
|
1163
|
+
}
|
|
1164
|
+
</code></pre>
|
|
1165
|
+
<p><strong>不要</strong>:把每一步都做成卡片;成功的步骤也用醒目的颜色;把原始日志直接铺在对话里(放进折叠详情)。</p>
|
|
1166
|
+
</div></details></section>
|
|
1167
|
+
<section class="component" id="c-Tag" data-search="Tag 状态与分类 # Tag
|
|
1168
|
+
|
|
1169
|
+
一到四个字的标签,标出状态或分类,放在标题旁边或元信息里。
|
|
1170
|
+
|
|
1171
|
+
**需要提供**:文字、语气(中性 / 强调 / success / warning / danger / info);表示状态时加一个圆点。
|
|
1172
|
+
|
|
1173
|
+
**规格**
|
|
1174
|
+
|
|
1175
|
+
- Mac:高 18pt,`radius-xs`,11pt 中粗,左右内边距 6pt。iOS:高 22pt,胶囊形,12pt。
|
|
1176
|
+
- 中性标签:`fill-selected` 底、`ink-secondary` 字。强调标签:`accent` 黑底、`on-accent` 白字,只用于「新」「Pro」这类需要跳出来的少数标记。有语气的标签:用对应的 `-soft` 作底色,用语气色作文字和圆点,例如 `success-soft` 底配 `success` 字。
|
|
1177
|
+
- 表示状态时一定加 5pt 圆点(`radius-full`),文字本身就要说清楚状态是什么,不能只靠颜色。
|
|
1178
|
+
|
|
1179
|
+
```swift
|
|
1180
|
+
struct Tag: View {
|
|
1181
|
+
var text: LocalizedStringKey
|
|
1182
|
+
var tone: Color = .inkSecondary
|
|
1183
|
+
var background: Color = .fillSelected
|
|
1184
|
+
var showsDot = false
|
|
1185
|
+
|
|
1186
|
+
var body: some View {
|
|
1187
|
+
HStack(spacing: 4) {
|
|
1188
|
+
if showsDot { Circle().frame(width: 5, height: 5) }
|
|
1189
|
+
Text(text)
|
|
1190
|
+
}
|
|
1191
|
+
.font(.caption.weight(.medium))
|
|
1192
|
+
.foregroundStyle(tone)
|
|
1193
|
+
.padding(.horizontal, 6)
|
|
1194
|
+
.frame(height: 18)
|
|
1195
|
+
.background(background, in: .rect(cornerRadius: 4))
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
Tag(text: "已同步", tone: .success, background: .successSoft, showsDot: true)
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**不要**:用标签当按钮;一行里放超过三个标签;大量使用黑底强调标签(它和主按钮长得像,多了就分不清)。
|
|
1203
|
+
"><div class="component-head"><h3>Tag</h3><span>状态与分类</span><a href="project/components/Tag/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="Tag 组件预览" src="project/components/Tag/preview.html" style="height:152px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>Tag</h1>
|
|
1204
|
+
<p>一到四个字的标签,标出状态或分类,放在标题旁边或元信息里。</p>
|
|
1205
|
+
<p><strong>需要提供</strong>:文字、语气(中性 / 强调 / success / warning / danger / info);表示状态时加一个圆点。</p>
|
|
1206
|
+
<p><strong>规格</strong></p>
|
|
1207
|
+
<ul>
|
|
1208
|
+
<li>Mac:高 18pt,<code>radius-xs</code>,11pt 中粗,左右内边距 6pt。iOS:高 22pt,胶囊形,12pt。</li>
|
|
1209
|
+
<li>中性标签:<code>fill-selected</code> 底、<code>ink-secondary</code> 字。强调标签:<code>accent</code> 黑底、<code>on-accent</code> 白字,只用于「新」「Pro」这类需要跳出来的少数标记。有语气的标签:用对应的 <code>-soft</code> 作底色,用语气色作文字和圆点,例如 <code>success-soft</code> 底配 <code>success</code> 字。</li>
|
|
1210
|
+
<li>表示状态时一定加 5pt 圆点(<code>radius-full</code>),文字本身就要说清楚状态是什么,不能只靠颜色。</li>
|
|
1211
|
+
</ul>
|
|
1212
|
+
<pre><code class="language-swift">struct Tag: View {
|
|
1213
|
+
var text: LocalizedStringKey
|
|
1214
|
+
var tone: Color = .inkSecondary
|
|
1215
|
+
var background: Color = .fillSelected
|
|
1216
|
+
var showsDot = false
|
|
1217
|
+
|
|
1218
|
+
var body: some View {
|
|
1219
|
+
HStack(spacing: 4) {
|
|
1220
|
+
if showsDot { Circle().frame(width: 5, height: 5) }
|
|
1221
|
+
Text(text)
|
|
1222
|
+
}
|
|
1223
|
+
.font(.caption.weight(.medium))
|
|
1224
|
+
.foregroundStyle(tone)
|
|
1225
|
+
.padding(.horizontal, 6)
|
|
1226
|
+
.frame(height: 18)
|
|
1227
|
+
.background(background, in: .rect(cornerRadius: 4))
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
Tag(text: "已同步", tone: .success, background: .successSoft, showsDot: true)
|
|
1232
|
+
</code></pre>
|
|
1233
|
+
<p><strong>不要</strong>:用标签当按钮;一行里放超过三个标签;大量使用黑底强调标签(它和主按钮长得像,多了就分不清)。</p>
|
|
1234
|
+
</div></details></section>
|
|
1235
|
+
<section class="component" id="c-TextField" data-search="TextField 单行输入 # TextField
|
|
1236
|
+
|
|
1237
|
+
单行文字输入,配一个标签和一句说明。
|
|
1238
|
+
|
|
1239
|
+
**需要提供**:标签(名词)、绑定的文本、可选的占位符(给示例,不要重复标签)、说明文字,校验失败时提供错误信息。
|
|
1240
|
+
|
|
1241
|
+
**规格**
|
|
1242
|
+
|
|
1243
|
+
- 底色 `surface-raised`,边框 `stroke-control` 宽、`line-control` 色。Mac 用 `radius-sm`,高 `control-mac`;iOS 用 `radius-md`,高 `control-ios`。
|
|
1244
|
+
- 聚焦时边框变 `focus-ring`,外圈加 3pt 的 `accent-soft` 灰色光晕。
|
|
1245
|
+
- 出错时边框变 `danger`,说明文字换成错误信息,同样用 `danger` 色。错误信息要说清楚怎么改。
|
|
1246
|
+
- 标签 12pt 中粗 `ink-secondary`,放在输入框上方;说明 11pt `ink-tertiary`,放在下方。
|
|
1247
|
+
|
|
1248
|
+
```swift
|
|
1249
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
1250
|
+
Text("邮箱").font(.caption.weight(.medium)).foregroundStyle(.inkSecondary)
|
|
1251
|
+
TextField("name@example.com", text: $email)
|
|
1252
|
+
.textFieldStyle(.roundedBorder) // Mac;iOS 用自定义样式实现上面的规格
|
|
1253
|
+
.textContentType(.emailAddress)
|
|
1254
|
+
.keyboardType(.emailAddress) // iOS
|
|
1255
|
+
Text(error ?? "用于接收登录验证码。")
|
|
1256
|
+
.font(.caption)
|
|
1257
|
+
.foregroundStyle(error == nil ? .inkTertiary : .danger)
|
|
1258
|
+
}
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
iOS 设置页这类表单,直接在 `Form` 里用 `TextField`,交给系统的分组样式(见 ListGroup)。
|
|
1262
|
+
|
|
1263
|
+
**不要**:只用占位符代替标签;出错时只把边框变红、不给说明;把多行输入塞进单行框(多行用 `TextField(axis: .vertical)` 或 Composer)。
|
|
1264
|
+
"><div class="component-head"><h3>TextField</h3><span>单行输入</span><a href="project/components/TextField/preview.html" target="_blank" rel="noopener">独立预览 ↗</a></div><div class="preview"><iframe loading="lazy" title="TextField 组件预览" src="project/components/TextField/preview.html" style="height:212px"></iframe></div><details><summary>使用规范与 SwiftUI 示例</summary><div class="prose"><h1>TextField</h1>
|
|
1265
|
+
<p>单行文字输入,配一个标签和一句说明。</p>
|
|
1266
|
+
<p><strong>需要提供</strong>:标签(名词)、绑定的文本、可选的占位符(给示例,不要重复标签)、说明文字,校验失败时提供错误信息。</p>
|
|
1267
|
+
<p><strong>规格</strong></p>
|
|
1268
|
+
<ul>
|
|
1269
|
+
<li>底色 <code>surface-raised</code>,边框 <code>stroke-control</code> 宽、<code>line-control</code> 色。Mac 用 <code>radius-sm</code>,高 <code>control-mac</code>;iOS 用 <code>radius-md</code>,高 <code>control-ios</code>。</li>
|
|
1270
|
+
<li>聚焦时边框变 <code>focus-ring</code>,外圈加 3pt 的 <code>accent-soft</code> 灰色光晕。</li>
|
|
1271
|
+
<li>出错时边框变 <code>danger</code>,说明文字换成错误信息,同样用 <code>danger</code> 色。错误信息要说清楚怎么改。</li>
|
|
1272
|
+
<li>标签 12pt 中粗 <code>ink-secondary</code>,放在输入框上方;说明 11pt <code>ink-tertiary</code>,放在下方。</li>
|
|
1273
|
+
</ul>
|
|
1274
|
+
<pre><code class="language-swift">VStack(alignment: .leading, spacing: 6) {
|
|
1275
|
+
Text("邮箱").font(.caption.weight(.medium)).foregroundStyle(.inkSecondary)
|
|
1276
|
+
TextField("name@example.com", text: $email)
|
|
1277
|
+
.textFieldStyle(.roundedBorder) // Mac;iOS 用自定义样式实现上面的规格
|
|
1278
|
+
.textContentType(.emailAddress)
|
|
1279
|
+
.keyboardType(.emailAddress) // iOS
|
|
1280
|
+
Text(error ?? "用于接收登录验证码。")
|
|
1281
|
+
.font(.caption)
|
|
1282
|
+
.foregroundStyle(error == nil ? .inkTertiary : .danger)
|
|
1283
|
+
}
|
|
1284
|
+
</code></pre>
|
|
1285
|
+
<p>iOS 设置页这类表单,直接在 <code>Form</code> 里用 <code>TextField</code>,交给系统的分组样式(见 ListGroup)。</p>
|
|
1286
|
+
<p><strong>不要</strong>:只用占位符代替标签;出错时只把边框变红、不给说明;把多行输入塞进单行框(多行用 <code>TextField(axis: .vertical)</code> 或 Composer)。</p>
|
|
1287
|
+
</div></details></section></section>
|
|
1288
|
+
<section id="install" class="section"><div class="section-title"><span>05 / GET STARTED</span><h2>把 Deernova 带进项目</h2><p>同一版本的规范、资源与模板,一起交到开发者和 AI 手中。</p></div><div class="install-grid"><article><h3>安装设计 skill</h3><p>发布到 npm 后,在项目根目录执行:</p><pre><code>npx @deernova/style@latest install</code></pre><button data-copy="npx @deernova/style@latest install">复制安装命令</button><p>更新:<code>npx @deernova/style@latest update</code><br>检查:<code>npx @deernova/style doctor</code></p></article><article><h3>使用样式与模板</h3><pre><code>npm i @deernova/tokens @deernova/ui
|
|
1289
|
+
npx @deernova/style init ./my-site</code></pre><p>CSS 入口:</p><pre><code>@import '@deernova/tokens/tokens.css';
|
|
1290
|
+
@import '@deernova/ui/components.css';</code></pre></article></div><p class="muted">本地开发:npm install → npm run dev。未发布前可运行 node packages/style/bin/cli.mjs install。网站模板为静态 HTML,不绑定前端框架。</p></section><footer><span>deernova / 有秩序,也有呼吸。</span><span>v0.1.0 · Origin: Paperlight</span></footer></main><div id="toast" role="status" aria-live="polite"></div></body></html>
|