@deernova/style 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deernova/style",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: deernova-style
3
- description: Deernova 的界面设计语言:只用黑白灰,内容放在不透明纸面上,原生端的操作层用系统 Liquid Glass,Web 应用用不透明平面加 1px 线;界面和 AI 回答用无衬线,衬线只给大标题和长文;按场景(Web 应用、macOS、iOS、iPadOS、官网与文档)切换密度。在 Deernova 的应用或网站里新建、修改或审查界面时使用,包括颜色、字体、间距、圆角、线条、阴影、按钮、输入框、菜单与下拉选择、侧边栏、顶栏、列表、卡片、弹窗、空状态、AI 对话和任务清单,以及对应的 CSS、Tailwind 或 SwiftUI 实现。
3
+ description: Deernova 的界面设计语言:只用黑白灰,内容放在不透明纸面上,原生端的操作层用系统 Liquid Glass,Web 应用用不透明平面加 1px 线;界面和 AI 回答用无衬线,衬线只给大标题和长文;按场景(Web 应用、macOS、iOS、iPadOS、官网与文档)切换密度。在 Deernova 的应用或网站里新建、修改或审查界面时使用,包括颜色、字体、间距、圆角、线条、阴影、按钮、输入框、菜单与下拉选择、侧边栏、顶栏、列表、卡片、弹窗、tooltip、空状态、AI 对话和任务清单,以及对应的 CSS、Tailwind 或 SwiftUI 实现。
4
4
  ---
5
5
 
6
6
  # Deernova 设计语言
@@ -53,10 +53,12 @@ description: Deernova 的界面设计语言:只用黑白灰,内容放在不
53
53
  2. **黑白灰,强调靠对比。** `accent` 就是墨色 `ink`:主按钮在浅色主题是黑底白字,在深色主题是白底黑字,文字用 `on-accent`,不要写死白色。唯一的彩色是状态色 `success`、`warning`、`danger`、`info`,只用来表达状态,而且必须同时带图标或文字。
54
54
  3. **线条代替阴影。** 区块之间用 `line` 色的 1px 线分隔(`stroke-line`)。卡片不加阴影;只有菜单、弹窗、浮动面板和 Composer 这类真正浮起来的东西才用 `shadow-float`。
55
55
  4. **无衬线做界面,衬线点题。** 界面文字、AI 回答、用户消息、标题栏都用系统无衬线(SF Pro);代码和需要对齐的数字用等宽(SF Mono)。衬线(New York)只用在页面大标题和空状态标题(`display`、`title`)、官网与文档,以及文章类的长文阅读(`prose`)。中文统一落到苹方。
56
- 5. **层次靠字号和灰度,少用粗体。** 600 只给真正的标题(`display`、`title`、`headline`)。分组标题、会话名、说明、时间戳用常规字重,靠 `ink-secondary`、`ink-tertiary` 和小一号的字区分层级。
57
- 6. **控件高度由 token 决定,点击区域单独补。** 按钮、输入框、列表行的可见高度直接用 `button-height-*`、`field-height`、`row-height`,上下内边距为 0,不让页面行高把它撑高。可见尺寸小于 `hit-min` 时,用透明的命中区补足,不加大可见的控件。
58
- 7. **间距成组。** 间距走 4pt 网格。组内用 `gap-stack`,组与组之间用 `gap-group`(至少 2 倍);内容区里段落、工具调用、卡片之间 `space-4`。用细线和少量留白分组,不靠大块空白和外框。
59
- 8. **原生端用系统控件,Web 端用同一套自绘组件。** SwiftUI 端的导航结构、侧边栏、工具栏、菜单、表单、弹窗直接用系统控件和系统尺寸,只通过颜色和字体让它们融入;自己画的内容区(对话、卡片、输入区、任务清单)用 token。Web 应用没有系统控件可借,按组件说明里的规格画,骨架见 [Web 应用](references/scenes/web.md)。
56
+ 5. **状态只有两种底色。** 悬停 `fill-hover`,按下和选中 `fill-selected`,都是半透明的,叠在任何纸面上都成立;选中项悬停时不再加深。悬停只改颜色,不加阴影、不位移,只写在 `@media (hover: hover)` 里,见 [交互状态](references/foundations/states.md)。
57
+ 6. **层次靠字号和灰度,少用粗体。** 600 只给真正的标题(`display`、`title`、`headline`)。分组标题、会话名、说明、时间戳用常规字重,靠 `ink-secondary`、`ink-tertiary` 和小一号的字区分层级。
58
+ 7. **控件高度由 token 决定,点击区域单独补。** 按钮、输入框、列表行的可见高度直接用 `button-height-*`、`field-height`、`row-height`,上下内边距为 0,不让页面行高把它撑高。可见尺寸小于 `hit-min` 时,用透明的命中区补足,不加大可见的控件。
59
+ 8. **间距成组。** 间距走 4pt 网格。组内用 `gap-stack`,组与组之间用 `gap-group`(至少 2 倍);内容区里段落、工具调用、卡片之间 `space-4`。用细线和少量留白分组,不靠大块空白和外框。
60
+ 9. **原生端用系统控件,Web 端用同一套自绘组件。** SwiftUI 端的导航结构、侧边栏、工具栏、菜单、表单、弹窗直接用系统控件和系统尺寸,只通过颜色和字体让它们融入;自己画的内容区(对话、卡片、输入区、任务清单)用 token。Web 应用没有系统控件可借,按组件说明里的规格画,骨架见 [Web 应用](references/scenes/web.md)。
61
+ 10. **对齐按内容选。** 单行的控件和行在固定高度里垂直居中;图标和文字按中线,不同字号的文字按基线;三行以上的内容,图标和头像对齐第一行;空状态放在略高于正中的位置。不用内边距或 `top` 微调去凑居中。内容栏限宽并在主内容区里居中,见 [布局与对齐](references/foundations/layout.md)。
60
62
 
