@q1travel/design-system 0.30.16 → 0.31.1

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.
@@ -54,9 +54,10 @@ brand-text semantic token is not introduced in this migration.
54
54
  The existing `presentation: { type: 'stacked' }` adopts one consistent visual
55
55
  behavior, without new props. Grouped and drilldown keep their presentations.
56
56
 
57
- - A selected `menuitemradio` retains `aria-checked`, with a subtle full-row fill
58
- and an inline-start accent rail instead of a trailing check. Pending feedback
59
- remains visible. Focus and current-context indicators remain distinguishable.
57
+ - A selected `menuitemradio` retains `aria-checked`, with a neutral trailing
58
+ check and regular-weight labels, without selected fill or a stronger selected
59
+ press layer. Pending feedback remains visible. Hover, focus, and submenu
60
+ expansion retain their own feedback.
60
61
  - `depth: 1` uses a connector to the preceding depth-0 context within the same
61
62
  group. The last child ends the branch at its center. Search results keep their
62
63
  existing filtering behavior; when the parent is absent, retain indentation
@@ -84,10 +85,11 @@ carries.
84
85
  step (16px) from its parent's inline padding, in every presentation. The
85
86
  connector lines are removed; `aria-describedby` still names the visible
86
87
  parent, so the parent relationship survives for assistive technology.
87
- - A selected `menuitemradio` keeps `aria-checked`, the subtle full-row fill and
88
- its rounded corners, and gains the trailing check already used by grouped and
89
- drilldown. The inline-start accent rail is removed. Hover remains a background
90
- layer; `:focus-visible` keeps its own focus shadow and is not a decoration.
88
+ - A selected `menuitemradio` keeps `aria-checked`, a neutral trailing check,
89
+ and regular-weight labels, without selected fill or a stronger selected press
90
+ layer, as in grouped and drilldown. The inline-start accent rail is removed.
91
+ Hover remains a background layer; `:focus-visible` keeps its own focus shadow.
92
+ Submenu expansion retains its own feedback.
91
93
 
92
94
  ## Acceptance and delivery boundary
93
95
 
@@ -456,6 +456,7 @@ Four passes. Do not start at pass three.
456
456
  - Core tokens define source design values such as color, spacing, radius, and typography.
457
457
  - Semantic tokens describe product meaning such as action, surface, border, text, focus, and status.
458
458
  - Component mappings consume semantic tokens and define shared behavior for controls and documentation specimens.
459
+ - Input focus shadows use one 4px, 16% semantic-color halo: `--q1-shadow-focus-input` uses focus and `--q1-shadow-focus-input-danger` uses status-danger. Existing real borders retain their resting colors on focus. The shared `--q1-shadow-focus` and `--q1-shadow-focus-danger` keep their two layers. Resting danger shadows that directly consume either input token follow that token; other resting field error shadows remain unchanged.
459
460
 
460
461
  ## Usage
461
462
 
@@ -516,6 +517,7 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
516
517
  - 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.
517
518
  - DestructiveActionModal is the required shape for irreversible actions. Do not rebuild that confirmation from a plain Modal. It is also where high-risk, irreversible, or data-overwriting consequences are stated; do not move that warning into option second lines.
518
519
  - Sheet slides in from an edge with a focus trap; opt-in `mobileFullscreen` fills narrow viewports with safe-area padding and no outer inset or radius, while default floating spacing remains unchanged; Drawer is the wider panel form. Menu is anchored and flips on collision; ContextMenu and DotsMenu share its keyboard engine.
520
+ - Menu, DotsMenu and ContextMenu item entries use the existing `danger: true` (no separate tone). Dangerous text and icons use `--q1-color-status-danger-text`, with no border or resting fill and no inverted foreground. Hover and visible focus inherit the neutral `--q1-color-bg-hover`; dark-theme danger active also uses that neutral hover background to keep both themes at AA. Ordinary active retains its text-primary 10% transparent mix. Disabled or locked dangerous entries use secondary text/icon color and have no hover or active feedback.
519
521
  - Tooltip carries a short label for a control. Anything a reader must read to decide belongs in the page or in a ContextCard.
520
522
 
521
523
  **Data and evidence.** Table, Pagination, DocsPagination, ShowMore, Chart, Gauge, Progress, JsonView, Entity, Description, RelativeTimeCard.
@@ -530,10 +532,16 @@ CheckCircleFill accepts `tone?: 'default' | 'success'` (default `default`); succ
530
532
 
