@cueplusplus/ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/chrome/_drag.js +59 -0
  3. package/dist/chrome/_status-bar-item.js +85 -0
  4. package/dist/chrome/_status-bar.d.ts +15 -0
  5. package/dist/chrome/_status-bar.js +171 -0
  6. package/dist/chrome/app-shell.d.ts +69 -1
  7. package/dist/chrome/app-shell.js +38 -11
  8. package/dist/chrome/index.d.ts +3 -2
  9. package/dist/chrome/status-bar.d.ts +87 -4
  10. package/dist/chrome/status-bar.js +80 -16
  11. package/dist/chrome/title-bar.d.ts +11 -1
  12. package/dist/chrome/title-bar.js +1 -3
  13. package/dist/date/_segments.js +15 -4
  14. package/dist/forms/_chassis.d.ts +104 -4
  15. package/dist/forms/_chassis.js +104 -4
  16. package/dist/forms/input-group.d.ts +8 -3
  17. package/dist/forms/input-group.js +8 -3
  18. package/dist/forms/number-field.d.ts +3 -2
  19. package/dist/forms/number-field.js +3 -2
  20. package/dist/index.d.ts +8 -3
  21. package/dist/index.js +5 -1
  22. package/dist/instruments/_data-row.d.ts +68 -0
  23. package/dist/instruments/_data-row.js +139 -0
  24. package/dist/instruments/data-row.d.ts +94 -0
  25. package/dist/instruments/data-row.js +151 -0
  26. package/dist/instruments/data-tree.d.ts +126 -0
  27. package/dist/instruments/data-tree.js +303 -0
  28. package/dist/instruments/index.d.ts +6 -2
  29. package/dist/instruments/index.js +5 -1
  30. package/dist/instruments/ledger.d.ts +105 -0
  31. package/dist/instruments/ledger.js +114 -0
  32. package/dist/instruments/table.d.ts +108 -2
  33. package/dist/instruments/table.js +165 -39
  34. package/dist/theming/_presets.js +3 -3
  35. package/manifest/components/app-shell.json +45 -4
  36. package/manifest/components/autocomplete.json +2 -0
  37. package/manifest/components/color-field.json +2 -0
  38. package/manifest/components/color-picker.json +2 -0
  39. package/manifest/components/combobox.json +2 -0
  40. package/manifest/components/composer.json +2 -0
  41. package/manifest/components/data-row.json +208 -0
  42. package/manifest/components/data-tree.json +151 -0
  43. package/manifest/components/date-field.json +2 -0
  44. package/manifest/components/date-picker.json +2 -0
  45. package/manifest/components/date-range-picker.json +2 -0
  46. package/manifest/components/env-var-input.json +2 -0
  47. package/manifest/components/input-group.json +4 -2
  48. package/manifest/components/input.json +3 -1
  49. package/manifest/components/ledger.json +187 -0
  50. package/manifest/components/multi-select.json +2 -0
  51. package/manifest/components/musical-time-input.json +2 -0
  52. package/manifest/components/number-field.json +3 -1
  53. package/manifest/components/otp-field.json +2 -0
  54. package/manifest/components/password-input.json +2 -0
  55. package/manifest/components/scrub-input.json +2 -0
  56. package/manifest/components/search-input.json +2 -0
  57. package/manifest/components/select.json +1 -0
  58. package/manifest/components/status-bar.json +146 -12
  59. package/manifest/components/table-scroll-region.json +6 -0
  60. package/manifest/components/table.json +113 -5
  61. package/manifest/components/tags-input.json +2 -0
  62. package/manifest/components/textarea.json +3 -1
  63. package/manifest/components/time-field.json +2 -0
  64. package/manifest/components/title-bar.json +1 -1
  65. package/manifest/components/toggle-group.json +2 -0
  66. package/manifest/components/toggle.json +2 -0
  67. package/manifest/manifest.json +72 -33
  68. package/manifest/tokens.json +1 -1
  69. package/package.json +2 -2
