@q1travel/design-system 0.11.0 → 0.13.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.
package/README.md CHANGED
@@ -8,14 +8,14 @@ q1travel 产品家族的设计权威上游仓,三件事集于一处:`@q1trav
8
8
 
9
9
  ## Sources
10
10
 
11
- - Source contracts(设计契约源,canonical):`contracts/design.md`、`contracts/design.dark.md`、`contracts/token-naming.md`、`contracts/icon-system.md`、`contracts/button-api-compatibility.md`、`contracts/interface-guidelines.md`、`contracts/design-engineering-principles.md`、`contracts/implementation-map.md`、`contracts/overlay-foundation.md`,以及迁移契约 `contracts/status-chip-badge-migration.md`、`contracts/pagination-migration.md`、`contracts/table-migration.md`、`contracts/overlay-shadow-migration.md`。
12
- - Generated mirrors(生成镜像,勿手改):`public/design.md`、`public/design.dark.md`,由 `scripts/sync-design-contracts.mjs` 从契约源复制。
11
+ - Source contracts(设计契约源,canonical):`contracts/design.md`、`contracts/design.dark.md`、`contracts/token-naming.md`、`contracts/icon-system.md`、`contracts/button-api-compatibility.md`、`contracts/interface-guidelines.md`、`contracts/design-engineering-principles.md`、`contracts/implementation-map.md`、`contracts/overlay-foundation.md`,以及迁移契约 `contracts/status-chip-badge-migration.md`、`contracts/pagination-migration.md`、`contracts/table-migration.md`、`contracts/overlay-shadow-migration.md`。独立 HTML 文档的有界 class 词汇表源为 `contracts/design.css`。
12
+ - Generated mirrors(生成镜像,勿手改):`public/design.md`、`public/design.dark.md` 由 `scripts/sync-design-contracts.mjs` 从契约源逐字节复制;`public/design.css` 由 `scripts/build-design-css.mjs` 把两份契约 frontmatter 的取值注入 `contracts/design.css` 的类词汇后生成,因而自足、可被独立 HTML 页面直接加载。
13
13
  - Runtime tokens:`src/styles/tokens.css`(canonical `--q1-*`,仅 `:root` 与 `:root[data-theme='dark']` 两个扁平 selector)。
14
14
  - 发布台账:`contracts/releases/`(仅存于仓库,不进 npm 包)。
15
15
 
16
16
  ## Getting Started
17
17
 
18
- 前置要求:Node.js 22+(CI 基线 22)与 npm(依赖锁定在 `package-lock.json`);运行 `check:browser` 需要本机 Chrome(可用 `CHROME_PATH` 指定)。
18
+ 前置要求:Node.js 22+(CI 基线 22)与 npm(依赖锁定在 `package-lock.json`);运行 `check:browser` 需要本机 Chrome(可用 `CHROME_PATH` 指定)。`check:browser` 在本地与 CI 都默认使用 `--headless=new`,不探测 TTY 或 `DISPLAY`;只有显式设置 `Q1_DESIGN_BROWSER_HEADLESS=false` 才切换到 headed 模式。
19
19
 
20
20
  ```bash
21
21
  npm install
@@ -58,6 +58,8 @@ docs/ review/ 架构骨架、agent 规范、历史决策记录
58
58
  ## Documentation & AI Guidelines
59
59
 
60
60
  - **AI Agent 必须遵循根目录 `AGENTS.md`**(Claude Code 与 Codex 共用的 canonical 入口:命令速查、红线约束、工作闭环与知识索引);`CLAUDE.md` 仅为其薄包装。
61
- - `docs/architecture.md` — 双构建管线、契约生成与下游消费模型、发布溯源链路。
61
+ - `docs/architecture.md` — 双构建管线、契约生成与下游消费模型、文档站托管与公开契约的缓存策略(含 Cloudflare Cache Rule 与 Purge 运维备忘)、发布溯源链路。
62
62
  - `docs/adr/template.md` — 新 ADR 从此模板开始;早期已接受决策存于 `review/decision-log/`。
63
+ - `docs/token-coverage-gaps.md` — 下游有真实消费需求但本仓未导出的 `--q1-*` 能力缺口登记(待决清单,非契约)。
64
+ - `docs/research/design-md-eval/` — design.md 有/无 对照评测:固定场景、确定性检查、修正队列(研究记录,非契约)。
63
65
  - 下游消费方式:安装 `@q1travel/design-system` 后 import 组件与 `style.css` / `tokens.css`;`npx q1-design sync` 把包内 skill 与契约物化为只读缓存,`q1-design verify` 校验缓存未被改动,`q1-design lint` 执行 `Q1-DS-*` 机械规则;`q1-design ls` 列出随包组件,`q1-design doc <组件名>` 打印其公共 API(两者现推自随包的 `.d.ts`,不另存清单);`q1-design overrides` 统计并分类消费方对组件外观的覆写。
package/bin/q1-design.mjs CHANGED
@@ -180,7 +180,7 @@ export function findsRawMotionLiteral(lineContent, commentState) {
180
180
  // 「用户动作到界面回应完毕」有多久;无限循环按定义不是对输入的响应,它的周期是该组件
181
181
  // 自己的节奏(spinner 1.2s、skeleton 1.6s),塞进 120–300ms 只会压坏它,或逼出假 token。
182
182
  if (/\binfinite\b/.test(segment)) return false;
183
- // 值来自 var() 时不判——`transition: var(--q1-navigation-chevron-transition)` 是合法组件 token。
183
+ // 值整体来自 var() 时不判——例如 `transition: var(--q1-duration-base)`,静态规则不能展开变量内容。
184
184
  const withoutVars = segment.replace(VAR_CALL_PATTERN, '');
185
185
  return BARE_DURATION_PATTERN.test(withoutVars) || BARE_EASING_PATTERN.test(withoutVars);
186
186
  });
