@godxjp/ui 31.1.0 → 31.2.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.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 31.1.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.2.0.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -52,7 +52,7 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
52
52
  names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
53
53
  `SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
54
54
  first; it exists because that is the mistake.
55
- 2. `components/<Name>.json` — one file per component (1 KB–34 KB, median 6 KB), carrying its props,
55
+ 2. `components/<Name>.json` — one file per component (1 KB–35 KB, median 6 KB), carrying its props,
56
56
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
57
57
  3. `rules.json` — 50 cardinal rules. The ones about raw HTML and hardcoded colour are not
58
58
  style advice.
@@ -14,7 +14,7 @@
14
14
  "type": "T[]"
15
15
  },
16
16
  {
17
- "description": "Lean column definitions (adapted to TanStack internally — `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hideBelow?: 'sm'|'md'|'lg'|'xl' (same contract as Flex hideBelow — stamped as data-hide-below on th/td); hiddenOnMobile?: boolean (alias for hideBelow:'md'); enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions' }. priority is the column-priority contract read by preset=\"action-collection\" — DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow — pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' — an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). ANT DESIGN PARITY on the same column: fixed:'start'|'end' freezes the column against a scroll edge (logical, so it mirrors in RTL; the stacking offsets are MEASURED from the rendered header, so several adjacent frozen columns are correct at any width — pin:'end' is the older spelling of fixed:'end'). ellipsis holds the cell to one line and keeps the full value as its title (it also switches the table to table-layout: fixed, without which no ellipsis truncates anything). sorter is antd's richer `sortable`: true | (a, b) => number | { compare, multiple }, where multiple is the MULTI-column sort priority (highest sorts first). sortOrder / defaultSortOrder / sortDirections control and shape the cycle per column, and showSorterTooltip explains the next step. filters + onFilter + filteredValue / defaultFilteredValue / filterMultiple add a real filter menu to the header (filterMultiple: false makes it single-choice); omit onFilter for a server filter and drive it from the table's onFilterChange.",
17
+ "description": "Lean column definitions (adapted to TanStack internally — `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hideBelow?: 'sm'|'md'|'lg'|'xl' (same contract as Flex hideBelow — stamped as data-hide-below on th/td); hiddenOnMobile?: boolean (alias for hideBelow:'md'); enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions'; flush?: boolean }. flush (gh#1016 — the same word as TableCell flush / CardContent flush) drops the column's BODY cell padding so a self-padded row primitive owns the inset: a selectable list whose single column renders a `ListRow` writes `{ key: 'row', header: '', ariaLabel: …, flush: true, render: (m) => <ListRow asChild …><a/></ListRow> }` — the ListRow then sits 16px from the checkbox (not 32px) and the row is ListRow's own height; an unread ListRow in a flush cell paints `--list-row-unread-background` across the whole table row (checkbox cell included). The header keeps its padding, so the header label sits on the ListRow's inset. Never zero the cell with a className instead. priority is the column-priority contract read by preset=\"action-collection\" — DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow — pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' — an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). ANT DESIGN PARITY on the same column: fixed:'start'|'end' freezes the column against a scroll edge (logical, so it mirrors in RTL; the stacking offsets are MEASURED from the rendered header, so several adjacent frozen columns are correct at any width — pin:'end' is the older spelling of fixed:'end'). ellipsis holds the cell to one line and keeps the full value as its title (it also switches the table to table-layout: fixed, without which no ellipsis truncates anything). sorter is antd's richer `sortable`: true | (a, b) => number | { compare, multiple }, where multiple is the MULTI-column sort priority (highest sorts first). sortOrder / defaultSortOrder / sortDirections control and shape the cycle per column, and showSorterTooltip explains the next step. filters + onFilter + filteredValue / defaultFilteredValue / filterMultiple add a real filter menu to the header (filterMultiple: false makes it single-choice); omit onFilter for a server filter and drive it from the table's onFilterChange.",
18
18
  "name": "columns",