@@ -34,12 +34,14 @@
34
34
  "tokensUsed": [
35
35
  "--cue-accent",
36
36
  "--cue-border",
37
+ "--cue-border-strong",
37
38
  "--cue-control-lg",
38
39
  "--cue-control-md",
39
40
  "--cue-control-sm",
40
41
  "--cue-danger",
41
42
  "--cue-fg",
42
43
  "--cue-fg-subtle",
44
+ "--cue-focus",
43
45
  "--cue-font-mono",
44
46
  "--cue-radius-control",
45
47
  "--cue-space-1",
@@ -130,12 +130,14 @@
130
130
  "tokensUsed": [
131
131
  "--cue-accent",
132
132
  "--cue-border",
133
+ "--cue-border-strong",
133
134
  "--cue-control-lg",
134
135
  "--cue-control-md",
135
136
  "--cue-control-sm",
136
137
  "--cue-danger",
137
138
  "--cue-fg",
138
139
  "--cue-fg-subtle",
140
+ "--cue-focus",
139
141
  "--cue-font-mono",
140
142
  "--cue-radius-control",
141
143
  "--cue-space-2",
@@ -144,6 +144,7 @@
144
144
  "--cue-accent-soft",
145
145
  "--cue-border",
146
146
  "--cue-border-overlay",
147
+ "--cue-border-strong",
147
148
  "--cue-chip-h",
148
149
  "--cue-control-lg",
149
150
  "--cue-control-md",
@@ -152,6 +153,7 @@
152
153
  "--cue-fg",
153
154
  "--cue-fg-muted",
154
155
  "--cue-fg-subtle",
156
+ "--cue-focus",
155
157
  "--cue-font-mono",
156
158
  "--cue-icon-md",
157
159
  "--cue-icon-sm",
@@ -130,12 +130,14 @@
130
130
  "tokensUsed": [
131
131
  "--cue-accent",
132
132
  "--cue-border",
133
+ "--cue-border-strong",
133
134
  "--cue-control-lg",
134
135
  "--cue-control-md",
135
136
  "--cue-control-sm",
136
137
  "--cue-danger",
137
138
  "--cue-fg",
138
139
  "--cue-fg-subtle",
140
+ "--cue-focus",
139
141
  "--cue-font-mono",
140
142
  "--cue-radius-control",
141
143
  "--cue-space-2",
@@ -0,0 +1,208 @@
1
+ {
2
+ "name": "DataRow",
3
+ "slug": "data-row",
4
+ "group": "instruments",
5
+ "importPath": "@cueplusplus/ui",
6
+ "peerDependencies": [],
7
+ "clientOnly": true,
8
+ "description": "The dense interactive row, as one anatomy three surfaces share.\n\n`Ledger`, a grouped `Table` and `DataTree` are three drawings of the same row\nmodel, and this is the model: a marker, a label, a description, tags, a\nfigure, and a trailing action cluster that is only there when you are looking\nat the row. Composing them yourself — rather than passing a `columns` array —\nis what keeps the primitive domain-neutral: a corpus of skills and a rig of\nfixtures need the same six slots and completely different columns.\n\nThe slots are thin: each is a `<span>` with a `data-slot`, the type and\ntruncation rule for its column, and nothing else. They may be used inside a\n`DataTree` row and inside a `Table.Cell` too — `Table.Row` carries the same\n`group/data-row` name, so `DataRow.Actions` reveals in the table idiom\nwithout a second implementation.",
9
+ "props": [],
10
+ "typeReferences": [],
11
+ "subcomponents": [
12
+ "Root",
13
+ "Marker",
14
+ "Label",
15
+ "Description",
16
+ "Tags",
17
+ "Meta",
18
+ "Actions"
19
+ ],
20
+ "parts": [
21
+ {
22
+ "name": "Root",
23
+ "usage": "DataRow.Root",
24
+ "description": "The row itself: a grid line of slots, hairline-separated from its neighbours.\n\nNot a `<button>`, which is the one decision everything else here follows\nfrom. A dense row carries its own controls — a copy button, a menu trigger, a\nlink to the thing it names — and no button may contain them. So the row is a\n`<div>` that earns its keyboard behaviour explicitly:\n`tabIndex`, Enter/Space, and a click reading that leaves nested controls and\nmodifier-clicked links alone (see `activateRowFromClick` in `_data-row.ts`).\n\nGeometry comes from the density row tokens and the column tracks from\n`--cue-data-row-cols`, which the caller sets — see `DATA_ROW_COLUMNS`. The\ncount of children must equal the count of tracks at every width: a template\none track short is exactly how the trailing action cluster ends up wrapped\nonto a phantom second row.\n\n**Tightening the controls inside a row** is a scoped custom-property retune,\nnot a prop. `IconButton`, `CopyButton` and `Avatar` at `size=\"sm\"` read\n`--cue-control-sm`, and `Chip` reads `--cue-chip-h`; redeclaring those two on\na wrapper around the list — `<div className=\"[--cue-control-sm:1.25rem] [--cue-chip-h:1rem]\">` —\nshrinks every control inside it and nothing outside it. A class on the button\ncannot do this: `size-control-sm` is a utility, and utilities are not shadowed\nby a rule with a longer selector. Custom properties inherit, which is what\nmakes them the only lever that reaches inside a component you did not write.",
25
+ "props": [
26
+ {
27
+ "name": "interactive",
28
+ "type": "boolean",
29
+ "required": false,
30
+ "defaultValue": "false",
31
+ "description": "The row responds to a pointer and to the keyboard: hover wash, `cursor`,\nan inset focus ring and a tab stop. Defaults to `false`."
32
+ },
33
+ {
34
+ "name": "selected",
35
+ "type": "boolean",
36
+ "required": false,
37
+ "defaultValue": "false",
38
+ "description": "Draw the row as picked out of the list. Defaults to `false`.\n\nSets `data-selected`, which is also what the hover-revealed action track and\nthe accent tint key off — so a `<tr data-selected>` in the table idiom gets\nexactly the same treatment without going through this component."
39
+ },
40
+ {
41
+ "name": "disabled",
42
+ "type": "boolean",
43
+ "required": false,
44
+ "defaultValue": "false",
45
+ "description": "Grey the row out and stop it responding to anything. Defaults to `false`."
46
+ },
47
+ {
48
+ "name": "onActivate",
49
+ "type": "(() => void)",
50
+ "required": false,
51
+ "defaultValue": null,
52
+ "description": "What \"open this row\" means — a drawer, a detail pane, a route."
53
+ },
54
+ {
55
+ "name": "disclosure",
56
+ "type": "DataRowDisclosure",
57
+ "required": false,
58
+ "defaultValue": null,
59
+ "description": "Expose the row as a disclosure: `data-expanded` always, `aria-expanded`\nwhen the row has a `role` that can carry it, and `aria-controls` when the\ndisclosed element is on the page.\n\n**`aria-expanded` is gated on the role, because on a bare `<div>` it is\nworse than nothing.** A role-less div maps to `generic`, which supports\nonly the global attributes — so the state is dropped outright by Blink,\nWebKit and Gecko alike (verified through Chromium's own accessibility\ntree), announces nothing, and adds a critical `aria-allowed-attr` finding\nto any axe run over the console that used it. `role` passes straight\nthrough as a div prop, so a row that genuinely is a disclosure says which\nkind it is — `role=\"button\"` for a row that opens a drawer, `role=\"row\"`\ninside a grid — and gets the attribute with it. Everything else gets\n`data-expanded`, which styles and tests exactly as well and promises\nnothing to a screen reader.\n\nThe row is still not given a widget role on its own account: a\n`role=\"button\"` row could not legally hold the buttons and links these rows\nexist to contain, and that judgement belongs to the caller who knows what\ntheir row does. Where the disclosure has to be bulletproof for assistive\ntechnology, reach for `DataTree` (real `treeitem` expansion) or a\n`Table.GroupRow` with a real toggle button in it."
60
+ }
61
+ ],
62
+ "typeReferences": [
63
+ "React.ComponentPropsWithoutRef<\"div\">"
64
+ ]
65
+ },
66
+ {
67
+ "name": "Marker",
68
+ "usage": "DataRow.Marker",
69
+ "description": "The leading mark: a tick, a status dot, a kind glyph. Never the label.",
70
+ "props": [],
71
+ "typeReferences": [
72
+ "React.ComponentPropsWithoutRef<\"span\">"
73
+ ]
74
+ },
75
+ {
76
+ "name": "Label",
77
+ "usage": "DataRow.Label",
78
+ "description": "The primary column: what the row *is*.\n\n`min-w-0 truncate` rather than a wrap, because a dense list is scanned down a\ncolumn: one row that grew to two lines breaks the scan for every row after it.",
79
+ "props": [],
80
+ "typeReferences": [
81
+ "React.ComponentPropsWithoutRef<\"span\">"
82
+ ]
83
+ },
84
+ {
85
+ "name": "Description",
86
+ "usage": "DataRow.Description",
87
+ "description": "The secondary column: the sentence under the name, one line, muted.",
88
+ "props": [],
89
+ "typeReferences": [
90
+ "React.ComponentPropsWithoutRef<\"span\">"
91
+ ]
92
+ },
93
+ {
94
+ "name": "Tags",
95
+ "usage": "DataRow.Tags",
96
+ "description": "A run of `Chip`s or other marks, kept on one line and allowed to clip.",
97
+ "props": [],
98
+ "typeReferences": [
99
+ "React.ComponentPropsWithoutRef<\"span\">"
100
+ ]
101
+ },
102
+ {
103
+ "name": "Meta",
104
+ "usage": "DataRow.Meta",
105
+ "description": "The figure column: a count, a version, a duration.\n\n`tabular-nums` for the same reason every other figure in this library is set\nthat way — a changing digit must not shift the column under the reader.",
106
+ "props": [],
107
+ "typeReferences": [
108
+ "React.ComponentPropsWithoutRef<\"span\">"
109
+ ]
110
+ },
111
+ {
112
+ "name": "Actions",
113
+ "usage": "DataRow.Actions",
114
+ "description": "The trailing controls, revealed on hover, on focus and on selection.\n\nThree mechanisms, each guarding a specific failure:\n\n- **`invisible`, not `hidden`.** `visibility` keeps the cluster's box, so the\n track it sits in is the same width at rest as it is revealed and nothing\n in the row moves when the pointer arrives. `display: none` frees the space\n and every column to its left slides on hover.\n- **`focus-within` reveals it too, and the row's own focus is what triggers\n it.** `:focus-within` matches the element that *has* focus as well as one\n that contains it, and that ordering is what keeps these controls reachable\n at all: content inside a `visibility: hidden` subtree is not focusable, so\n Tab could never enter a track that only appeared once something inside it\n was focused. The row takes focus, the track appears, and Tab walks into it.\n (Which is also why an interactive row is the shape to use when the row has\n actions: on a static row nothing takes focus first.)\n- **`@media (hover: none)` shows it outright.** On a touch screen there is no\n hover to reveal anything with, so the cluster is simply always there. This\n is the `hover: none` query rather than `pointer: coarse` on purpose: what\n the reveal depends on is hovering, not the pointer's precision, and a\n trackpad-and-touchscreen laptop has both.\n\n**The host has to supply the focus, and not every host does.** Because the\ntrack cannot be entered until something outside it takes focus, a row with no\nfocusable element outside the cluster puts these controls beyond a keyboard\nuser entirely — visible to a mouse, unreachable by Tab, WCAG 2.1.1. An\ninteractive `DataRow.Root` and a `DataTree` `treeitem` are focusable rows and\nsatisfy this on their own; a `<tr>` is not, so a `Table.Row` holding this\ncluster needs a real focusable element of its own in another cell — the row's\nname as a link is the usual one, and `Table.Row`'s own note says so too.\n\n`data-stop` is the row's opt-out marker: a click anywhere inside this cluster\nnever opens the row (see `activateRowFromClick` in `_data-row.ts`).",
115
+ "props": [],
116
+ "typeReferences": [
117
+ "React.ComponentPropsWithoutRef<\"span\">"
118
+ ]
119
+ }
120
+ ],
121
+ "variants": {},
122
+ "defaultVariants": {},
123
+ "tokensUsed": [
124
+ "--cue-accent",
125
+ "--cue-accent-soft",
126
+ "--cue-chip-h",
127
+ "--cue-control-sm",
128
+ "--cue-data-row-cols",
129
+ "--cue-data-tree-indent",
130
+ "--cue-fg",
131
+ "--cue-fg-muted",
132
+ "--cue-fg-subtle",
133
+ "--cue-font-mono",
134
+ "--cue-hair",
135
+ "--cue-pad-row-x",
136
+ "--cue-pad-row-y",
137
+ "--cue-row-hover",
138
+ "--cue-space-1",
139
+ "--cue-space-3",
140
+ "--cue-text-label",
141
+ "--cue-text-ui"
142
+ ],
143
+ "summary": "The dense list row: six slots, a hover-revealed action track, and columns the caller declares.",
144
+ "examples": [
145
+ {
146
+ "title": "Declaring the tracks",
147
+ "code": "<Ledger.Root className=\"[--cue-data-row-cols:1.5rem_minmax(8rem,1fr)_6rem_auto]\">\n <DataRow.Root interactive onActivate={() => open(device)}>\n <DataRow.Marker>\n <StatusDot tone=\"ok\" />\n </DataRow.Marker>\n <DataRow.Label>{device.name}</DataRow.Label>\n <DataRow.Meta>{device.latency}</DataRow.Meta>\n <DataRow.Actions>\n <CopyButton value={device.id} size=\"sm\" className=\"w-control-sm px-0\" />\n <IconButton icon={MoreVertical} size=\"sm\" aria-label={`Actions for ${device.name}`} />\n </DataRow.Actions>\n </DataRow.Root>\n</Ledger.Root>",
148
+ "language": "tsx"
149
+ },
150
+ {
151
+ "title": "The container, named",
152
+ "code": "<Ledger.Root className=\"patch [container-name:patch]\">…</Ledger.Root>",
153
+ "language": "tsx"
154
+ },
155
+ {
156
+ "title": "corpus.css",
157
+ "code": "@container patch (max-width: 46rem) {\n .patch [data-slot=\"data-row\"] {\n --cue-data-row-cols: 1.5rem minmax(8rem, 1fr) auto;\n }\n .patch [data-slot=\"data-row-description\"] {\n display: none;\n }\n}",
158
+ "language": "css"
159
+ },
160
+ {
161
+ "title": "The same decision, on a table row",
162
+ "code": "import { activateRowFromClick, activateRowFromKeyDown } from \"@cueplusplus/ui\";\n\n<Table.Row\n interactive\n tabIndex={0}\n onClick={(event) => activateRowFromClick(event, () => open(device))}\n onKeyDown={(event) => activateRowFromKeyDown(event, () => open(device))}\n>",
163
+ "language": "tsx"
164
+ },
165
+ {
166
+ "title": "A density island for the list, not for the app",
167
+ "code": "<div className=\"[--cue-chip-h:1rem] [--cue-control-sm:1.25rem]\">\n <Ledger.Root>…</Ledger.Root>\n</div>",
168
+ "language": "tsx"
169
+ },
170
+ {
171
+ "title": "Usage",
172
+ "code": "<DataRow.Root interactive onActivate={() => open(skill)}>\n <DataRow.Marker><StatusDot tone=\"ok\" /></DataRow.Marker>\n <DataRow.Label>{skill.name}</DataRow.Label>\n <DataRow.Meta>{skill.installs}</DataRow.Meta>\n</DataRow.Root>",
173
+ "language": "tsx"
174
+ }
175
+ ],
176
+ "status": "stable",
177
+ "url": "/docs/components/data-row",
178
+ "mdUrl": "/docs/components/data-row.md",
179
+ "jsonUrl": "/r/components/data-row.json",
180
+ "whenToUse": [
181
+ "A list of hundreds of records that is scanned down a column and opened one at a time — a corpus, a patch, a run log.",
182
+ "Any row that has to look identical in a `Ledger`, a grouped `Table` and a `DataTree`, because all three draw this anatomy."
183
+ ],
184
+ "whenNotToUse": [
185
+ "A panel's label-and-value lines. Use `Row`, which is a real `<button>` when it acts and needs no column tracks.",
186
+ "One object's fields. Use `DescriptionList`.",
187
+ "A row whose only job is to run one action. Use a `Button` — this row exists because rows contain controls, and a row that has just one should be the control."
188
+ ],
189
+ "commonMistakes": [
190
+ "Rendering more children than the tracks in `--cue-data-row-cols`. The extra child wraps onto a phantom second row, which is what the count rule exists to prevent.",
191
+ "Shedding a column by branching `className` in JSX. A utility outranks the `@container` rule meant to hide it, so the child survives while the template loses its track — declare the shed child's `display` in the stylesheet that changes the template.",
192
+ "Putting a `@container` rule's subject on `Ledger.Root` itself. It is the size container, and nothing is its own container — that rule never matches, while the `display: none` rule on the slots inside does, so the row sheds a child and keeps its track. Target a descendant: `.patch [data-slot=\"data-row\"]`.",
193
+ "Shrinking the controls inside rows with `h-5 w-5` on each button. Redeclare `--cue-control-sm` and `--cue-chip-h` on a wrapper instead; custom properties inherit and reach inside components you did not write.",
194
+ "Wrapping the row in a `<button>` or giving it `role=\"button\"` to get keyboard behaviour. It already has Enter and Space, and a button may not contain the row's own buttons and links."
195
+ ],
196
+ "specimens": [
197
+ {
198
+ "title": "DataRow",
199
+ "group": "instruments",
200
+ "components": [
201
+ "DataRow"
202
+ ],
203
+ "code": "<Panel className={`w-full ${PATCH_TRACKS}`}>\n {PATCH_DEVICES.slice(0, 3).map((device, index) => (\n <DataRow.Root key={device.id} interactive selected={index === 1}>\n <PatchRowSlots device={device} />\n </DataRow.Root>\n ))}\n <DataRow.Root disabled>\n <PatchRowSlots device={{ ...PATCH_DEVICES[4], name: \"Offline monitor\" }} />\n </DataRow.Root>\n</Panel>",
204
+ "note": "The row anatomy on its own: a marker, a name, a sentence, a figure and a trailing action track. The column tracks are the caller's, set as `--cue-data-row-cols` — the primitive never knows the dataset.",
205
+ "interaction": "hover, focus-within and selection each reveal the action track; it is hidden with `visibility`, so revealing it moves nothing else in the row. On a touch screen it is simply always there."
206
+ }
207
+ ]
208
+ }
@@ -0,0 +1,151 @@
1
+ {
2
+ "name": "DataTree",
3
+ "slug": "data-tree",
4
+ "group": "instruments",
5
+ "importPath": "@cueplusplus/ui",
6
+ "peerDependencies": [],
7
+ "clientOnly": true,
8
+ "description": "A hierarchy of multi-column rows: the tree idiom over the `DataRow` anatomy.\n\n## The ARIA pattern, chosen before the component was written\n\n`role=\"treegrid\"` is **rejected** for this foundation. It is the semantically\nexact answer — tabular *and* hierarchical — and it is the one with the worst\nreal-world assistive-technology record: Adrian Roselli's survey of expando\ntables calls its support poor and notes that the APG's own worked example\ncarries unresolved issues; fancytree's maintainer describes treegrid support\nas unproven ground; MUI X shipped hierarchical rows with no `aria-level` /\n`aria-expanded` / `aria-setsize` at all and retrofitted them later. Treegrid\nearns its cost when cells are individually interactive or editable, because\nthen a reader genuinely needs two-dimensional cell navigation. This surface is\nread-and-navigate: you move down rows, fold a branch, and open the thing a row\nnames.\n\nSo the pattern is `role=\"tree\"` with `role=\"treeitem\"` rows — VS Code's\nchoice and Carbon's, and the best-supported composite pattern in ARIA:\n\n- A **flat DOM** with `aria-level` / `aria-posinset` / `aria-setsize` per row\n rather than nested `role=\"group\"` wrappers.\n- **Roving tabindex**: the tree is one Tab stop and the arrows move inside it.\n- The **APG tree keyboard map**: Up/Down between visible rows, Home/End to the\n ends, Right to unfold then to descend, Left to fold then to ascend,\n Enter/Space to select.\n- Expansion lives on the **row**, as `aria-expanded` on the `treeitem` — that\n is what tree semantics mean by a parent, and it is why the chevron here is\n `role=\"presentation\"` rather than a button. (The other half of this trade\n is `Table.GroupRow`, where the table's semantics own the grouping and a real\n `<button aria-expanded>` owns the disclosure. Two patterns, each internally\n consistent; what would be wrong is half of each.)\n\n**The documented trade**: columns are not independently navigable. A row's\nmulti-column content lives *inside* the treeitem, so a screen-reader user\nhears the row as one string rather than moving cell to cell through it. For a\ndense browse-and-open surface that is the better bargain — `tree` works today\non every screen reader, `treegrid` does not — but a surface whose cells are\ngenuinely editable has outgrown this component and wants a real treegrid from\na library that maintains one.\n\n## Everything else\n\nRows are the `DataRow` chassis: same padding, same hairline, same hover wash,\nsame inset focus ring, same `group/data-row`, so `DataRow.Actions` reveals in\na tree exactly as it does in a ledger. The row element itself is the\ntreeitem — one element owns focus, expansion, selection and the click reading\n— and the caller's `DataRow.*` slots are its content.\n\nA tree whose items declare no children anywhere is a flat list: no chevrons,\nno spacer column where a chevron would be, no guides, and `aria-expanded` on\nnothing. A leaf row that carried `aria-expanded=\"false\"` would tell a screen\nreader it has children it does not have.",
9
+ "props": [
10
+ {
11
+ "name": "items",
12
+ "type": "readonly DataTreeItem[]",
13
+ "required": true,
14
+ "defaultValue": null,
15
+ "description": "The roots, in display order."
16
+ },
17
+ {
18
+ "name": "expanded",
19
+ "type": "readonly string[]",
20
+ "required": false,
21
+ "defaultValue": null,
22
+ "description": "Expanded ids when controlled. Pass with `onExpandedChange`."
23
+ },
24
+ {
25
+ "name": "defaultExpanded",
26
+ "type": "readonly string[]",
27
+ "required": false,
28
+ "defaultValue": null,
29
+ "description": "Initially expanded ids when uncontrolled."
30
+ },
31
+ {
32
+ "name": "onExpandedChange",
33
+ "type": "((expanded: string[]) => void)",
34
+ "required": false,
35
+ "defaultValue": null,
36
+ "description": "Called with the whole next expanded set whenever a row is folded or unfolded."
37
+ },
38
+ {
39
+ "name": "selected",
40
+ "type": "string | null",
41
+ "required": false,
42
+ "defaultValue": null,
43
+ "description": "Selected id when controlled. Pass with `onSelect`."
44
+ },
45
+ {
46
+ "name": "defaultSelected",
47
+ "type": "string | null",
48
+ "required": false,
49
+ "defaultValue": "null",
50
+ "description": "Initially selected id when uncontrolled."
51
+ },
52
+ {
53
+ "name": "onSelect",
54
+ "type": "((id: string) => void)",
55
+ "required": false,
56
+ "defaultValue": null,
57
+ "description": "Called with the id of the row the user selected."
58
+ },
59
+ {
60
+ "name": "guides",
61
+ "type": "boolean",
62
+ "required": false,
63
+ "defaultValue": "true",
64
+ "description": "Draw the depth guides. Defaults to `true`.\n\nRoots never draw one — there is no ancestor to draw — so a flat tree draws\nnone at all whatever this is set to."
65
+ },
66
+ {
67
+ "name": "aria-label",
68
+ "type": "string",
69
+ "required": false,
70
+ "defaultValue": null,
71
+ "description": "Accessible name for the tree. Required in practice — name what it lists."
72
+ }
73
+ ],
74
+ "typeReferences": [
75
+ "Omit<React.ComponentPropsWithoutRef<\"div\">, \"onSelect\">"
76
+ ],
77
+ "subcomponents": [],
78
+ "parts": [],
79
+ "variants": {},
80
+ "defaultVariants": {},
81
+ "tokensUsed": [
82
+ "--cue-accent",
83
+ "--cue-accent-soft",
84
+ "--cue-data-row-cols",
85
+ "--cue-data-tree-indent",
86
+ "--cue-fg",
87
+ "--cue-fg-subtle",
88
+ "--cue-font-mono",
89
+ "--cue-hair",
90
+ "--cue-icon-sm",
91
+ "--cue-pad-row-x",
92
+ "--cue-pad-row-y",
93
+ "--cue-row-hover",
94
+ "--cue-space-3",
95
+ "--cue-text-ui"
96
+ ],
97
+ "summary": "A hierarchy of multi-column rows under real `role=\"tree\"` semantics, with CSS depth guides.",
98
+ "examples": [
99
+ {
100
+ "title": "Items carry rows, not labels",
101
+ "code": "const items: DataTreeItem[] = groups.map((group) => ({\n id: group.id,\n row: <DataRow.Label>{group.label}</DataRow.Label>,\n children: group.rows.map((device) => ({\n id: device.id,\n row: (\n <>\n <DataRow.Label>{device.name}</DataRow.Label>\n <DataRow.Meta>{device.latency}</DataRow.Meta>\n </>\n ),\n })),\n}));\n\n<DataTree\n aria-label=\"Patch\"\n className=\"[--cue-data-row-cols:1fr_6rem]\"\n items={items}\n defaultExpanded={[\"inputs\"]}\n selected={selected}\n onSelect={setSelected}\n/>;",
102
+ "language": "tsx"
103
+ },
104
+ {
105
+ "title": "The container, named",
106
+ "code": "<div className=\"patch [container-type:inline-size] [container-name:patch]\">\n <DataTree … />\n</div>",
107
+ "language": "tsx"
108
+ },
109
+ {
110
+ "title": "Narrow the step before you drop the guides",
111
+ "code": "@container patch (max-width: 32rem) {\n .patch [data-slot=\"data-tree\"] {\n --cue-data-tree-indent: 0.75rem;\n }\n}",
112
+ "language": "css"
113
+ },
114
+ {
115
+ "title": "Usage",
116
+ "code": "<DataTree\n aria-label=\"Patch\"\n items={nodes}\n defaultExpanded={[\"inputs\"]}\n selected={selected}\n onSelect={setSelected}\n className=\"[--cue-data-row-cols:1fr_8rem_auto]\"\n/>",
117
+ "language": "tsx"
118
+ }
119
+ ],
120
+ "status": "stable",
121
+ "url": "/docs/components/data-tree",
122
+ "mdUrl": "/docs/components/data-tree.md",
123
+ "jsonUrl": "/r/components/data-tree.json",
124
+ "whenToUse": [
125
+ "A grouped list the reader folds and unfolds, where each row still carries columns — a patch by bus, a corpus by stack, a plan by phase.",
126
+ "A hierarchy that has to keep working for a screen-reader user today: this is the `tree` pattern, which is the best-supported composite pattern in ARIA."
127
+ ],
128
+ "whenNotToUse": [
129
+ "A single-column outline of labels with a glyph gutter. Use `Tree`, which is that component.",
130
+ "A grouped list nobody folds. Use `Ledger` or a grouped `Table`; disclosure semantics you never use are a promise you never keep.",
131
+ "A grid whose cells are individually editable. That needs a real `treegrid` from a library that maintains one — this component trades cell navigation away on purpose."
132
+ ],
133
+ "commonMistakes": [
134
+ "Expecting cell-by-cell arrow navigation. Columns live inside the treeitem, so a row is announced as one string; that is the documented trade for the support `tree` has and `treegrid` does not.",
135
+ "Giving a leaf `children: []` to make it look like the others. An empty array *is* a parent — an empty folder — and it will carry `aria-expanded`.",
136
+ "Passing `expanded` without `onExpandedChange`, which freezes the tree: controlled means the caller owns the next state.",
137
+ "Fighting the guides with a row background. They are a `background-image` and survive hover and selection fills by design; switch them off with `guides={false}` instead."
138
+ ],
139
+ "specimens": [
140
+ {
141
+ "title": "DataTree",
142
+ "group": "instruments",
143
+ "components": [
144
+ "DataTree"
145
+ ],
146
+ "code": "<Panel className={`w-full ${PATCH_TRACKS}`}>\n <DataTree\n aria-label=\"Patch\"\n items={PATCH_TREE}\n defaultExpanded={[\"inputs\"]}\n defaultSelected=\"scarlett\"\n />\n</Panel>",
147
+ "note": "The same rows as a hierarchy: `role='tree'` with `treeitem` rows, chosen over `treegrid` for the support it actually has. Guides are a background gradient keyed off the row's depth, so a hover or selection fill cannot cover them.",
148
+ "interaction": "one tab stop: arrows move between rows, Right unfolds then descends, Left folds then climbs, Enter selects."
149
+ }
150
+ ]
151
+ }
@@ -35,12 +35,14 @@
35
35
  "--cue-accent",
36
36
  "--cue-accent-fg",
37
37
  "--cue-border",
38
+ "--cue-border-strong",
38
39
  "--cue-control-lg",
39
40
  "--cue-control-md",
40
41
  "--cue-control-sm",
41
42
  "--cue-danger",
42
43
  "--cue-fg",
43
44
  "--cue-fg-subtle",
45
+ "--cue-focus",
44
46
  "--cue-font-mono",
45
47
  "--cue-radius-control",
46
48
  "--cue-space-1",
@@ -145,12 +145,14 @@
145
145
  "tokensUsed": [
146
146
  "--cue-accent",
147
147
  "--cue-border",
148
+ "--cue-border-strong",
148
149
  "--cue-control-lg",
149
150
  "--cue-control-md",
150
151
  "--cue-control-sm",
151
152
  "--cue-danger",
152
153
  "--cue-fg",
153
154
  "--cue-fg-subtle",
155
+ "--cue-focus",
154
156
  "--cue-font-mono",
155
157
  "--cue-icon-sm",
156
158
  "--cue-radius-control",
@@ -145,12 +145,14 @@
145
145
  "tokensUsed": [
146
146
  "--cue-accent",
147
147
  "--cue-border",
148
+ "--cue-border-strong",
148
149
  "--cue-control-lg",
149
150
  "--cue-control-md",
150
151
  "--cue-control-sm",
151
152
  "--cue-danger",
152
153
  "--cue-fg",
153
154
  "--cue-fg-subtle",
155
+ "--cue-focus",
154
156
  "--cue-font-mono",
155
157
  "--cue-icon-sm",
156
158
  "--cue-radius-control",
@@ -102,12 +102,14 @@
102
102
  "tokensUsed": [
103
103
  "--cue-accent",
104
104
  "--cue-border",
105
+ "--cue-border-strong",
105
106
  "--cue-control-lg",
106
107
  "--cue-control-md",
107
108
  "--cue-control-sm",
108
109
  "--cue-danger",
109
110
  "--cue-fg",
110
111
  "--cue-fg-subtle",
112
+ "--cue-focus",
111
113
  "--cue-font-mono",
112
114
  "--cue-icon-md",
113
115
  "--cue-icon-sm",
@@ -5,7 +5,7 @@
5
5
  "importPath": "@cueplusplus/ui",
6
6
  "peerDependencies": [],
7
7
  "clientOnly": true,
8
- "description": "A control and its adornments, drawn as a single field.\n\nThe group — not the input — owns the chassis, so there is exactly one rim and\none fill however many affordances are bolted on. It publishes that fact\nthrough context: an {@link Input } rendered inside strips its own border,\nbackground, height and padding and inherits the group's font size. Focus\nfeedback moves to `focus-within`, since a `<div>` never matches\n`:focus-visible`.",
8
+ "description": "A control and its adornments, drawn as a single field.\n\nThe group — not the input — owns the chassis, so there is exactly one rim and\none fill however many affordances are bolted on. It publishes that fact\nthrough context: an {@link Input } rendered inside strips its own border,\nbackground, height and padding and inherits the group's font size.\n\nFocus feedback splits in two, because a `<div>` matches neither `:focus` nor\n`:focus-visible` itself. The rim strengthens on `:focus-within` — anything\nfocused inside, however it got there — while the ring waits for\n`:has(:focus-visible)`, so a mouse click into the field does not light the\nwhole cluster up. Both halves live on the wrapper; the inner control paints\nno ring of its own.",
9
9
  "props": [
10
10
  {
11
11
  "name": "leading",
@@ -46,12 +46,14 @@
46
46
  "tokensUsed": [
47
47
  "--cue-accent",
48
48
  "--cue-border",
49
+ "--cue-border-strong",
49
50
  "--cue-control-lg",
50
51
  "--cue-control-md",
51
52
  "--cue-control-sm",
52
53
  "--cue-danger",
53
54
  "--cue-fg",
54
55
  "--cue-fg-subtle",
56
+ "--cue-focus",
55
57
  "--cue-font-mono",
56
58
  "--cue-radius-control",
57
59
  "--cue-space-2",
@@ -84,7 +86,7 @@
84
86
  ],
85
87
  "code": "<Specimens>\n <Specimen label=\"leading\">\n <InputGroup leading={<Plug aria-hidden=\"true\" className=\"size-icon-md\" />}>\n <Input aria-label=\"Node address\" defaultValue=\"10.0.0.42\" />\n </InputGroup>\n </Specimen>\n <Specimen label=\"trailing\">\n <InputGroup trailing={<span className=\"font-mono text-fg-subtle\">%</span>}>\n <Input aria-label=\"Level\" defaultValue=\"80\" />\n </InputGroup>\n </Specimen>\n <Specimen label=\"both, sm\">\n <InputGroup\n size=\"sm\"\n leading={<Ruler aria-hidden=\"true\" className=\"size-icon-sm\" />}\n trailing={<span className=\"font-mono text-fg-subtle\">m</span>}\n >\n <Input aria-label=\"Throw distance\" defaultValue=\"7.5\" />\n </InputGroup>\n </Specimen>\n</Specimens>",
86
88
  "note": "One rim around a control and its adornments, so the group focuses as a unit.",
87
- "interaction": "focus inside the group lights the whole rim, not just the input."
89
+ "interaction": "any focus inside strengthens the whole rim; the ring waits for a keyboard-visible descendant, so a click on an adornment does not light the cluster up. The inner control never rings — the group owns the rim."
88
90
  }
89
91
  ],
90
92
  "todo": true
@@ -32,12 +32,14 @@
32
32
  "tokensUsed": [
33
33
  "--cue-accent",
34
34
  "--cue-border",
35
+ "--cue-border-strong",
35
36
  "--cue-control-lg",
36
37
  "--cue-control-md",
37
38
  "--cue-control-sm",
38
39
  "--cue-danger",
39
40
  "--cue-fg",
40
41
  "--cue-fg-subtle",
42
+ "--cue-focus",
41
43
  "--cue-font-mono",
42
44
  "--cue-radius-control",
43
45
  "--cue-space-3",
@@ -83,7 +85,7 @@
83
85
  ],
84
86
  "code": "<Specimens>\n {SIZES.map((size) => (\n <Specimen key={size} label={size}>\n <Input aria-label={`Cue label, ${size}`} size={size} defaultValue=\"1/12 @ 80%\" />\n </Specimen>\n ))}\n <Specimen label=\"placeholder\">\n <Input aria-label=\"Empty cue label\" placeholder=\"e.g. Blackout\" />\n </Specimen>\n <Specimen label=\"disabled\">\n <Input aria-label=\"Disabled cue label\" defaultValue=\"Read-only\" disabled />\n </Specimen>\n <Specimen label=\"readonly\">\n <Input aria-label=\"Read-only cue label\" defaultValue=\"1/12 @ 80%\" readOnly />\n </Specimen>\n</Specimens>",
85
87
  "note": "The recessed chassis: sunken fill, hairline rim, height from the density ladder.",
86
- "interaction": "the rim goes accent on focus and the ring sits outside it; `invalid` turns the rim danger and outranks focus."
88
+ "interaction": "the rim strengthens one rung on focus and a single flush ring in the theme's focus colour sits just inside it — clicking in shows the same ring as tabbing in, because a typing surface is always focus-visible; `invalid` keeps the danger rim and the ring stays visible beside it."
87
89
  }
88
90
  ]
89
91
  }