@q1travel/design-system 0.28.1 → 0.29.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.
|
@@ -105,7 +105,7 @@ Timeline / TimelineItem (ol/li, 8px token dot or Avatar), Marker (quiet actor/ti
|
|
|
105
105
|
|
|
106
106
|
## ToggleGroup selection ownership
|
|
107
107
|
|
|
108
|
-
Accepted 2026-10-07: public `ToggleGroup`
|
|
108
|
+
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.
|
|
109
109
|
|
|
110
110
|
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 without a selected ring, 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.
|
|
111
111
|
## Checkbox task completion circle
|
|
@@ -113,7 +113,7 @@ Native buttons expose `aria-pressed` within `role="group"`. Roving tabindex and
|
|
|
113
113
|
- 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.
|
|
114
114
|
- Verification: `scripts/check-checkbox-circle-contract.test.mjs`, `scripts/fixtures/checkbox-circle-api-consumer.tsx`, `scripts/lib/checkbox-circle-contrast.mjs`, `scripts/lib/check-checkbox-circle-browser.mjs`. Package gate covers API/default/native markup/readonly semantics/token existence and both-theme contrast; browser gate covers native toggles, focus without preview, readonly, tone fill, reduced motion and coarse hit targets.
|
|
115
115
|
## Chat messages
|
|
116
|
-
Bubble is separate from Message: chat has optional identity and direction, while Message retains its required author, comment attachments and receipt API unchanged. MessageScroller is separate from Scroller: keyed prepend anchors and message following need ownership of the viewport; the general Scroller API remains unchanged.
|
|
116
|
+
Bubble is separate from Message: chat has optional identity and direction, while Message retains its required author, comment attachments and receipt API unchanged. MessageScroller is separate from Scroller: keyed prepend anchors and message following need ownership of the viewport; the general Scroller API remains unchanged. Quiet chat surfaces use existing q1travel panel/selected backgrounds, borders, spacing, radii and Button. No reference CSS, motion background or private tokens are shipped.
|
|
117
117
|
The consumer mapping was checked against downstream chat-style implementations (read-only, no downstream edits).
|
|
118
118
|
| Web capability | Package support / caller boundary |
|
|
119
119
|
| --- | --- |
|
|
@@ -26,7 +26,7 @@ Adding a clause therefore means answering two questions before the wording: who
|
|
|
26
26
|
- [both] MUST keep mobile control targets at least 44px where touch is expected.
|
|
27
27
|
- [both] MUST use `--q1-touch-target-min` (44px) for the shared centered invisible child-span hit region of Button (all sizes, including compact consumer overrides and icon-only), CopyButton, Menu/DotsMenu triggers, Description info/hint buttons, and the independent FilterChip picker/clear buttons only under `(pointer: coarse), (any-pointer: coarse)`. Preserve every existing coarse-pointer visual-size rule. Only axes still below the minimum receive extra hit area; existing 44px targets do not expand outside their visual frame. The region covers the complete border box: canonical Button/CopyButton/Menu/DotsMenu borders are 1px, while Description borders are none. Consumers overriding a control border width must also set its private `--touch-target-border-width` to the actual per-side width (including zero for `border: none`); asymmetric borders need their own hit-region sizing. FilterChip keeps its default sm32 visual height and existing gaps/margins; its picker and clear hit regions must not overlap. Menu item rows use a real `min-height: var(--q1-touch-target-min)` under the same coarse-pointer query so neighboring rows remain independent. Focus rings stay on the visual control; child hit-region events activate that native control without replacing its pseudo-elements or overflow rules.
|
|
28
28
|
- [both] MUST leave enough space for expanded targets: dense neighbors must not overlap each other's visual frames. Consumers must reserve the expansion on each side and avoid clipping ancestors; if this is impossible, regroup controls or use larger visual controls. The shared region cannot infer neighbor gaps or escape control/ancestor clipping; clipped compact controls must use a visual size of at least 44px.
|
|
29
|
-
- [both] SHOULD NOT ship dense measured 24–32px controls to coarse pointers without enlargement:
|
|
29
|
+
- [both] SHOULD NOT ship dense measured 24–32px controls to coarse pointers without enlargement: q1travel uses a 44×44 CSS-pixel mobile usability target, informed by WCAG 2.5.5.
|
|
30
30
|
- [both] NEVER disable browser zoom.
|
|
31
31
|
- [both] MUST avoid dead zones: if a region looks clickable, the clickable element must cover it.
|
|
32
32
|
- [both] MUST show a loading indicator on in-flight buttons while keeping the original label.
|
|
@@ -191,7 +191,7 @@ Theming owns tokens and `color-scheme`; the interaction-contrast rule lives in D
|
|
|
191
191
|
- [both] MUST separate Chinese characters from adjacent Latin letters or digits with a half-width space in authored prose; code contents, URLs, literal UI quotations, and code samples are excluded.
|
|
192
192
|
- [both] MUST use full-width `,。:;` immediately after Chinese characters in authored Chinese sentences; code contents, URLs, literal UI quotations, and code samples are excluded.
|
|
193
193
|
- [both] SHOULD use full-width `,。:;` throughout Chinese sentences, including after Latin words or inline code; identifying sentence language across mixed JSX nodes remains an editorial review item.
|
|
194
|
-
- [both] SHOULD follow common Chinese technical-writing practice for mixed-language wording
|
|
194
|
+
- [both] SHOULD follow common Chinese technical-writing practice for mixed-language wording. Semantic choices and text whose rendered role cannot be reliably inferred remain editorial review items rather than machine-enforced requirements.
|
|
195
195
|
- [both] SHOULD prefer short action-oriented labels.
|
|
196
196
|
- [both] MUST use active voice.
|
|
197
197
|
- [both] MUST use unambiguous action labels (「保存 API Key」, not 「继续」).
|
|
@@ -213,7 +213,7 @@ These copy clauses keep reader address and empty states consistent with a formal
|
|
|
213
213
|
|
|
214
214
|
### Component Naming
|
|
215
215
|
|
|
216
|
-
- [repo] SHOULD use the industry-common component name when one exists
|
|
216
|
+
- [repo] SHOULD use the industry-common component name when one exists. Invent a name only when no established name fits. Name what the component is, not how it is built.
|
|
217
217
|
- [repo] MUST switch public names directly to the new export without deprecated aliases or old documentation route redirects; record the removal as a breaking change in CHANGELOG.
|
|
218
218
|
|
|
219
219
|
### Component Documentation Pages
|
|
@@ -1578,7 +1578,7 @@ export declare type MultiSelectProps = {
|
|
|
1578
1578
|
preserveSelectionOrder?: boolean;
|
|
1579
1579
|
};
|
|
1580
1580
|
|
|
1581
|
-
export declare function Navigation({ className, orientation, ...props }: NavigationProps): JSX.Element;
|
|
1581
|
+
export declare function Navigation({ className, orientation, variant, ...props }: NavigationProps): JSX.Element;
|
|
1582
1582
|
|
|
1583
1583
|
export declare function NavigationGroup({ label, icon, expanded, defaultExpanded, onExpandedChange, children, }: NavigationGroupProps): JSX.Element;
|
|
1584
1584
|
|
|
@@ -1617,6 +1617,8 @@ export declare type NavigationItemProps = NavigationItemAsLink | NavigationItemA
|
|
|
1617
1617
|
export declare type NavigationProps = HTMLAttributes<HTMLElement> & {
|
|
1618
1618
|
'aria-label': string;
|
|
1619
1619
|
orientation?: 'vertical' | 'horizontal';
|
|
1620
|
+
/** Line styling applies only to horizontal navigation; vertical navigation ignores it. */
|
|
1621
|
+
variant?: 'default' | 'line';
|
|
1620
1622
|
};
|
|
1621
1623
|
|
|
1622
1624
|
export declare function NavigationSection({ label, action, children, className, ...props }: NavigationSectionProps): JSX.Element;
|
|
@@ -5987,16 +5987,17 @@ function cu({
|
|
|
5987
5987
|
) : null
|
|
5988
5988
|
] });
|
|
5989
5989
|
}
|
|
5990
|
-
function lu({ className: e, orientation: t = "vertical",
|
|
5990
|
+
function lu({ className: e, orientation: t = "vertical", variant: r = "default", ...o }) {
|
|
5991
5991
|
return /* @__PURE__ */ n(
|
|
5992
5992
|
"nav",
|
|
5993
5993
|
{
|
|
5994
5994
|
className: se(
|
|
5995
5995
|
"q1-navigation",
|
|
5996
5996
|
t === "horizontal" && "q1-navigation--horizontal",
|
|
5997
|
+
t === "horizontal" && r === "line" && "q1-navigation--line",
|
|
5997
5998
|
e
|
|
5998
5999
|
),
|
|
5999
|
-
...
|
|
6000
|
+
...o
|
|
6000
6001
|
}
|
|
6001
6002
|
);
|
|
6002
6003
|
}
|
package/dist-pkg/style.css
CHANGED
|
@@ -9845,7 +9845,7 @@ button.q1-navigation__item:not(:disabled),
|
|
|
9845
9845
|
}
|
|
9846
9846
|
|
|
9847
9847
|
.q1-navigation__item--active {
|
|
9848
|
-
background: var(--q1-color-
|
|
9848
|
+
background: var(--q1-color-gray-alpha-200);
|
|
9849
9849
|
color: var(--q1-color-text-primary);
|
|
9850
9850
|
font-weight: var(--q1-font-weight-medium);
|
|
9851
9851
|
}
|
|
@@ -10034,15 +10034,20 @@ a.q1-navigation__item[aria-disabled='true'] {
|
|
|
10034
10034
|
background-color var(--q1-duration-press) var(--q1-ease-state);
|
|
10035
10035
|
}
|
|
10036
10036
|
|
|
10037
|
+
/* 滚动容器内沿用 Table 的内嵌焦点环,避免四边被 overflow 裁切。 */
|
|
10038
|
+
.q1-navigation--horizontal .q1-navigation__item:focus-visible {
|
|
10039
|
+
box-shadow: inset 0 0 0 calc(var(--q1-space-1) / 2) var(--q1-color-focus);
|
|
10040
|
+
}
|
|
10041
|
+
|
|
10037
10042
|
.q1-navigation--horizontal .q1-navigation__item--active {
|
|
10038
|
-
background: var(--q1-color-
|
|
10043
|
+
background: var(--q1-color-gray-alpha-200);
|
|
10039
10044
|
color: var(--q1-color-text-primary);
|
|
10040
10045
|
}
|
|
10041
10046
|
|
|
10042
10047
|
@media (hover: hover) and (pointer: fine) {
|
|
10043
|
-
.q1-navigation__item:hover:not(:active):not([aria-disabled='true']):not(:disabled),
|
|
10048
|
+
.q1-navigation__item:hover:not(.q1-navigation__item--active):not(:active):not([aria-disabled='true']):not(:disabled),
|
|
10044
10049
|
.q1-navigation__group-trigger:hover:not(:active) {
|
|
10045
|
-
background: var(--q1-color-bg-
|
|
10050
|
+
background: var(--q1-color-bg-hover);
|
|
10046
10051
|
}
|
|
10047
10052
|
|
|
10048
10053
|
.q1-navigation__item-row:hover .q1-navigation__item-action {
|
|
@@ -10053,11 +10058,12 @@ a.q1-navigation__item[aria-disabled='true'] {
|
|
|
10053
10058
|
|
|
10054
10059
|
.q1-navigation--horizontal .q1-navigation__item:hover:not(:active):not([aria-disabled='true']):not(:disabled) {
|
|
10055
10060
|
color: var(--q1-color-text-primary);
|
|
10056
|
-
background: var(--q1-color-bg-
|
|
10061
|
+
background: var(--q1-color-bg-hover);
|
|
10057
10062
|
}
|
|
10058
10063
|
|
|
10059
10064
|
.q1-navigation--horizontal .q1-navigation__item--active:hover:not(:active):not([aria-disabled='true']):not(:disabled) {
|
|
10060
10065
|
color: var(--q1-color-text-primary);
|
|
10066
|
+
background: var(--q1-color-gray-alpha-200);
|
|
10061
10067
|
}
|
|
10062
10068
|
}
|
|
10063
10069
|
|
|
@@ -11266,6 +11272,71 @@ a.q1-navigation__item[aria-disabled='true'] {
|
|
|
11266
11272
|
display: none;
|
|
11267
11273
|
}
|
|
11268
11274
|
|
|
11275
|
+
.q1-navigation--line {
|
|
11276
|
+
--q1-navigation-row-min-height: calc(var(--q1-space-10) + var(--q1-space-1));
|
|
11277
|
+
|
|
11278
|
+
scrollbar-width: none;
|
|
11279
|
+
align-items: stretch;
|
|
11280
|
+
gap: var(--q1-space-6);
|
|
11281
|
+
/* 基线不占布局空间,绘于子项下方;滚动时下划线仍压在同一底边。 */
|
|
11282
|
+
box-shadow: inset 0 calc(var(--q1-space-1) / -4) 0 var(--q1-color-border);
|
|
11283
|
+
}
|
|
11284
|
+
|
|
11285
|
+
.q1-navigation--line::-webkit-scrollbar {
|
|
11286
|
+
display: none;
|
|
11287
|
+
}
|
|
11288
|
+
|
|
11289
|
+
.q1-navigation--line .q1-navigation__item {
|
|
11290
|
+
position: relative;
|
|
11291
|
+
border-bottom: calc(var(--q1-space-1) / 2) solid transparent;
|
|
11292
|
+
border-radius: 0;
|
|
11293
|
+
padding: var(--q1-space-3) var(--q1-space-2);
|
|
11294
|
+
background: none;
|
|
11295
|
+
color: var(--q1-color-text-secondary);
|
|
11296
|
+
font-weight: var(--q1-font-weight-regular);
|
|
11297
|
+
transition: color var(--q1-duration-press) var(--q1-ease-state);
|
|
11298
|
+
}
|
|
11299
|
+
|
|
11300
|
+
/* 焦点环仅纵向收进项内,底边与选中下划线保留一档间距。 */
|
|
11301
|
+
.q1-navigation--horizontal.q1-navigation--line .q1-navigation__item:focus-visible {
|
|
11302
|
+
box-shadow: none;
|
|
11303
|
+
}
|
|
11304
|
+
|
|
11305
|
+
.q1-navigation--line .q1-navigation__item:focus-visible::after {
|
|
11306
|
+
content: '';
|
|
11307
|
+
position: absolute;
|
|
11308
|
+
inset: var(--q1-space-1) 0;
|
|
11309
|
+
border-radius: var(--q1-radius-2);
|
|
11310
|
+
box-shadow: inset 0 0 0 calc(var(--q1-space-1) / 2) var(--q1-color-focus);
|
|
11311
|
+
pointer-events: none;
|
|
11312
|
+
}
|
|
11313
|
+
|
|
11314
|
+
.q1-navigation--line .q1-navigation__item:is(.q1-navigation__item--active, [aria-current='page']) {
|
|
11315
|
+
border-bottom-color: var(--q1-color-text-primary);
|
|
11316
|
+
color: var(--q1-color-text-primary);
|
|
11317
|
+
}
|
|
11318
|
+
|
|
11319
|
+
.q1-navigation--line .q1-navigation__item--disabled {
|
|
11320
|
+
color: var(--q1-color-text-muted);
|
|
11321
|
+
}
|
|
11322
|
+
|
|
11323
|
+
@media (hover: hover) and (pointer: fine) {
|
|
11324
|
+
.q1-navigation--horizontal.q1-navigation--line .q1-navigation__item:hover:not(:active):not([aria-disabled='true']):not(:disabled) {
|
|
11325
|
+
background: none;
|
|
11326
|
+
color: var(--q1-color-text-primary);
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
|
|
11330
|
+
.q1-navigation--line .q1-navigation__item:active:not([aria-disabled='true']):not(:disabled) {
|
|
11331
|
+
background: none;
|
|
11332
|
+
}
|
|
11333
|
+
|
|
11334
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11335
|
+
.q1-navigation--line .q1-navigation__item {
|
|
11336
|
+
transition: none;
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11339
|
+
|
|
11269
11340
|
/* Calendar grid rows, focus treatment, touch targets, and RangeField entrance motion. */
|
|
11270
11341
|
.calendar__weekdays,
|
|
11271
11342
|
.calendar__row {
|