531
533
  **Images and proportions.** AspectRatio requires a finite positive width/height ratio (invalid values render as 1 with one development warning) and only reserves layout space. Image requires alt (empty for decoration), defaults to contain and eager, accepts optional src, fit, loading, sm/md/lg square sizes (32/40/48), onError and root className; omitted size follows host width and natural image ratio. Loading keeps img mounted under a decorative Skeleton; missing and failed sources show a single named image fallback, and source changes isolate stale events. Decorative states are hidden from assistive technology; neither Image nor its host becomes inert. Inside AspectRatio, omit size: the Image root and img fill the ratio frame and object-fit controls complete centered containment or cropping. Image also accepts opt-in frame="none": only none emits data-frame="none" on the existing .image root, removing its border and radius while preserving background, dimensions, fit, loading, empty/error text and accessible names. Omitted frame and frame="default" preserve the previous markup and appearance exactly; the host may provide the outer frame. No new tokens or existing-consumer migration; remove new usages before reverting the additive exports.
532
534
 
535
+ **Stat.** Tier C read-only single statistic: `label: string` and `value: ReactNode` are required; `unit?: ReactNode` and `placeholder?: ReactNode` are optional. Only null or undefined values use the placeholder (default `—`); 0, "0", and the empty string remain unchanged. Native HTMLDListElement attributes and className pass through the root. Structure is `<dl class="stat"><dt class="stat__label">label</dt><dd class="stat__value">value<span class="stat__unit">unit</span></dd></dl>`; omit the span when no unit is supplied. Label and unit use label-14 with text-muted; the value uses heading-20-strong with text-primary and tabular-nums. Unit text aligns on the baseline; long content wraps within narrow hosts. The placeholder is visual content without additional ARIA. Stat creates no headings, live regions or interactive elements; hosts own layout, Card surfaces, trends, links and loading. No new tokens or existing-consumer migration; remove Stat usages before reverting this additive export.
536
+
537
+ **FileInput.** Tier B native file selection. Root `div.file-input` contains `input.file-input__native.q1-sr-only` (`type="file"`, `tabIndex=-1`, `aria-hidden=true`), DS `Button.file-input__trigger` (`type="button"`) and `span.file-input__status` (`role="status"`). `name`, `accept`, `multiple`, `required`, `disabled`, `onChange` and `ref` act on the input; ref exposes files and clearing. `id` and all aria attributes act on the button for Label htmlFor / FormField controlId. `className` merges on the root. `buttonLabel?: ReactNode` defaults to 选择文件; variant defaults to secondary and size follows Button. Props derive from input props excluding type/value/defaultValue/children and replacing native numeric size with ButtonSize; only the listed attributes take effect. Status starts empty, shows a single filename or 已选 N 个文件, truncates on one line and retains all names in title. Native validation retains its default event handling and first-invalid-control order. When the invalid hidden input receives focus, its focus listener immediately marks aria-invalid=true and transfers focus to the visible button. Calling form.checkValidity() alone neither moves focus nor marks the button. Selecting files and form reset clear automatic invalid state; an explicit caller aria-invalid, including false, takes precedence. An enabled aria-invalid=true trigger uses the status-danger border at rest and on hover, and shadow-focus-input-danger for focus-visible; resting invalid adds no shadow and disabled styling is unchanged. Callers provide error copy through FormField. Native change and form reset synchronize status; reset waits for the native value to clear. After imperative ref clearing, dispatch change to synchronize display. Caller owns files, validation and upload; no reset prop, drag/drop, progress, preview or file list. Native button keyboard, accessible naming and shared focus ring satisfy Tier B; input is hidden from keyboard and accessibility tree. Existing consumers need no migration; remove new usages before reverting additive exports, CSS and route.
538
+
539
+ **Stepper.** Tier C read-only horizontal steps. Required `steps: StepperStep[]` contains `label: ReactNode` and optional `description?: ReactNode`; required `current: number` is zero-based. Negative current is treated as 0; earlier steps are complete, the matching step is current, later steps are upcoming. Current at or above length completes all steps without a current item. Native ol attributes pass through and className merges; children are supplied through steps. DOM: `ol.stepper > li.stepper__step`, with exactly one state class `stepper__step--complete`, `stepper__step--current` or `stepper__step--upcoming`; only the current li carries `aria-current="step"`. The decorative indicator is aria-hidden; complete uses CheckCircleFill with its default tone, current uses an emphasized numbered circle, upcoming a neutral outlined circle. Every item includes visually hidden state text: 已完成 / 当前步骤 / 未开始. Labels use label-14 and text-primary; descriptions use label-13 and text-muted. Connectors use text-primary after completed steps, border-strong otherwise. Existing tokens only; long labels wrap without overflow in hosts of 360px or less. No built-in navigation, error states, orientation, size or keyboard model. Existing consumers need no migration; remove new usages before reverting the additive exports and route.
540
+
533
541
  **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, RegionErrorState, ErrorMessage, EmptyState, Badge, Pill, CheckCircleFill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton, StatusPage, SkeletonIllustration, EmptyListIllustration.
