@deernova/style 0.1.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 (215) hide show
  1. package/README.md +8 -6
  2. package/bin/cli.mjs +212 -29
  3. package/package.json +3 -4
  4. package/skill/SKILL.md +91 -21
  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
  134. package/template/app.js +0 -7
  135. package/template/index.html +0 -1290
  136. package/template/manifest.json +0 -24
  137. package/template/project/README.md +0 -170
  138. package/template/project/components/Banner/README.md +0 -33
  139. package/template/project/components/Banner/preview.html +0 -26
  140. package/template/project/components/Button/README.md +0 -59
  141. package/template/project/components/Button/preview.html +0 -27
  142. package/template/project/components/Card/README.md +0 -31
  143. package/template/project/components/Card/preview.html +0 -24
  144. package/template/project/components/CodeBlock/README.md +0 -34
  145. package/template/project/components/CodeBlock/preview.html +0 -16
  146. package/template/project/components/Composer/README.md +0 -42
  147. package/template/project/components/Composer/preview.html +0 -28
  148. package/template/project/components/Cover/preview.html +0 -53
  149. package/template/project/components/EmptyState/README.md +0 -28
  150. package/template/project/components/EmptyState/preview.html +0 -16
  151. package/template/project/components/GlassControls/README.md +0 -43
  152. package/template/project/components/GlassControls/preview.html +0 -40
  153. package/template/project/components/ListGroup/README.md +0 -39
  154. package/template/project/components/ListGroup/preview.html +0 -22
  155. package/template/project/components/MacWindow/README.md +0 -27
  156. package/template/project/components/MacWindow/preview.html +0 -70
  157. package/template/project/components/Message/README.md +0 -31
  158. package/template/project/components/Message/preview.html +0 -21
  159. package/template/project/components/PhoneScreen/README.md +0 -25
  160. package/template/project/components/PhoneScreen/preview.html +0 -74
  161. package/template/project/components/Segmented/README.md +0 -21
  162. package/template/project/components/Segmented/preview.html +0 -21
  163. package/template/project/components/Sheet/README.md +0 -29
  164. package/template/project/components/Sheet/preview.html +0 -31
  165. package/template/project/components/Sidebar/README.md +0 -36
  166. package/template/project/components/Sidebar/preview.html +0 -20
  167. package/template/project/components/StepRow/README.md +0 -35
  168. package/template/project/components/StepRow/preview.html +0 -17
  169. package/template/project/components/Tag/README.md +0 -36
  170. package/template/project/components/Tag/preview.html +0 -22
  171. package/template/project/components/TextField/README.md +0 -29
  172. package/template/project/components/TextField/preview.html +0 -28
  173. package/template/project/components/bundle.css +0 -189
  174. package/template/project/design-system.json +0 -25
  175. package/template/project/images/Banner-dark.png +0 -0
  176. package/template/project/images/Banner-light.png +0 -0
  177. package/template/project/images/Button-dark.png +0 -0
  178. package/template/project/images/Button-light.png +0 -0
  179. package/template/project/images/Card-dark.png +0 -0
  180. package/template/project/images/Card-light.png +0 -0
  181. package/template/project/images/CodeBlock-dark.png +0 -0
  182. package/template/project/images/CodeBlock-light.png +0 -0
  183. package/template/project/images/Composer-dark.png +0 -0
  184. package/template/project/images/Composer-light.png +0 -0
  185. package/template/project/images/Cover-dark.png +0 -0
  186. package/template/project/images/Cover-light.png +0 -0
  187. package/template/project/images/EmptyState-dark.png +0 -0
  188. package/template/project/images/EmptyState-light.png +0 -0
  189. package/template/project/images/Example-iOSButtonsInPhone-dark.png +0 -0
  190. package/template/project/images/Example-iOSButtonsInPhone-light.png +0 -0
  191. package/template/project/images/GlassControls-dark.png +0 -0
  192. package/template/project/images/GlassControls-light.png +0 -0
  193. package/template/project/images/ListGroup-dark.png +0 -0
  194. package/template/project/images/ListGroup-light.png +0 -0
  195. package/template/project/images/MacWindow-dark.png +0 -0
  196. package/template/project/images/MacWindow-light.png +0 -0
  197. package/template/project/images/Message-dark.png +0 -0
  198. package/template/project/images/Message-light.png +0 -0
  199. package/template/project/images/PhoneScreen-dark.png +0 -0
  200. package/template/project/images/PhoneScreen-light.png +0 -0
  201. package/template/project/images/Segmented-dark.png +0 -0
  202. package/template/project/images/Segmented-light.png +0 -0
  203. package/template/project/images/Sheet-dark.png +0 -0
  204. package/template/project/images/Sheet-light.png +0 -0
  205. package/template/project/images/Sidebar-dark.png +0 -0
  206. package/template/project/images/Sidebar-light.png +0 -0
  207. package/template/project/images/StepRow-dark.png +0 -0
  208. package/template/project/images/StepRow-light.png +0 -0
  209. package/template/project/images/Tag-dark.png +0 -0
  210. package/template/project/images/Tag-light.png +0 -0
  211. package/template/project/images/TextField-dark.png +0 -0
  212. package/template/project/images/TextField-light.png +0 -0
  213. package/template/project/tokens.css +0 -187
  214. package/template/project/tokens.json +0 -142
  215. package/template/style.css +0 -1
