@q1travel/design-system 0.20.0 → 0.22.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.
@@ -3,3 +3,31 @@
3
3
  用户可见文字与契约里的名词以此为准,跨页 / 表格 / 示例保持一致(`contracts/interface-guidelines.md` §Copy 要求 "consistent nouns")。
4
4
 
5
5
  **Coverage gap**:统一术语表尚未成形。消费者(如 q1travel-web)正在做「内部黑话清理」,清理产出的中性术语应回流到这里,成为 canonical 词表。在此之前,遇到术语一致性问题记入 `references/coverage-gaps.md`。
6
+
7
+ ## 组件文档页
8
+
9
+ 组件文档页(`contracts/interface-guidelines.md` §Component Documentation Pages)的共享词表。同一概念全库只用左列一个名字;右列的说法不再使用。
10
+
11
+ ### 小节名
12
+
13
+ | 概念 | 小节名 | 不再使用 |
14
+ |---|---|---|
15
+ | 外观的几种分支 | 变体 | 样式、类型、风格 |
16
+ | 大小档位 | 尺寸 | 大小、规格、小号、大号 |
17
+ | 不可用 | 禁用 | 不可用、禁止 |
18
+ | 校验失败或出错 | 错误 | 失败态、异常、报错态 |
19
+ | 等待中 | 加载中 | 加载态、载入 |
20
+ | 状态由外部持有 | 受控用法 | 受控、状态管理 |
21
+ | 相关组件 | 另请参阅 | 相关组件、延伸阅读 |
22
+
23
+ ### 描述用词
24
+
25
+ | 用 | 不用 | 说明 |
26
+ |---|---|---|
27
+ | 显示 | 呈现、承载 | 描述组件在界面上出现什么 |
28
+ | 读屏读出的内容 | 可访问名称 | 面向读者的说法;API 表里可保留 `aria-label` 等属性名 |
29
+ | 调用 `onChange` | 回调、输出 | 描述里不写;放到 API 表 |
30
+
31
+ ## 中英混排
32
+
33
+ 以 `contracts/interface-guidelines.md` §Copy 为准:代码标识符用行内 `code` 或 `DocToken`;通用英文译为中文;品牌、产品与组件名以及 Token 保留英文正文。中文与相邻拉丁字母、数字留半角空格,中文句子使用全角逗号、句号、冒号、分号;代码、网址、界面原文引述与源码示例除外。语义判断通过文案审阅完成。
@@ -8,6 +8,8 @@ source-policy:
8
8
  implementation: "Translate references into q1travel tokens and canonical components before production use."
9
9
  forbidden: "Reference markup, class names, copied computed styles, and screenshot-only dimensions."
10
10
  colors:
11
+ gray-alpha-100: "rgba(255, 255, 255, 0.08)"
12
+ gray-alpha-200: "rgba(255, 255, 255, 0.12)"
11
13
  primary: "#533afd"
12
14
  primary-hover: "#4631d6"
13
15
  primary-press: "#342899"
@@ -146,8 +148,8 @@ semantic-colors:
146
148
  overlay-scrim: "rgba(2, 6, 23, 0.44)"
147
149
  selection-background: "{colors.text-primary}"
148
150
  selection-foreground: "{colors.background-100}"
149
- focus-ring: "{colors.primary-900}"
150
- focus: "{colors.primary-900}"
151
+ focus-ring: "#ffffff"
152
+ focus: "#ffffff"
151
153
  status-success: "{colors.green-900}"
152
154
  status-success-text: "{colors.green-900}"
153
155
  status-success-surface: "{colors.green-100}"
@@ -169,62 +171,86 @@ typography:
169
171
  fontFamily: Noto Sans SC
170
172
  fontSize: 72px
171
173
  fontWeight: 500
172
- lineHeight: 72px
173
- letterSpacing: -0.06em
174
+ lineHeight: 88px
175
+ letterSpacing: 0
174
176
  heading-64:
175
177
  fontFamily: Noto Sans SC
176
178
  fontSize: 64px
177
179
  fontWeight: 500
178
- lineHeight: 68px
179
- letterSpacing: -0.06em
180
+ lineHeight: 80px
181
+ letterSpacing: 0
180
182
  heading-56:
181
183
  fontFamily: Noto Sans SC
182
184
  fontSize: 56px
183
185
  fontWeight: 500
184
- lineHeight: 60px
185
- letterSpacing: -0.06em
186
+ lineHeight: 72px
187
+ letterSpacing: 0
186
188
  heading-48:
187
189
  fontFamily: Noto Sans SC
188
190
  fontSize: 48px
189
191
  fontWeight: 500
192
+ lineHeight: 64px
193
+ letterSpacing: 0
194
+ heading-40-strong:
195
+ fontFamily: Noto Sans SC
196
+ fontSize: 40px
197
+ fontWeight: 600
190
198
  lineHeight: 52px
191
- letterSpacing: -0.06em
199
+ letterSpacing: 0
192
200
  heading-40:
193
201
  fontFamily: Noto Sans SC
194
202
  fontSize: 40px
195
203
  fontWeight: 500
204
+ lineHeight: 52px
205
+ letterSpacing: 0
206
+ heading-32-strong:
207
+ fontFamily: Noto Sans SC
208
+ fontSize: 32px
209
+ fontWeight: 600
196
210
  lineHeight: 44px
197
- letterSpacing: -0.06em
211
+ letterSpacing: 0
198
212
  heading-32:
199
213
  fontFamily: Noto Sans SC
200
214
  fontSize: 32px
201
215
  fontWeight: 500