534
542
 
535
543
  - 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.
536
- - 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: default `layout="center"` keeps the full empty view; `layout="inline"` is an icon and regular secondary title row inside a section whose heading and actions belong to the host, without description, actions, headingLevel, or illustration. Center accepts a decorative `illustration?: ReactNode` in place of icon; built-in `EmptyListIllustration` depicts a fully processed list or inbox, without an icon backplate. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
544
+ - 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: default `layout="center"` keeps the full empty view; `layout="inline"` is a small illustration or icon and regular secondary title row inside a section whose heading and actions belong to the host, without description, actions, or headingLevel. Inline accepts `illustration?: ReactNode`, using `--q1-space-16` (64px) for its width and preserving the artwork aspect ratio; illustration takes priority over icon with a development warning when both are supplied. Without illustration, existing icon-only and title-only calls stay unchanged. Center accepts a decorative `illustration?: ReactNode` in place of icon; when neither illustration nor icon is supplied, it renders `EmptyListIllustration variant="neutral"` inside the decorative `empty-state__illustration` slot. Explicit icons retain their slot with a transparent background in both themes. Both layouts accept `titleHidden?: boolean`: true adds `empty-state--title-hidden` to the existing root and `q1-sr-only` to the required title, preserving its p or h2–h6 tag, text and accessibility semantics. Center keeps illustration/icon, optional description and actions; inline keeps its small illustration or icon. The hidden title takes no layout space; remaining siblings keep the existing gap and margins, and an illustration/icon alone has no trailing margin. Omitted or false preserves the existing DOM, classes and styles exactly. Built-in `EmptyListIllustration` accepts `scene?: 'list' | 'search' | 'complete' | 'chart' | 'inventory' | 'orders' | 'product' | 'permission'` and the compatible `variant?: 'complete' | 'neutral'`. Explicit scene takes priority; without scene, neutral maps to list and all other calls default to complete. Accepted #830 intentionally changes both standalone default complete and EmptyState default list artwork and internal SVG structure; the outer decorative span, class and 96×104 viewBox remain. Complete draws its own decorative check without depending on CheckCircleFill. All scenes use opaque neutral bodies, token radii, canonical stroke width, and at most one brand-colored element (light primary-700, dark primary-900); geometry is identical across themes. Body contrast against the actual panel is at least 1.3:1. Both variants have no icon backplate. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
537
545
  - StatusPage defaults to a whole unavailable page with a caller-owned title, decorative SkeletonIllustration, plain-text description rendered as a paragraph, and actions. Its size="region" variant centers an icon or small illustration, title, description, and up to two actions inside a substantial content region, with a transparent background and content-driven height. ErrorCard covers a blocking compact failure; RegionErrorState is a compact retryable region message with its own status announcement; EmptyState means loading succeeded without content. StatusPage headingLevel accepts 1–6 (default 1); choose the next heading level under the host section. Existing omitted, 1 and 2 calls need no migration. StatusPage creates no live region; its host owns focus and announcements. StatusPage code is metadata only, never a message or spoken number.
538
546
  - 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. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
539
547
  - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
@@ -456,6 +456,7 @@ Four passes. Do not start at pass three.
456
456
  - Core tokens define source design values such as color, spacing, radius, and typography.
457
457
  - Semantic tokens describe product meaning such as action, surface, border, text, focus, and status.
458
458
  - Component mappings consume semantic tokens and define shared behavior for controls and documentation specimens.
459
+ - Input focus shadows use one 4px, 16% semantic-color halo: `--q1-shadow-focus-input` uses focus and `--q1-shadow-focus-input-danger` uses status-danger. Existing real borders retain their resting colors on focus. The shared `--q1-shadow-focus` and `--q1-shadow-focus-danger` keep their two layers. Resting danger shadows that directly consume either input token follow that token; other resting field error shadows remain unchanged.
459
460
 
460
461
  ## Usage
461
462
 
@@ -516,6 +517,7 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
516
517
  - 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.
