@q1travel/design-system 0.30.11 → 0.30.13
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 +3 -2
- package/bin/lint-component-enums.mjs +42 -0
- package/bin/q1-design.mjs +28 -2
- package/contracts/cascader.md +10 -0
- package/contracts/design.dark.md +7 -0
- package/contracts/design.md +7 -0
- package/contracts/implementation-map.md +6 -1
- package/contracts/interface-guidelines.md +1 -0
- package/contracts/token-naming.md +2 -0
- package/dist-pkg/component-enums.json +284 -0
- package/dist-pkg/design-system.d.ts +12 -8
- package/dist-pkg/design-system.js +4471 -4439
- package/dist-pkg/style.css +72 -13
- package/dist-pkg/tokens.css +2 -0
- package/package.json +7 -5
- package/tooling/README.md +7 -1
- package/tooling/scripts/evals/rules-checklist.json +2 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ root.render(
|
|
|
71
71
|
| --- | --- |
|
|
72
72
|
| `npx q1-design ls` | 列出包内提供的组件、hook 与应用模板 |
|
|
73
73
|
| `npx q1-design doc <组件名>` | 打印组件的公共 API;`doc app-base` 查看应用基础样式模板 |
|
|
74
|
-
| `npx q1-design lint [路径...]` | 按 `Q1-DS-*`
|
|
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` 覆写 |
|
|
77
77
|
| `npx q1-design sync` | 将设计契约与 AI 编码指南同步为项目内的只读缓存 |
|
|
@@ -129,8 +129,9 @@ npm run dev
|
|
|
129
129
|
| `npm run check:shell` | 结构与源码契约断言 + token 包检查 + 发布溯源检查 |
|
|
130
130
|
| `npm run check:browser` | 基于 CDP 的浏览器行为契约(含 StrictMode 前置轮) |
|
|
131
131
|
| `npm run check:package-api` | 已构建包的声明与运行时 API + 组件契约 `node --test` 套件 + `check:release-diff` |
|
|
132
|
+
| `npm run changelog:assemble -- --version <x.y.z>` | 正式候选汇编 `.changes/*.md`,锁定版本段并消费片段;普通 PR 只加片段 |
|
|
132
133
|
| `npm run check:release-provenance` | 发布记录 vs `CHANGELOG.md`、origin tag 与 npm registry |
|
|
133
|
-
| `npm run check:release-diff` | 与 registry 上一已发布版本比对 `dist-pkg` 的类名、导出、声明与 token;类名变化未写入
|
|
134
|
+
| `npm run check:release-diff` | 与 registry 上一已发布版本比对 `dist-pkg` 的类名、导出、声明与 token;类名变化未写入 `.changes/*.md` 片段或候选版本段时失败 |
|
|
134
135
|
| `npm run check:release-sync` | 发布后 registry 完整性校验与依赖更新通知脚本测试 |
|
|
135
136
|
| `npm run release:verify -- <tarball>` | 只校验 workflow tarball 与 npm registry 完整性 |
|
|
136
137
|
| `npm run release:sync -- <tarball>` | 校验 registry 后发送依赖更新通知;不具备发布能力 |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const COMPONENT_ENUM_RULE = 'Q1-DS-002';
|
|
2
|
+
|
|
3
|
+
// Match the CLI's import-based scan; only literal JSX attributes are checked.
|
|
4
|
+
export function lintComponentEnums(source, filename, loadSchema) {
|
|
5
|
+
const imports = new Map();
|
|
6
|
+
const importPattern = /import\s*\{([^{}]*?)\}\s*from\s*["']@q1travel\/design-system["']/gs;
|
|
7
|
+
for (const match of source.matchAll(importPattern)) {
|
|
8
|
+
for (const part of match[1].split(',')) {
|
|
9
|
+
if (/^\s*type\s/.test(part)) continue;
|
|
10
|
+
const [component, local = component] = part.trim().split(/\s+as\s+/);
|
|
11
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(local)) imports.set(local, component);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
for (const match of source.matchAll(/import\s*\*\s*as\s+(\w+)\s*from\s*["']@q1travel\/design-system["']/g)) {
|
|
15
|
+
imports.set(`${match[1]}.*`, '*');
|
|
16
|
+
}
|
|
17
|
+
if (!imports.size) return [];
|
|
18
|
+
const hits = [];
|
|
19
|
+
const tagPattern = /<([A-Z][\w.]*)\b((?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'{}])*)>/g;
|
|
20
|
+
for (const tag of source.matchAll(tagPattern)) {
|
|
21
|
+
const [namespace, member] = tag[1].split('.');
|
|
22
|
+
const component = member && imports.has(`${namespace}.*`) ? member : imports.get(tag[1]);
|
|
23
|
+
if (!component) continue;
|
|
24
|
+
const attributes = /(?:^|\s)(variant|size)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*(?:"([^"]*)"|'([^']*)')\s*\})/g;
|
|
25
|
+
for (const attribute of tag[2].matchAll(attributes)) {
|
|
26
|
+
let value = attribute[2] ?? attribute[3] ?? attribute[4] ?? attribute[5];
|
|
27
|
+
if (attribute[2] !== undefined || attribute[3] !== undefined) {
|
|
28
|
+
value = value.replace(/&#(x[\da-f]+|\d+);/gi, (_, code) => String.fromCodePoint(code[0].toLowerCase() === 'x' ? parseInt(code.slice(1), 16) : Number(code)));
|
|
29
|
+
}
|
|
30
|
+
const allowed = loadSchema()[component]?.[attribute[1]];
|
|
31
|
+
if (allowed && !allowed.includes(value)) {
|
|
32
|
+
const offset = tag.index + tag[0].indexOf(tag[2]) + attribute.index;
|
|
33
|
+
hits.push({
|
|
34
|
+
ruleId: COMPONENT_ENUM_RULE,
|
|
35
|
+
lineNumber: source.slice(0, offset + attribute[0].search(/\S/)).split('\n').length,
|
|
36
|
+
lineContent: `error ${component}.${attribute[1]}=${JSON.stringify(value)}; 允许值: ${allowed.join(' | ')}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return hits;
|
|
42
|
+
}
|
package/bin/q1-design.mjs
CHANGED
|
@@ -22,6 +22,8 @@ import { createHash } from 'node:crypto';
|
|
|
22
22
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
|
|
25
|
+
import { COMPONENT_ENUM_RULE, lintComponentEnums } from './lint-component-enums.mjs';
|
|
26
|
+
|
|
25
27
|
const PACKAGE_NAME = '@q1travel/design-system';
|
|
26
28
|
const SKILL_RELATIVE_DIR = '.claude/skills/product-design';
|
|
27
29
|
const MANIFEST_FILE_NAME = '.q1-design-manifest.json';
|
|
@@ -644,6 +646,17 @@ const LINT_RULES = [
|
|
|
644
646
|
function collectLintHits(inputPaths, baseDir) {
|
|
645
647
|
const targetFiles = collectLintTargetFiles(inputPaths, baseDir);
|
|
646
648
|
const hits = [];
|
|
649
|
+
let enumSchema;
|
|
650
|
+
const loadEnumSchema = () => {
|
|
651
|
+
if (!enumSchema) {
|
|
652
|
+
const relativePath = 'dist-pkg/component-enums.json';
|
|
653
|
+
const packageRoot = resolveInstalledPackageRoot(baseDir) ?? baseDir;
|
|
654
|
+
const schemaPath = join(packageRoot, relativePath);
|
|
655
|
+
if (!existsSync(schemaPath)) throw new Error(`[q1-design] 找不到 ${relativePath},无法检查组件枚举;请安装 ${PACKAGE_NAME} 或先运行 npm run build:lib。`);
|
|
656
|
+
enumSchema = JSON.parse(readFileSync(schemaPath, 'utf8'));
|
|
657
|
+
}
|
|
658
|
+
return enumSchema;
|
|
659
|
+
};
|
|
647
660
|
|
|
648
661
|
for (const absoluteFilePath of targetFiles) {
|
|
649
662
|
const fileContent = readFileSync(absoluteFilePath, 'utf8');
|
|
@@ -653,6 +666,14 @@ function collectLintHits(inputPaths, baseDir) {
|
|
|
653
666
|
isTokens: isTokensPath(absoluteFilePath, baseDir),
|
|
654
667
|
};
|
|
655
668
|
|
|
669
|
+
if (!fileFacts.isCssFile && !hasFileDisablePragma(fileContent, COMPONENT_ENUM_RULE)) {
|
|
670
|
+
const isSuppressed = createSuppressionTracker(COMPONENT_ENUM_RULE);
|
|
671
|
+
const suppressedLines = fileContent.split('\n').map((line) => isSuppressed(line));
|
|
672
|
+
for (const hit of lintComponentEnums(fileContent, absoluteFilePath, loadEnumSchema)) {
|
|
673
|
+
if (!suppressedLines[hit.lineNumber - 1]) hits.push({ ...hit, filePath: relativeFilePath });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
656
677
|
const activeRules = LINT_RULES.map((rule) => ({
|
|
657
678
|
rule,
|
|
658
679
|
applicable: rule.appliesToFile(fileFacts) && !hasFileDisablePragma(fileContent, rule.ruleId),
|
|
@@ -717,7 +738,7 @@ function runLint(inputPaths, consumerRootDir, diffBaseRef) {
|
|
|
717
738
|
|
|
718
739
|
for (const hit of hits) {
|
|
719
740
|
console.log(
|
|
720
|
-
`${hit.ruleId} ${hit.filePath}:${hit.lineNumber} ${truncateLine(hit.lineContent)}`,
|
|
741
|
+
`${hit.ruleId} ${hit.filePath}:${hit.lineNumber} ${hit.ruleId === COMPONENT_ENUM_RULE ? hit.lineContent : truncateLine(hit.lineContent)}`,
|
|
721
742
|
);
|
|
722
743
|
}
|
|
723
744
|
console.log(`[q1-design] 共 ${hits.length} 处命中`);
|
|
@@ -1109,7 +1130,12 @@ if (isDirectRun) {
|
|
|
1109
1130
|
}
|
|
1110
1131
|
args.splice(diffIndex, 2);
|
|
1111
1132
|
}
|
|
1112
|
-
|
|
1133
|
+
try {
|
|
1134
|
+
runLint(args.length > 0 ? args : ['src'], consumerRootDir, diffBaseRef);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
console.error(error.message);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1113
1139
|
} else if (subcommand === 'eval') {
|
|
1114
1140
|
runEval();
|
|
1115
1141
|
} else if (subcommand === 'overrides') {
|
package/contracts/cascader.md
CHANGED
|
@@ -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、默认渲染或默认选择策略。
|
package/contracts/design.dark.md
CHANGED
|
@@ -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,调用方无需迁移。
|
package/contracts/design.md
CHANGED
|
@@ -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
|
|
|
@@ -111,7 +112,7 @@ Timeline / TimelineItem (ol/li, 8px token dot or Avatar), Marker (quiet actor/ti
|
|
|
111
112
|
|
|
112
113
|
Accepted 2026-10-07: public `ToggleGroup` names a group of pressable controls with single or multiple selection. Its `items` API exposes `value`, `label`, optional `icon`, `disabled`, and `ariaLabel`; icon-only items require an icon and `ariaLabel`. `type="single"` accepts string `value` / `defaultValue` and `onValueChange`; `allowEmpty` defaults to false. `type="multiple"` accepts string arrays. Controlled values remain caller-owned; uncontrolled single selection falls back to the first enabled item when its value is absent or unknown and empty selection is disallowed. Disabling an existing selected item retains its pressed state and moves only the tabstop. No existing Switch consumer is migrated.
|
|
113
114
|
|
|
114
|
-
Native buttons expose `aria-pressed` within `role="group"`. Roving tabindex and Arrow / Home / End move focus without selecting; Enter / Space activate through native clicks. Disabled items are skipped. Button-aligned sm32 / md40 / lg48 sizes use existing tokens, quiet selected background and primary text
|
|
115
|
+
Native buttons expose `aria-pressed` within `role="group"`. Roving tabindex and Arrow / Home / End move focus without selecting; Enter / Space activate through native clicks. Disabled items are skipped. Button-aligned sm32 / md40 / lg48 sizes use existing tokens, quiet selected background and primary text with the shared `selection-recipe.css` inset border-strong selection edge (bg-subtle in light, bg-raised in dark); keyboard focus replaces that edge with the shared focus shadow, and the shared coarse-pointer 44px TouchTarget with reserved neighbor space. The Tier A keyboard contract lives on `/components/toggle-group`; Switch retains radio semantics and Tabs retains panel semantics.
|
|
115
116
|
## Checkbox task completion circle
|
|
116
117
|
- Native base: `src/components/ui/ControlOption.tsx`; circle-only readonly/warning module: `src/components/ui/CircleCheckbox.tsx`; independent style: `src/styles/checkbox-circle.css`, imported at the top of global.css and included by build-style.mjs. Square defaults remain unchanged.
|
|
117
118
|
- Documentation: `src/pages/CheckboxPage.tsx`, `CheckboxCircleDemo.tsx`, `src/styles/checkbox-circle-specimen.css`; circle completion is restricted to tasks/checklists and row completion treatment stays consumer-owned.
|
|
@@ -149,3 +150,7 @@ The announcement region wraps only the icon, the `q1-sr-only` 「错误:」 pr
|
|
|
149
150
|
## Cascader 层级选择
|
|
150
151
|
|
|
151
152
|
`Cascader`(`src/components/ui/Cascader.tsx`)负责字段及触发器;`cascader/` 的 Types、model、window、Panel 分别负责受控边界、路径与独立选择、测量窗口、关联 listbox 与搜索。复用 Popover/useOverlay、RegionErrorState 和 Spinner;样式来自既有 surface、text、control、space、radius 与 focus token。窄屏同一弹层单列钻取,亮暗继承 anchor。完整约束、可选省略、公开面与回滚见 [Cascader](./cascader.md)。文档入口 `/components/cascader`,不迁移既有选择器。
|
|
153
|
+
|
|
154
|
+
## ThemeSwitcher selection appearance
|
|
155
|
+
|
|
156
|
+
Accepted 2026-11-02 (#761): ThemeSwitcher keeps a single outer group border. Options have no border; selection uses `--q1-color-bg-selected-neutral` and primary text, with no inset selection edge. Unselected options keep secondary text and the container background. Light and dark selected and unselected text meet 4.5:1 contrast; the selected background differs from the container. Native labeled radios retain their keyboard behavior, hover changes enabled option text to primary, and keyboard focus uses the shared focus shadow. Public props, class names, DOM structure, exports, and tokens are unchanged.
|
|
@@ -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.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Badge": {
|
|
3
|
+
"size": [
|
|
4
|
+
"sm",
|
|
5
|
+
"md",
|
|
6
|
+
"lg"
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
"Book": {
|
|
10
|
+
"variant": [
|
|
11
|
+
"stripe",
|
|
12
|
+
"simple"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"Browser": {
|
|
16
|
+
"variant": [
|
|
17
|
+
"light",
|
|
18
|
+
"dark"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"Button": {
|
|
22
|
+
"variant": [
|
|
23
|
+
"primary",
|
|
24
|
+
"secondary",
|
|
25
|
+
"ghost",
|
|
26
|
+
"warning",
|
|
27
|
+
"danger"
|
|
28
|
+
],
|
|
29
|
+
"size": [
|
|
30
|
+
"sm",
|
|
31
|
+
"md",
|
|
32
|
+
"lg"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"ButtonLink": {
|
|
36
|
+
"variant": [
|
|
37
|
+
"primary",
|
|
38
|
+
"secondary",
|
|
39
|
+
"ghost",
|
|
40
|
+
"warning",
|
|
41
|
+
"danger"
|
|
42
|
+
],
|
|
43
|
+
"size": [
|
|
44
|
+
"sm",
|
|
45
|
+
"md",
|
|
46
|
+
"lg"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"CalendarGrid": {
|
|
50
|
+
"size": [
|
|
51
|
+
"large",
|
|
52
|
+
"small"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"Checkbox": {
|
|
56
|
+
"size": [
|
|
57
|
+
"sm",
|
|
58
|
+
"md"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"CodeBlock": {
|
|
62
|
+
"variant": [
|
|
63
|
+
"default",
|
|
64
|
+
"flush"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"Combobox": {
|
|
68
|
+
"size": [
|
|
69
|
+
"sm",
|
|
70
|
+
"md",
|
|
71
|
+
"lg"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"DotsMenu": {
|
|
75
|
+
"size": [
|
|
76
|
+
"sm",
|
|
77
|
+
"md",
|
|
78
|
+
"lg"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"ErrorMessage": {
|
|
82
|
+
"size": [
|
|
83
|
+
"sm",
|
|
84
|
+
"md",
|
|
85
|
+
"lg"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"FilterChip": {
|
|
89
|
+
"size": [
|
|
90
|
+
"sm",
|
|
91
|
+
"md",
|
|
92
|
+
"lg"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"Gauge": {
|
|
96
|
+
"size": [
|
|
97
|
+
"xs",
|
|
98
|
+
"sm",
|
|
99
|
+
"md",
|
|
100
|
+
"lg"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"IconButton": {
|
|
104
|
+
"variant": [
|
|
105
|
+
"primary",
|
|
106
|
+
"secondary",
|
|
107
|
+
"ghost",
|
|
108
|
+
"warning",
|
|
109
|
+
"danger"
|
|
110
|
+
],
|
|
111
|
+
"size": [
|
|
112
|
+
"sm",
|
|
113
|
+
"md",
|
|
114
|
+
"lg"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"Input": {
|
|
118
|
+
"size": [
|
|
119
|
+
"xs",
|
|
120
|
+
"sm",
|
|
121
|
+
"md",
|
|
122
|
+
"lg"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"InputOTP": {
|
|
126
|
+
"size": [
|
|
127
|
+
"md",
|
|
128
|
+
"lg"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"Marker": {
|
|
132
|
+
"variant": [
|
|
133
|
+
"default",
|
|
134
|
+
"separator",
|
|
135
|
+
"unread"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
"Menu": {
|
|
139
|
+
"size": [
|
|
140
|
+
"sm",
|
|
141
|
+
"md",
|
|
142
|
+
"lg"
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
"Navigation": {
|
|
146
|
+
"variant": [
|
|
147
|
+
"default",
|
|
148
|
+
"line"
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
"Note": {
|
|
152
|
+
"size": [
|
|
153
|
+
"small",
|
|
154
|
+
"default",
|
|
155
|
+
"large"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"PageFeedback": {
|
|
159
|
+
"variant": [
|
|
160
|
+
"rating",
|
|
161
|
+
"helpful"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"Pill": {
|
|
165
|
+
"size": [
|
|
166
|
+
"sm",
|
|
167
|
+
"md",
|
|
168
|
+
"lg"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"ProductLockup": {
|
|
172
|
+
"size": [
|
|
173
|
+
"sm",
|
|
174
|
+
"md"
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"ProjectBanner": {
|
|
178
|
+
"variant": [
|
|
179
|
+
"default",
|
|
180
|
+
"success",
|
|
181
|
+
"warning",
|
|
182
|
+
"error"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"RangeField": {
|
|
186
|
+
"size": [
|
|
187
|
+
"large",
|
|
188
|
+
"small"
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
"Select": {
|
|
192
|
+
"size": [
|
|
193
|
+
"xs",
|
|
194
|
+
"sm",
|
|
195
|
+
"md",
|
|
196
|
+
"lg"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
"Sheet": {
|
|
200
|
+
"size": [
|
|
201
|
+
"sm",
|
|
202
|
+
"md",
|
|
203
|
+
"lg",
|
|
204
|
+
"xl"
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
"SidebarDrawer": {
|
|
208
|
+
"variant": [
|
|
209
|
+
"panel",
|
|
210
|
+
"plain"
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
"Spinner": {
|
|
214
|
+
"size": [
|
|
215
|
+
"sm",
|
|
216
|
+
"md",
|
|
217
|
+
"lg",
|
|
218
|
+
"xl"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
"SplitButton": {
|
|
222
|
+
"variant": [
|
|
223
|
+
"default",
|
|
224
|
+
"secondary"
|
|
225
|
+
],
|
|
226
|
+
"size": [
|
|
227
|
+
"sm",
|
|
228
|
+
"md",
|
|
229
|
+
"lg"
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
"StatusPage": {
|
|
233
|
+
"size": [
|
|
234
|
+
"page",
|
|
235
|
+
"region"
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
"Switch": {
|
|
239
|
+
"variant": [
|
|
240
|
+
"segmented",
|
|
241
|
+
"cards"
|
|
242
|
+
],
|
|
243
|
+
"size": [
|
|
244
|
+
"sm",
|
|
245
|
+
"md",
|
|
246
|
+
"lg"
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
"Tabs": {
|
|
250
|
+
"variant": [
|
|
251
|
+
"default",
|
|
252
|
+
"secondary",
|
|
253
|
+
"line"
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
"Textarea": {
|
|
257
|
+
"size": [
|
|
258
|
+
"xs",
|
|
259
|
+
"sm",
|
|
260
|
+
"md",
|
|
261
|
+
"lg"
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
"TextWithCopyButton": {
|
|
265
|
+
"size": [
|
|
266
|
+
"default",
|
|
267
|
+
"small"
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
"Toggle": {
|
|
271
|
+
"size": [
|
|
272
|
+
"sm",
|
|
273
|
+
"md",
|
|
274
|
+
"lg"
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
"ToggleGroup": {
|
|
278
|
+
"size": [
|
|
279
|
+
"sm",
|
|
280
|
+
"md",
|
|
281
|
+
"lg"
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -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
|
-
/**
|
|
1620
|
+
/** 后缀内容(如快捷键提示);锁定原因独立显示在标签下方。 */
|
|
1617
1621
|
suffix?: ReactNode;
|
|
1618
1622
|
danger?: boolean;
|
|
1619
1623
|
};
|