@q1travel/design-system 0.27.1 → 0.28.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/.agents/skills/product-design/references/glossary.md +8 -0
- package/.agents/skills/product-design/references/review-component.md +1 -1
- package/.agents/skills/product-design/references/surfaces.md +2 -1
- package/README.md +2 -1
- package/contracts/design.dark.md +4 -2
- package/contracts/design.md +4 -2
- package/contracts/icon-system.md +10 -1
- package/contracts/implementation-map.md +7 -1
- package/contracts/interface-guidelines.md +2 -1
- package/contracts/token-naming.md +11 -1
- package/dist-pkg/design-system.d.ts +92 -7
- package/dist-pkg/design-system.js +6271 -5962
- package/dist-pkg/style.css +639 -327
- package/dist-pkg/tokens.css +2 -34
- package/package.json +4 -3
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
| 等待中 | 加载中 | 加载态、载入 |
|
|
20
20
|
| 状态由外部持有 | 受控用法 | 受控、状态管理 |
|
|
21
21
|
| 相关组件 | 另请参阅 | 相关组件、延伸阅读 |
|
|
22
|
+
| 不带可见标签的写法 | 不带标签 | 裸形态 |
|
|
23
|
+
| 空值、超长、极值等极端输入 | 极端内容 | 边界、内容边界 |
|
|
24
|
+
| 数据量很大时只渲染可见部分 | 大量数据 | 虚拟化 |
|
|
25
|
+
| 组件与调用方各自负责什么 | 职责分工 | API 与行为矩阵 |
|
|
26
|
+
| 某个事件发生后调用的函数 | 按事件命名,如「复制后」「清除后」「选择变化后」 | 回调、变化回调、清除回调 |
|
|
27
|
+
| 组件使用的语义 Token 表 | 设计 Token | — |
|
|
28
|
+
|
|
29
|
+
小节名描述读者想达成的效果或看到的样子,不描述实现;读者扫一眼目录就该知道这一节讲什么。「迁移」类内容放到 CHANGELOG 或迁移合同,不在组件页单列小节。
|
|
22
30
|
|
|
23
31
|
### 描述用词
|
|
24
32
|
|
|
@@ -12,7 +12,7 @@ Use this workflow when reviewing this repository's component implementation or d
|
|
|
12
12
|
## Workflow
|
|
13
13
|
|
|
14
14
|
1. Load the full design principles and interface guidelines, then the affected theme, icon, token, or implementation-map contract.
|
|
15
|
-
2. Inspect the target component, its documentation page, exports, nearby tests, and the relevant browser assertions.
|
|
15
|
+
2. Inspect the target component, its documentation page, exports, nearby tests, and the relevant browser assertions. Each example must explain itself through realistic content and states; flag any caption that tells the reader how to read the example, and any component relationship written outside 「另请参阅」.
|
|
16
16
|
3. Check the rendered local component in light and dark themes. Cover sparse, dense, loading, error, focus, disabled, and reduced-motion states that the component can reach.
|
|
17
17
|
4. Probe every element that appears interactive. Verify semantics, accessible names, keyboard behavior, focus, state announcement, disabled behavior, cleanup, and provider independence where applicable.
|
|
18
18
|
5. Compare the implementation with the matching local component family before proposing a new page-local pattern.
|
|
@@ -8,6 +8,7 @@ Use `contracts/interface-guidelines.md` and `contracts/implementation-map.md` as
|
|
|
8
8
|
- Tables, lists, and pagination: Layout, Performance, and Content
|
|
9
9
|
- Destructive actions: Interactions
|
|
10
10
|
- Empty, loading, error, and permission states: `resilience.md`
|
|
11
|
-
-
|
|
11
|
+
- Motion: `contracts/interface-guidelines.md` → Animation; `contracts/token-naming.md` → Motion Tokens; read the affected component contracts and nearby runtime assertions
|
|
12
|
+
- Icons, themes, and copy: the matching named contract section
|
|
12
13
|
|
|
13
14
|
If no route covers the requested behavior, record the missing coverage in `coverage-gaps.md` instead of inventing a contract during unrelated work.
|
package/README.md
CHANGED
|
@@ -118,8 +118,9 @@ npm run dev
|
|
|
118
118
|
| `npm run check` | 全量门禁:build + `check:shell` + `check:browser` + `build:pkg`(含 `check:package-api`) |
|
|
119
119
|
| `npm run check:shell` | 结构与源码契约断言 + token 包检查 + 发布溯源检查 |
|
|
120
120
|
| `npm run check:browser` | 基于 CDP 的浏览器行为契约(含 StrictMode 前置轮) |
|
|
121
|
-
| `npm run check:package-api` | 已构建包的声明与运行时 API + 组件契约 `node --test` 套件 |
|
|
121
|
+
| `npm run check:package-api` | 已构建包的声明与运行时 API + 组件契约 `node --test` 套件 + `check:release-diff` |
|
|
122
122
|
| `npm run check:release-provenance` | 发布记录 vs `CHANGELOG.md`、origin tag 与 npm registry |
|
|
123
|
+
| `npm run check:release-diff` | 与 registry 上一已发布版本比对 `dist-pkg` 的类名、导出、声明与 token;类名变化未写入 CHANGELOG 时失败 |
|
|
123
124
|
| `npm run check:release-sync` | 发布后 registry 完整性校验与依赖更新通知脚本测试 |
|
|
124
125
|
| `npm run release:verify -- <tarball>` | 只校验 workflow tarball 与 npm registry 完整性 |
|
|
125
126
|
| `npm run release:sync -- <tarball>` | 校验 registry 后发送依赖更新通知;不具备发布能力 |
|
package/contracts/design.dark.md
CHANGED
|
@@ -471,13 +471,14 @@ These components ship in the package. Compose from this list before writing new
|
|
|
471
471
|
|
|
472
472
|
**Record activity.** Timeline, TimelineItem, TimelineCollapsedGroup, Marker, Message, Mention, Composer, Attachment. Timeline uses chronological ol/li with tone dots or Avatar; TimelineCollapsedGroup uses a native disclosure for consecutive system events; Marker keeps system events quiet and dates centered. Message supports pending/failed and caller actions; Mention renders caller-owned inline member references using action-text and the selected surface. Composer is a textarea combobox with listbox mentions, Enter sends and Shift+Enter inserts a newline; IME composition never sends or picks. Attachment reuses Progress and named removal. Network, permissions and notifications remain caller-owned. Composer accepts optional `triggers` ({char,label,options?,onQuery?,emptyText?,errorText?,toToken?}); single-symbol triggers share debounce, abort/stale rejection, statuses, retry, combobox/listbox keyboard and synchronous caret insertion. Explicit @ overrides legacy mentions/onMentionQuery; otherwise they remain an implicit @ trigger. Slash is reserved for unchanged commands and rejected as a trigger with a development warning. Enabled triggers alone appear in the default hint. Han text may directly precede any trigger; word/email interiors do not trigger. Selection inserts toToken(item), defaulting to char + label, and surviving selections produce {char,id,label} references alongside existing text/mentionIds; references is optional in the type for legacy constructed submissions and always present on component submissions. Editing a token invalidates its reference.
|
|
473
473
|
|
|
474
|
-
**Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
|
|
474
|
+
**Actions.** Button, ButtonLink, IconButton, CornerRemoveButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
|
|
475
475
|
|
|
476
476
|
- 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.
|
|
477
477
|
- 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.
|
|
478
478
|
- FilterChip opens a consumer-owned filter picker; undefined means unset, other values mean set. Its default sm 32px pill uses a dashed border-strong outline with CirclePlus when unset, and a solid outline with label, border-token divider, action-text value and ChevronDown when set. The optional leading CircleX clear action is a separate named native button before the picker, with its own Tab stop and shared focus ring.
|
|
479
479
|
- FilterOptionPanel is searchable single-select content inside Popover, usually anchored by FilterChip. It owns initial search focus, named combobox/listbox, active-descendant arrow navigation with disabled skipping, loading/error/retry/empty/no-match states, and label/keywords includes filtering. Callers own selection, closing, business ranking and async queries through controlled query/onQueryChange and an injected filter; Escape dismissal and trigger focus return stay with Popover. It does not provide multiselect or an application footer.
|
|
480
480
|
- IconButton requires an accessible name. An icon-only control without one is not deliverable.
|
|
481
|
+
- CornerRemoveButton is a fixed 24px corner removal control for thumbnails, avatars, and tags; require a caller-provided aria-label. Wrap the host and button in `.q1-corner-anchor`; reserve space around the host and avoid clipping the shared 44px coarse-pointer hit region. This does not extend the Button size scale.
|
|
481
482
|
|
|
482
483
|
**Text fields.** FormField, Label, InfoTip, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
|
|
483
484
|
|
|
@@ -521,9 +522,10 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
|
|
|
521
522
|
- Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
|
|
522
523
|
- RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
|
|
523
524
|
|
|
524
|
-
**Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
|
|
525
|
+
**Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, RegionErrorState, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
|
|
525
526
|
|
|
526
527
|
- 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.
|
|
528
|
+
- RegionErrorState replaces only the failed region in place (table body under a kept header, card body, page section) while the rest of the page stays usable: neutral surface, danger color on the icon only, polite `role=status` by default, built-in retry that keeps focus while `retrying`. ErrorCard is for a failure that blocks the current task (danger card, assertive `role=alert`). EmptyState is a successful load with no data. See `implementation-map.md` for the retry and announcement boundary.
|
|
527
529
|
- Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
|
|
528
530
|
- 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.
|
|
529
531
|
- Skeleton holds the shape of content that is coming. Spinner and LoadingDots say only that something is happening.
|
package/contracts/design.md
CHANGED
|
@@ -471,13 +471,14 @@ These components ship in the package. Compose from this list before writing new
|
|
|
471
471
|
|
|
472
472
|
**Record activity.** Timeline, TimelineItem, TimelineCollapsedGroup, Marker, Message, Mention, Composer, Attachment. Timeline uses chronological ol/li with tone dots or Avatar; TimelineCollapsedGroup uses a native disclosure for consecutive system events; Marker keeps system events quiet and dates centered. Message supports pending/failed and caller actions; Mention renders caller-owned inline member references using action-text and the selected surface. Composer is a textarea combobox with listbox mentions, Enter sends and Shift+Enter inserts a newline; IME composition never sends or picks. Composer also accepts `onMentionQuery(query, {signal})` with 200ms debounce, abort/stale-result rejection, loading/empty/error states and keyboard retry; async lookup takes precedence over static members. Optional `placeholder` and `hint` preserve the input label; member instructions appear only with mention support. Han text may directly precede @, while Unicode word/email interiors do not trigger. Selection inserts text and sets the caret synchronously. Attachment reuses Progress and named removal. Network, permissions and notifications remain caller-owned. Composer accepts optional `triggers` ({char,label,options?,onQuery?,emptyText?,errorText?,toToken?}); single-symbol triggers share debounce, abort/stale rejection, statuses, retry, combobox/listbox keyboard and synchronous caret insertion. Explicit @ overrides legacy mentions/onMentionQuery; otherwise they remain an implicit @ trigger. Slash is reserved for unchanged commands and rejected as a trigger with a development warning. Enabled triggers alone appear in the default hint. Han text may directly precede any trigger; word/email interiors do not trigger. Selection inserts toToken(item), defaulting to char + label, and surviving selections produce {char,id,label} references alongside existing text/mentionIds; references is optional in the type for legacy constructed submissions and always present on component submissions. Editing a token invalidates its reference.
|
|
473
473
|
|
|
474
|
-
**Actions.** Button, ButtonLink, IconButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
|
|
474
|
+
**Actions.** Button, ButtonLink, IconButton, CornerRemoveButton, SplitButton, LoadMoreButton, ClearButton, CopyButton, FilterChip, Link, Toolbar.
|
|
475
475
|
|
|
476
476
|
- 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.
|
|
477
477
|
- 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.
|
|
478
478
|
- FilterChip opens a consumer-owned filter picker; undefined means unset, other values mean set. Its default sm 32px pill uses a dashed border-strong outline with CirclePlus when unset, and a solid outline with label, border-token divider, action-text value and ChevronDown when set. The optional leading CircleX clear action is a separate named native button before the picker, with its own Tab stop and shared focus ring.
|
|
479
479
|
- FilterOptionPanel is searchable single-select content inside Popover, usually anchored by FilterChip. It owns initial search focus, named combobox/listbox, active-descendant arrow navigation with disabled skipping, loading/error/retry/empty/no-match states, and label/keywords includes filtering. Callers own selection, closing, business ranking and async queries through controlled query/onQueryChange and an injected filter; Escape dismissal and trigger focus return stay with Popover. It does not provide multiselect or an application footer.
|
|
480
480
|
- IconButton requires an accessible name. An icon-only control without one is not deliverable.
|
|
481
|
+
- CornerRemoveButton is a fixed 24px corner removal control for thumbnails, avatars, and tags; require a caller-provided aria-label. Wrap the host and button in `.q1-corner-anchor`; reserve space around the host and avoid clipping the shared 44px coarse-pointer hit region. This does not extend the Button size scale.
|
|
481
482
|
|
|
482
483
|
**Text fields.** FormField, Label, InfoTip, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
|
|
483
484
|
|
|
@@ -521,9 +522,10 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
|
|
|
521
522
|
- Gauge is one ratio as a ring. Progress is advancement along a task, and takes segment stops.
|
|
522
523
|
- RelativeTimeCard reveals the absolute time behind a relative one. Prefer it to printing both.
|
|
523
524
|
|
|
524
|
-
**Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
|
|
525
|
+
**Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, RegionErrorState, ErrorMessage, EmptyState, Badge, Pill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton.
|
|
525
526
|
|
|
526
527
|
- 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.
|
|
528
|
+
- RegionErrorState replaces only the failed region in place (table body under a kept header, card body, page section) while the rest of the page stays usable: neutral surface, danger color on the icon only, polite `role=status` by default, built-in retry that keeps focus while `retrying`. ErrorCard is for a failure that blocks the current task (danger card, assertive `role=alert`). EmptyState is a successful load with no data. See `implementation-map.md` for the retry and announcement boundary.
|
|
527
529
|
- Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
|
|
528
530
|
- 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.
|
|
529
531
|
- Skeleton holds the shape of content that is coming. Spinner and LoadingDots say only that something is happening.
|
package/contracts/icon-system.md
CHANGED
|
@@ -15,7 +15,7 @@ q1travel 的界面图标**目标是**一套自建的线性图标系统:几何
|
|
|
15
15
|
| 属性 | 值 |
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| 画布 `viewBox` | `0 0 24 24` |
|
|
18
|
-
| 描边宽度 `stroke-width` | `
|
|
18
|
+
| 描边宽度 `stroke-width` | `2`(见下方「描边」)|
|
|
19
19
|
| 填充 `fill` | `none` |
|
|
20
20
|
| 描边颜色 `stroke` | `currentColor` |
|
|
21
21
|
| 线帽 `stroke-linecap` | `round` |
|
|
@@ -26,6 +26,14 @@ q1travel 的界面图标**目标是**一套自建的线性图标系统:几何
|
|
|
26
26
|
- **线性风格**:统一描边、无填充,与 Lucide 图标库一致的线性视觉语言。
|
|
27
27
|
- **`currentColor`**:图标颜色继承当前文本颜色,随主题与组件状态自动适配;**不在图标内写死颜色**。
|
|
28
28
|
|
|
29
|
+
### 描边
|
|
30
|
+
|
|
31
|
+
- **统一 `2`**:控件内的图标(按钮前后缀、触发器 chevron、输入框图标、菜单项、关闭与清除按钮)与状态提示图标一律用 `2`,不按相邻文字的字重再分档。
|
|
32
|
+
- **为什么是 `2`**:控件文字多为 Medium(500),中文竖笔约 1.5px;`1.8` 的 16px 图标实际线宽约 1.2px,并排时显得偏细偏淡。`2` 约 1.33px,与文字笔画同重。
|
|
33
|
+
- **为什么不按字重分档**:同一控件族里,Select、Combobox 的值文本是正文字重,Menu、MultiSelect 的触发器文字是 Medium。按字重分档会让同族 chevron 出现两种描边,违反下方「同一控件组内不混用不同描边」,边界也无法稳定执行。
|
|
34
|
+
- **单一取值来源**:组件统一引用代码层共享常量 `ICON_STROKE_WIDTH`(`src/lib/iconStroke.ts`),不写描边字面量;文档示例代码写 `strokeWidth={2}`。暂不设描边 token,新增 token 须另行评审。
|
|
35
|
+
- **受控例外**:以下描边不是界面图标描边,可保留各自取值:≤12px 的勾选标记与头像角标(`2.5` / `3`,小尺寸下保持可辨);仪表、图表等数据图形的几何描边;展示性插画图形(如书封、首页图标墙)。新增例外须同时登记 check-shell 守卫白名单。
|
|
36
|
+
|
|
29
37
|
---
|
|
30
38
|
|
|
31
39
|
## 尺寸
|
|
@@ -75,4 +83,5 @@ q1travel 的界面图标**目标是**一套自建的线性图标系统:几何
|
|
|
75
83
|
- **颜色**:不为图标单独设色,依赖 `currentColor` 跟随文本。
|
|
76
84
|
- **间距**:图标与文本之间保留一致间距,并与文本基线对齐。
|
|
77
85
|
- **一致性**:同一控件组内不混用不同描边或尺寸。
|
|
86
|
+
- **末尾图标的留白**:16px 图标框左右各自带约 3–5px 空白(chevron-down 约 3px,chevron-right 约 5px)。控件末尾是图标时,结束侧内边距比起始侧收一档(如 `--q1-space-3` → `--q1-space-2`,或半步档 `--q1-space-2-5`),使「文字墨迹到起始边」与「图标墨迹到结束边」相差不超过 1px。只用现有间距 token;纯图标按钮本身居中,不适用。`check:browser` 对 ShowMore、Menu 触发器、MultiSelect、Select 按渲染墨迹锁定该对称。
|
|
78
87
|
- **光学位移**:16px 线性图标贴文字标签时,图标框几何居中不等于看起来居中。位移统一消费 `--q1-optical-icon-shift-cjk-label`,通过独立的 `translate` 属性施加,不改 `transform`,因此不会冲掉 chevron 的旋转。取值按字体栈校准:包默认 `--q1-font-sans` 为 `0px`;拉丁首选字体决定行盒、中文回退 PingFang SC 的字体栈(web 当前字体栈)为 `-1px`,由消费方在自己的根上覆盖。纯英文标签会因此偏高约 1–2px,这是 x-height 的物理位置,不再按脚本细分。已接入:Navigation 分组图标、分组 chevron、链接项图标;ContextSwitcher 单行条目图标与动作图标。Button 前后缀默认不位移,消费方在已知全是中文的场景把 `--q1-button-affix-optical-y` 设为该 token。组件内部另有两处具名、整数像素的局部位移,只作用于本组件:`--q1-product-lockup-optical-y`(品牌符号与竖线对齐产品名,`1px`)与 `--q1-note-icon-optical-y`(Note 图标对齐第一行正文,默认 `3px`、`large` `2px`)。数值由 `check:browser` 的光学 fixture 锁定,见 `contracts/token-naming.md` 的 Optical Tokens。
|
|
@@ -42,7 +42,7 @@ README owns the repository inventory and generated-file boundary. This file owns
|
|
|
42
42
|
Two component-page sections are q1travel supersets with no external counterpart, so their scope is defined here rather than inferred page by page.
|
|
43
43
|
|
|
44
44
|
- **设计 Token** — every Tier B native-semantic control page (Button, Input, Textarea, Select, Checkbox, Radio, Toggle, FilterChip) MUST carry this section. Those controls take their whole surface from semantic tokens, so the role/token/usage table is the only place a consumer can see which tokens to override. Tier A and Tier C pages MUST NOT add it by default: their appearance comes mostly from structure, and a token table there restates the global palette without telling the reader anything component-specific. **Switch and Tabs are the two recorded exceptions** — these Tier A components are likewise entirely token-driven; they keep their tables, and they do not license other Tier A pages to add one.
|
|
45
|
-
-
|
|
45
|
+
- **职责分工** — reserved for composite host components that the consumer assembles while the host owns the state (ContextSwitcher, Navigation, Sidebar). The table exists to state who owns what; a component that owns its own state does not need it.
|
|
46
46
|
|
|
47
47
|
Rows in these tables reference semantic tokens (`--q1-color-*`, `--q1-shadow-*`, `--q1-radius-*`), not component-private custom properties. Component-private properties driven by props (for example an avatar diameter) are implementation detail and MUST NOT be documented as a consumer-facing token contract.
|
|
48
48
|
|
|
@@ -135,3 +135,9 @@ Accepted 2026-10-08: additive `labelHidden?: boolean` (default false) keeps a re
|
|
|
135
135
|
`FormField` establishes a relative containing block for hidden labels and empty status regions. Package consumer fixtures enforce label ownership, SSR contracts cover names/messages, and `scripts/lib/check-label-hidden-browser.mjs` checks native labels, visible errors, inline Button alignment and a deterministic 390px scrollable max-content row without page overflow.
|
|
136
136
|
## Control density scale
|
|
137
137
|
Accepted 2026-10-08: explicitly sized single-line Button, ButtonLink, Input, SearchInput, Select, RangeField trigger, Menu text trigger and FilterChip share sm32 / md40 / lg48 on fine pointers. SearchInput defaults to md and retains native numeric size compatibility; FilterChip defaults to sm32. Menu text triggers default to md40 after the rendered fine-pointer audit confirmed the prior omitted-size trigger measured 38–39px; icon triggers retain their prior behavior. Calendar size remains grid-only; fieldSize controls its trigger. Switch outer sm / md / lg heights align to 32 / 40 / 48px, defaulting to md; coarse-pointer 44px rules remain unchanged. FormField and Select omit empty or absent visible labels; callers supply aria-label, aria-labelledby or a native external label. Select reuses the development-only bare-field accessible-name warning.
|
|
138
|
+
|
|
139
|
+
## RegionErrorState retry and announcement boundary
|
|
140
|
+
|
|
141
|
+
Owner approved 2026-10-15 (#455): public `RegionErrorState` replaces only a region whose data request failed, while the host keeps its frame (table header, card title, section heading). Props: `title` (required), `description`, `layout` `center` (default; table bodies and large regions, block padding `--q1-space-16` keeps roughly five table rows of height) or `inline` (cards and page sections; the retry button wraps under the description when the component's own container is narrower than 30rem), `headingLevel` 2–6 (default renders `p`, matching EmptyState), `onRetry`, `retrying`, and `live` `polite` (default, `role=status`) or `assertive` (`role=alert`). There is no `size` prop; the two layouts cover the observed hosts.
|
|
142
|
+
|
|
143
|
+
The announcement region wraps only the icon, the `q1-sr-only` 「错误:」 prefix, the title and the description, and carries `aria-busy` while `retrying`. The retry Button sits outside that region so its loading status never nests inside another live region. Retry uses Button `loading` (focusable, `aria-disabled`, polite 「正在重试」 status) rather than `busy` (native disabled drops focus); the component stays mounted while retrying, and the visible label swaps 「重试」 to 「正在重试」 (hidden from assistive technology because the loading status already announces it). Icons are rendered internally from the existing icon set (AlertCircle, RotateCcw); no icon is exported. Copy is caller-owned; field errors, 403/404 and backend messages are out of scope. After a successful retry the caller moves focus to the restored content. Existing tokens only; the danger color appears only on the icon.
|
|
@@ -223,8 +223,9 @@ Component pages follow one skeleton so a reader who has used one page can find t
|
|
|
223
223
|
- [repo] MUST open each component page with one sentence that says what a reader uses the component for. It names no props, no implementation, and no 「输入…输出…」 pairing.
|
|
224
224
|
- [repo] MUST keep the fixed sections in this order: examples first, then feature sections, then 「API」, then 「使用原则」, then 「可访问性」 where the page has one, then 「另请参阅」 as the last section.
|
|
225
225
|
- [repo] MUST end each component page with 「另请参阅」, linking the components a reader is likely to confuse with this one or use beside it.
|
|
226
|
+
- [repo] MUST make every example self-evident: realistic product content and states show the behavior on their own. Do not add meta captions that teach the reader how to read the example (「下面的示例」「此处用于演示」「模拟请求」); how components relate to each other goes in 「另请参阅」.
|
|
226
227
|
- [repo] MUST name a section that covers a shared concept with the glossary's section name (for example 「尺寸」, never 「小号」 or 「大小」).
|
|
227
|
-
- [repo] SHOULD name a component-specific section after the feature it covers, usually the prop it documents (「图标」, 「品牌名」), not after a concept or as a sentence; the
|
|
228
|
+
- [repo] SHOULD name a component-specific section after the feature it covers, usually the prop it documents (「图标」, 「品牌名」), not after a concept or as a sentence; the section description says what the feature does, not how to read the example.
|
|
228
229
|
- [repo] SHOULD follow the opening sentence with one sentence that separates the component from its nearest neighbour (「和 Button 不同,Badge 只显示、不能点。」).
|
|
229
230
|
|
|
230
231
|
`scripts/check-shell.mjs` enforces the MUST clauses above on every component page.
|
|
@@ -161,7 +161,17 @@ The theme contracts register these as `semantic-colors.auth-*`, and `check:token
|
|
|
161
161
|
|
|
162
162
|
The input background, the hover background, and the secondary button background are separate roles. Secondary buttons inside the card use `--q1-color-auth-surface-card`, because they sit on the card surface; `--q1-color-auth-surface-input` is reserved for text entry. `--q1-color-auth-surface-hover` is the interaction hover and is not substituted by `--q1-color-auth-surface-disabled`, even where the values match. `--q1-color-auth-action-hover` and `--q1-color-auth-action-press` keep the same value in both themes, unlike the shared `--q1-color-action-primary-hover` / `-press`. `--q1-color-auth-error-text` is the form-level error and stays distinct from `--q1-color-auth-field-error-text`.
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
Theme subtrees: a surface that must keep one theme regardless of the page theme sets `data-q1-theme="light"` or `data-q1-theme="dark"` on its subtree root. Account entry surfaces use `data-q1-theme="light"`; inside that subtree every `--q1-color-auth-*` token resolves to its light value, as before.
|
|
165
|
+
|
|
166
|
+
- The subtree root redeclares the complete token set of that theme, not only the auth palette: the light block is declared on `:root, [data-q1-theme='light'], [data-q1-theme='dark']`, and the dark block on `:root[data-theme='dark'], [data-q1-theme='dark']`. `var()` references therefore resolve against the subtree theme, so the neutral chain, backgrounds, text, borders, focus color and focus shadows, overlay shadows, status colors, and the auth palette all follow the subtree, not the page. Shared component tokens declared in the same two-block form (tone recipes, Book cover) follow the same rule. No second copy of any value exists; the build and `check:token-package` assert that the published token CSS contains exactly these two selector lists.
|
|
167
|
+
- Component dark-theme adjustments use the selector prefix `:is(:root[data-theme='dark'], [data-q1-theme='dark'])` and exclude `[data-q1-theme='light']` subtrees, so Input, Combobox, Button, and the other shared components render the subtree theme. Specificity matches the former `:root[data-theme='dark']` prefix.
|
|
168
|
+
- The subtree root sets `color-scheme` and the inherited text color (`--q1-color-text-primary`) for its theme, so native controls and unstyled text follow the subtree.
|
|
169
|
+
- Because the root redeclares every token, a consumer override of a `--q1-*` token on an ancestor does not reach into the subtree; apply such overrides on the subtree root or inside it.
|
|
170
|
+
- Apply the attribute to a descendant element, not to the root element that carries `data-theme`. Opposite-theme subtrees do not nest: tokens resolve correctly at any depth, but component dark adjustments assume at most one subtree between an element and the page root.
|
|
171
|
+
- Anchored overlays follow the effective theme of their anchor: the nearest `[data-q1-theme]` element at or above the trigger, or the page theme when there is none. This covers every anchored surface rendered outside the subtree, into the shared overlay root or the document body: Popover, HoverCard, Menu, DotsMenu, SplitButton, Combobox, MultiSelect, PeriodCombobox, the Calendar field popover, ContextSwitcher (menu and drilldown), Tooltip (and InfoTip), ContextCard, and RelativeTimeCard. The shared overlay layer in `src/components/ui/overlay/` writes the same `data-q1-theme` value on the panel element when it mounts, so the panel is a subtree root under the rules above; with no anchor theme it writes nothing and the panel follows the page. One observer per document re-resolves every mounted panel whenever a `data-q1-theme` attribute changes, so switching a subtree theme while an overlay is open updates the overlay, and an overlay anchored inside another overlay resolves through its host panel. A `data-q1-theme` that the consumer sets on the panel itself is left unchanged. Moving an anchor into a different subtree without changing any attribute is resolved on the next open.
|
|
172
|
+
- Overlays that render in place inside the subtree (ContextMenu, Composer suggestions) inherit the subtree directly.
|
|
173
|
+
- SearchDialog renders its scrim and panel into the shared overlay root, so sticky, `backdrop-filter`, or `transform` ancestors of the trigger cannot clip them. Its panel follows the effective theme of the trigger through the same overlay theme binding as the anchored overlays above: the panel receives the trigger's `data-q1-theme` value, or none when the trigger is outside any subtree, and updates while open when that subtree changes. The scrim follows the page theme like the other blocking surfaces.
|
|
174
|
+
- Page-level blocking surfaces follow the page theme, even when opened from inside a subtree: Modal, DestructiveActionModal, Drawer, Sheet, SidebarDrawer, and CommandMenu. They cover the whole viewport with a scrim and are not positioned against an element, so they have no reliable anchor: the focused opener differs across browsers and pointer types, and programmatic opens have none. Following the page keeps their theme deterministic. A dialog pinned to a subtree theme needs a new public API and goes through API review.
|
|
165
175
|
|
|
166
176
|
### Component Tokens
|
|
167
177
|
|
|
@@ -3,6 +3,7 @@ import { AriaRole } from 'react';
|
|
|
3
3
|
import { ButtonHTMLAttributes } from 'react';
|
|
4
4
|
import { ComponentProps } from 'react';
|
|
5
5
|
import { ComponentPropsWithoutRef } from 'react';
|
|
6
|
+
import { ComponentPropsWithRef } from 'react';
|
|
6
7
|
import { CSSProperties } from 'react';
|
|
7
8
|
import { FocusEventHandler } from 'react';
|
|
8
9
|
import { ForwardRefExoticComponent } from 'react';
|
|
@@ -10,6 +11,7 @@ import { HTMLAttributes } from 'react';
|
|
|
10
11
|
import { InputHTMLAttributes } from 'react';
|
|
11
12
|
import { JSX } from 'react';
|
|
12
13
|
import { KeyboardEventHandler } from 'react';
|
|
14
|
+
import { MouseEvent as MouseEvent_2 } from 'react';
|
|
13
15
|
import { MouseEventHandler } from 'react';
|
|
14
16
|
import { PointerEventHandler } from 'react';
|
|
15
17
|
import { ReactElement } from 'react';
|
|
@@ -898,6 +900,13 @@ export declare type CopyFeedbackOptions = {
|
|
|
898
900
|
resetAfterMs?: number;
|
|
899
901
|
};
|
|
900
902
|
|
|
903
|
+
/** 放在 q1-corner-anchor 容器内;容器须为扩展命中区留白且不裁切。 */
|
|
904
|
+
export declare function CornerRemoveButton({ className, 'aria-label': label, ...props }: CornerRemoveButtonProps): JSX.Element;
|
|
905
|
+
|
|
906
|
+
export declare type CornerRemoveButtonProps = Omit<ComponentPropsWithRef<'button'>, 'aria-label' | 'children' | 'type'> & {
|
|
907
|
+
'aria-label': string;
|
|
908
|
+
};
|
|
909
|
+
|
|
901
910
|
export declare function Description({ title, tooltip, align, ellipsis, children, }: {
|
|
902
911
|
title: string;
|
|
903
912
|
tooltip?: string;
|
|
@@ -927,11 +936,18 @@ export declare type DocsPageLink = {
|
|
|
927
936
|
href: string;
|
|
928
937
|
};
|
|
929
938
|
|
|
930
|
-
export declare function DocsPagination({ previous, next }: DocsPaginationProps): JSX.Element;
|
|
939
|
+
export declare function DocsPagination({ previous, next, onNavigate, children }: DocsPaginationProps): JSX.Element;
|
|
931
940
|
|
|
932
941
|
export declare type DocsPaginationProps = {
|
|
933
942
|
previous?: DocsPageLink;
|
|
934
943
|
next?: DocsPageLink;
|
|
944
|
+
/**
|
|
945
|
+
* 单页应用导航回调:未按修饰键的主键点击会阻止原生跳转并调用此回调;
|
|
946
|
+
* 修饰键点击、中键点击与已被阻止的事件仍走原生链接行为。
|
|
947
|
+
*/
|
|
948
|
+
onNavigate?: (href: string, event: MouseEvent_2<HTMLAnchorElement>) => void;
|
|
949
|
+
/** 中间插槽:渲染在上一页与下一页之间,例如页面反馈入口。 */
|
|
950
|
+
children?: ReactNode;
|
|
935
951
|
};
|
|
936
952
|
|
|
937
953
|
export declare function DotsMenu({ entries, size, disabled, align, label, }: {
|
|
@@ -1543,12 +1559,24 @@ declare type ModalLegacyDismissalProps = {
|
|
|
1543
1559
|
|
|
1544
1560
|
export declare type ModalProps = ModalBaseProps & (ModalLegacyDismissalProps | ModalEscapeActionProps);
|
|
1545
1561
|
|
|
1546
|
-
export declare function MultiSelect({ options, defaultSelected, placeholder, onChange }:
|
|
1547
|
-
|
|
1562
|
+
export declare function MultiSelect({ options: rawOptions, defaultSelected, placeholder, onChange, preserveSelectionOrder }: MultiSelectProps): JSX.Element;
|
|
1563
|
+
|
|
1564
|
+
export declare type MultiSelectOption = {
|
|
1565
|
+
/** 选中值:写入 defaultSelected / onChange,同一组内唯一。 */
|
|
1566
|
+
value: string;
|
|
1567
|
+
/** 显示文本:选项、触发器与已选摘要都读它。 */
|
|
1568
|
+
label: string;
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
export declare type MultiSelectProps = {
|
|
1572
|
+
/** 纯字符串选项的值与显示文本相同;值与显示文本不同时传 { value, label }。 */
|
|
1573
|
+
options: ReadonlyArray<string | MultiSelectOption>;
|
|
1548
1574
|
defaultSelected?: string[];
|
|
1549
1575
|
placeholder: string;
|
|
1550
1576
|
onChange?: (next: string[]) => void;
|
|
1551
|
-
|
|
1577
|
+
/** 全选时保留已选项的先后顺序,只把未选项按列表顺序追加到末尾;默认 false,全选按列表顺序重排。 */
|
|
1578
|
+
preserveSelectionOrder?: boolean;
|
|
1579
|
+
};
|
|
1552
1580
|
|
|
1553
1581
|
export declare function Navigation({ className, orientation, ...props }: NavigationProps): JSX.Element;
|
|
1554
1582
|
|
|
@@ -1863,6 +1891,11 @@ export declare function RangeField(props: CalendarSelectionProps & {
|
|
|
1863
1891
|
'aria-labelledby'?: string;
|
|
1864
1892
|
/** 禁用触发器与清除操作,弹层不可打开。 */
|
|
1865
1893
|
disabled?: boolean;
|
|
1894
|
+
/** 业务校验态;错误文字由调用方渲染,与 Input 一致。 */
|
|
1895
|
+
invalid?: boolean;
|
|
1896
|
+
/** 业务错误文字元素的 id,拼入 aria-describedby。 */
|
|
1897
|
+
errorId?: string;
|
|
1898
|
+
'aria-describedby'?: string;
|
|
1866
1899
|
}): JSX.Element;
|
|
1867
1900
|
|
|
1868
1901
|
export declare function RangeInputs(props: (CalendarSelectionProps | {
|
|
@@ -1900,6 +1933,27 @@ export declare type RangeValue = {
|
|
|
1900
1933
|
end: Date | null;
|
|
1901
1934
|
};
|
|
1902
1935
|
|
|
1936
|
+
export declare function RegionErrorState({ title, description, layout, headingLevel, onRetry, retrying, live, }: RegionErrorStateProps): JSX.Element;
|
|
1937
|
+
|
|
1938
|
+
export declare type RegionErrorStateLayout = 'center' | 'inline';
|
|
1939
|
+
|
|
1940
|
+
export declare type RegionErrorStateProps = {
|
|
1941
|
+
/** 失败的是哪块内容,点名资源;必填。 */
|
|
1942
|
+
title: string;
|
|
1943
|
+
/** 原因或下一步,一句话;可省略。 */
|
|
1944
|
+
description?: string;
|
|
1945
|
+
/** center:居中占位,用于表格主体与大区块;inline:左对齐紧凑行,用于卡片与页面分区。默认 center。 */
|
|
1946
|
+
layout?: RegionErrorStateLayout;
|
|
1947
|
+
/** 标题层级:缺省渲染 p(不进标题树);作为页面区块时传 2-6 渲染对应 h 标签。 */
|
|
1948
|
+
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
1949
|
+
/** 传入时渲染「重试」按钮;不传则不提供重试。 */
|
|
1950
|
+
onRetry?: () => void;
|
|
1951
|
+
/** 重试进行中:组件原地保留,按钮进入载入态并显示「正在重试」,焦点留在按钮上。 */
|
|
1952
|
+
retrying?: boolean;
|
|
1953
|
+
/** 出现时的播报级别:polite 用 role=status,assertive 用 role=alert。默认 polite。 */
|
|
1954
|
+
live?: 'polite' | 'assertive';
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1903
1957
|
export declare function RelativeTimeCard({ relative, relativeLong, iso, local, utc, }: {
|
|
1904
1958
|
relative: string;
|
|
1905
1959
|
relativeLong: string;
|
|
@@ -1928,6 +1982,11 @@ export declare type SearchDialogItem = {
|
|
|
1928
1982
|
label: string;
|
|
1929
1983
|
description?: string;
|
|
1930
1984
|
disabled?: boolean;
|
|
1985
|
+
/**
|
|
1986
|
+
* 标记当前项(例如站内搜索里的当前页面):选项输出 aria-current 并显示勾选标记,
|
|
1987
|
+
* 与键盘活动项的高亮相互独立。传 true 输出 aria-current="true",也可传具体取值。
|
|
1988
|
+
*/
|
|
1989
|
+
current?: boolean | 'page' | 'step' | 'location' | 'date' | 'time';
|
|
1931
1990
|
};
|
|
1932
1991
|
|
|
1933
1992
|
export declare type SearchDialogProps = {
|
|
@@ -2025,13 +2084,39 @@ export declare type SheetSide = 'top' | 'right' | 'bottom' | 'left';
|
|
|
2025
2084
|
|
|
2026
2085
|
export declare type SheetSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
2027
2086
|
|
|
2028
|
-
export declare function ShowMore({ items, initial, noBorder, initialExpanded, }:
|
|
2087
|
+
export declare function ShowMore({ items, initial, controls, hiddenCount: externalHiddenCount, noBorder, initialExpanded, open, onOpenChange, unit, }: ShowMoreProps): JSX.Element;
|
|
2088
|
+
|
|
2089
|
+
/** 列表模式:组件渲染 `items` 并截断到 `initial` 项。 */
|
|
2090
|
+
export declare type ShowMoreListProps = ShowMoreSharedProps & {
|
|
2029
2091
|
items: string[];
|
|
2030
2092
|
initial?: number;
|
|
2093
|
+
controls?: never;
|
|
2094
|
+
hiddenCount?: never;
|
|
2095
|
+
};
|
|
2096
|
+
|
|
2097
|
+
export declare type ShowMoreProps = ShowMoreListProps | ShowMoreTriggerOnlyProps;
|
|
2098
|
+
|
|
2099
|
+
declare type ShowMoreSharedProps = {
|
|
2031
2100
|
noBorder?: boolean;
|
|
2032
|
-
/**
|
|
2101
|
+
/** 非受控模式的初始展开态;传入 `open` 时忽略。 */
|
|
2033
2102
|
initialExpanded?: boolean;
|
|
2034
|
-
|
|
2103
|
+
/** 受控展开态;传入后由外部决定是否展开。 */
|
|
2104
|
+
open?: boolean;
|
|
2105
|
+
/** 触发器请求切换时调用,参数为期望的展开态;受控与非受控模式都会调用。 */
|
|
2106
|
+
onOpenChange?: (open: boolean) => void;
|
|
2107
|
+
/** 触发器计数单位,默认「项」,例如表格传「行」。 */
|
|
2108
|
+
unit?: string;
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
/** 仅触发器模式:只渲染触发器,`aria-controls` 指向调用方渲染的外部内容。 */
|
|
2112
|
+
export declare type ShowMoreTriggerOnlyProps = ShowMoreSharedProps & {
|
|
2113
|
+
/** 外部内容元素的 id,写入触发器的 `aria-controls`。 */
|
|
2114
|
+
controls: string;
|
|
2115
|
+
/** 折叠时隐藏的数量;小于等于 0 时不渲染触发器。 */
|
|
2116
|
+
hiddenCount: number;
|
|
2117
|
+
items?: never;
|
|
2118
|
+
initial?: never;
|
|
2119
|
+
};
|
|
2035
2120
|
|
|
2036
2121
|
export declare const Sidebar: typeof SidebarRoot & {
|
|
2037
2122
|
Header: typeof SidebarHeader;
|