61
63
  ## 读什么
62
64
 
@@ -72,11 +74,11 @@ description: Deernova 的界面设计语言:只用黑白灰,内容放在不
72
74
 
73
75
  ### 基础
74
76
 
75
- [颜色](references/foundations/color.md) · [字体](references/foundations/typography.md) · [间距与布局](references/foundations/spacing.md) · [圆角](references/foundations/radius.md) · [线条](references/foundations/stroke.md) · [层级与阴影](references/foundations/elevation.md) · [玻璃](references/foundations/glass.md) · [动效](references/foundations/motion.md) · [图标](references/foundations/iconography.md) · [文案](references/foundations/writing.md) · [无障碍](references/foundations/accessibility.md)
77
+ [颜色](references/foundations/color.md) · [交互状态](references/foundations/states.md) · [字体](references/foundations/typography.md) · [间距](references/foundations/spacing.md) · [布局与对齐](references/foundations/layout.md) · [圆角](references/foundations/radius.md) · [线条](references/foundations/stroke.md) · [层级与阴影](references/foundations/elevation.md) · [玻璃](references/foundations/glass.md) · [动效](references/foundations/motion.md) · [图标](references/foundations/iconography.md) · [文案](references/foundations/writing.md) · [无障碍](references/foundations/accessibility.md)
76
78
 
77
79
  ### 组件
78
80
 
79
- [按钮](references/components/button.md) · [顶栏与侧面板](references/components/toolbar.md) · [玻璃操作层](references/components/glass-controls.md) · [分段控件](references/components/segmented.md) · [文本框](references/components/text-field.md) · [菜单与下拉](references/components/menu.md) · [Composer](references/components/composer.md) · [侧边栏](references/components/sidebar.md) · [分组列表](references/components/list.md) · [卡片](references/components/card.md) · [标签](references/components/tag.md) · [代码块](references/components/code-block.md) · [任务清单](references/components/task-list.md) · [空状态](references/components/empty-state.md) · [横幅与轻提示](references/components/banner.md) · [弹窗与 popover](references/components/sheet.md) · [消息](references/components/message.md) · [工具调用与执行步骤](references/components/step-row.md)
81
+ [按钮](references/components/button.md) · [顶栏与侧面板](references/components/toolbar.md) · [玻璃操作层](references/components/glass-controls.md) · [分段控件](references/components/segmented.md) · [文本框](references/components/text-field.md) · [菜单与下拉](references/components/menu.md) · [Composer](references/components/composer.md) · [侧边栏](references/components/sidebar.md) · [分组列表](references/components/list.md) · [卡片](references/components/card.md) · [标签](references/components/tag.md) · [代码块](references/components/code-block.md) · [任务清单](references/components/task-list.md) · [空状态](references/components/empty-state.md) · [横幅与轻提示](references/components/banner.md) · [弹窗与 popover](references/components/sheet.md) · [Tooltip 与悬停浮层](references/components/tooltip.md) · [消息](references/components/message.md) · [工具调用与执行步骤](references/components/step-row.md)
80
82
 
81
83
  ### 场景
82
84
 
@@ -96,7 +98,7 @@ description: Deernova 的界面设计语言:只用黑白灰,内容放在不
96
98
  - 在 SwiftUI 端给系统工具栏、侧边栏加实色背景盖掉玻璃;在 Web 应用里用模糊效果模仿玻璃。
97
99
  - 给卡片加阴影;在玻璃上放正文或列表。
98
100
  - 给界面文字、AI 回答或按钮用衬线;到处用粗体。
