@deernova/style 0.2.0 → 0.3.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.
Files changed (133) hide show
  1. package/README.md +9 -10
  2. package/bin/cli.mjs +212 -32
  3. package/package.json +1 -1
  4. package/skill/SKILL.md +90 -22
  5. package/skill/assets/DeernovaTokens.swift +376 -0
  6. package/skill/assets/screens/iphone-button-sizes-dark.png +0 -0
  7. package/skill/assets/screens/iphone-button-sizes-light.png +0 -0
  8. package/skill/assets/screens/iphone-screens-dark.png +0 -0
  9. package/skill/assets/screens/iphone-screens-light.png +0 -0
  10. package/skill/assets/screens/mac-window-dark.png +0 -0
  11. package/skill/assets/screens/mac-window-light.png +0 -0
  12. package/skill/assets/tailwind.css +106 -0
  13. package/skill/assets/tokens.css +533 -0
  14. package/skill/assets/tokens.json +1686 -0
  15. package/skill/manifest.json +2 -22
  16. package/skill/references/components/banner.md +95 -0
  17. package/skill/references/components/button.md +181 -0
  18. package/skill/references/components/card.md +89 -0
  19. package/skill/references/components/code-block.md +77 -0
  20. package/skill/references/components/composer.md +206 -0
  21. package/skill/references/components/empty-state.md +63 -0
  22. package/skill/references/components/glass-controls.md +127 -0
  23. package/skill/references/components/list.md +130 -0
  24. package/skill/references/components/message.md +94 -0
  25. package/skill/references/components/segmented.md +79 -0
  26. package/skill/references/components/sheet.md +84 -0
  27. package/skill/references/components/sidebar.md +139 -0
  28. package/skill/references/components/step-row.md +135 -0
  29. package/skill/references/components/tag.md +86 -0
  30. package/skill/references/components/task-list.md +83 -0
  31. package/skill/references/components/text-field.md +86 -0
  32. package/skill/references/components/toolbar.md +139 -0
  33. package/skill/references/foundations/accessibility.md +27 -0
  34. package/skill/references/foundations/color.md +58 -0
  35. package/skill/references/foundations/elevation.md +17 -0
  36. package/skill/references/foundations/glass.md +36 -0
  37. package/skill/references/foundations/iconography.md +44 -0
  38. package/skill/references/foundations/motion.md +22 -0
  39. package/skill/references/foundations/radius.md +31 -0
  40. package/skill/references/foundations/spacing.md +38 -0
  41. package/skill/references/foundations/stroke.md +35 -0
  42. package/skill/references/foundations/typography.md +54 -0
  43. package/skill/references/foundations/writing.md +10 -0
  44. package/skill/references/review.md +26 -13
  45. package/skill/references/scenes/ios.md +61 -0
  46. package/skill/references/scenes/ipados.md +42 -0
  47. package/skill/references/scenes/macos.md +65 -0
  48. package/skill/references/scenes/swiftui.md +94 -0
  49. package/skill/references/scenes/web-marketing.md +32 -0
  50. package/skill/references/scenes/web.md +83 -0
  51. package/skill/references/tokens.md +441 -74
  52. package/skill/assets/design/README.md +0 -170
  53. package/skill/assets/design/components/Banner/README.md +0 -33
  54. package/skill/assets/design/components/Banner/preview.html +0 -26
  55. package/skill/assets/design/components/Button/README.md +0 -59
  56. package/skill/assets/design/components/Button/preview.html +0 -27
  57. package/skill/assets/design/components/Card/README.md +0 -31
  58. package/skill/assets/design/components/Card/preview.html +0 -24
  59. package/skill/assets/design/components/CodeBlock/README.md +0 -34
  60. package/skill/assets/design/components/CodeBlock/preview.html +0 -16
  61. package/skill/assets/design/components/Composer/README.md +0 -42
  62. package/skill/assets/design/components/Composer/preview.html +0 -28
  63. package/skill/assets/design/components/Cover/preview.html +0 -53
  64. package/skill/assets/design/components/EmptyState/README.md +0 -28
  65. package/skill/assets/design/components/EmptyState/preview.html +0 -16
  66. package/skill/assets/design/components/GlassControls/README.md +0 -43
  67. package/skill/assets/design/components/GlassControls/preview.html +0 -40
  68. package/skill/assets/design/components/ListGroup/README.md +0 -39
  69. package/skill/assets/design/components/ListGroup/preview.html +0 -22
  70. package/skill/assets/design/components/MacWindow/README.md +0 -27
  71. package/skill/assets/design/components/MacWindow/preview.html +0 -70
  72. package/skill/assets/design/components/Message/README.md +0 -31
  73. package/skill/assets/design/components/Message/preview.html +0 -21
  74. package/skill/assets/design/components/PhoneScreen/README.md +0 -25
  75. package/skill/assets/design/components/PhoneScreen/preview.html +0 -74
  76. package/skill/assets/design/components/Segmented/README.md +0 -21
  77. package/skill/assets/design/components/Segmented/preview.html +0 -21
  78. package/skill/assets/design/components/Sheet/README.md +0 -29
  79. package/skill/assets/design/components/Sheet/preview.html +0 -31
  80. package/skill/assets/design/components/Sidebar/README.md +0 -36
  81. package/skill/assets/design/components/Sidebar/preview.html +0 -20
  82. package/skill/assets/design/components/StepRow/README.md +0 -35
  83. package/skill/assets/design/components/StepRow/preview.html +0 -17
  84. package/skill/assets/design/components/Tag/README.md +0 -36
  85. package/skill/assets/design/components/Tag/preview.html +0 -22
  86. package/skill/assets/design/components/TextField/README.md +0 -29
  87. package/skill/assets/design/components/TextField/preview.html +0 -28
  88. package/skill/assets/design/components/bundle.css +0 -189
  89. package/skill/assets/design/design-system.json +0 -25
  90. package/skill/assets/design/images/Banner-dark.png +0 -0
  91. package/skill/assets/design/images/Banner-light.png +0 -0
  92. package/skill/assets/design/images/Button-dark.png +0 -0
  93. package/skill/assets/design/images/Button-light.png +0 -0
  94. package/skill/assets/design/images/Card-dark.png +0 -0
  95. package/skill/assets/design/images/Card-light.png +0 -0
  96. package/skill/assets/design/images/CodeBlock-dark.png +0 -0
  97. package/skill/assets/design/images/CodeBlock-light.png +0 -0
  98. package/skill/assets/design/images/Composer-dark.png +0 -0
  99. package/skill/assets/design/images/Composer-light.png +0 -0
  100. package/skill/assets/design/images/Cover-dark.png +0 -0
  101. package/skill/assets/design/images/Cover-light.png +0 -0
  102. package/skill/assets/design/images/EmptyState-dark.png +0 -0
  103. package/skill/assets/design/images/EmptyState-light.png +0 -0
  104. package/skill/assets/design/images/Example-iOSButtonsInPhone-dark.png +0 -0
  105. package/skill/assets/design/images/Example-iOSButtonsInPhone-light.png +0 -0
  106. package/skill/assets/design/images/GlassControls-dark.png +0 -0
  107. package/skill/assets/design/images/GlassControls-light.png +0 -0
  108. package/skill/assets/design/images/ListGroup-dark.png +0 -0
  109. package/skill/assets/design/images/ListGroup-light.png +0 -0
  110. package/skill/assets/design/images/MacWindow-dark.png +0 -0
  111. package/skill/assets/design/images/MacWindow-light.png +0 -0
  112. package/skill/assets/design/images/Message-dark.png +0 -0
  113. package/skill/assets/design/images/Message-light.png +0 -0
  114. package/skill/assets/design/images/PhoneScreen-dark.png +0 -0
  115. package/skill/assets/design/images/PhoneScreen-light.png +0 -0
  116. package/skill/assets/design/images/Segmented-dark.png +0 -0
  117. package/skill/assets/design/images/Segmented-light.png +0 -0
  118. package/skill/assets/design/images/Sheet-dark.png +0 -0
  119. package/skill/assets/design/images/Sheet-light.png +0 -0
  120. package/skill/assets/design/images/Sidebar-dark.png +0 -0
  121. package/skill/assets/design/images/Sidebar-light.png +0 -0
  122. package/skill/assets/design/images/StepRow-dark.png +0 -0
  123. package/skill/assets/design/images/StepRow-light.png +0 -0
  124. package/skill/assets/design/images/Tag-dark.png +0 -0
  125. package/skill/assets/design/images/Tag-light.png +0 -0
  126. package/skill/assets/design/images/TextField-dark.png +0 -0
  127. package/skill/assets/design/images/TextField-light.png +0 -0
  128. package/skill/assets/design/tokens.css +0 -187
  129. package/skill/assets/design/tokens.json +0 -142
  130. package/skill/references/components.md +0 -648
  131. package/skill/references/foundations.md +0 -170
  132. package/skill/references/native.md +0 -7
  133. package/skill/references/web.md +0 -11
