@cueplusplus/ui 0.5.0 → 0.6.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.
- package/CHANGELOG.md +34 -0
- package/dist/chrome/_drag.js +59 -0
- package/dist/chrome/_status-bar-item.js +85 -0
- package/dist/chrome/_status-bar.d.ts +15 -0
- package/dist/chrome/_status-bar.js +171 -0
- package/dist/chrome/app-shell.d.ts +69 -1
- package/dist/chrome/app-shell.js +38 -11
- package/dist/chrome/index.d.ts +3 -2
- package/dist/chrome/status-bar.d.ts +87 -4
- package/dist/chrome/status-bar.js +80 -16
- package/dist/chrome/title-bar.d.ts +11 -1
- package/dist/chrome/title-bar.js +1 -3
- package/dist/date/_segments.js +15 -4
- package/dist/forms/_chassis.d.ts +104 -4
- package/dist/forms/_chassis.js +104 -4
- package/dist/forms/input-group.d.ts +8 -3
- package/dist/forms/input-group.js +8 -3
- package/dist/forms/number-field.d.ts +3 -2
- package/dist/forms/number-field.js +3 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -1
- package/dist/instruments/_data-row.js +118 -0
- package/dist/instruments/data-row.d.ts +94 -0
- package/dist/instruments/data-row.js +151 -0
- package/dist/instruments/data-tree.d.ts +126 -0
- package/dist/instruments/data-tree.js +303 -0
- package/dist/instruments/index.d.ts +5 -2
- package/dist/instruments/index.js +4 -1
- package/dist/instruments/ledger.d.ts +105 -0
- package/dist/instruments/ledger.js +114 -0
- package/dist/instruments/table.d.ts +70 -2
- package/dist/instruments/table.js +133 -39
- package/dist/theming/_presets.js +3 -3
- package/manifest/components/app-shell.json +45 -4
- package/manifest/components/autocomplete.json +2 -0
- package/manifest/components/color-field.json +2 -0
- package/manifest/components/color-picker.json +2 -0
- package/manifest/components/combobox.json +2 -0
- package/manifest/components/composer.json +2 -0
- package/manifest/components/data-row.json +203 -0
- package/manifest/components/data-tree.json +151 -0
- package/manifest/components/date-field.json +2 -0
- package/manifest/components/date-picker.json +2 -0
- package/manifest/components/date-range-picker.json +2 -0
- package/manifest/components/env-var-input.json +2 -0
- package/manifest/components/input-group.json +4 -2
- package/manifest/components/input.json +3 -1
- package/manifest/components/ledger.json +187 -0
- package/manifest/components/multi-select.json +2 -0
- package/manifest/components/musical-time-input.json +2 -0
- package/manifest/components/number-field.json +3 -1
- package/manifest/components/otp-field.json +2 -0
- package/manifest/components/password-input.json +2 -0
- package/manifest/components/scrub-input.json +2 -0
- package/manifest/components/search-input.json +2 -0
- package/manifest/components/select.json +1 -0
- package/manifest/components/status-bar.json +146 -12
- package/manifest/components/table-scroll-region.json +6 -0
- package/manifest/components/table.json +88 -4
- package/manifest/components/tags-input.json +2 -0
- package/manifest/components/textarea.json +3 -1
- package/manifest/components/time-field.json +2 -0
- package/manifest/components/title-bar.json +1 -1
- package/manifest/components/toggle-group.json +2 -0
- package/manifest/components/toggle.json +2 -0
- package/manifest/manifest.json +72 -33
- package/manifest/tokens.json +1 -1
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/instruments/table.tsx
|
|
5
5
|
/**
|
|
6
6
|
* A named horizontal viewport for a table wider than its container.
|
|
@@ -20,6 +20,108 @@ const TableScrollRegion = React.forwardRef(function TableScrollRegion({ classNam
|
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
/**
|
|
23
|
+
* The table frame. Owns the type, the collapse and the numeric alignment rule.
|
|
24
|
+
*
|
|
25
|
+
* `data-numeric` is the contract, not a prop: the alignment and
|
|
26
|
+
* `tabular-nums` live in one descendant selector here, so a hand-written
|
|
27
|
+
* `<td data-numeric>` inside a data-driven body lines up exactly like a
|
|
28
|
+
* `<Table.Cell numeric>`. Figures that do not share a column width are
|
|
29
|
+
* unreadable as a column, which is the whole point of a table.
|
|
30
|
+
*/
|
|
31
|
+
const TableRoot = React.forwardRef(function TableRoot({ className, ...elementProps }, ref) {
|
|
32
|
+
return /* @__PURE__ */ jsx("table", {
|
|
33
|
+
ref,
|
|
34
|
+
"data-slot": "table",
|
|
35
|
+
className: cn("w-full border-collapse font-mono text-(length:--cue-text-ui) text-fg", "[&_[data-numeric]]:text-right [&_[data-numeric]]:tabular-nums", className),
|
|
36
|
+
...elementProps
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
/** The header band. Sticky-able by the caller; sized by the row padding tokens. */
|
|
40
|
+
const TableHeader = React.forwardRef(function TableHeader({ className, ...elementProps }, ref) {
|
|
41
|
+
return /* @__PURE__ */ jsx("thead", {
|
|
42
|
+
ref,
|
|
43
|
+
"data-slot": "table-header",
|
|
44
|
+
className,
|
|
45
|
+
...elementProps
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
/** The data band. */
|
|
49
|
+
const TableBody = React.forwardRef(function TableBody({ className, ...elementProps }, ref) {
|
|
50
|
+
return /* @__PURE__ */ jsx("tbody", {
|
|
51
|
+
ref,
|
|
52
|
+
"data-slot": "table-body",
|
|
53
|
+
className,
|
|
54
|
+
...elementProps
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* One row, separated by a hairline underneath — dropped on the last row so the
|
|
59
|
+
* table never draws a rule against the panel rim it sits in. In a `<thead>` the
|
|
60
|
+
* "last row" is the header row itself, so the header's own bottom border (on
|
|
61
|
+
* the cells) is the only rule there and the two can never stack into 2px.
|
|
62
|
+
*
|
|
63
|
+
* Every row carries the `group/data-row` name unconditionally. It paints
|
|
64
|
+
* nothing by itself, and it is what lets a `DataRow.Actions` cluster inside a
|
|
65
|
+
* `Table.Cell` reveal on hover, on focus and on selection with no second
|
|
66
|
+
* implementation of that idiom for the table — the whole point of the three
|
|
67
|
+
* idioms sharing one row anatomy.
|
|
68
|
+
*
|
|
69
|
+
* **The focus half of that reveal is the caller's to supply here.** A `<tr>` is
|
|
70
|
+
* not focusable and this component does not make it one — a tab stop on every
|
|
71
|
+
* row of a long table is a tab stop a keyboard user has to walk past hundreds
|
|
72
|
+
* of times, which is the reason `Ledger` rows opt in individually. So the
|
|
73
|
+
* reveal-on-focus only happens if the row *contains* something focusable
|
|
74
|
+
* outside the hidden track: a link on the row's name, a checkbox in the leading
|
|
75
|
+
* cell, any real control. Without one, `:focus-within` can never fire — content
|
|
76
|
+
* inside a `visibility: hidden` subtree cannot be focused, so the track cannot
|
|
77
|
+
* bootstrap its own reveal — and a hidden action cluster is then reachable by
|
|
78
|
+
* pointer only, which is a WCAG 2.1.1 failure the screen gives no sign of. A
|
|
79
|
+
* table whose only per-row controls live in a hidden track wants either a
|
|
80
|
+
* focusable cell beside it or an always-visible cluster.
|
|
81
|
+
*
|
|
82
|
+
* The selected tint is the row's, not the cell's: a `background` on the `<tr>`
|
|
83
|
+
* shows through every cell in it, so the fill cannot end up ragged where one
|
|
84
|
+
* cell sets a ground of its own.
|
|
85
|
+
*/
|
|
86
|
+
const TableRow = React.forwardRef(function TableRow({ className, interactive = false, selected = false, ...elementProps }, ref) {
|
|
87
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
88
|
+
ref,
|
|
89
|
+
"data-slot": "table-row",
|
|
90
|
+
"data-selected": selected ? "" : void 0,
|
|
91
|
+
className: cn("group/data-row border-b border-border last:border-b-0 data-[selected]:bg-accent-soft", interactive ? "cursor-pointer hover:bg-(--cue-row-hover)" : null, className),
|
|
92
|
+
...elementProps
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* A column heading: the same mono uppercase label as `SectionHeader` and
|
|
97
|
+
* `Eyebrow`, on the raised surface so the band reads as chrome rather than as
|
|
98
|
+
* the first row of data.
|
|
99
|
+
*
|
|
100
|
+
* **Sticky swaps the bottom border for an inset shadow.** `Table.Root` collapses
|
|
101
|
+
* its borders, and a collapsed border belongs to the table's shared grid rather
|
|
102
|
+
* than to the cell — so when a cell detaches to stay at the top of the scroll
|
|
103
|
+
* container, its rule stays behind with the rows and the stuck band appears to
|
|
104
|
+
* lose its edge. An inset `box-shadow` is a paint effect on the cell itself, so
|
|
105
|
+
* it travels with it. The alternative — putting the whole table on
|
|
106
|
+
* `border-separate` — would change the geometry of every table in the library
|
|
107
|
+
* to fix one that scrolls.
|
|
108
|
+
*
|
|
109
|
+
* The opaque `bg-surface-1` is not decoration either: a transparent sticky cell
|
|
110
|
+
* is a sticky cell you read the rows through. A caller who wants the header on
|
|
111
|
+
* the page's own ground can pass `className="bg-bg"` — what it must not pass is
|
|
112
|
+
* nothing.
|
|
113
|
+
*/
|
|
114
|
+
const TableHead = React.forwardRef(function TableHead({ className, numeric = false, sticky = false, scope = "col", ...elementProps }, ref) {
|
|
115
|
+
return /* @__PURE__ */ jsx("th", {
|
|
116
|
+
ref,
|
|
117
|
+
scope,
|
|
118
|
+
"data-slot": "table-head",
|
|
119
|
+
"data-numeric": numeric ? "" : void 0,
|
|
120
|
+
className: cn("border-b border-border bg-surface-1 px-(--cue-pad-row-x) py-(--cue-pad-row-y) text-left font-mono text-(length:--cue-text-label) font-normal tracking-[0.15em] whitespace-nowrap text-fg-subtle uppercase", sticky ? "sticky top-0 z-3 border-b-0 shadow-[inset_0_-1px_0_var(--cue-hair-strong)]" : null, className),
|
|
121
|
+
...elementProps
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
23
125
|
* The canonical console table: hairlines, mono figures, no zebra striping.
|
|
24
126
|
*
|
|
25
127
|
* Semantic `<table>` markup throughout — a grid of divs loses row/column
|
|
@@ -30,6 +132,14 @@ const TableScrollRegion = React.forwardRef(function TableScrollRegion({ classNam
|
|
|
30
132
|
* Static markup — no `"use client"`. Compose it with `Panel` for the rim.
|
|
31
133
|
* Use {@link TableScrollRegion} only when the table can exceed its container.
|
|
32
134
|
*
|
|
135
|
+
* For a grouped list, the table stays *one* table: `Table.Head sticky` docks the
|
|
136
|
+
* column names, and one `Table.Body` per group opens with a `Table.GroupRow`
|
|
137
|
+
* divider — so the columns are named once and scan straight through every group
|
|
138
|
+
* boundary. Rows may hold `DataRow` slots, and `DataRow.Actions` reveals inside
|
|
139
|
+
* a cell on hover and on selection exactly as it does in a `Ledger` row — on
|
|
140
|
+
* focus too, once the row holds something focusable outside the track, which a
|
|
141
|
+
* `<tr>` never is by itself. See {@link TableRowProps.interactive}.
|
|
142
|
+
*
|
|
33
143
|
* @example
|
|
34
144
|
* <Table.Root>
|
|
35
145
|
* <Table.Header>
|
|
@@ -47,48 +157,32 @@ const TableScrollRegion = React.forwardRef(function TableScrollRegion({ classNam
|
|
|
47
157
|
* </Table.Root>
|
|
48
158
|
*/
|
|
49
159
|
const Table = {
|
|
50
|
-
Root:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
...elementProps
|
|
56
|
-
});
|
|
57
|
-
}),
|
|
58
|
-
Header: React.forwardRef(function TableHeader({ className, ...elementProps }, ref) {
|
|
59
|
-
return /* @__PURE__ */ jsx("thead", {
|
|
60
|
-
ref,
|
|
61
|
-
"data-slot": "table-header",
|
|
62
|
-
className,
|
|
63
|
-
...elementProps
|
|
64
|
-
});
|
|
65
|
-
}),
|
|
66
|
-
Body: React.forwardRef(function TableBody({ className, ...elementProps }, ref) {
|
|
67
|
-
return /* @__PURE__ */ jsx("tbody", {
|
|
68
|
-
ref,
|
|
69
|
-
"data-slot": "table-body",
|
|
70
|
-
className,
|
|
71
|
-
...elementProps
|
|
72
|
-
});
|
|
73
|
-
}),
|
|
74
|
-
Row: React.forwardRef(function TableRow({ className, ...elementProps }, ref) {
|
|
160
|
+
Root: TableRoot,
|
|
161
|
+
Header: TableHeader,
|
|
162
|
+
Body: TableBody,
|
|
163
|
+
Row: TableRow,
|
|
164
|
+
GroupRow: React.forwardRef(function TableGroupRow({ className, span, label, count, sticky = false, ...elementProps }, ref) {
|
|
75
165
|
return /* @__PURE__ */ jsx("tr", {
|
|
76
166
|
ref,
|
|
77
|
-
"data-slot": "table-row",
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
167
|
+
"data-slot": "table-group-row",
|
|
168
|
+
...elementProps,
|
|
169
|
+
children: /* @__PURE__ */ jsxs("th", {
|
|
170
|
+
colSpan: span,
|
|
171
|
+
scope: "rowgroup",
|
|
172
|
+
"data-slot": "table-group-cell",
|
|
173
|
+
className: cn("bg-bg px-(--cue-pad-row-x) py-(--cue-pad-row-y) text-left font-mono text-(length:--cue-text-label) font-normal tracking-[0.15em] whitespace-nowrap text-fg-subtle uppercase", sticky ? "sticky z-2 [top:var(--cue-table-group-top,0px)]" : null, className),
|
|
174
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
175
|
+
"data-slot": "table-group-label",
|
|
176
|
+
children: label
|
|
177
|
+
}), count === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
178
|
+
"data-slot": "table-group-count",
|
|
179
|
+
className: "ml-(--cue-space-2) tabular-nums",
|
|
180
|
+
children: count
|
|
181
|
+
})]
|
|
182
|
+
})
|
|
90
183
|
});
|
|
91
184
|
}),
|
|
185
|
+
Head: TableHead,
|
|
92
186
|
Cell: React.forwardRef(function TableCell({ className, numeric = false, ...elementProps }, ref) {
|
|
93
187
|
return /* @__PURE__ */ jsx("td", {
|
|
94
188
|
ref,
|
package/dist/theming/_presets.js
CHANGED
|
@@ -263,7 +263,7 @@ const THEME_PRESETS = {
|
|
|
263
263
|
"info": "#1a75d2",
|
|
264
264
|
"stream": "hsl(212 78% 42%)",
|
|
265
265
|
"selection": "#cce0ff",
|
|
266
|
-
"focus": "#
|
|
266
|
+
"focus": "#c75600",
|
|
267
267
|
"data-ground": "#000000",
|
|
268
268
|
"scrim": "rgba(0,0,0,0.35)"
|
|
269
269
|
},
|
|
@@ -322,7 +322,7 @@ const THEME_PRESETS = {
|
|
|
322
322
|
"info": "#2f6fd0",
|
|
323
323
|
"stream": "hsl(221 78% 45%)",
|
|
324
324
|
"selection": "rgba(224,101,16,0.15)",
|
|
325
|
-
"focus": "#
|
|
325
|
+
"focus": "#c75600",
|
|
326
326
|
"data-ground": "#000000",
|
|
327
327
|
"scrim": "rgba(15,23,42,0.45)"
|
|
328
328
|
},
|
|
@@ -440,7 +440,7 @@ const THEME_PRESETS = {
|
|
|
440
440
|
"info": "#1a75d2",
|
|
441
441
|
"stream": "#0f6ac4",
|
|
442
442
|
"selection": "rgba(184,134,47,0.20)",
|
|
443
|
-
"focus": "#
|
|
443
|
+
"focus": "#9f6e08",
|
|
444
444
|
"data-ground": "#000000",
|
|
445
445
|
"scrim": "rgba(0,0,0,0.55)"
|
|
446
446
|
},
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
{
|
|
89
89
|
"name": "Bar",
|
|
90
90
|
"usage": "AppShell.Bar",
|
|
91
|
-
"description": "The bar across the top of the frame, divided by the navigation column: a\nheader cell exactly the column's width, and the rest of the bar beside it.\n\n`TitleBar` and `AppBar` span the whole frame and know nothing about the\ncolumn under them, which is right for a document and wrong for a console\nthat has an app mark over its navigation: two boxes that are meant to read\nas one column have to be measured from one length, or they drift the first\ntime the column changes width. This is the bar that is measured that way. It\nreads `--cue-nav-width` from {@link AppShellRoot}, so railing the console\nmoves the cell and the column together with nothing to keep in sync.\n\nBelow `md` the cell takes its content's width instead. That is the same\ndecision the column and the drawer trigger already make and not a third one:\nbelow the breakpoint the column is not in the layout, and a cell aligned to\na column that is not there is 14rem of a 390px bar walled off for nothing.\n\nA `<div>` rather than a `<header>`, like `TitleBar` beside it: a console\nframe that also carries an `AppBar` would otherwise have two `banner`\nlandmarks, and a page that wants one can say `role=\"banner\"` here.",
|
|
91
|
+
"description": "The bar across the top of the frame, divided by the navigation column: a\nheader cell exactly the column's width, and the rest of the bar beside it.\n\n`TitleBar` and `AppBar` span the whole frame and know nothing about the\ncolumn under them, which is right for a document and wrong for a console\nthat has an app mark over its navigation: two boxes that are meant to read\nas one column have to be measured from one length, or they drift the first\ntime the column changes width. This is the bar that is measured that way. It\nreads `--cue-nav-width` from {@link AppShellRoot}, so railing the console\nmoves the cell and the column together with nothing to keep in sync.\n\nBelow `md` the cell takes its content's width instead. That is the same\ndecision the column and the drawer trigger already make and not a third one:\nbelow the breakpoint the column is not in the layout, and a cell aligned to\na column that is not there is 14rem of a 390px bar walled off for nothing.\n\nA `<div>` rather than a `<header>`, like `TitleBar` beside it: a console\nframe that also carries an `AppBar` would otherwise have two `banner`\nlandmarks, and a page that wants one can say `role=\"banner\"` here.\n\n**It is also the desktop app's one chrome row.** `platformDrag` gives it the\ndrag contract `TitleBar` has always had, and `center` and `actions` give it\nthe two slots that made a second stacked bar necessary — so a Tauri or\nElectron window spends one `--cue-chrome-titlebar` on furniture instead of\ntwo. See {@link AppShellBarProps.platformDrag} for why the attribute lands on\nthree surfaces rather than one.\n\nThe bar is always three nested clusters — brand content, leading children,\ntrailing actions — whether or not it is draggable. One shape, not two: the\ndrag mode is then a class on a box that already exists rather than a\ndifferent tree, and a caller's own CSS cannot be right in a browser and wrong\nin a shell. In an ordinary web shell each cluster grows into the space its\ncell gives it, so an `ms-auto` on a brand control still reaches the cell's\ntrailing edge exactly as it did before the clusters existed; under\n`platformDrag` the clusters shrink to their content instead, so that the\nslack stays on the attributed cell and the window can be dragged by it.",
|
|
92
92
|
"props": [
|
|
93
93
|
{
|
|
94
94
|
"name": "brand",
|
|
@@ -102,7 +102,28 @@
|
|
|
102
102
|
"type": "string",
|
|
103
103
|
"required": false,
|
|
104
104
|
"defaultValue": null,
|
|
105
|
-
"description": "Extra classes for the header cell."
|
|
105
|
+
"description": "Extra classes for the header cell — the bordered box that spans the\nnavigation column, not the row of brand content inside it. Anything about\nthe cell's width, rim or gutter belongs here; anything about how the mark\nand its controls sit belongs on the nodes passed as `brand`."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "platformDrag",
|
|
109
|
+
"type": "boolean",
|
|
110
|
+
"required": false,
|
|
111
|
+
"defaultValue": "false",
|
|
112
|
+
"description": "Make the bar's dead space a window drag handle. Defaults to `false`.\n\nThis is the same contract {@link TitleBar } has always offered, on the bar\na console with a navigation column actually uses — a desktop app should\nnot need a `TitleBar` *and* an `AppShell.Bar` stacked on top of each other\nto get one draggable row, which is two chrome rungs of a small window\nspent on furniture.\n\nEvery structural surface is attributed and every interactive cluster is\ncarved back out, because the two shells this ships into fail in opposite\ndirections. Tauri matches the *exact* element under the cursor, so the\nbrand cell and the rest cell carry the attribute themselves — a click on\nthe cell's own dead space targets the cell, never the root beneath it, and\na bar that only attributed its root would be draggable nowhere. Electron\nbuilds a geometric mask from painted boxes instead and excludes nothing on\nits own, so the brand content, the leading children and `actions` each sit\nin a cluster marked `app-region: no-drag` — without it a button that merely\noverlaps the bar stops receiving clicks at all. See `./_drag.ts`.\n\n**It changes how the clusters are sized, and that is a trade.** Off, each\ncluster grows into its cell, so a caller's own `ms-auto` reaches the cell's\ntrailing rim. On, the clusters take their content's width and `actions` is\npushed over with `ms-auto` instead, because a cluster that fills its cell\nis a `no-drag` rectangle painted over every dead pixel the cell just\nattributed — the bar would be draggable nowhere but its paddings and its\none gap. The dead area belongs to the window in drag mode, and a caller who\nneeds content to reach the rim there should say so on a box of their own\ninside the cluster.\n\n`select-none` comes with it: dragging a window and selecting the text you\nstarted the drag on are the same gesture, and the shell wins.\n\nNot wired here: double-click-to-maximise. Tauri's injected script already\ndoes it for any drag region, macOS's cancel-by-dragging-away included.\nElectron does not, and an Electron app has to listen for `dblclick` and\ntoggle the window itself. Which of those an app is, is not something a\nlayout component can know."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "center",
|
|
116
|
+
"type": "ReactNode",
|
|
117
|
+
"required": false,
|
|
118
|
+
"defaultValue": null,
|
|
119
|
+
"description": "A title centred on the **bar**, not between the cells beside it.\n\nAbsolutely positioned against the bar's own box, so it stays put when a\nbutton appears in `actions` or the column rails — the whole reason a\ndocument name in desktop chrome is not just another flex child.\n`pointer-events-none` so the dead space around a short title still drags,\nwhich is also what makes it click through to the drag surface underneath\nin Tauri, whose hit-testing is the browser's own.\n\nInert by construction. Anything the reader has to click belongs in\n`actions` or in `children`, where it can be carved out of the drag region."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "actions",
|
|
123
|
+
"type": "ReactNode",
|
|
124
|
+
"required": false,
|
|
125
|
+
"defaultValue": null,
|
|
126
|
+
"description": "The trailing cluster: connection state, a run indicator, an overflow menu.\n\nPinned to the bar's trailing edge and never squeezed, so it reads as window\nfurniture rather than the end of a sentence. `children` keeps flowing into\nthe same cell ahead of it, which is what lets an existing web-shell caller\npass free-form content and gain a cluster without rearranging anything."
|
|
106
127
|
}
|
|
107
128
|
],
|
|
108
129
|
"typeReferences": [
|
|
@@ -289,13 +310,16 @@
|
|
|
289
310
|
"--cue-chrome-titlebar",
|
|
290
311
|
"--cue-control-md",
|
|
291
312
|
"--cue-fg",
|
|
313
|
+
"--cue-fg-muted",
|
|
314
|
+
"--cue-font-mono",
|
|
292
315
|
"--cue-icon-md",
|
|
293
316
|
"--cue-nav-width",
|
|
294
317
|
"--cue-pad-row-x",
|
|
295
318
|
"--cue-space-3",
|
|
296
319
|
"--cue-space-4",
|
|
297
320
|
"--cue-space-5",
|
|
298
|
-
"--cue-surface-1"
|
|
321
|
+
"--cue-surface-1",
|
|
322
|
+
"--cue-text-label"
|
|
299
323
|
],
|
|
300
324
|
"summary": "The console frame: title bar, navigation, work area and status bar, one viewport tall with exactly one thing scrolling.",
|
|
301
325
|
"examples": [
|
|
@@ -329,6 +353,11 @@
|
|
|
329
353
|
"code": "<span className=\"group-data-[rail]/app-shell:hidden\">CUE++</span>",
|
|
330
354
|
"language": "tsx"
|
|
331
355
|
},
|
|
356
|
+
{
|
|
357
|
+
"title": "The desktop shape: one chrome row",
|
|
358
|
+
"code": "<AppShell.Bar\n platformDrag\n center=\"main-stage.cue\"\n brand={<><AppShell.NavTrigger /><Eyebrow>cue++</Eyebrow></>}\n actions={<><Chip tone=\"ok\">live</Chip><IconButton aria-label=\"Preview\" icon={Monitor} size=\"sm\" /></>}\n>\n <Toolbar.Button icon={Play}>Go</Toolbar.Button>\n</AppShell.Bar>",
|
|
359
|
+
"language": "tsx"
|
|
360
|
+
},
|
|
332
361
|
{
|
|
333
362
|
"title": "Where the footer goes",
|
|
334
363
|
"code": "<AppShell.Content>\n <AppShell.Scroller className=\"flex flex-col\">\n <div className=\"flex-1\">{children}</div>\n <Footer meta={…}>…</Footer>\n </AppShell.Scroller>\n</AppShell.Content>",
|
|
@@ -366,7 +395,9 @@
|
|
|
366
395
|
"Reaching for a viewport variant of your own because the shell has one. The `md` switch is licensed for navigation shape and nothing else; everything else in this system is intrinsically responsive.",
|
|
367
396
|
"Hard-coding the navigation column's width somewhere else in the frame — a header cell, a toolbar offset, a sticky rule. Read `--cue-nav-width` instead, or the two drift the first time the column rails.",
|
|
368
397
|
"Railing a column of items with no icons. At 3.5rem the label goes `sr-only` and the icon is all that is left; without one the reader gets an empty strip. `AppShell.Sidebar` takes `collapsible={false}` for exactly that column.",
|
|
369
|
-
"Ending the page with a `Footer` after `AppShell.Body`. A footer belongs to the content column — the last child of `AppShell.Scroller` — or it stretches under the navigation and pretends to be part of the frame."
|
|
398
|
+
"Ending the page with a `Footer` after `AppShell.Body`. A footer belongs to the content column — the last child of `AppShell.Scroller` — or it stretches under the navigation and pretends to be part of the frame.",
|
|
399
|
+
"Stacking a `TitleBar` above an `AppShell.Bar` in a desktop app to get a drag region. `AppShell.Bar platformDrag` is the same contract on the bar you already have, and two chrome rungs of a small window is a lot to spend on furniture.",
|
|
400
|
+
"Putting something clickable in `AppShell.Bar`'s `center`. It is `pointer-events-none` so the dead space around a short title still drags — an interactive centre belongs in `actions` or in `children`, where it is carved out of the drag region."
|
|
370
401
|
],
|
|
371
402
|
"specimens": [
|
|
372
403
|
{
|
|
@@ -378,6 +409,16 @@
|
|
|
378
409
|
"code": "<div className=\"w-full overflow-hidden rounded-(--radius-surface) border border-border\">\n {/* `h-[26rem]` overrides the shell's own `h-dvh`, and `main={false}`\n * keeps it from emitting a second `<main>` inside the page already\n * wrapping this corpus. Both are what embedding a whole-screen\n * component in a bench costs. */}\n <AppShell.Root className=\"h-[26rem]\" rail={rail} onRailChange={setRail}>\n {/* The toggle is rendered here rather than by the shell: where the\n * control lives is a decision about this bar, and persisting what\n * it sets is a decision about hydration. Both are the caller's. */}\n <AppShell.Bar\n brand={\n <>\n <AppShell.NavTrigger />\n <Eyebrow className=\"group-data-[rail]/app-shell:hidden\">CUE++</Eyebrow>\n <IconButton\n icon={PanelLeft}\n size=\"sm\"\n aria-label={rail ? \"Expand the sidebar\" : \"Collapse the sidebar\"}\n aria-pressed={rail}\n className=\"ms-auto\"\n onClick={() => setRail(!rail)}\n />\n </>\n }\n >\n <span className=\"font-mono text-(length:--cue-text-label) text-fg-muted\">\n main-stage.cue\n </span>\n <Chip tone=\"ok\" className=\"ms-auto\">\n live\n </Chip>\n </AppShell.Bar>\n\n <AppShell.Body>\n <AppShell.Sidebar aria-label=\"Console\" drawerTitle=\"Console\">\n <Sidebar.Section label=\"Show\">\n <Sidebar.Item icon={List} active>\n Cues\n </Sidebar.Item>\n <Sidebar.Item icon={Lightbulb} badge={<Chip>48</Chip>}>\n Fixtures\n </Sidebar.Item>\n </Sidebar.Section>\n </AppShell.Sidebar>\n\n <AppShell.Content main={false}>\n <Toolbar.Root aria-label=\"Cue list\">\n <Toolbar.Button icon={Plus}>Add cue</Toolbar.Button>\n <Toolbar.Separator />\n <Toolbar.Button icon={Monitor}>Live</Toolbar.Button>\n </Toolbar.Root>\n\n <AppShell.Scroller>\n <Panel>\n <PanelHeader title=\"Cue stack\" actions={<Chip tone=\"ok\">live</Chip>} />\n {CUES.map((cue) => (\n <Row key={cue.id}>\n <span className=\"w-[2rem] shrink-0 font-mono tabular-nums\">{cue.id}</span>\n <span className=\"flex-1 truncate\">{cue.label}</span>\n <span className=\"font-mono tabular-nums text-fg-muted\">{cue.time}</span>\n </Row>\n ))}\n </Panel>\n </AppShell.Scroller>\n </AppShell.Content>\n\n <AppShell.Aside aria-label=\"Inspector\" width=\"11rem\">\n <SectionHeader label=\"Selected\" />\n <p className=\"font-mono text-(length:--cue-text-ui) text-fg-muted\">\n Its own scroller, so reading it never moves the work area.\n </p>\n </AppShell.Aside>\n </AppShell.Body>\n\n <StatusBar>\n <StatusBar.Item tone=\"ok\">Art-Net</StatusBar.Item>\n <StatusBar.Item push>Cue 4</StatusBar.Item>\n </StatusBar>\n </AppShell.Root>\n</div>",
|
|
379
410
|
"note": "The console frame, and the arithmetic that holds it up: one viewport tall, an unbroken min-h-0 chain, and exactly one thing scrolling. AppShell.Bar divides the bar at the navigation column, so the mark over the column and the column itself are one box measured from one length. Boxed to 26rem here — a bench has no viewport to give it.",
|
|
380
411
|
"interaction": "the button beside the mark rails the column to 3.5rem of icons, and the header cell over it narrows with it — one --cue-nav-width, rewritten once. The 4px strip on the column's edge does the same. Below 48rem the column is not rendered at all and the trigger opens the same navigation as a drawer."
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"title": "AppShell.Bar",
|
|
415
|
+
"group": "chrome",
|
|
416
|
+
"components": [
|
|
417
|
+
"AppShell"
|
|
418
|
+
],
|
|
419
|
+
"code": "<div className=\"w-full overflow-hidden rounded-(--radius-surface) border border-border\">\n <AppShell.Root className=\"h-auto\">\n <AppShell.Bar\n platformDrag\n center=\"main-stage.cue\"\n brand={\n <>\n <AppShell.NavTrigger />\n <Eyebrow>cue++</Eyebrow>\n </>\n }\n actions={\n <>\n <Chip tone=\"ok\">live</Chip>\n <IconButton aria-label=\"Preview\" icon={Monitor} size=\"sm\" />\n </>\n }\n >\n <Toolbar.Root aria-label=\"Playback\" className=\"border-0 bg-transparent px-0\">\n <Toolbar.Button icon={Play}>Go</Toolbar.Button>\n <Toolbar.Button icon={Eye}>Blind</Toolbar.Button>\n </Toolbar.Root>\n </AppShell.Bar>\n </AppShell.Root>\n</div>",
|
|
420
|
+
"note": "The same bar a console uses, given the desktop app's three extra slots: platformDrag hands every dead pixel to the window manager, center pins the document name to the middle of the bar rather than the middle of the space left over, and actions holds the trailing cluster. One chrome row instead of a TitleBar with a Toolbar stacked under it.",
|
|
421
|
+
"interaction": "the drag region is inert in a browser — there is no window to move — and live in Tauri and Electron, where it is also carved back out around the mark, the leading controls and the actions, so every one of them still takes its click. Double-click-to-maximise is Tauri's for free and the app's own work in Electron."
|
|
381
422
|
}
|
|
382
423
|
]
|
|
383
424
|
}
|
|
@@ -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-sm",
|
|
157
159
|
"--cue-radius-control",
|
|
@@ -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,203 @@
|
|
|
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": "A density island for the list, not for the app",
|
|
162
|
+
"code": "<div className=\"[--cue-chip-h:1rem] [--cue-control-sm:1.25rem]\">\n <Ledger.Root>…</Ledger.Root>\n</div>",
|
|
163
|
+
"language": "tsx"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"title": "Usage",
|
|
167
|
+
"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>",
|
|
168
|
+
"language": "tsx"
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"status": "stable",
|
|
172
|
+
"url": "/docs/components/data-row",
|
|
173
|
+
"mdUrl": "/docs/components/data-row.md",
|
|
174
|
+
"jsonUrl": "/r/components/data-row.json",
|
|
175
|
+
"whenToUse": [
|
|
176
|
+
"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.",
|
|
177
|
+
"Any row that has to look identical in a `Ledger`, a grouped `Table` and a `DataTree`, because all three draw this anatomy."
|
|
178
|
+
],
|
|
179
|
+
"whenNotToUse": [
|
|
180
|
+
"A panel's label-and-value lines. Use `Row`, which is a real `<button>` when it acts and needs no column tracks.",
|
|
181
|
+
"One object's fields. Use `DescriptionList`.",
|
|
182
|
+
"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."
|
|
183
|
+
],
|
|
184
|
+
"commonMistakes": [
|
|
185
|
+
"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.",
|
|
186
|
+
"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.",
|
|
187
|
+
"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\"]`.",
|
|
188
|
+
"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.",
|
|
189
|
+
"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."
|
|
190
|
+
],
|
|
191
|
+
"specimens": [
|
|
192
|
+
{
|
|
193
|
+
"title": "DataRow",
|
|
194
|
+
"group": "instruments",
|
|
195
|
+
"components": [
|
|
196
|
+
"DataRow"
|
|
197
|
+
],
|
|
198
|
+
"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>",
|
|
199
|
+
"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.",
|
|
200
|
+
"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."
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|