202
- lineHeight: 38px
203
- letterSpacing: -0.04em
216
+ lineHeight: 44px
217
+ letterSpacing: 0
218
+ heading-24-strong:
219
+ fontFamily: Noto Sans SC
220
+ fontSize: 24px
221
+ fontWeight: 600
222
+ lineHeight: 32px
223
+ letterSpacing: 0
204
224
  heading-24:
205
225
  fontFamily: Noto Sans SC
206
226
  fontSize: 24px
207
227
  fontWeight: 500
208
228
  lineHeight: 32px
209
- letterSpacing: -0.04em
229
+ letterSpacing: 0
230
+ heading-20-strong:
231
+ fontFamily: Noto Sans SC
232
+ fontSize: 20px
233
+ fontWeight: 600
234
+ lineHeight: 28px
235
+ letterSpacing: 0
210
236
  heading-20:
211
237
  fontFamily: Noto Sans SC
212
238
  fontSize: 20px
213
239
  fontWeight: 500
214
240
  lineHeight: 28px
215
- letterSpacing: -0.02em
241
+ letterSpacing: 0
216
242
  heading-16:
217
243
  fontFamily: Noto Sans SC
218
244
  fontSize: 16px
219
245
  fontWeight: 500
220
246
  lineHeight: 24px
221
- letterSpacing: -0.02em
247
+ letterSpacing: 0
222
248
  heading-14:
223
249
  fontFamily: Noto Sans SC
224
250
  fontSize: 14px
225
251
  fontWeight: 500
226
252
  lineHeight: 20px
227
- letterSpacing: -0.02em
253
+ letterSpacing: 0
228
254
  label-14:
229
255
  fontFamily: Noto Sans SC
230
256
  fontSize: 14px
@@ -241,13 +267,13 @@ typography:
241
267
  fontFamily: Noto Sans SC
242
268
  fontSize: 16px
243
269
  fontWeight: 400
244
- lineHeight: 24px
270
+ lineHeight: 28px
245
271
  letterSpacing: 0
246
272
  copy-14:
247
273
  fontFamily: Noto Sans SC
248
274
  fontSize: 14px
249
275
  fontWeight: 400
250
- lineHeight: 20px
276
+ lineHeight: 24px
251
277
  letterSpacing: 0
252
278
  code-13:
253
279
  fontFamily: Geist Mono
@@ -261,12 +287,14 @@ spacing:
261
287
  3: 12px
262
288
  4: 16px
263
289
  6: 24px
290
+ 7: 28px
264
291
  8: 32px
265
292
  10: 40px
266
293
  16: 64px
267
294
  24: 96px
268
295
  base: 4px
269
296
  sizing:
297
+ touch-target-min: 44px
270
298
  control-default: 40px
271
299
  rounded:
272
300
  xs: 4px
@@ -307,9 +335,9 @@ components:
307
335
  height: 40px
308
336
  button-secondary:
309
337
  backgroundColor: "{semantic-colors.surface-panel}"
310
- hoverBackgroundColor: "{semantic-colors.surface-subtle}"
338
+ hoverBackgroundColor: "{semantic-colors.surface-hover}"
311
339
  textColor: "{semantic-colors.text-primary}"
312
- borderColor: "{semantic-colors.border-strong}"
340
+ borderColor: "{semantic-colors.border}"
313
341
  typography: "{typography.label-14}"
314
342
  rounded: "{rounded.sm}"
315
343
  height: 40px
@@ -317,7 +345,7 @@ components:
317
345
  backgroundColor: "{semantic-colors.surface-panel}"
318
346
  textColor: "{semantic-colors.text-primary}"
319
347
  placeholderColor: "{semantic-colors.text-muted}"
320
- borderColor: "{semantic-colors.border-strong}"
348
+ borderColor: "{semantic-colors.border}"
321
349
  focusColor: "{semantic-colors.focus-ring}"
322
350
  typography: "{typography.label-14}"
323
351
  rounded: "{rounded.sm}"
@@ -402,14 +430,18 @@ These components ship in the package. Compose from this list before writing new
402
430
 
403
431
  `q1-design ls` prints this list from the installed package, and `q1-design doc <name>` prints one component's public API. Use those when the package is installed; use this section when it is not.
404
432
 
405
- **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, Link, Toolbar.
433
+ **Record activity.** Timeline, TimelineItem, Marker, Message, Mention, Composer, Attachment. Timeline uses chronological ol/li with tone dots or Avatar; Marker keeps system events quiet and dates centered. Message supports pending/failed and caller actions; Mention renders caller-owned inline member references using action-text and the selected surface. Composer is a textarea combobox with listbox mentions, Enter sends and Shift+Enter inserts a newline; IME composition never sends or picks. Attachment reuses Progress and named removal. Network, permissions and notifications remain caller-owned.
434
+
435
+ **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
406
436
 
407
437
  - Button carries the action. ButtonLink navigates but reads as a button, for a page-level call to action. Link reads as text, inside a sentence. Substituting one for another makes the page read wrong.
408
438
  - SplitButton is a primary action with an adjacent menu whose first entry mirrors it. Do not use it to hide unrelated actions behind a chevron.
439
+ - FilterChip opens a consumer-owned filter picker; undefined means unset, other values mean set. Its 28px pill uses a dashed border-strong outline with CirclePlus when unset, and a solid outline with label, border-token divider, action-text value and ChevronDown when set. The optional leading CircleX clear action is a separate named native button before the picker, with its own Tab stop and shared focus ring.
409
440
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
410
441
 
411
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, VerificationCodeInput, Textarea, Select, Fieldset, ErrorMessage.
442
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
412
443
 
444
+ - InputOTP collects a fixed six-digit code in one joined group by default; `separator` explicitly enables 3+3 groups. VerificationCodeInput and VerificationCodeInputProps remain deprecated aliases for one minor version; migrate to InputOTP and InputOTPProps before their removal in the next minor version. The old `/components/verification-code-input` route redirects to `/components/input-otp`.
413
445
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
414
446
  - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