99
- - 用上下内边距撑出按钮高度;为了凑 44 点击区域把可见的按钮做大。
101
+ - 用上下内边距撑出按钮高度;为了凑 44 点击区域把可见的按钮做大;用 `padding-top`、`top: 1px` 或等高的 `line-height` 凑垂直居中。
100
102
  - 把官网的大按钮、大字号搬进应用界面;把桌面端的尺寸直接搬到触屏上。
101
103
  - 一屏出现两个主按钮;用「确定」「OK」当按钮文字。
102
104
  - 只靠颜色表达状态。
@@ -168,12 +168,20 @@ public enum Deernova {
168
168
  public static let opacitySolidHover: Double = 0.88
169
169
  /// 对话栏和长文阅读栏的最大宽度。
170
170
  public static let contentMax: CGFloat = 680
171
+ /// 表单页、设置页内容栏的最大宽度。
172
+ public static let formMax: CGFloat = 560
173
+ /// 表格、看板和官网内容的最大宽度。
174
+ public static let wideMax: CGFloat = 1120
171
175
  /// 菜单最大宽度;放不下的文字用省略号。
172
176
  public static let menuMaxWidth: CGFloat = 320
173
177
  /// 底部弹窗顶部拖拽条的宽度。
174
178
  public static let sheetGrabberWidth: CGFloat = 36
175
179
  /// 拖拽条的高度。
176
180
  public static let sheetGrabberHeight: CGFloat = 5
181
+ /// tooltip 和触发它的控件之间的距离。
182
+ public static let tooltipOffset: CGFloat = 8
183
+ /// tooltip 最大宽度,超过时折行,最多两行。
184
+ public static let tooltipMaxWidth: CGFloat = 240
177
185
 
178
186
  // 按密度取值的尺寸:macOS 是 desktop(只用于自己画的内容区),iOS 和 iPadOS 是 touch。
179
187
  #if os(macOS)
@@ -183,7 +191,11 @@ public enum Deernova {
183
191
  public static let pageMargin: CGFloat = 32
184
192
  public static let hitMin: CGFloat = 24
185
193
  public static let sidebarWidth: CGFloat = 288
194
+ public static let sidebarWidthMin: CGFloat = 200
195
+ public static let sidebarWidthMax: CGFloat = 400
196
+ public static let paneWidth: CGFloat = 320
186
197
  public static let toolbarHeight: CGFloat = 44
198
+ public static let gridGutter: CGFloat = 24
187
199
  public static let buttonHeightSm: CGFloat = 24
188
200
  public static let buttonHeightMd: CGFloat = 28
189
201
  public static let buttonHeightLg: CGFloat = 32
@@ -246,6 +258,10 @@ public enum Deernova {
246
258
  public static let sheetMargin: CGFloat = 32
247
259
  public static let sheetRadius: CGFloat = 12
248
260
  public static let popoverPadding: CGFloat = 12
261
+ public static let tooltipHeight: CGFloat = 24
262
+ public static let tooltipPaddingX: CGFloat = 8
263
+ public static let tooltipFontSize: CGFloat = 12
264
+ public static let tooltipRadius: CGFloat = 6
249
265
  public static let messageFontSize: CGFloat = 15
250
266
  public static let messageActionSize: CGFloat = 24
251
267
  public static let iconSizeSm: CGFloat = 14
@@ -258,7 +274,11 @@ public enum Deernova {
258
274
  public static let pageMargin: CGFloat = 16
259
275
  public static let hitMin: CGFloat = 44
260
276
  public static let sidebarWidth: CGFloat = 320
277
+ public static let sidebarWidthMin: CGFloat = 260
278
+ public static let sidebarWidthMax: CGFloat = 400
279
+ public static let paneWidth: CGFloat = 360
261
280
  public static let toolbarHeight: CGFloat = 44
281
+ public static let gridGutter: CGFloat = 16
262
282
  public static let buttonHeightSm: CGFloat = 30
263
283
  public static let buttonHeightMd: CGFloat = 36
264
284
  public static let buttonHeightLg: CGFloat = 46
@@ -321,6 +341,10 @@ public enum Deernova {
321
341
  public static let sheetMargin: CGFloat = 8
322
342
  public static let sheetRadius: CGFloat = 24
323
343
  public static let popoverPadding: CGFloat = 16
344
+ public static let tooltipHeight: CGFloat = 28
345
+ public static let tooltipPaddingX: CGFloat = 10
346
+ public static let tooltipFontSize: CGFloat = 13
347
+ public static let tooltipRadius: CGFloat = 8
324
348
  public static let messageFontSize: CGFloat = 15
325
349
  public static let messageActionSize: CGFloat = 32
326
350
  public static let iconSizeSm: CGFloat = 17
@@ -148,6 +148,7 @@
148
148
  --z-dropdown: 100;
149
149
  --z-overlay: 1000;
150
150
  --z-toast: 1100;
151
+ --z-tooltip: 1200;
151
152
  --duration-instant: 120ms;
152
153
  --duration-base: 200ms;
153
154
  --duration-slow: 320ms;
@@ -157,9 +158,17 @@
157
158
  --glass-blur: 16px;
158
159
  --glass-saturate: 1.4;
159
160
  --content-max: 680px;
161
+ --form-max: 560px;
162
+ --wide-max: 1120px;
163
+ --scrollbar-size: 10px;
164
+ --scrollbar-thumb-size: 6px;
160
165
  --menu-max-width: 320px;
161
166
  --sheet-grabber-width: 36px;
162
167
  --sheet-grabber-height: 5px;
168
+ --tooltip-offset: 8px;
169
+ --tooltip-max-width: 240px;
170
+ --tooltip-delay: 500ms;
171
+ --tooltip-skip-window: 300ms;
163
172
  --icon-stroke: 1.75px;
164
173
  }
165
174
 
@@ -198,7 +207,11 @@
198
207
  --page-margin: 32px;
199
208
  --hit-min: 24px;
200
209
  --sidebar-width: 288px;
210
+ --sidebar-width-min: 200px;
211
+ --sidebar-width-max: 400px;
212
+ --pane-width: 320px;
201
213
  --toolbar-height: 44px;
214
+ --grid-gutter: 24px;
202
215
  --button-height-sm: 24px;
203
216
  --button-height-md: 28px;
204
217
  --button-height-lg: 32px;
@@ -261,6 +274,10 @@
261
274
  --sheet-margin: 32px;
262
275
  --sheet-radius: 12px;
263
276
  --popover-padding: 12px;
277
+ --tooltip-height: 24px;
278
+ --tooltip-padding-x: 8px;
279
+ --tooltip-font-size: 12px;
280
+ --tooltip-radius: 6px;
264
281
  --message-font-size: 15px;
265
282
  --message-action-size: 24px;
266
283
  --icon-size-sm: 14px;
@@ -303,7 +320,11 @@
303
320
  --page-margin: 16px;
304
321
  --hit-min: 44px;
305
322
  --sidebar-width: 320px;
323
+ --sidebar-width-min: 260px;
324
+ --sidebar-width-max: 400px;
325
+ --pane-width: 360px;
306
326
  --toolbar-height: 44px;
327
+ --grid-gutter: 16px;
307
328
  --button-height-sm: 30px;
308
329
  --button-height-md: 36px;
309
330
  --button-height-lg: 46px;
@@ -366,6 +387,10 @@
366
387
  --sheet-margin: 8px;
367
388
  --sheet-radius: 24px;
368
389
  --popover-padding: 16px;
390
+ --tooltip-height: 28px;
391
+ --tooltip-padding-x: 10px;
392
+ --tooltip-font-size: 13px;
393
+ --tooltip-radius: 8px;
369
394
  --message-font-size: 15px;
370
395
  --message-action-size: 32px;
371
396
  --icon-size-sm: 17px;
@@ -408,7 +433,11 @@
408
433
  --page-margin: 16px;
409
434
  --hit-min: 44px;
410
435
  --sidebar-width: 320px;
436
+ --sidebar-width-min: 260px;
437
+ --sidebar-width-max: 400px;
438
+ --pane-width: 360px;
411
439
  --toolbar-height: 44px;
440
+ --grid-gutter: 16px;
412
441
  --button-height-sm: 30px;
413
442
  --button-height-md: 36px;
414
443
  --button-height-lg: 46px;
@@ -471,6 +500,10 @@
471
500
  --sheet-margin: 8px;
472
501
  --sheet-radius: 24px;
473
502
  --popover-padding: 16px;
503
+ --tooltip-height: 28px;
504
+ --tooltip-padding-x: 10px;
505
+ --tooltip-font-size: 13px;
506
+ --tooltip-radius: 8px;
474
507
  --message-font-size: 15px;
475
508
  --message-action-size: 32px;
476
509
  --icon-size-sm: 17px;
@@ -512,7 +545,11 @@
512
545
  --page-margin: 32px;
513
546
  --hit-min: 24px;
514
547
  --sidebar-width: 260px;
548
+ --sidebar-width-min: 200px;
549
+ --sidebar-width-max: 320px;
550
+ --pane-width: 320px;
515
551
  --toolbar-height: 52px;
552
+ --grid-gutter: 32px;
516
553
  --button-height-sm: 32px;
517
554
  --button-height-md: 36px;
518
555
  --button-height-lg: 44px;
@@ -575,6 +612,10 @@
575
612
  --sheet-margin: 32px;
576
613
  --sheet-radius: 16px;
577
614
  --popover-padding: 16px;
615
+ --tooltip-height: 28px;
616
+ --tooltip-padding-x: 10px;
617
+ --tooltip-font-size: 13px;
618
+ --tooltip-radius: 8px;
578
619
  --message-font-size: 15px;
579
620
  --message-action-size: 28px;
580
621
  --icon-size-sm: 16px;
@@ -938,6 +938,12 @@
938
938
  "value": 1100,
939
939
  "swift": false,
940
940
  "usage": "轻提示,压在弹窗之上。"
941
+ },
942
+ {
943
+ "name": "z-tooltip",
944
+ "value": 1200,
945
+ "swift": false,
946
+ "usage": "tooltip 和悬停卡片,压在所有层之上。"
941
947
  }
942
948
  ]
943
949
  },
@@ -1011,6 +1017,16 @@
1011
1017
  "value": "680px",
1012
1018
  "usage": "对话栏和长文阅读栏的最大宽度。"
1013
1019
  },