@@ -0,0 +1,206 @@
1
+ # Composer 输入区
2
+
3
+ 发消息、写笔记、向 AI 提问的输入区,放在对话栏底部。它承载的是用户正在写的内容,所以是纸面,不是玻璃。
4
+
5
+ 由上到下三部分:可选的**提示条**(贴在输入框正上方)、**输入框**(加号、输入、语音、发送排在同一行)、**信息行**(模型、思考强度、权限模式、上下文用量,靠右)。
6
+
7
+ **需要提供**:绑定的文本、占位符、发送动作、是否可以发送(内容为空或正在生成时禁用)。可选:附件列表、提示条、模型和权限的选择、上下文用量。
8
+
9
+ ## 尺寸
10
+
11
+ <!-- tokens:composer -->
12
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
13
+ | --- | --- | --- | --- | --- |
14
+ | `composer-radius` | 14 | 24 | 16 | Composer 外框圆角。 |
15
+ | `composer-min-height` | 44 | 44 | 48 | 单行时的高度:加号、输入、发送排在同一行,输入多行时向上长高。 |
16
+ <!-- /tokens -->
17
+
18
+ - 输入框:`surface-raised` 底、1px `line` 描边、`composer-radius`、`shadow-float`。单行时高 `composer-min-height`(桌面 44),加号、输入、发送垂直居中在同一行;输入多行时向上长高,最多 8 行,超出后内部滚动。获得焦点时描边加深为 `line-control`。
19
+ - 文字用 `type-body`;占位符写清快捷键,例如「按 ⏎ 发送,按 ⌘⏎ 加入队列稍后发送」。
20
+ - 加号和语音是[图标按钮](button.md)。发送按钮是 `button-height-md` 的圆形,`accent` 底、`on-accent` 箭头;不能发送时 `fill-selected` 底、`ink-tertiary` 箭头,不隐藏;正在生成时变成停止方块。
21
+ - 附件:在输入框内、文字上方单独一行,`surface-sunken` 底、`radius-sm` 的文件条。
22
+ - 提示条:宽度和输入框一致,`surface-raised` 底、`banner-radius`,单行高 `composer-min-height`,左边图标和一句话,右边关闭按钮;次要信息用 `ink-secondary`。和输入框之间 `space-2`。
23
+ - 信息行:输入框下方 `space-2`,靠右排列,`callout` 字号;当前值用 `ink`,说明用 `ink-secondary`;每一项都是可以点开选择的文字按钮。
24
+ - 指针端 Return 发送、Shift-Return 换行;触控端换行键就是换行,只能点按钮发送。
25
+
26
+ ## Web
27
+
28
+ ```css
29
+ .dn-composer-stack { display: grid; gap: var(--space-2); width: 100%; max-width: var(--content-max); }
30
+ .dn-composer-notice {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: var(--space-2);
34
+ min-height: var(--composer-min-height);
35
+ padding: 0 var(--space-1) 0 var(--space-3);
36
+ border-radius: var(--banner-radius);
37
+ background: var(--surface-raised);
38
+ color: var(--ink);
39
+ font: var(--type-callout);
40
+ }
41
+ .dn-composer-notice > .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); color: var(--ink-secondary); }
42
+ .dn-composer-notice .dn-muted { color: var(--ink-secondary); }
43
+ .dn-composer-notice .dn-icon-button { margin-left: auto; }
44
+ .dn-composer {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ align-items: flex-end;
48
+ gap: var(--space-1);
49
+ min-height: var(--composer-min-height);
50
+ padding: calc((var(--composer-min-height) - var(--button-height-md)) / 2 - var(--stroke-line)) var(--space-2);
51
+ border: var(--stroke-line) solid var(--line);
52
+ border-radius: var(--composer-radius);
53
+ background: var(--surface-raised);
54
+ box-shadow: var(--shadow-float);
55
+ transition: border-color var(--duration-instant) var(--ease-standard);
56
+ }
57
+ .dn-composer:focus-within { border-color: var(--line-control); }
58
+ .dn-composer textarea {
59
+ flex: 1;
60
+ min-width: 0;
61
+ min-height: var(--button-height-md);
62
+ max-height: calc(var(--type-body-line) * 8 + 4px);
63
+ padding: calc((var(--button-height-md) - var(--type-body-line)) / 2) var(--space-1);
64
+ border: 0;
65
+ outline: none;
66
+ resize: none;
67
+ field-sizing: content;
68
+ background: transparent;
69
+ color: var(--ink);
70
+ font: var(--type-body);
71
+ }
72
+ .dn-composer textarea::placeholder { color: var(--ink-tertiary); }
73
+ .dn-composer-attachments { display: flex; flex-wrap: wrap; flex-basis: 100%; gap: var(--space-1); padding: 0 0 var(--space-1); }
74
+ .dn-attachment {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ gap: var(--gap-inline);
78
+ height: var(--button-height-sm);
79
+ padding: 0 var(--space-2);
80
+ border-radius: var(--radius-sm);
81
+ background: var(--surface-sunken);
82
+ color: var(--ink-secondary);
83
+ font: var(--weight-regular) var(--type-callout-size)/1 var(--font-sans);
84
+ }
85
+ .dn-attachment .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); }
86
+ .dn-send {
87
+ position: relative;
88
+ display: grid;
89
+ place-items: center;
90
+ flex: none;
91
+ width: var(--button-height-md);
92
+ height: var(--button-height-md);
93
+ padding: 0;
94
+ border: 0;
95
+ border-radius: var(--radius-full);
96
+ background: var(--accent);
97
+ color: var(--on-accent);
98
+ cursor: pointer;
99
+ }
100
+ .dn-send .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); stroke-width: 2.2; }
101
+ .dn-send:disabled { background: var(--fill-selected); color: var(--ink-tertiary); cursor: default; }
102
+ .dn-send:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
103
+ .dn-send::before {
104
+ content: "";
105
+ position: absolute;
106
+ top: 50%;
107
+ left: 50%;
108
+ width: max(100%, var(--hit-min));
109
+ height: max(100%, var(--hit-min));
110
+ transform: translate(-50%, -50%);
111
+ }
112
+ .dn-composer-meta { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 2px; }
113
+ .dn-meta-button {
114
+ position: relative;
115
+ display: inline-flex;
116
+ align-items: center;
117
+ gap: var(--gap-inline);
118
+ height: var(--button-height-sm);
119
+ padding: 0 var(--space-2);
120
+ border: 0;
121
+ border-radius: var(--button-radius-sm);
122
+ background: transparent;
123
+ color: var(--ink-secondary);
124
+ font: var(--weight-regular) var(--type-callout-size)/1 var(--font-sans);
125
+ cursor: pointer;
126
+ }
127
+ .dn-meta-button:hover { background: var(--fill-hover); }
128
+ .dn-meta-button b { color: var(--ink); font-weight: var(--weight-regular); }
129
+ .dn-context-ring {
130
+ width: var(--icon-size-sm);
131
+ height: var(--icon-size-sm);
132
+ border-radius: var(--radius-full);
133
+ background: conic-gradient(var(--ink-secondary) calc(var(--used, 0.3) * 1turn), var(--fill-selected) 0);
134
+ -webkit-mask: radial-gradient(circle, transparent 55%, #000 57%);
135
+ mask: radial-gradient(circle, transparent 55%, #000 57%);
136
+ }
137
+ ```
138
+
139
+ ```html
140
+ <div class="dn-composer-stack">
141
+ <div class="dn-composer-notice" role="status">
142
+ <svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
143
+ <span>本周额度将提前用完 <span class="dn-muted">已使用 52% · 11 月 9 日重置</span></span>
144
+ <button class="dn-icon-button" type="button" aria-label="关闭提示"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
145
+ </div>
146
+ <div class="dn-composer">
147
+ <div class="dn-composer-attachments"><span class="dn-attachment"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3h7l5 5v13H7z"/><path d="M14 3v5h5"/></svg>周报草稿.md</span></div>
148
+ <button class="dn-icon-button" type="button" aria-label="添加附件"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
149
+ <textarea rows="1" aria-label="消息" placeholder="按 ⏎ 发送,按 ⌘⏎ 加入队列稍后发送"></textarea>
150
+ <button class="dn-icon-button" type="button" aria-label="语音输入"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg></button>
151
+ <button class="dn-send" type="button" aria-label="发送" disabled><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button>
152
+ </div>
153
+ <div class="dn-composer-meta">
154
+ <button class="dn-meta-button" type="button"><b>Opus 5.5</b>高</button>
155
+ <button class="dn-meta-button" type="button"><b>询问权限</b></button>
156
+ <span class="dn-context-ring" role="img" aria-label="上下文已用 30%"></span>
157
+ </div>
158
+ </div>
159
+ ```
160
+
161
+ ## SwiftUI
162
+
163
+ ```swift
164
+ VStack(alignment: .trailing, spacing: Deernova.space2) {
165
+ HStack(alignment: .bottom, spacing: Deernova.space1) {
166
+ Button("添加附件", systemImage: "plus") { attach() }
167
+ .labelStyle(.iconOnly).buttonStyle(.borderless)
168
+ TextField("按 ⏎ 发送,按 ⌘⏎ 加入队列稍后发送", text: $draft, axis: .vertical)
169
+ .lineLimit(1...8)
170
+ .textFieldStyle(.plain)
171
+ .font(.deernovaBody)
172
+ .onSubmit(send) // Mac:Return 发送
173
+ Button("语音输入", systemImage: "mic") { dictate() }
174
+ .labelStyle(.iconOnly).buttonStyle(.borderless)
175
+ Button(action: send) {
176
+ Image(systemName: "arrow.up").fontWeight(.semibold)
177
+ .frame(width: Deernova.buttonHeightMd, height: Deernova.buttonHeightMd)
178
+ }
179
+ .buttonStyle(.borderedProminent)
180
+ .buttonBorderShape(.circle)
181
+ .disabled(draft.isEmpty)
182
+ }
183
+ .padding(.horizontal, Deernova.space2)
184
+ .frame(minHeight: Deernova.composerMinHeight)
185
+ .background(.surfaceRaised, in: .rect(cornerRadius: Deernova.composerRadius))
186
+ .overlay(RoundedRectangle(cornerRadius: Deernova.composerRadius).strokeBorder(.line, lineWidth: Deernova.strokeLine))
187
+ .shadow(color: .black.opacity(0.08), radius: 10, y: 6)
188
+
189
+ HStack(spacing: Deernova.space2) { // 信息行:模型、权限
190
+ Menu("Opus 5.5 · 高") { … }
191
+ Menu("询问权限") { … }
192
+ }
193
+ .font(.deernovaCallout)
194
+ .menuStyle(.borderlessButton)
195
+ .foregroundStyle(.inkSecondary)
196
+ }
197
+ ```
198
+
199
+ 放在页面底部时用 `.safeAreaInset(edge: .bottom)`,让内容可以滚到它的上方。
200
+
201
+ ## 不要
202
+
203
+ - 把 Composer 做成玻璃(它承载的是内容)。
204
+ - 把模型、权限这类设置塞进输入框里;把发送按钮放到框外。
205
+ - 禁用时把按钮直接隐藏,让布局跳动。
206
+ - 提示条和输入框宽度不一致,或者离得很远。
@@ -0,0 +1,63 @@
1
+ # 空状态
2
+
3
+ 列表、搜索结果或整个页面没有内容时显示:说明现在是什么状态,再给出下一步。
4
+
5
+ **需要提供**:图标、标题(描述状态)、一句正文(告诉用户怎么开始),以及最多一个主操作和一个次操作。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:empty-state -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `empty-title-size` | 24 | 20 | 32 | 空状态标题(衬线)字号。 |
13
+ | `empty-icon-size` | 32 | 36 | 40 | 空状态图标的边长。 |
14
+ <!-- /tokens -->
15
+
16
+ - 在可用区域里居中。图标 `empty-icon-size`、细线、`ink-tertiary`;标题用衬线 `empty-title-size`、半粗;正文 `type-body`、`ink-secondary`,最大宽度 320。
17
+ - 按钮用[大号](button.md)。
18
+ - 搜索没有结果时不放按钮,标题写成「没有找到“关键词”」,正文给出修改建议。
19
+
20
+ ## Web
21
+
22
+ ```css
23
+ .dn-empty { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); padding: var(--space-6) var(--space-5); text-align: center; }
24
+ .dn-empty > .dn-icon { width: var(--empty-icon-size); height: var(--empty-icon-size); color: var(--ink-tertiary); stroke-width: 1.4; }
25
+ .dn-empty-title { margin: var(--space-2) 0 0; color: var(--ink); font: var(--weight-semibold) var(--empty-title-size)/1.25 var(--font-serif); letter-spacing: var(--type-display-tracking); }
26
+ .dn-empty-body { max-width: 320px; margin: 0 0 var(--space-3); color: var(--ink-secondary); font: var(--type-body); }
27
+ .dn-empty-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--gap-stack); }
28
+ ```
29
+
30
+ ```html
31
+ <div class="dn-empty">
32
+ <svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3h7l5 5v13H7z"/><path d="M14 3v5h5"/></svg>
33
+ <h3 class="dn-empty-title">还没有笔记</h3>
34
+ <p class="dn-empty-body">写下第一条想法,它会同步到你所有的设备。</p>
35
+ <div class="dn-empty-actions">
36
+ <button class="dn-button is-primary is-lg" type="button">新建笔记</button>
37
+ <button class="dn-button is-lg" type="button">从文件导入</button>
38
+ </div>
39
+ </div>
40
+ ```
41
+
42
+ ## SwiftUI
43
+
44
+ ```swift
45
+ ContentUnavailableView {
46
+ Label("还没有笔记", systemImage: "doc.text")
47
+ .font(.system(size: Deernova.emptyTitleSize, weight: .semibold, design: .serif))
48
+ } description: {
49
+ Text("写下第一条想法,它会同步到你所有的设备。")
50
+ } actions: {
51
+ Button("新建笔记") { create() }.buttonStyle(.borderedProminent).controlSize(.large)
52
+ Button("从文件导入") { importFile() }.controlSize(.large)
53
+ }
54
+
55
+ // 搜索没有结果
56
+ ContentUnavailableView.search(text: query)
57
+ ```
58
+
59
+ ## 不要
60
+
61
+ - 用插画或吉祥物填满空白。
62
+ - 标题写「空空如也」这种玩笑话。
63
+ - 出现三个以上的按钮。
@@ -0,0 +1,127 @@
1
+ # 玻璃操作层
2
+
3
+ 浮在内容之上的玻璃操作层:SwiftUI 端的工具栏按钮组、标签栏、浮动操作按钮,以及官网与文档的页眉。规则见 [玻璃](../foundations/glass.md)。
4
+
5
+ **Web 应用不用玻璃**:顶栏和工具按钮见 [顶栏与侧面板](toolbar.md)。
6
+
7
+ **什么时候用**:用户在浏览内容时随时要用到的操作,比如新建、搜索、切换页面、分享。内容本身永远不放在玻璃上。
8
+
9
+ **需要提供**:每个按钮的图标和无障碍标签、点击动作;如果是标签栏,还要提供当前选中项。
10
+
11
+ ## 尺寸
12
+
13
+ <!-- tokens:glass-controls -->
14
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
15
+ | --- | --- | --- | --- | --- |
16
+ | `glass-button-size` | 34 | 44 | 36 | 玻璃按钮的边长:macOS 26 工具栏按钮约 34(系统绘制,这里只作参考和自绘时对齐),官网页眉的图标按钮也用它;按钮组里的按钮比它小 8。Web 应用的工具栏不用玻璃,用 button-height-md。 |
17
+ <!-- /tokens -->
18
+
19
+ - 单个玻璃按钮:圆形,边长 `glass-button-size`,图标 `icon-size-lg`。macOS 26 的工具栏按钮由系统绘制,约 34,和红绿灯在同一条线上,见 [macOS](../scenes/macos.md)。
20
+ - 按钮组:胶囊形,内边距 3,组内按钮比单个按钮小 8,间距 2。相邻的玻璃元素放进同一组,组与组之间 `space-3`。
21
+ - 选中项:图标从 `ink-secondary` 变成 `ink`,底色 `fill-selected`。
22
+ - 实色玻璃(`accent` 底、`on-accent` 图标)一屏只用一次,留给主操作,比如「新建」。
23
+
24
+ ## Web
25
+
26
+ 只用于官网与文档:网页没有系统玻璃,页眉用半透明填充加背景模糊近似。Web 应用里不要用。
27
+
28
+ ```css
29
+ .dn-glass {
30
+ border: var(--stroke-line) solid var(--glass-stroke);
31
+ border-radius: var(--radius-full);
32
+ background: var(--glass-fill);
33
+ box-shadow: var(--shadow-glass);
34
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
35
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
36
+ }
37
+ @media (prefers-reduced-transparency: reduce) {
38
+ .dn-glass { background: var(--surface-raised); -webkit-backdrop-filter: none; backdrop-filter: none; }
39
+ }
40
+
41
+ .dn-glass-button {
42
+ position: relative;
43
+ display: inline-grid;
44
+ place-items: center;
45
+ flex: none;
46
+ width: var(--glass-button-size);
47
+ height: var(--glass-button-size);
48
+ padding: 0;
49
+ border-radius: var(--radius-full);
50
+ color: var(--ink);
51
+ font: inherit;
52
+ cursor: pointer;
53
+ }
54
+ .dn-glass-button .dn-icon { width: var(--icon-size-lg); height: var(--icon-size-lg); }
55
+ .dn-glass-button.is-prominent { border-color: transparent; background: var(--accent); color: var(--on-accent); }
56
+ .dn-glass-button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
57
+ .dn-glass-button::before {
58
+ content: "";
59
+ position: absolute;
60
+ top: 50%;
61
+ left: 50%;
62
+ width: max(100%, var(--hit-min));
63
+ height: max(100%, var(--hit-min));
64
+ transform: translate(-50%, -50%);
65
+ }
66
+
67
+ .dn-glass-group { display: inline-flex; align-items: center; gap: 2px; padding: 3px; }
68
+ .dn-glass-group .dn-glass-button {
69
+ width: calc(var(--glass-button-size) - 8px);
70
+ height: calc(var(--glass-button-size) - 8px);
71
+ border: 0;
72
+ background: transparent;
73
+ box-shadow: none;
74
+ color: var(--ink-secondary);
75
+ }
76
+ .dn-glass-group .dn-glass-button[aria-current="page"], .dn-glass-group .dn-glass-button[aria-pressed="true"] { background: var(--fill-selected); color: var(--ink); }
77
+ .dn-glass-group .dn-glass-button .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); }
78
+ ```
79
+
80
+ ```html
81
+ <div class="dn-glass-demo">
82
+ <div class="dn-row">
83
+ <nav class="dn-glass dn-glass-group" aria-label="页面">
84
+ <button class="dn-glass-button" type="button" aria-label="笔记" aria-current="page"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3h7l5 5v13H7z"/><path d="M14 3v5h5"/></svg></button>
85
+ <button class="dn-glass-button" type="button" aria-label="收藏"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4l2.4 5 5.6.6-4.2 3.8 1.2 5.5L12 16l-5 2.9 1.2-5.5L4 9.6l5.6-.6z"/></svg></button>
86
+ <button class="dn-glass-button" type="button" aria-label="设置"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg></button>
87
+ </nav>
88
+ <button class="dn-glass dn-glass-button" type="button" aria-label="搜索"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6"/><path d="M20 20l-4.5-4.5"/></svg></button>
89
+ <button class="dn-glass dn-glass-button is-prominent" type="button" aria-label="新建"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
90
+ </div>
91
+ </div>
92
+ ```
93
+
94
+ ## SwiftUI
95
+
96
+ ```swift
97
+ // 标准标签栏与工具栏,系统自动渲染为玻璃
98
+ TabView {
99
+ Tab("笔记", systemImage: "doc.text") { NotesView() }
100
+ Tab("收藏", systemImage: "star") { FavoritesView() }
101
+ Tab(role: .search) { SearchView() } // 搜索单独成为右侧的圆形玻璃按钮
102
+ }
103
+ .tabBarMinimizeBehavior(.onScrollDown)
104
+ .tint(.brandAccent)
105
+
106
+ // 自定义浮动按钮:放进同一个容器,互相融合
107
+ GlassEffectContainer(spacing: 12) {
108
+ HStack(spacing: 12) {
109
+ Button("搜索", systemImage: "magnifyingglass") { search() }
110
+ .buttonStyle(.glass)
111
+ Button("新建", systemImage: "plus") { create() }
112
+ .buttonStyle(.glassProminent)
113
+ }
114
+ .labelStyle(.iconOnly)
115
+ }
116
+
117
+ // 完全自定义形状时
118
+ Image(systemName: "mic")
119
+ .frame(width: Deernova.glassButtonSize, height: Deernova.glassButtonSize)
120
+ .glassEffect(.regular.interactive(), in: .circle)
121
+ ```
122
+
123
+ ## 不要
124
+
125
+ - 在玻璃上放段落文字或列表;玻璃叠玻璃。
126
+ - 给工具栏加实色背景挡住滚动边缘效果。
127
+ - 同一屏出现两个实色玻璃。
@@ -0,0 +1,130 @@
1
+ # 分组列表与设置页
2
+
3
+ 分组列表,用于设置页、详情页和各种表单:一组组白色的列表浮在 `canvas` 上。
4
+
5
+ **需要提供**:分组,每个分组可以有页头和页脚说明;每一行提供标题,以及可选的图标、右侧的值、开关或箭头,点击后跳转或执行动作。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:list -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `row-icon-size` | 20 | 28 | 24 | 列表行左侧图标底的边长。 |
13
+ | `switch-width` | 36 | 51 | 44 | 开关宽度。 |
14
+ | `switch-height` | 20 | 31 | 24 | 开关高度;圆钮直径 = 高度 − 4。 |
15
+ <!-- /tokens -->
16
+
17
+ 行高、行内边距沿用[侧边栏](sidebar.md)的 `row-height`、`row-padding-x`;分组圆角沿用[卡片](card.md)的 `card-radius`。
18
+
19
+ - 页面底色 `canvas`,分组底色 `surface-raised`,分组之间 `gap-group`。
20
+ - 每行最小高度 `row-height`,文字 `type-body`、`ink`;右侧的值用 `ink-secondary`,箭头用 `ink-tertiary`。
21
+ - 图标放在边长 `row-icon-size` 的方块里,`accent-soft` 底、`ink` 图标、`radius-sm`。整个列表统一用灰色,不要每行一个颜色。
22
+ - 分隔线 `stroke-line`、`line` 色,从文字起点开始。
23
+ - 页头 `footnote` 字号、`ink-secondary`;页脚 `footnote` 字号、`ink-tertiary`,写清楚这个设置会带来什么后果。
24
+ - 开关打开时用 `success`。不要用黑白色调的开关:深色主题下白色轨道会把白色圆钮吞掉。
25
+
26
+ ## Web
27
+
28
+ 开关用原生的 `<input type="checkbox" role="switch">`,保留键盘和读屏支持。
29
+
30
+ ```css
31
+ .dn-list-section { display: grid; gap: 6px; }
32
+ .dn-list-header, .dn-list-footer { margin: 0; padding: 0 var(--row-padding-x); font: var(--weight-regular) var(--type-footnote-size)/var(--type-footnote-line) var(--font-sans); }
33
+ .dn-list-header { color: var(--ink-secondary); }
34
+ .dn-list-footer { color: var(--ink-tertiary); }
35
+ .dn-list { margin: 0; padding: 0; overflow: hidden; list-style: none; border-radius: var(--card-radius); background: var(--surface-raised); }
36
+ .dn-list-row {
37
+ --separator-inset: var(--row-padding-x);
38
+ position: relative;
39
+ display: flex;
40
+ align-items: center;
41
+ gap: var(--space-3);
42
+ min-height: var(--row-height);
43
+ padding: 0 var(--row-padding-x);
44
+ color: var(--ink);
45
+ font: var(--type-body);
46
+ }
47
+ .dn-list-row.has-icon { --separator-inset: calc(var(--row-padding-x) + var(--row-icon-size) + var(--space-3)); }
48
+ .dn-list-row + .dn-list-row::before {
49
+ content: "";
50
+ position: absolute;
51
+ top: 0;
52
+ right: 0;
53
+ left: var(--separator-inset);
54
+ border-top: var(--stroke-line) var(--line-style-divider) var(--line);
55
+ }
56
+ .dn-list-icon { display: grid; place-items: center; flex: none; width: var(--row-icon-size); height: var(--row-icon-size); border-radius: var(--radius-sm); background: var(--accent-soft); color: var(--ink); }
57
+ .dn-list-icon .dn-icon { width: calc(var(--row-icon-size) * 0.6); height: calc(var(--row-icon-size) * 0.6); }
58
+ .dn-list-value { margin-left: auto; color: var(--ink-secondary); white-space: nowrap; }
59
+ .dn-list-row .dn-chevron { width: var(--icon-size-sm); height: var(--icon-size-sm); color: var(--ink-tertiary); }
60
+
61
+ .dn-switch {
62
+ position: relative;
63
+ flex: none;
64
+ width: var(--switch-width);
65
+ height: var(--switch-height);
66
+ margin: 0 0 0 auto;
67
+ border-radius: var(--radius-full);
68
+ background: var(--fill-selected);
69
+ appearance: none;
70
+ cursor: pointer;
71
+ transition: background-color var(--duration-base) var(--ease-standard);
72
+ }
73
+ .dn-switch::after {
74
+ content: "";
75
+ position: absolute;
76
+ top: 2px;
77
+ left: 2px;
78
+ width: calc(var(--switch-height) - 4px);
79
+ height: calc(var(--switch-height) - 4px);
80
+ border-radius: var(--radius-full);
81
+ background: var(--knob);
82
+ box-shadow: var(--shadow-control);
83
+ transition: transform var(--duration-base) var(--ease-standard);
84
+ }
85
+ .dn-switch:checked { background: var(--success); }
86
+ .dn-switch:checked::after { transform: translateX(calc(var(--switch-width) - var(--switch-height))); }
87
+ .dn-switch:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
88
+ ```
89
+
90
+ ```html
91
+ <section class="dn-list-section">
92
+ <h3 class="dn-list-header">同步</h3>
93
+ <ul class="dn-list">
94
+ <li class="dn-list-row has-icon"><span class="dn-list-icon"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 20a8 8 0 0 1 16 0"/></svg></span>个人资料<span class="dn-list-value">林一</span><svg class="dn-icon dn-chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></li>
95
+ <li class="dn-list-row has-icon"><span class="dn-list-icon"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 18h10a4 4 0 0 0 .5-8 6 6 0 0 0-11.5 1.5A3.5 3.5 0 0 0 7 18z"/></svg></span><label for="sync">iCloud 同步</label><input class="dn-switch" id="sync" type="checkbox" role="switch" checked></li>
96
+ </ul>
97
+ <p class="dn-list-footer">关闭 iCloud 同步后,新笔记只保存在这台设备上。</p>
98
+ </section>
99
+ ```
100
+
101
+ ## SwiftUI
102
+
103
+ ```swift
104
+ Form {
105
+ Section("账号") {
106
+ NavigationLink { ProfileView() } label: {
107
+ LabeledContent { Text("林一") } label: { Label("个人资料", systemImage: "person") }
108
+ }
109
+ }
110
+ Section {
111
+ Toggle(isOn: $iCloudSync) { Label("iCloud 同步", systemImage: "icloud") }
112
+ .tint(.success)
113
+ } header: {
114
+ Text("同步")
115
+ } footer: {
116
+ Text("关闭 iCloud 同步后,新笔记只保存在这台设备上。")
117
+ }
118
+ }
119
+ .scrollContentBackground(.hidden)
120
+ .background(.canvas)
121
+ .tint(.brandAccent)
122
+ ```
123
+
124
+ Mac 上的设置窗口同样用 `Form` 加 `.formStyle(.grouped)`,系统会给出对应的 Mac 样式。
125
+
126
+ ## 不要
127
+
128
+ - 每行用不同颜色的图标。
129
+ - 页脚写成一大段。
130
+ - 在列表行里放主按钮(主按钮放在页面底部)。
@@ -0,0 +1,94 @@
1
+ # 消息
2
+
3
+ 对话里的一条消息。用户说的话放在比内容区亮一层的气泡里,靠右;AI 的回答直接排在内容区上,用和界面一样的无衬线正文,靠左。执行过程见 [工具调用与执行步骤](step-row.md),任务进度见 [任务清单](task-list.md)。
4
+
5
+ **需要提供**:发送者(用户或 AI)、内容(Markdown,可以包含代码块、列表、链接)、AI 消息的操作(复制、重新生成),以及生成中的状态。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:message -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `message-font-size` | 15 | 15 | 15 | 用户消息气泡的字号。AI 回答用 type-body,和界面正文一致。 |
13
+ | `message-action-size` | 24 | 32 | 28 | AI 消息下方操作按钮的可见尺寸。 |
14
+ <!-- /tokens -->
15
+
16
+ - 用户消息:靠右,最大宽度 80%,`surface-raised` 底,`radius-lg`,内边距上下 `space-3`、左右 `space-4`,字号 `message-font-size`、行高同 `type-body`。
17
+ - AI 消息:靠左,不加气泡,正文用 `type-body`(桌面 15/24),栏宽不超过 `content-max`。段落之间 `space-3`;加粗用 600;列表缩进一级、项与项之间 4。代码见[代码块](code-block.md),行内代码用等宽字加 `fill-selected` 底。
18
+ - 消息、工具调用行、卡片之间统一 `space-4`。
19
+ - 操作按钮放在 AI 消息下方,`message-action-size` 的图标按钮,`ink-tertiary` 色;指针端只在悬停时显示,触控端一直显示。
20
+ - 链接用 `ink` 色,加 1px 的 `line-control` 色下划线,下偏移 3px。
21
+ - 生成中:回答末尾显示「正在运行」状态(见 [工具调用与执行步骤](step-row.md)),Composer 的发送按钮变成「停止」。
22
+
23
+ ## Web
24
+
25
+ ```css
26
+ .dn-thread { display: flex; flex-direction: column; gap: var(--space-4); max-width: var(--content-max); }
27
+ .dn-message-user {
28
+ align-self: flex-end;
29
+ max-width: 80%;
30
+ padding: var(--space-3) var(--space-4);
31
+ border-radius: var(--radius-lg);
32
+ background: var(--surface-raised);
33
+ color: var(--ink);
34
+ font: var(--weight-regular) var(--message-font-size)/var(--type-body-line) var(--font-sans);
35
+ }
36
+ .dn-message-assistant { color: var(--ink); font: var(--type-body); }
37
+ .dn-message-assistant p { margin: 0 0 var(--space-3); }
38
+ .dn-message-assistant p:last-of-type { margin-bottom: 0; }
39
+ .dn-message-assistant ul, .dn-message-assistant ol { margin: 0 0 var(--space-3); padding-left: 1.4em; }
40
+ .dn-message-assistant li + li { margin-top: var(--space-1); }
41
+ .dn-message-assistant strong { font-weight: var(--weight-semibold); }
42
+ .dn-message-assistant code { padding: 1px 5px; border-radius: var(--radius-xs); background: var(--fill-selected); font: 0.9em/1 var(--font-mono); }
43
+ .dn-message-assistant a { color: var(--ink); text-decoration: underline 1px var(--line-control); text-underline-offset: 3px; }
44
+ .dn-message-actions { display: flex; gap: 2px; margin: var(--space-2) 0 0 calc(var(--space-1) * -1); }
45
+ .dn-message-actions .dn-icon-button { width: var(--message-action-size); height: var(--message-action-size); color: var(--ink-tertiary); }
46
+ .dn-message-actions .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); }
47
+ @media (hover: hover) and (pointer: fine) {
48
+ .dn-message-actions { opacity: 0; transition: opacity var(--duration-instant) var(--ease-standard); }
49
+ .dn-message-assistant:hover .dn-message-actions, .dn-message-actions:focus-within { opacity: 1; }
50
+ }
51
+ ```
52
+
53
+ ```html
54
+ <div class="dn-thread">
55
+ <div class="dn-message-user">帮我把这份周报整理成三条要点</div>
56
+ <div class="dn-message-assistant">
57
+ <p>这周的进展可以归成三件事:</p>
58
+ <ul>
59
+ <li><strong>首页改版定稿。</strong>信息层级按「最近、收藏、全部」排列,下周三前交给开发。</li>
60
+ <li><strong>同步问题修复。</strong>离线编辑后重复保存的问题已经解决。</li>
61
+ <li><strong>订阅转化提升。</strong>年付转化率从 3.1% 升到 4.4%。</li>
62
+ </ul>
63
+ <p>需要的话,我可以按 <a href="#">周报模板</a> 再排一次,或者导出成 <code>weekly.md</code>。</p>
64
+ <div class="dn-message-actions">
65
+ <button class="dn-icon-button" type="button" aria-label="复制"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg></button>
66
+ <button class="dn-icon-button" type="button" aria-label="重新生成"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12a8 8 0 1 0 2.3-5.6"/><path d="M4 4v4h4"/></svg></button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ ```
71
+
72
+ ## SwiftUI
73
+
74
+ ```swift
75
+ // 用户消息
76
+ Text(message.text)
77
+ .font(.system(size: Deernova.messageFontSize))
78
+ .padding(.vertical, Deernova.space3).padding(.horizontal, Deernova.space4)
79
+ .background(.surfaceRaised, in: .rect(cornerRadius: Deernova.radiusLg))
80
+ .frame(maxWidth: .infinity, alignment: .trailing)
81
+
82
+ // AI 消息:用 AttributedString(markdown:) 渲染,或接入 Markdown 渲染库
83
+ Text(try AttributedString(markdown: message.text))
84
+ .font(.deernovaBody)
85
+ .lineSpacing(6)
86
+ .textSelection(.enabled)
87
+ .frame(maxWidth: Deernova.contentMax, alignment: .leading)
88
+ ```
89
+
90
+ ## 不要
91
+
92
+ - 给 AI 回答加气泡,或者用衬线排 AI 回答。
93
+ - 用头像区分谁在说话(靠位置就够了)。
94
+ - 在正文里用彩色强调文字(强调用加粗)。