@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { flexRender, type ColumnVisibilityState, type OnChangeFn, type PaginationState } from "@tanstack/react-table";
|
|
3
|
-
import type { BreakpointProp, ColumnDefProp, DensityProp, OnColumnFilterChangeProp, OnRowProp, RowToneProp, SortDirectionProp, SortStateProp, TableExpandableProp, TablePaginationProp, TablePresetProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp } from "../../props/vocabulary/index.js";
|
|
3
|
+
import type { BreakpointProp, ColumnDefProp, DensityProp, LabelProp, OnColumnFilterChangeProp, OnRowProp, RowToneProp, SortDirectionProp, SortStateProp, TableExpandableProp, TablePaginationProp, TablePresetProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp } from "../../props/vocabulary/index.js";
|
|
4
4
|
export type Density = DensityProp;
|
|
5
5
|
/**
|
|
6
6
|
* Lean column definition — the simple, common-case column API. `render` shapes a cell; `sortable`
|
|
@@ -93,6 +93,14 @@ interface DataTableProps<T> {
|
|
|
93
93
|
preset?: TablePresetProp;
|
|
94
94
|
/** Default `"sm"`. Ignored while `preset` is `"default"`. */
|
|
95
95
|
collapseBelow?: BreakpointProp;
|
|
96
|
+
/**
|
|
97
|
+
* Accessible name for the horizontal-scroll REGION (the `tabindex="0"` wrapper a keyboard user
|
|
98
|
+
* lands on), not for the `<table>`. Optional: left out, the region takes the localized
|
|
99
|
+
* `dataTable.scrollRegion` default, so no consumer has to invent a name for every list. The
|
|
100
|
+
* region is announced only while it HAS overflow to reach. Same contract as `Table label`.
|
|
101
|
+
* (gh#817)
|
|
102
|
+
*/
|
|
103
|
+
label?: LabelProp;
|
|
96
104
|
/**
|
|
97
105
|
* Per-row className for state-based row tinting (e.g. flag an invalid or empty record). Returned
|
|
98
106
|
* classes are appended last, so they win over the built-in hover/selected fills.
|
|
@@ -146,7 +154,7 @@ interface DataTableProps<T> {
|
|
|
146
154
|
className?: string;
|
|
147
155
|
children?: React.ReactNode;
|
|
148
156
|
}
|
|
149
|
-
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
157
|
+
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, label, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
150
158
|
export declare namespace DataTable {
|
|
151
159
|
export var Toolbar: ({ children, className, }: {
|
|
152
160
|
children?: React.ReactNode;
|
|
@@ -65,9 +65,11 @@ import {
|
|
|
65
65
|
TableCell,
|
|
66
66
|
TableHead,
|
|
67
67
|
TableHeader,
|
|
68
|
-
TableRow
|
|
68
|
+
TableRow,
|
|
69
|
+
scrollRegionLabel
|
|
69
70
|
} from "../data-display/table.js";
|
|
70
71
|
import { cn } from "../../lib/utils.js";
|
|
72
|
+
import { useScrollsHorizontally } from "../../lib/hooks.js";
|
|
71
73
|
import { densityClass } from "../../lib/variants.js";
|
|
72
74
|
import {
|
|
73
75
|
controlIconSmClass,
|
|
@@ -230,6 +232,7 @@ function DataTable({
|
|
|
230
232
|
stickyHeader = true,
|
|
231
233
|
preset = "default",
|
|
232
234
|
collapseBelow = "sm",
|
|
235
|
+
label,
|
|
233
236
|
rowClassName,
|
|
234
237
|
rowTone,
|
|
235
238
|
rowSelection,
|
|
@@ -450,6 +453,7 @@ function DataTable({
|
|
|
450
453
|
stickyHeader: sticky === void 0 ? stickyHeader : sticky !== false,
|
|
451
454
|
preset,
|
|
452
455
|
collapseBelow,
|
|
456
|
+
label,
|
|
453
457
|
rowClassName,
|
|
454
458
|
rowTone,
|
|
455
459
|
bordered,
|
|
@@ -774,6 +778,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
774
778
|
stickyHeader,
|
|
775
779
|
preset,
|
|
776
780
|
collapseBelow,
|
|
781
|
+
label,
|
|
777
782
|
rowClassName,
|
|
778
783
|
rowTone,
|
|
779
784
|
bordered,
|
|
@@ -816,6 +821,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
816
821
|
}, [missingHeaderNames]);
|
|
817
822
|
const selectLabelColumn = visibleColumns.find((col) => col.priority === "primary") ?? visibleColumns[0];
|
|
818
823
|
const scrollRef = React.useRef(null);
|
|
824
|
+
const scrolls = useScrollsHorizontally(scrollRef, !presetAttr);
|
|
819
825
|
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
820
826
|
React.useEffect(() => {
|
|
821
827
|
const el = scrollRef.current;
|
|
@@ -923,7 +929,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
923
929
|
"data-scroll-x": scroll?.x !== void 0 ? "" : void 0,
|
|
924
930
|
"data-scroll-y": scroll?.y !== void 0 ? "" : void 0,
|
|
925
931
|
"aria-busy": loading,
|
|
926
|
-
|
|
932
|
+
...scrolls ? { role: "group", "aria-label": scrollRegionLabel(label, t), tabIndex: 0 } : {},
|
|
927
933
|
children: /* @__PURE__ */ jsx(
|
|
928
934
|
"div",
|
|
929
935
|
{
|
|
@@ -936,6 +942,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
936
942
|
Table,
|
|
937
943
|
{
|
|
938
944
|
scrollable: preset !== "default",
|
|
945
|
+
label,
|
|
939
946
|
preset,
|
|
940
947
|
collapseBelow,
|
|
941
948
|
bordered,
|
|
@@ -982,7 +989,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
982
989
|
) : null;
|
|
983
990
|
const headerEmpty = isEmptyHeader(col.header);
|
|
984
991
|
const headerContent = headerEmpty && col.ariaLabel ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: col.ariaLabel }) : col.header;
|
|
985
|
-
const
|
|
992
|
+
const label2 = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
|
|
986
993
|
headerContent,
|
|
987
994
|
sortIndicator
|
|
988
995
|
] });
|
|
@@ -1000,7 +1007,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1000
1007
|
onClick: () => {
|
|
1001
1008
|
onHeaderClick(col);
|
|
1002
1009
|
},
|
|
1003
|
-
children:
|
|
1010
|
+
children: label2
|
|
1004
1011
|
}
|
|
1005
1012
|
);
|
|
1006
1013
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1027,7 +1034,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1027
1034
|
isSortable ? tooltipOn ? /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1028
1035
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: sortButton }),
|
|
1029
1036
|
/* @__PURE__ */ jsx(TooltipContent, { children: sortHint })
|
|
1030
|
-
] }) : sortButton :
|
|
1037
|
+
] }) : sortButton : label2,
|
|
1031
1038
|
col.filters ? /* @__PURE__ */ jsx(ColumnFilterMenu, { column: col }) : null
|
|
1032
1039
|
]
|
|
1033
1040
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { useScrollsOnAxis } from "../../lib/hooks.js";
|
|
4
6
|
import { cn } from "../../lib/utils.js";
|
|
5
7
|
const ANCHOR_OFFSET_TOKEN = "--scroll-area-anchor-offset";
|
|
6
8
|
const ANCHOR_OFFSET_FALLBACK_PX = 48;
|
|
@@ -121,12 +123,16 @@ const ScrollArea = React.forwardRef(
|
|
|
121
123
|
onAnchoredChange,
|
|
122
124
|
orientation = "vertical",
|
|
123
125
|
scrollbar = "auto",
|
|
126
|
+
label,
|
|
124
127
|
...props
|
|
125
128
|
}, ref) => {
|
|
129
|
+
const { t } = useTranslation();
|
|
126
130
|
const [viewport, setViewport] = React.useState(null);
|
|
131
|
+
const viewportHandle = React.useRef(null);
|
|
127
132
|
const attachViewport = React.useCallback(
|
|
128
133
|
(node) => {
|
|
129
134
|
setViewport(node);
|
|
135
|
+
viewportHandle.current = node;
|
|
130
136
|
for (const target of [ref, viewportRef]) {
|
|
131
137
|
if (typeof target === "function") target(node);
|
|
132
138
|
else if (target) target.current = node;
|
|
@@ -135,9 +141,17 @@ const ScrollArea = React.forwardRef(
|
|
|
135
141
|
[ref, viewportRef]
|
|
136
142
|
);
|
|
137
143
|
useBottomAnchor(viewport, anchor === "bottom", anchorOffset, onAnchoredChange);
|
|
144
|
+
const scrolls = useScrollsOnAxis(viewportHandle, true, orientation);
|
|
145
|
+
const regionLabel = typeof label === "string" && label.trim() !== "" ? label : t("dataDisplay.scrollArea.region");
|
|
138
146
|
return (
|
|
139
147
|
// `tabIndex={0}` keeps the scroll viewport keyboard-reachable so overflowing content can be
|
|
140
|
-
// scrolled without a pointer (WCAG 2.1.1 / axe scrollable-region-focusable).
|
|
148
|
+
// scrolled without a pointer (WCAG 2.1.1 / axe scrollable-region-focusable). That stop must
|
|
149
|
+
// not be ANONYMOUS: unroled and unnamed it announces nothing, so it carries `role="group"`
|
|
150
|
+
// plus an accessible name (gh#821, the same defect gh#817 fixed in Table). `group`, not
|
|
151
|
+
// `region`: a named `region` IS a landmark, and a page with several scroll areas would ship
|
|
152
|
+
// several identically-named landmarks (axe `landmark-unique`). All three attributes come and
|
|
153
|
+
// go together with the overflow, because a focus stop that scrolls nothing is noise — and
|
|
154
|
+
// they precede `{...props}`, so a consumer's own `role`/`aria-label`/`tabIndex` still wins.
|
|
141
155
|
//
|
|
142
156
|
// NO `dir` IS STAMPED HERE, deliberately. Radix's Root called `useDirection(dir)`, which
|
|
143
157
|
// falls back to the literal "ltr" and writes it onto the element — and a `dir` attribute is
|
|
@@ -150,12 +164,12 @@ const ScrollArea = React.forwardRef(
|
|
|
150
164
|
"div",
|
|
151
165
|
{
|
|
152
166
|
ref: attachViewport,
|
|
153
|
-
tabIndex: 0,
|
|
154
167
|
"data-slot": "scroll-area-viewport",
|
|
155
168
|
"data-anchor": anchor,
|
|
156
169
|
"data-orientation": orientation,
|
|
157
170
|
"data-scrollbar": scrollbar,
|
|
158
171
|
className: cn("ui-scroll-area", className),
|
|
172
|
+
...scrolls ? { role: "group", "aria-label": regionLabel, tabIndex: 0 } : {},
|
|
159
173
|
...props,
|
|
160
174
|
children: /* @__PURE__ */ jsx("div", { "data-slot": "scroll-area-content", className: "ui-scroll-area-content", children })
|
|
161
175
|
}
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RECORDED EXCEPTION to the Framework-Component Test (gh#814).
|
|
3
|
+
*
|
|
4
|
+
* `docs/COMPOSITION-VS-COMPONENT.md` §2 says all seven criteria must PASS and any FAIL makes a
|
|
5
|
+
* thing a composition pattern. This file FAILS two of them, and stays anyway:
|
|
6
|
+
*
|
|
7
|
+
* C2 (encapsulates reusable BEHAVIOR) — FAIL. It owns no state, no keyboard handling and no
|
|
8
|
+
* focus management; its only ARIA is three static attributes. §2 calls this "pure static
|
|
9
|
+
* layout/visual arrangement".
|
|
10
|
+
* C3 (not expressible by composing primitives) — FAIL. Its own imports are the answer: this is
|
|
11
|
+
* `Card` + `CardContent` + `Badge` + one Lucide glyph, and §2's heuristic — "could I build
|
|
12
|
+
* this right now from existing primitives + token overrides?" — is answered yes.
|
|
13
|
+
*
|
|
14
|
+
* It is RETAINED because `src/components/layout/app-launcher.tsx` consumes it: it is an internal
|
|
15
|
+
* building block of a component that does pass the test, so deleting it is not on the table, and
|
|
16
|
+
* removing a public export is breaking. Keeping it public was the cheaper call.
|
|
17
|
+
*
|
|
18
|
+
* SO: a consumer reaching for a service tile should compose `Card` + `CardContent` + `Badge`
|
|
19
|
+
* themselves, which is what C3 says. Reach for `ServiceLauncherCard` only to match `AppLauncher`'s
|
|
20
|
+
* own tiles. Do not cite it as precedent for adding another static tile to `src/components/` —
|
|
21
|
+
* the test still means what it says, and this is the exception that was argued and written down
|
|
22
|
+
* rather than the rule.
|
|
23
|
+
*
|
|
24
|
+
* Ledger, options considered and the decision: gh#814.
|
|
25
|
+
*/
|
|
1
26
|
import * as React from "react";
|
|
2
27
|
import type { LucideIcon } from "lucide-react";
|
|
3
28
|
import type { HeadingLevelProp, ToneProp } from "../../props/vocabulary/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { BreakpointProp, FlushProp, TableCellIndentProp, TableColumnPriorityProp, TablePresetProp, WidthProp } from "../../props/vocabulary/index.js";
|
|
2
|
+
import type { BreakpointProp, FlushProp, LabelProp, TableCellIndentProp, TableColumnPriorityProp, TablePresetProp, WidthProp } from "../../props/vocabulary/index.js";
|
|
3
3
|
export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
4
4
|
/**
|
|
5
5
|
* Whether the Table owns its own horizontal-scroll region (default `true`). When `true` a table
|
|
@@ -58,7 +58,29 @@ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
|
58
58
|
};
|
|
59
59
|
/** Defaults to `"sm"` (40rem). Ignored while `preset` is `"default"`. */
|
|
60
60
|
collapseBelow?: BreakpointProp;
|
|
61
|
+
/**
|
|
62
|
+
* Accessible name for the horizontal-scroll REGION — the `tabindex="0"` wrapper a keyboard user
|
|
63
|
+
* lands on to scroll a table wider than its container, NOT the `<table>` itself (pass `aria-label`
|
|
64
|
+
* for that; it reaches the table element as it always has).
|
|
65
|
+
*
|
|
66
|
+
* Optional on purpose. A consumer is never forced to invent a name for every table: left out, the
|
|
67
|
+
* region takes the localized `dataTable.scrollRegion` default ("Scrollable table"), which is what
|
|
68
|
+
* a screen-reader user needs to hear anyway — that the arrow keys now scroll something. Pass a
|
|
69
|
+
* plain string when the page can say WHICH table ("Pending approvals"); a non-string node cannot
|
|
70
|
+
* be an `aria-label`, so it falls back to the default (the PermissionMatrix `label` contract).
|
|
71
|
+
*
|
|
72
|
+
* The region is only announced while it HAS overflow to reach: no overflow, no tab stop, no role
|
|
73
|
+
* and no name, because a focus stop that scrolls nothing is noise. (gh#817)
|
|
74
|
+
*/
|
|
75
|
+
label?: LabelProp;
|
|
61
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* The scroll region's accessible name. A `label` is only usable as an `aria-label` when it is a
|
|
79
|
+
* plain string, and no consumer is obliged to supply one at all — so anything else takes the
|
|
80
|
+
* localized default, which still tells the user what the stop is for. Shared with `DataTable`, so
|
|
81
|
+
* both tables name their region the same way.
|
|
82
|
+
*/
|
|
83
|
+
export declare function scrollRegionLabel(label: LabelProp | undefined, t: (key: string) => string): string;
|
|
62
84
|
export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableElement> & {
|
|
63
85
|
/**
|
|
64
86
|
* Whether the Table owns its own horizontal-scroll region (default `true`). When `true` a table
|
|
@@ -117,6 +139,21 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
117
139
|
};
|
|
118
140
|
/** Defaults to `"sm"` (40rem). Ignored while `preset` is `"default"`. */
|
|
119
141
|
collapseBelow?: BreakpointProp;
|
|
142
|
+
/**
|
|
143
|
+
* Accessible name for the horizontal-scroll REGION — the `tabindex="0"` wrapper a keyboard user
|
|
144
|
+
* lands on to scroll a table wider than its container, NOT the `<table>` itself (pass `aria-label`
|
|
145
|
+
* for that; it reaches the table element as it always has).
|
|
146
|
+
*
|
|
147
|
+
* Optional on purpose. A consumer is never forced to invent a name for every table: left out, the
|
|
148
|
+
* region takes the localized `dataTable.scrollRegion` default ("Scrollable table"), which is what
|
|
149
|
+
* a screen-reader user needs to hear anyway — that the arrow keys now scroll something. Pass a
|
|
150
|
+
* plain string when the page can say WHICH table ("Pending approvals"); a non-string node cannot
|
|
151
|
+
* be an `aria-label`, so it falls back to the default (the PermissionMatrix `label` contract).
|
|
152
|
+
*
|
|
153
|
+
* The region is only announced while it HAS overflow to reach: no overflow, no tab stop, no role
|
|
154
|
+
* and no name, because a focus stop that scrolls nothing is noise. (gh#817)
|
|
155
|
+
*/
|
|
156
|
+
label?: LabelProp;
|
|
120
157
|
} & React.RefAttributes<HTMLTableElement>>;
|
|
121
158
|
export declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
|
|
122
159
|
export declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
3
5
|
import { tableHeadHeightClass } from "../../lib/control-styles.js";
|
|
6
|
+
import { useScrollsHorizontally } from "../../lib/hooks.js";
|
|
4
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
+
function scrollRegionLabel(label, t) {
|
|
9
|
+
return typeof label === "string" && label.trim() !== "" ? label : t("dataTable.scrollRegion");
|
|
10
|
+
}
|
|
5
11
|
const Table = React.forwardRef(
|
|
6
12
|
({
|
|
7
13
|
className,
|
|
@@ -11,45 +17,56 @@ const Table = React.forwardRef(
|
|
|
11
17
|
preset = "default",
|
|
12
18
|
collapseBelow = "sm",
|
|
13
19
|
columnWidths,
|
|
20
|
+
label,
|
|
14
21
|
...props
|
|
15
|
-
}, ref) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
}, ref) => {
|
|
23
|
+
const { t } = useTranslation();
|
|
24
|
+
const scrollRef = React.useRef(null);
|
|
25
|
+
const scrolls = useScrollsHorizontally(scrollRef, scrollable);
|
|
26
|
+
return (
|
|
27
|
+
// A table wider than its container scrolls horizontally in this wrapper; keep it
|
|
28
|
+
// keyboard-reachable so it can be scrolled without a pointer (WCAG 2.1.1 / axe
|
|
29
|
+
// scrollable-region-focusable). That stop must not be ANONYMOUS: an unnamed, unroled focus
|
|
30
|
+
// stop announces nothing at all, so it carries `role="group"` + an accessible name (gh#817).
|
|
31
|
+
// `group`, not `region`: a named `region` IS a landmark, and a page with three tables would
|
|
32
|
+
// then ship three same-named landmarks (axe `landmark-unique`) — the reason the role was left
|
|
33
|
+
// off in the first place. `group` is announced, takes a name, and is not a landmark.
|
|
34
|
+
// When `scrollable` is false an ancestor owns the scroll region, so this is a bare
|
|
35
|
+
// positioning box (no `overflow`, no tab stop) to avoid a redundant nested scroller.
|
|
36
|
+
// step attribute; with `preset="default"` neither is emitted, so the box is byte-identical.
|
|
37
|
+
/* @__PURE__ */ jsx(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
ref: scrollRef,
|
|
41
|
+
className: cn(
|
|
42
|
+
scrollable ? "relative w-full overflow-auto" : "relative w-full",
|
|
43
|
+
preset === "action-collection" && "ui-table-collection",
|
|
44
|
+
preset === "stacked-record-collection" && "ui-table-stacked-collection"
|
|
45
|
+
),
|
|
46
|
+
"data-preset": preset === "default" ? void 0 : preset,
|
|
47
|
+
"data-collapse-below": preset === "default" ? void 0 : collapseBelow,
|
|
48
|
+
"data-column-widths": columnWidths ? "" : void 0,
|
|
49
|
+
style: columnWidths ? {
|
|
50
|
+
"--table-action-collection-actions-width": columnWidths.actions,
|
|
51
|
+
"--table-action-collection-actions-width-compact": columnWidths.actionsCompact,
|
|
52
|
+
"--table-action-collection-meta-width-compact": columnWidths.metaCompact,
|
|
53
|
+
"--table-action-collection-min-inline-size-compact": columnWidths.minInlineSizeCompact
|
|
54
|
+
} : void 0,
|
|
55
|
+
...scrolls ? { role: "group", "aria-label": scrollRegionLabel(label, t), tabIndex: 0 } : {},
|
|
56
|
+
children: /* @__PURE__ */ jsx(
|
|
57
|
+
"table",
|
|
58
|
+
{
|
|
59
|
+
ref,
|
|
60
|
+
"data-slot": "table",
|
|
61
|
+
"data-striped": striped === void 0 ? void 0 : striped ? "" : "false",
|
|
62
|
+
className: cn("w-full caption-bottom", bordered && "ui-table-bordered", className),
|
|
63
|
+
...props
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
53
70
|
);
|
|
54
71
|
Table.displayName = "Table";
|
|
55
72
|
const TableHeader = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
|
|
@@ -140,5 +157,6 @@ export {
|
|
|
140
157
|
TableCell,
|
|
141
158
|
TableHead,
|
|
142
159
|
TableHeader,
|
|
143
|
-
TableRow
|
|
160
|
+
TableRow,
|
|
161
|
+
scrollRegionLabel
|
|
144
162
|
};
|
|
@@ -2,4 +2,4 @@ import * as React from "react";
|
|
|
2
2
|
import type { FieldProp } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { FieldProp, FieldProp as FieldProps } from "../../props/components/data-entry.prop.js";
|
|
4
4
|
/** Label + optional description beside a checkbox/radio/switch control. */
|
|
5
|
-
export declare function Field({ id, label, description, className, children }: FieldProp): React.JSX.Element;
|
|
5
|
+
export declare function Field({ id, label, labelAddon, description, error, className, children, }: FieldProp): React.JSX.Element;
|
|
@@ -1,12 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
2
4
|
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { mergeAriaIds } from "../../lib/field-a11y.js";
|
|
3
6
|
import { Label } from "./label.js";
|
|
4
|
-
function Field({
|
|
7
|
+
function Field({
|
|
8
|
+
id,
|
|
9
|
+
label,
|
|
10
|
+
labelAddon,
|
|
11
|
+
description,
|
|
12
|
+
error,
|
|
13
|
+
className,
|
|
14
|
+
children
|
|
15
|
+
}) {
|
|
16
|
+
const descriptionId = description ? `${id}-description` : void 0;
|
|
17
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
18
|
+
const childProps = React.isValidElement(children) ? children.props : void 0;
|
|
19
|
+
const control = React.isValidElement(children) ? React.cloneElement(children, {
|
|
20
|
+
"aria-describedby": mergeAriaIds(
|
|
21
|
+
childProps?.["aria-describedby"],
|
|
22
|
+
descriptionId,
|
|
23
|
+
errorId
|
|
24
|
+
),
|
|
25
|
+
"aria-errormessage": mergeAriaIds(
|
|
26
|
+
childProps?.["aria-errormessage"],
|
|
27
|
+
errorId
|
|
28
|
+
),
|
|
29
|
+
"aria-invalid": error ? true : childProps?.["aria-invalid"]
|
|
30
|
+
}) : children;
|
|
31
|
+
const labelNode = /* @__PURE__ */ jsx(Label, { htmlFor: id, className: "ui-choice-label", children: label });
|
|
5
32
|
return /* @__PURE__ */ jsxs("div", { className: cn("ui-choice-field", className), children: [
|
|
6
|
-
/* @__PURE__ */ jsx("div", { className: "ui-choice-control", children }),
|
|
33
|
+
/* @__PURE__ */ jsx("div", { className: "ui-choice-control", children: control }),
|
|
7
34
|
/* @__PURE__ */ jsxs("div", { className: "ui-choice-content", children: [
|
|
8
|
-
/* @__PURE__ */
|
|
9
|
-
|
|
35
|
+
labelAddon != null ? /* @__PURE__ */ jsxs("div", { className: "ui-choice-label-row", children: [
|
|
36
|
+
labelNode,
|
|
37
|
+
labelAddon
|
|
38
|
+
] }) : labelNode,
|
|
39
|
+
description ? /* @__PURE__ */ jsx("p", { id: descriptionId, className: "ui-choice-description", children: description }) : null,
|
|
40
|
+
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-choice-error", children: error }) : null
|
|
10
41
|
] })
|
|
11
42
|
] });
|
|
12
43
|
}
|
|
@@ -4,7 +4,6 @@ import * as React from "react";
|
|
|
4
4
|
import { CheckCircle, CircleAlert, LoaderCircle, TriangleAlert } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { Label } from "../data-entry/label.js";
|
|
7
|
-
import { controlIconClass } from "../../lib/control-styles.js";
|
|
8
7
|
import { cn } from "../../lib/utils.js";
|
|
9
8
|
import { FieldIdentityContext, FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
|
|
10
9
|
import { useOptionalAppContext } from "../../app/app-provider.js";
|
|
@@ -107,7 +106,16 @@ function FormField({
|
|
|
107
106
|
errorId
|
|
108
107
|
),
|
|
109
108
|
"aria-required": required ? true : childProps?.["aria-required"],
|
|
110
|
-
"aria-invalid": validationStatus === "error" ? true : childProps?.["aria-invalid"]
|
|
109
|
+
"aria-invalid": validationStatus === "error" ? true : childProps?.["aria-invalid"],
|
|
110
|
+
/* THE BOX SHOWS ITS OWN STATE, not just the line under it. `error` already reached the
|
|
111
|
+
* control through `aria-invalid`, but `warning` reached nothing — so a warned field drew an
|
|
112
|
+
* ordinary border and the only signal was a sentence below it. `.ui-control[data-status]`
|
|
113
|
+
* has painted both edges for a long time; FormField simply never passed the value down.
|
|
114
|
+
*
|
|
115
|
+
* Only `error` and `warning` are sent: `ControlStatusProp` is exactly those two, and
|
|
116
|
+
* `success` / `validating` have no control-level representation today. A child that sets its
|
|
117
|
+
* own `status` keeps it. */
|
|
118
|
+
...validationStatus === "error" || validationStatus === "warning" ? { status: childProps?.status ?? validationStatus } : {}
|
|
111
119
|
}) : children;
|
|
112
120
|
const style = {};
|
|
113
121
|
if (labelWidth != null)
|
|
@@ -166,16 +174,25 @@ function FormField({
|
|
|
166
174
|
children: childWithA11y
|
|
167
175
|
}
|
|
168
176
|
) }) }),
|
|
169
|
-
hasFeedback && validationStatus ? /* @__PURE__ */ jsxs(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
hasFeedback && validationStatus ? /* @__PURE__ */ jsxs(
|
|
178
|
+
"span",
|
|
179
|
+
{
|
|
180
|
+
id: feedbackId,
|
|
181
|
+
role: "status",
|
|
182
|
+
"data-status": validationStatus,
|
|
183
|
+
className: "ui-form-feedback ui-inline-xs text-xs",
|
|
184
|
+
children: [
|
|
185
|
+
feedback ?? /* @__PURE__ */ jsx(
|
|
186
|
+
FeedbackIcon,
|
|
187
|
+
{
|
|
188
|
+
"aria-hidden": "true",
|
|
189
|
+
className: validationStatus === "validating" ? "motion-safe:animate-spin" : void 0
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
t(`dataEntry.form.${validationStatus}`)
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
) : null,
|
|
179
196
|
helperPlacement === "after" ? helperNode : null,
|
|
180
197
|
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-error-strong text-xs", children: error }) : null
|
|
181
198
|
] })
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
export type { InputProp, InputProp as InputProps } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "size"> & {
|
|
4
4
|
onValueChange?: (value: string) => void;
|
|
5
|
-
size?: "sm" | "md" | "lg";
|
|
5
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
6
6
|
status?: import("../../props/index.js").ControlStatusProp;
|
|
7
7
|
variant?: import("../../props/index.js").ControlVariantProp;
|
|
8
8
|
allowClear?: import("../../props/index.js").AllowClearProp;
|
|
@@ -38,7 +38,7 @@ export type { ErrorSurfaceModeProp, ErrorSurfaceStatusProp } from "../../props/v
|
|
|
38
38
|
export { Breadcrumb } from "./breadcrumb.js";
|
|
39
39
|
export type { BreadcrumbProps } from "./breadcrumb.js";
|
|
40
40
|
export { createSidebarLink, Sidebar, SidebarHeader, SidebarItem, SidebarSection } from "./sidebar.js";
|
|
41
|
-
export type { SidebarItemData, SidebarLinkComponentProp, SidebarLinkProp, SidebarProductProp as SidebarProduct, SidebarProp, SidebarProp as SidebarProps, SidebarRenderItemProp, SidebarSectionProp, } from "../../props/components/layout.prop.js";
|
|
41
|
+
export type { SidebarItemData, SidebarItemProp, SidebarLinkComponentProp, SidebarLinkProp, SidebarProductProp as SidebarProduct, SidebarProp, SidebarProp as SidebarProps, SidebarRenderItemProp, SidebarSectionProp, } from "../../props/components/layout.prop.js";
|
|
42
42
|
export { Topbar } from "./topbar.js";
|
|
43
43
|
export type { TopbarProp, TopbarProps } from "./topbar.js";
|
|
44
44
|
export { TopbarItem } from "./topbar-item.js";
|
|
@@ -16,9 +16,17 @@ export type NavListProps = NavListProp;
|
|
|
16
16
|
* consumer that already learnt `SidebarItemProp` / `linkComponent` for the rail knows this too.
|
|
17
17
|
* Only the container differs — a `<nav>` landmark instead of the shell's grid area, and no
|
|
18
18
|
* collapsed rail (a page-level nav has no rail to collapse into).
|
|
19
|
+
*
|
|
20
|
+
* `item.children` is part of that shared row shape, and it is rendered through the rail's OWN
|
|
21
|
+
* `NavGroup` (gh#815). It used to be dropped: `items` mapped straight to `SidebarItem`, so a
|
|
22
|
+
* nested group type-checked, rendered as a single flat row, and its children vanished with no
|
|
23
|
+
* error and no warning. A grouped settings nav — アカウント / セキュリティ / 通知 / 請求 — is the
|
|
24
|
+
* canonical use of this component, and it took one `NavList` per group wrapped in
|
|
25
|
+
* `SidebarSection`, i.e. N `<nav>` landmarks where the page has one navigation. Now it is one
|
|
26
|
+
* `NavList`, one landmark, and the group opens itself whenever the route lands on a descendant.
|
|
19
27
|
*/
|
|
20
28
|
export declare const NavList: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> & {
|
|
21
|
-
items: import("
|
|
29
|
+
items: import("./sidebar.js").SidebarItemProp[];
|
|
22
30
|
activeId?: string;
|
|
23
31
|
label: string;
|
|
24
32
|
linkComponent?: import("./sidebar.js").SidebarLinkComponentProp;
|
|
@@ -2,18 +2,29 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
|
-
import { SidebarItem } from "./sidebar.js";
|
|
5
|
+
import { NavGroup, SidebarItem } from "./sidebar.js";
|
|
6
6
|
const NavList = React.forwardRef(
|
|
7
|
-
({ items, activeId, label, linkComponent, onSelect, className, ...props }, ref) => /* @__PURE__ */ jsx("nav", { ref, "aria-label": label, className: cn("ui-nav-list", className), ...props, children: items.map(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
({ items, activeId, label, linkComponent, onSelect, className, ...props }, ref) => /* @__PURE__ */ jsx("nav", { ref, "aria-label": label, className: cn("ui-nav-list", className), ...props, children: items.map(
|
|
8
|
+
(item) => item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(
|
|
9
|
+
NavGroup,
|
|
10
|
+
{
|
|
11
|
+
item,
|
|
12
|
+
activeId: activeId ?? "",
|
|
13
|
+
linkComponent,
|
|
14
|
+
onSelect
|
|
15
|
+
},
|
|
16
|
+
item.id
|
|
17
|
+
) : /* @__PURE__ */ jsx(
|
|
18
|
+
SidebarItem,
|
|
19
|
+
{
|
|
20
|
+
item,
|
|
21
|
+
active: item.id === activeId,
|
|
22
|
+
linkComponent,
|
|
23
|
+
onActivate: onSelect
|
|
24
|
+
},
|
|
25
|
+
item.id
|
|
26
|
+
)
|
|
27
|
+
) })
|
|
17
28
|
);
|
|
18
29
|
NavList.displayName = "NavList";
|
|
19
30
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { SidebarItemData, SidebarLinkComponentProp, SidebarProp, SidebarRenderItemProp } from "../../props/components/layout.prop.js";
|
|
3
|
-
export type { SidebarItemData, SidebarLinkComponentProp, SidebarLinkProp, SidebarProductProp as SidebarProduct, SidebarProp, SidebarProp as SidebarProps, SidebarRenderItemProp, } from "../../props/components/layout.prop.js";
|
|
2
|
+
import type { SidebarItemData, SidebarItemProp, SidebarLinkComponentProp, SidebarProp, SidebarRenderItemProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { SidebarItemData, SidebarItemProp, SidebarLinkComponentProp, SidebarLinkProp, SidebarProductProp as SidebarProduct, SidebarProp, SidebarProp as SidebarProps, SidebarRenderItemProp, } from "../../props/components/layout.prop.js";
|
|
4
4
|
type RenderItem = (item: SidebarItemData, rowProps: SidebarRenderItemProp) => React.ReactNode;
|
|
5
5
|
type SidebarHeaderProps = React.HTMLAttributes<HTMLDivElement>;
|
|
6
6
|
type SidebarSectionProps = {
|
|
@@ -28,6 +28,20 @@ type SidebarItemProps = {
|
|
|
28
28
|
export declare function SidebarHeader({ children, className, ...props }: SidebarHeaderProps): React.JSX.Element;
|
|
29
29
|
export declare function SidebarSection({ label, collapsed, children, className, ...props }: SidebarSectionProps & React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
30
30
|
export declare function SidebarItem({ item, active, sub, onActivate, linkComponent: LinkComponent, asChild, renderItem, children, ...props }: SidebarItemProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onClick">): React.JSX.Element;
|
|
31
|
+
type RowProps = {
|
|
32
|
+
item: SidebarItemProp;
|
|
33
|
+
activeId: string;
|
|
34
|
+
onSelect?: (id: string) => void;
|
|
35
|
+
sub?: boolean;
|
|
36
|
+
linkComponent?: SidebarLinkComponentProp;
|
|
37
|
+
renderItem?: RenderItem;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The collapsible submenu group of a nested `SidebarItemProp`. Exported (not from the package —
|
|
41
|
+
* only within `src/components/layout`) so `NavList` renders nested items through the SAME group as
|
|
42
|
+
* the rail instead of dropping `item.children` on the floor (gh#815).
|
|
43
|
+
*/
|
|
44
|
+
export declare function NavGroup({ item, activeId, onSelect, linkComponent, renderItem }: RowProps): React.JSX.Element;
|
|
31
45
|
export { createSidebarLink } from "./sidebar-link.js";
|
|
32
46
|
/**
|
|
33
47
|
* Sidebar — data-driven vertical nav rail. Use {@link createSidebarLink} to adapt a router `Link`,
|