@godxjp/ui 18.12.7 → 18.12.11
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/table.d.ts +60 -17
- package/dist/components/data-display/table.js +93 -38
- package/dist/components/navigation/pagination.js +1 -1
- package/dist/components/ui/table.js +1 -0
- package/dist/i18n/messages/en.json +1 -0
- package/dist/i18n/messages/ja.json +1 -0
- package/dist/i18n/messages/vi.json +1 -0
- package/dist/props/vocabulary/data.prop.d.ts +14 -1
- package/dist/styles/table-layout.css +348 -0
- package/dist/tokens/components/table.css +18 -0
- package/package.json +2 -2
|
@@ -19,11 +19,13 @@ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
|
19
19
|
bordered?: boolean;
|
|
20
20
|
/**
|
|
21
21
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
22
|
-
* table exactly as it is.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* table exactly as it is.
|
|
23
|
+
*
|
|
24
|
+
* `"action-collection"` is the canonical dense approval / action queue (gh#253): below
|
|
25
|
+
* `collapseBelow`, the desktop intrinsic column widths — the thing that pushes the last columns
|
|
26
|
+
* outside a 390px viewport — are replaced by the token-owned column PRIORITY measures
|
|
27
|
+
* (`--table-action-collection-*`) under `table-layout: fixed`, and cells wrap instead of
|
|
28
|
+
* forcing a horizontal scroll. Mark each column with `priority` on its `TableHead`/`TableCell`.
|
|
27
29
|
* The table keeps its real semantics: no `display` change, no role rewriting, no card swap, so
|
|
28
30
|
* header association, `aria-sort` and screen-reader table navigation are identical at every width.
|
|
29
31
|
*
|
|
@@ -32,12 +34,26 @@ export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
|
32
34
|
* its rem floor (`--table-action-collection-*-width-floor`, ~5 CJK glyphs per line) and the
|
|
33
35
|
* table intentionally scrolls inside this wrapper — SC 1.4.10 permits one-dimensional scrolling
|
|
34
36
|
* of a data table; compressing further would shred CJK headers one character per line.
|
|
37
|
+
*
|
|
38
|
+
* `"stacked-record-collection"` (gh#356) is the canonical RECORD list — a table whose columns
|
|
39
|
+
* are independent facts about ONE record (client ID · purpose · scopes · last use · status ·
|
|
40
|
+
* lifecycle actions) rather than a dense queue `action-collection` can reflow into a
|
|
41
|
+
* column-priority budget. Below `collapseBelow` each row renders as a card-like block with
|
|
42
|
+
* every column's label + value stacked vertically (a definition-list composition); row-level
|
|
43
|
+
* actions stay in document flow at the end of the card, never scrolled off-canvas. Supply the
|
|
44
|
+
* stacked-tier legend for each column with `label` on its `TableCell` (leave it unset on a
|
|
45
|
+
* self-evident column, e.g. row actions). This DOES change `display` (table family → block), so
|
|
46
|
+
* table/rowgroup/row/cell roles are re-asserted explicitly on every element that keeps
|
|
47
|
+
* rendering — see the prop doc on `TableCell`. The one element that does NOT keep rendering is
|
|
48
|
+
* the header row: below the step it is removed outright (not just visually hidden), because
|
|
49
|
+
* each `TableCell`'s own `label` becomes the field name for every modality at that tier — see
|
|
50
|
+
* `TableCellLabel`.
|
|
35
51
|
*/
|
|
36
52
|
preset?: TablePresetProp;
|
|
37
53
|
/**
|
|
38
|
-
* Step at which `preset
|
|
39
|
-
*
|
|
40
|
-
*
|
|
54
|
+
* Step at which a named `preset` switches tiers, measured against the TABLE's own container
|
|
55
|
+
* (not the viewport), so a table inside a rail collapses before the page does. Defaults to
|
|
56
|
+
* `"sm"` (40rem). Ignored while `preset` is `"default"`.
|
|
41
57
|
*/
|
|
42
58
|
collapseBelow?: BreakpointProp;
|
|
43
59
|
};
|
|
@@ -60,11 +76,13 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
60
76
|
bordered?: boolean;
|
|
61
77
|
/**
|
|
62
78
|
* Named collection contract. `"default"` (the default) emits no attribute and keeps the plain
|
|
63
|
-
* table exactly as it is.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
79
|
+
* table exactly as it is.
|
|
80
|
+
*
|
|
81
|
+
* `"action-collection"` is the canonical dense approval / action queue (gh#253): below
|
|
82
|
+
* `collapseBelow`, the desktop intrinsic column widths — the thing that pushes the last columns
|
|
83
|
+
* outside a 390px viewport — are replaced by the token-owned column PRIORITY measures
|
|
84
|
+
* (`--table-action-collection-*`) under `table-layout: fixed`, and cells wrap instead of
|
|
85
|
+
* forcing a horizontal scroll. Mark each column with `priority` on its `TableHead`/`TableCell`.
|
|
68
86
|
* The table keeps its real semantics: no `display` change, no role rewriting, no card swap, so
|
|
69
87
|
* header association, `aria-sort` and screen-reader table navigation are identical at every width.
|
|
70
88
|
*
|
|
@@ -73,12 +91,26 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
73
91
|
* its rem floor (`--table-action-collection-*-width-floor`, ~5 CJK glyphs per line) and the
|
|
74
92
|
* table intentionally scrolls inside this wrapper — SC 1.4.10 permits one-dimensional scrolling
|
|
75
93
|
* of a data table; compressing further would shred CJK headers one character per line.
|
|
94
|
+
*
|
|
95
|
+
* `"stacked-record-collection"` (gh#356) is the canonical RECORD list — a table whose columns
|
|
96
|
+
* are independent facts about ONE record (client ID · purpose · scopes · last use · status ·
|
|
97
|
+
* lifecycle actions) rather than a dense queue `action-collection` can reflow into a
|
|
98
|
+
* column-priority budget. Below `collapseBelow` each row renders as a card-like block with
|
|
99
|
+
* every column's label + value stacked vertically (a definition-list composition); row-level
|
|
100
|
+
* actions stay in document flow at the end of the card, never scrolled off-canvas. Supply the
|
|
101
|
+
* stacked-tier legend for each column with `label` on its `TableCell` (leave it unset on a
|
|
102
|
+
* self-evident column, e.g. row actions). This DOES change `display` (table family → block), so
|
|
103
|
+
* table/rowgroup/row/cell roles are re-asserted explicitly on every element that keeps
|
|
104
|
+
* rendering — see the prop doc on `TableCell`. The one element that does NOT keep rendering is
|
|
105
|
+
* the header row: below the step it is removed outright (not just visually hidden), because
|
|
106
|
+
* each `TableCell`'s own `label` becomes the field name for every modality at that tier — see
|
|
107
|
+
* `TableCellLabel`.
|
|
76
108
|
*/
|
|
77
109
|
preset?: TablePresetProp;
|
|
78
110
|
/**
|
|
79
|
-
* Step at which `preset
|
|
80
|
-
*
|
|
81
|
-
*
|
|
111
|
+
* Step at which a named `preset` switches tiers, measured against the TABLE's own container
|
|
112
|
+
* (not the viewport), so a table inside a rail collapses before the page does. Defaults to
|
|
113
|
+
* `"sm"` (40rem). Ignored while `preset` is `"default"`.
|
|
82
114
|
*/
|
|
83
115
|
collapseBelow?: BreakpointProp;
|
|
84
116
|
} & React.RefAttributes<HTMLTableElement>>;
|
|
@@ -94,6 +126,17 @@ export declare const TableRow: React.ForwardRefExoticComponent<React.HTMLAttribu
|
|
|
94
126
|
type TableCellPriority = {
|
|
95
127
|
priority?: TableColumnPriorityProp;
|
|
96
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Stacked-tier legend for ONE column, read only by `TableCell` under
|
|
131
|
+
* `Table preset="stacked-record-collection"`. Rendered as an inline label ahead of the cell's
|
|
132
|
+
* value, visible only below the collapse step (a wide container still shows the real `<th>`
|
|
133
|
+
* header instead, so the label stays invisible — never duplicated — above the step). Leave unset
|
|
134
|
+
* on a column whose value is self-evident without one (e.g. a row-actions cell whose buttons
|
|
135
|
+
* already carry their own text). Ignored under every other preset.
|
|
136
|
+
*/
|
|
137
|
+
type TableCellLabel = {
|
|
138
|
+
label?: React.ReactNode;
|
|
139
|
+
};
|
|
97
140
|
export declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & TableCellPriority & React.RefAttributes<HTMLTableCellElement>>;
|
|
98
|
-
export declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & TableCellPriority & React.RefAttributes<HTMLTableCellElement>>;
|
|
141
|
+
export declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & TableCellPriority & TableCellLabel & React.RefAttributes<HTMLTableCellElement>>;
|
|
99
142
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
import { tableHeadHeightClass } from "../../lib/control-styles.js";
|
|
4
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
const TablePresetContext = React.createContext("default");
|
|
5
7
|
const Table = React.forwardRef(
|
|
6
8
|
({
|
|
7
9
|
className,
|
|
@@ -23,62 +25,115 @@ const Table = React.forwardRef(
|
|
|
23
25
|
{
|
|
24
26
|
className: cn(
|
|
25
27
|
scrollable ? "relative w-full overflow-auto" : "relative w-full",
|
|
26
|
-
preset
|
|
28
|
+
preset === "action-collection" && "ui-table-collection",
|
|
29
|
+
preset === "stacked-record-collection" && "ui-table-stacked-collection"
|
|
27
30
|
),
|
|
28
31
|
"data-preset": preset === "default" ? void 0 : preset,
|
|
29
32
|
"data-collapse-below": preset === "default" ? void 0 : collapseBelow,
|
|
30
33
|
...scrollable ? { tabIndex: 0 } : {},
|
|
31
|
-
children: /* @__PURE__ */ jsx(
|
|
34
|
+
children: /* @__PURE__ */ jsx(TablePresetContext.Provider, { value: preset, children: /* @__PURE__ */ jsx(
|
|
32
35
|
"table",
|
|
33
36
|
{
|
|
34
37
|
ref,
|
|
35
38
|
"data-slot": "table",
|
|
36
|
-
|
|
39
|
+
role: preset === "stacked-record-collection" ? "table" : void 0,
|
|
40
|
+
className: cn(
|
|
41
|
+
"w-full caption-bottom text-sm",
|
|
42
|
+
bordered && "ui-table-bordered",
|
|
43
|
+
className
|
|
44
|
+
),
|
|
37
45
|
...props
|
|
38
46
|
}
|
|
39
|
-
)
|
|
47
|
+
) })
|
|
40
48
|
}
|
|
41
49
|
)
|
|
42
50
|
)
|
|
43
51
|
);
|
|
44
52
|
Table.displayName = "Table";
|
|
45
|
-
const TableHeader = React.forwardRef(({ className, ...props }, ref) =>
|
|
53
|
+
const TableHeader = React.forwardRef(({ className, ...props }, ref) => {
|
|
54
|
+
const preset = React.useContext(TablePresetContext);
|
|
55
|
+
const stacked = preset === "stacked-record-collection";
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"thead",
|
|
58
|
+
{
|
|
59
|
+
ref,
|
|
60
|
+
role: stacked ? "rowgroup" : void 0,
|
|
61
|
+
className: cn(
|
|
62
|
+
"[&_tr]:border-b",
|
|
63
|
+
// Removed below the collapse step (the stacked-tier CSS makes it display:none) — the
|
|
64
|
+
// per-cell label TableCell renders inline (real text, not aria-hidden) replaces it for
|
|
65
|
+
// every modality, so the raw header is fully redundant there, not just visually.
|
|
66
|
+
stacked && "ui-table-stacked-collection-head",
|
|
67
|
+
className
|
|
68
|
+
),
|
|
69
|
+
...props
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
});
|
|
46
73
|
TableHeader.displayName = "TableHeader";
|
|
47
|
-
const TableBody = React.forwardRef(({ className, ...props }, ref) =>
|
|
74
|
+
const TableBody = React.forwardRef(({ className, ...props }, ref) => {
|
|
75
|
+
const preset = React.useContext(TablePresetContext);
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
"tbody",
|
|
78
|
+
{
|
|
79
|
+
ref,
|
|
80
|
+
role: preset === "stacked-record-collection" ? "rowgroup" : void 0,
|
|
81
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
82
|
+
...props
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
});
|
|
48
86
|
TableBody.displayName = "TableBody";
|
|
49
|
-
const TableRow = React.forwardRef(({ className, ...props }, ref) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
87
|
+
const TableRow = React.forwardRef(({ className, ...props }, ref) => {
|
|
88
|
+
const preset = React.useContext(TablePresetContext);
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"tr",
|
|
91
|
+
{
|
|
92
|
+
ref,
|
|
93
|
+
role: preset === "stacked-record-collection" ? "row" : void 0,
|
|
94
|
+
className: cn(
|
|
95
|
+
"ui-table-row hover:bg-accent/70 data-[state=selected]:bg-primary/[0.06] border-b transition-colors",
|
|
96
|
+
className
|
|
97
|
+
),
|
|
98
|
+
...props
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
60
102
|
TableRow.displayName = "TableRow";
|
|
61
|
-
const TableHead = React.forwardRef(({ className, priority, ...props }, ref) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
103
|
+
const TableHead = React.forwardRef(({ className, priority, ...props }, ref) => {
|
|
104
|
+
const preset = React.useContext(TablePresetContext);
|
|
105
|
+
return /* @__PURE__ */ jsx(
|
|
106
|
+
"th",
|
|
107
|
+
{
|
|
108
|
+
ref,
|
|
109
|
+
"data-slot": "table-head",
|
|
110
|
+
"data-priority": priority,
|
|
111
|
+
role: preset === "stacked-record-collection" ? "columnheader" : void 0,
|
|
112
|
+
className: cn(tableHeadHeightClass, className),
|
|
113
|
+
...props
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
});
|
|
71
117
|
TableHead.displayName = "TableHead";
|
|
72
|
-
const TableCell = React.forwardRef(({ className, priority, ...props }, ref) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
const TableCell = React.forwardRef(({ className, priority, label, children, ...props }, ref) => {
|
|
119
|
+
const preset = React.useContext(TablePresetContext);
|
|
120
|
+
const stacked = preset === "stacked-record-collection";
|
|
121
|
+
return /* @__PURE__ */ jsxs(
|
|
122
|
+
"td",
|
|
123
|
+
{
|
|
124
|
+
ref,
|
|
125
|
+
"data-slot": "table-cell",
|
|
126
|
+
"data-priority": priority,
|
|
127
|
+
role: stacked ? "cell" : void 0,
|
|
128
|
+
className: cn(className),
|
|
129
|
+
...props,
|
|
130
|
+
children: [
|
|
131
|
+
stacked && label != null ? /* @__PURE__ */ jsx("span", { className: "ui-table-stacked-collection-label", children: label }) : null,
|
|
132
|
+
children
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
});
|
|
82
137
|
TableCell.displayName = "TableCell";
|
|
83
138
|
export {
|
|
84
139
|
Table,
|
|
@@ -178,7 +178,7 @@ function Pagination({
|
|
|
178
178
|
{
|
|
179
179
|
className: "ui-pagination-size-trigger w-max min-w-[var(--pagination-size-width)]",
|
|
180
180
|
"aria-label": t("navigation.pagination.pageSize"),
|
|
181
|
-
children: /* @__PURE__ */ jsx(SelectValue, { children: t("navigation.pagination.
|
|
181
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: t("navigation.pagination.pageSizeTrigger", { size: pageSize }) })
|
|
182
182
|
}
|
|
183
183
|
),
|
|
184
184
|
/* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, { value: String(size), children: size }, size)) })
|
|
@@ -81,8 +81,21 @@ export type StickyProp = boolean;
|
|
|
81
81
|
* the desktop intrinsic column widths are replaced by token-owned column-PRIORITY measures under
|
|
82
82
|
* `table-layout: fixed` and cells wrap, so every column — including the row actions — stays inside
|
|
83
83
|
* the initial narrow frame. The real `<table>` semantics are never rewritten.
|
|
84
|
+
*
|
|
85
|
+
* `"stacked-record-collection"` (gh#356) is the canonical RECORD list — a table whose columns
|
|
86
|
+
* cannot be reflowed into a fixed-layout budget (a scopes/status/lifecycle-action record, not a
|
|
87
|
+
* dense queue): below `collapseBelow` each row renders as a card-like block with every column's
|
|
88
|
+
* label + value stacked vertically (a definition-list composition), and row-level actions stay in
|
|
89
|
+
* document flow at the bottom of the card — never scrolled off-canvas. Unlike `action-collection`
|
|
90
|
+
* this DOES change `display` (table → block), so the preset re-asserts table semantics with
|
|
91
|
+
* explicit `role` attributes on every element that keeps rendering (browsers drop the IMPLICIT
|
|
92
|
+
* table roles once `display` leaves the table family — the standard fix, see e.g. Scott O'Hara's
|
|
93
|
+
* "breaking tables"). The header row is the one element that does NOT keep rendering below the
|
|
94
|
+
* step — it is removed outright, because each `TableCell`'s own `label` becomes the field name
|
|
95
|
+
* for every modality at that tier. Mark each column's stacked-tier legend with `label` on its
|
|
96
|
+
* `TableCell`.
|
|
84
97
|
*/
|
|
85
|
-
export type TablePresetProp = "default" | "action-collection";
|
|
98
|
+
export type TablePresetProp = "default" | "action-collection" | "stacked-record-collection";
|
|
86
99
|
/**
|
|
87
100
|
* Relative importance of a table column, used by `preset="action-collection"` to allocate the
|
|
88
101
|
* narrow-frame measure. `"primary"` is the row's subject, `"secondary"` its object/target,
|
|
@@ -363,6 +363,32 @@
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
@layer components {
|
|
367
|
+
/* ── Table · `preset="stacked-record-collection"` (gh#356) ──────────────────────────────────
|
|
368
|
+
* The canonical RECORD list: client ID · purpose · scopes · last use · status · lifecycle
|
|
369
|
+
* actions — independent facts about ONE record, not a dense queue `action-collection`'s
|
|
370
|
+
* column-priority budget can reflow. Below `collapseBelow` every row becomes a card-like block
|
|
371
|
+
* and every column's label + value stack vertically (a definition-list composition), so every
|
|
372
|
+
* field — the row's lifecycle actions included — stays inside the initial narrow frame instead
|
|
373
|
+
* of requiring horizontal scroll.
|
|
374
|
+
*
|
|
375
|
+
* Unlike `action-collection` this DOES change `display` (table family → block) below the step,
|
|
376
|
+
* so table.tsx re-asserts table/rowgroup/row/columnheader/cell explicitly via `role` — browsers
|
|
377
|
+
* drop the IMPLICIT roles once an element's `display` leaves the table family. The static rules
|
|
378
|
+
* here are unqueried (apply whenever the preset is on); the display-changing, card-drawing
|
|
379
|
+
* rules live in the `godxjp-ui-responsive` layer below, same split as `action-collection`. */
|
|
380
|
+
.ui-table-stacked-collection {
|
|
381
|
+
container: ui-table-stacked-collection / inline-size;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* The stacked-tier field label TableCell renders ahead of its value when `label` is supplied.
|
|
385
|
+
* Hidden above the collapse step so a wide container never shows both the real `<th>` header
|
|
386
|
+
* AND the inline label for the same column — the responsive layer below turns it back on. */
|
|
387
|
+
.ui-table-stacked-collection-label {
|
|
388
|
+
display: none;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
366
392
|
/* The compact tier lives in `godxjp-ui-responsive` — the LAST layer, declared after Tailwind in
|
|
367
393
|
* styles/base.css — not in `@layer components`. `<table>` carries `text-sm`, a Tailwind utility,
|
|
368
394
|
* and `utilities` outranks `components` by LAYER ORDER, so a `font-size:` re-point written here as
|
|
@@ -537,3 +563,325 @@
|
|
|
537
563
|
}
|
|
538
564
|
}
|
|
539
565
|
}
|
|
566
|
+
|
|
567
|
+
/* `preset="stacked-record-collection"` compact tier (gh#356) — same layer-contract reason as
|
|
568
|
+
* `action-collection` above: `<table>` carries `text-sm`/`border-b`/`border-0`, all Tailwind
|
|
569
|
+
* utilities, and `utilities` outranks `components` by LAYER ORDER, so the display-changing card
|
|
570
|
+
* transform has to live in this LAST layer to ever win the cascade. One block per canonical step
|
|
571
|
+
* (a container query cannot read a `var()`, so the scale is written out — sm 40rem · md 48rem ·
|
|
572
|
+
* lg 64rem · xl 80rem, the same steps `action-collection` and `--master-detail-collapse-below`
|
|
573
|
+
* use). See styles/base.css · THE LAYER CONTRACT. */
|
|
574
|
+
@layer godxjp-ui-responsive {
|
|
575
|
+
@container ui-table-stacked-collection (width < 40rem) {
|
|
576
|
+
[data-collapse-below="sm"] [data-slot="table"] {
|
|
577
|
+
display: block;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* The header is fully removed below the step — see the rule's own comment for why
|
|
581
|
+
* `display: none` (not a sr-only clip) is the right call here. */
|
|
582
|
+
[data-collapse-below="sm"] .ui-table-stacked-collection-head {
|
|
583
|
+
/* `display: none` (not a visually-hidden clip) removes the header from BOTH the paint
|
|
584
|
+
* tree and the accessibility tree below the step — deliberate: TableCell's stacked-tier
|
|
585
|
+
* label (rendered as real, non-aria-hidden text) is what carries the field name to every
|
|
586
|
+
* modality at this tier, sighted and AT alike, so the raw `<th>` is fully redundant here,
|
|
587
|
+
* not just visually. */
|
|
588
|
+
display: none;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
[data-collapse-below="sm"] tbody {
|
|
592
|
+
display: block;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/* Card shell. `border`/`background` here are the SHORTHAND form, which beats the individual
|
|
596
|
+
* `border-bottom-width` / `background-color` Tailwind utilities TableRow also carries — layer
|
|
597
|
+
* order decides that, not specificity (this layer is declared after `utilities`). */
|
|
598
|
+
[data-collapse-below="sm"] .ui-table-row {
|
|
599
|
+
display: block;
|
|
600
|
+
/* `.ui-table-row` carries a FIXED `height: var(--table-row-height)` from
|
|
601
|
+
* control.css (an earlier layer, but that only matters for a property BOTH
|
|
602
|
+
* layers declare — this preset never touches `height` anywhere else, so without
|
|
603
|
+
* this reset the fixed row height survives untouched and every stacked field
|
|
604
|
+
* below the first overflows the ~34px box, visually overlapping the next card).
|
|
605
|
+
* `auto` lets the card grow to fit its stacked fields. */
|
|
606
|
+
height: auto;
|
|
607
|
+
border: 1px solid
|
|
608
|
+
var(--table-stacked-record-collection-card-border-color, hsl(var(--border)));
|
|
609
|
+
border-radius: var(--table-stacked-record-collection-card-radius);
|
|
610
|
+
background: var(
|
|
611
|
+
--table-stacked-record-collection-card-background,
|
|
612
|
+
hsl(var(--card, var(--background)))
|
|
613
|
+
);
|
|
614
|
+
padding: var(--table-stacked-record-collection-card-padding);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
[data-collapse-below="sm"] .ui-table-row + .ui-table-row {
|
|
618
|
+
margin-block-start: var(--table-stacked-record-collection-card-gap);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/* Each column becomes its own block inside the card. Consistent vertical rhythm (the
|
|
622
|
+
* field-gap padding below) separates fields — no rule between them, matching the
|
|
623
|
+
* canonical's plain definition-list composition. */
|
|
624
|
+
[data-collapse-below="sm"] [data-slot="table-cell"] {
|
|
625
|
+
display: block;
|
|
626
|
+
padding-block: var(--table-stacked-record-collection-field-gap);
|
|
627
|
+
white-space: normal;
|
|
628
|
+
overflow-wrap: anywhere;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
[data-collapse-below="sm"] [data-slot="table-cell"]:first-child {
|
|
632
|
+
padding-block-start: 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
[data-collapse-below="sm"] [data-slot="table-cell"]:last-child {
|
|
636
|
+
padding-block-end: 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* A nowrap child (a Badge pill) would otherwise clip inside the narrow card. */
|
|
640
|
+
[data-collapse-below="sm"] [data-slot="table-cell"] * {
|
|
641
|
+
white-space: normal;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
[data-collapse-below="sm"] .ui-table-stacked-collection-label {
|
|
645
|
+
display: block;
|
|
646
|
+
font-size: var(--table-stacked-record-collection-label-font-size);
|
|
647
|
+
color: var(--table-stacked-record-collection-label-color, hsl(var(--muted-foreground)));
|
|
648
|
+
margin-block-end: var(--table-stacked-record-collection-label-gap);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* Lifecycle actions (`priority="actions"`) span the card edge-to-edge and split evenly —
|
|
652
|
+
* the canonical's full-width button pair — instead of the compact right-aligned desktop
|
|
653
|
+
* pair. Targets any actionable element inside the cell regardless of nesting, so it
|
|
654
|
+
* still applies through a `Flex` wrapper. */
|
|
655
|
+
[data-collapse-below="sm"] [data-slot="table-cell"][data-priority="actions"] :is(button, a) {
|
|
656
|
+
flex: 1 1 0%;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
@container ui-table-stacked-collection (width < 48rem) {
|
|
661
|
+
[data-collapse-below="md"] [data-slot="table"] {
|
|
662
|
+
display: block;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
[data-collapse-below="md"] .ui-table-stacked-collection-head {
|
|
666
|
+
/* `display: none` (not a visually-hidden clip) removes the header from BOTH the paint
|
|
667
|
+
* tree and the accessibility tree below the step — deliberate: TableCell's stacked-tier
|
|
668
|
+
* label (rendered as real, non-aria-hidden text) is what carries the field name to every
|
|
669
|
+
* modality at this tier, sighted and AT alike, so the raw `<th>` is fully redundant here,
|
|
670
|
+
* not just visually. */
|
|
671
|
+
display: none;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
[data-collapse-below="md"] tbody {
|
|
675
|
+
display: block;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
[data-collapse-below="md"] .ui-table-row {
|
|
679
|
+
display: block;
|
|
680
|
+
/* `.ui-table-row` carries a FIXED `height: var(--table-row-height)` from
|
|
681
|
+
* control.css (an earlier layer, but that only matters for a property BOTH
|
|
682
|
+
* layers declare — this preset never touches `height` anywhere else, so without
|
|
683
|
+
* this reset the fixed row height survives untouched and every stacked field
|
|
684
|
+
* below the first overflows the ~34px box, visually overlapping the next card).
|
|
685
|
+
* `auto` lets the card grow to fit its stacked fields. */
|
|
686
|
+
height: auto;
|
|
687
|
+
border: 1px solid
|
|
688
|
+
var(--table-stacked-record-collection-card-border-color, hsl(var(--border)));
|
|
689
|
+
border-radius: var(--table-stacked-record-collection-card-radius);
|
|
690
|
+
background: var(
|
|
691
|
+
--table-stacked-record-collection-card-background,
|
|
692
|
+
hsl(var(--card, var(--background)))
|
|
693
|
+
);
|
|
694
|
+
padding: var(--table-stacked-record-collection-card-padding);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
[data-collapse-below="md"] .ui-table-row + .ui-table-row {
|
|
698
|
+
margin-block-start: var(--table-stacked-record-collection-card-gap);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
[data-collapse-below="md"] [data-slot="table-cell"] {
|
|
702
|
+
display: block;
|
|
703
|
+
padding-block: var(--table-stacked-record-collection-field-gap);
|
|
704
|
+
white-space: normal;
|
|
705
|
+
overflow-wrap: anywhere;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
[data-collapse-below="md"] [data-slot="table-cell"]:first-child {
|
|
709
|
+
padding-block-start: 0;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
[data-collapse-below="md"] [data-slot="table-cell"]:last-child {
|
|
713
|
+
padding-block-end: 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
[data-collapse-below="md"] [data-slot="table-cell"] * {
|
|
717
|
+
white-space: normal;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
[data-collapse-below="md"] .ui-table-stacked-collection-label {
|
|
721
|
+
display: block;
|
|
722
|
+
font-size: var(--table-stacked-record-collection-label-font-size);
|
|
723
|
+
color: var(--table-stacked-record-collection-label-color, hsl(var(--muted-foreground)));
|
|
724
|
+
margin-block-end: var(--table-stacked-record-collection-label-gap);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/* Lifecycle actions (`priority="actions"`) span the card edge-to-edge and split evenly —
|
|
728
|
+
* the canonical's full-width button pair — instead of the compact right-aligned desktop
|
|
729
|
+
* pair. Targets any actionable element inside the cell regardless of nesting, so it
|
|
730
|
+
* still applies through a `Flex` wrapper. */
|
|
731
|
+
[data-collapse-below="md"] [data-slot="table-cell"][data-priority="actions"] :is(button, a) {
|
|
732
|
+
flex: 1 1 0%;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@container ui-table-stacked-collection (width < 64rem) {
|
|
737
|
+
[data-collapse-below="lg"] [data-slot="table"] {
|
|
738
|
+
display: block;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
[data-collapse-below="lg"] .ui-table-stacked-collection-head {
|
|
742
|
+
/* `display: none` (not a visually-hidden clip) removes the header from BOTH the paint
|
|
743
|
+
* tree and the accessibility tree below the step — deliberate: TableCell's stacked-tier
|
|
744
|
+
* label (rendered as real, non-aria-hidden text) is what carries the field name to every
|
|
745
|
+
* modality at this tier, sighted and AT alike, so the raw `<th>` is fully redundant here,
|
|
746
|
+
* not just visually. */
|
|
747
|
+
display: none;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
[data-collapse-below="lg"] tbody {
|
|
751
|
+
display: block;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
[data-collapse-below="lg"] .ui-table-row {
|
|
755
|
+
display: block;
|
|
756
|
+
/* `.ui-table-row` carries a FIXED `height: var(--table-row-height)` from
|
|
757
|
+
* control.css (an earlier layer, but that only matters for a property BOTH
|
|
758
|
+
* layers declare — this preset never touches `height` anywhere else, so without
|
|
759
|
+
* this reset the fixed row height survives untouched and every stacked field
|
|
760
|
+
* below the first overflows the ~34px box, visually overlapping the next card).
|
|
761
|
+
* `auto` lets the card grow to fit its stacked fields. */
|
|
762
|
+
height: auto;
|
|
763
|
+
border: 1px solid
|
|
764
|
+
var(--table-stacked-record-collection-card-border-color, hsl(var(--border)));
|
|
765
|
+
border-radius: var(--table-stacked-record-collection-card-radius);
|
|
766
|
+
background: var(
|
|
767
|
+
--table-stacked-record-collection-card-background,
|
|
768
|
+
hsl(var(--card, var(--background)))
|
|
769
|
+
);
|
|
770
|
+
padding: var(--table-stacked-record-collection-card-padding);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
[data-collapse-below="lg"] .ui-table-row + .ui-table-row {
|
|
774
|
+
margin-block-start: var(--table-stacked-record-collection-card-gap);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
[data-collapse-below="lg"] [data-slot="table-cell"] {
|
|
778
|
+
display: block;
|
|
779
|
+
padding-block: var(--table-stacked-record-collection-field-gap);
|
|
780
|
+
white-space: normal;
|
|
781
|
+
overflow-wrap: anywhere;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
[data-collapse-below="lg"] [data-slot="table-cell"]:first-child {
|
|
785
|
+
padding-block-start: 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
[data-collapse-below="lg"] [data-slot="table-cell"]:last-child {
|
|
789
|
+
padding-block-end: 0;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
[data-collapse-below="lg"] [data-slot="table-cell"] * {
|
|
793
|
+
white-space: normal;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
[data-collapse-below="lg"] .ui-table-stacked-collection-label {
|
|
797
|
+
display: block;
|
|
798
|
+
font-size: var(--table-stacked-record-collection-label-font-size);
|
|
799
|
+
color: var(--table-stacked-record-collection-label-color, hsl(var(--muted-foreground)));
|
|
800
|
+
margin-block-end: var(--table-stacked-record-collection-label-gap);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* Lifecycle actions (`priority="actions"`) span the card edge-to-edge and split evenly —
|
|
804
|
+
* the canonical's full-width button pair — instead of the compact right-aligned desktop
|
|
805
|
+
* pair. Targets any actionable element inside the cell regardless of nesting, so it
|
|
806
|
+
* still applies through a `Flex` wrapper. */
|
|
807
|
+
[data-collapse-below="lg"] [data-slot="table-cell"][data-priority="actions"] :is(button, a) {
|
|
808
|
+
flex: 1 1 0%;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
@container ui-table-stacked-collection (width < 80rem) {
|
|
813
|
+
[data-collapse-below="xl"] [data-slot="table"] {
|
|
814
|
+
display: block;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
[data-collapse-below="xl"] .ui-table-stacked-collection-head {
|
|
818
|
+
/* `display: none` (not a visually-hidden clip) removes the header from BOTH the paint
|
|
819
|
+
* tree and the accessibility tree below the step — deliberate: TableCell's stacked-tier
|
|
820
|
+
* label (rendered as real, non-aria-hidden text) is what carries the field name to every
|
|
821
|
+
* modality at this tier, sighted and AT alike, so the raw `<th>` is fully redundant here,
|
|
822
|
+
* not just visually. */
|
|
823
|
+
display: none;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
[data-collapse-below="xl"] tbody {
|
|
827
|
+
display: block;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
[data-collapse-below="xl"] .ui-table-row {
|
|
831
|
+
display: block;
|
|
832
|
+
/* `.ui-table-row` carries a FIXED `height: var(--table-row-height)` from
|
|
833
|
+
* control.css (an earlier layer, but that only matters for a property BOTH
|
|
834
|
+
* layers declare — this preset never touches `height` anywhere else, so without
|
|
835
|
+
* this reset the fixed row height survives untouched and every stacked field
|
|
836
|
+
* below the first overflows the ~34px box, visually overlapping the next card).
|
|
837
|
+
* `auto` lets the card grow to fit its stacked fields. */
|
|
838
|
+
height: auto;
|
|
839
|
+
border: 1px solid
|
|
840
|
+
var(--table-stacked-record-collection-card-border-color, hsl(var(--border)));
|
|
841
|
+
border-radius: var(--table-stacked-record-collection-card-radius);
|
|
842
|
+
background: var(
|
|
843
|
+
--table-stacked-record-collection-card-background,
|
|
844
|
+
hsl(var(--card, var(--background)))
|
|
845
|
+
);
|
|
846
|
+
padding: var(--table-stacked-record-collection-card-padding);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
[data-collapse-below="xl"] .ui-table-row + .ui-table-row {
|
|
850
|
+
margin-block-start: var(--table-stacked-record-collection-card-gap);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
[data-collapse-below="xl"] [data-slot="table-cell"] {
|
|
854
|
+
display: block;
|
|
855
|
+
padding-block: var(--table-stacked-record-collection-field-gap);
|
|
856
|
+
white-space: normal;
|
|
857
|
+
overflow-wrap: anywhere;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
[data-collapse-below="xl"] [data-slot="table-cell"]:first-child {
|
|
861
|
+
padding-block-start: 0;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
[data-collapse-below="xl"] [data-slot="table-cell"]:last-child {
|
|
865
|
+
padding-block-end: 0;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
[data-collapse-below="xl"] [data-slot="table-cell"] * {
|
|
869
|
+
white-space: normal;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
[data-collapse-below="xl"] .ui-table-stacked-collection-label {
|
|
873
|
+
display: block;
|
|
874
|
+
font-size: var(--table-stacked-record-collection-label-font-size);
|
|
875
|
+
color: var(--table-stacked-record-collection-label-color, hsl(var(--muted-foreground)));
|
|
876
|
+
margin-block-end: var(--table-stacked-record-collection-label-gap);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/* Lifecycle actions (`priority="actions"`) span the card edge-to-edge and split evenly —
|
|
880
|
+
* the canonical's full-width button pair — instead of the compact right-aligned desktop
|
|
881
|
+
* pair. Targets any actionable element inside the cell regardless of nesting, so it
|
|
882
|
+
* still applies through a `Flex` wrapper. */
|
|
883
|
+
[data-collapse-below="xl"] [data-slot="table-cell"][data-priority="actions"] :is(button, a) {
|
|
884
|
+
flex: 1 1 0%;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
@@ -136,3 +136,21 @@
|
|
|
136
136
|
* Japanese at the compact type tier) and the table scrolls instead of crushing. */
|
|
137
137
|
--table-action-collection-min-inline-size-compact: 0;
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
/* Table · stacked-record-collection preset (gh#356) — the canonical RECORD list: below the
|
|
141
|
+
* collapse step each row becomes a card and every column's label + value stack vertically. Every
|
|
142
|
+
* measure here is themeable so a service can retint/respace the cards without forking the
|
|
143
|
+
* component (rule #45). Colour/background tokens are `initial` so the default re-resolves the
|
|
144
|
+
* LIVE role at the call site (a :root binding would freeze a scoped [data-tenant] override —
|
|
145
|
+
* docs/TOKENS.md). */
|
|
146
|
+
:root {
|
|
147
|
+
--table-stacked-record-collection-card-gap: var(--space-stack-sm);
|
|
148
|
+
--table-stacked-record-collection-card-padding: var(--space-4);
|
|
149
|
+
--table-stacked-record-collection-card-radius: var(--card-radius);
|
|
150
|
+
--table-stacked-record-collection-card-border-color: initial; /* default = hsl(var(--border)) */
|
|
151
|
+
--table-stacked-record-collection-card-background: initial; /* default = hsl(var(--card, var(--background))) */
|
|
152
|
+
--table-stacked-record-collection-field-gap: var(--space-2);
|
|
153
|
+
--table-stacked-record-collection-label-gap: var(--space-1);
|
|
154
|
+
--table-stacked-record-collection-label-font-size: var(--font-size-xs);
|
|
155
|
+
--table-stacked-record-collection-label-color: initial; /* default = hsl(var(--muted-foreground)) */
|
|
156
|
+
}
|