@@ -313,13 +313,46 @@ components:
313
313
  activeTextColor: "{semantic-colors.text-primary}"
314
314
  borderColor: "{semantic-colors.border}"
315
315
  ---
316
-
317
316
  # q1travel Design System
318
317
 
319
318
  ## Overview
320
319
 
321
320
  This is the dark-theme source contract. It keeps token names aligned with the light contract and changes values only where the theme requires a different surface, text, border, or status value.
322
321
 
322
+ ## Scope and Reader
323
+
324
+ This file is the public entry point to q1travel design authority. It is written to be loaded whole, by an agent that may have no access to this repository and no other q1travel context.
325
+
326
+ It covers: which decisions are already made, which components exist, how space and hierarchy are composed, and which recognizable failures to avoid.
327
+
328
+ It does not cover: per-component API surfaces, per-surface interaction detail, or migration state. Inside this repository those live in `contracts/interface-guidelines.md` (surface rules), `contracts/implementation-map.md` (reference-to-implementation mapping), `contracts/token-naming.md` (token layers and migration), and `contracts/icon-system.md`. Where this file and those contracts disagree, the narrower contract wins and this file should be corrected.
329
+
330
+ Reference systems provide intent, never implementation source. Do not copy markup, class names, computed styles, or measurements taken from a screenshot.
331
+
332
+ ## Priority Order
333
+
334
+ When requirements conflict, protect in this order. A lower item never justifies breaking a higher one.
335
+
336
+ 1. Supplied facts: amounts, units, currencies, time zones, identifiers, and every qualifier attached to them.
337
+ 2. Accessibility contracts for the component tier in use. A Tier A keyboard model is not negotiable.
338
+ 3. Registered components and semantic tokens over new local structure.
339
+ 4. The reader's question, and the shortest honest path to its answer.
340
+ 5. q1travel authorship being unmistakable.
341
+ 6. Composition chosen for this particular material.
342
+ 7. Responsive refinement and motion detail.
343
+
344
+ ## Working Method
345
+
346
+ Four passes. Do not start at pass three.
347
+
348
+ **Frame the task.** Name the person deciding, what they decide, and the strongest supported answer. Separate observation from projection. Support two paths through the same page: a fast path that reaches the conclusion, and an audit path that exposes the evidence behind it.
349
+
350
+ **Choose composition.** Reject the obvious template for the document category. Map the material to visual variables: magnitude to length, time to sequence, alternatives to aligned rows. Use a table for exact lookup, prose for a single conclusion, a chart only where a relationship reads faster as a shape than as numbers.
351
+
352
+ **Apply the system.** Compose from the inventory below. Consume semantic tokens, not source values. Stay monochrome unless colour carries meaning that also survives without colour.
353
+
354
+ **Inspect before delivery.** Run the Verification section. Check the first viewport, the peer alignment, the theme pair, and the keyboard path before calling the work done.
355
+
323
356
  ## Token Layers
324
357
 
325
358
  - Core tokens define source design values such as color, spacing, radius, and typography.
@@ -328,6 +361,177 @@ This is the dark-theme source contract. It keeps token names aligned with the li
328
361
 
329
362
  ## Usage
330
363
 
