@cueplusplus/ui 0.6.0 → 0.8.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.
@@ -49,14 +49,14 @@
49
49
  {
50
50
  "name": "Row",
51
51
  "usage": "Table.Row",
52
- "description": "One row, separated by a hairline underneath — dropped on the last row so the\ntable never draws a rule against the panel rim it sits in. In a `<thead>` the\n\"last row\" is the header row itself, so the header's own bottom border (on\nthe cells) is the only rule there and the two can never stack into 2px.\n\nEvery row carries the `group/data-row` name unconditionally. It paints\nnothing by itself, and it is what lets a `DataRow.Actions` cluster inside a\n`Table.Cell` reveal on hover, on focus and on selection with no second\nimplementation of that idiom for the table — the whole point of the three\nidioms sharing one row anatomy.\n\n**The focus half of that reveal is the caller's to supply here.** A `<tr>` is\nnot focusable and this component does not make it one — a tab stop on every\nrow of a long table is a tab stop a keyboard user has to walk past hundreds\nof times, which is the reason `Ledger` rows opt in individually. So the\nreveal-on-focus only happens if the row *contains* something focusable\noutside the hidden track: a link on the row's name, a checkbox in the leading\ncell, any real control. Without one, `:focus-within` can never fire — content\ninside a `visibility: hidden` subtree cannot be focused, so the track cannot\nbootstrap its own reveal — and a hidden action cluster is then reachable by\npointer only, which is a WCAG 2.1.1 failure the screen gives no sign of. A\ntable whose only per-row controls live in a hidden track wants either a\nfocusable cell beside it or an always-visible cluster.\n\nThe selected tint is the row's, not the cell's: a `background` on the `<tr>`\nshows through every cell in it, so the fill cannot end up ragged where one\ncell sets a ground of its own.",
52
+ "description": "One row, separated by a hairline underneath — dropped on the last row so the\ntable never draws a rule against the panel rim it sits in. In a `<thead>` the\n\"last row\" is the header row itself, so the header's own bottom border (on\nthe cells) is the only rule there and the two can never stack into 2px.\n\nEvery row carries the `group/data-row` name unconditionally. It paints\nnothing by itself, and it is what lets a `DataRow.Actions` cluster inside a\n`Table.Cell` reveal on hover, on focus and on selection with no second\nimplementation of that idiom for the table — the whole point of the three\nidioms sharing one row anatomy.\n\n**The focus half of that reveal is the caller's to supply here.** A `<tr>` is\nnot focusable and this component does not make it one — a tab stop on every\nrow of a long table is a tab stop a keyboard user has to walk past hundreds\nof times, which is the reason `Ledger` rows opt in individually. So the\nreveal-on-focus only happens if the row *contains* something focusable\noutside the hidden track: a link on the row's name, a checkbox in the leading\ncell, any real control. Without one, `:focus-within` can never fire — content\ninside a `visibility: hidden` subtree cannot be focused, so the track cannot\nbootstrap its own reveal — and a hidden action cluster is then reachable by\npointer only, which is a WCAG 2.1.1 failure the screen gives no sign of. A\ntable whose only per-row controls live in a hidden track wants either a\nfocusable cell beside it or an always-visible cluster.\n\nA caller who instead makes the *row* focusable — a `tabIndex` plus\n{@link activateRowFromClick } and {@link activateRowFromKeyDown }, which is the\nshape a row that opens a drawer takes — gets the focus ring from\n{@link TableRowProps.interactive} and needs to draw nothing of its own.\n\nThe selected tint is the row's, not the cell's: a `background` on the `<tr>`\nshows through every cell in it, so the fill cannot end up ragged where one\ncell sets a ground of its own.",
53
53
  "props": [
54
54
  {
55
55
  "name": "interactive",
56
56
  "type": "boolean",
57
57
  "required": false,
58
58
  "defaultValue": "false",
59
- "description": "The row responds to a pointer: the row-hover wash and a pointer cursor\nacross the whole row. Defaults to `false`.\n\nOpt-in rather than automatic, because a table of figures nobody can click\nthat lit up under the pointer would be promising an interaction it does not\nhave. Wiring the row up to actually do something — a click handler, a\nkeyboard path to the same thing — stays the caller's job; this is the paint."
59
+ "description": "The row responds to a pointer and to focus: the row-hover wash, a pointer\ncursor across the whole row, and an inset focus ring. Defaults to `false`.\n\nOpt-in rather than automatic, because a table of figures nobody can click\nthat lit up under the pointer would be promising an interaction it does not\nhave. Wiring the row up to actually do something — a click handler, a\nkeyboard path to the same thing — stays the caller's job; this is the paint.\n\n**The focus ring ships with it, because the caller's half is what makes the\nrow focusable.** A `<tr>` cannot take focus on its own, so the moment a\ncaller gives one a `tabIndex` to run\n{@link activateRowFromKeyDown } against, a keyboard user can reach and\nactivate a row that shows nothing — a WCAG 2.4.7 failure the pointer user\nnever sees. The ring is *inset* (`-outline-offset-2`), the same ruling as\n`Row` and `DataRow.Root`: these tables live in clipped panes and scroll\nregions, where an outset ring on the first or last row is cut off by the\npane's own rim. `outline-solid` rides along with every `outline-<n>` here\nbecause Tailwind v4 leaves `--tw-outline-style` unset otherwise and the\nring never paints — `test/focus-ring.test.ts` is what keeps the pair\ntogether."
60
60
  },
61
61
  {
62
62
  "name": "selected",
@@ -73,7 +73,7 @@
73
73
  {
74
74
  "name": "GroupRow",
75
75
  "usage": "Table.GroupRow",
76
- "description": "A group divider inside one continuous table.\n\nThe whole point is that it is *inside*: a grouped list drawn as one table per\ngroup names its columns once per group, so a reader scanning a column\ntop-to-bottom crosses a fresh header band at every boundary and a screen\nreader announces four tables where there is one dataset. So the table stays\none `<table>` with one `<thead>`, and each group is its own `<tbody>` — which\nis valid HTML, keeps the group's rows together, and gives the divider a row\ngroup to be the header of.\n\nThat last part is why the cell is a `<th scope=\"rowgroup\">` rather than a\nstyled `<td>`: `rowgroup` scope is exactly the statement \"this heading names\nthe rows of this `<tbody>`\", and it leaves the column headers' associations\nuntouched. A `<td colSpan>` divider is invisible to assistive technology as a\nheading, and a `<th scope=\"col\">` one would quietly re-scope the columns.\n\nNo `aria-expanded` here, and no toggle: groups in a table divide, they do not\nfold. A hierarchy that folds is `DataTree`, whose treeitem rows own their own\nexpansion — and if a table genuinely needs to fold rows, the pattern is a real\n`<button aria-expanded aria-controls>` inside this cell, never the row itself.",
76
+ "description": "A group divider inside one continuous table.\n\nThe whole point is that it is *inside*: a grouped list drawn as one table per\ngroup names its columns once per group, so a reader scanning a column\ntop-to-bottom crosses a fresh header band at every boundary and a screen\nreader announces four tables where there is one dataset. So the table stays\none `<table>` with one `<thead>`, and each group is its own `<tbody>` — which\nis valid HTML, keeps the group's rows together, and gives the divider a row\ngroup to be the header of.\n\nThat last part is why the cell is a `<th scope=\"rowgroup\">` rather than a\nstyled `<td>`: `rowgroup` scope is exactly the statement \"this heading names\nthe rows of this `<tbody>`\", and it leaves the column headers' associations\nuntouched. A `<td colSpan>` divider is invisible to assistive technology as a\nheading, and a `<th scope=\"col\">` one would quietly re-scope the columns.\n\nNo `aria-expanded` here, and no toggle: groups in a table divide, they do not\nfold. A hierarchy that folds is `DataTree`, whose treeitem rows own their own\nexpansion — and if a table genuinely needs to fold rows, the pattern is a real\n`<button aria-expanded aria-controls>` inside this cell, never the row itself.\n\nThe divider can also be a real heading — see {@link * TableGroupRowProps.headingLevel}, which is how a page whose heading outline\nhas to survive a switch between the ledger, table and tree idioms keeps the\nsame `h2`/`h3` structure in all three.",
77
77
  "props": [
78
78
  {
79
79
  "name": "span",
@@ -89,6 +89,13 @@
89
89
  "defaultValue": null,
90
90
  "description": "What the group is called."
91
91
  },
92
+ {
93
+ "name": "headingLevel",
94
+ "type": "4 | 2 | 3 | 5 | 6",
95
+ "required": false,
96
+ "defaultValue": null,
97
+ "description": "Make the label slot a real heading element. Defaults to `undefined` — no\nheading, and the label stays the `<span>` it has always been.\n\n**Set this rather than passing a heading as `label`.** The label slot wraps\nwhatever it is given, so an `<h2>` handed in as `label` lands *inside* a\n`<span>` — which renders, and exposes the heading, and does not validate: a\n`<span>` is phrasing content and may not contain a heading. With this prop\nthe wrapper *is* the heading (`<h2 data-slot=\"table-group-label\">`), so\nthere is nothing to nest.\n\nA heading inside a `<th>` is valid — `<th>` takes flow content — and it is\nuseful: a screen-reader user navigates a long grouped table by heading, and\nwithout one the group names are reachable only by walking the rows. The\n`scope=\"rowgroup\"` association is untouched either way; this adds the\ndivider to the document outline, it does not change what the cell heads.\n\nLevels run `2`–`6` because the outline has to describe the real nesting of\nthe page the table sits in, not the table's own idea of itself — the same\nruling as {@link LedgerTierProps.headingLevel }. There is no `1`: a group\ndivider is never the title of the document."
98
+ },
92
99
  {
93
100
  "name": "count",
94
101
  "type": "number",
@@ -189,6 +196,11 @@
189
196
  ],
190
197
  "summary": "The semantic table: mono uppercase headers, hairline rows, tabular numerals.",
191
198
  "examples": [
199
+ {
200
+ "title": "A grouped table whose groups are h3s",
201
+ "code": "{groups.map((group) => (\n <Table.Body key={group.id}>\n <Table.GroupRow span={2} headingLevel={3} label={group.label} count={group.rows.length} />\n {group.rows.map((device) => (\n <Table.Row key={device.id}>\n <Table.Cell>{device.name}</Table.Cell>\n <Table.Cell numeric>{device.latency}</Table.Cell>\n </Table.Row>\n ))}\n </Table.Body>\n))}",
202
+ "language": "tsx"
203
+ },
192
204
  {
193
205
  "title": "Usage",
194
206
  "code": "<Table.Root>\n <Table.Header>\n <Table.Row>\n <Table.Head>Fixture</Table.Head>\n <Table.Head numeric>Address</Table.Head>\n </Table.Row>\n </Table.Header>\n <Table.Body>\n <Table.Row>\n <Table.Cell>Par 64</Table.Cell>\n <Table.Cell numeric>001</Table.Cell>\n </Table.Row>\n </Table.Body>\n</Table.Root>",
@@ -211,7 +223,9 @@
211
223
  ],
212
224
  "commonMistakes": [
213
225
  "Leaving out `Table.Caption`, which is what names the table for a screen reader.",
214
- "Wrapping cells in a `Row`. The table has its own row part."
226
+ "Wrapping cells in a `Row`. The table has its own row part.",
227
+ "Giving a row a `tabIndex` to make it activatable and then drawing no focus indicator. `Table.Row interactive` ships the inset focus ring along with the hover wash, so mark the row `interactive` rather than hand-rolling `focus-visible` classes on it.",
228
+ "Passing an `<h2>` as `Table.GroupRow`'s `label` to get a heading. The label slot wraps what it is given, so the heading lands inside a `<span>` — which renders, and does not validate. Set `headingLevel` and the wrapper becomes the heading."
215
229
  ],
216
230
  "specimens": [
217
231
  {
@@ -234,6 +248,16 @@
234
248
  "code": "<Panel className=\"w-full\">\n <div className=\"max-h-[18rem] overflow-y-auto [--cue-table-group-top:1.75rem]\">\n <Table.Root>\n <Table.Caption>Patch, one table, two row groups.</Table.Caption>\n <Table.Header>\n <Table.Row>\n <Table.Head sticky>Device</Table.Head>\n <Table.Head sticky>Detail</Table.Head>\n <Table.Head sticky numeric>\n Latency\n </Table.Head>\n <Table.Head sticky className=\"sr-only\">\n Actions\n </Table.Head>\n </Table.Row>\n </Table.Header>\n {PATCH_GROUPS.map((group) => (\n <Table.Body key={group.id}>\n <Table.GroupRow\n span={4}\n label={group.label}\n count={group.rows.length}\n sticky\n />\n {group.rows.map((device) => (\n <Table.Row key={device.id} interactive>\n {/* The name is a link, and that is load-bearing rather\n than decorative. A `<tr>` is not focusable, so\n without a real control somewhere outside the hidden\n action track nothing in the row can take focus,\n `:focus-within` never fires, and the Copy and\n Actions buttons are reachable by pointer only —\n visible to a mouse user, invisible to Tab. The\n ledger and the tree get this for free because their\n rows are focusable themselves; the table has to be\n given it. */}\n <Table.Cell>\n <Link href={`#patch-${device.id}`}>{device.name}</Link>\n </Table.Cell>\n <Table.Cell className=\"text-fg-muted\">{device.detail}</Table.Cell>\n <Table.Cell numeric>{device.latency}</Table.Cell>\n {/* The action column's width is a sum of the tokens it\n holds — two small controls, the gap between them and\n the cell's own padding on each side — so it stays\n right whatever the density does to any of them. */}\n <Table.Cell className=\"w-[calc(2_*_var(--cue-control-sm)_+_var(--cue-space-1)_+_2_*_var(--cue-pad-row-x))]\">\n <DataRow.Actions>\n <CopyButton\n value={device.id}\n size=\"sm\"\n className=\"w-control-sm px-0\"\n />\n <IconButton\n icon={MoreVertical}\n size=\"sm\"\n aria-label={`Actions for ${device.name}`}\n />\n </DataRow.Actions>\n </Table.Cell>\n </Table.Row>\n ))}\n </Table.Body>\n ))}\n </Table.Root>\n </div>\n</Panel>",
235
249
  "note": "The same rows as one continuous table: `Table.Head sticky` docks the column names, and one `Table.Body` per group opens with a `Table.GroupRow` divider — a `th` with `scope='rowgroup'`, so the columns are named once and scan straight through every boundary.",
236
250
  "interaction": "rows marked `interactive` take the row-hover wash across the whole row; `selected` tints it and reveals the action track. Tab reaches each row through its name link, which is what lets `:focus-within` reveal the track at all — a `tr` cannot take focus, so a table row whose only controls are in the hidden cluster would be pointer-only."
251
+ },
252
+ {
253
+ "title": "Table — group dividers as headings",
254
+ "group": "instruments",
255
+ "components": [
256
+ "Table"
257
+ ],
258
+ "code": "<Panel className=\"w-full\">\n <Table.Root>\n <Table.Caption>Patch, grouped into the page's outline.</Table.Caption>\n <Table.Header>\n <Table.Row>\n <Table.Head>Device</Table.Head>\n <Table.Head numeric>Latency</Table.Head>\n </Table.Row>\n </Table.Header>\n {PATCH_GROUPS.map((group) => (\n <Table.Body key={group.id}>\n <Table.GroupRow\n span={2}\n headingLevel={3}\n label={group.label}\n count={group.rows.length}\n />\n {group.rows.map((device) => (\n <Table.Row key={device.id}>\n <Table.Cell>{device.name}</Table.Cell>\n <Table.Cell numeric>{device.latency}</Table.Cell>\n </Table.Row>\n ))}\n </Table.Body>\n ))}\n </Table.Root>\n</Panel>",
259
+ "note": "`headingLevel` makes the divider's label slot a real `h3` instead of a `span`, so a grouped table joins the page's heading outline and a screen-reader user can jump between groups by heading. It is the prop to reach for rather than passing an `<h3>` as `label`: the label slot wraps what it is given, so a heading handed in that way lands inside a `<span>`, which may not contain one. The heading sits in the same `<th scope='rowgroup'>` as before — valid, because a `<th>` takes flow content — and wears the same type as the span form, so the level is in the outline and never in the design.",
260
+ "interaction": "none. Navigate by heading in a screen reader to hear the group names as an outline."
237
261
  }
238
262
  ]
239
263
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "@cueplusplus/ui",
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "generatedAt": "1970-01-01T00:00:00.000Z",
6
6
  "themes": [
7
7
  "cue",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "tokens": {
31
31
  "path": "./tokens.json",
32
- "sha256": "ee37d52427d7da166c441686519f77621e1397fe6fa42ddfe4343a5f5cd10965"
32
+ "sha256": "6d7c8411b968edd7c1d6dca18d845ec52370da8f6364cc28454b48122fb3ead0"
33
33
  },
34
34
  "systemApis": [
35
35
  {
@@ -3462,7 +3462,7 @@
3462
3462
  "mdUrl": "/docs/components/data-row.md",
3463
3463
  "jsonUrl": "/r/components/data-row.json",
3464
3464
  "path": "./components/data-row.json",
3465
- "sha256": "9e2e483bafb74e65051e5f8c00b108da5245499e870427258d386df72ce73502"
3465
+ "sha256": "2d9d18bea0dfbd9ae65527639e1a2238a5de80aa82fe69ae4f3ff18f1c296c65"
3466
3466
  },
3467
3467
  {
3468
3468
  "name": "DataTablePagination",
@@ -3501,7 +3501,7 @@
3501
3501
  "mdUrl": "/docs/components/data-tree.md",
3502
3502
  "jsonUrl": "/r/components/data-tree.json",
3503
3503
  "path": "./components/data-tree.json",
3504
- "sha256": "33244c5faf277716152a605c28726b0be0fb110f1bafa8b0b84f8a00a9315b55"
3504
+ "sha256": "3e68587c9fde809009a4a9661636a7b64dbe72b519cde4d6093bd36996106bdf"
3505
3505
  },
3506
3506
  {
3507
3507
  "name": "GroupBar",
@@ -3521,13 +3521,13 @@
3521
3521
  "slug": "ledger",
3522
3522
  "group": "instruments",
3523
3523
  "importPath": "@cueplusplus/ui",
3524
- "summary": "Grouped dense rows under sticky tier headings and gutter slugs, with no disclosure anywhere.",
3524
+ "summary": "Grouped dense rows under sticky tier headings and gutter slugs, with an opt-in disclosure that folds a heading without leaving the outline.",
3525
3525
  "status": "stable",
3526
3526
  "url": "/docs/components/ledger",
3527
3527
  "mdUrl": "/docs/components/ledger.md",
3528
3528
  "jsonUrl": "/r/components/ledger.json",
3529
3529
  "path": "./components/ledger.json",
3530
- "sha256": "129b47da8898b856d150ae7c6901e1e5340cabacd15457b860aa03e80139c388"
3530
+ "sha256": "52cc7f45dbf809e5be6eb04fc2420038143ee199d4f8bef29033d78197c20da6"
3531
3531
  },
3532
3532
  {
3533
3533
  "name": "LogViewer",
@@ -3631,7 +3631,7 @@
3631
3631
  "mdUrl": "/docs/components/table.md",
3632
3632
  "jsonUrl": "/r/components/table.json",
3633
3633
  "path": "./components/table.json",
3634
- "sha256": "56a6df96c2b040570356c0b1fc0049e754139dccf2faf0ccc20a8d4afab7297d"
3634
+ "sha256": "b4e6132a0cae4618117d374b3f9e0d5496d1b92128a1295d70337637261ad30d"
3635
3635
  },
3636
3636
  {
3637
3637
  "name": "TableScrollRegion",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "@cueplusplus/tokens",
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "themes": [
6
6
  "cue",
7
7
  "terminal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueplusplus/ui",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "CUE++ design system components: Tailwind v4 styled wrappers over Base UI, driven by @cueplusplus/tokens.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -140,7 +140,7 @@
140
140
  "clsx": "^2.1.1",
141
141
  "culori": "^4.0.2",
142
142
  "tailwind-merge": "^3.6.0",
143
- "@cueplusplus/tokens": "0.6.0"
143
+ "@cueplusplus/tokens": "0.8.0"
144
144
  },
145
145
  "peerDependencies": {
146
146
  "@assistant-ui/react": "^0.15.16",