415
447
 
@@ -419,7 +451,7 @@ These components ship in the package. Compose from this list before writing new
419
451
  - Switch selects among mutually exclusive views or modes.
420
452
  - RangeSlider is two thumbs over one interval, paired with numeric inputs; Slider is a single value.
421
453
 
422
- **Dates and ranges.** CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
454
+ **Dates and ranges.** Calendar, CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
423
455
 
424
456
  **Overlays.** Modal, ModalInset, DestructiveActionModal, Drawer, Sheet, Menu, ContextMenu, DotsMenu, Tooltip, ContextCard, SearchDialog, CommandMenu.
425
457
 
@@ -435,7 +467,7 @@ These components ship in the package. Compose from this list before writing new
435
467
  - Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
436
468
  - RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
437
469
 
438
- **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, Spinner, LoadingDots, Skeleton.
470
+ **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
439
471
 
440
472
  - Banner announces at page scope. Note is an inline aside. ErrorMessage sits against one field. ErrorCard replaces a region that failed to load. Picking the widest one available overstates the problem.
441
473
  - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
@@ -8,6 +8,8 @@ source-policy:
8
8
  implementation: "Translate references into q1travel tokens and canonical components before production use."
9
9
  forbidden: "Reference markup, class names, copied computed styles, and screenshot-only dimensions."
10
10
  colors:
11
+ gray-alpha-100: "rgba(0, 0, 0, 0.06)"
12
+ gray-alpha-200: "rgba(0, 0, 0, 0.08)"
11
13
  primary: "#533afd"
12
14
  primary-hover: "#4434d4"
13
15
  primary-press: "#2e2b8c"
@@ -134,7 +136,7 @@ semantic-colors:
134
136
  text-on-inverse: "{colors.background-100}"
135
137
  surface-media: "#0a0a0a"
136
138
  text-on-media: "#ffffff"
137
- border: "{colors.gray-400}"
139
+ border: "{colors.gray-alpha-200}"
138
140
  border-strong: "{colors.gray-500}"
139
141
  border-active: "{colors.primary-700}"
140
142
  border-subtle: "rgba(203, 213, 225, 0.82)"
@@ -146,8 +148,8 @@ semantic-colors:
146
148
  overlay-scrim: "rgba(2, 6, 23, 0.44)"
147
149
  selection-background: "{colors.text-primary}"
148
150
  selection-foreground: "{colors.background-100}"
149
- focus-ring: "{colors.primary}"
150
- focus: "{colors.primary-700}"
151
+ focus-ring: "#000000"
152
+ focus: "#000000"
151
153
  status-success: "{colors.green-700}"
152
154
  status-success-text: "{colors.green-900}"
153
155
  status-success-surface: "{colors.green-100}"
@@ -169,62 +171,86 @@ typography:
169
171
  fontFamily: Noto Sans SC
170
172
  fontSize: 72px
171
173
  fontWeight: 500
172
- lineHeight: 72px
173
- letterSpacing: -0.06em
174
+ lineHeight: 88px
175
+ letterSpacing: 0
174
176
  heading-64:
175
177
  fontFamily: Noto Sans SC
176
178
  fontSize: 64px
177
179
  fontWeight: 500
178
- lineHeight: 68px
179
- letterSpacing: -0.06em
180
+ lineHeight: 80px
181
+ letterSpacing: 0
180
182
  heading-56:
181
183
  fontFamily: Noto Sans SC
182
184
  fontSize: 56px
183
185
  fontWeight: 500
184
- lineHeight: 60px
185
- letterSpacing: -0.06em
186
+ lineHeight: 72px
187
+ letterSpacing: 0
186
188
  heading-48:
187
189
  fontFamily: Noto Sans SC
188
190
  fontSize: 48px
189
191
  fontWeight: 500
192
+ lineHeight: 64px
193
+ letterSpacing: 0
194
+ heading-40-strong:
195
+ fontFamily: Noto Sans SC
196
+ fontSize: 40px
197
+ fontWeight: 600
190
198
  lineHeight: 52px
191
- letterSpacing: -0.06em
199
+ letterSpacing: 0
192
200
  heading-40:
193
201
  fontFamily: Noto Sans SC
194
202
  fontSize: 40px
195
203
  fontWeight: 500
204
+ lineHeight: 52px
205
+ letterSpacing: 0
206
+ heading-32-strong:
207
+ fontFamily: Noto Sans SC
208
+ fontSize: 32px
209
+ fontWeight: 600
196
210
  lineHeight: 44px
197
- letterSpacing: -0.06em
211
+ letterSpacing: 0
198
212
  heading-32:
199
213
  fontFamily: Noto Sans SC
200
214
  fontSize: 32px
201
215
  fontWeight: 500
202
- lineHeight: 38px
203
- letterSpacing: -0.04em
216
+ lineHeight: 44px
217
+ letterSpacing: 0
218
+ heading-24-strong:
219
+ fontFamily: Noto Sans SC
220
+ fontSize: 24px
221
+ fontWeight: 600
222
+ lineHeight: 32px
223
+ letterSpacing: 0
204
224
  heading-24:
205
225
  fontFamily: Noto Sans SC
206
226
  fontSize: 24px
207
227
  fontWeight: 500
208
228
  lineHeight: 32px
209
- letterSpacing: -0.04em
229
+ letterSpacing: 0
230
+ heading-20-strong:
231
+ fontFamily: Noto Sans SC
232
+ fontSize: 20px
233
+ fontWeight: 600
234
+ lineHeight: 28px
235
+ letterSpacing: 0
210
236
  heading-20:
211
237
  fontFamily: Noto Sans SC
212
238
  fontSize: 20px
213
239
  fontWeight: 500
214
240
  lineHeight: 28px
215
- letterSpacing: -0.02em
241
+ letterSpacing: 0
216
242
  heading-16:
217
243
  fontFamily: Noto Sans SC
218
244
  fontSize: 16px
219
245
  fontWeight: 500
220
246
  lineHeight: 24px
221
- letterSpacing: -0.02em
247
+ letterSpacing: 0
222
248
  heading-14:
223
249
  fontFamily: Noto Sans SC
224
250
  fontSize: 14px
225
251
  fontWeight: 500
226
252
  lineHeight: 20px
227
- letterSpacing: -0.02em
253
+ letterSpacing: 0
228
254
  label-14:
229
255
  fontFamily: Noto Sans SC
230
256
  fontSize: 14px
@@ -241,13 +267,13 @@ typography:
241
267
  fontFamily: Noto Sans SC
242
268
  fontSize: 16px
243
269
  fontWeight: 400
244
- lineHeight: 24px
270
+ lineHeight: 28px
245
271
  letterSpacing: 0
246
272
  copy-14:
247
273
  fontFamily: Noto Sans SC
248
274
  fontSize: 14px
249
275
  fontWeight: 400
250
- lineHeight: 20px
276
+ lineHeight: 24px
251
277
  letterSpacing: 0
252
278
  code-13:
253
279
  fontFamily: Geist Mono
@@ -261,12 +287,14 @@ spacing:
261
287
  3: 12px
262
288
  4: 16px
263
289
  6: 24px
290
+ 7: 28px
264
291
  8: 32px
265
292
  10: 40px
266
293
  16: 64px
267
294
  24: 96px
268
295
  base: 4px
269
296
  sizing:
297
+ touch-target-min: 44px
270
298
  control-default: 40px
271
299
  rounded:
272
300
  xs: 4px
@@ -307,9 +335,9 @@ components:
307
335
  height: 40px
308
336
  button-secondary:
309
337
  backgroundColor: "{semantic-colors.surface-panel}"
310
- hoverBackgroundColor: "{semantic-colors.surface-subtle}"
338
+ hoverBackgroundColor: "{semantic-colors.surface-hover}"
311
339
  textColor: "{semantic-colors.text-primary}"
312
- borderColor: "{semantic-colors.border-strong}"
340
+ borderColor: "{semantic-colors.border}"
313
341
  typography: "{typography.label-14}"
314
342
  rounded: "{rounded.sm}"
315
343
  height: 40px
@@ -317,7 +345,7 @@ components:
317
345
  backgroundColor: "{semantic-colors.surface-panel}"
318
346
  textColor: "{semantic-colors.text-primary}"
319
347
  placeholderColor: "{semantic-colors.text-muted}"
320
- borderColor: "{semantic-colors.border-strong}"
348
+ borderColor: "{semantic-colors.border}"
321
349
  focusColor: "{semantic-colors.focus-ring}"
322
350
  typography: "{typography.label-14}"
323
351
  rounded: "{rounded.sm}"
@@ -402,14 +430,18 @@ These components ship in the package. Compose from this list before writing new
402
430
 
403
431
  `q1-design ls` prints this list from the installed package, and `q1-design doc <name>` prints one component's public API. Use those when the package is installed; use this section when it is not.
404
432
 
405
- **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, Link, Toolbar.
433
+ **Record activity.** Timeline, TimelineItem, Marker, Message, Mention, Composer, Attachment. Timeline uses chronological ol/li with tone dots or Avatar; Marker keeps system events quiet and dates centered. Message supports pending/failed and caller actions; Mention renders caller-owned inline member references using action-text and the selected surface. Composer is a textarea combobox with listbox mentions, Enter sends and Shift+Enter inserts a newline; IME composition never sends or picks. Attachment reuses Progress and named removal. Network, permissions and notifications remain caller-owned.
434
+
435
+ **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
406
436
 
407
437
  - Button carries the action. ButtonLink navigates but reads as a button, for a page-level call to action. Link reads as text, inside a sentence. Substituting one for another makes the page read wrong.
408
438
  - SplitButton is a primary action with an adjacent menu whose first entry mirrors it. Do not use it to hide unrelated actions behind a chevron.
439
+ - FilterChip opens a consumer-owned filter picker; undefined means unset, other values mean set. Its 28px pill uses a dashed border-strong outline with CirclePlus when unset, and a solid outline with label, border-token divider, action-text value and ChevronDown when set. The optional leading CircleX clear action is a separate named native button before the picker, with its own Tab stop and shared focus ring.
409
440
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
410
441
 
411
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, VerificationCodeInput, Textarea, Select, Fieldset, ErrorMessage.
442
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
412
443
 
444
+ - InputOTP collects a fixed six-digit code in one joined group by default; `separator` explicitly enables 3+3 groups. VerificationCodeInput and VerificationCodeInputProps remain deprecated aliases for one minor version; migrate to InputOTP and InputOTPProps before their removal in the next minor version. The old `/components/verification-code-input` route redirects to `/components/input-otp`.
413
445
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
414
446
  - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
415
447
 
@@ -419,7 +451,7 @@ These components ship in the package. Compose from this list before writing new
419
451
  - Switch selects among mutually exclusive views or modes.
420
452
  - RangeSlider is two thumbs over one interval, paired with numeric inputs; Slider is a single value.
421
453
 
422
- **Dates and ranges.** CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
454
+ **Dates and ranges.** Calendar, CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
423
455
 
424
456
  **Overlays.** Modal, ModalInset, DestructiveActionModal, Drawer, Sheet, Menu, ContextMenu, DotsMenu, Tooltip, ContextCard, SearchDialog, CommandMenu.