331
- - Product code should consume semantic tokens and component mappings before using core values directly.
332
- - Theme-specific values may differ, but token names should stay stable across light and dark contracts.
333
- - Current theme resolved values are inspection output only; source values live in this contract and the runtime token layer.
364
+ - Product code consumes semantic tokens and component mappings before core values, and never literal colour values.
365
+ - New public tokens use the `--q1-*` prefix. Token names stay stable across the light and dark contracts; only values differ.
366
+ - Consume token names, never the resolved values printed by an inspector. A resolved value is inspection output, not an API.
367
+ - Every reference must resolve. `var(--x)` naming a token that does not exist fails silently: the declaration is dropped and the surface quietly loses a radius, a colour, or a border.
368
+
369
+ ## Primitives Inventory
370
+
371
+ These components ship in the package. Compose from this list before writing new structure. If a needed capability is missing from a component, extend that component rather than adding another local pattern beside it.
372
+
373
+ `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.
374
+
375
+ **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, Link, Toolbar.
376
+
377
+ - 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.
378
+ - 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.
379
+ - IconButton requires an accessible name. An icon-only control without one is not deliverable.
380
+
381
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, Textarea, Select, Fieldset, ErrorMessage.
382
+
383
+ - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
384
+ - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
385
+
386
+ **Option fields.** ControlOption, Checkbox, Radio, Choicebox, Combobox, MultiSelect, Switch, Toggle, Slider, RangeSlider.
387
+
388
+ - Switch commits immediately. Checkbox stages a change that a submit applies. Choosing the wrong one misstates when the change takes effect.
389
+ - Toggle selects among mutually exclusive views. It is not a Switch with more entries.
390
+ - RangeSlider is two thumbs over one interval, paired with numeric inputs; Slider is a single value.
391
+
392
+ **Dates and ranges.** CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
393
+
394
+ **Overlays.** Modal, ModalInset, DestructiveActionModal, Drawer, Sheet, Menu, ContextMenu, DotsMenu, Tooltip, ContextCard, SearchDialog, CommandMenu.
395
+
396
+ - Modal blocks and demands an answer. Use it for confirmation, not for a long form; a long form belongs in a Drawer or its own route.
397
+ - DestructiveActionModal is the required shape for irreversible actions. Do not rebuild that confirmation from a plain Modal.
398
+ - Sheet slides in from an edge with a focus trap; Drawer is the wider panel form. Menu is anchored and flips on collision; ContextMenu and DotsMenu share its keyboard engine.
399
+ - Tooltip carries a short label for a control. Anything a reader must read to decide belongs in the page or in a ContextCard.
400
+
401
+ **Data and evidence.** Table, Pagination, DocsPagination, ShowMore, Chart, Gauge, Progress, JsonView, Entity, Description, RelativeTimeCard.
402
+
403
+ - Table owns tabular semantics, selection, sorting, and density. Never assemble rows out of divs.
404
+ - Pagination paginates a data set with known or unknown totals. DocsPagination is previous/next reading order and is not interchangeable with it.
405
+ - Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
406
+ - RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
407
+
408
+ **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, Spinner, LoadingDots, Skeleton.
409
+
410
+ - 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.
411
+ - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
412
+ - Badge and Pill label a state drawn from a known set. Ordinary metadata is text, not a badge. StatusDot is the smallest form, and needs a text label beside it.
413
+ - Skeleton holds the shape of content that is coming. Spinner and LoadingDots say only that something is happening.
414
+
415
+ **Structure and navigation.** Card, AuthCard, Sidebar, SidebarDrawer, Navigation, NavigationSection, NavigationItem, NavigationGroup, ContextSwitcher, Breadcrumb, CollapsibleBreadcrumb, Tabs, Collapse, CollapseGroup, Separator, Scroller, ScrollerWithButtons, FileTree.
416
+
417
+ - Card is a surface you have earned, not a default wrapper. Reach for spacing and alignment first.
418
+ - Tabs switch between peer views of the same subject. They are not a way to shorten a page.
419
+ - ContextSwitcher is the two-level selection model; the consumer owns the state behind it.
420
+
421
+ **Identity and media.** Avatar, AvatarGroup, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher.
422
+
423
+ **Text utilities.** Code, CodeInline, CodeTokens, CodeBlock, Snippet, Kbd, MiddleTruncate, TextWithCopyButton.
424
+
425
+ - Kbd holds one key or one modifier per element.
426
+ - MiddleTruncate preserves both ends of an identifier. Do not truncate identifiers with a trailing ellipsis.
427
+
428
+ **Hooks.** useToast, useCopy, useRadio.
429
+
430
+ ## Document Stylesheet
431
+
432
+ The inventory above needs a React application. When the page is a standalone HTML document — a report, a proposal, a brief — produced outside that application, load the published stylesheet instead and compose from its class vocabulary.
433
+
434
+ ```html
435
+ <link rel="stylesheet" href="https://design.q1lx.com/design.css">
436
+ <body class="q1-doc">
437
+ <div class="q1-shell">…</div>
438
+ </body>
439
+ ```
440
+
441
+ The stylesheet is self-contained: it carries the token values it needs, so no other file has to load first. It resolves the light and dark palettes from the reader's system setting, and accepts an explicit `data-theme` of `light` or `dark` on the root element. Do not add a visible theme control.
442
+
443
+ **The vocabulary is bounded.** Treat the names below as the entire public surface. Do not invent class names, do not guess at undocumented ones, and do not write page-level `style` attributes or a second stylesheet beside it — typography, spacing, and rhythm are decided here precisely so they are not decided again per page. If something is missing, add it to the vocabulary rather than working around it.
444
+
445
+ Never load this stylesheet together with the component stylesheet that ships in the package. They serve different worlds, and loading both creates two answers to the same question.
446
+
447
+ Shell: `q1-doc`, `q1-shell`, `q1-skip-link`, `q1-header`, `q1-masthead`, `q1-footer`.
448
+
449
+ Composition: `q1-grid`, `q1-span-4`, `q1-span-6`, `q1-span-8`, `q1-span-12`, `q1-stack`, `q1-cluster`, `q1-split`, `q1-section`, `q1-reading`.
450
+
451
+ Typography: `q1-display`, `q1-title`, `q1-heading-24`, `q1-heading-20`, `q1-heading-16`, `q1-lede`, `q1-body`, `q1-label`, `q1-meta`, `q1-caption`, `q1-mono`, `q1-numeric`.
452
+
453
+ Evidence: `q1-stat-strip`, `q1-stat`, `q1-stat-label`, `q1-stat-value`, `q1-stat-detail`, `q1-table-wrap`, `q1-note`, `q1-formula`, `q1-sources`.
454
+
455
+ Comparison: `q1-bar-list`, `q1-bar`, `q1-bar-label`, `q1-bar-value`, `q1-bar-track`, `q1-bar-fill`.
456
+
457
+ Utility: `q1-sr-only`.
458
+
459
+ Rules that the classes alone do not carry:
460
+
461
+ - `q1-table-wrap` owns exactly one semantic `table`. Mark numeric headers and cells with `q1-numeric` so the column aligns and its digits share a width. `q1-numeric` belongs on table cells only; `q1-stat-value` already sets tabular figures and must not carry it.
462
+ - `q1-note` takes its meaning from `data-tone` of `info`, `success`, `warning`, or `danger`. The wording must carry that meaning too; the colour is a second cue, never the only one.
463
+ - Every `q1-bar-fill` in one list sets `--q1-bar-ratio` against the same maximum. Bars drawn without a shared scale compare nothing.
464
+ - A `q1-stat-strip` holds peer figures. Label, value, and detail are present or absent consistently across the whole strip.
465
+ - Reading prose sits in `q1-reading` or a six-to-seven column span, not across the full twelve.
466
+
467
+ ## Composition Rules
468
+
469
+ **Grid.** Twelve columns on desktop, six on tablet, four on mobile. Reading prose occupies six to seven desktop columns. Tables, charts, and major comparisons take the full twelve. Grid children set a zero minimum width so content reflows instead of overflowing.
470
+
471
+ **Spacing expresses grouping, not decoration.** Choose the step from the relationship, then keep it identical across peers.
472
+
473
+ - Within a group: `space-1` to `space-3`.
474
+ - Between groups: `space-4` to `space-6`.
475
+ - Between major sections: `space-8` to `space-10`.
476
+ - A true chapter break: `space-16`. Reserved; not a default.
477
+
478
+ Label, value, and detail spacing is identical across every peer in a set. One paragraph rhythm throughout the body.
479
+
480
+ **Hierarchy comes from type, weight, and space before it comes from surfaces.** Earn every border and every filled surface. Align every object to a shared edge, baseline, or grid line.
481
+
482
+ **Colour.** Monochrome by default. Add colour only where it encodes state, action, or a data series, and always with a second cue that is not colour. The light and dark themes are equivalent in meaning and hierarchy; neither is the primary one.
483
+
484
+ **Copy.** Sentence case headings that state the claim rather than name the section. Simplify the language, never the claim. Keep every qualifier attached to the number it qualifies.
485
+
486
+ ## DO
487
+
488
+ - Answer the reader's question in the first viewport, together with the one relationship the page turns on.
489
+ - Give the page exactly one `h1`, and descend heading levels without skipping.
490
+ - Consume semantic tokens; let the theme resolve the value.
491
+ - Use a registered component; extend it when it falls short.
492
+ - Right-align numeric columns and keep their decimal places consistent down the column.
493
+ - Let a table occupy the full width of the region that owns it.
494
+ - Keep peer values, baselines, and label positions aligned across a set.
495
+ - Name every icon-only control, and keep focus visible on every interactive element.
496
+ - Preserve uncertainty: a projection is labelled as one, and a caveat travels with its figure.
497
+ - Honour reduced-motion preferences.
498
+
499
+ ## DON'T
500
+
501
+ Do not ship any of these. Each is a recognizable generated-page default, and several are already blocked by gates in consuming repositories.
502
+
503
+ - Literal colour values in product code. Use tokens.
504
+ - A token reference with no definition behind it, or a component token with no fallback to the semantic token it replaces.
505
+ - A native `select` where the list is searchable or long.
506
+ - A hand-built Button, Badge, Table, Input, or Textarea beside the registered one.
507
+ - Reaching into another component's class names to adjust its appearance.
508
+ - All-caps eyebrows, overlines, and decorative numbered section labels.
509
+ - Decorative gradients, glows, blobs, stripes, glass effects, and shadows that carry no elevation meaning.
510
+ - A centred hero followed by a grid of equal cards.
511
+ - Repeated metric boxes standing in for a composed relationship.
512
+ - Badges or pills used for ordinary metadata.
513
+ - Nested cards, or a second border, added to repair a hierarchy that type and spacing should have carried.
514
+ - A dark rounded rectangle drawn around every chart.
515
+ - Tiny muted prose, inconsistent peer values, and misaligned baselines.
516
+ - A narrow table floating in a wide section, or a wide table compressed until words break.
517
+ - A legend where a direct label would do, or colour used without meaning.
518
+ - Bars at full width without a shared scale.
519
+ - Summary, conclusion, and recommendation sections that all restate the same sentence.
520
+ - Narration of how the page was produced.
521
+ - A visible theme switcher on a delivered page, or decorative brand marks.
522
+
523
+ ## Verification
524
+
525
+ Before delivery, confirm each of these. They are observable; none of them is a matter of taste.
526
+
527
+ - Every `var(--token)` resolves to a defined token, or carries an explicit fallback.
528
+ - No literal colour value appears outside the token definition layer.
529
+ - Every interactive element is reachable by keyboard, in a sensible order, with a visible focus style.
530
+ - Tier A components follow their WAI-ARIA APG keyboard model; Tier B controls use native semantics with an associated name; Tier C hides decorative visuals from assistive technology and names every icon-only control.
531
+ - The page carries one `h1`, ordered headings, and landmark regions.
532
+ - Text and its background meet WCAG AA, and no state is signalled by colour alone.
533
+ - Light and dark render the same hierarchy, with no missing surface or invisible border in either.
534
+ - Peer spacing, alignment, and numeric formatting are identical across each set.
535
+ - Nothing scrolls horizontally at the page level.
536
+
537
+ In this repository, `npm run check` is the full gate; a partial run is not a pass. In consuming repositories, run their design and token gates before reporting completion.
@@ -313,13 +313,46 @@ components:
313
313
  activeTextColor: "{semantic-colors.text-primary}"
314
314
  borderColor: "{semantic-colors.border}"
315
315
  ---
316
-
317
316
  # q1travel Design System
318
317
 
319
318
  ## Overview
320
319
 
321
320
  This is the light-theme source contract. It defines token names, semantic roles, and component mappings before documentation examples are rendered.
322
321
 
322
+ ## Scope and Reader
323
+
324
+ This file is the public entry point to q1travel design authority. It is written to be loaded whole, by an agent that may have no access to this repository and no other q1travel context.
325
+
326
+ It covers: which decisions are already made, which components exist, how space and hierarchy are composed, and which recognizable failures to avoid.
327
+
328
+ It does not cover: per-component API surfaces, per-surface interaction detail, or migration state. Inside this repository those live in `contracts/interface-guidelines.md` (surface rules), `contracts/implementation-map.md` (reference-to-implementation mapping), `contracts/token-naming.md` (token layers and migration), and `contracts/icon-system.md`. Where this file and those contracts disagree, the narrower contract wins and this file should be corrected.
329
+
330
+ Reference systems provide intent, never implementation source. Do not copy markup, class names, computed styles, or measurements taken from a screenshot.
331
+
332
+ ## Priority Order
333
+
334
+ When requirements conflict, protect in this order. A lower item never justifies breaking a higher one.
335
+
336
+ 1. Supplied facts: amounts, units, currencies, time zones, identifiers, and every qualifier attached to them.
337
+ 2. Accessibility contracts for the component tier in use. A Tier A keyboard model is not negotiable.
338
+ 3. Registered components and semantic tokens over new local structure.
339
+ 4. The reader's question, and the shortest honest path to its answer.
340
+ 5. q1travel authorship being unmistakable.
341
+ 6. Composition chosen for this particular material.
342
+ 7. Responsive refinement and motion detail.
343
+
344
+ ## Working Method
345
+
346
+ Four passes. Do not start at pass three.
347
+
348
+ **Frame the task.** Name the person deciding, what they decide, and the strongest supported answer. Separate observation from projection. Support two paths through the same page: a fast path that reaches the conclusion, and an audit path that exposes the evidence behind it.
349
+
350
+ **Choose composition.** Reject the obvious template for the document category. Map the material to visual variables: magnitude to length, time to sequence, alternatives to aligned rows. Use a table for exact lookup, prose for a single conclusion, a chart only where a relationship reads faster as a shape than as numbers.
351
+
352
+ **Apply the system.** Compose from the inventory below. Consume semantic tokens, not source values. Stay monochrome unless colour carries meaning that also survives without colour.
353
+
354
+ **Inspect before delivery.** Run the Verification section. Check the first viewport, the peer alignment, the theme pair, and the keyboard path before calling the work done.
355
+
323
356
  ## Token Layers
324
357
 
325
358
  - Core tokens define source design values such as color, spacing, radius, and typography.
@@ -328,6 +361,177 @@ This is the light-theme source contract. It defines token names, semantic roles,
328
361
 
329
362
  ## Usage
330
363
 
331
- - Product code should consume semantic tokens and component mappings before using core values directly.
332
- - Theme-specific values may differ, but token names should stay stable across light and dark contracts.
333
- - Current theme resolved values are inspection output only; source values live in this contract and the runtime token layer.
364
+ - Product code consumes semantic tokens and component mappings before core values, and never literal colour values.
365
+ - New public tokens use the `--q1-*` prefix. Token names stay stable across the light and dark contracts; only values differ.
366
+ - Consume token names, never the resolved values printed by an inspector. A resolved value is inspection output, not an API.
367
+ - Every reference must resolve. `var(--x)` naming a token that does not exist fails silently: the declaration is dropped and the surface quietly loses a radius, a colour, or a border.
368
+
369
+ ## Primitives Inventory
370
+
371
+ These components ship in the package. Compose from this list before writing new structure. If a needed capability is missing from a component, extend that component rather than adding another local pattern beside it.
372
+
373
+ `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.
374
+
375
+ **Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, Link, Toolbar.
376
+
377
+ - 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.
378
+ - 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.
379
+ - IconButton requires an accessible name. An icon-only control without one is not deliverable.
380
+
381
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, Textarea, Select, Fieldset, ErrorMessage.
382
+
383
+ - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
384
+ - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
385
+
386
+ **Option fields.** ControlOption, Checkbox, Radio, Choicebox, Combobox, MultiSelect, Switch, Toggle, Slider, RangeSlider.
387
+
388
+ - Switch commits immediately. Checkbox stages a change that a submit applies. Choosing the wrong one misstates when the change takes effect.
389
+ - Toggle selects among mutually exclusive views. It is not a Switch with more entries.
390
+ - RangeSlider is two thumbs over one interval, paired with numeric inputs; Slider is a single value.
391
+
392
+ **Dates and ranges.** CalendarGrid, RangeInputs, RangeField, PeriodCombobox, addDays.
393
+
394
+ **Overlays.** Modal, ModalInset, DestructiveActionModal, Drawer, Sheet, Menu, ContextMenu, DotsMenu, Tooltip, ContextCard, SearchDialog, CommandMenu.
395
+
396
+ - Modal blocks and demands an answer. Use it for confirmation, not for a long form; a long form belongs in a Drawer or its own route.
397
+ - DestructiveActionModal is the required shape for irreversible actions. Do not rebuild that confirmation from a plain Modal.
398
+ - Sheet slides in from an edge with a focus trap; Drawer is the wider panel form. Menu is anchored and flips on collision; ContextMenu and DotsMenu share its keyboard engine.
399
+ - Tooltip carries a short label for a control. Anything a reader must read to decide belongs in the page or in a ContextCard.
400
+
401
+ **Data and evidence.** Table, Pagination, DocsPagination, ShowMore, Chart, Gauge, Progress, JsonView, Entity, Description, RelativeTimeCard.
402
+
403
+ - Table owns tabular semantics, selection, sorting, and density. Never assemble rows out of divs.
404
+ - Pagination paginates a data set with known or unknown totals. DocsPagination is previous/next reading order and is not interchangeable with it.
405
+ - Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
406
+ - RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
407
+
408
+ **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, Spinner, LoadingDots, Skeleton.
409
+
410
+ - 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.
411
+ - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
412
+ - Badge and Pill label a state drawn from a known set. Ordinary metadata is text, not a badge. StatusDot is the smallest form, and needs a text label beside it.
413
+ - Skeleton holds the shape of content that is coming. Spinner and LoadingDots say only that something is happening.
414
+
415
+ **Structure and navigation.** Card, AuthCard, Sidebar, SidebarDrawer, Navigation, NavigationSection, NavigationItem, NavigationGroup, ContextSwitcher, Breadcrumb, CollapsibleBreadcrumb, Tabs, Collapse, CollapseGroup, Separator, Scroller, ScrollerWithButtons, FileTree.
416
+
417
+ - Card is a surface you have earned, not a default wrapper. Reach for spacing and alignment first.
418
+ - Tabs switch between peer views of the same subject. They are not a way to shorten a page.
419
+ - ContextSwitcher is the two-level selection model; the consumer owns the state behind it.
420
+
421
+ **Identity and media.** Avatar, AvatarGroup, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher.
422
+
423
+ **Text utilities.** Code, CodeInline, CodeTokens, CodeBlock, Snippet, Kbd, MiddleTruncate, TextWithCopyButton.
424
+
425
+ - Kbd holds one key or one modifier per element.
426
+ - MiddleTruncate preserves both ends of an identifier. Do not truncate identifiers with a trailing ellipsis.
427
+
428
+ **Hooks.** useToast, useCopy, useRadio.
429
+
430
+ ## Document Stylesheet
431
+
432
+ The inventory above needs a React application. When the page is a standalone HTML document — a report, a proposal, a brief — produced outside that application, load the published stylesheet instead and compose from its class vocabulary.
433
+
434
+ ```html
435
+ <link rel="stylesheet" href="https://design.q1lx.com/design.css">
436
+ <body class="q1-doc">
437
+ <div class="q1-shell">…</div>
438
+ </body>
439
+ ```
440
+
441
+ The stylesheet is self-contained: it carries the token values it needs, so no other file has to load first. It resolves the light and dark palettes from the reader's system setting, and accepts an explicit `data-theme` of `light` or `dark` on the root element. Do not add a visible theme control.
442
+
443
+ **The vocabulary is bounded.** Treat the names below as the entire public surface. Do not invent class names, do not guess at undocumented ones, and do not write page-level `style` attributes or a second stylesheet beside it — typography, spacing, and rhythm are decided here precisely so they are not decided again per page. If something is missing, add it to the vocabulary rather than working around it.
444
+
445
+ Never load this stylesheet together with the component stylesheet that ships in the package. They serve different worlds, and loading both creates two answers to the same question.
446
+
447
+ Shell: `q1-doc`, `q1-shell`, `q1-skip-link`, `q1-header`, `q1-masthead`, `q1-footer`.
448
+
449
+ Composition: `q1-grid`, `q1-span-4`, `q1-span-6`, `q1-span-8`, `q1-span-12`, `q1-stack`, `q1-cluster`, `q1-split`, `q1-section`, `q1-reading`.
450
+
451
+ Typography: `q1-display`, `q1-title`, `q1-heading-24`, `q1-heading-20`, `q1-heading-16`, `q1-lede`, `q1-body`, `q1-label`, `q1-meta`, `q1-caption`, `q1-mono`, `q1-numeric`.
452
+
453
+ Evidence: `q1-stat-strip`, `q1-stat`, `q1-stat-label`, `q1-stat-value`, `q1-stat-detail`, `q1-table-wrap`, `q1-note`, `q1-formula`, `q1-sources`.
454
+
455
+ Comparison: `q1-bar-list`, `q1-bar`, `q1-bar-label`, `q1-bar-value`, `q1-bar-track`, `q1-bar-fill`.
456
+
457
+ Utility: `q1-sr-only`.
458
+
459
+ Rules that the classes alone do not carry:
460
+
461
+ - `q1-table-wrap` owns exactly one semantic `table`. Mark numeric headers and cells with `q1-numeric` so the column aligns and its digits share a width. `q1-numeric` belongs on table cells only; `q1-stat-value` already sets tabular figures and must not carry it.
462
+ - `q1-note` takes its meaning from `data-tone` of `info`, `success`, `warning`, or `danger`. The wording must carry that meaning too; the colour is a second cue, never the only one.
463
+ - Every `q1-bar-fill` in one list sets `--q1-bar-ratio` against the same maximum. Bars drawn without a shared scale compare nothing.
464
+ - A `q1-stat-strip` holds peer figures. Label, value, and detail are present or absent consistently across the whole strip.
465
+ - Reading prose sits in `q1-reading` or a six-to-seven column span, not across the full twelve.
466
+
467
+ ## Composition Rules
468
+
469
+ **Grid.** Twelve columns on desktop, six on tablet, four on mobile. Reading prose occupies six to seven desktop columns. Tables, charts, and major comparisons take the full twelve. Grid children set a zero minimum width so content reflows instead of overflowing.
470
+
471
+ **Spacing expresses grouping, not decoration.** Choose the step from the relationship, then keep it identical across peers.
472
+
473
+ - Within a group: `space-1` to `space-3`.
474
+ - Between groups: `space-4` to `space-6`.
475
+ - Between major sections: `space-8` to `space-10`.
476
+ - A true chapter break: `space-16`. Reserved; not a default.
477
+
478
+ Label, value, and detail spacing is identical across every peer in a set. One paragraph rhythm throughout the body.
479
+
480
+ **Hierarchy comes from type, weight, and space before it comes from surfaces.** Earn every border and every filled surface. Align every object to a shared edge, baseline, or grid line.
481
+
482
+ **Colour.** Monochrome by default. Add colour only where it encodes state, action, or a data series, and always with a second cue that is not colour. The light and dark themes are equivalent in meaning and hierarchy; neither is the primary one.
483
+
484
+ **Copy.** Sentence case headings that state the claim rather than name the section. Simplify the language, never the claim. Keep every qualifier attached to the number it qualifies.
485
+
486
+ ## DO
487
+
488
+ - Answer the reader's question in the first viewport, together with the one relationship the page turns on.
489
+ - Give the page exactly one `h1`, and descend heading levels without skipping.
490
+ - Consume semantic tokens; let the theme resolve the value.
491
+ - Use a registered component; extend it when it falls short.
492
+ - Right-align numeric columns and keep their decimal places consistent down the column.
493
+ - Let a table occupy the full width of the region that owns it.
494
+ - Keep peer values, baselines, and label positions aligned across a set.
495
+ - Name every icon-only control, and keep focus visible on every interactive element.
496
+ - Preserve uncertainty: a projection is labelled as one, and a caveat travels with its figure.
497
+ - Honour reduced-motion preferences.
498
+
499
+ ## DON'T
500
+
501
+ Do not ship any of these. Each is a recognizable generated-page default, and several are already blocked by gates in consuming repositories.
502
+
503
+ - Literal colour values in product code. Use tokens.
504
+ - A token reference with no definition behind it, or a component token with no fallback to the semantic token it replaces.
505
+ - A native `select` where the list is searchable or long.
506
+ - A hand-built Button, Badge, Table, Input, or Textarea beside the registered one.
507
+ - Reaching into another component's class names to adjust its appearance.
508
+ - All-caps eyebrows, overlines, and decorative numbered section labels.
509
+ - Decorative gradients, glows, blobs, stripes, glass effects, and shadows that carry no elevation meaning.
510
+ - A centred hero followed by a grid of equal cards.
511
+ - Repeated metric boxes standing in for a composed relationship.
512
+ - Badges or pills used for ordinary metadata.
513
+ - Nested cards, or a second border, added to repair a hierarchy that type and spacing should have carried.
514
+ - A dark rounded rectangle drawn around every chart.
515
+ - Tiny muted prose, inconsistent peer values, and misaligned baselines.
516
+ - A narrow table floating in a wide section, or a wide table compressed until words break.
517
+ - A legend where a direct label would do, or colour used without meaning.
518
+ - Bars at full width without a shared scale.
519
+ - Summary, conclusion, and recommendation sections that all restate the same sentence.
520
+ - Narration of how the page was produced.
521
+ - A visible theme switcher on a delivered page, or decorative brand marks.
522
+
523
+ ## Verification
524
+
525
+ Before delivery, confirm each of these. They are observable; none of them is a matter of taste.
526
+
527
+ - Every `var(--token)` resolves to a defined token, or carries an explicit fallback.
528
+ - No literal colour value appears outside the token definition layer.
529
+ - Every interactive element is reachable by keyboard, in a sensible order, with a visible focus style.
530
+ - Tier A components follow their WAI-ARIA APG keyboard model; Tier B controls use native semantics with an associated name; Tier C hides decorative visuals from assistive technology and names every icon-only control.
531
+ - The page carries one `h1`, ordered headings, and landmark regions.
532
+ - Text and its background meet WCAG AA, and no state is signalled by colour alone.
533
+ - Light and dark render the same hierarchy, with no missing surface or invisible border in either.
534
+ - Peer spacing, alignment, and numeric formatting are identical across each set.
535
+ - Nothing scrolls horizontally at the page level.
536
+
537
+ In this repository, `npm run check` is the full gate; a partial run is not a pass. In consuming repositories, run their design and token gates before reporting completion.
@@ -80,6 +80,14 @@ Examples:
80
80
  - `--q1-button-bg-hover`