517
518
  - DestructiveActionModal is the required shape for irreversible actions. Do not rebuild that confirmation from a plain Modal. It is also where high-risk, irreversible, or data-overwriting consequences are stated; do not move that warning into option second lines.
518
519
  - Sheet slides in from an edge with a focus trap; opt-in `mobileFullscreen` fills narrow viewports with safe-area padding and no outer inset or radius, while default floating spacing remains unchanged; Drawer is the wider panel form. Menu is anchored and flips on collision; ContextMenu and DotsMenu share its keyboard engine. DotsMenu accepts contextual `label` (default 更多操作) for trigger and menu, plus `{type:'separator'}` entries using the shared Menu separator; roving focus and typeahead skip separators and disabled items.
520
+ - Menu, DotsMenu and ContextMenu item entries use the existing `danger: true` (no separate tone). Dangerous text and icons use `--q1-color-status-danger-text`, with no border or resting fill and no inverted foreground. Hover and visible focus inherit the neutral `--q1-color-bg-hover`; dark-theme danger active also uses that neutral hover background to keep both themes at AA. Ordinary active retains its text-primary 10% transparent mix. Disabled or locked dangerous entries use secondary text/icon color and have no hover or active feedback.
519
521
  - Tooltip carries a short label for a control. Anything a reader must read to decide belongs in the page or in a ContextCard.
520
522
 
521
523
  **Data and evidence.** Table, Pagination, DocsPagination, ShowMore, Chart, Gauge, Progress, JsonView, Entity, Description, RelativeTimeCard.
@@ -530,10 +532,16 @@ CheckCircleFill accepts `tone?: 'default' | 'success'` (default `default`); succ
530
532
 
531
533
  **Images and proportions.** AspectRatio requires a finite positive width/height ratio (invalid values render as 1 with one development warning) and only reserves layout space. Image requires alt (empty for decoration), defaults to contain and eager, accepts optional src, fit, loading, sm/md/lg square sizes (32/40/48), onError and root className; omitted size follows host width and natural image ratio. Loading keeps img mounted under a decorative Skeleton; missing and failed sources show a single named image fallback, and source changes isolate stale events. Decorative states are hidden from assistive technology; neither Image nor its host becomes inert. Inside AspectRatio, omit size: the Image root and img fill the ratio frame and object-fit controls complete centered containment or cropping. Image also accepts opt-in frame="none": only none emits data-frame="none" on the existing .image root, removing its border and radius while preserving background, dimensions, fit, loading, empty/error text and accessible names. Omitted frame and frame="default" preserve the previous markup and appearance exactly; the host may provide the outer frame. No new tokens or existing-consumer migration; remove new usages before reverting the additive exports.
532
534
 