425
457
 
@@ -435,7 +467,7 @@ These components ship in the package. Compose from this list before writing new
435
467
  - Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
436
468
  - RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
437
469
 
438
- **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, Spinner, LoadingDots, Skeleton.
470
+ **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
439
471
 
440
472
  - Banner announces at page scope. Note is an inline aside. ErrorMessage sits against one field. ErrorCard replaces a region that failed to load. Picking the widest one available overstates the problem.
441
473
  - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
@@ -445,7 +477,7 @@ These components ship in the package. Compose from this list before writing new
445
477
  **Structure and navigation.** Card, AuthCard, Sidebar, SidebarDrawer, Navigation, NavigationSection, NavigationItem, NavigationGroup, ContextSwitcher, Breadcrumb, CollapsibleBreadcrumb, Tabs, Collapse, CollapseGroup, Separator, Scroller, ScrollerWithButtons, FileTree.
446
478
 
447
479
  - Card is a surface you have earned, not a default wrapper. Reach for spacing and alignment first.
448
- - Tabs switch between peer views of the same subject. They are not a way to shorten a page.
480
+ - Tabs switch between peer views of the same subject. They are not a way to shorten a page. Optional `value` / `onValueChange` let the consumer own selection and URL synchronization; `defaultValue` seeds uncontrolled selection. Invalid or disabled values render the first enabled item without notifying. See `implementation-map.md` for selection ownership.
449
481
  - ContextSwitcher is the two-level selection model; the consumer owns the state behind it.
450
482
 
451
483
  **Identity and media.** Avatar, AvatarGroup, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher, ProductLockup, Q1travelSymbol.
@@ -75,4 +75,4 @@ q1travel 的界面图标**目标是**一套自建的线性图标系统:几何
75
75
  - **颜色**:不为图标单独设色,依赖 `currentColor` 跟随文本。
76
76
  - **间距**:图标与文本之间保留一致间距,并与文本基线对齐。
77
77
  - **一致性**:同一控件组内不混用不同描边或尺寸。
78
- - **光学位移**:16px 线性图标贴文字标签时,图标框几何居中不等于看起来居中。位移统一消费 `--q1-optical-icon-shift-cjk-label`,通过独立的 `translate` 属性施加,不改 `transform`,因此不会冲掉 chevron 的旋转。取值按字体栈校准:包默认 `--q1-font-sans` 为 `0px`;拉丁首选字体决定行盒、中文回退 PingFang SC 的字体栈(web 当前字体栈)为 `-1px`,由消费方在自己的根上覆盖。纯英文标签会因此偏高约 1–2px,这是 x-height 的物理位置,不再按脚本细分。已接入:Navigation 分组图标、分组 chevron、链接项图标;ContextSwitcher 单行条目图标与动作图标。Button 前后缀默认不位移,消费方在已知全是中文的场景把 `--q1-button-affix-optical-y` 设为该 token。数值由 `check:browser` 的光学 fixture 锁定,见 `contracts/token-naming.md` 的 Optical Tokens。
78
+ - **光学位移**:16px 线性图标贴文字标签时,图标框几何居中不等于看起来居中。位移统一消费 `--q1-optical-icon-shift-cjk-label`,通过独立的 `translate` 属性施加,不改 `transform`,因此不会冲掉 chevron 的旋转。取值按字体栈校准:包默认 `--q1-font-sans` 为 `0px`;拉丁首选字体决定行盒、中文回退 PingFang SC 的字体栈(web 当前字体栈)为 `-1px`,由消费方在自己的根上覆盖。纯英文标签会因此偏高约 1–2px,这是 x-height 的物理位置,不再按脚本细分。已接入:Navigation 分组图标、分组 chevron、链接项图标;ContextSwitcher 单行条目图标与动作图标。Button 前后缀默认不位移,消费方在已知全是中文的场景把 `--q1-button-affix-optical-y` 设为该 token。组件内部另有两处具名、整数像素的局部位移,只作用于本组件:`--q1-product-lockup-optical-y`(品牌符号与竖线对齐产品名,`1px`)与 `--q1-note-icon-optical-y`(Note 图标对齐第一行正文,默认 `3px`、`large` `2px`)。数值由 `check:browser` 的光学 fixture 锁定,见 `contracts/token-naming.md` 的 Optical Tokens。
@@ -9,6 +9,8 @@ README owns the repository inventory and generated-file boundary. This file owns
9
9
  | Reference intent | q1travel semantic | Canonical surface | Token source | State | Responsive | Accessibility |
10
10
  | --- | --- | --- | --- | --- | --- | --- |
11
11
  | Bounded documentation frame | Public design-system shell | `AppShell`, `Sidebar`, `Topbar` | `--q1-color-bg-canvas`, `--q1-color-border`, spacing tokens | Route changes reset document scroll | Frame width and sidebar collapse through layout CSS | Landmarks, keyboard search, focus-visible ring |
12
+ | Aligned date-range field | Date-range trigger shares the FieldSize scale | `Calendar`, `RangeField` (`fieldSize`; `size` remains grid-only) | `--q1-control-height-default`, `--q1-space-6`, `--q1-space-8`, `--q1-space-2`, `--q1-touch-target-min` | Draft, apply, clear and popover keyboard behavior preserved; clear/caret centered | Default md40 matches Select/Input; coarse44 matches Select | Named sibling buttons, APG date-picker dialog and focus return |
13
+ | Compact dashboard filters | Consumer-owned filter picker | `FilterChip` | `--q1-space-7` (28px), border-strong, text-secondary, action-text, shared focus | Unset dashed CirclePlus; set solid leading CircleX clear, label, divider, value and ChevronDown | Ellipsis bounds long values; shared TouchTarget preserves coarse 44px targets | Sibling native buttons, clear before picker in Tab order, focus returns to picker |
12
14
  | Compact search and command menu | Fast documentation navigation | `CommandMenu`, `Topbar` search trigger | Surface, border, text, focus tokens | Open, active option, escape close | Dialog width clamps on small screens | `role="dialog"`, `aria-modal`, keyboard navigation |