1020
+ {
1021
+ "name": "form-max",
1022
+ "value": "560px",
1023
+ "usage": "表单页、设置页内容栏的最大宽度。"
1024
+ },
1025
+ {
1026
+ "name": "wide-max",
1027
+ "value": "1120px",
1028
+ "usage": "表格、看板和官网内容的最大宽度。"
1029
+ },
1014
1030
  {
1015
1031
  "name": "breakpoint-sm",
1016
1032
  "value": "760px",
@@ -1047,6 +1063,33 @@
1047
1063
  },
1048
1064
  "usage": "侧边栏默认宽度。iPhone 没有侧边栏;macOS 用系统侧边栏,宽度只作参考。"
1049
1065
  },
1066
+ {
1067
+ "name": "sidebar-width-min",
1068
+ "values": {
1069
+ "desktop": 200,
1070
+ "touch": 260,
1071
+ "comfortable": 200
1072
+ },
1073
+ "usage": "侧边栏拖动时的最小宽度。"
1074
+ },
1075
+ {
1076
+ "name": "sidebar-width-max",
1077
+ "values": {
1078
+ "desktop": 400,
1079
+ "touch": 400,
1080
+ "comfortable": 320
1081
+ },
1082
+ "usage": "侧边栏拖动时的最大宽度。"
1083
+ },
1084
+ {
1085
+ "name": "pane-width",
1086
+ "values": {
1087
+ "desktop": 320,
1088
+ "touch": 360,
1089
+ "comfortable": 320
1090
+ },
1091
+ "usage": "列表加详情里列表栏的默认宽度,也是右侧 inspector 的默认宽度。"
1092
+ },
1050
1093
  {
1051
1094
  "name": "toolbar-height",
1052
1095
  "values": {
@@ -1055,6 +1098,31 @@
1055
1098
  "comfortable": 52
1056
1099
  },
1057
1100
  "usage": "Web 应用顶栏、侧面板标签头的高度。macOS 用系统工具栏。"
1101
+ },
1102
+ {
1103
+ "name": "grid-gutter",
1104
+ "values": {
1105
+ "desktop": 24,
1106
+ "touch": 16,
1107
+ "comfortable": 32
1108
+ },
1109
+ "usage": "官网和文档 12 栏网格的栏间距。应用里的卡片网格用 card-gap。"
1110
+ },
1111
+ {
1112
+ "name": "scrollbar-size",
1113
+ "value": "10px",
1114
+ "platforms": [
1115
+ "web"
1116
+ ],
1117
+ "usage": "网页滚动条占的宽度(横向滚动条是高度)。"
1118
+ },
1119
+ {
1120
+ "name": "scrollbar-thumb-size",
1121
+ "value": "6px",
1122
+ "platforms": [
1123
+ "web"
1124
+ ],
1125
+ "usage": "滚动条里胶囊滑块的粗细,两边各留 (scrollbar-size − 它) ÷ 2。"
1058
1126
  }
