@apliteni/apliteni-ui 0.105.0 → 0.108.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/guidelines/dense-tables.md +12 -0
- package/guidelines/state-set.md +1 -1
- package/package.json +1 -1
- package/react/dist/index.css +9 -0
- package/react/dist/index.d.ts +13 -1
- package/react/dist/index.js +17 -9
- package/src/components/dropdown.js +20 -1
- package/src/styles/dropdown.css +27 -1
- package/src/styles/table.css +139 -11
- package/src/styles/topbar.css +2 -2
|
@@ -74,6 +74,18 @@
|
|
|
74
74
|
|
|
75
75
|
**Except:** On a phone, a row whose long column is a paragraph stacks instead of scrolling: the short values on one line, the paragraph under them, and the header kept for screen readers. No column is dropped.
|
|
76
76
|
|
|
77
|
+
## Keep identity sub-lines as text
|
|
78
|
+
|
|
79
|
+
<!-- rule: pinned-identity-sub-lines -->
|
|
80
|
+
|
|
81
|
+
**Why:** An ellipsis cannot cut a flex or grid box, so the name spills past the cell. No specimen draws it, because the cut needs a phone viewport.
|
|
82
|
+
|
|
83
|
+
**Rule:** A pinned identity sub-line is running text.
|
|
84
|
+
|
|
85
|
+
**Do:** Write it as a sentence, with any link inside.
|
|
86
|
+
|
|
87
|
+
**Don't:** Lay a badge, a control or a picture along it, or make it a flex or grid box.
|
|
88
|
+
|
|
77
89
|
## Keep updates stable
|
|
78
90
|
|
|
79
91
|
<!-- rule: states -->
|
package/guidelines/state-set.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
**Don't:** Give the input a separate muted outline while the button uses `--ring`.
|
|
14
14
|
|
|
15
|
-
**Except:** A
|
|
15
|
+
**Except:** A box whose own edges are a surface's — a scroll region, or a box filling a table cell — takes `--ring-scroll` with `--ring-scroll-offset`. A box that is the outermost surface, such as a panel or card, keeps `--ring`. Text-entry controls may match `:focus-visible` on mouse focus. Both bands are declared once at the root and recomposed nowhere, because an outline leaves its offset unpainted. Adjust width and colour there.
|
|
16
16
|
|
|
17
17
|
## Busy controls
|
|
18
18
|
|
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
|
@@ -291,6 +291,15 @@
|
|
|
291
291
|
outline-offset: var(--ring-offset);
|
|
292
292
|
border-radius: var(--radius-xs);
|
|
293
293
|
}
|
|
294
|
+
.rx-sort {
|
|
295
|
+
max-width: 100%;
|
|
296
|
+
min-width: 0;
|
|
297
|
+
}
|
|
298
|
+
.rx-sort__label {
|
|
299
|
+
min-width: 0;
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
text-overflow: ellipsis;
|
|
302
|
+
}
|
|
294
303
|
.rx-caret {
|
|
295
304
|
margin-left: 2px;
|
|
296
305
|
width: 16px;
|
package/react/dist/index.d.ts
CHANGED
|
@@ -207,6 +207,16 @@ type Column<T> = {
|
|
|
207
207
|
num?: boolean;
|
|
208
208
|
sortable?: boolean;
|
|
209
209
|
render?: (row: T) => ReactNode;
|
|
210
|
+
/**
|
|
211
|
+
* `true` marks this column's body cells as holding one link and nothing else, so the
|
|
212
|
+
* link fills its cell and its focus ring outlines the cell rather than the words. It is
|
|
213
|
+
* the consumer's to declare because CSS counts elements and not words: a cell reading
|
|
214
|
+
* "Invoice 1162" with the number linked looks the same to a selector, and filling that
|
|
215
|
+
* one would put the number on a line of its own. The header cell is never marked — it
|
|
216
|
+
* holds a label, or a sort button that keeps its own ring.
|
|
217
|
+
* why: docs/components.md#dense-financial-tables
|
|
218
|
+
*/
|
|
219
|
+
linked?: boolean;
|
|
210
220
|
};
|
|
211
221
|
type TableSort<T> = {
|
|
212
222
|
key: (keyof T & string) | undefined;
|
|
@@ -469,6 +479,8 @@ type DropdownProps = {
|
|
|
469
479
|
/** Replaces the trigger's label/value pair. With `ariaLabel`, names the trigger. */
|
|
470
480
|
triggerContent?: ReactNode;
|
|
471
481
|
triggerClass?: string;
|
|
482
|
+
/** The trigger is off: no stop, no panel, the kit's unavailable paint. #580 */
|
|
483
|
+
disabled?: boolean;
|
|
472
484
|
chevron?: boolean;
|
|
473
485
|
/** The edge the panel hugs. */
|
|
474
486
|
align?: 'start' | 'end';
|
|
@@ -490,7 +502,7 @@ type DropdownProps = {
|
|
|
490
502
|
/** Draw the row yourself. `props` is everything the row needs; spread it. */
|
|
491
503
|
row?: (item: DropdownItem, props: DropdownRowProps) => ReactNode;
|
|
492
504
|
};
|
|
493
|
-
declare function Dropdown({ label, value, placeholder, variant, items, sections, header, footer, triggerContent, triggerClass, chevron, align, direction, scroll, search, ariaLabel, id, panelClass, open: openProp, defaultOpen, onOpenChange, onSelect, row, }: DropdownProps): react.JSX.Element;
|
|
505
|
+
declare function Dropdown({ label, value, placeholder, variant, items, sections, header, footer, triggerContent, triggerClass, disabled, chevron, align, direction, scroll, search, ariaLabel, id, panelClass, open: openProp, defaultOpen, onOpenChange, onSelect, row, }: DropdownProps): react.JSX.Element;
|
|
494
506
|
|
|
495
507
|
type BackLinkOwnProps = {
|
|
496
508
|
/** The address. No address, or a `javascript:` one, and nothing is rendered. */
|
package/react/dist/index.js
CHANGED
|
@@ -1284,10 +1284,15 @@ function DataTable({
|
|
|
1284
1284
|
scope: "col",
|
|
1285
1285
|
className: [pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.num && "ui-table__num", c.sortable && "rx-sortable"].filter(Boolean).join(" "),
|
|
1286
1286
|
"aria-sort": sort.key === c.key ? sort.dir === 1 ? "ascending" : "descending" : c.sortable && sortIsKnown ? "none" : void 0,
|
|
1287
|
-
children: c.sortable ?
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1287
|
+
children: c.sortable ? (
|
|
1288
|
+
// The label is its own box so it can truncate: in a pinned
|
|
1289
|
+
// identity column on a phone the header is capped, and a bare
|
|
1290
|
+
// text node would push the caret out of the cell instead.
|
|
1291
|
+
/* @__PURE__ */ jsxs13("button", { type: "button", className: "rx-sort", onClick: () => onSort(c.key), children: [
|
|
1292
|
+
/* @__PURE__ */ jsx17("span", { className: "rx-sort__label", children: c.label }),
|
|
1293
|
+
caret(c.key)
|
|
1294
|
+
] })
|
|
1295
|
+
) : c.label
|
|
1291
1296
|
},
|
|
1292
1297
|
c.key
|
|
1293
1298
|
)
|
|
@@ -1305,7 +1310,7 @@ function DataTable({
|
|
|
1305
1310
|
onChange: () => onToggle(r.name)
|
|
1306
1311
|
}
|
|
1307
1312
|
) }) : null,
|
|
1308
|
-
columns.map((c, columnIndex) => /* @__PURE__ */ jsx17("td", { className: [c.num && "ui-table__num", pinnedIdentity && columnIndex === 0 && "ui-table__identity"].filter(Boolean).join(" "), children: c.render ? c.render(r) : String(r[c.key]) }, c.key))
|
|
1313
|
+
columns.map((c, columnIndex) => /* @__PURE__ */ jsx17("td", { className: [c.num && "ui-table__num", pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.linked && "ui-table__linked"].filter(Boolean).join(" "), children: c.render ? c.render(r) : String(r[c.key]) }, c.key))
|
|
1309
1314
|
] }, r.name))
|
|
1310
1315
|
] })
|
|
1311
1316
|
]
|
|
@@ -1939,6 +1944,7 @@ function Dropdown({
|
|
|
1939
1944
|
footer,
|
|
1940
1945
|
triggerContent,
|
|
1941
1946
|
triggerClass = "",
|
|
1947
|
+
disabled = false,
|
|
1942
1948
|
chevron = true,
|
|
1943
1949
|
align = "start",
|
|
1944
1950
|
direction = "down",
|
|
@@ -2235,6 +2241,7 @@ function Dropdown({
|
|
|
2235
2241
|
trigger.current?.focus();
|
|
2236
2242
|
};
|
|
2237
2243
|
const onKeyDown = (e) => {
|
|
2244
|
+
if (disabled && !open) return;
|
|
2238
2245
|
const onTrigger = e.target === trigger.current;
|
|
2239
2246
|
const onField = e.target === field.current;
|
|
2240
2247
|
if (onField && composing(e)) return;
|
|
@@ -2299,14 +2306,14 @@ function Dropdown({
|
|
|
2299
2306
|
if (!isRow(entry)) {
|
|
2300
2307
|
return /* @__PURE__ */ jsx20("div", { className: "ui-dropdown__sep", role: "separator", hidden: filtering || void 0 }, key);
|
|
2301
2308
|
}
|
|
2302
|
-
const
|
|
2309
|
+
const disabled2 = !!entry.disabled;
|
|
2303
2310
|
const selected = selectedOf(entry);
|
|
2304
|
-
const asLink = !!entry.href && !
|
|
2311
|
+
const asLink = !!entry.href && !disabled2 && !asOption;
|
|
2305
2312
|
const props = {
|
|
2306
2313
|
className: cx6(
|
|
2307
2314
|
"ui-dropdown__item",
|
|
2308
2315
|
selected && "is-selected",
|
|
2309
|
-
|
|
2316
|
+
disabled2 && "is-disabled",
|
|
2310
2317
|
entry.danger && "is-danger",
|
|
2311
2318
|
Boolean(sx) && open && entry === active && "is-active"
|
|
2312
2319
|
),
|
|
@@ -2315,7 +2322,7 @@ function Dropdown({
|
|
|
2315
2322
|
tabIndex: -1,
|
|
2316
2323
|
...entry.value != null ? { "data-value": String(entry.value) } : null,
|
|
2317
2324
|
...asOption ? { "aria-selected": selected } : null,
|
|
2318
|
-
...
|
|
2325
|
+
...disabled2 ? { "aria-disabled": true } : null,
|
|
2319
2326
|
...asLink ? { href: entry.href, ...entry.target ? { target: entry.target } : null } : null,
|
|
2320
2327
|
...sx ? { id: rowIds.get(entry), hidden: hiddenBy(entry) || void 0 } : null,
|
|
2321
2328
|
onClick: (e) => choose(entry, e),
|
|
@@ -2362,6 +2369,7 @@ function Dropdown({
|
|
|
2362
2369
|
type: "button",
|
|
2363
2370
|
className: cx6("ui-dropdown__trigger", triggerClass),
|
|
2364
2371
|
"data-dropdown-trigger": "",
|
|
2372
|
+
disabled: disabled || void 0,
|
|
2365
2373
|
"aria-haspopup": sx ? "dialog" : listRole,
|
|
2366
2374
|
"aria-expanded": open,
|
|
2367
2375
|
"aria-label": ariaLabel && triggerContent != null ? ariaLabel : void 0,
|
|
@@ -157,6 +157,8 @@ function ddSearchBody({ items, sections }, sx, name, scroll) {
|
|
|
157
157
|
* @param {boolean} [o.portal] mount the panel on <body>, for a clipping or sticky ancestor
|
|
158
158
|
* @param {boolean|number} [o.scroll] true, or a maxHeight in px, to cap and scroll
|
|
159
159
|
* @param {boolean} [o.open] render already-open (handy for screenshots)
|
|
160
|
+
* @param {boolean} [o.disabled] the trigger is off: no stop, no panel, the kit's
|
|
161
|
+
* unavailable paint. #580
|
|
160
162
|
* @param {string} [o.ariaLabel] accessible name for the panel and trigger
|
|
161
163
|
* @param {boolean|object} [o.search] true, or { placeholder, label, empty, hint, query } —
|
|
162
164
|
* a field above the rows that filters them; `empty` may carry {q}
|
|
@@ -166,7 +168,7 @@ export function dropdown({
|
|
|
166
168
|
label, value, placeholder = 'Select…', variant, items, sections,
|
|
167
169
|
foot = '', header = '', footer = '', triggerContent, triggerClass = '', chevron = true,
|
|
168
170
|
align = 'start', direction = 'down', portal = false,
|
|
169
|
-
scroll = false, open = false, ariaLabel, id, panelClass = '', search = false,
|
|
171
|
+
scroll = false, open = false, disabled = false, ariaLabel, id, panelClass = '', search = false,
|
|
170
172
|
} = {}) {
|
|
171
173
|
const flat = sections ? sections.flatMap((s) => s.items || []) : (items || []);
|
|
172
174
|
const isSelect = variant === 'select' || (variant == null && flat.some((it) => it && (it.selected || it.value != null)));
|
|
@@ -185,6 +187,9 @@ export function dropdown({
|
|
|
185
187
|
'data-dropdown-trigger',
|
|
186
188
|
`aria-haspopup="${sx ? 'dialog' : listRole}"`,
|
|
187
189
|
`aria-expanded="${open ? 'true' : 'false'}"`,
|
|
190
|
+
// The attribute, not aria-disabled: a dropdown's trigger carries no message a
|
|
191
|
+
// reader has to stop on, so it leaves the tab order the way .ui-btn's does.
|
|
192
|
+
disabled ? 'disabled' : '',
|
|
188
193
|
ariaLabel && triggerContent != null ? `aria-label="${esc(ariaLabel)}"` : '',
|
|
189
194
|
].filter(Boolean).join(' ');
|
|
190
195
|
|
|
@@ -269,6 +274,17 @@ const DD_GAP = 9;
|
|
|
269
274
|
// below asks the container for its panel rather than querying inside it.
|
|
270
275
|
const ddPanelOf = (dd) => dd.__ddPanel || dd.querySelector('[data-dropdown-panel]');
|
|
271
276
|
|
|
277
|
+
// A trigger the sheet paints off. The kit paints both spellings unavailable, so
|
|
278
|
+
// the wiring refuses both: the native attribute keeps the browser from
|
|
279
|
+
// dispatching anything, but an aria-disabled trigger is still a keyboard stop,
|
|
280
|
+
// and a click or an Enter on it reached openDropdown() until #580's round two.
|
|
281
|
+
// ddItemsOf() above refuses an aria-disabled ROW the same way.
|
|
282
|
+
// why: docs/foundations.md#colour-and-contrast
|
|
283
|
+
const ddTriggerOff = (dd) => {
|
|
284
|
+
const trigger = dd.querySelector('[data-dropdown-trigger]');
|
|
285
|
+
return !!trigger && (trigger.disabled === true || trigger.getAttribute('aria-disabled') === 'true');
|
|
286
|
+
};
|
|
287
|
+
|
|
272
288
|
function ddGap(panel) {
|
|
273
289
|
const declared = parseFloat(ddViewOf(panel).getComputedStyle(panel).getPropertyValue('--ui-dropdown-gap'));
|
|
274
290
|
return Number.isFinite(declared) ? declared : DD_GAP;
|
|
@@ -836,6 +852,9 @@ function closeAllDropdowns(except) {
|
|
|
836
852
|
}
|
|
837
853
|
|
|
838
854
|
function openDropdown(dd, focusIdx) {
|
|
855
|
+
// Every way in goes through here — the trigger's click, the Enter the browser
|
|
856
|
+
// turns into one, and the arrows — so one refusal covers them all. #580
|
|
857
|
+
if (ddTriggerOff(dd)) return;
|
|
839
858
|
closeAllDropdowns(dd);
|
|
840
859
|
const panel = ddPanelOf(dd);
|
|
841
860
|
const search = ddSearchOf(dd);
|
package/src/styles/dropdown.css
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
font-weight: 500;
|
|
19
19
|
color: var(--text);
|
|
20
20
|
background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
|
|
21
|
-
|
|
21
|
+
/* The kit's live control edge, the one .ui-btn takes on the same fill. --border
|
|
22
|
+
is what --disabled-border resolves to in LIGHT, so a live trigger used to draw
|
|
23
|
+
the exact line the kit paints an unavailable control with. #580
|
|
24
|
+
why: docs/foundations.md#colour-and-contrast */
|
|
25
|
+
border: 1px solid var(--control-edge);
|
|
22
26
|
border-radius: var(--radius-pill);
|
|
23
27
|
padding: 7px 11px 7px 13px;
|
|
24
28
|
transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
|
|
@@ -29,6 +33,28 @@
|
|
|
29
33
|
what the kit's rows already chose. why: docs/foundations.md#the-focus-ring */
|
|
30
34
|
.ui-dropdown__trigger:hover:not(:focus-visible) { border-color: var(--accent); }
|
|
31
35
|
.ui-dropdown__trigger:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
|
|
36
|
+
/* Off. The off hover-not-focus selectors are (0,4,0), above live hover's (0,3,0).
|
|
37
|
+
A pointer over a dead control changes nothing while an aria-disabled one keeps its stop and the ring.
|
|
38
|
+
No fill: --disabled-surface IS this control's --surface, in both themes. The
|
|
39
|
+
activation is refused in ddTriggerOff(), not with pointer-events, which would
|
|
40
|
+
take that stop away. #580 why: docs/foundations.md#colour-and-contrast */
|
|
41
|
+
.ui-dropdown__trigger:disabled,
|
|
42
|
+
.ui-dropdown__trigger:disabled:hover,
|
|
43
|
+
.ui-dropdown__trigger:disabled:hover:not(:focus-visible),
|
|
44
|
+
.ui-dropdown__trigger:disabled:focus-visible,
|
|
45
|
+
.ui-dropdown__trigger[aria-disabled="true"]:hover,
|
|
46
|
+
.ui-dropdown__trigger[aria-disabled="true"]:hover:not(:focus-visible),
|
|
47
|
+
.ui-dropdown__trigger[aria-disabled="true"]:focus-visible,
|
|
48
|
+
.ui-dropdown__trigger[aria-disabled="true"] {
|
|
49
|
+
border-color: var(--disabled-border);
|
|
50
|
+
/* muted-ink: state — unavailable control */ color: var(--disabled-ink);
|
|
51
|
+
cursor: not-allowed;
|
|
52
|
+
}
|
|
53
|
+
/* The prefix and the value carry inks of their own, so they take the state's. */
|
|
54
|
+
.ui-dropdown__trigger:disabled .ui-dropdown__pre,
|
|
55
|
+
.ui-dropdown__trigger:disabled .ui-dropdown__value,
|
|
56
|
+
.ui-dropdown__trigger[aria-disabled="true"] .ui-dropdown__pre,
|
|
57
|
+
.ui-dropdown__trigger[aria-disabled="true"] .ui-dropdown__value { color: inherit; }
|
|
32
58
|
.ui-dropdown__pre { color: var(--text); }
|
|
33
59
|
.ui-dropdown__value { color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
|
|
34
60
|
|
package/src/styles/table.css
CHANGED
|
@@ -161,12 +161,42 @@
|
|
|
161
161
|
top: 50%;
|
|
162
162
|
translate: 0 -50%;
|
|
163
163
|
}
|
|
164
|
-
/* No focus rule here
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
/* No generic focus rule here. #510 gave a cell link the ring because `base.css`
|
|
165
|
+
reached no bare `a`; #587 put `a` on that shared list, so this file only keeps
|
|
166
|
+
the filled-cell override below. why: guidelines/state-set.md#focus-rings */
|
|
167
|
+
/* The handle: a cell MARKED `.ui-table__linked` holds one link and nothing else, so the
|
|
168
|
+
link fills the cell and its ring outlines the cell rather than the words. The cell hands
|
|
169
|
+
its padding over and nothing moves. `height: 1px` is the cell stating no height of its
|
|
170
|
+
own, which a percentage height on the link can resolve against; a cell is stretched to
|
|
171
|
+
its row either way, so no row changes.
|
|
172
|
+
The mark is the consumer's because a selector counts elements and not words: `:only-child`
|
|
173
|
+
reads `<td>Invoice <a>1162</a></td>` as a lone link too. The link is named rather than
|
|
174
|
+
denied — a denylist of the anchors that own their box went stale twice (#510).
|
|
175
|
+
why: docs/components.md#dense-financial-tables, #500
|
|
176
|
+
held by: src/styles/table-focus.test.js */
|
|
177
|
+
.ui-table td.ui-table__linked:has(> :is(a:not([class]), a.ui-identity):only-child) { padding: 0; height: 1px; }
|
|
178
|
+
.ui-table td.ui-table__linked > :is(a:not([class]), a.ui-identity):only-child {
|
|
179
|
+
box-sizing: border-box;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
width: 100%;
|
|
183
|
+
height: 100%;
|
|
184
|
+
padding: var(--space-4) var(--space-4) var(--space-4) 0;
|
|
185
|
+
border-radius: var(--radius-xs);
|
|
186
|
+
}
|
|
187
|
+
.ui-table--dense td.ui-table__linked > :is(a:not([class]), a.ui-identity):only-child { padding: var(--space-2) var(--space-3); }
|
|
188
|
+
.ui-table--compact td.ui-table__linked > :is(a:not([class]), a.ui-identity):only-child { padding: var(--space-1) var(--space-3); }
|
|
189
|
+
/* Zebra insets the row's END cells only, so the fill does too: a marked identity behind a
|
|
190
|
+
selection column is a middle cell, and a step it never gave up would move its name. */
|
|
191
|
+
.ui-table--zebra td.ui-table__linked:first-child > :is(a:not([class]), a.ui-identity):only-child { padding-left: var(--space-3); }
|
|
192
|
+
.ui-table--zebra td.ui-table__linked:last-child > :is(a:not([class]), a.ui-identity):only-child { padding-right: var(--space-3); }
|
|
193
|
+
/* The inward band, the one a scroll region takes: a filled cell's edges are the
|
|
194
|
+
table's, where the pinned column's border and the scroller's 4px side padding
|
|
195
|
+
cut an outset ring away. Its 1px gap is unpainted, and the ring is the outline. */
|
|
196
|
+
.ui-table td.ui-table__linked > :is(a:not([class]), a.ui-identity):only-child:focus-visible {
|
|
197
|
+
outline: var(--ring-scroll);
|
|
198
|
+
outline-offset: var(--ring-scroll-offset);
|
|
199
|
+
}
|
|
170
200
|
|
|
171
201
|
.ui-table__num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
172
202
|
/* A numeric column is sized to its content while .ui-table__title claims the rest,
|
|
@@ -356,14 +386,78 @@
|
|
|
356
386
|
a.ui-identity:hover { text-decoration: underline; }
|
|
357
387
|
.ui-identity:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
|
|
358
388
|
@media (max-width: 720px) {
|
|
359
|
-
|
|
389
|
+
/* One line wide: nothing wraps, and --ui-table-identity-max is the ceiling a consumer
|
|
390
|
+
retunes. The cut takes NO margin — the cell is sticky and the next column scrolls
|
|
391
|
+
under it, so whatever the cut lets out is painted over that column. A child's ring
|
|
392
|
+
needs 3px and the cell's padding is 12px, so the room comes from the padding.
|
|
393
|
+
why: docs/components.md#dense-financial-tables, #500, #513 */
|
|
394
|
+
.ui-table--pinned .ui-table__identity {
|
|
395
|
+
width: 1%;
|
|
396
|
+
max-width: var(--ui-table-identity-max);
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
overflow: clip;
|
|
399
|
+
text-overflow: ellipsis;
|
|
400
|
+
}
|
|
401
|
+
/* A sub-line is one line of running text and cuts itself: the cell's ellipsis cannot
|
|
402
|
+
reach into a child block. 4px is a focused link's ring, inside the cell's padding.
|
|
403
|
+
Nothing is capped here — block layout already holds a child to the cell. */
|
|
404
|
+
.ui-table--pinned .ui-table__identity > * {
|
|
405
|
+
overflow: clip;
|
|
406
|
+
overflow-clip-margin: var(--space-1);
|
|
407
|
+
text-overflow: ellipsis;
|
|
408
|
+
}
|
|
409
|
+
/* A picture has no shorter form, so it keeps its intrinsic size and the cell cuts it.
|
|
410
|
+
Nothing caps it, so write neither a cap nor a reset of one here. #513 */
|
|
411
|
+
.ui-table--pinned .ui-table__identity :is(svg, img, canvas, video, picture) {
|
|
412
|
+
overflow: visible;
|
|
413
|
+
text-overflow: clip;
|
|
414
|
+
}
|
|
415
|
+
.ui-table--pinned .ui-table__identity > *:has(:is(svg, img, canvas, video, picture)) {
|
|
416
|
+
text-overflow: clip;
|
|
417
|
+
}
|
|
418
|
+
/* A control's label is text, so it yields: held to the cell and cut at its END. A kit
|
|
419
|
+
control centres its label, and a capped one that centres loses the front of it. #513 */
|
|
420
|
+
.ui-table--pinned .ui-table__identity :is(.ui-btn, .ui-nav__item, .ui-badge, button, input, select, textarea) {
|
|
421
|
+
max-width: 100%;
|
|
422
|
+
min-width: auto;
|
|
423
|
+
overflow: hidden;
|
|
424
|
+
justify-content: flex-start;
|
|
425
|
+
text-align: start;
|
|
426
|
+
}
|
|
427
|
+
/* `a:not([class])` above is an inline-block so a WRAPPING title link paints one ring.
|
|
428
|
+
Nothing wraps here, and an inline-block is atomic: `text-overflow` cannot cut inside
|
|
429
|
+
one, so the link came out cut mid-glyph. Inline, it draws one fragment and one
|
|
430
|
+
ring, and the line's ellipsis reaches it. #513 */
|
|
431
|
+
.ui-table--pinned .ui-table__identity a:not([class]) { display: inline; }
|
|
432
|
+
/* Except where the fill gave the lone link the cell's box: it cuts as a block box. */
|
|
433
|
+
.ui-table--pinned .ui-table__identity.ui-table__linked > a:not([class]):only-child {
|
|
434
|
+
display: block;
|
|
435
|
+
align-content: center;
|
|
436
|
+
}
|
|
437
|
+
/* The ring goes on the LINE: a link longer than the cell runs past the cut, so a ring
|
|
438
|
+
around the link has no surface to draw its right side on. Inward, like every ring
|
|
439
|
+
the kit puts in a clipped region. why: docs/foundations.md#the-focus-ring, #513 */
|
|
440
|
+
.ui-table--pinned .ui-table__identity > *:has(a:focus-visible) {
|
|
441
|
+
outline: var(--ring-scroll);
|
|
442
|
+
outline-offset: var(--ring-scroll-offset);
|
|
443
|
+
border-radius: var(--radius-xs);
|
|
444
|
+
}
|
|
445
|
+
.ui-table--pinned .ui-table__identity > * a:focus-visible { outline: none; }
|
|
446
|
+
.ui-table--pinned .ui-table__identity:not(.ui-table__linked):has(> a:focus-visible) {
|
|
447
|
+
outline: var(--ring-scroll);
|
|
448
|
+
outline-offset: var(--ring-scroll-offset);
|
|
449
|
+
border-radius: var(--radius-xs);
|
|
450
|
+
}
|
|
451
|
+
.ui-table--pinned .ui-table__identity:not(.ui-table__linked) > a:focus-visible { outline: none; }
|
|
360
452
|
.ui-table--pinned .ui-identity__logo { display: none; }
|
|
361
|
-
|
|
362
|
-
|
|
453
|
+
/* An inline-flex box is the one child block layout does not hold to the cell, so the
|
|
454
|
+
cap belongs on it. The whole name stays in the DOM; only the drawn line is cut. */
|
|
455
|
+
.ui-table--pinned .ui-identity { max-width: 100%; }
|
|
456
|
+
.ui-table--pinned .ui-identity__name { min-width: 0; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
363
457
|
}
|
|
364
458
|
|
|
365
459
|
/* One width reserves the selection track and offsets the adjacent identity. */
|
|
366
|
-
.ui-table--pinned { --ui-table-selection-width: var(--space-12); }
|
|
460
|
+
.ui-table--pinned { --ui-table-selection-width: var(--space-12); --ui-table-identity-max: min(var(--panel-sm), 50vw); }
|
|
367
461
|
.ui-table--pinned .ui-table__selection { box-sizing: border-box; width: var(--ui-table-selection-width); min-width: var(--ui-table-selection-width); max-width: var(--ui-table-selection-width); padding-inline: 0; text-align: center; }
|
|
368
462
|
.ui-table--pinned .ui-table__selection + .ui-table__identity { left: var(--ui-table-selection-width); }
|
|
369
463
|
|
|
@@ -413,10 +507,44 @@ a.ui-identity:hover { text-decoration: underline; }
|
|
|
413
507
|
.ui-table.ui-table--stack.ui-table--pinned > :where(thead, tbody) > tr > :where(.ui-table__identity, .ui-table__selection) {
|
|
414
508
|
position: static; width: auto; min-width: 0; max-width: none;
|
|
415
509
|
padding-inline: 0; border-right: 0; text-align: left;
|
|
510
|
+
/* The cap's cut goes with the cap: a card is as wide as the row, so there is
|
|
511
|
+
nothing to cut, and a clip box here would only reach a focused link's ring. */
|
|
512
|
+
overflow: visible; text-overflow: clip;
|
|
416
513
|
}
|
|
514
|
+
/* The name is what the card is about, and a card has the width to wrap it. #500 put
|
|
515
|
+
`nowrap` on it for the narrow column, so the stacked row takes that off too. */
|
|
417
516
|
.ui-table.ui-table--stack.ui-table--pinned .ui-identity__name {
|
|
418
517
|
position: static; width: auto; height: auto; max-width: none;
|
|
419
|
-
overflow: visible; clip-path: none;
|
|
518
|
+
overflow: visible; clip-path: none; white-space: normal; text-overflow: clip;
|
|
519
|
+
}
|
|
520
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-table__identity > * {
|
|
521
|
+
overflow: visible; text-overflow: clip;
|
|
522
|
+
}
|
|
523
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-table__identity > * a {
|
|
524
|
+
max-width: none; overflow: visible; text-overflow: clip;
|
|
525
|
+
}
|
|
526
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-table__identity > a:not([class]) {
|
|
527
|
+
display: inline-block;
|
|
528
|
+
}
|
|
529
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-table__identity:not(.ui-table__linked):has(> a:focus-visible) {
|
|
530
|
+
outline: none;
|
|
531
|
+
}
|
|
532
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-table__identity:not(.ui-table__linked) > a:focus-visible {
|
|
533
|
+
outline: var(--ring);
|
|
534
|
+
outline-offset: var(--ring-offset);
|
|
535
|
+
}
|
|
536
|
+
/* A card has no column to fill: every cell is a block on its own line and the ROW pays
|
|
537
|
+
the inset, so the fill's padding would stand the link a step right of the card's text
|
|
538
|
+
edge and the cell's `height: 1px` would hold a block cell to one pixel. Written through
|
|
539
|
+
the row like the resets above it, so it outranks the fill and its density. */
|
|
540
|
+
.ui-table.ui-table--stack > tbody > tr > td.ui-table__linked:has(> :is(a:not([class]), a.ui-identity):only-child) { height: auto; }
|
|
541
|
+
.ui-table.ui-table--stack > tbody > tr > td.ui-table__linked > :is(a:not([class]), a.ui-identity):only-child {
|
|
542
|
+
width: auto; height: auto; padding: 0;
|
|
543
|
+
}
|
|
544
|
+
/* The stacked card gives the link its own box again, so it takes the outset band. */
|
|
545
|
+
.ui-table.ui-table--stack > tbody > tr > td.ui-table__linked > a:only-child:focus-visible {
|
|
546
|
+
outline: var(--ring);
|
|
547
|
+
outline-offset: var(--ring-offset);
|
|
420
548
|
}
|
|
421
549
|
/* The bleed now pays for the row's inset instead of the end cells', so the cells still
|
|
422
550
|
start on the card's own text edge and the outline keeps its room on both sides. Same
|
package/src/styles/topbar.css
CHANGED
|
@@ -206,10 +206,10 @@
|
|
|
206
206
|
transition: background var(--dur-fast) var(--ease);
|
|
207
207
|
}
|
|
208
208
|
/* Menu rows use the reading surface and an edge on hover — see
|
|
209
|
-
src/styles/dropdown.css:
|
|
209
|
+
src/styles/dropdown.css:271 `.ui-dropdown__item:hover`.
|
|
210
210
|
why: docs/foundations.md#elevation */
|
|
211
211
|
.vopt:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
|
|
212
|
-
/* The row is a keyboard stop: src/components/dropdown.js:
|
|
212
|
+
/* The row is a keyboard stop: src/components/dropdown.js:1107
|
|
213
213
|
`items[next < 0 ? 0 : next]?.focus()` moves focus onto a `[data-dd-item]` with
|
|
214
214
|
the arrow keys, and this is one. It takes the ring on its own 10px radius, the
|
|
215
215
|
same way `.amenu a` above does. #482 */
|