13
15
  | Foundation page rhythm | Scannable reference pages | `PageHeader`, `SpecSection`, `SpecTable` | Heading, copy, border, spacing tokens | None beyond links and focus | Tables scroll horizontally when needed | Semantic headings and tables |
14
16
  | Introduction tile grid | Entry points into public groups | `IntroTileGrid`, `IntroTile` | Surface, border, text, spacing tokens | Hover, focus | Two columns on desktop, one column on small screens | Tile is a single link with hidden decorative visual |
@@ -26,14 +28,70 @@ README owns the repository inventory and generated-file boundary. This file owns
26
28
  - New public tokens use `--q1-*`; existing product `--ds-*` names are legacy migration inputs, not new contract names.
27
29
  - Canonical components own repeated structure. Page-local custom structures are allowed only for visual specimens with a narrow purpose.
28
30
  - Token references are the design source for production code. Current theme resolved values are clipboard output only.
29
- - The same public navigation groups remain in the product surface: `基础`, `品牌`, and `组件`.
31
+ - The same public navigation groups remain in the product surface: `基础`, `品牌`, `组件`, and `模式`; `模式` owns compositions across components, starting with `/patterns/admin`.
30
32
  - If a capability is missing from a shared component, add the capability to that component before adding another page-local pattern.
31
33
 
32
34
  ## Documentation Section Ownership
33
35
 
34
36
  Two component-page sections are q1travel supersets with no external counterpart, so their scope is defined here rather than inferred page by page.
35
37
 
36
- - **设计 Token** — every Tier B native-semantic control page (Button, Input, Textarea, Select, Checkbox, Radio, Toggle) MUST carry this section. Those controls take their whole surface from semantic tokens, so the role/token/usage table is the only place a consumer can see which tokens to override. Tier A and Tier C pages MUST NOT add it by default: their appearance comes mostly from structure, and a token table there restates the global palette without telling the reader anything component-specific. **Switch and Tabs are the two recorded exceptions** — these Tier A components are likewise entirely token-driven; they keep their tables, and they do not license other Tier A pages to add one.
38
+ - **设计 Token** — every Tier B native-semantic control page (Button, Input, Textarea, Select, Checkbox, Radio, Toggle, FilterChip) MUST carry this section. Those controls take their whole surface from semantic tokens, so the role/token/usage table is the only place a consumer can see which tokens to override. Tier A and Tier C pages MUST NOT add it by default: their appearance comes mostly from structure, and a token table there restates the global palette without telling the reader anything component-specific. **Switch and Tabs are the two recorded exceptions** — these Tier A components are likewise entirely token-driven; they keep their tables, and they do not license other Tier A pages to add one.
37
39
  - **API 与行为矩阵** — reserved for composite host components that the consumer assembles while the host owns the state (ContextSwitcher, Navigation, Sidebar). The table exists to state who owns what; a component that owns its own state does not need it.
38
40
 
39
41
  Rows in these tables reference semantic tokens (`--q1-color-*`, `--q1-shadow-*`, `--q1-radius-*`), not component-private custom properties. Component-private properties driven by props (for example an avatar diameter) are implementation detail and MUST NOT be documented as a consumer-facing token contract.
42
+
43
+ ## Embedded documentation examples
44
+
45
+ `DemoBlock` owns one preview and its `ShowCode` footer. `SpecSection` pairs adjacent native
46
+ `.specimen` / `ShowCode` children, including multiple pairs; other previews use explicit
47
+ `DemoBlock` boundaries. Explanation tables and standalone code retain their own structure.
48
+ `ShowCode` opts into `Collapse headingLevel={null}` and `CodeBlock variant="flush"`.
49
+ Collapse keeps its h3 default, keyboard disclosure semantics, focus restoration, and inert
50
+ collapsed panel. A null heading level uses a plain wrapper for an ancillary disclosure;
51
+ it does not enter the document heading outline. CodeBlock keeps its standalone default
52
+ header and outer frame; flush removes that frame and floats copy only when there is no
53
+ filename or language switch. No existing consumer needs an API migration.
54
+
55
+ Light documentation canvas uses background-200, previews use background-100, and the
56
+ code footer uses bg-subtle. The public light border now uses gray-alpha-200 (black 8%);
57
+ dark borders keep their established value. Consumers with border screenshots should
58
+ refresh them; token names and existing overrides remain valid. Gray-alpha-100/200
59
+ provide theme-relative list-state overlays. Strong heading-40/32/24/20 token roles are
60
+ opt-in 600 weights; existing heading roles keep 500. Space-7 adds the 28px spacing step.
61
+
62
+ ## Component visual subtraction
63
+
64
+ Destructive Action Modal keeps its typed confirmation gate, error recovery, busy lock,
65
+ and Escape/Cancel dismissal. Optional irreversible copy lives inside the scrollable
66
+ body on status-danger-surface/text tokens; disabled confirmation uses bg-subtle and
67
+ text-muted. There is no duplicate header close control.
68
+
69
+ Command Menu cycles Tab between its search input and sole Esc keycap close button;
70
+ commands use text without leading arrows and space-8 + space-1 row height. Multi Select
71
+ options use background-only focus feedback; its bulk text action is compact and neutral,
72
+ with no divider. Coarse-pointer targets retain the existing 44px minimum; the compact
73
+ Command Menu close button uses the shared TouchTarget hit region.
74
+
75
+ Context Switcher identifies the current context with a neutral check and regular-weight
76
+ labels, without selected fill or a stronger selected press layer. File Tree starts with
77
+ no selection and uses only subtle hover fill, retaining tree semantics and visible
78
+ keyboard focus. Folder icons are neutral. Secondary text Button hover changes its
79
+ background while keeping its border static. Public props and token values are unchanged.
80
+
81
+ ## Tabs selection ownership
82
+
83
+ Owner approved 2026-10-06: `Tabs` accepts optional `value?: string`,
84
+ `defaultValue?: string`, and `onValueChange?: (id: string) => void`. Existing
85
+ consumers need no migration: without these props, selection starts at the first
86
+ enabled item and preserves automatic activation, roving tabindex, and availability
87
+ fallback. `defaultValue` seeds uncontrolled selection once. With `value` defined,
88
+ render derives selection from that value; a missing or disabled ID renders the first
89
+ enabled item without emitting a callback. All-disabled lists select nothing.
90
+ Click, ArrowLeft/ArrowRight, Home, and End notify activation; controlled selection
91
+ changes only when the parent updates `value`. Keyboard navigation follows the
92
+ focused enabled tab while the parent update is pending, and a later value update
93
+ does not pull focus from the activated button. Consumers own URL synchronization.
94
+
95
+ ## Record activity (owner approved 2026-10-06)
96
+
97
+ Timeline / TimelineItem (ol/li, 8px token dot or Avatar), Marker (quiet actor/time, date separator), Message (default/pending/failed, caller actions) / Mention (inline action-text on selected surface), Composer (Tier A textarea combobox/listbox, IME guard, Enter send / Shift+Enter newline), Attachment (Progress, retry/remove) are public package components. `/patterns/record-activity` composes order facts, Switch, chronological activity and a pinned Composer. Existing tokens only; data, permissions, network and notifications remain caller-owned.
@@ -24,6 +24,9 @@ Adding a clause therefore means answering two questions before the wording: who
24
24
  - [both] MUST use native links for navigation and native buttons for actions.