535
+ **Stat.** Tier C read-only single statistic: `label: string` and `value: ReactNode` are required; `unit?: ReactNode` and `placeholder?: ReactNode` are optional. Only null or undefined values use the placeholder (default `—`); 0, "0", and the empty string remain unchanged. Native HTMLDListElement attributes and className pass through the root. Structure is `<dl class="stat"><dt class="stat__label">label</dt><dd class="stat__value">value<span class="stat__unit">unit</span></dd></dl>`; omit the span when no unit is supplied. Label and unit use label-14 with text-muted; the value uses heading-20-strong with text-primary and tabular-nums. Unit text aligns on the baseline; long content wraps within narrow hosts. The placeholder is visual content without additional ARIA. Stat creates no headings, live regions or interactive elements; hosts own layout, Card surfaces, trends, links and loading. No new tokens or existing-consumer migration; remove Stat usages before reverting this additive export.
536
+
537
+ **FileInput.** Tier B native file selection. Root `div.file-input` contains `input.file-input__native.q1-sr-only` (`type="file"`, `tabIndex=-1`, `aria-hidden=true`), DS `Button.file-input__trigger` (`type="button"`) and `span.file-input__status` (`role="status"`). `name`, `accept`, `multiple`, `required`, `disabled`, `onChange` and `ref` act on the input; ref exposes files and clearing. `id` and all aria attributes act on the button for Label htmlFor / FormField controlId. `className` merges on the root. `buttonLabel?: ReactNode` defaults to 选择文件; variant defaults to secondary and size follows Button. Props derive from input props excluding type/value/defaultValue/children and replacing native numeric size with ButtonSize; only the listed attributes take effect. Status starts empty, shows a single filename or 已选 N 个文件, truncates on one line and retains all names in title. Native validation retains its default event handling and first-invalid-control order. When the invalid hidden input receives focus, its focus listener immediately marks aria-invalid=true and transfers focus to the visible button. Calling form.checkValidity() alone neither moves focus nor marks the button. Selecting files and form reset clear automatic invalid state; an explicit caller aria-invalid, including false, takes precedence. An enabled aria-invalid=true trigger uses the status-danger border at rest and on hover, and shadow-focus-input-danger for focus-visible; resting invalid adds no shadow and disabled styling is unchanged. Callers provide error copy through FormField. Native change and form reset synchronize status; reset waits for the native value to clear. After imperative ref clearing, dispatch change to synchronize display. Caller owns files, validation and upload; no reset prop, drag/drop, progress, preview or file list. Native button keyboard, accessible naming and shared focus ring satisfy Tier B; input is hidden from keyboard and accessibility tree. Existing consumers need no migration; remove new usages before reverting additive exports, CSS and route.
538
+
539
+ **Stepper.** Tier C read-only horizontal steps. Required `steps: StepperStep[]` contains `label: ReactNode` and optional `description?: ReactNode`; required `current: number` is zero-based. Negative current is treated as 0; earlier steps are complete, the matching step is current, later steps are upcoming. Current at or above length completes all steps without a current item. Native ol attributes pass through and className merges; children are supplied through steps. DOM: `ol.stepper > li.stepper__step`, with exactly one state class `stepper__step--complete`, `stepper__step--current` or `stepper__step--upcoming`; only the current li carries `aria-current="step"`. The decorative indicator is aria-hidden; complete uses CheckCircleFill with its default tone, current uses an emphasized numbered circle, upcoming a neutral outlined circle. Every item includes visually hidden state text: 已完成 / 当前步骤 / 未开始. Labels use label-14 and text-primary; descriptions use label-13 and text-muted. Connectors use text-primary after completed steps, border-strong otherwise. Existing tokens only; long labels wrap without overflow in hosts of 360px or less. No built-in navigation, error states, orientation, size or keyboard model. Existing consumers need no migration; remove new usages before reverting the additive exports and route.
540
+
533
541
  **Status and feedback.** Banner, ProjectBanner, Note, ErrorCard, RegionErrorState, ErrorMessage, EmptyState, Badge, Pill, CheckCircleFill, StatusDot, ToastProvider, useToast, Feedback, PageFeedback, Spinner, LoadingDots, Skeleton, StatusPage, SkeletonIllustration, EmptyListIllustration.
534
542
 
535
543
  - 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.
536
- - 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: default `layout="center"` keeps the full empty view; `layout="inline"` is an icon and regular secondary title row inside a section whose heading and actions belong to the host, without description, actions, headingLevel, or illustration. Center accepts a decorative `illustration?: ReactNode` in place of icon; built-in `EmptyListIllustration` depicts a fully processed list or inbox, without an icon backplate. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
544
+ - 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: default `layout="center"` keeps the full empty view; `layout="inline"` is a small illustration or icon and regular secondary title row inside a section whose heading and actions belong to the host, without description, actions, or headingLevel. Inline accepts `illustration?: ReactNode`, using `--q1-space-16` (64px) for its width and preserving the artwork aspect ratio; illustration takes priority over icon with a development warning when both are supplied. Without illustration, existing icon-only and title-only calls stay unchanged. Center accepts a decorative `illustration?: ReactNode` in place of icon; when neither illustration nor icon is supplied, it renders `EmptyListIllustration variant="neutral"` inside the decorative `empty-state__illustration` slot. Explicit icons retain their slot with a transparent background in both themes. Both layouts accept `titleHidden?: boolean`: true adds `empty-state--title-hidden` to the existing root and `q1-sr-only` to the required title, preserving its p or h2–h6 tag, text and accessibility semantics. Center keeps illustration/icon, optional description and actions; inline keeps its small illustration or icon. The hidden title takes no layout space; remaining siblings keep the existing gap and margins, and an illustration/icon alone has no trailing margin. Omitted or false preserves the existing DOM, classes and styles exactly. Built-in `EmptyListIllustration` accepts `scene?: 'list' | 'search' | 'complete' | 'chart' | 'inventory' | 'orders' | 'product' | 'permission'` and the compatible `variant?: 'complete' | 'neutral'`. Explicit scene takes priority; without scene, neutral maps to list and all other calls default to complete. Accepted #830 intentionally changes both standalone default complete and EmptyState default list artwork and internal SVG structure; the outer decorative span, class and 96×104 viewBox remain. Complete draws its own decorative check without depending on CheckCircleFill. All scenes use opaque neutral bodies, token radii, canonical stroke width, and at most one brand-colored element (light primary-700, dark primary-900); geometry is identical across themes. Body contrast against the actual panel is at least 1.3:1. Both variants have no icon backplate. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
537
545
  - StatusPage defaults to a whole unavailable page with a caller-owned title, decorative SkeletonIllustration, plain-text description rendered as a paragraph, and actions. Its size="region" variant centers an icon or small illustration, title, description, and up to two actions inside a substantial content region, with a transparent background and content-driven height. ErrorCard covers a blocking compact failure; RegionErrorState is a compact retryable region message with its own status announcement; EmptyState means loading succeeded without content. StatusPage headingLevel accepts 1–6 (default 1); choose the next heading level under the host section. Existing omitted, 1 and 2 calls need no migration. StatusPage creates no live region; its host owns focus and announcements. StatusPage code is metadata only, never a message or spoken number.
