@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.
@@ -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. `"action-collection"` is the canonical dense approval / action queue
23
- * (gh#253): below `collapseBelow`, the desktop intrinsic column widths — the thing that pushes
24
- * the last columns outside a 390px viewport are replaced by the token-owned column PRIORITY
25
- * measures (`--table-action-collection-*`) under `table-layout: fixed`, and cells wrap instead
26
- * of forcing a horizontal scroll. Mark each column with `priority` on its `TableHead`/`TableCell`.
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="action-collection"` switches to the priority measures, measured against
39
- * the TABLE's own container (not the viewport), so a table inside a rail collapses before the
40
- * page does. Defaults to `"sm"` (40rem). Ignored while `preset` is `"default"`.
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. `"action-collection"` is the canonical dense approval / action queue
64
- * (gh#253): below `collapseBelow`, the desktop intrinsic column widths — the thing that pushes
65
- * the last columns outside a 390px viewport are replaced by the token-owned column PRIORITY
66
- * measures (`--table-action-collection-*`) under `table-layout: fixed`, and cells wrap instead
67
- * of forcing a horizontal scroll. Mark each column with `priority` on its `TableHead`/`TableCell`.
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="action-collection"` switches to the priority measures, measured against
80
- * the TABLE's own container (not the viewport), so a table inside a rail collapses before the
81
- * page does. Defaults to `"sm"` (40rem). Ignored while `preset` is `"default"`.
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
- import { jsx } from "react/jsx-runtime";
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 !== "default" && "ui-table-collection"
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
- className: cn("w-full caption-bottom text-sm", bordered && "ui-table-bordered", className),
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) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
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) => /* @__PURE__ */ jsx("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props }));
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) => /* @__PURE__ */ jsx(
50
- "tr",
51
- {
52
- ref,
53
- className: cn(
54
- "ui-table-row hover:bg-accent/70 data-[state=selected]:bg-primary/[0.06] border-b transition-colors",
55
- className
56
- ),
57
- ...props
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) => /* @__PURE__ */ jsx(
62
- "th",
63
- {
64
- ref,
65
- "data-slot": "table-head",
66
- "data-priority": priority,
67
- className: cn(tableHeadHeightClass, className),
68
- ...props
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) => /* @__PURE__ */ jsx(
73
- "td",
74
- {
75
- ref,
76
- "data-slot": "table-cell",
77
- "data-priority": priority,
78
- className: cn(className),
79
- ...props
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.pageSizeOption", { size: pageSize }) })
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)) })
@@ -1 +1,2 @@
1
+ "use client";
1
2
  export * from "../data-display/table.js";
@@ -311,6 +311,7 @@
311
311
  "next": "Next page",
312
312
  "page": "Page {page}",
313
313
  "pageSize": "Page size",
314
+ "pageSizeTrigger": "{size} / page",
314
315
  "pageSizeOption": "{size} / page",
315
316
  "total": {
316
317
  "one": "Total {total} item",
@@ -302,6 +302,7 @@
302
302
  "next": "次のページ",
303
303
  "page": "ページ {page}",
304
304
  "pageSize": "ページサイズ",
305
+ "pageSizeTrigger": "{size}件",
305
306
  "pageSizeOption": "{size} 件/ページ",
306
307
  "total": "全 {total} 件"
307
308
  },
@@ -302,6 +302,7 @@
302
302
  "next": "Trang sau",
303
303
  "page": "Trang {page}",
304
304
  "pageSize": "Số dòng mỗi trang",
305
+ "pageSizeTrigger": "{size} / trang",
305
306
  "pageSizeOption": "{size} / trang",
306
307
  "total": "Tổng {total} mục"
307
308
  },
@@ -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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "18.12.7",
4
- "godxUiMcp": "18.12.7",
3
+ "version": "18.12.11",
4
+ "godxUiMcp": "18.12.11",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",