1059
1127
  ]
1060
1128
  },
@@ -1745,6 +1813,75 @@
1745
1813
  }
1746
1814
  ]
1747
1815
  },
1816
+ {
1817
+ "id": "tooltip",
1818
+ "title": "Tooltip 与悬停浮层",
1819
+ "reference": "references/components/tooltip.md",
1820
+ "tokens": [
1821
+ {
1822
+ "name": "tooltip-height",
1823
+ "values": {
1824
+ "desktop": 24,
1825
+ "touch": 28,
1826
+ "comfortable": 28
1827
+ },
1828
+ "usage": "单行 tooltip 的高度;折成两行时随内容长高。"
1829
+ },
1830
+ {
1831
+ "name": "tooltip-padding-x",
1832
+ "values": {
1833
+ "desktop": 8,
1834
+ "touch": 10,
1835
+ "comfortable": 10
1836
+ },
1837
+ "usage": "tooltip 左右内边距。"
1838
+ },
1839
+ {
1840
+ "name": "tooltip-font-size",
1841
+ "values": {
1842
+ "desktop": 12,
1843
+ "touch": 13,
1844
+ "comfortable": 13
1845
+ },
1846
+ "usage": "tooltip 字号。"
1847
+ },
1848
+ {
1849
+ "name": "tooltip-radius",
1850
+ "values": {
1851
+ "desktop": 6,
1852
+ "touch": 8,
1853
+ "comfortable": 8
1854
+ },
1855
+ "usage": "tooltip 圆角。"
1856
+ },
1857
+ {
1858
+ "name": "tooltip-offset",
1859
+ "value": "8px",
1860
+ "usage": "tooltip 和触发它的控件之间的距离。"
1861
+ },
1862
+ {
1863
+ "name": "tooltip-max-width",
1864
+ "value": "240px",
1865
+ "usage": "tooltip 最大宽度,超过时折行,最多两行。"
1866
+ },
1867
+ {
1868
+ "name": "tooltip-delay",
1869
+ "value": "500ms",
1870
+ "platforms": [
1871
+ "web"
1872
+ ],
1873
+ "usage": "指针停留多久后显示 tooltip 和悬停卡片。SwiftUI 的 .help() 用系统的时间。"
1874
+ },
1875
+ {
1876
+ "name": "tooltip-skip-window",
1877
+ "value": "300ms",
1878
+ "platforms": [
1879
+ "web"
1880
+ ],
1881
+ "usage": "一个 tooltip 刚关闭的这段时间里,移到相邻控件立即显示,不再等待。"
1882
+ }
1883
+ ]
1884
+ },
1748
1885
  {
1749
1886
  "id": "message",
1750
1887
  "title": "消息",
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.3.2",
3
- "sourceHash": "6ed23a30b39d95119a4c31208fe65781b75f549fb34e1104931b81b650349282"
2
+ "version": "0.3.4",
3
+ "sourceHash": "c02287466d37f62aeb1ea4007db56bddf8895d4fc4781a24bbc08a94f385c38a"
4
4
  }