538
546
  - 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. `layout="strip"` adds a full-width neutral strip for list failures and load-more rows, with retry always inside its right edge. See `implementation-map.md` for the retry and announcement boundary.
539
547
  - Toast is for non-blocking confirmation of something that already happened. It is not where an error the reader must act on belongs.
@@ -154,3 +154,7 @@ The announcement region wraps only the icon, the `q1-sr-only` 「错误:」 pr
154
154
  ## ThemeSwitcher selection appearance
155
155
 
156
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.
157
+
158
+ ## EmptyState title visibility
159
+
160
+ Accepted #568: `EmptyState` supports `titleHidden?: boolean` in both center and inline layouts. Both layouts accept `titleHidden?: boolean`: true adds `empty-state--title-hidden` to the existing root and `q1-sr-only` to the required title, preserving its p or h2–h6 tag, text and accessibility semantics. Center keeps illustration/icon, optional description and actions; inline keeps its small illustration or icon. Inline accepts `illustration?: ReactNode` with width `--q1-space-16` (64px), preserving the artwork aspect ratio; illustration takes priority over icon and supplying both emits a development warning. Description, actions and headingLevel remain center-only. Existing inline icon-only and title-only calls retain their exact rendering. The hidden title takes no layout space; remaining siblings keep the existing gap and margins, and an illustration/icon alone has no trailing margin. Omitted or false preserves the existing DOM, classes and styles exactly.
@@ -240,3 +240,11 @@ Component pages follow one skeleton so a reader who has used one page can find t
240
240
  - This file is the machine-readable mirror of the public 界面准则 page (`/guidelines`); keep them in sync.
241
241
  - `AGENTS.md` at the repository root constrains agent generation and modification behavior.
242
242
  - `npm run check` (build + shell contract + browser contract) is the merge gate for any interface change.
243
+
244
+ ### Empty illustrations
245
+
246
+ Accepted #830: decorative empty illustrations share a 96×104 viewBox, radius-3 outer frames, radius-1 clips, and ICON_STROKE_WIDTH round strokes. All scene geometry, including half the computed stroke width around each element, stays inside the viewBox. Inventory integrates the bell into the shelf; permission centers a complete padlock on the panel; product uses a front-facing label. No detached brand decorations. The family specimen uses one site-themed card per scene, four columns on wide screens and two on narrow screens. Opaque neutral bodies must contrast at least 1.3:1 against the actual card background. Use no gradients, shadows or particles; at most one element uses primary-700 in light and primary-900 in dark, with identical geometry. Scene selects list, search, complete, chart, inventory, orders, product or permission. Scene takes precedence over the compatible variant; absent scene, neutral maps to list and all other calls to complete. Standalone complete and EmptyState list defaults intentionally receive new artwork and internal SVG structure. Keep the decorative outer span, aria-hidden=true, non-focusable SVG and text-owned state meaning. Complete owns its check; CheckCircleFill semantics are unchanged.
247
+
248
+ ### EmptyState illustration-only presentation
249
+
250
+ Accepted #568: `titleHidden` visually hides the required title using `q1-sr-only`, while retaining its original paragraph or heading semantics and accessible text. Illustration/icon remains decorative; description and actions are independent optional center content. Inline accepts a small decorative `illustration?: ReactNode` at `--q1-space-16` (64px) width, preserving its aspect ratio; illustration takes priority over icon, with a development warning when both are supplied. Inline description, actions and headingLevel remain unsupported. Omitted or false leaves the default presentation unchanged; existing inline calls without illustration retain their rendering.
@@ -111,9 +111,9 @@ Examples:
111
111
 