25
25
  - [both] MUST keep visual and hit targets aligned; controls smaller than 24px need a larger interactive area.
26
26
  - [both] MUST keep mobile control targets at least 44px where touch is expected.
27
+ - [both] MUST use `--q1-touch-target-min` (44px) for the shared centered invisible child-span hit region of Button (all sizes, including compact consumer overrides and icon-only), CopyButton, Menu/DotsMenu triggers, and Description info/hint buttons only under `(pointer: coarse), (any-pointer: coarse)`. Preserve every existing coarse-pointer visual-size rule. Only axes still below the minimum receive extra hit area; existing 44px targets do not expand outside their visual frame. The region covers the complete border box: canonical Button/CopyButton/Menu/DotsMenu borders are 1px, while Description borders are none. Consumers overriding a control border width must also set its private `--touch-target-border-width` to the actual per-side width (including zero for `border: none`); asymmetric borders need their own hit-region sizing. Focus rings stay on the visual control; child hit-region events activate that native control without replacing its pseudo-elements or overflow rules.
28
+ - [both] MUST leave enough space for expanded targets: dense neighbors must not overlap each other's visual frames. Consumers must reserve the expansion on each side and avoid clipping ancestors; if this is impossible, regroup controls or use larger visual controls. The shared region cannot infer neighbor gaps or escape control/ancestor clipping; clipped compact controls must use a visual size of at least 44px.
29
+ - [both] SHOULD deliberately depart from Geist's measured 24–32px controls without coarse-pointer enlargement: Apple HIG's 44pt guidance and WCAG 2.5.5 motivate q1travel's 44×44 CSS-pixel mobile usability target; pt and CSS pixels are different units.
27
30
  - [both] NEVER disable browser zoom.
28
31
  - [both] MUST avoid dead zones: if a region looks clickable, the clickable element must cover it.
29
32
  - [both] MUST show a loading indicator on in-flight buttons while keeping the original label.
@@ -183,6 +186,12 @@ Theming owns tokens and `color-scheme`; the interaction-contrast rule lives in D
183
186
 
184
187
  - [both] MUST be public, neutral, and specific.
185
188
  - [both] MUST use consistent nouns across pages, navigation, tables, and examples.
189
+ - [both] SHOULD render code identifiers (classes, CSS variables, tokens, props, values, filenames, commands, and code values) with the existing inline `code` or `DocToken` treatment.
190
+ - [both] SHOULD translate generic English terms into Chinese and keep brand, product, and component names, and the term "Token", in plain text.
191
+ - [both] MUST separate Chinese characters from adjacent Latin letters or digits with a half-width space in authored prose; code contents, URLs, literal UI quotations, and code samples are excluded.
192
+ - [both] MUST use full-width `,。:;` immediately after Chinese characters in authored Chinese sentences; code contents, URLs, literal UI quotations, and code samples are excluded.
193
+ - [both] SHOULD use full-width `,。:;` throughout Chinese sentences, including after Latin words or inline code; identifying sentence language across mixed JSX nodes remains an editorial review item.
194
+ - [both] SHOULD follow common Chinese technical-writing practice for mixed-language wording; Microsoft's Chinese localization guidance is a reference example, not a proprietary q1travel rule. Semantic choices and text whose rendered role cannot be reliably inferred remain editorial review items rather than machine-enforced requirements.
186
195
  - [both] SHOULD prefer short action-oriented labels.
187
196
  - [both] MUST use active voice.
188
197
  - [both] MUST use unambiguous action labels (「保存 API Key」, not 「继续」).
@@ -190,6 +199,35 @@ Theming owns tokens and `color-scheme`; the interaction-contrast rule lives in D
190
199
  - [both] SHOULD default to positive, problem-solving language, including errors.