@@ -1,24 +1,4 @@
1
1
  {
2
- "version": "0.1.0",
3
- "sourceHash": "277411693f87d8b2b805a12f90482ef52a45f52dbfe51a2e607b683f0a1119b9",
4
- "components": [
5
- "Banner",
6
- "Button",
7
- "Card",
8
- "CodeBlock",
9
- "Composer",
10
- "Cover",
11
- "EmptyState",
12
- "GlassControls",
13
- "ListGroup",
14
- "MacWindow",
15
- "Message",
16
- "PhoneScreen",
17
- "Segmented",
18
- "Sheet",
19
- "Sidebar",
20
- "StepRow",
21
- "Tag",
22
- "TextField"
23
- ]
2
+ "version": "0.3.0",
3
+ "sourceHash": "fe10f84def992286ab1d1fcb7230de8cde61c4973cc3e366603517b0add8cb9d"
24
4
  }
@@ -0,0 +1,95 @@
1
+ # 横幅与轻提示
2
+
3
+ 横幅是页面内的提示条,告诉用户一件需要知道的事,必要时附带一个解决动作。轻提示(Toast)是它的短暂版本。
4
+
5
+ **需要提供**:语气(中性 / success / warning / danger)、标题(一句话说明状态)、正文(后果或解决办法)、可选的一个动作按钮;能关闭的横幅还要提供关闭动作。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:banner -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `toast-height` | 36 | 40 | 40 | 轻提示胶囊的高度。 |
13
+ | `banner-radius` | 10 | 12 | 12 | 横幅和贴在输入区上方的提示条的圆角。 |
14
+ <!-- /tokens -->
15
+
16
+ - 中性横幅:`surface-raised` 底、`line` 描边、`ink-secondary` 图标。有语气的横幅:对应的 `-soft` 底、无描边、图标用语气色。文字始终是 `ink` 和 `ink-secondary`。
17
+ - 圆角 `banner-radius`,内边距上下 `space-2`、左右 `space-3`,图标 `icon-size-md`;标题 `type-body` 半粗,正文 `callout`。状态图标见 [图标](../foundations/iconography.md)。
18
+ - 动作按钮用小号次按钮,放在右侧,一个横幅最多一个。
19
+ - 和输入区有关的提示(额度将用完、网络断开)贴在 Composer 正上方,宽度和 Composer 一致,见 [Composer](composer.md)。
20
+ - 轻提示:底部居中的玻璃胶囊,高 `toast-height`,2 秒后自动消失,只用来确认刚刚完成的操作(「已复制」「已保存」),文字用完成态,不放按钮。读屏用 `role="status"`。
21
+
22
+ ## Web
23
+
24
+ ```css
25
+ .dn-banner {
26
+ display: flex;
27
+ align-items: flex-start;
28
+ gap: var(--space-2);
29
+ padding: var(--space-2) var(--space-3);
30
+ border: var(--stroke-line) solid var(--line);
31
+ border-radius: var(--banner-radius);
32
+ background: var(--surface-raised);
33
+ }
34
+ .dn-banner > .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); margin-top: calc((var(--type-body-line) - var(--icon-size-md)) / 2); color: var(--ink-secondary); }
35
+ .dn-banner-text { flex: 1; min-width: 0; }
36
+ .dn-banner-title { margin: 0; color: var(--ink); font: var(--weight-semibold) var(--type-body-size)/var(--type-body-line) var(--font-sans); }
37
+ .dn-banner-body { margin: 0; color: var(--ink-secondary); font: var(--type-callout); }
38
+ .dn-banner .dn-button { align-self: center; }
39
+ .dn-banner.is-success { border-color: transparent; background: var(--success-soft); }
40
+ .dn-banner.is-success > .dn-icon { color: var(--success); }
41
+ .dn-banner.is-warning { border-color: transparent; background: var(--warning-soft); }
42
+ .dn-banner.is-warning > .dn-icon { color: var(--warning); }
43
+ .dn-banner.is-danger { border-color: transparent; background: var(--danger-soft); }
44
+ .dn-banner.is-danger > .dn-icon { color: var(--danger); }
45
+
46
+ .dn-toast { display: inline-flex; align-items: center; gap: var(--space-2); height: var(--toast-height); padding: 0 var(--space-4) 0 var(--space-3); color: var(--ink); font: var(--weight-medium) var(--type-body-size)/1 var(--font-sans); }
47
+ .dn-toast .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); color: var(--success); }
48
+ ```
49
+
50
+ ```html
51
+ <div class="dn-stack">
52
+ <div class="dn-banner">
53
+ <svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/></svg>
54
+ <div class="dn-banner-text"><p class="dn-banner-title">新版本已就绪</p><p class="dn-banner-body">重新打开应用后生效,未保存的内容会自动保留。</p></div>
55
+ <button class="dn-button is-sm" type="button">重新打开</button>
56
+ </div>
57
+ <div class="dn-banner is-warning">
58
+ <svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4l9 16H3z"/><path d="M12 10v4M12 17h.01"/></svg>
59
+ <div class="dn-banner-text"><p class="dn-banner-title">本月 AI 次数还剩 10%</p><p class="dn-banner-body">用完后可以继续写笔记,AI 功能会在 11 月 1 日恢复。</p></div>
60
+ </div>
61
+ <div class="dn-banner is-danger">
62
+ <svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></svg>
63
+ <div class="dn-banner-text"><p class="dn-banner-title">同步失败</p><p class="dn-banner-body">网络连接已断开,恢复后会自动重试。</p></div>
64
+ <button class="dn-button is-sm" type="button">立即重试</button>
65
+ </div>
66
+ <div class="dn-glass dn-toast" role="status"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M8 12.5l2.5 2.5L16 9.5"/></svg>已复制到剪贴板</div>
67
+ </div>
68
+ ```
69
+
70
+ ## SwiftUI
71
+
72
+ ```swift
73
+ HStack(alignment: .top, spacing: Deernova.space2) {
74
+ Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.warning)
75
+ VStack(alignment: .leading, spacing: 2) {
76
+ Text("本月 AI 次数还剩 10%").font(.headline)
77
+ Text("用完后可以继续写笔记,AI 功能会在 11 月 1 日恢复。")
78
+ .font(.deernovaCallout).foregroundStyle(.inkSecondary)
79
+ }
80
+ }
81
+ .padding(.vertical, Deernova.space2).padding(.horizontal, Deernova.space3)
82
+ .frame(maxWidth: .infinity, alignment: .leading)
83
+ .background(.warningSoft, in: .rect(cornerRadius: Deernova.bannerRadius))
84
+
85
+ // 轻提示
86
+ Label("已复制到剪贴板", systemImage: "checkmark.circle.fill")
87
+ .padding(.horizontal, Deernova.space4).frame(height: Deernova.toastHeight)
88
+ .glassEffect(in: .capsule)
89
+ ```
90
+
91
+ ## 不要
92
+
93
+ - 一个页面同时出现多个横幅。
94
+ - 用横幅打广告;错误信息只写「出错了」。
95
+ - 轻提示里放需要用户处理的事情(那应该用横幅或弹窗)。
@@ -0,0 +1,181 @@
1
+ # 按钮
2
+
3
+ 按钮执行一个动作。每个视图最多一个主按钮(`accent` 填充:浅色主题黑底白字,深色主题白底黑字),用来完成这一屏存在的理由。
4
+
5
+ **需要提供**:标签(动词开头)、可选的图标、点击动作;加载中和禁用状态。
6
+
7
+ ## 变体
8
+
9
+ - **主按钮** `is-primary`:`accent` 底、`on-accent` 字。保存、发送、购买这类关键动作,一屏最多一个。
10
+ - **次按钮**(默认):`fill-selected` 灰底、无描边、无阴影、`ink` 字。半透明的灰底放在任何纸面上都成立。
11
+ - **文字按钮** `is-plain`:无底,`ink-secondary` 字,悬停时出现 `fill-hover`。工具栏和卡片里的次要操作。
12
+ - **危险按钮** `is-danger`:`danger-soft` 底、`danger` 字。只有在最终确认弹窗里,才用实色的危险样式(SwiftUI 的 `role: .destructive`)。
13
+ - **图标按钮** `dn-icon-button`:只有图标,无底,`ink-secondary`,必须有无障碍标签。
14
+
15
+ ## 尺寸
16
+
17
+ <!-- tokens:button -->
18
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
19
+ | --- | --- | --- | --- | --- |
20
+ | `button-height-sm` | 24 | 30 | 32 | 小按钮的可见高度:列表行、卡片角落里的行内操作。 |
21
+ | `button-height-md` | 28 | 36 | 36 | 默认按钮、图标按钮、发送按钮、步骤行的高度。 |
22
+ | `button-height-lg` | 32 | 46 | 44 | 大按钮:空状态、引导页;触控端是整页唯一的通栏主按钮。 |
23
+ | `button-padding-x-sm` | 8 | 12 | 14 | 小按钮左右内边距。上下内边距永远是 0。 |
24
+ | `button-padding-x-md` | 12 | 16 | 18 | 默认按钮左右内边距。 |
25
+ | `button-padding-x-lg` | 16 | 20 | 24 | 大按钮左右内边距。 |
26
+ | `button-font-size-sm` | 12 | 13 | 13 | 小按钮字号。 |
27
+ | `button-font-size-md` | 14 | 15 | 14 | 默认按钮字号。 |
28
+ | `button-font-size-lg` | 14 | 16 | 15 | 大按钮字号。 |
29
+ | `button-font-weight` | 500 | 600 | 500 | 按钮字重。 |
30
+ | `button-radius` | 6 | 胶囊 | 胶囊 | 默认和大按钮的圆角:指针端是 radius-sm,触控端和官网是胶囊。 |
31
+ | `button-radius-sm` | 4 | 胶囊 | 胶囊 | 小按钮的圆角。 |
32
+ <!-- /tokens -->
33
+
34
+ 高度只由 `button-height-*` 决定:**上下内边距为 0,文字行高为 1**。按钮里的文字不能继承页面的行高,否则按钮会跟着页面行高变高。
35
+
36
+ 可见高度小于 `hit-min` 时(触控端的 30 和 36),点击区域用透明的命中区补足到 44,可见的按钮不变大。
37
+
38
+ **怎么选尺寸**:按按钮所在的位置定,不随意挑。
39
+
40
+ - 列表行、卡片角落里的行内操作(获取、关注、打开)→ 小号 `is-sm`。
41
+ - 页面和卡片里的普通按钮,包括并排的两个按钮(以后再说 / 开启)→ 默认。同一行里的按钮用同一个尺寸。
42
+ - 空状态、引导页的操作 → 大号 `is-lg`。触控端整页唯一的底部主操作(登录、订阅、开始使用)用大号加 `is-block` 通栏,一屏最多一个。
43
+ - 导航栏、标签栏、工具栏里的按钮 → 用系统控件或[玻璃操作层](glass-controls.md),不自己设尺寸。
44
+
45
+ 触控端比指针端大是有意的:手指比鼠标指针粗。三种尺寸放在手机里的样子见 `assets/screens/iphone-button-sizes-light.png`。
46
+
47
+ ## 状态
48
+
49
+ - 悬停:次按钮在灰底上再叠一层 `fill-hover`,文字按钮出现 `fill-hover`,主按钮不透明度降到 `opacity-solid-hover`。
50
+ - 焦点:`stroke-focus` 宽的 `focus-ring`,外偏移 `focus-offset`,只在键盘操作(`:focus-visible`)时显示。
51
+ - 禁用:`opacity-disabled`,保持原位,不隐藏。
52
+ - 加载:按钮宽度不变,把标签换成转圈,`aria-busy="true"`。
53
+
54
+ ## Web
55
+
56
+ ```css
57
+ .dn-button {
58
+ position: relative;
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: var(--gap-inline);
63
+ height: var(--button-height-md);
64
+ padding: 0 var(--button-padding-x-md); /* 上下永远是 0:高度只由 height 决定 */
65
+ border: 0;
66
+ border-radius: var(--button-radius);
67
+ background: var(--fill-selected);
68
+ color: var(--ink);
69
+ font: var(--button-font-weight) var(--button-font-size-md)/1 var(--font-sans); /* 行高 1,不继承页面行高 */
70
+ white-space: nowrap;
71
+ text-decoration: none;
72
+ cursor: pointer;
73
+ transition: background-color var(--duration-instant) var(--ease-standard), opacity var(--duration-instant) var(--ease-standard);
74
+ }
75
+ .dn-button:hover:not(:disabled) { background-image: linear-gradient(var(--fill-hover), var(--fill-hover)); }
76
+ .dn-button:active:not(:disabled) { background-image: linear-gradient(var(--fill-selected), var(--fill-selected)); }
77
+ .dn-button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
78
+ .dn-button:disabled { opacity: var(--opacity-disabled); cursor: default; }
79
+
80
+ .dn-button.is-primary { background: var(--accent); color: var(--on-accent); }
81
+ .dn-button.is-primary:hover:not(:disabled), .dn-button.is-primary:active { background-image: none; opacity: var(--opacity-solid-hover); }
82
+ .dn-button.is-plain { background: transparent; color: var(--ink-secondary); }
83
+ .dn-button.is-plain:hover:not(:disabled) { background: var(--fill-hover); color: var(--ink); }
84
+ .dn-button.is-danger { background: var(--danger-soft); color: var(--danger); }
85
+
86
+ .dn-button.is-sm { height: var(--button-height-sm); padding: 0 var(--button-padding-x-sm); border-radius: var(--button-radius-sm); font-size: var(--button-font-size-sm); }
87
+ .dn-button.is-lg { height: var(--button-height-lg); padding: 0 var(--button-padding-x-lg); font-size: var(--button-font-size-lg); }
88
+ .dn-button.is-block { width: 100%; }
89
+ .dn-button .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); }
90
+
91
+ .dn-icon-button {
92
+ position: relative;
93
+ display: inline-grid;
94
+ place-items: center;
95
+ flex: none;
96
+ width: var(--button-height-md);
97
+ height: var(--button-height-md);
98
+ padding: 0;
99
+ border: 0;
100
+ border-radius: var(--button-radius);
101
+ background: transparent;
102
+ color: var(--ink-secondary);
103
+ cursor: pointer;
104
+ transition: background-color var(--duration-instant) var(--ease-standard);
105
+ }
106
+ .dn-icon-button:hover { background: var(--fill-hover); color: var(--ink); }
107
+ .dn-icon-button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
108
+
109
+ /* 点击区域:可见尺寸不变,用透明的伪元素把可点范围撑到 hit-min。
110
+ 不要用 padding 补:padding 在背景里面,会把可见的按钮一起撑大。 */
111
+ .dn-button::before, .dn-icon-button::before {
112
+ content: "";
113
+ position: absolute;
114
+ top: 50%;
115
+ left: 50%;
116
+ width: max(100%, var(--hit-min));
117
+ height: max(100%, var(--hit-min));
118
+ transform: translate(-50%, -50%);
119
+ }
120
+ ```
121
+
122
+ ```html
123
+ <div class="dn-row">
124
+ <button class="dn-button is-primary" type="button">保存</button>
125
+ <button class="dn-button" type="button">取消</button>
126
+ <button class="dn-button is-plain" type="button">跳过</button>
127
+ <button class="dn-button is-danger" type="button">删除</button>
128
+ <button class="dn-button is-primary" type="button" disabled>保存</button>
129
+ <button class="dn-icon-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>
130
+ </div>
131
+ <div class="dn-row">
132
+ <button class="dn-button is-sm" type="button">获取</button>
133
+ <button class="dn-button" type="button"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>新建笔记</button>
134
+ <button class="dn-button is-primary is-lg" type="button">开始使用</button>
135
+ </div>
136
+ ```
137
+
138
+ ## SwiftUI
139
+
140
+ macOS 直接用系统按钮,`.controlSize(.regular)` 就是 24pt。iOS 的可见高度和点击区域分开处理。
141
+
142
+ ```swift
143
+ Button("保存") { save() }
144
+ .buttonStyle(.borderedProminent) // 主按钮,颜色跟随 .tint(.brandAccent)
145
+ Button("取消", role: .cancel) { dismiss() }
146
+ .buttonStyle(.bordered)
147
+ Button("删除", role: .destructive) { delete() }
148
+
149
+ // iOS 默认按钮:看起来 36pt,点击区域补足到 44pt
150
+ Button("继续") { next() }
151
+ .font(.subheadline.weight(.semibold))
152
+ .buttonStyle(.borderedProminent)
153
+ .buttonBorderShape(.capsule)
154
+ .controlSize(.regular)
155
+ .padding(.vertical, (Deernova.hitMin - Deernova.buttonHeightMd) / 2) // 写在 buttonStyle 之后,是透明的
156
+ .contentShape(.rect)
157
+
158
+ // iOS 底部通栏主按钮
159
+ Button { start() } label: {
160
+ Text("开始使用").font(.callout.weight(.semibold))
161
+ .frame(maxWidth: .infinity, minHeight: Deernova.buttonHeightLg)
162
+ }
163
+ .buttonStyle(.borderedProminent)
164
+ .buttonBorderShape(.capsule)
165
+
166
+ // 加载中:宽度不变,标签换成转圈
167
+ Button { save() } label: {
168
+ ZStack { Text("保存").opacity(isSaving ? 0 : 1); if isSaving { ProgressView() } }
169
+ }
170
+ ```
171
+
172
+ 放在玻璃层上时改用 `.glassProminent` / `.glass`,见 [玻璃操作层](glass-controls.md)。
173
+
174
+ ## 不要
175
+
176
+ - 用上下内边距撑出按钮高度,或让按钮继承页面的行高。
177
+ - 为了凑 44 点击区域把可见的按钮做成 44 高。
178
+ - 给次按钮加描边和阴影(它是一块灰底,不是浮起来的卡片)。
179
+ - 一个视图里放两个主按钮;为了强调把次要按钮也做成黑底。
180
+ - 用「确定」「OK」做标签;按钮里写一整句话。
181
+ - 把官网的胶囊大按钮搬进应用界面。
@@ -0,0 +1,89 @@
1
+ # 卡片
2
+
3
+ 卡片把一条内容的摘要装进一个可以点开的块里,用于网格和看板。
4
+
5
+ **需要提供**:标题、一到两行摘要、元信息(时间、分类、状态[标签](tag.md)),点击后打开详情。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:card -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `card-padding-x` | 16 | 16 | 24 | 卡片左右内边距。 |
13
+ | `card-padding-y` | 12 | 14 | 24 | 卡片上下内边距。 |
14
+ | `card-radius` | 12 | 12 | 12 | 卡片、分组列表、任务清单的圆角。 |
15
+ | `card-gap` | 12 | 12 | 16 | 卡片网格的间距。 |
16
+ <!-- /tokens -->
17
+
18
+ - `surface` 底、1px(`stroke-line`)`line` 描边,不加阴影,圆角 `card-radius`。
19
+ - 标题用 `type-headline`(无衬线、半粗);摘要用 `footnote`、`ink-secondary`,最多 3 行;元信息用 `caption`、`ink-tertiary`。内部间距 `space-1`。
20
+ - 悬停时描边加深为 `line-control`。选中时描边变成 `stroke-control` 宽的 `ink`。
21
+ - 网格间距 `card-gap`;每张卡片高度由内容决定,同一行顶部对齐。
22
+
23
+ ## Web
24
+
25
+ 整张卡片可点时,用 `<a>` 或 `<button>` 包住整张卡片,不要在卡片里再放一个主按钮。
26
+
27
+ ```css
28
+ .dn-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--card-gap); align-items: start; }
29
+ .dn-card {
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: var(--space-1);
33
+ min-width: 0;
34
+ padding: var(--card-padding-y) var(--card-padding-x);
35
+ border: var(--stroke-line) solid var(--line);
36
+ border-radius: var(--card-radius);
37
+ background: var(--surface);
38
+ color: var(--ink);
39
+ text-decoration: none;
40
+ transition: border-color var(--duration-instant) var(--ease-standard);
41
+ }
42
+ a.dn-card:hover, button.dn-card:hover { border-color: var(--line-control); }
43
+ .dn-card:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
44
+ .dn-card[aria-current="true"], .dn-card.is-selected { border-color: var(--ink); }
45
+ .dn-card-title { margin: 0; font: var(--type-headline); }
46
+ .dn-card-summary {
47
+ display: -webkit-box;
48
+ margin: 0;
49
+ overflow: hidden;
50
+ color: var(--ink-secondary);
51
+ font: var(--type-footnote);
52
+ -webkit-line-clamp: 3;
53
+ -webkit-box-orient: vertical;
54
+ }
55
+ .dn-card-meta { display: flex; align-items: center; gap: 6px; margin-top: 2px; color: var(--ink-tertiary); font: var(--weight-regular) var(--type-caption-size)/var(--type-caption-line) var(--font-sans); font-variant-numeric: tabular-nums; }
56
+ ```
57
+
58
+ ```html
59
+ <div class="dn-card-grid">
60
+ <a class="dn-card" href="#"><h3 class="dn-card-title">本周回顾</h3><p class="dn-card-summary">完成 12 条笔记,整理了两个项目的待办。</p><div class="dn-card-meta">今天 09:41</div></a>
61
+ <a class="dn-card is-selected" href="#" aria-current="true"><h3 class="dn-card-title">读书摘录</h3><p class="dn-card-summary">好的工具会退到背景里,让人只看见自己的工作。</p><div class="dn-card-meta">昨天</div></a>
62
+ <a class="dn-card" href="#"><h3 class="dn-card-title">旅行清单</h3><p class="dn-card-summary">护照、转换插头、两本书、一件薄外套。</p><div class="dn-card-meta">10 月 28 日</div></a>
63
+ </div>
64
+ ```
65
+
66
+ ## SwiftUI
67
+
68
+ ```swift
69
+ VStack(alignment: .leading, spacing: Deernova.space1) {
70
+ Text(note.title).font(.deernovaHeadline)
71
+ Text(note.summary).font(.deernovaFootnote).foregroundStyle(.inkSecondary).lineLimit(3)
72
+ Text(note.date, format: .relative(presentation: .named))
73
+ .font(.caption).foregroundStyle(.inkTertiary)
74
+ }
75
+ .padding(.horizontal, Deernova.cardPaddingX)
76
+ .padding(.vertical, Deernova.cardPaddingY)
77
+ .frame(maxWidth: .infinity, alignment: .leading)
78
+ .background(.surface, in: .rect(cornerRadius: Deernova.cardRadius))
79
+ .overlay(
80
+ RoundedRectangle(cornerRadius: Deernova.cardRadius)
81
+ .strokeBorder(isSelected ? .ink : .line, lineWidth: Deernova.strokeLine)
82
+ )
83
+ ```
84
+
85
+ ## 不要
86
+
87
+ - 给卡片加阴影或玻璃效果。
88
+ - 在卡片左侧加一条彩色竖条。
89
+ - 卡片里套卡片;一张卡片里放多个主按钮。
@@ -0,0 +1,77 @@
1
+ # 代码块
2
+
3
+ 展示代码、命令或结构化数据,带语言标签和复制按钮。
4
+
5
+ **需要提供**:代码文本、语言名称;可选的文件名(替代语言名显示在表头)。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:code-block -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `code-header-height` | 32 | 36 | 36 | 代码块表头的高度。 |
13
+ <!-- /tokens -->
14
+
15
+ - `surface-sunken` 底、`line` 细线描边、`radius-md`。表头高 `code-header-height`,左边是语言名或文件名(`caption` 字号、等宽、`ink-tertiary`),右边是一个小号文字按钮「复制」;点击后显示「已复制」1.5 秒,按钮宽度不变。
16
+ - 代码用 `type-code`,内边距上下 `space-2`、左右 `space-3`,过长时横向滚动,不自动换行。
17
+ - 语法高亮只用灰阶:关键字 `ink` 加粗,字符串 `ink-secondary`,注释 `ink-tertiary` 加斜体,其余 `ink`。这些颜色在 `surface-sunken` 上都不低于 4.5:1。
18
+
19
+ ## Web
20
+
21
+ ```css
22
+ .dn-code { overflow: hidden; min-width: 0; border: var(--stroke-line) solid var(--line); border-radius: var(--radius-md); background: var(--surface-sunken); }
23
+ .dn-code-header {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--space-2);
27
+ height: var(--code-header-height);
28
+ padding: 0 var(--space-1) 0 var(--space-3);
29
+ border-bottom: var(--stroke-line) solid var(--line);
30
+ color: var(--ink-tertiary);
31
+ font: var(--weight-regular) var(--type-caption-size)/1 var(--font-mono);
32
+ }
33
+ .dn-code-header .dn-button { margin-left: auto; }
34
+ .dn-code pre { margin: 0; padding: var(--space-2) var(--space-3); overflow-x: auto; color: var(--ink); font: var(--type-code); }
35
+ .dn-code code { font: inherit; }
36
+ .dn-code .tk-keyword { color: var(--ink); font-weight: var(--weight-semibold); }
37
+ .dn-code .tk-string { color: var(--ink-secondary); }
38
+ .dn-code .tk-comment { color: var(--ink-tertiary); font-style: italic; }
39
+ ```
40
+
41
+ ```html
42
+ <div class="dn-code">
43
+ <div class="dn-code-header">swift<button class="dn-button is-plain is-sm" type="button"><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></div>
44
+ <pre><code><span class="tk-comment">// 统计本周完成的笔记数</span>
45
+ <span class="tk-keyword">let</span> done = notes.filter { $0.completedAt &gt;= start }
46
+ <span class="tk-keyword">print</span>(<span class="tk-string">"本周完成 \(done.count) 条"</span>)</code></pre>
47
+ </div>
48
+ ```
49
+
50
+ ## SwiftUI
51
+
52
+ ```swift
53
+ VStack(alignment: .leading, spacing: 0) {
54
+ HStack {
55
+ Text(language).font(.caption.monospaced()).foregroundStyle(.inkTertiary)
56
+ Spacer()
57
+ Button(copied ? "已复制" : "复制", systemImage: copied ? "checkmark" : "doc.on.doc") { copy() }
58
+ .buttonStyle(.borderless).controlSize(.small)
59
+ }
60
+ .padding(.horizontal, Deernova.space3).frame(height: Deernova.codeHeaderHeight)
61
+ Divider()
62
+ ScrollView(.horizontal) {
63
+ Text(highlighted) // 用 AttributedString 按上面的颜色着色
64
+ .font(.deernovaCode)
65
+ .textSelection(.enabled)
66
+ .padding(.vertical, Deernova.space2).padding(.horizontal, Deernova.space3)
67
+ }
68
+ }
69
+ .background(.surfaceSunken, in: .rect(cornerRadius: Deernova.radiusMd))
70
+ .overlay(RoundedRectangle(cornerRadius: Deernova.radiusMd).strokeBorder(.line, lineWidth: 1 / displayScale))
71
+ ```
72
+
73
+ ## 不要
74
+
75
+ - 用彩色的编辑器配色主题(和黑白灰的整体风格冲突)。
76
+ - 给代码块加阴影。
77
+ - 自动换行打乱缩进。