112
112
  Owner-approved exception (2026-10-05): ErrorMessage, field errors, and ErrorCard titles use `--q1-color-red-900`; other status text retains its existing semantic text token.
113
113
 
114
- `--q1-color-focus` is neutral black in light mode and white in dark mode. `--q1-shadow-focus` and `--q1-shadow-focus-input` use a gapless 1px ring at 34% plus a 4px halo at 16%. Buttons and inputs consume this recipe; input focus preserves the resting border, without an active-border swap or inset stroke. `--q1-input-border-focus` remains an opt-in override and defaults to the resting input border. Invalid inputs retain their danger border; `--q1-shadow-focus-input-danger` and `--q1-shadow-focus-danger` use the same 1px / 4px geometry with danger color at 34% / 16%. Text links retain the 3px neutral focus underline. Forced colors and high contrast retain visible system outlines.
114
+ `--q1-color-focus` is neutral black in light mode and white in dark mode. The shared `--q1-shadow-focus` and `--q1-shadow-focus-danger` remain unchanged: both keep a gapless 1px ring at 34% plus a 4px halo at 16%, using focus and danger color respectively. Inputs already have a real border, so `--q1-shadow-focus-input` omits the 1px shadow and uses only the 4px halo at 16%; focus preserves the resting border color, without an active-border swap or inset stroke. `--q1-input-border-focus` remains an opt-in override and defaults to the resting input border. Invalid inputs retain their danger border; `--q1-shadow-focus-input-danger` uses the same single 4px halo at 16% in danger color. Text links retain the 3px neutral focus underline. Forced colors and high contrast retain visible system outlines.
115
115
 
116
- Migration: `--q1-color-bg-panel` remains `#ffffff` in light mode and changes from `#0a0a0a` to `#1a1a1a` in dark mode. The dark canvas, raised surface, and overlay now use `#121212`, `#222222`, and `#2b2b2b`. Downstream overrides that manually raised panel or overlay backgrounds should be reviewed. The 2026-10-05 owner-approved focus change replaces the brand ring and inset input stroke with the neutral 1px / 4px recipe and preserves resting input borders. Consumers with custom focus visuals should update their screenshots and forced-colors fallback. The unused `--q1-shadow-card` name is removed before release.
116
+ Migration: `--q1-color-bg-panel` remains `#ffffff` in light mode and changes from `#0a0a0a` to `#1a1a1a` in dark mode. The dark canvas, raised surface, and overlay now use `#121212`, `#222222`, and `#2b2b2b`. Downstream overrides that manually raised panel or overlay backgrounds should be reviewed. The 2026-10-05 owner-approved focus change replaced the brand ring and inset input stroke with the neutral 1px / 4px recipe. Issue #835 revises its input portion to one 4px halo at 16%, preserving real borders and their resting colors; the shared focus recipe stays unchanged. Consumers with custom focus visuals should update their screenshots and forced-colors fallback. The unused `--q1-shadow-card` name is removed before release.
117
117
 
118
118
  Owner-approved contrast roles (2026-10-07): `--q1-color-border-control` is the resting form-control boundary (light uses `gray-700`, about 3.23:1 on white, since the 2026-10-13 owner decision; dark uses `gray-600`); controls with a strong light border retain that value and opt into this role only in dark mode. Decorative borders retain `border` / `border-strong`. Dark `--q1-color-border-strong` moves from `gray-400` to `gray-500` (2026-10-13) so selected and emphasized outlines stay distinguishable from the default `border`. `--q1-color-accent-graphic` is for brand-colored non-text graphics (light `primary-700`, dark `primary-900`), independent of button action colors. Slider dark unfilled tracks use `gray-500`.
119
119
 
@@ -78,6 +78,12 @@
78
78
  "lg"
79
79
  ]
80
80
  },
81
+ "EmptyListIllustration": {
82
+ "variant": [
83
+ "complete",
84
+ "neutral"
85
+ ]
86
+ },
81
87
  "ErrorMessage": {
82
88
  "size": [
83
89
  "sm",
@@ -85,6 +91,20 @@
85
91
  "lg"
86
92
  ]
87
93
  },