191
200
  - [both] SHOULD use numerals for counts and keep number/unit formatting consistent.
192
201
  - [product] MUST keep currency decimal places consistent within a context (0 or 2, never mixed).
202
+ - [both] MUST address the reader as 「您」, never 「你」.
203
+ - [both] MUST NOT end a button or link label with punctuation (「删除项目」, not 「删除项目?」).
204
+ - [both] MUST keep a Toast title to one short line of at most 15 Chinese characters, excluding interpolated values; details belong in the description or in a Banner.
205
+ - [both] SHOULD write button and link labels as a verb plus a noun (「保存草稿」「添加成员」) and name the action or destination; common single actions (「完成」「关闭」「取消」「添加」「删除」) may stand alone.
206
+ - [both] SHOULD compose an empty state from a title, an explanatory description, and an action. Use 「没有…」 for a no-data title (「没有成员」), without 「还」 or trailing punctuation; a first-use title may guide the reader with 「添加您的第一个…」 (「添加您的第一个产品」), also without trailing punctuation. Write the description as one sentence ending in 「。」 that explains when content will appear (「成员加入后将显示在此处。」), and name the action explicitly (「添加成员」, not 「开始使用」).
207
+ - [both] SHOULD offer 「清除筛选」 instead of a create action when filters hide existing records, and show a load error with a retry instead of an empty state when data failed to load.
208
+ - [both] SHOULD use a compact, centered empty state for an empty section, with a regular-weight title, secondary description, and a dashed token border; omit actions when no immediate action is available.
209
+ - [both] SHOULD resolve data states in the order loading → error → empty → content.
210
+ - [both] SHOULD give every flow a way forward and a way back (「取消」「返回」), and confirm costly or hard-to-reverse actions before running them.
211
+
212
+ These copy clauses follow the actual Stripe Chinese Dashboard for reader address and empty-state copy: address the reader as 「您」, omit trailing punctuation from empty-state titles, and explain when content will appear in the description.
213
+
214
+ ### Component Naming
215
+
216
+ - [repo] SHOULD use the Geist component name when Geist has the component; otherwise use the industry name, preferring shadcn/ui, then Radix. Invent a name only when neither has one. Name what the component is, not how it is built.
217
+ - [repo] MUST switch public names directly to the new export without deprecated aliases or old documentation route redirects; record the removal as a breaking change in CHANGELOG.
218
+
219
+ ### Component Documentation Pages
220
+
221
+ Component pages follow one skeleton so a reader who has used one page can find the same thing on every other. The shape follows the Stripe Apps component reference: one sentence says what the component is for, the fixed sections sit in a fixed order, and only the feature sections in the middle vary by component. Shared terms live in `.agents/skills/product-design/references/glossary.md`.
222
+
223
+ - [repo] MUST open each component page with one sentence that says what a reader uses the component for. It names no props, no implementation, and no 「输入…输出…」 pairing.
224
+ - [repo] MUST keep the fixed sections in this order: examples first, then feature sections, then 「API」, then 「使用原则」, then 「可访问性」 where the page has one, then 「另请参阅」 as the last section.
225
+ - [repo] MUST end each component page with 「另请参阅」, linking the components a reader is likely to confuse with this one or use beside it.
226
+ - [repo] MUST name a section that covers a shared concept with the glossary's section name (for example 「尺寸」, never 「小号」 or 「大小」).
227
+ - [repo] SHOULD name a component-specific section after the feature it covers, usually the prop it documents (「图标」, 「品牌名」), not after a concept or as a sentence; the explanation goes in the section description.
228
+ - [repo] SHOULD follow the opening sentence with one sentence that separates the component from its nearest neighbour (「和 Button 不同,Badge 只显示、不能点。」).
229
+
230
+ `scripts/check-shell.mjs` enforces the MUST clauses above on every component page.
193
231
 
194
232
  ## Agent Integration
195
233
 
@@ -4,6 +4,18 @@ Status: accepted
4
4
 
5
5
  Decision date: 2026-08-26
6
6
 
7
+ ## 2026-10-05 所有者批准的直接替换
8
+
9
+ 本批跳过过渡流程,直接在 Token 层替换,不保留旧阴影或兼容档。以下取值取代后文的历史取值及方向性面板例外;后文保留为历史证据,不代表当前下游版本。
10
+
11
+ - 浅色 popover:1px gray-alpha-200 发丝边 + `0 4px 8px -4px` 黑 4% + `0 16px 24px -8px` 黑 6%。
12
+ - 暗色 popover:1px 白 alpha 发丝边 + 同几何黑 12% / 18%。
13
+ - 浅色 modal:同发丝边 + `0 8px 24px -12px` 黑 12% + `0 32px 72px -12px` 黑 6%。
14
+ - 暗色 modal:同发丝边 + 同几何黑 24% / 18%。
15
+ - Menu、Combobox、Calendar、Multi Select、Context Menu / Card / Switcher、Relative Time Card 删除额外实体边框,由 popover token 统一提供发丝边;Toast 使用同一 token。
16
+ - Drawer / Sheet 使用 modal token,删除组件自持重阴影及重复边框。
17
+ - 名称与消费方式保留;只改本仓,不声明下游已接入。升级后仍需明暗视觉复核;回滚单位仍为完整 token 声明。
18
+
7
19
  关联:q1travel-design-system#140(B2-1)、q1travel-design-system#138(界面准则 · 设计章节)
8
20
 
9
21
  这份合同冻结 `--q1-shadow-popover` 与 `--q1-shadow-modal` 由单层改为分层的取值、下游影响面与回滚单位。它**不修改 q1travel-web**,不创建下游迁移票,也不声称消费方已完成接入。