@@ -10,7 +10,7 @@
10
10
  - **次按钮**(默认):`fill-selected` 灰底、无描边、无阴影、`ink` 字。半透明的灰底放在任何纸面上都成立。
11
11
  - **文字按钮** `is-plain`:无底,`ink-secondary` 字,悬停时出现 `fill-hover`。工具栏和卡片里的次要操作。
12
12
  - **危险按钮** `is-danger`:`danger-soft` 底、`danger` 字。只有在最终的[危险确认弹窗](sheet.md)里,才用实色的危险样式 `is-primary is-danger`:`danger` 底、`on-accent` 字(SwiftUI 的 `role: .destructive`)。
13
- - **图标按钮** `dn-icon-button`:只有图标,无底,`ink-secondary`,必须有无障碍标签。
13
+ - **图标按钮** `dn-icon-button`:只有图标,无底,`ink-secondary`,必须有无障碍标签;指针端用 [tooltip](tooltip.md) 显示它的名称。
14
14
 
15
15
  ## 尺寸
16
16
 
@@ -72,15 +72,16 @@
72
72
  cursor: pointer;
73
73
  transition: background-color var(--duration-instant) var(--ease-standard), opacity var(--duration-instant) var(--ease-standard);
74
74
  }
75
- .dn-button:hover:not(:disabled) { background-image: linear-gradient(var(--fill-hover), var(--fill-hover)); }
75
+ @media (hover: hover) { .dn-button:hover:not(:disabled) { background-image: linear-gradient(var(--fill-hover), var(--fill-hover)); } }
76
76
  .dn-button:active:not(:disabled) { background-image: linear-gradient(var(--fill-selected), var(--fill-selected)); }
77
77
  .dn-button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
78
78
  .dn-button:disabled { opacity: var(--opacity-disabled); cursor: default; }
79
79
 
80
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); }
81
+ @media (hover: hover) { .dn-button.is-primary:hover:not(:disabled) { background-image: none; opacity: var(--opacity-solid-hover); } }
82
+ .dn-button.is-primary:active:not(:disabled) { background-image: none; opacity: var(--opacity-solid-hover); }
82
83
  .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
+ @media (hover: hover) { .dn-button.is-plain:hover:not(:disabled) { background: var(--fill-hover); color: var(--ink); } }
84
85
  .dn-button.is-danger { background: var(--danger-soft); color: var(--danger); }
85
86
  .dn-button.is-primary.is-danger { background: var(--danger); color: var(--on-accent); }
86
87
 
@@ -104,7 +105,8 @@
104
105
  cursor: pointer;
105
106
  transition: background-color var(--duration-instant) var(--ease-standard);
106
107
  }