19
19
  "required": true,
20
20
  "type": "ColumnDef<T>[]"
@@ -75,6 +75,7 @@
75
75
  "usage": [
76
76
  "DO use ListRow for a SHORT (≈2–8 item) list of entities inside a Card where each row is one line with an action — account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.",
77
77
  "DON'T reach for DataTable here — it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",
78
+ "DO host ListRow in a DataTable column when the list needs ROW SELECTION (a mail/notification inbox with bulk actions) — DataTable is the only primitive that selects rows. Mark that column `flush: true` so the cell drops its own padding and the ListRow owns the inset (gh#1016); without it the cell padding stacks on the row's (a 32px dead gap after the checkbox, taller rows) and the unread band stops at the checkbox cell. DON'T zero the cell with a className.",
78
79
  "DO write a list of LINKS as `<Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">` + `<ListRow as=\"li\" asChild><Link/></ListRow>` — one call gives the list item, the whole-row link and the divider. DON'T wrap the row in your own `<li>` or `role=\"listitem\"` element to get list semantics back: the divider rule reads `:not(:last-child)` among the rows themselves, so a wrapper per row makes each one an only child and EVERY divider disappears — silently, which is why `ui-audit` now flags it as `no-hand-rolled-list`.",
79
80
  "DON'T hand-roll `<div className=\"flex items-center justify-between border-b py-3\">` — that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).",
80
81
  "DO put the row's action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as=\"li\"` when the rows live inside a semantic list, and build that list as `<Flex as=\"ul\" marker=\"none\">` — `ui-audit` flags a raw `<ul>`/`role=\"list\"` as `no-hand-rolled-list`.",
@@ -3380,7 +3380,7 @@
3380
3380
  "type": "T[]"
3381
3381
  },