81
81
  - `--q1-input-border-focus`
82
82
 
83
+ Shipped today for the `.form-control` skin: `--q1-input-bg`, `--q1-input-border`,
84
+ `--q1-input-fg`, `--q1-input-border-focus`, `--q1-input-focus-ring`.
85
+
86
+ Every component token MUST declare a fallback to the semantic token it replaces, so a
87
+ consumer that defines none of them sees no change. A component token without a fallback
88
+ turns an opt-in customisation surface into a required one: the first consumer to upgrade
89
+ gets an input with no border and no background.
90
+
83
91
  ## Usage Rules
84
92
 
85
93
  - Product code should use semantic tokens first.
@@ -582,7 +582,7 @@ export declare type ContextMenuEntry = {
582
582
  type: 'separator';
583
583
  };
584
584
 
585
- export declare function ContextSwitcher({ currentContext, groups, open, defaultOpen, onOpenChange, onSelect, pendingId, errorMessage, footerActions, onFooterAction, label, presentation, }: ContextSwitcherProps): JSX.Element;
585
+ export declare function ContextSwitcher({ currentContext, groups, open, defaultOpen, onOpenChange, onSelect, pendingId, errorMessage, footerActions, header, headerActions, onFooterAction, label, presentation, }: ContextSwitcherProps): JSX.Element;
586
586
 