107
- .dn-icon-button:hover { background: var(--fill-hover); color: var(--ink); }
108
+ @media (hover: hover) { .dn-icon-button:hover { background: var(--fill-hover); color: var(--ink); } }
109
+ .dn-icon-button:active { background: var(--fill-selected); color: var(--ink); }
108
110
  .dn-icon-button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
109
111
 
110
112
  /* 点击区域:可见尺寸不变,用透明的伪元素把可点范围撑到 hit-min。
@@ -39,7 +39,7 @@
39
39
  text-decoration: none;
40
40
  transition: border-color var(--duration-instant) var(--ease-standard);
41
41
  }
42
- a.dn-card:hover, button.dn-card:hover { border-color: var(--line-control); }
42
+ @media (hover: hover) { a.dn-card:hover, button.dn-card:hover { border-color: var(--line-control); } }
43
43
  .dn-card:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
44
44
  .dn-card[aria-current="true"], .dn-card.is-selected { border-color: var(--ink); }
45
45
  .dn-card-title { margin: 0; font: var(--type-headline); }
@@ -124,7 +124,8 @@
124
124
  font: var(--weight-regular) var(--type-callout-size)/1 var(--font-sans);
125
125
  cursor: pointer;
126
126
  }
127
- .dn-meta-button:hover { background: var(--fill-hover); }
127
+ @media (hover: hover) { .dn-meta-button:hover { background: var(--fill-hover); } }
128
+ .dn-meta-button:active { background: var(--fill-selected); }
128
129
  .dn-meta-button b { color: var(--ink); font-weight: var(--weight-regular); }
129
130
  .dn-context-ring {
130
131
  width: var(--icon-size-sm);
@@ -13,7 +13,7 @@
13
13
  | `empty-icon-size` | 32 | 36 | 40 | 空状态图标的边长。 |
14
14
  <!-- /tokens -->
15
15
 
16
- - 在可用区域里居中。图标 `empty-icon-size`、细线、`ink-tertiary`;标题用衬线 `empty-title-size`、半粗;正文 `type-body`、`ink-secondary`,最大宽度 320。
16
+ - 在可用区域里视觉居中:水平居中,垂直方向略高于正中(上下留白约 2 : 3),见 [布局与对齐](../foundations/layout.md)。图标 `empty-icon-size`、细线、`ink-tertiary`;标题用衬线 `empty-title-size`、半粗;正文 `type-body`、`ink-secondary`,最大宽度 320。
17
17
  - 按钮用[大号](button.md)。
18
18
  - 搜索没有结果时不放按钮,标题写成「没有找到“关键词”」,正文给出修改建议。
19
19
 
@@ -119,6 +119,7 @@
119
119
  .dn-menu-item:is([aria-checked="true"], [aria-selected="true"]) > .dn-menu-check { visibility: visible; }
120
120
  .dn-menu-item > .dn-menu-more { width: var(--icon-size-sm); height: var(--icon-size-sm); color: var(--ink-tertiary); }
121
121
  @media (hover: hover) { .dn-menu-item:hover { background: var(--fill-hover); } }
122
+ .dn-menu-item:active { background: var(--fill-selected); }
122
123
  .dn-menu-item:focus { outline: none; }
123
124
  .dn-menu-item:focus-visible, .dn-menu-item[aria-expanded="true"] { background: var(--fill-hover); }
124
125
  .dn-menu-item:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: calc(var(--stroke-focus) * -1); }
@@ -18,6 +18,7 @@
18
18
  - 消息、工具调用行、卡片之间统一 `space-4`。
19
19
  - 操作按钮放在 AI 消息下方,`message-action-size` 的图标按钮,`ink-tertiary` 色;指针端只在悬停时显示,触控端一直显示。
20
20
  - 链接用 `ink` 色,加 1px 的 `line-control` 色下划线,下偏移 3px。
21
+ - 操作按钮悬停才出现时,键盘聚焦和触屏上仍然可见,规则见 [悬停才出现的操作](tooltip.md#悬停才出现的操作)。
21
22
  - 生成中:回答末尾显示「正在运行」状态(见 [工具调用与执行步骤](step-row.md)),Composer 的发送按钮变成「停止」。
22
23
 
23
24
  ## Web
@@ -45,7 +45,7 @@
45
45
  cursor: pointer;
46
46
  transition: background-color var(--duration-instant) var(--ease-standard), color var(--duration-instant) var(--ease-standard);
47
47
  }
48
- .dn-segmented button:hover { color: var(--ink); }
48
+ @media (hover: hover) { .dn-segmented button:hover { color: var(--ink); } }
49
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
50
  .dn-segmented button:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: var(--focus-offset); }
51
51
  ```
@@ -54,7 +54,7 @@
54
54
  .dn-sidebar-heading::-webkit-details-marker { display: none; }
55
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
56
  .dn-sidebar-group:not([open]) > .dn-sidebar-heading .dn-icon { transform: rotate(-90deg); }
57
- .dn-sidebar-heading:hover { color: var(--ink-secondary); }
57
+ @media (hover: hover) { .dn-sidebar-heading:hover { color: var(--ink-secondary); } }
58
58
  .dn-sidebar-item {
59
59
  display: flex;
60
60
  align-items: center;
@@ -70,7 +70,8 @@
70
70
  }
71
71
  .dn-sidebar-group .dn-sidebar-item { font: var(--type-callout); }
72
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); }
73
+ @media (hover: hover) { .dn-sidebar-item:hover { background: var(--fill-hover); } }
74
+ .dn-sidebar-item:active { background: var(--fill-selected); }
74
75
  .dn-sidebar-item[aria-current="page"] { background: var(--fill-selected); }
75
76
  .dn-sidebar-item:focus-visible { outline: var(--stroke-focus) solid var(--focus-ring); outline-offset: calc(var(--focus-offset) * -1); }
76
77
  .dn-sidebar-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -30,7 +30,7 @@ AI 在对话里做了什么。分两层:平时只显示一行灰色的**摘要
30
30
  cursor: pointer;
31
31
  }
32
32
  .dn-activity > summary::-webkit-details-marker { display: none; }
33
- .dn-activity > summary:hover { color: var(--ink); }
33
+ @media (hover: hover) { .dn-activity > summary:hover { color: var(--ink); } }
34
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
35
  .dn-activity[open] > summary .dn-icon { transform: rotate(90deg); }
36
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); }
@@ -50,7 +50,8 @@ AI 在对话里做了什么。分两层:平时只显示一行灰色的**摘要
50
50
  }
51
51
  .dn-step::-webkit-details-marker { display: none; }
52
52
  details > .dn-step { cursor: pointer; }
53
- .dn-step:hover { background: var(--fill-hover); }
53
+ @media (hover: hover) { .dn-step:hover { background: var(--fill-hover); } }
54
+ .dn-step:active { background: var(--fill-selected); }
54
55
  .dn-step .dn-icon { width: var(--icon-size-md); height: var(--icon-size-md); }
55
56
  .dn-step-name { color: var(--ink); font-weight: var(--weight-semibold); white-space: nowrap; }
56
57
  .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); }
@@ -23,7 +23,7 @@ Web 应用的页面骨架:左边是[侧边栏](sidebar.md),中间是内容
23
23
  ```css
24
24
  .dn-app { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; height: 100dvh; background: var(--canvas); color: var(--ink); }
25
25
  .dn-app-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
26
- .dn-app-content { flex: 1; min-height: 0; overflow: auto; padding: var(--space-6) calc(var(--page-margin) + var(--space-3)); }
26
+ .dn-app-content { flex: 1; min-height: 0; overflow: auto; scrollbar-gutter: stable; padding: var(--space-6) calc(var(--page-margin) + var(--space-3)); }
27
27
 
28
28
  .dn-toolbar {
29
29
  position: sticky;
@@ -53,7 +53,8 @@ Web 应用的页面骨架:左边是[侧边栏](sidebar.md),中间是内容
53
53
  }
54
54
  .dn-toolbar-title span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
55
55
  .dn-toolbar-title .dn-icon { width: var(--icon-size-sm); height: var(--icon-size-sm); color: var(--ink-secondary); }
56
- .dn-toolbar-title:hover { background: var(--fill-hover); }
56
+ @media (hover: hover) { .dn-toolbar-title:hover { background: var(--fill-hover); } }
57
+ .dn-toolbar-title:active { background: var(--fill-selected); }
57
58
  .dn-toolbar-actions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
58
59
  .dn-toolbar .dn-icon-button[aria-pressed="true"], .dn-panel-tabs .dn-icon-button[aria-pressed="true"] { background: var(--fill-selected); color: var(--ink); }
59
60
 
@@ -77,7 +78,7 @@ Web 应用的页面骨架:左边是[侧边栏](sidebar.md),中间是内容
77
78
  white-space: nowrap;
78
79
  cursor: pointer;
79
80
  }
80
- .dn-panel-tabs [role="tab"]:hover { color: var(--ink); }
81
+ @media (hover: hover) { .dn-panel-tabs [role="tab"]:hover { color: var(--ink); } }
81
82
  .dn-panel-tabs [role="tab"][aria-selected="true"] { background: var(--fill-selected); color: var(--ink); }
82
83
  .dn-panel-tabs .dn-panel-actions { display: flex; gap: 2px; margin-left: auto; }
83
84
  .dn-panel-body { flex: 1; min-height: 0; overflow: auto; padding: var(--space-3) var(--space-4); font: var(--type-code); color: var(--ink-secondary); }