3382
3382
  {
3383
- "description": "Lean column definitions (adapted to TanStack internally — `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hideBelow?: 'sm'|'md'|'lg'|'xl' (same contract as Flex hideBelow — stamped as data-hide-below on th/td); hiddenOnMobile?: boolean (alias for hideBelow:'md'); enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions' }. priority is the column-priority contract read by preset=\"action-collection\" — DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow — pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' — an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). ANT DESIGN PARITY on the same column: fixed:'start'|'end' freezes the column against a scroll edge (logical, so it mirrors in RTL; the stacking offsets are MEASURED from the rendered header, so several adjacent frozen columns are correct at any width — pin:'end' is the older spelling of fixed:'end'). ellipsis holds the cell to one line and keeps the full value as its title (it also switches the table to table-layout: fixed, without which no ellipsis truncates anything). sorter is antd's richer `sortable`: true | (a, b) => number | { compare, multiple }, where multiple is the MULTI-column sort priority (highest sorts first). sortOrder / defaultSortOrder / sortDirections control and shape the cycle per column, and showSorterTooltip explains the next step. filters + onFilter + filteredValue / defaultFilteredValue / filterMultiple add a real filter menu to the header (filterMultiple: false makes it single-choice); omit onFilter for a server filter and drive it from the table's onFilterChange.",
3383
+ "description": "Lean column definitions (adapted to TanStack internally — `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hideBelow?: 'sm'|'md'|'lg'|'xl' (same contract as Flex hideBelow — stamped as data-hide-below on th/td); hiddenOnMobile?: boolean (alias for hideBelow:'md'); enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions'; flush?: boolean }. flush (gh#1016 — the same word as TableCell flush / CardContent flush) drops the column's BODY cell padding so a self-padded row primitive owns the inset: a selectable list whose single column renders a `ListRow` writes `{ key: 'row', header: '', ariaLabel: …, flush: true, render: (m) => <ListRow asChild …><a/></ListRow> }` — the ListRow then sits 16px from the checkbox (not 32px) and the row is ListRow's own height; an unread ListRow in a flush cell paints `--list-row-unread-background` across the whole table row (checkbox cell included). The header keeps its padding, so the header label sits on the ListRow's inset. Never zero the cell with a className instead. priority is the column-priority contract read by preset=\"action-collection\" — DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow — pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' — an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). ANT DESIGN PARITY on the same column: fixed:'start'|'end' freezes the column against a scroll edge (logical, so it mirrors in RTL; the stacking offsets are MEASURED from the rendered header, so several adjacent frozen columns are correct at any width — pin:'end' is the older spelling of fixed:'end'). ellipsis holds the cell to one line and keeps the full value as its title (it also switches the table to table-layout: fixed, without which no ellipsis truncates anything). sorter is antd's richer `sortable`: true | (a, b) => number | { compare, multiple }, where multiple is the MULTI-column sort priority (highest sorts first). sortOrder / defaultSortOrder / sortDirections control and shape the cycle per column, and showSorterTooltip explains the next step. filters + onFilter + filteredValue / defaultFilteredValue / filterMultiple add a real filter menu to the header (filterMultiple: false makes it single-choice); omit onFilter for a server filter and drive it from the table's onFilterChange.",
3384
3384
  "name": "columns",
3385
3385
  "required": true,
3386
3386
  "type": "ColumnDef<T>[]"
@@ -4386,6 +4386,7 @@
4386
4386
  "usage": [
4387
4387
  "DO use ListRow for a SHORT (≈2–8 item) list of entities inside a Card where each row is one line with an action — account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.",
4388
4388
  "DON'T reach for DataTable here — it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",
4389
+ "DO host ListRow in a DataTable column when the list needs ROW SELECTION (a mail/notification inbox with bulk actions) — DataTable is the only primitive that selects rows. Mark that column `flush: true` so the cell drops its own padding and the ListRow owns the inset (gh#1016); without it the cell padding stacks on the row's (a 32px dead gap after the checkbox, taller rows) and the unread band stops at the checkbox cell. DON'T zero the cell with a className.",
4389
4390
  "DO write a list of LINKS as `<Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">` + `<ListRow as=\"li\" asChild><Link/></ListRow>` — one call gives the list item, the whole-row link and the divider. DON'T wrap the row in your own `<li>` or `role=\"listitem\"` element to get list semantics back: the divider rule reads `:not(:last-child)` among the rows themselves, so a wrapper per row makes each one an only child and EVERY divider disappears — silently, which is why `ui-audit` now flags it as `no-hand-rolled-list`.",
4390
4391
  "DON'T hand-roll `<div className=\"flex items-center justify-between border-b py-3\">` — that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).",
4391
4392
  "DO put the row's action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as=\"li\"` when the rows live inside a semantic list, and build that list as `<Flex as=\"ul\" marker=\"none\">` — `ui-audit` flags a raw `<ul>`/`role=\"list\"` as `no-hand-rolled-list`.",
package/agent/index.json CHANGED
@@ -15,7 +15,7 @@
15
15
  },
16
16
  {
17
17
  "file": "components/<Name>.json",
18
- "note": "One file per component (1 KB–34 KB, median 6 KB), each carrying its importPath. This is the selective route: read the index, then fetch only what you need instead of the 1.2 MB blob.",
18
+ "note": "One file per component (1 KB–35 KB, median 6 KB), each carrying its importPath. This is the selective route: read the index, then fetch only what you need instead of the 1.2 MB blob.",
19
19
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/<Name>.json"
20
20
  },
21
21
  {
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.1.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.2.0/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "31.1.0"
65
+ "version": "31.2.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 175 components, 2074 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.1.0.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.2.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@31.1.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.2.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -16,7 +16,7 @@ that can only fetch URLs.
16
16
 
17
17
  - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 21 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
18
18
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 46 KB — all 175 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
- - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–34 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
19
+ - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–35 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
20
20
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
21
  - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 103 `semantic` roles, 1760 `component` knobs.
22
22
  - [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v31.1.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.2.0/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -1296,7 +1296,8 @@ DataTable.Content = function DataTableContent() {
1296
1296
  ...fixedCellProps(col.key, fixedEdge(col)),
1297
1297
  ...columnHideBelowProps(col),
1298
1298
  style: columnCellStyle(col),
1299
- className: cn(cellPadding, columnCellClass(col)),
1299
+ flush: col.flush,
1300
+ className: cn(!col.flush && cellPadding, columnCellClass(col)),
1300
1301
  children: rendered
1301
1302
  },
1302
1303
  col.key
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "31.1.0",
3
+ "version": "31.2.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -5,6 +5,7 @@
5
5
  import type * as React from "react";
6
6
  import type { BreakpointProp, ColumnAlignProp, SortDirectionProp } from "./interaction.prop.js";
7
7
  import type { TableDensityProp } from "./layout.prop.js";
8
+ import type { FlushProp } from "./shared.prop.js";
8
9
  /** Generic row identifier extractor for tables with selection. */
9
10
  export type GetRowIdProp<T> = (row: T) => string;
10
11
  /**
@@ -82,6 +83,13 @@ export type ColumnDefProp<T> = {
82
83
  * is kept reachable as the cell's `title`, so nothing is silently lost.
83
84
  */
84
85
  ellipsis?: boolean;
86
+ /**
87
+ * The cell's CONTENT owns its inset (the same contract as `TableCell flush`): the column's body
88
+ * cells drop their padding so a self-padded row primitive — a `ListRow` in a selectable list —
89
+ * sits on the column's text axis instead of stacking a second inset on the cell's (gh#1016).
90
+ * The header cell keeps its padding, which lines the header label up with the row's own inset.
91
+ */
92
+ flush?: FlushProp;
85
93
  /**
86
94
  * Sort declaration (antd `ColumnType.sorter`). `sorter: true` is the same opt-in as `sortable`;
87
95
  * a comparator sorts by it; `{ compare, multiple }` joins the MULTI-column sort, highest
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.1.0",
2
+ "version": "31.2.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -136,7 +136,7 @@
136
136
  white-space: nowrap;
137
137
  }
138
138
 
139
- [data-slot="table-head"]:has([role="checkbox"]) {
139
+ [data-slot="table-head"]:has([data-slot="checkbox"], [role="checkbox"]) {
140
140
  padding-inline-end: 0;
141
141
  }
142
142
 
@@ -151,7 +151,7 @@
151
151
  white-space: nowrap;
152
152
  }
153
153
 
154
- [data-slot="table-cell"]:has([role="checkbox"]) {
154
+ [data-slot="table-cell"]:has([data-slot="checkbox"], [role="checkbox"]) {
155
155
  padding-inline-end: 0;
156
156
  }
157
157
 
@@ -187,6 +187,10 @@
187
187
  padding: 0;
188
188
  }
189
189
 
190
+ .ui-table-row:has(> [data-slot="table-cell"][data-flush] > [data-slot="list-row"][data-unread]) {
191
+ background-color: var(--list-row-unread-background, hsl(var(--muted)));
192
+ }
193
+
190
194
  .ui-data-table-toolbar {
191
195
  display: flex;
192
196
  flex-wrap: wrap;
@@ -0,0 +1,113 @@
1
+ import { useState } from "react";
2
+
3
+ import {
4
+ Avatar,
5
+ AvatarFallback,
6
+ Card,
7
+ CardContent,
8
+ CardHeader,
9
+ CardTitle,
10
+ DataTable,
11
+ ListRow,
12
+ type ColumnDef,
13
+ } from "@godxjp/ui/data-display";
14
+ import { PageContainer } from "@godxjp/ui/layout";
15
+
16
+ /**
17
+ * A selectable inbox — `ListRow` hosted in a DataTable column (gh#1016).
18
+ *
19
+ * DataTable is the only primitive that selects rows; ListRow is the canonical entity row. The
20
+ * column is marked `flush: true`, so its body cells drop their own padding and the ListRow owns
21
+ * the inset: the avatar sits 16px from the checkbox (the same step as any other column, not 32px),
22
+ * the row is ListRow's own height, and an unread row's band spans the whole table row — the
23
+ * checkbox cell included. The header cell keeps its padding, so its label sits on the row's inset.
24
+ */
25
+ type Message = {
26
+ id: string;
27
+ from: string;
28
+ initials: string;
29
+ subject: string;
30
+ read: boolean;
31
+ };
32
+
33
+ const MESSAGES: Message[] = [
34
+ {
35
+ id: "m1",
36
+ from: "Aiko Tanaka",
37
+ initials: "AT",
38
+ subject: "April invoice is ready",
39
+ read: false,
40
+ },
41
+ {
42
+ id: "m2",
43
+ from: "Minh Nguyen",
44
+ initials: "MN",
45
+ subject: "Release notes for 4.2",
46
+ read: false,
47
+ },
48
+ {
49
+ id: "m3",
50
+ from: "Kenji Sato",
51
+ initials: "KS",
52
+ subject: "Meeting minutes",
53
+ read: true,
54
+ },
55
+ {
56
+ id: "m4",
57
+ from: "Lan Pham",
58
+ initials: "LP",
59
+ subject: "Access request approved",
60
+ read: true,
61
+ },
62
+ ];
63
+
64
+ const columns: ColumnDef<Message>[] = [
65
+ {
66
+ key: "message",
67
+ header: "Message",
68
+ // The cell's CONTENT owns the inset — without it the cell padding stacks on the row's.
69
+ flush: true,
70
+ render: (m) => (
71
+ <ListRow
72
+ asChild
73
+ density="compact"
74
+ unread={!m.read}
75
+ leading={
76
+ <Avatar size="sm">
77
+ <AvatarFallback>{m.initials}</AvatarFallback>
78
+ </Avatar>
79
+ }
80
+ title={m.from}
81
+ description={m.subject}
82
+ >
83
+ <a href={`#${m.id}`} />
84
+ </ListRow>
85
+ ),
86
+ },
87
+ ];
88
+
89
+ export default function SelectableInboxDemo() {
90
+ const [selected, setSelected] = useState<Set<string>>(new Set(["m2"]));
91
+
92
+ return (
93
+ <PageContainer title="Inbox" subtitle="DataTable selectable · ListRow in a flush column">
94
+ <Card>
95
+ <CardHeader>
96
+ <CardTitle>Messages</CardTitle>
97
+ </CardHeader>
98
+ <CardContent flush>
99
+ <DataTable
100
+ data={MESSAGES}
101
+ columns={columns}
102
+ getRowId={(m) => m.id}
103
+ getRowLabel={(m) => m.subject}
104
+ selectable
105
+ selected={selected}
106
+ onSelectChange={setSelected}
107
+ density="compact"
108
+ />
109
+ </CardContent>
110
+ </Card>
111
+ </PageContainer>
112
+ );
113
+ }
@@ -3,3 +3,5 @@
3
3
  The admin list primitive — sticky header, sorting, bulk selection, density toggle, cursor pagination, and a built-in empty/loading state. Never wrap it in a `data.length === 0` guard; the empty state renders itself. See Examples for a full list-page screen.
4
4
 
5
5
  See the "Approval queue" example.
6
+
7
+ See the "Selectable inbox" example for a `ListRow` hosted in a selectable DataTable: mark that column `flush: true` so the cell drops its own padding and the ListRow owns the inset (gh#1016).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.1.0",
4
- "godxUiMcp": "31.1.0",
3
+ "version": "31.2.0",
4
+ "godxUiMcp": "31.2.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",