@q1travel/design-system 0.30.10 → 0.30.12

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
@@ -69,8 +69,8 @@ root.render(
69
69
 
70
70
  | 命令 | 说明 |
71
71
  | --- | --- |
72
- | `npx q1-design ls` | 列出包内提供的组件与 hook |
73
- | `npx q1-design doc <组件名>` | 打印组件的公共 API |
72
+ | `npx q1-design ls` | 列出包内提供的组件、hook 与应用模板 |
73
+ | `npx q1-design doc <组件名>` | 打印组件的公共 API;`doc app-base` 查看应用基础样式模板 |
74
74
  | `npx q1-design lint [路径...]` | 按 `Q1-DS-*` 规则检查样式用法,默认扫描 `src` |
75
75
  | `npx q1-design lint --diff <base-ref>` | 只检查相对 `base-ref` 有改动的文件 |
76
76
  | `npx q1-design overrides [路径...]` | 统计对组件外观的 `className` 覆写 |
@@ -79,7 +79,17 @@ root.render(
79
79
 
80
80
  ## 设计契约与 AI 编码指南
81
81
 
82
- 包内随附设计契约(`contracts/*.md`)与 product-design 编码指南(`.agents/`),可供 AI 编码助手读取,使生成的界面遵循同一套 token 与组件规范。契约文件通过 `@q1travel/design-system/contracts/*` 路径导出,也可以用 `npx q1-design sync` 同步到项目中。
82
+ 包内随附设计契约(`contracts/*.md`、`contracts/app-base.css`)与 product-design 编码指南(`.agents/`),可供 AI 编码助手读取,使生成的界面遵循同一套 token 与组件规范。契约文件通过 `@q1travel/design-system/contracts/*` 路径导出,也可以用 `npx q1-design sync` 同步到项目中。
83
+
84
+ 基础层归应用所有。运行 `npx q1-design sync` 后,模板位于
85
+ `.claude/skills/product-design/contracts/app-base.css`(只读缓存,纳入 `verify` 哈希校验)。首次接入时复制为应用自己的文件:
86
+
87
+ ```bash
88
+ mkdir -p src/styles
89
+ cp .claude/skills/product-design/contracts/app-base.css src/styles/app-base.css
90
+ ```
91
+
92
+ 在应用入口先引入 `@q1travel/design-system/tokens.css`(已引入 `style.css` 时无需重复),再引入 `./styles/app-base.css`。应用可修改该副本或在之后加载覆盖样式;升级并重新 `sync` 后自行审阅模板差异再采纳,CLI 不覆盖应用副本。模板只包含盒模型、页面边距与最小高度、画布文字字体与字体平滑、点按高亮、原生表单字体继承;根容器、读屏工具与组件运动/触控行为由各自所属层管理。
83
93
 
84
94
  ## 版本
85
95
 
@@ -102,7 +112,7 @@ npm run dev
102
112
 
103
113
  ### Sources
104
114
 
105
- - 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/context-contrast-migration.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`。
115
+ - 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/context-contrast-migration.md`、`contracts/status-chip-badge-migration.md`、`contracts/pagination-migration.md`、`contracts/table-migration.md`、`contracts/overlay-shadow-migration.md`。应用自有基础样式模板源为 `contracts/app-base.css`(非运行时样式导出)。独立 HTML 文档的有界 class 词汇表源为 `contracts/design.css`。
106
116
  - 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 页面直接加载。
107
117
  - Runtime tokens:`src/styles/tokens.css`(canonical `--q1-*`,仅 `:root` 与 `:root[data-theme='dark']` 两个扁平 selector)。
108
118
  - 发布记录:`contracts/releases/`(仅存于仓库,不进 npm 包)。
@@ -119,8 +129,9 @@ npm run dev
119
129
  | `npm run check:shell` | 结构与源码契约断言 + token 包检查 + 发布溯源检查 |
120
130
  | `npm run check:browser` | 基于 CDP 的浏览器行为契约(含 StrictMode 前置轮) |
121
131
  | `npm run check:package-api` | 已构建包的声明与运行时 API + 组件契约 `node --test` 套件 + `check:release-diff` |
132
+ | `npm run changelog:assemble -- --version <x.y.z>` | 正式候选汇编 `.changes/*.md`,锁定版本段并消费片段;普通 PR 只加片段 |
122
133
  | `npm run check:release-provenance` | 发布记录 vs `CHANGELOG.md`、origin tag 与 npm registry |
123
- | `npm run check:release-diff` | 与 registry 上一已发布版本比对 `dist-pkg` 的类名、导出、声明与 token;类名变化未写入 CHANGELOG 时失败 |
134
+ | `npm run check:release-diff` | 与 registry 上一已发布版本比对 `dist-pkg` 的类名、导出、声明与 token;类名变化未写入 `.changes/*.md` 片段或候选版本段时失败 |
124
135
  | `npm run check:release-sync` | 发布后 registry 完整性校验与依赖更新通知脚本测试 |
125
136
  | `npm run release:verify -- <tarball>` | 只校验 workflow tarball 与 npm registry 完整性 |
126
137
  | `npm run release:sync -- <tarball>` | 校验 registry 后发送依赖更新通知;不具备发布能力 |
package/bin/q1-design.mjs CHANGED
@@ -854,10 +854,21 @@ function runList(consumerRootDir) {
854
854
  console.log(`\n工具与 hook (${hooks.length})`);
855
855
  console.log(` ${hooks.join(' ')}`);
856
856
  }
857
+ console.log('\n应用模板:app-base(q1-design doc app-base)');
857
858
  console.log(`\n查看某个组件的 API:q1-design doc <名称>`);
858
859
  }
859
860
 
860
861
  function runDoc(consumerRootDir, requestedName) {
862
+ if (requestedName === 'app-base') {
863
+ const packageRootDir = resolveInstalledPackageRoot(consumerRootDir) ?? resolve(dirname(fileURLToPath(import.meta.url)), '..');
864
+ const templatePath = join(packageRootDir, 'contracts/app-base.css');
865
+ if (!existsSync(templatePath)) {
866
+ console.error('当前安装的设计系统版本不含应用基础样式模板,请升级后重试');
867
+ process.exit(1);
868
+ }
869
+ console.log(readFileSync(templatePath, 'utf8'));
870
+ return;
871
+ }
861
872
  if (!requestedName) {
862
873
  console.error('Usage: q1-design doc <组件名>');
863
874
  process.exit(1);
@@ -0,0 +1,38 @@
1
+ /*
2
+ * q1-design sync 生成此模板缓存;复制到应用样式目录后归应用所有。
3
+ * 应用可按需修改或在其后覆盖;不要修改只读契约缓存。
4
+ * 先加载 @q1travel/design-system/tokens.css,再加载应用基础样式。
5
+ * 更新包并重新 sync 只更新模板,应用自行审阅并采纳差异。
6
+ */
7
+
8
+ *,
9
+ *::before,
10
+ *::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ html,
15
+ body {
16
+ margin: 0;
17
+ }
18
+
19
+ body {
20
+ min-height: 100%;
21
+ background: var(--q1-color-bg-canvas);
22
+ color: var(--q1-color-text-primary);
23
+ font-family: var(--q1-font-sans);
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
+ }
27
+
28
+ html {
29
+ height: 100%;
30
+ -webkit-tap-highlight-color: transparent;
31
+ }
32
+
33
+ button,
34
+ input,
35
+ textarea,
36
+ select {
37
+ font: inherit;
38
+ }
@@ -39,3 +39,13 @@ Popover 为非模态 dialog,无锁滚动或焦点陷阱。footer 独立于列
39
39
  公开类名:q1-cascader;__label、__control、__trigger、__placeholder、__tags、__tag、__remove、__popup、__panel、__search、__columns、__column、__list、__option、__copy、__path、__forward、__results、__back、__status、__footer、__error。只使用既有 token,不新增刻度。每列优选 space-10 × 6,高度上限 min(space-10 × 6, 40dvh);视口低于 space-10 × 12 + space-6 时同一 Popover 单列钻取,有完整父路径和返回,隐藏列带 hidden,SSR 默认单列。亮暗继承 anchor 局部主题。
40
40
 
41
41
  新增导出 Cascader、CascaderProps、CascaderNode、CascaderPathItem、CascaderPath、CascaderSearchResult、CascaderAsyncState、CascaderBranchStates 与 /components/cascader 文档入口。没有使用端迁移,既有选择器默认行为不变。接入时适配业务规则为标准节点,输出在 onChange 转换;回滚先撤新增调用和路由再撤导出,发布另行授权。
42
+
43
+ ## 可选分支确认、路径回显与空树说明
44
+
45
+ `branchSelection?: 'immediate' | 'confirm'` 仅 single 允许,默认 immediate,省略时上述指针和键盘行为不变。confirm 模式下,可选分支的点击、触屏、Enter/Space 只进入子级,不通知 onChange;下一列 listbox 外列头使用既有 DS Button secondary/sm,无勾选图形,名称「选择“<label>”」。确认后提交分支并关闭回焦;分支已经是当前值时名称「已选择“<label>”」,激活只关闭回焦,不重复通知 onChange。窄屏列头在返回行下方。叶子与搜索结果仍直接提交,disabled/selectable 约束不变。
46
+
47
+ `displayPath?: boolean` 默认 false;true 时单选触发器和多选标签把已知路径 label 以 ` / ` 连接,沿用标签换行。路径来源仍为 selectedPaths、已加载节点、存活快照;未知路径回退既有末项或 value。移除名称及读屏数量与路径摘要不变。
48
+
49
+ `emptyTreeText?: ReactNode` 默认「没有可选项」,只影响非搜索根列在 idle 且 nodes 为空时的说明;搜索继续使用 emptyText,子级空列保持默认,加载与错误不变。内容替换默认说明且容器仅一个 role=status;交互操作使用 footer,不在说明中嵌套操作。
50
+
51
+ 三个属性均为可选新增,无使用端迁移;回滚调用方先删除属性,再撤新增实现。不改变公开类名、token、默认渲染或默认选择策略。
@@ -149,6 +149,7 @@ semantic-colors:
149
149
  text-soft: "#9a9a9c"
150
150
  text-disabled: "{colors.gray-500}"
151
151
  overlay-scrim: "rgba(2, 6, 23, 0.44)"
152
+ media-scrim: "rgba(2, 6, 23, 0.56)"
152
153
  selection-background: "{colors.text-primary}"
153
154
  selection-foreground: "{colors.background-100}"
154
155
  focus-ring: "#ffffff"
@@ -676,3 +677,9 @@ In this repository, `npm run check` is the full gate; a partial run is not a pas
676
677
  ## Cascader 层级选择
677
678
 
678
679
  Cascader 用于已知层级中的节点选择,采用关联 listbox + 搜索 combobox,值完全受控、父子独立多选,默认 selectable=true、clearable=false、关闭保留查询。节点可选规则、异步请求与输出转换归调用方。字段、键盘、默认省略、公开类名和加法迁移见 [Cascader 契约](./cascader.md)。无新增 token,既有选择组件默认行为不变。
680
+
681
+ ### Menu 锁定原因
682
+
683
+ 锁定项的 `lockedReason` 固定在标签下方,以 `--q1-font-size-label-13` 和次级文字色完整换行,不使用省略号,不撑破默认最大宽度;锁图标对齐标签首行。锁定原因仍并入 menuitem 的 `aria-label`,可见原因节点 `aria-hidden="true"` 避免重复播报;键盘可发现但不可激活。普通后缀与快捷键保持原有行为。
684
+
685
+ 新增公开类名:`menu__item-content`(锁定项标签与原因容器)、`menu__item-reason`(完整原因)。仅锁定项新增这两层 DOM,非锁定项结构不变;不新增 props 或 token,调用方无需迁移。
@@ -149,6 +149,7 @@ semantic-colors:
149
149
  text-soft: "#6e6e73"
150
150
  text-disabled: "#b7b7b7"
151
151
  overlay-scrim: "rgba(2, 6, 23, 0.44)"
152
+ media-scrim: "rgba(2, 6, 23, 0.56)"
152
153
  selection-background: "{colors.text-primary}"
153
154
  selection-foreground: "{colors.background-100}"
154
155
  focus-ring: "#000000"
@@ -676,3 +677,9 @@ In this repository, `npm run check` is the full gate; a partial run is not a pas
676
677
  ## Cascader 层级选择
677
678
 
678
679
  Cascader 用于已知层级中的节点选择,采用关联 listbox + 搜索 combobox,值完全受控、父子独立多选,默认 selectable=true、clearable=false、关闭保留查询。节点可选规则、异步请求与输出转换归调用方。字段、键盘、默认省略、公开类名和加法迁移见 [Cascader 契约](./cascader.md)。无新增 token,既有选择组件默认行为不变。
680
+
681
+ ### Menu 锁定原因
682
+
683
+ 锁定项的 `lockedReason` 固定在标签下方,以 `--q1-font-size-label-13` 和次级文字色完整换行,不使用省略号,不撑破默认最大宽度;锁图标对齐标签首行。锁定原因仍并入 menuitem 的 `aria-label`,可见原因节点 `aria-hidden="true"` 避免重复播报;键盘可发现但不可激活。普通后缀与快捷键保持原有行为。
684
+
685
+ 新增公开类名:`menu__item-content`(锁定项标签与原因容器)、`menu__item-reason`(完整原因)。仅锁定项新增这两层 DOM,非锁定项结构不变;不新增 props 或 token,调用方无需迁移。
@@ -25,6 +25,7 @@ README owns the repository inventory and generated-file boundary. This file owns
25
25
  | Component preview tile | Control density sample | Custom preview composition using component token mappings | Surface, border, action, focus, radius tokens | Button and segment visual states | Flex wrap preserves compact layout | Decorative in intro, real controls documented on component pages |
26
26
  | Color rods preview | Color scale summary | Custom preview composition using color scale aliases | `--q1-color-scale-*` aliases | None | Rods distribute across available width | Semantic labels identify the represented scale |
27
27
  | Color page reference and semantic tokens | Token reference and theme value copy | `ColorSwatch`, `SemanticColorListItem`, and `SemanticColorDemo` inside `ColorsPage` | `--q1-color-*`, semantic color tokens (including `--q1-color-border-warning` and `--q1-color-bg-warning-subtle`), and semantic action tokens | Left click copies token, context menu copies resolved value. The focus shadow token shows as an applied focus ring, not a flat color swatch | Ten-step grid and semantic rows adapt to available width | Button labels, semantic rows, live copy feedback |
28
+ | Readable normal text over images | Opt-in media-text backing | `MediaScrimSpecimen` on `ColorsPage`; image/text composition belongs to the host, with no new Image, Video or Modal slot | `--q1-color-media-scrim` + opaque `--q1-color-text-on-media` | Existing component defaults remain unchanged | Image and text wrap within their host | Complete text backing; white-image alpha-composition gate requires 4.5:1 in both themes |
28
29
  | Optical alignment | Aligned-looking media, icons, labels, and column edges | `ContextSwitcher` two-line rows, `Navigation` icons and chevron, `Table` edge columns and actions column, `Badge` label wrapper, `Button` affix | Optical tokens: `--q1-optical-icon-shift-cjk-label`, `--q1-optical-media-first-line-offset`, `--q1-surface-inset-inline`, `--q1-table-edge-padding-inline`, `--q1-button-hang-inline`, `--q1-badge-optical-shift`, `--q1-button-affix-optical-y` | Shifts use standalone `translate`; rotations and layout stay intact | Integer-pixel values only; locked by the `check:browser` optical fixture | Wrapper and class changes add no accessible names or roles |
29
30
  | Page footer navigation | Sequential documentation reading | `PageNavigation` | Surface, border, text, spacing tokens | Hover, focus, feedback buttons | Three-column footer collapses with content | Links are named and feedback controls are buttons |
30
31
 
@@ -100,6 +100,7 @@ Adding a clause therefore means answering two questions before the wording: who
100
100
  - [both] SHOULD avoid widows and orphans in headings and short paragraphs; `text-wrap: balance` for headings and `text-wrap: pretty` for body copy, plus punctuation hanging where the script needs it.
101
101
  - [both] SHOULD separate a number from its unit with a non-breaking space, and keep glued terms (product names, shortcuts) from wrapping mid-term. (排版瑕疵而非缺陷;本仓中文文案里数字+单位的组合极少,写断言的假阳风险高于收益。)
102
102
  - [product] SHOULD resolve language from the browser language setting and request headers, never from IP address or GPS location.
103
+ - [both] MUST pair normal text over an image with fully opaque `--q1-color-text-on-media` and `--q1-color-media-scrim` covering the entire text background, or an independently verified opaque text backing. Do not assume `overlay-scrim` provides text contrast. Verify at least 4.5:1 after actual composition; translucent text, parent opacity, filters, blending and gradients require their own measurement.
103
104
  - [both] MUST caption spoken audio and provide a transcript for any media that carries information not available in surrounding text.
104
105
 
105
106
  ## Accessibility Tiers
@@ -103,6 +103,8 @@ Examples:
103
103
 
104
104
  `--q1-color-status-danger` is the bright red for fills and strokes only; use `--q1-color-status-danger-text` for text and weak notices without an underline. `--q1-color-status-danger-surface` and `--q1-color-status-danger-border` keep the background and stroke roles separate. `--q1-color-text-soft`, `--q1-color-border-subtle`, and `--q1-color-overlay-scrim` cover tertiary text, quiet separators, and overlay scrims.
105
105
 
106
+ `--q1-color-media-scrim` is the media-text backdrop, separate from the unchanged `--q1-color-overlay-scrim` overlay role. Both themes use `rgba(2, 6, 23, 0.56)`. Pair it with fully opaque `--q1-color-text-on-media`, covering the complete text background; ordinary source-over composition over an opaque white image meets 4.5:1 for normal text. Parent opacity, translucent text, filters, blending and shallower gradient regions require separate contrast verification. The overlay token does not guarantee media text contrast. Adoption is additive and opt-in: migrate only media text hosts; rollback removes their new references before deleting this token and its guards, then regenerates the contract mirrors.
107
+
106
108
  `--q1-color-border-warning` and `--q1-color-bg-warning-subtle` name the warning border and subtle background by role. They are equivalent to the existing `--q1-color-status-warning-border` and `--q1-color-status-warning-surface`; the background uses amber-100 in both themes, matching the success/danger subtle-surface mapping. Existing status names remain valid. Use `--q1-color-status-warning-text` on the subtle background for AA normal text. The border is a quiet decorative outline, not a standalone control boundary or the sole warning cue; pair it with warning text or an icon.
107
109
 
108
110
  `--q1-color-bg-selected-neutral` is the opaque neutral current-item background, separate from the branded `bg-selected`, quiet `bg-subtle`, and transient `bg-hover`. It references existing `gray-300` in both light and dark mode. Default, secondary, and ghost Button selected states consume it while retaining border and `aria-pressed` cues and using a heavier font weight than unselected controls; primary selected retains its primary press color, and warning/danger selected retains `bg-subtle`. The neutral selected background has measured DeltaE00 >= 3 against resting canvas, panel, and subtle surfaces in both themes; no separation from transient hover is required. Consumers may adopt the new role without renaming existing tokens; revert the Button background references and remove this additive role to roll back.
@@ -473,6 +473,8 @@ export declare type CascaderProps = CascaderSelectionProps & CascaderQueryProps
473
473
  placeholder?: string;
474
474
  searchPlaceholder?: string;
475
475
  emptyText?: ReactNode;
476
+ emptyTreeText?: ReactNode;
477
+ displayPath?: boolean;
476
478
  disabled?: boolean;
477
479
  readOnly?: boolean;
478
480
  required?: boolean;
@@ -507,10 +509,12 @@ export declare type CascaderSearchResult = {
507
509
 
508
510
  declare type CascaderSelectionProps = {
509
511
  mode: 'single';
512
+ branchSelection?: 'immediate' | 'confirm';
510
513
  value: string | null;
511
514
  onChange: (value: string | null, path: CascaderPath | null) => void;
512
515
  } | {
513
516
  mode: 'multiple';
517
+ branchSelection?: never;
514
518
  value: string[];
515
519
  onChange: (values: string[], changedPath: CascaderPath | null) => void;
516
520
  };
@@ -1232,13 +1236,13 @@ export declare type FileTreeNode = {
1232
1236
  };
1233
1237
 
1234
1238
  /** 筛选选择器入口;弹层、筛选值与 expanded 状态归调用方。 */
1235
- export declare const FilterChip: ForwardRefExoticComponent< {
1236
- label: string;
1237
- /** 可选同高尺寸;默认 sm(32px)胶囊。 */
1238
- size?: ButtonSize;
1239
- /** undefined 表示未设置;0、空字符串和 null 仍是已设置。 */
1240
- value?: ReactNode;
1241
- onClear?: () => void;
1239
+ export declare const FilterChip: ForwardRefExoticComponent<{
1240
+ label: string;
1241
+ /** 可选同高尺寸;默认 sm(32px)胶囊。 */
1242
+ size?: ButtonSize;
1243
+ /** undefined 表示未设置;0、空字符串和 null 仍是已设置。 */
1244
+ value?: ReactNode;
1245
+ onClear?: () => void;
1242
1246
  } & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "value"> & RefAttributes<HTMLButtonElement>>;
1243
1247
 
1244
1248
  export declare type FilterChipProps = {
@@ -1613,7 +1617,7 @@ declare type MenuItemBase = {
1613
1617
  type: 'item';
1614
1618
  label: string;
1615
1619
  icon?: ReactNode;
1616
- /** 后缀内容(如快捷键提示);locked 项未显式传入时显示锁定原因。 */
1620
+ /** 后缀内容(如快捷键提示);锁定原因独立显示在标签下方。 */
1617
1621
  suffix?: ReactNode;
1618
1622
  danger?: boolean;
1619
1623
  };
@@ -2337,10 +2341,12 @@ export declare function Separator({ orientation, decorative, className, }: {
2337
2341
 
2338
2342
  export declare type SeparatorOrientation = 'horizontal' | 'vertical';
2339
2343
 
2340
- export declare function Sheet({ open, onClose, title, side, size, dismissable, mobileFullscreen, children, footer }: {
2344
+ export declare function Sheet({ open, onClose, title, description, side, size, dismissable, mobileFullscreen, children, footer }: {
2341
2345
  open: boolean;
2342
2346
  onClose: () => void;
2343
2347
  title: string;
2348
+ /** 标题下方的导语;通过 aria-describedby 关联,不传则不渲染。 */
2349
+ description?: ReactNode;
2344
2350
  side?: SheetSide;
2345
2351
  size?: SheetSize;
2346
2352
  dismissable?: boolean;