@deernova/style 0.2.0 → 0.3.1

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 +19 -9
  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 +34 -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,79 @@
1
+ # 分段控件
2
+
3
+ 在同一份数据的 2–4 种视图或范围之间切换。
4
+
5
+ **需要提供**:选项列表(每项 2–4 个字,或者一个图标)和绑定的选中值。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:segmented -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `segmented-height` | 24 | 28 | 28 | 选项块的高度;外层再加上下各 segmented-padding。 |
13
+ | `segmented-padding` | 2 | 2 | 2 | 底槽内边距。 |
14
+ | `segmented-padding-x` | 12 | 16 | 16 | 选项块左右内边距。 |
15
+ | `segmented-font-size` | 13 | 14 | 14 | 选项字号。 |
16
+ | `segmented-radius` | 6 | 胶囊 | 胶囊 | 底槽圆角;选项块圆角 = 它减去 segmented-padding。 |
17
+ <!-- /tokens -->
18
+
19
+ - 底槽 `fill-selected`;选中块 `surface-raised` 加 `shadow-control`;未选中文字 `ink-secondary`,选中文字 `ink`。
20
+ - 选中块的圆角 = `segmented-radius` − `segmented-padding`,两层同心。指针端是小圆角矩形,触控端是胶囊。
21
+ - 选项等宽或按文字宽度排列都可以,同一个控件里保持一致。
22
+
23
+ ## Web
24
+
25
+ 用一组 `button` 加 `aria-pressed`;如果它切换的是一组面板,改用 `role="tablist"` 和 `role="tab"`。
26
+
27
+ ```css
28
+ .dn-segmented {
29
+ display: inline-flex;
30
+ gap: 2px;
31
+ padding: var(--segmented-padding);
32
+ border-radius: var(--segmented-radius);
33
+ background: var(--fill-selected);
34
+ }
35
+ .dn-segmented button {
36
+ position: relative;
37
+ height: var(--segmented-height);
38
+ padding: 0 var(--segmented-padding-x);
39
+ border: 0;
40
+ border-radius: calc(var(--segmented-radius) - var(--segmented-padding));
41
+ background: transparent;
42
+ color: var(--ink-secondary);
43
+ font: var(--weight-medium) var(--segmented-font-size)/1 var(--font-sans);
44
+ white-space: nowrap;
45
+ cursor: pointer;
46
+ transition: background-color var(--duration-instant) var(--ease-standard), color var(--duration-instant) var(--ease-standard);
47
+ }
48
+ .dn-segmented button:hover { color: var(--ink); }
49
+ .dn-segmented button[aria-pressed="true"], .dn-segmented button[aria-selected="true"] { background: var(--surface-raised); color: var(--ink); box-shadow: var(--shadow-control); }
50
+ .dn-segmented button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
51
+ ```
52
+
53
+ ```html
54
+ <div class="dn-segmented" role="group" aria-label="视图">
55
+ <button type="button" aria-pressed="true">列表</button>
56
+ <button type="button" aria-pressed="false">卡片</button>
57
+ <button type="button" aria-pressed="false">日历</button>
58
+ </div>
59
+ ```
60
+
61
+ ## SwiftUI
62
+
63
+ 直接用系统控件就能得到上面的效果,不需要自己画。
64
+
65
+ ```swift
66
+ Picker("视图", selection: $mode) {
67
+ Text("列表").tag(Mode.list)
68
+ Text("卡片").tag(Mode.cards)
69
+ Text("日历").tag(Mode.calendar)
70
+ }
71
+ .pickerStyle(.segmented)
72
+ .labelsHidden()
73
+ ```
74
+
75
+ ## 不要
76
+
77
+ - 选项超过 4 个(改用菜单或下拉选择)。
78
+ - 拿它当执行动作的按钮组。
79
+ - 选项文字长短差别太大。
@@ -0,0 +1,84 @@
1
+ # 弹窗与底部弹窗
2
+
3
+ 用于一个短小、独立的任务:确认购买、编辑一个字段、选择分享方式。触控端从底部弹出,指针端在窗口中央或附着在窗口上。
4
+
5
+ **需要提供**:标题、一两句说明、内容(短表单或选项)、主操作和取消操作;底部弹窗还要提供支持的高度档位。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:sheet -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `sheet-radius` | 12 | 24 | 16 | 弹窗和底部弹窗的圆角。 |
13
+ | `menu-radius` | 8 | 12 | 12 | 弹出菜单、popover 的圆角。 |
14
+ <!-- /tokens -->
15
+
16
+ - 弹窗:`surface-raised` 底、`line` 细线描边、`sheet-radius`、`shadow-float`,背后用 `scrim` 压暗。弹出菜单和 popover 用 `menu-radius`。
17
+ - 标题用 `type-title`(衬线),说明用 `callout`、`ink-secondary`,下方留 `space-4`。
18
+ - 触控端:顶部拖拽条 36×5、`line-control` 色;主操作是[大号通栏按钮](button.md),下面是同尺寸的文字按钮「暂不需要」,两者间距 `space-1`。
19
+ - 指针端:按钮靠右排成一行,主按钮在最右边,取消在它左边。Esc 关闭。
20
+ - 内容超过一屏,或需要长时间阅读时,改成全高弹窗或推入新页面,背景用 `canvas`。
21
+ - iOS 26 上系统会把中等高度的弹窗渲染成悬浮的玻璃面板;拉到全高后变成不透明。Mac 上对应的是附着在窗口上的 sheet 和系统 Alert。直接使用系统样式。
22
+
23
+ ## Web
24
+
25
+ 用 `<dialog>` 和 `showModal()`:自带焦点管理、Esc 关闭和背景遮罩。
26
+
27
+ ```css
28
+ .dn-sheet {
29
+ box-sizing: border-box;
30
+ width: min(100% - 2 * var(--page-margin), 420px);
31
+ padding: var(--space-5);
32
+ border: var(--stroke-line) solid var(--line);
33
+ border-radius: var(--sheet-radius);
34
+ background: var(--surface-raised);
35
+ color: var(--ink);
36
+ box-shadow: var(--shadow-float);
37
+ }
38
+ .dn-sheet::backdrop { background: var(--scrim); }
39
+ .dn-sheet-grabber { width: 36px; height: 5px; margin: calc(var(--space-3) * -1) auto var(--space-3); border-radius: var(--radius-full); background: var(--line-control); }
40
+ .dn-sheet-title { margin: 0 0 var(--space-1); font: var(--type-title); }
41
+ .dn-sheet-body { margin: 0 0 var(--space-4); color: var(--ink-secondary); font: var(--type-callout); }
42
+ .dn-sheet-actions { display: flex; justify-content: flex-end; gap: var(--gap-stack); }
43
+ @media (pointer: coarse) {
44
+ .dn-sheet[open] { margin: auto auto var(--space-2); }
45
+ .dn-sheet-actions { flex-direction: column; gap: var(--space-1); }
46
+ .dn-sheet-actions .dn-button { width: 100%; height: var(--button-height-lg); }
47
+ }
48
+ @media (pointer: fine) { .dn-sheet-grabber { display: none; } }
49
+ ```
50
+
51
+ ```html
52
+ <div class="dn-sheet" role="dialog" aria-labelledby="sheet-title">
53
+ <div class="dn-sheet-grabber" aria-hidden="true"></div>
54
+ <h3 class="dn-sheet-title" id="sheet-title">开启 iCloud 同步</h3>
55
+ <p class="dn-sheet-body">笔记会自动同步到你登录同一 Apple 账户的所有设备。</p>
56
+ <div class="dn-sheet-actions">
57
+ <button class="dn-button is-plain" type="button">暂不需要</button>
58
+ <button class="dn-button is-primary" type="button">开启</button>
59
+ </div>
60
+ </div>
61
+ ```
62
+
63
+ 真实页面里外层用 `<dialog class="dn-sheet">`,打开时调用 `dialog.showModal()`。
64
+
65
+ ## SwiftUI
66
+
67
+ ```swift
68
+ .sheet(isPresented: $showPaywall) {
69
+ PaywallView()
70
+ .presentationDetents([.medium, .large])
71
+ .presentationDragIndicator(.visible)
72
+ }
73
+
74
+ // 确认删除这类二选一的决定,用系统对话框
75
+ .confirmationDialog("删除这条笔记?", isPresented: $confirmDelete) {
76
+ Button("删除", role: .destructive) { delete() }
77
+ }
78
+ ```
79
+
80
+ ## 不要
81
+
82
+ - 在弹窗里再弹一个弹窗。
83
+ - 把长篇文章放进玻璃弹窗。
84
+ - 没有取消方式;把价格之类的关键信息藏在小字里。
@@ -0,0 +1,139 @@
1
+ # 侧边栏
2
+
3
+ 应用的主导航。由上到下:带图标的全局入口(新建、搜索、定时任务……)、按项目分组的会话(组标题可以折叠,会话名不带图标)、底部的账号和设置。macOS、iPadOS 和桌面 Web 应用都用它;iPhone 和窄屏 Web 收起成导航栈或抽屉。
4
+
5
+ **需要提供**:全局入口(图标、名称、可选的快捷键);分组(名称、是否展开)和每组的条目;当前选中项;账号信息和设置入口。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:sidebar -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `row-height` | 32 | 44 | 40 | 侧边栏项、列表行的最小高度。 |
13
+ | `row-padding-x` | 10 | 16 | 12 | 行的左右内边距;分隔线从这里开始。 |
14
+ | `row-radius` | 8 | 10 | 8 | 悬停和选中底色的圆角。 |
15
+ | `row-gap` | 2 | 0 | 2 | 侧边栏项之间的间隔。触控端的列表用分隔线,不留间隔。 |
16
+ <!-- /tokens -->
17
+
18
+ - **macOS / iPadOS**:用系统的 `NavigationSplitView` 侧边栏,它是悬浮的玻璃面板;行高、字号交给系统,不要加实色背景。
19
+ - **Web**:宽 `sidebar-width`,`surface` 底(比内容区亮一层),右边一条 1px `line`;用户可以拖动调整宽度。
20
+ - 每项高 `row-height`,项与项之间 `row-gap`,左右内边距 `row-padding-x`,选中块离侧边栏边缘 `space-2`,圆角 `row-radius`。
21
+ - 全局入口:图标 `icon-size-md`、`ink-secondary`,文字 `type-body`、`ink`。
22
+ - 分组标题:`footnote` 字号、**常规字重**、`ink-tertiary`,后面一个小箭头,点击折叠。组与组之间 `space-4`。
23
+ - 会话名:`callout` 字号、`ink`,不带图标,末尾截断,悬停时显示完整名称。
24
+ - 悬停 `fill-hover`;选中 `fill-selected`,字重不变。选中块不用黑色填满,保持安静。
25
+ - 底部:头像、账号名(`callout`)、设置齿轮,上方一条 1px `line`。
26
+ - 计数和快捷键放在右侧,`caption` 字号、`ink-tertiary`,用等宽数字。
27
+
28
+ ## Web
29
+
30
+ ```css
31
+ .dn-sidebar {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--space-4);
35
+ width: var(--sidebar-width);
36
+ max-width: 100%;
37
+ padding: var(--space-2);
38
+ border-right: var(--stroke-line) solid var(--line);
39
+ background: var(--surface);
40
+ }
41
+ .dn-sidebar-section, .dn-sidebar-group { display: flex; flex-direction: column; gap: var(--row-gap); }
42
+ .dn-sidebar-heading {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: var(--space-1);
46
+ min-height: var(--row-height);
47
+ padding: 0 var(--row-padding-x);
48
+ border-radius: var(--row-radius);
49
+ color: var(--ink-tertiary);
50
+ font: var(--type-footnote);
51
+ list-style: none;
52
+ cursor: pointer;
53
+ }
54
+ .dn-sidebar-heading::-webkit-details-marker { display: none; }
55
+ .dn-sidebar-heading .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); transition: transform var(--duration-base) var(--ease-standard); }
56
+ .dn-sidebar-group:not([open]) > .dn-sidebar-heading .dn-icon { transform: rotate(-90deg); }
57
+ .dn-sidebar-heading:hover { color: var(--ink-secondary); }
58
+ .dn-sidebar-item {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: var(--space-2);
62
+ min-width: 0;
63
+ min-height: var(--row-height);
64
+ padding: 0 var(--row-padding-x);
65
+ border-radius: var(--row-radius);
66
+ color: var(--ink);
67
+ font: var(--type-body);
68
+ text-decoration: none;
69
+ transition: background-color var(--duration-instant) var(--ease-standard);
70
+ }
71
+ .dn-sidebar-group .dn-sidebar-item { font: var(--type-callout); }
72
+ .dn-sidebar-item .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); color: var(--ink-secondary); }
73
+ .dn-sidebar-item:hover { background: var(--fill-hover); }
74
+ .dn-sidebar-item[aria-current="page"] { background: var(--fill-selected); }
75
+ .dn-sidebar-item:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: calc(var(--focus-offset) * -1); }
76
+ .dn-sidebar-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
77
+ .dn-sidebar-count { color: var(--ink-tertiary); font: var(--weight-regular) var(--type-caption-size)/1 var(--font-sans); font-variant-numeric: tabular-nums; }
78
+ .dn-sidebar-footer {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: var(--space-2);
82
+ margin-top: auto;
83
+ padding: var(--space-2) var(--row-padding-x) 0;
84
+ border-top: var(--stroke-line) solid var(--line);
85
+ font: var(--type-callout);
86
+ }
87
+ .dn-avatar { display: grid; place-items: center; flex: none; width: var(--button-height-sm); height: var(--button-height-sm); border-radius: var(--radius-full); background: var(--fill-selected); color: var(--ink); font: var(--weight-medium) var(--type-caption-size)/1 var(--font-sans); }
88
+ ```
89
+
90
+ ```html
91
+ <nav class="dn-sidebar" aria-label="侧边栏">
92
+ <div class="dn-sidebar-section">
93
+ <a class="dn-sidebar-item" href="#"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg><span class="dn-sidebar-label">新建</span><span class="dn-sidebar-count">⌘N</span></a>
94
+ <a class="dn-sidebar-item" href="#"><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><span class="dn-sidebar-label">搜索</span></a>
95
+ <a class="dn-sidebar-item" href="#"><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><span class="dn-sidebar-label">定时任务</span></a>
96
+ </div>
97
+ <details class="dn-sidebar-group" open>
98
+ <summary class="dn-sidebar-heading">deernova-style<svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg></summary>
99
+ <a class="dn-sidebar-item" href="#" aria-current="page"><span class="dn-sidebar-label">Deernova UI 风格指南优化</span></a>
100
+ <a class="dn-sidebar-item" href="#"><span class="dn-sidebar-label">首页麋鹿的散步动画</span></a>
101
+ </details>
102
+ <details class="dn-sidebar-group" open>
103
+ <summary class="dn-sidebar-heading">dumbo<svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg></summary>
104
+ <a class="dn-sidebar-item" href="#"><span class="dn-sidebar-label">沙箱个人助理实现审查</span></a>
105
+ </details>
106
+ <div class="dn-sidebar-footer"><span class="dn-avatar" aria-hidden="true">林</span><span class="dn-sidebar-label">林一 · Pro</span><button class="dn-icon-button" type="button" aria-label="设置"><svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/></svg></button></div>
107
+ </nav>
108
+ ```
109
+
110
+ ## SwiftUI
111
+
112
+ 系统侧边栏自带玻璃和系统行高,直接用;分组用可折叠的 `Section`。
113
+
114
+ ```swift
115
+ NavigationSplitView {
116
+ List(selection: $selection) {
117
+ Label("新建", systemImage: "plus").tag(Item.new)
118
+ Label("搜索", systemImage: "magnifyingglass").tag(Item.search)
119
+ ForEach(projects) { project in
120
+ Section(project.name, isExpanded: $expanded[project.id]) {
121
+ ForEach(project.chats) { chat in
122
+ Text(chat.title).lineLimit(1).tag(Item.chat(chat.id))
123
+ }
124
+ }
125
+ }
126
+ }
127
+ .navigationSplitViewColumnWidth(min: 220, ideal: Deernova.sidebarWidth, max: 360)
128
+ .safeAreaInset(edge: .bottom) { AccountRow() } // 头像、账号名、设置
129
+ } detail: {
130
+ DetailView(selection: selection)
131
+ }
132
+ .tint(.brandAccent)
133
+ ```
134
+
135
+ ## 不要
136
+
137
+ - 在 SwiftUI 端给侧边栏加实色背景盖掉系统玻璃;在 Web 上给侧边栏加模糊效果。
138
+ - 分组标题用粗体或大写字母;会话名前加图标。
139
+ - 一级导航超过两层;用彩色图标区分每一项(图标一律灰色)。
@@ -0,0 +1,135 @@
1
+ # 工具调用与执行步骤
2
+
3
+ AI 在对话里做了什么。分两层:平时只显示一行灰色的**摘要**(「已运行 2 个命令,已读取 3 个文件」),点开才是逐条的**执行步骤**(做了什么、结果如何、用了多久);正在做的事显示为**运行中**状态。也用于导入导出、批量处理这类任务。
4
+
5
+ **需要提供**:摘要文字(动词 + 数量);每一步的状态(进行中 / 成功 / 失败)、动作名(2–4 个字的动词)、对象摘要(文件名、参数、结果)、耗时;可选的详细内容和缩略图;运行中的动作名和已用时间。
6
+
7
+ ## 尺寸
8
+
9
+ 没有自己的 token:行高用 `button-height-md`,图标用 `icon-size-md`。
10
+
11
+ - **摘要行**:`type-body` 字号、`ink-secondary`,末尾一个小箭头,点开后箭头转向下;悬停时文字变 `ink`。没有底色、没有边框,和正文段落之间 `space-4`。
12
+ - **执行步骤**:在摘要下方缩进,左边一条 1px `line`。每步高 `button-height-md`,`radius-sm`,悬停 `fill-hover`。状态图标:进行中是 `ink` 色的转圈,成功 `success`,失败 `danger`(失败时摘要写清楚原因)。动作名 `callout` 半粗 `ink`;对象摘要 `caption` 等宽、单行截断;耗时 `caption`、`ink-tertiary`、等宽数字。
13
+ - **步骤详情**:再点开一步,缩进到和文字对齐,`surface-sunken` 底,`radius-sm`,等宽。
14
+ - **运行中**:三个依次闪烁的圆点(`ink-secondary`)+ 动作名 + 已用时间(`ink-tertiary`),`type-body` 字号。减弱动态效果时圆点不闪。
15
+
16
+ ## Web
17
+
18
+ 用 `<details>` 和 `<summary>` 实现展开,键盘和读屏都能用。
19
+
20
+ ```css
21
+ .dn-activity { min-width: 0; }
22
+ .dn-activity > summary {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--space-1);
26
+ min-height: var(--button-height-md);
27
+ color: var(--ink-secondary);
28
+ font: var(--type-body);
29
+ list-style: none;
30
+ cursor: pointer;
31
+ }
32
+ .dn-activity > summary::-webkit-details-marker { display: none; }
33
+ .dn-activity > summary:hover { color: var(--ink); }
34
+ .dn-activity > summary .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); transition: transform var(--duration-base) var(--ease-standard); }
35
+ .dn-activity[open] > summary .dn-icon { transform: rotate(90deg); }
36
+ .dn-activity > .dn-steps { margin: var(--space-1) 0 0 6px; padding-left: var(--space-3); border-left: var(--stroke-line) solid var(--line); }
37
+
38
+ .dn-steps { display: flex; flex-direction: column; min-width: 0; }
39
+ .dn-step {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: var(--space-2);
43
+ min-width: 0;
44
+ height: var(--button-height-md);
45
+ padding: 0 6px;
46
+ border-radius: var(--radius-sm);
47
+ color: var(--ink-secondary);
48
+ font: var(--weight-regular) var(--type-callout-size)/1 var(--font-sans);
49
+ list-style: none;
50
+ }
51
+ .dn-step::-webkit-details-marker { display: none; }
52
+ details > .dn-step { cursor: pointer; }
53
+ .dn-step:hover { background: var(--fill-hover); }
54
+ .dn-step .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); }
55
+ .dn-step-name { color: var(--ink); font-weight: var(--weight-semibold); white-space: nowrap; }
56
+ .dn-step-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--weight-regular) var(--type-caption-size)/1 var(--font-mono); }
57
+ .dn-step-meta { margin-left: auto; padding-left: var(--space-2); color: var(--ink-tertiary); font: var(--weight-regular) var(--type-caption-size)/1 var(--font-sans); font-variant-numeric: tabular-nums; white-space: nowrap; }
58
+ .dn-step.is-success .dn-step-glyph { color: var(--success); }
59
+ .dn-step.is-failed .dn-step-glyph { color: var(--danger); }
60
+ .dn-step-detail {
61
+ margin: 2px 0 6px calc(6px + var(--icon-size-md) + var(--space-2));
62
+ padding: 6px var(--space-2);
63
+ border-radius: var(--radius-sm);
64
+ background: var(--surface-sunken);
65
+ color: var(--ink-secondary);
66
+ font: var(--weight-regular) var(--type-caption-size)/1.45 var(--font-mono);
67
+ white-space: pre-wrap;
68
+ }
69
+ .dn-spinner { flex: none; width: var(--icon-size-sm); height: var(--icon-size-sm); border: 2px solid var(--line); border-top-color: var(--ink); border-radius: var(--radius-full); animation: dn-spin 0.9s linear infinite; }
70
+ @keyframes dn-spin { to { transform: rotate(360deg); } }
71
+
72
+ .dn-running { display: flex; align-items: center; gap: var(--space-2); min-height: var(--button-height-md); color: var(--ink-secondary); font: var(--type-body); }
73
+ .dn-running-dots { display: inline-flex; gap: 3px; }
74
+ .dn-running-dots span { width: 4px; height: 4px; border-radius: var(--radius-full); background: currentColor; animation: dn-blink 1.2s var(--ease-standard) infinite; }
75
+ .dn-running-dots span:nth-child(2) { animation-delay: 0.2s; }
76
+ .dn-running-dots span:nth-child(3) { animation-delay: 0.4s; }
77
+ .dn-running-time { color: var(--ink-tertiary); font-variant-numeric: tabular-nums; }
78
+ @keyframes dn-blink { 0%, 100% { opacity: 0.25; } 40% { opacity: 1; } }
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .dn-spinner { animation-duration: 2.4s; }
81
+ .dn-running-dots span { animation: none; opacity: 0.6; }
82
+ }
83
+ ```
84
+
85
+ ```html
86
+ <div class="dn-stack">
87
+ <details class="dn-activity" open>
88
+ <summary>已运行 2 个命令,已读取 1 个文件<svg class="dn-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></summary>
89
+ <div class="dn-steps">
90
+ <div class="dn-step is-success"><svg class="dn-icon dn-step-glyph" 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><span class="dn-step-name">读取</span><span class="dn-step-summary">周报草稿.md</span><span class="dn-step-meta">0.4 秒</span></div>
91
+ <details open>
92
+ <summary class="dn-step is-success"><svg class="dn-icon dn-step-glyph" 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><span class="dn-step-name">运行</span><span class="dn-step-summary">npm test</span><span class="dn-step-meta">12.3 秒</span></summary>
93
+ <div class="dn-step-detail">tests 63 · pass 63 · fail 0</div>
94
+ </details>
95
+ <div class="dn-step is-failed"><svg class="dn-icon dn-step-glyph" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></svg><span class="dn-step-name">运行</span><span class="dn-step-summary">npm run lint:找不到 eslint 配置</span><span class="dn-step-meta">已跳过</span></div>
96
+ </div>
97
+ </details>
98
+ <div class="dn-running" role="status"><span class="dn-running-dots" aria-hidden="true"><span></span><span></span><span></span></span>正在运行 命令<span class="dn-running-time">42 秒</span></div>
99
+ </div>
100
+ ```
101
+
102
+ ## SwiftUI
103
+
104
+ ```swift
105
+ // 摘要行:点开后是逐条的执行步骤
106
+ DisclosureGroup(isExpanded: $expanded) {
107
+ ForEach(steps) { step in StepRow(step: step) }
108
+ .padding(.leading, Deernova.space3)
109
+ } label: {
110
+ Text("已运行 \(commands) 个命令,已读取 \(files) 个文件")
111
+ .font(.deernovaBody).foregroundStyle(.inkSecondary)
112
+ }
113
+
114
+ struct StepRow: View {
115
+ var step: Step
116
+ var body: some View {
117
+ HStack(spacing: Deernova.space2) {
118
+ StatusGlyph(step.status) // ProgressView / checkmark.circle / xmark.circle
119
+ Text(step.action).fontWeight(.semibold).foregroundStyle(.ink)
120
+ Text(step.summary).font(.caption.monospaced()).lineLimit(1)
121
+ Spacer()
122
+ Text(step.duration).font(.caption).monospacedDigit().foregroundStyle(.inkTertiary)
123
+ }
124
+ .font(.deernovaCallout)
125
+ .foregroundStyle(.inkSecondary)
126
+ .frame(minHeight: Deernova.buttonHeightMd)
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## 不要
132
+
133
+ - 把每一步都做成卡片,或者默认把所有步骤展开铺在对话里。
134
+ - 成功的步骤也用醒目的颜色。
135
+ - 把原始日志直接铺在对话里(放进折叠详情)。
@@ -0,0 +1,86 @@
1
+ # 标签
2
+
3
+ 一到四个字的标签,标出状态或分类,放在标题旁边或元信息里。
4
+
5
+ **需要提供**:文字、语气(中性 / 强调 / success / warning / danger / info);表示状态时加一个圆点。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:tag -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `tag-height` | 20 | 22 | 24 | 标签高度。 |
13
+ | `tag-padding-x` | 6 | 8 | 10 | 标签左右内边距。 |
14
+ | `tag-font-size` | 12 | 12 | 12 | 标签字号。 |
15
+ | `tag-radius` | 4 | 胶囊 | 胶囊 | 标签圆角。 |
16
+ <!-- /tokens -->
17
+
18
+ - 中性标签:`fill-selected` 底、`ink-secondary` 字。
19
+ - 强调标签:`accent` 底、`on-accent` 字,只用于「新」「Pro」这类需要跳出来的少数标记。
20
+ - 有语气的标签:对应的 `-soft` 作底色,语气色作文字和圆点,例如 `success-soft` 底配 `success` 字。
21
+ - 表示状态时一定加 5px 圆点,文字本身就要说清楚状态是什么,不能只靠颜色。
22
+
23
+ ## Web
24
+
25
+ ```css
26
+ .dn-tag {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: var(--space-1);
30
+ height: var(--tag-height);
31
+ padding: 0 var(--tag-padding-x);
32
+ border-radius: var(--tag-radius);
33
+ background: var(--fill-selected);
34
+ color: var(--ink-secondary);
35
+ font: var(--weight-medium) var(--tag-font-size)/1 var(--font-sans);
36
+ white-space: nowrap;
37
+ }
38
+ .dn-tag.is-accent { background: var(--accent); color: var(--on-accent); }
39
+ .dn-tag.is-success { background: var(--success-soft); color: var(--success); }
40
+ .dn-tag.is-warning { background: var(--warning-soft); color: var(--warning); }
41
+ .dn-tag.is-danger { background: var(--danger-soft); color: var(--danger); }
42
+ .dn-tag.is-info { background: var(--info-soft); color: var(--info); }
43
+ .dn-dot { flex: none; width: 5px; height: 5px; border-radius: var(--radius-full); background: currentColor; }
44
+ ```
45
+
46
+ ```html
47
+ <div class="dn-row">
48
+ <span class="dn-tag">草稿</span>
49
+ <span class="dn-tag is-accent">新</span>
50
+ <span class="dn-tag is-success"><span class="dn-dot"></span>已同步</span>
51
+ <span class="dn-tag is-warning"><span class="dn-dot"></span>即将到期</span>
52
+ <span class="dn-tag is-danger"><span class="dn-dot"></span>同步失败</span>
53
+ <span class="dn-tag is-info">测试版</span>
54
+ </div>
55
+ ```
56
+
57
+ ## SwiftUI
58
+
59
+ ```swift
60
+ struct Tag: View {
61
+ var text: LocalizedStringKey
62
+ var tone: Color = .inkSecondary
63
+ var background: Color = .fillSelected
64
+ var showsDot = false
65
+
66
+ var body: some View {
67
+ HStack(spacing: Deernova.space1) {
68
+ if showsDot { Circle().frame(width: 5, height: 5) }
69
+ Text(text)
70
+ }
71
+ .font(.system(size: Deernova.tagFontSize, weight: .medium))
72
+ .foregroundStyle(tone)
73
+ .padding(.horizontal, Deernova.tagPaddingX)
74
+ .frame(height: Deernova.tagHeight)
75
+ .background(background, in: .rect(cornerRadius: min(Deernova.tagRadius, Deernova.tagHeight / 2)))
76
+ }
77
+ }
78
+
79
+ Tag(text: "已同步", tone: .success, background: .successSoft, showsDot: true)
80
+ ```
81
+
82
+ ## 不要
83
+
84
+ - 用标签当按钮。
85
+ - 一行里放超过三个标签。
86
+ - 大量使用强调标签(它和主按钮长得像,多了就分不清)。
@@ -0,0 +1,83 @@
1
+ # 任务清单
2
+
3
+ AI 或批量任务的计划和进度:一张描边卡片,每行一个任务,左边一个状态圈。放在对话里,随着进度原地更新,不重复发新的清单。
4
+
5
+ **需要提供**:任务列表(每项一句话,动词开头),每项的状态(待办 / 进行中 / 完成 / 失败)。
6
+
7
+ ## 尺寸
8
+
9
+ <!-- tokens:task-list -->
10
+ | Token | 桌面<br>Web 应用 · macOS 内容区 | 触控<br>iOS · iPadOS · Web 触屏 | 舒展<br>官网与文档 | 用途 |
11
+ | --- | --- | --- | --- | --- |
12
+ | `task-row-height` | 28 | 44 | 32 | 任务清单每一行的高度。 |
13
+ | `task-glyph-size` | 16 | 20 | 18 | 左侧状态圈的直径。 |
14
+ <!-- /tokens -->
15
+
16
+ - 卡片:无底色(直接在内容区上),1px `line` 描边,`card-radius`,内边距上下 `space-3`、左右 `card-padding-x`。
17
+ - 每行高 `task-row-height`,文字 `type-body`、`ink`,状态圈和文字之间 `space-2`。
18
+ - 状态圈直径 `task-glyph-size`:
19
+ - 待办:`ink-tertiary` 虚线圆圈。
20
+ - 进行中:转圈,`ink` 色。同一时间只有一项在进行。
21
+ - 完成:`ink-secondary` 的对勾圆圈,文字变 `ink-secondary`,不加删除线。
22
+ - 失败:`danger` 的叉号圆圈,后面跟一句原因。
23
+ - 任务超过 8 项时,默认只显示进行中前后的几项,其余折叠成「还有 5 项」。
24
+
25
+ ## Web
26
+
27
+ 用 `role="list"`,进行中的一项加 `aria-current="step"`,读屏能知道进度。
28
+
29
+ ```css
30
+ .dn-task-list {
31
+ display: grid;
32
+ margin: 0;
33
+ padding: var(--space-3) var(--card-padding-x);
34
+ border: var(--stroke-line) solid var(--line);
35
+ border-radius: var(--card-radius);
36
+ list-style: none;
37
+ }
38
+ .dn-task { display: flex; align-items: center; gap: var(--space-2); min-height: var(--task-row-height); color: var(--ink); font: var(--type-body); }
39
+ .dn-task-glyph { flex: none; box-sizing: border-box; width: var(--task-glyph-size); height: var(--task-glyph-size); border: 1.5px var(--line-style-dropzone) var(--ink-tertiary); border-radius: var(--radius-full); }
40
+ .dn-task[aria-current="step"] .dn-task-glyph { border: 2px solid var(--line); border-top-color: var(--ink); animation: dn-task-spin 0.9s linear infinite; }
41
+ .dn-task.is-done { color: var(--ink-secondary); }
42
+ .dn-task.is-done .dn-task-glyph, .dn-task.is-failed .dn-task-glyph { border: 0; fill: none; stroke: currentColor; stroke-width: var(--icon-stroke); stroke-linecap: var(--line-cap); stroke-linejoin: var(--line-join); }
43
+ .dn-task.is-failed .dn-task-glyph { color: var(--danger); }
44
+ .dn-task-note { color: var(--ink-tertiary); font: var(--type-callout); }
45
+ @keyframes dn-task-spin { to { transform: rotate(360deg); } }
46
+ @media (prefers-reduced-motion: reduce) { .dn-task[aria-current="step"] .dn-task-glyph { animation-duration: 2.4s; } }
47
+ ```
48
+
49
+ ```html
50
+ <ul class="dn-task-list" aria-label="任务">
51
+ <li class="dn-task is-done"><svg class="dn-task-glyph" 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>读取现有规范和设计变量</li>
52
+ <li class="dn-task" aria-current="step"><span class="dn-task-glyph" aria-hidden="true"></span>按场景改写设计变量</li>
53
+ <li class="dn-task"><span class="dn-task-glyph" aria-hidden="true"></span>更新组件说明和参考写法</li>
54
+ <li class="dn-task is-failed"><svg class="dn-task-glyph" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></svg>编译 Tailwind 主题<span class="dn-task-note">无法访问 npm</span></li>
55
+ </ul>
56
+ ```
57
+
58
+ ## SwiftUI
59
+
60
+ ```swift
61
+ VStack(alignment: .leading, spacing: 0) {
62
+ ForEach(tasks) { task in
63
+ HStack(spacing: Deernova.space2) {
64
+ TaskGlyph(task.status) // 虚线圈 / ProgressView / checkmark.circle / xmark.circle
65
+ .frame(width: Deernova.taskGlyphSize, height: Deernova.taskGlyphSize)
66
+ Text(task.title)
67
+ .foregroundStyle(task.status == .done ? .inkSecondary : .ink)
68
+ }
69
+ .font(.deernovaBody)
70
+ .frame(minHeight: Deernova.taskRowHeight, alignment: .leading)
71
+ }
72
+ }
73
+ .padding(.vertical, Deernova.space3)
74
+ .padding(.horizontal, Deernova.cardPaddingX)
75
+ .frame(maxWidth: .infinity, alignment: .leading)
76
+ .overlay(RoundedRectangle(cornerRadius: Deernova.cardRadius).strokeBorder(.line, lineWidth: Deernova.strokeLine))
77
+ ```
78
+
79
+ ## 不要
80
+
81
+ - 每次进度变化都发一张新清单(原地更新)。
82
+ - 用彩色区分待办和完成;给完成的任务加删除线。
83
+ - 同时显示多个“进行中”。