@godxjp/ui 28.5.0 → 28.7.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/dist/components/data-display/data-table.d.ts +10 -2
- package/dist/components/data-display/data-table.js +12 -5
- package/dist/components/data-display/scroll-area.js +16 -2
- package/dist/components/data-display/service-launcher-card.d.ts +25 -0
- package/dist/components/data-display/table.d.ts +38 -1
- package/dist/components/data-display/table.js +57 -39
- package/dist/components/data-entry/field.d.ts +1 -1
- package/dist/components/data-entry/field.js +35 -4
- package/dist/components/data-entry/form-field.js +29 -12
- package/dist/components/data-entry/input.d.ts +1 -1
- package/dist/components/layout/index.d.ts +1 -1
- package/dist/components/layout/nav-list.d.ts +9 -1
- package/dist/components/layout/nav-list.js +22 -11
- package/dist/components/layout/sidebar.d.ts +16 -2
- package/dist/components/layout/sidebar.js +1 -0
- package/dist/components/navigation/app-setting-picker.js +20 -3
- package/dist/components/ui/password-input.d.ts +1 -1
- package/dist/components/ui/table.js +1 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +5 -1
- package/dist/i18n/messages/ja.json +5 -1
- package/dist/i18n/messages/vi.json +5 -1
- package/dist/lib/control-styles.d.ts +18 -3
- package/dist/lib/control-styles.js +1 -1
- package/dist/lib/hooks.d.ts +23 -0
- package/dist/lib/hooks.js +27 -0
- package/dist/props/components/data-display.prop.d.ts +14 -0
- package/dist/props/components/data-entry.prop.d.ts +36 -2
- package/dist/props/components/layout.prop.d.ts +16 -5
- package/dist/props/registry.d.ts +2 -2
- package/dist/props/registry.js +10 -2
- package/dist/styles/control.css +12 -0
- package/dist/styles/data-display-layout.css +25 -2
- package/dist/styles/dialog-layout.css +12 -0
- package/dist/styles/form-layout.css +22 -0
- package/dist/styles/layout.css +1 -2
- package/dist/styles/navigation-layout.css +18 -19
- package/dist/tokens/components/data-display.css +2 -1
- package/dist/tokens/components/feedback.css +2 -0
- package/dist/tokens/components/form.css +2 -0
- package/dist/tokens/components/navigation.css +2 -0
- package/docs/COMPOSITION-VS-COMPONENT.md +3 -0
- package/docs/DESIGN-AUTHORITY.md +25 -5
- package/docs/FRAME-COVERAGE-REPORT.md +1 -1
- package/docs/data-display/collapsible.tsx +5 -5
- package/docs/data-display/descriptions.tsx +188 -109
- package/docs/data-display/legend.tsx +17 -4
- package/docs/data-display/scroll-area.tsx +106 -5
- package/docs/data-display/service-launcher-card.tsx +275 -115
- package/docs/data-display/stat-card.tsx +319 -64
- package/docs/data-entry/form.tsx +700 -6
- package/docs/data-entry/input.tsx +663 -53
- package/docs/data-entry/radio-group.tsx +216 -2
- package/docs/data-entry/switch.tsx +210 -4
- package/docs/data-entry/upload-crop-dialog.tsx +24 -25
- package/docs/layout/nav-list.tsx +556 -35
- package/package.json +9 -7
- package/scripts/visual-audit.mjs +29 -1
|
@@ -186,10 +186,27 @@ const AppSettingPicker = React.forwardRef(
|
|
|
186
186
|
)
|
|
187
187
|
) : (
|
|
188
188
|
// Labeled: sized to a per-kind width from `sm` up; below `sm` it hugs its content and
|
|
189
|
-
// A form field that wants a full-width control passes
|
|
190
|
-
// `className="w-full"`, which wins over `w-auto`.
|
|
191
189
|
// `compact` drops the per-kind width entirely so the trigger hugs its value.
|
|
192
|
-
|
|
190
|
+
//
|
|
191
|
+
// The width is a UTILITY READING A TOKEN, for the third time in this file's
|
|
192
|
+
// history and for the same reason (gh#366 icon, gh#375 Select, gh#819 here):
|
|
193
|
+
// @layer utilities is ordered after @layer components, so the `w-auto` that
|
|
194
|
+
// stood here outranked `.ui-app-setting-picker-trigger[data-kind]`'s
|
|
195
|
+
// `inline-size` and every one of the eight per-kind tokens was inert — a
|
|
196
|
+
// consumer who retuned --app-setting-picker-density-width saw nothing move.
|
|
197
|
+
// `--app-setting-picker-trigger-width` is what `data-kind` resolves in
|
|
198
|
+
// navigation-layout.css (a custom property, which no utility competes with);
|
|
199
|
+
// it defaults to `auto`, so below the breakpoint and under `compact` this is
|
|
200
|
+
// byte-for-byte the old `w-auto` behaviour.
|
|
201
|
+
//
|
|
202
|
+
// It stays a SINGLE unprefixed `w-*` (not `sm:w-[…]` per kind): `cn` is
|
|
203
|
+
// tailwind-merge, so a form field that wants a full-width control still passes
|
|
204
|
+
// `className="w-full"` and REPLACES this one. A `sm:`-variant utility would
|
|
205
|
+
// survive that merge and quietly ignore the consumer above 40rem.
|
|
206
|
+
cn(
|
|
207
|
+
"w-[length:var(--app-setting-picker-trigger-width)] max-w-full",
|
|
208
|
+
!isCompact && "ui-app-setting-picker-trigger"
|
|
209
|
+
)
|
|
193
210
|
),
|
|
194
211
|
// Compact re-tiers the box through tokens (--app-setting-picker-compact-*); the height
|
|
195
212
|
// still comes from the official --control-height-sm tier, never a literal. The gap is a
|
|
@@ -18,7 +18,7 @@ export type PasswordInputProps = Omit<React.ComponentPropsWithoutRef<typeof Inpu
|
|
|
18
18
|
};
|
|
19
19
|
export declare const PasswordInput: React.ForwardRefExoticComponent<Omit<Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "size"> & {
|
|
20
20
|
onValueChange?: (value: string) => void;
|
|
21
|
-
size?: "sm" | "md" | "lg";
|
|
21
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
22
22
|
status?: import("../../props/index.js").ControlStatusProp;
|
|
23
23
|
variant?: import("../../props/index.js").ControlVariantProp;
|
|
24
24
|
allowClear?: import("../../props/index.js").AllowClearProp;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "28.
|
|
3
|
+
"version": "28.7.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
|
@@ -325,6 +325,9 @@
|
|
|
325
325
|
"ariaLabel": "Progress",
|
|
326
326
|
"breakdownSeparator": ", "
|
|
327
327
|
},
|
|
328
|
+
"scrollArea": {
|
|
329
|
+
"region": "Scrollable region"
|
|
330
|
+
},
|
|
328
331
|
"tree": {
|
|
329
332
|
"expand": "Expand",
|
|
330
333
|
"collapse": "Collapse",
|
|
@@ -537,7 +540,8 @@
|
|
|
537
540
|
"sortAscending": "Sort ascending",
|
|
538
541
|
"sortDescending": "Sort descending",
|
|
539
542
|
"sortCancel": "Cancel sort",
|
|
540
|
-
"selectColumn": "Select"
|
|
543
|
+
"selectColumn": "Select",
|
|
544
|
+
"scrollRegion": "Scrollable table"
|
|
541
545
|
},
|
|
542
546
|
"dataGrid": {
|
|
543
547
|
"search": "Search",
|
|
@@ -318,6 +318,9 @@
|
|
|
318
318
|
"ariaLabel": "進捗",
|
|
319
319
|
"breakdownSeparator": "、"
|
|
320
320
|
},
|
|
321
|
+
"scrollArea": {
|
|
322
|
+
"region": "スクロール可能な領域"
|
|
323
|
+
},
|
|
321
324
|
"tree": {
|
|
322
325
|
"expand": "展開",
|
|
323
326
|
"collapse": "折りたたむ",
|
|
@@ -524,7 +527,8 @@
|
|
|
524
527
|
"sortAscending": "昇順に並べ替え",
|
|
525
528
|
"sortDescending": "降順に並べ替え",
|
|
526
529
|
"sortCancel": "並べ替えを解除",
|
|
527
|
-
"selectColumn": "選択"
|
|
530
|
+
"selectColumn": "選択",
|
|
531
|
+
"scrollRegion": "スクロール可能な表"
|
|
528
532
|
},
|
|
529
533
|
"dataGrid": {
|
|
530
534
|
"search": "検索",
|
|
@@ -319,6 +319,9 @@
|
|
|
319
319
|
"ariaLabel": "Tiến độ",
|
|
320
320
|
"breakdownSeparator": ", "
|
|
321
321
|
},
|
|
322
|
+
"scrollArea": {
|
|
323
|
+
"region": "Vùng có thể cuộn"
|
|
324
|
+
},
|
|
322
325
|
"tree": {
|
|
323
326
|
"expand": "Mở rộng",
|
|
324
327
|
"collapse": "Thu gọn",
|
|
@@ -525,7 +528,8 @@
|
|
|
525
528
|
"sortAscending": "Sắp xếp tăng dần",
|
|
526
529
|
"sortDescending": "Sắp xếp giảm dần",
|
|
527
530
|
"sortCancel": "Bỏ sắp xếp",
|
|
528
|
-
"selectColumn": "Chọn"
|
|
531
|
+
"selectColumn": "Chọn",
|
|
532
|
+
"scrollRegion": "Bảng có thể cuộn"
|
|
529
533
|
},
|
|
530
534
|
"dataGrid": {
|
|
531
535
|
"search": "Tìm kiếm",
|
|
@@ -30,11 +30,26 @@ export declare const controlOpenRingClass = "ui-control-trigger";
|
|
|
30
30
|
* `--control-bounded-width` in gh#375). A control that wants the antd `variant` × `status` matrix
|
|
31
31
|
* therefore composes THIS class plus `ui-control-surface`, which supplies the identical resting
|
|
32
32
|
* border and fill from `--control-surface-*`.
|
|
33
|
+
*
|
|
34
|
+
* TRUNCATION HAS TO READ AS TRUNCATION (gh#813). `line-clamp-1` clips the value and — inside the
|
|
35
|
+
* trigger's `whitespace-nowrap` — never reaches a second line, so its own ellipsis never engages:
|
|
36
|
+
* `YYYY-MM-DD(年-月-日)` in a 101px value box showed `YYYY-MM-DD(全`, a format string cut through
|
|
37
|
+
* the middle of a glyph, with nothing to say it had been cut. Two declarations because the two
|
|
38
|
+
* engines ellipsize by different mechanisms, both measured on
|
|
39
|
+
* `/isolate/navigation-app-setting-picker` (dateFormat, ja, 1280×1000, value box 101px / text
|
|
40
|
+
* 160px):
|
|
41
|
+
*
|
|
42
|
+
* text-ellipsis Blink honours `text-overflow` on the clamped box → `YYYY-MM-DD…`.
|
|
43
|
+
* Gecko ignores it there (still `YYYY-MM-DD(全`).
|
|
44
|
+
* whitespace-normal lets `-webkit-line-clamp` clamp a real line, which is the ellipsis Gecko
|
|
45
|
+
* DOES paint → `YYYY-MM-D…` in Firefox, `YYYY-MM-DD…` in Chromium.
|
|
46
|
+
*
|
|
47
|
+
* The clamp keeps it to one line, so a wrappable value cannot grow the control.
|
|
33
48
|
*/
|
|
34
|
-
export declare const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1";
|
|
35
|
-
export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 border-input bg-background";
|
|
49
|
+
export declare const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
|
|
50
|
+
export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis border-input bg-background";
|
|
36
51
|
/** `controlTriggerBaseClass` + the token-driven surface — the select-family trigger. */
|
|
37
|
-
export declare const controlSurfaceTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 ui-control-surface";
|
|
52
|
+
export declare const controlSurfaceTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-surface";
|
|
38
53
|
export declare const controlIconClass = "size-[length:var(--control-height)] shrink-0";
|
|
39
54
|
export declare const controlIconSmClass = "size-[calc(var(--control-height)-0.5rem)] shrink-0";
|
|
40
55
|
/** Leading/affix icon inside an input row (search, command) — sized to `--control-icon-size`. */
|
|
@@ -2,7 +2,7 @@ const controlMultilineClass = "ui-control-multiline aria-invalid:border-destruct
|
|
|
2
2
|
const controlMultilineGhostClass = "ui-control-multiline data-[status=error]:border-destructive data-[status=warning]:border-warning w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:ring-0";
|
|
3
3
|
const controlMultilineFilledClass = "ui-control-multiline ui-control--filled aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
|
|
4
4
|
const controlOpenRingClass = "ui-control-trigger";
|
|
5
|
-
const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1";
|
|
5
|
+
const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
|
|
6
6
|
const controlTriggerClass = `${controlTriggerBaseClass} border-input bg-background`;
|
|
7
7
|
const controlSurfaceTriggerClass = `${controlTriggerBaseClass} ui-control-surface`;
|
|
8
8
|
const controlIconClass = "size-[length:var(--control-height)] shrink-0";
|
package/dist/lib/hooks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
1
2
|
/**
|
|
2
3
|
* Returns a debounced view of `value`, updated only after `delay` ms of no
|
|
3
4
|
* change. Use for search inputs to avoid querying on every keystroke.
|
|
@@ -37,3 +38,25 @@ export declare function useIsMobile(): boolean;
|
|
|
37
38
|
* @param element the scroll container itself (state, not a ref, so the effect re-runs when it mounts)
|
|
38
39
|
*/
|
|
39
40
|
export declare function useScrollableRegionTabIndex(element: HTMLElement | null): void;
|
|
41
|
+
/**
|
|
42
|
+
* Does this scroll box actually have somewhere to scroll, on the axes it is allowed to scroll on?
|
|
43
|
+
*
|
|
44
|
+
* Drives BOTH halves of a scroll region: the `tabindex="0"` a keyboard user needs to reach the
|
|
45
|
+
* overflow (WCAG 2.1.1 / axe `scrollable-region-focusable`) AND the `role`/name that stop needs in
|
|
46
|
+
* order not to be an anonymous one (gh#817 for the table, gh#821 for `ScrollArea`). A stop that
|
|
47
|
+
* scrolls nothing is pure noise, and a name on it is noise too, so both are withheld until there is
|
|
48
|
+
* overflow to reach.
|
|
49
|
+
*
|
|
50
|
+
* The measurement may only ever REMOVE the stop, never withhold it on a guess: a box that has not
|
|
51
|
+
* been laid out reports 0 for `clientWidth`/`clientHeight` (the server render, jsdom, a
|
|
52
|
+
* `display:none` ancestor, the frame before first layout), and that is not evidence that nothing
|
|
53
|
+
* overflows. Reading it as "no overflow" would strand the overflow from every keyboard user — a
|
|
54
|
+
* worse failure than an extra tab stop — so an unmeasured box counts as scrolling.
|
|
55
|
+
*
|
|
56
|
+
* `axis` is the box's OWN `overflow`, not a preference: an axis it does not scroll on is `hidden`
|
|
57
|
+
* there, so overflow on that axis is CLIPPED rather than reachable, and measuring it would keep a
|
|
58
|
+
* tab stop that scrolls nothing. `ScrollArea`'s `orientation` is exactly this union.
|
|
59
|
+
*/
|
|
60
|
+
export declare function useScrollsOnAxis(ref: RefObject<HTMLElement | null>, enabled: boolean, axis: "horizontal" | "vertical" | "both"): boolean;
|
|
61
|
+
/** A table's wrapper scrolls on one axis only, so it asks the one question it has (gh#817). */
|
|
62
|
+
export declare function useScrollsHorizontally(ref: RefObject<HTMLElement | null>, enabled: boolean): boolean;
|
package/dist/lib/hooks.js
CHANGED
|
@@ -104,11 +104,38 @@ function useScrollableRegionTabIndex(element) {
|
|
|
104
104
|
};
|
|
105
105
|
}, [element]);
|
|
106
106
|
}
|
|
107
|
+
function useScrollsOnAxis(ref, enabled, axis) {
|
|
108
|
+
const [scrolls, setScrolls] = useState(true);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const el = ref.current;
|
|
111
|
+
if (!enabled || !el) return void 0;
|
|
112
|
+
const overflows = (client, scroll) => client === 0 || scroll - client > 1;
|
|
113
|
+
const update = () => {
|
|
114
|
+
setScrolls(
|
|
115
|
+
axis !== "vertical" && overflows(el.clientWidth, el.scrollWidth) || axis !== "horizontal" && overflows(el.clientHeight, el.scrollHeight)
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
update();
|
|
119
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
120
|
+
const observer = new ResizeObserver(update);
|
|
121
|
+
observer.observe(el);
|
|
122
|
+
if (el.firstElementChild) observer.observe(el.firstElementChild);
|
|
123
|
+
return () => {
|
|
124
|
+
observer.disconnect();
|
|
125
|
+
};
|
|
126
|
+
}, [ref, enabled, axis]);
|
|
127
|
+
return enabled && scrolls;
|
|
128
|
+
}
|
|
129
|
+
function useScrollsHorizontally(ref, enabled) {
|
|
130
|
+
return useScrollsOnAxis(ref, enabled, "horizontal");
|
|
131
|
+
}
|
|
107
132
|
export {
|
|
108
133
|
useControlledLatch,
|
|
109
134
|
useDebouncedValue,
|
|
110
135
|
useIsMobile,
|
|
111
136
|
useMediaQuery,
|
|
112
137
|
useScrollableRegionTabIndex,
|
|
138
|
+
useScrollsHorizontally,
|
|
139
|
+
useScrollsOnAxis,
|
|
113
140
|
useTimeoutFlag
|
|
114
141
|
};
|
|
@@ -605,6 +605,20 @@ export type ScrollAreaProp = {
|
|
|
605
605
|
* from it — anchoring must never be the only route back to new content.
|
|
606
606
|
*/
|
|
607
607
|
onAnchoredChange?: (anchored: boolean) => void;
|
|
608
|
+
/**
|
|
609
|
+
* Accessible name for the scroll REGION — the `tabindex="0"` element a keyboard user lands on to
|
|
610
|
+
* scroll content taller or wider than the box.
|
|
611
|
+
*
|
|
612
|
+
* Optional on purpose. A consumer is never forced to invent a name for every scrolling box: left
|
|
613
|
+
* out, the region takes the localized `dataDisplay.scrollArea.region` default ("Scrollable
|
|
614
|
+
* region"), which is what a screen-reader user needs to hear anyway — that the arrow keys now
|
|
615
|
+
* scroll something. Pass a plain string when the screen can say WHICH region ("Activity log"); a
|
|
616
|
+
* non-string node cannot be an `aria-label`, so it falls back to the default.
|
|
617
|
+
*
|
|
618
|
+
* The region is only announced while it HAS overflow to reach, on the axes `orientation` opens:
|
|
619
|
+
* no overflow, no tab stop, no role and no name. (gh#821)
|
|
620
|
+
*/
|
|
621
|
+
label?: LabelProp;
|
|
608
622
|
};
|
|
609
623
|
/**
|
|
610
624
|
* @see TimelineGrid — one COLUMN of the grid (a day, a room, a machine). The label names the
|
|
@@ -126,8 +126,18 @@ export type CommandProp = {
|
|
|
126
126
|
};
|
|
127
127
|
export type InputProp = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "prefix"> & {
|
|
128
128
|
onValueChange?: (value: string) => void;
|
|
129
|
-
/**
|
|
130
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Control height tier: `md` (default), plus `xs`, `sm` and `lg` — the same tiers as
|
|
131
|
+
* SelectTrigger and NumberInput.
|
|
132
|
+
*
|
|
133
|
+
* `xs` was missing from this union alone. Everything underneath it already worked: `Input`
|
|
134
|
+
* renders `ui-control` and emits `data-size={size}`, and `.ui-control[data-size="xs"]`
|
|
135
|
+
* (src/styles/control.css:1674) binds `--control-height-xs`. That rule was added for the
|
|
136
|
+
* select family — its comment says the token "existed with nothing reading it" — and this
|
|
137
|
+
* union was never widened to match, so the most-used control in the package was the one
|
|
138
|
+
* control missing the bottom rung, for a reason no user could have guessed from behaviour.
|
|
139
|
+
*/
|
|
140
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
131
141
|
/** Validation state the field paints — antd `status`. `error` also reports `aria-invalid`. */
|
|
132
142
|
status?: ControlStatusProp;
|
|
133
143
|
/** Chrome level — antd `variant`. Default `outlined`. */
|
|
@@ -547,7 +557,31 @@ export type SwitchProp = Omit<React.ComponentPropsWithoutRef<"button">, "checked
|
|
|
547
557
|
export type FieldProp = {
|
|
548
558
|
id: IdProp;
|
|
549
559
|
label: LabelProp;
|
|
560
|
+
/**
|
|
561
|
+
* Optional content rendered BESIDE the label and OUTSIDE the `<label>` element — a help
|
|
562
|
+
* button, a Tooltip trigger, a status chip, a short text action. `FormField.labelAddon` with
|
|
563
|
+
* the same semantics, and it has to live outside the label for a reason this row has that a
|
|
564
|
+
* stacked field does not: `Field`'s label is a real `<label htmlFor>` pointing at a
|
|
565
|
+
* checkbox / radio / switch, so a browser forwards a click anywhere inside it to that
|
|
566
|
+
* control. An interactive addon passed through `label` therefore TOGGLES the control when
|
|
567
|
+
* pressed (gh#812). A non-interactive mark (a `Badge as="span"`) is still fine inside
|
|
568
|
+
* `label`; anything pressable belongs here.
|
|
569
|
+
*/
|
|
570
|
+
labelAddon?: React.ReactNode;
|
|
571
|
+
/**
|
|
572
|
+
* Muted hint under the label. Announced with the control (`aria-describedby`) rather than
|
|
573
|
+
* left as decoration, and it COMPOSES with `error` — `.ui-choice-content` is already a
|
|
574
|
+
* vertical stack, so the two lines sit under each other exactly as they do on `FormField`.
|
|
575
|
+
*/
|
|
550
576
|
description?: React.ReactNode;
|
|
577
|
+
/**
|
|
578
|
+
* Validation message under the description (`role="alert"`). Presence of a message IS the
|
|
579
|
+
* invalid state: the control is cloned with `aria-invalid`, and the message id reaches it
|
|
580
|
+
* through `aria-errormessage` AND `aria-describedby` (see field.tsx — react-aria's hidden
|
|
581
|
+
* input drops the former). This is the slot that lets a validated boolean stay in `Field`:
|
|
582
|
+
* `FormField` must not wrap a bare `Switch`, so before this there was nowhere to put it.
|
|
583
|
+
*/
|
|
584
|
+
error?: ErrorProp;
|
|
551
585
|
className?: ClassNameProp;
|
|
552
586
|
children: React.ReactNode;
|
|
553
587
|
};
|
|
@@ -932,11 +932,14 @@ export type SidebarItemProp = {
|
|
|
932
932
|
id: string;
|
|
933
933
|
label: string;
|
|
934
934
|
/**
|
|
935
|
-
* Leading 16px glyph
|
|
936
|
-
*
|
|
935
|
+
* Leading 16px glyph. STRONGLY RECOMMENDED on the rail — the collapsed rail is icon-only, so a
|
|
936
|
+
* row without one reads as a hole there — but OPTIONAL at the type level (gh#815): the runtime
|
|
937
|
+
* has always rendered an empty `.sb-icon` box for a row whose data carries no glyph, precisely
|
|
938
|
+
* so the label column still aligns, and `NavList` (which has no collapsed rail) routinely mixes
|
|
939
|
+
* rows with and without one. Its colour is themeable separately from the label via
|
|
937
940
|
* `--sidebar-nav-icon-foreground` (see {@link SidebarProp}).
|
|
938
941
|
*/
|
|
939
|
-
icon
|
|
942
|
+
icon?: ComponentType<SVGProps<SVGSVGElement>>;
|
|
940
943
|
/**
|
|
941
944
|
* Count/status affix rendered in the row's `.sb-badge` pill. Pass the CONTENT ONLY — a number, a
|
|
942
945
|
* string, `"9+"`.
|
|
@@ -968,7 +971,11 @@ export type SidebarItemProp = {
|
|
|
968
971
|
* right-click / open-in-new-tab / middle-click all work.
|
|
969
972
|
*/
|
|
970
973
|
href?: string;
|
|
971
|
-
/**
|
|
974
|
+
/**
|
|
975
|
+
* Nested rows — renders a collapsible submenu group (the parent reads active when any child is).
|
|
976
|
+
* Honoured by BOTH `Sidebar` and `NavList` (gh#815): `NavList` used to map `items` straight to a
|
|
977
|
+
* row, so a nested group type-checked, rendered as one flat row and lost its children silently.
|
|
978
|
+
*/
|
|
972
979
|
children?: SidebarItemProp[];
|
|
973
980
|
};
|
|
974
981
|
/**
|
|
@@ -979,7 +986,11 @@ export type SidebarItemProp = {
|
|
|
979
986
|
* learn a second one, and a fix to the row reaches both.
|
|
980
987
|
*/
|
|
981
988
|
export type NavListProp = Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> & {
|
|
982
|
-
/**
|
|
989
|
+
/**
|
|
990
|
+
* Rows, in reading order. An item carrying `children` renders as a collapsible GROUP — the same
|
|
991
|
+
* `.sb-nav-group` the rail draws, opened by the route whenever a descendant is active — so a
|
|
992
|
+
* grouped settings nav is one `NavList` and one `<nav>` landmark, not one per group (gh#815).
|
|
993
|
+
*/
|
|
983
994
|
items: SidebarItemProp[];
|
|
984
995
|
/** `SidebarItemProp.id` of the current route; that row gets `aria-current="page"`. */
|
|
985
996
|
activeId?: string;
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -1470,7 +1470,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
1470
1470
|
readonly FieldProp: {
|
|
1471
1471
|
readonly group: "data-entry";
|
|
1472
1472
|
readonly file: "components/data-entry.prop.ts";
|
|
1473
|
-
readonly vocabulary: readonly ["IdProp", "LabelProp", "DescriptionProp", "ClassNameProp", "ChildrenProp"];
|
|
1473
|
+
readonly vocabulary: readonly ["IdProp", "LabelProp", "DescriptionProp", "ErrorProp", "ClassNameProp", "ChildrenProp"];
|
|
1474
1474
|
};
|
|
1475
1475
|
readonly SliderProp: {
|
|
1476
1476
|
readonly group: "data-entry";
|
|
@@ -1851,7 +1851,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
1851
1851
|
readonly field: "onAnchoredChange";
|
|
1852
1852
|
readonly local: true;
|
|
1853
1853
|
readonly reason: 'Pinned-state change handler (boolean), so the consumer can render its own focusable "jump to newest" affordance.';
|
|
1854
|
-
}];
|
|
1854
|
+
}, "LabelProp"];
|
|
1855
1855
|
};
|
|
1856
1856
|
readonly ChartSeriesProp: {
|
|
1857
1857
|
readonly group: "data-display";
|
package/dist/props/registry.js
CHANGED
|
@@ -1689,7 +1689,14 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
1689
1689
|
FieldProp: {
|
|
1690
1690
|
group: "data-entry",
|
|
1691
1691
|
file: "components/data-entry.prop.ts",
|
|
1692
|
-
vocabulary: [
|
|
1692
|
+
vocabulary: [
|
|
1693
|
+
"IdProp",
|
|
1694
|
+
"LabelProp",
|
|
1695
|
+
"DescriptionProp",
|
|
1696
|
+
"ErrorProp",
|
|
1697
|
+
"ClassNameProp",
|
|
1698
|
+
"ChildrenProp"
|
|
1699
|
+
]
|
|
1693
1700
|
},
|
|
1694
1701
|
SliderProp: {
|
|
1695
1702
|
group: "data-entry",
|
|
@@ -2254,7 +2261,8 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
2254
2261
|
field: "onAnchoredChange",
|
|
2255
2262
|
local: true,
|
|
2256
2263
|
reason: 'Pinned-state change handler (boolean), so the consumer can render its own focusable "jump to newest" affordance.'
|
|
2257
|
-
}
|
|
2264
|
+
},
|
|
2265
|
+
"LabelProp"
|
|
2258
2266
|
]
|
|
2259
2267
|
},
|
|
2260
2268
|
ChartSeriesProp: {
|
package/dist/styles/control.css
CHANGED
|
@@ -435,11 +435,23 @@
|
|
|
435
435
|
font-weight: var(--font-weight-normal);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
.ui-choice-label-row {
|
|
439
|
+
display: flex;
|
|
440
|
+
flex-wrap: wrap;
|
|
441
|
+
align-items: center;
|
|
442
|
+
gap: var(--control-label-space-gap);
|
|
443
|
+
}
|
|
444
|
+
|
|
438
445
|
.ui-choice-description {
|
|
439
446
|
color: hsl(var(--muted-foreground));
|
|
440
447
|
font-size: var(--choice-description-font-size);
|
|
441
448
|
}
|
|
442
449
|
|
|
450
|
+
.ui-choice-error {
|
|
451
|
+
color: hsl(var(--text-error));
|
|
452
|
+
font-size: var(--choice-description-font-size);
|
|
453
|
+
}
|
|
454
|
+
|
|
443
455
|
.ui-label {
|
|
444
456
|
display: flex;
|
|
445
457
|
align-items: center;
|
|
@@ -289,9 +289,32 @@
|
|
|
289
289
|
.ui-progress-ring-label {
|
|
290
290
|
position: relative;
|
|
291
291
|
color: hsl(var(--foreground));
|
|
292
|
-
font-size: var(--progress-ring-label-font-size);
|
|
292
|
+
font-size: var(--progress-ring-label-font-size, calc(var(--progress-ring-size) * 0.28));
|
|
293
293
|
font-variant-numeric: tabular-nums;
|
|
294
|
-
|
|
294
|
+
|
|
295
|
+
line-height: var(--line-height-normal);
|
|
296
|
+
|
|
297
|
+
max-inline-size: calc(
|
|
298
|
+
var(--progress-ring-size) - 2 * var(--progress-ring-border-width) - var(--space-1)
|
|
299
|
+
);
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
text-overflow: ellipsis;
|
|
302
|
+
white-space: nowrap;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.ui-disclosure-chevron {
|
|
306
|
+
transition: rotate var(--duration-fast) var(--ease-standard);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
:is([data-state="open"], [aria-expanded="true"]) > .ui-disclosure-chevron,
|
|
310
|
+
:is([data-state="open"], [aria-expanded="true"]) .ui-disclosure-chevron {
|
|
311
|
+
rotate: 180deg;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@media (prefers-reduced-motion: reduce) {
|
|
315
|
+
.ui-disclosure-chevron {
|
|
316
|
+
transition: none;
|
|
317
|
+
}
|
|
295
318
|
}
|
|
296
319
|
|
|
297
320
|
.ui-legend {
|
|
@@ -146,6 +146,18 @@
|
|
|
146
146
|
opacity: var(--dialog-close-rest-alpha);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
:is([data-slot="dialog-close"], [data-slot="sheet-close"])::after {
|
|
150
|
+
content: "";
|
|
151
|
+
position: absolute;
|
|
152
|
+
inset-block-start: 50%;
|
|
153
|
+
inset-inline-start: 50%;
|
|
154
|
+
translate: -50% -50%;
|
|
155
|
+
min-inline-size: var(--dialog-close-size);
|
|
156
|
+
min-block-size: var(--dialog-close-size);
|
|
157
|
+
inline-size: 100%;
|
|
158
|
+
block-size: 100%;
|
|
159
|
+
}
|
|
160
|
+
|
|
149
161
|
[data-slot="dialog-close"]:hover {
|
|
150
162
|
opacity: 1;
|
|
151
163
|
}
|
|
@@ -259,4 +259,26 @@
|
|
|
259
259
|
text-align: end;
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
+
|
|
263
|
+
.ui-form-feedback > svg {
|
|
264
|
+
inline-size: var(--form-feedback-icon-size);
|
|
265
|
+
block-size: var(--form-feedback-icon-size);
|
|
266
|
+
flex-shrink: 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.ui-form-feedback[data-status="success"] {
|
|
270
|
+
color: hsl(var(--text-success));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.ui-form-feedback[data-status="warning"] {
|
|
274
|
+
color: hsl(var(--text-warning));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.ui-form-feedback[data-status="error"] {
|
|
278
|
+
color: hsl(var(--text-error));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.ui-form-feedback[data-status="validating"] {
|
|
282
|
+
color: hsl(var(--muted-foreground));
|
|
283
|
+
}
|
|
262
284
|
}
|
package/dist/styles/layout.css
CHANGED
|
@@ -834,7 +834,7 @@
|
|
|
834
834
|
.ui-page-header-extra {
|
|
835
835
|
width: auto;
|
|
836
836
|
flex-shrink: 1;
|
|
837
|
-
min-inline-size:
|
|
837
|
+
min-inline-size: auto;
|
|
838
838
|
justify-content: flex-end;
|
|
839
839
|
}
|
|
840
840
|
|
|
@@ -1053,7 +1053,6 @@
|
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
1055
|
.ui-pagination-size-trigger {
|
|
1056
|
-
width: var(--pagination-size-width);
|
|
1057
1056
|
|
|
1058
1057
|
height: var(--control-height);
|
|
1059
1058
|
}
|
|
@@ -220,27 +220,27 @@
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
[data-slot="tabs"][data-variant="card"][data-placement="start"] [data-slot="tabs-list"],
|
|
223
|
-
[data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
|
|
224
|
-
[data-slot="tabs-list"] {
|
|
223
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="start"] [data-slot="tabs-list"] {
|
|
225
224
|
box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
[data-slot="tabs"][data-variant="card"][data-placement="end"] [data-slot="tabs-list"],
|
|
229
|
-
[data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
|
|
230
|
-
[data-slot="tabs-list"] {
|
|
228
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="end"] [data-slot="tabs-list"] {
|
|
231
229
|
box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
|
|
232
230
|
}
|
|
233
231
|
|
|
234
|
-
[dir="rtl"]
|
|
232
|
+
[dir="rtl"]
|
|
233
|
+
[data-slot="tabs"][data-variant="card"][data-placement="start"]
|
|
235
234
|
[data-slot="tabs-list"],
|
|
236
|
-
[dir="rtl"]
|
|
235
|
+
[dir="rtl"]
|
|
236
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
|
|
237
237
|
[data-slot="tabs-list"] {
|
|
238
238
|
box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
[dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="end"]
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
[dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="end"] [data-slot="tabs-list"],
|
|
242
|
+
[dir="rtl"]
|
|
243
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
|
|
244
244
|
[data-slot="tabs-list"] {
|
|
245
245
|
box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
|
|
246
246
|
}
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
margin-inline-start: calc(var(--tabs-panel-space-inset) + var(--tabs-panel-border-width));
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
[data-slot="tabs"][data-bodied="true"] > [data-slot="tabs-panel"],
|
|
281
281
|
[data-slot="tabs"][data-bodied="true"] > [data-slot="tabs-content"] {
|
|
282
282
|
border: var(--tabs-panel-border-width) solid hsl(var(--border));
|
|
283
283
|
border-radius: var(--tabs-panel-radius);
|
|
@@ -1002,7 +1002,6 @@
|
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
1004
|
.ui-app-setting-picker-icon {
|
|
1005
|
-
inline-size: var(--control-height);
|
|
1006
1005
|
justify-content: center;
|
|
1007
1006
|
border-color: transparent;
|
|
1008
1007
|
background: transparent;
|
|
@@ -1026,35 +1025,35 @@
|
|
|
1026
1025
|
|
|
1027
1026
|
@media (min-width: 40rem) {
|
|
1028
1027
|
.ui-app-setting-picker-trigger[data-kind="locale"] {
|
|
1029
|
-
|
|
1028
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-locale-width);
|
|
1030
1029
|
}
|
|
1031
1030
|
|
|
1032
1031
|
.ui-app-setting-picker-trigger[data-kind="timezone"] {
|
|
1033
|
-
|
|
1032
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-timezone-width);
|
|
1034
1033
|
}
|
|
1035
1034
|
|
|
1036
1035
|
.ui-app-setting-picker-trigger[data-kind="dateFormat"] {
|
|
1037
|
-
|
|
1036
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-date-format-width);
|
|
1038
1037
|
}
|
|
1039
1038
|
|
|
1040
1039
|
.ui-app-setting-picker-trigger[data-kind="timeFormat"] {
|
|
1041
|
-
|
|
1040
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-time-format-width);
|
|
1042
1041
|
}
|
|
1043
1042
|
|
|
1044
1043
|
.ui-app-setting-picker-trigger[data-kind="theme"] {
|
|
1045
|
-
|
|
1044
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-theme-width);
|
|
1046
1045
|
}
|
|
1047
1046
|
|
|
1048
1047
|
.ui-app-setting-picker-trigger[data-kind="brand"] {
|
|
1049
|
-
|
|
1048
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-brand-width);
|
|
1050
1049
|
}
|
|
1051
1050
|
|
|
1052
1051
|
.ui-app-setting-picker-trigger[data-kind="density"] {
|
|
1053
|
-
|
|
1052
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-density-width);
|
|
1054
1053
|
}
|
|
1055
1054
|
|
|
1056
1055
|
.ui-app-setting-picker-trigger[data-kind="fontSize"] {
|
|
1057
|
-
|
|
1056
|
+
--app-setting-picker-trigger-width: var(--app-setting-picker-font-size-width);
|
|
1058
1057
|
}
|
|
1059
1058
|
}
|
|
1060
1059
|
}
|
|
@@ -113,7 +113,8 @@
|
|
|
113
113
|
--progress-ring-size: 2.75rem;
|
|
114
114
|
--progress-ring-size-sm: 2rem;
|
|
115
115
|
--progress-ring-border-width: var(--stroke-lg);
|
|
116
|
-
|
|
116
|
+
|
|
117
|
+
--progress-ring-label-font-size: initial;
|
|
117
118
|
|
|
118
119
|
--legend-gap: 0.875rem;
|
|
119
120
|
--legend-item-gap: 0.3125rem;
|