587
587
  export declare type ContextSwitcherAction = {
588
588
  id: string;
@@ -612,6 +612,7 @@ export declare type ContextSwitcherGroup = {
612
612
  items: ContextSwitcherItem[];
613
613
  search?: ContextSwitcherSearch;
614
614
  emptyState?: ContextSwitcherEmptyState;
615
+ divider?: boolean;
615
616
  };
616
617
 
617
618
  export declare type ContextSwitcherItem = {
@@ -622,10 +623,14 @@ export declare type ContextSwitcherItem = {
622
623
  description?: ReactNode;
623
624
  avatar?: ReactNode;
624
625
  disabled?: boolean;
626
+ depth?: 0 | 1;
627
+ kind?: 'context' | 'action';
625
628
  };
626
629
 
627
630
  export declare type ContextSwitcherPresentation = {
628
631
  type?: 'grouped';
632
+ } | {
633
+ type: 'stacked';
629
634
  } | {
630
635
  type: 'drilldown';
631
636
  primaryGroupId: string;
@@ -642,6 +647,8 @@ export declare type ContextSwitcherProps = {
642
647
  pendingId?: string;
643
648
  errorMessage?: ReactNode;
644
649
  footerActions?: ContextSwitcherAction[];
650
+ header?: ContextSwitcherCurrent;
651
+ headerActions?: ContextSwitcherAction[];
645
652
  onFooterAction?: (action: ContextSwitcherAction) => void;
646
653
  label?: string;
647
654
  presentation?: ContextSwitcherPresentation;
@@ -1389,18 +1396,48 @@ export declare const Sidebar: typeof SidebarRoot & {
1389
1396
 
1390
1397
  declare function SidebarContent({ className, ...props }: SidebarSlotProps): JSX.Element;
1391
1398
 
1399
+ export declare function SidebarDrawer({ 'aria-label': ariaLabel, open, onOpenChange, trigger, header, footer, children, collapseBelow, drawerWidth, variant, dismissOnBackdrop, }: SidebarDrawerProps): JSX.Element;
1400
+
1401
+ export declare type SidebarDrawerProps = {
1402
+ 'aria-label': string;
1403
+ open: boolean;
1404
+ onOpenChange: (open: boolean) => void;
1405
+ trigger: (props: SidebarDrawerTriggerProps) => ReactNode;
1406
+ header?: ReactNode;
1407
+ footer?: ReactNode;
1408
+ children: ReactNode;
1409
+ collapseBelow?: number;
1410
+ drawerWidth?: SidebarDrawerWidth;
1411
+ variant?: SidebarVariant;
1412
+ dismissOnBackdrop?: boolean;
1413
+ };
1414
+
1415
+ export declare type SidebarDrawerTriggerProps = {
1416
+ ref: Ref<HTMLButtonElement>;
1417
+ type: 'button';
1418
+ onClick: NonNullable<ButtonHTMLAttributes<HTMLButtonElement>['onClick']>;
1419
+ 'aria-controls': string;
1420
+ 'aria-expanded': boolean;
1421
+ 'aria-haspopup': 'dialog';
1422
+ };
1423
+
1424
+ export declare type SidebarDrawerWidth = 'compact' | 'default' | 'wide';
1425
+
1392
1426
  declare function SidebarFooter({ className, ...props }: SidebarSlotProps): JSX.Element;
1393
1427
 
1394
1428
  declare function SidebarHeader({ className, ...props }: SidebarSlotProps): JSX.Element;
1395
1429
 
1396
1430
  export declare type SidebarProps = HTMLAttributes<HTMLElement> & {
1397
1431
  'aria-label': string;
1432
+ variant?: SidebarVariant;
1398
1433
  };
1399
1434
 
1400
- declare function SidebarRoot({ className, ...props }: SidebarProps): JSX.Element;
1435
+ declare function SidebarRoot({ className, variant, ...props }: SidebarProps): JSX.Element;
1401
1436
 
1402
1437
  export declare type SidebarSlotProps = HTMLAttributes<HTMLDivElement>;
1403
1438
 
1439
+ export declare type SidebarVariant = 'panel' | 'plain';
1440
+
1404
1441
  export declare function Skeleton({ shape, animated, show, className, children, }: {
1405
1442
  shape?: SkeletonShape;
1406
1443
  animated?: boolean;
@@ -1612,8 +1649,15 @@ export declare function ThemeSwitcher({ small, disabled }: {
1612
1649
 
1613
1650
  export declare type ThresholdTone = 'primary' | 'success' | 'warning' | 'danger';
1614
1651
 
1652
+ declare type ToastAction = {
1653
+ label: string;
1654
+ /** 点击后先执行回调再关闭该提示。 */
1655
+ onClick?: () => void;
1656
+ };
1657
+
1615
1658
  declare type ToastContextValue = {
1616
- push: (toast: ToastOptions) => void;
1659
+ /** 返回提示 id,可交给 dismiss 提前关闭(如异步流程完成后收起 preserve 提示)。 */
1660
+ push: (toast: ToastOptions) => number;
1617
1661
  dismiss: (id: number) => void;
1618
1662
  };
1619
1663
 
@@ -1622,7 +1666,7 @@ export declare type ToastOptions = {
1622
1666
  /** 支持纯文本或富文本(如加粗/行内链接);aria-live 区域会播报文本内容,行内链接仍可被 Tab 到并激活。 */
1623
1667
  title: ReactNode;
1624
1668
  description?: ReactNode;
1625
- actionLabel?: string;
1669
+ action?: ToastAction;
1626
1670
  preserve?: boolean;
1627
1671
  duration?: number;
1628
1672
  };