94
+ "FileInput": {
95
+ "variant": [
96
+ "primary",
97
+ "secondary",
98
+ "ghost",
99
+ "warning",
100
+ "danger"
101
+ ],
102
+ "size": [
103
+ "sm",
104
+ "md",
105
+ "lg"
106
+ ]
107
+ },
88
108
  "FilterChip": {
89
109
  "size": [
90
110
  "sm",
@@ -1099,12 +1099,21 @@ export declare function Drawer({ open, onClose, title, children, footer, height,
1099
1099
  height?: number;
1100
1100
  }): ReactNode;
1101
1101
 
1102
- /** 列表或收件箱内容已全部处理的装饰插画;状态含义由相邻文字表达。 */
1103
- export declare function EmptyListIllustration(): JSX.Element;
1102
+ /** 场景空态的装饰插画;状态含义由相邻文字表达。 */
1103
+ export declare function EmptyListIllustration({ scene, variant }?: EmptyListIllustrationProps): JSX.Element;
1104
1104
 
1105
- export declare function EmptyState({ icon, illustration, title, description, actions, headingLevel, layout, }: {
1105
+ export declare type EmptyListIllustrationProps = {
1106
+ scene?: EmptyListIllustrationScene;
1107
+ variant?: 'complete' | 'neutral';
1108
+ };
1109
+
1110
+ export declare type EmptyListIllustrationScene = 'list' | 'search' | 'complete' | 'chart' | 'inventory' | 'orders' | 'product' | 'permission';
1111
+
1112
+ export declare function EmptyState({ icon, illustration, title, titleHidden, description, actions, headingLevel, layout, }: {
1106
1113
  icon?: ReactNode;
1107
1114
  title: string;
1115
+ /** 仅视觉隐藏标题,保留原标签与可访问文本。 */
1116
+ titleHidden?: boolean;
1108
1117
  } & ({
1109
1118
  layout?: 'center';
1110
1119
  illustration?: ReactNode;
@@ -1115,7 +1124,7 @@ export declare function EmptyState({ icon, illustration, title, description, act
1115
1124
  headingLevel?: 2 | 3 | 4 | 5 | 6;
1116
1125
  } | {
1117
1126
  layout: 'inline';
1118
- illustration?: never;
1127
+ illustration?: ReactNode;
1119
1128
  description?: never;
1120
1129
  actions?: never;
1121
1130
  headingLevel?: never;
@@ -1223,6 +1232,14 @@ declare type FieldShellProps = {
1223
1232
 
1224
1233
  export declare type FieldSize = 'xs' | 'sm' | 'md' | 'lg';
1225
1234
 
1235
+ export declare function FileInput({ name, accept, multiple, required, disabled, onChange, ref, id, className, buttonLabel, variant, size, ...rest }: FileInputProps): JSX.Element;
1236
+
1237
+ export declare type FileInputProps = Omit<ComponentPropsWithRef<'input'>, 'type' | 'value' | 'defaultValue' | 'children' | 'size'> & {
1238
+ buttonLabel?: ReactNode;
1239
+ variant?: ButtonVariant;
1240
+ size?: ButtonSize;
1241
+ };
1242
+
1226
1243
  export declare function FileTree({ nodes, label, defaultExpanded, }: {
1227
1244
  nodes: FileTreeNode[];
1228
1245
  label?: string;
@@ -2528,6 +2545,15 @@ export declare type SplitButtonEntry = {
2528
2545
  onSelect?: () => void;
2529
2546
  };
2530
2547
 
2548
+ export declare function Stat({ label, value, unit, placeholder, className, ...rest }: StatProps): JSX.Element;
2549
+
2550
+ export declare type StatProps = Omit<HTMLAttributes<HTMLDListElement>, 'children'> & {
2551
+ label: string;
2552
+ value: ReactNode;
2553
+ unit?: ReactNode;
2554
+ placeholder?: ReactNode;
2555
+ };
2556
+
2531
2557
  export declare function StatusDot({ tone, label, showLabel, titlePrefix, }: {
2532
2558
  tone: StatusDotTone;
2533
2559
  label: string;
@@ -2553,6 +2579,18 @@ export declare type StatusPageProps = {
2553
2579
  headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
2554
2580
  };
2555
2581
 
2582
+ export declare function Stepper({ steps, current, className, ...rest }: StepperProps): JSX.Element;
2583
+
2584
+ export declare type StepperProps = Omit<ComponentPropsWithoutRef<'ol'>, 'children'> & {
2585
+ steps: StepperStep[];
2586
+ current: number;
2587
+ };
2588
+
2589
+ export declare type StepperStep = {
2590
+ label: ReactNode;
2591
+ description?: ReactNode;
2592
+ };
2593
+
2556
2594
  export declare function Switch({ options, ariaLabel, name, defaultValue, value, onChange, size, variant, fullWidth, iconOnly, }: {
2557
2595
  options: SwitchOption[];
2558
2596
  ariaLabel: string;