@apliteni/apliteni-ui 0.116.0 → 0.117.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 +14 -0
- package/package.json +1 -1
- package/react/dist/index.css +6 -0
- package/react/dist/index.d.ts +3 -2
- package/react/dist/index.js +155 -113
- package/src/components/filter-bar.js +3 -4
- package/src/styles/button.css +30 -1
- package/src/styles/card.css +5 -0
- package/src/styles/pagination.css +3 -1
- package/src/styles/table.css +11 -0
|
@@ -60,6 +60,20 @@
|
|
|
60
60
|
|
|
61
61
|
**Don't:** Hide units in muted text or make readers guess what an abbreviation means.
|
|
62
62
|
|
|
63
|
+
## Size the table to its content
|
|
64
|
+
|
|
65
|
+
<!-- rule: content-width -->
|
|
66
|
+
|
|
67
|
+
**Why:** Large gaps separate labels from values.
|
|
68
|
+
|
|
69
|
+
**Rule:** Limit each column to its longest cell plus the cell inset.
|
|
70
|
+
|
|
71
|
+
**Do:** Keep amounts beside their references in a two-column table.
|
|
72
|
+
|
|
73
|
+
**Don't:** Stretch short tables across the page.
|
|
74
|
+
|
|
75
|
+
**Except:** A text column intended to grow, such as a name or title, takes the remaining width.
|
|
76
|
+
|
|
63
77
|
## Scroll instead of squeezing
|
|
64
78
|
|
|
65
79
|
<!-- rule: overflow -->
|
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
package/react/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ declare const Switch: react.ForwardRefExoticComponent<Omit<InputHTMLAttributes<H
|
|
|
23
23
|
} & react.RefAttributes<HTMLInputElement>>;
|
|
24
24
|
|
|
25
25
|
type ButtonOptions = {
|
|
26
|
-
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
26
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
|
|
27
27
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
28
28
|
icon?: string;
|
|
29
29
|
/** Decorative caller artwork, before the label; takes precedence over icon. */
|
|
@@ -581,6 +581,7 @@ type FilterBarProps = {
|
|
|
581
581
|
filters: Filter[];
|
|
582
582
|
label?: string;
|
|
583
583
|
clearLabel?: string;
|
|
584
|
+
clearVariant?: 'ghost' | 'secondary' | 'link';
|
|
584
585
|
disabled?: boolean;
|
|
585
586
|
busy?: boolean;
|
|
586
587
|
add?: AddFilter[];
|
|
@@ -590,7 +591,7 @@ type FilterBarProps = {
|
|
|
590
591
|
onChange: (id: string, value: string | undefined) => void;
|
|
591
592
|
onAdd?: (id: string, value: string | undefined) => void;
|
|
592
593
|
};
|
|
593
|
-
declare function FilterBar({ filters, label, clearLabel, add, addLabel, disabled, busy, onRemove, onClear, onChange, onAdd }: FilterBarProps): react.JSX.Element;
|
|
594
|
+
declare function FilterBar({ filters, label, clearLabel, clearVariant, add, addLabel, disabled, busy, onRemove, onClear, onChange, onAdd }: FilterBarProps): react.JSX.Element;
|
|
594
595
|
|
|
595
596
|
type KeyValueRow = {
|
|
596
597
|
label: string;
|
package/react/dist/index.js
CHANGED
|
@@ -945,7 +945,7 @@ function rowId(uid, groups, item) {
|
|
|
945
945
|
}
|
|
946
946
|
|
|
947
947
|
// src/DataTable.tsx
|
|
948
|
-
import { useMemo as useMemo2, useState as useState5 } from "react";
|
|
948
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef9, useState as useState5 } from "react";
|
|
949
949
|
import { DEFAULT_PAGE_SIZE as DEFAULT_PAGE_SIZE2 } from "@apliteni/apliteni-ui";
|
|
950
950
|
|
|
951
951
|
// src/Pagination.tsx
|
|
@@ -1209,6 +1209,33 @@ function DataTable({
|
|
|
1209
1209
|
hasMore = false
|
|
1210
1210
|
}) {
|
|
1211
1211
|
const scrollable = stickyHeader || pinnedIdentity;
|
|
1212
|
+
const table = useRef9(null);
|
|
1213
|
+
const [pagerHost, setPagerHost] = useState5(null);
|
|
1214
|
+
const holdPagerBox = useCallback2(
|
|
1215
|
+
(box) => setPagerHost(box?.parentElement ?? null),
|
|
1216
|
+
[]
|
|
1217
|
+
);
|
|
1218
|
+
const [strip2, setStrip] = useState5();
|
|
1219
|
+
useEffect5(() => {
|
|
1220
|
+
const el = table.current;
|
|
1221
|
+
const host = pagerHost;
|
|
1222
|
+
if (!el || !host || typeof ResizeObserver === "undefined") {
|
|
1223
|
+
setStrip(void 0);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const measure = () => {
|
|
1227
|
+
const rect = el.getBoundingClientRect();
|
|
1228
|
+
const box = host.getBoundingClientRect();
|
|
1229
|
+
const left = box.left + host.clientLeft + (Number.parseFloat(getComputedStyle(host).paddingLeft) || 0);
|
|
1230
|
+
const inset = Math.max(0, rect.left - left);
|
|
1231
|
+
const width = rect.right - left - inset;
|
|
1232
|
+
setStrip(width > 0 ? { width, inset } : void 0);
|
|
1233
|
+
};
|
|
1234
|
+
const observer = new ResizeObserver(measure);
|
|
1235
|
+
observer.observe(el);
|
|
1236
|
+
measure();
|
|
1237
|
+
return () => observer.disconnect();
|
|
1238
|
+
}, [pagerHost, columns, rows]);
|
|
1212
1239
|
const [localSort, setLocalSort] = useState5(
|
|
1213
1240
|
{ key: columns.find((c) => c.sortable)?.key, dir: -1 }
|
|
1214
1241
|
);
|
|
@@ -1271,6 +1298,7 @@ function DataTable({
|
|
|
1271
1298
|
children: /* @__PURE__ */ jsxs13(
|
|
1272
1299
|
"table",
|
|
1273
1300
|
{
|
|
1301
|
+
ref: table,
|
|
1274
1302
|
className: ["ui-table ui-table--hover", (density === "dense" || !density && dense) && "ui-table--dense", density === "compact" && "ui-table--compact", stickyHeader && "ui-table--sticky", pinnedIdentity && "ui-table--pinned"].filter(Boolean).join(" "),
|
|
1275
1303
|
"aria-busy": loading || void 0,
|
|
1276
1304
|
children: [
|
|
@@ -1328,23 +1356,37 @@ function DataTable({
|
|
|
1328
1356
|
)
|
|
1329
1357
|
}
|
|
1330
1358
|
),
|
|
1331
|
-
pager ?
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1359
|
+
pager ? (
|
|
1360
|
+
// The strip that pages a table may not be wider than the rows it pages (#504).
|
|
1361
|
+
// The two are siblings, and no selector sizes one to the other, so the width is
|
|
1362
|
+
// measured: a cap rather than a width, so a pager wider than the table wraps
|
|
1363
|
+
// inside it and a table wider than the room still leaves the strip in the room.
|
|
1364
|
+
/* @__PURE__ */ jsx17(
|
|
1365
|
+
"div",
|
|
1366
|
+
{
|
|
1367
|
+
className: "rx-table-pager",
|
|
1368
|
+
ref: holdPagerBox,
|
|
1369
|
+
style: strip2 ? { maxWidth: strip2.width, marginInlineStart: strip2.inset || void 0 } : void 0,
|
|
1370
|
+
children: /* @__PURE__ */ jsx17(
|
|
1371
|
+
Pagination,
|
|
1372
|
+
{
|
|
1373
|
+
page,
|
|
1374
|
+
pageSize: size,
|
|
1375
|
+
total: owned ? ordered.length : total ?? null,
|
|
1376
|
+
hasMore,
|
|
1377
|
+
pageSizes,
|
|
1378
|
+
loading,
|
|
1379
|
+
...pagerLabel === void 0 ? {} : { label: pagerLabel },
|
|
1380
|
+
onPageChange: goTo,
|
|
1381
|
+
onPageSizeChange: (s) => {
|
|
1382
|
+
if (pageSize === void 0) setLocalSize(s);
|
|
1383
|
+
onPageSizeChange?.(s);
|
|
1384
|
+
if (owned) setLocalPage(1);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
)
|
|
1346
1388
|
}
|
|
1347
|
-
|
|
1389
|
+
)
|
|
1348
1390
|
) : null
|
|
1349
1391
|
] });
|
|
1350
1392
|
}
|
|
@@ -1424,10 +1466,10 @@ function Denied({
|
|
|
1424
1466
|
|
|
1425
1467
|
// src/DatePicker.tsx
|
|
1426
1468
|
import {
|
|
1427
|
-
useCallback as
|
|
1428
|
-
useEffect as
|
|
1469
|
+
useCallback as useCallback3,
|
|
1470
|
+
useEffect as useEffect6,
|
|
1429
1471
|
useMemo as useMemo3,
|
|
1430
|
-
useRef as
|
|
1472
|
+
useRef as useRef10,
|
|
1431
1473
|
useState as useState6
|
|
1432
1474
|
} from "react";
|
|
1433
1475
|
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
@@ -1440,7 +1482,7 @@ var DAY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
|
1440
1482
|
var PHONE_MAX = 560;
|
|
1441
1483
|
function usePhone() {
|
|
1442
1484
|
const [phone, setPhone] = useState6(false);
|
|
1443
|
-
|
|
1485
|
+
useEffect6(() => {
|
|
1444
1486
|
const query = typeof window === "undefined" ? null : window.matchMedia?.(`(max-width: ${PHONE_MAX}px)`);
|
|
1445
1487
|
if (!query) return;
|
|
1446
1488
|
const read = () => setPhone(query.matches);
|
|
@@ -1535,9 +1577,9 @@ function DatePicker({
|
|
|
1535
1577
|
const picksRange = mode === "range" || mode === "day-range";
|
|
1536
1578
|
const phone = usePhone();
|
|
1537
1579
|
const asSheet = sheet ?? phone;
|
|
1538
|
-
const root =
|
|
1539
|
-
const trigger =
|
|
1540
|
-
const grid =
|
|
1580
|
+
const root = useRef10(null);
|
|
1581
|
+
const trigger = useRef10(null);
|
|
1582
|
+
const grid = useRef10(null);
|
|
1541
1583
|
const [selfOpen, setSelfOpen] = useState6(defaultOpen);
|
|
1542
1584
|
const open = openProp ?? selfOpen;
|
|
1543
1585
|
const [selfValue, setSelfValue] = useState6(defaultValue);
|
|
@@ -1555,7 +1597,7 @@ function DatePicker({
|
|
|
1555
1597
|
weekdayShort: new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" })
|
|
1556
1598
|
}), [locale]);
|
|
1557
1599
|
const monthLabels = useMemo3(() => evenMonthLabels(names.monthShort), [names]);
|
|
1558
|
-
const at =
|
|
1600
|
+
const at = useCallback3((index) => new Date(
|
|
1559
1601
|
grain === "month" ? Date.UTC(Math.floor(index / 12), index % 12, 1) : index * DAY_MS
|
|
1560
1602
|
), [grain]);
|
|
1561
1603
|
const lo = toIndex(min, grain, "start");
|
|
@@ -1574,19 +1616,19 @@ function DatePicker({
|
|
|
1574
1616
|
}, [today, grain]);
|
|
1575
1617
|
const anchor = toIndex(picked, grain) ?? toIndex(picksRange ? span.start ?? span.end : null, grain) ?? todayIndex;
|
|
1576
1618
|
const [cursor, setCursor] = useState6(() => clamp(anchor, lo, hi));
|
|
1577
|
-
const land =
|
|
1578
|
-
const setOpen =
|
|
1619
|
+
const land = useRef10(false);
|
|
1620
|
+
const setOpen = useCallback3((next) => {
|
|
1579
1621
|
if (openProp === void 0) setSelfOpen(next);
|
|
1580
1622
|
onOpenChange?.(next);
|
|
1581
1623
|
}, [openProp, onOpenChange]);
|
|
1582
|
-
const close =
|
|
1624
|
+
const close = useRef10(setOpen);
|
|
1583
1625
|
useIsoLayoutEffect(() => {
|
|
1584
1626
|
close.current = setOpen;
|
|
1585
1627
|
});
|
|
1586
|
-
|
|
1628
|
+
useEffect6(() => {
|
|
1587
1629
|
if (open) setCursor(clamp(anchor, lo, hi));
|
|
1588
1630
|
}, [open]);
|
|
1589
|
-
|
|
1631
|
+
useEffect6(() => {
|
|
1590
1632
|
if (!open || asSheet) return;
|
|
1591
1633
|
const doc = root.current?.ownerDocument ?? document;
|
|
1592
1634
|
const onClick = (e) => {
|
|
@@ -1612,7 +1654,7 @@ function DatePicker({
|
|
|
1612
1654
|
const columns = grain === "month" ? 3 : 7;
|
|
1613
1655
|
const page = grain === "month" ? Math.floor(cursor / 12) : monthOf(cursor);
|
|
1614
1656
|
const caption = grain === "month" ? String(page) : names.monthYear.format(at(firstDayOf(page)));
|
|
1615
|
-
const periodName =
|
|
1657
|
+
const periodName = useCallback3((index) => grain === "month" ? names.monthYear.format(at(index)) : `${names.weekday.format(at(index))} ${names.day.format(at(index))}`, [grain, names, at]);
|
|
1616
1658
|
const count = grain === "month" ? 12 : daysInMonth(page);
|
|
1617
1659
|
const first = grain === "month" ? page * 12 : firstDayOf(page);
|
|
1618
1660
|
const last = first + count - 1;
|
|
@@ -1895,10 +1937,10 @@ function DatePicker({
|
|
|
1895
1937
|
// src/Dropdown.tsx
|
|
1896
1938
|
import {
|
|
1897
1939
|
Fragment as Fragment4,
|
|
1898
|
-
useCallback as
|
|
1899
|
-
useEffect as
|
|
1940
|
+
useCallback as useCallback4,
|
|
1941
|
+
useEffect as useEffect7,
|
|
1900
1942
|
useId as useId7,
|
|
1901
|
-
useRef as
|
|
1943
|
+
useRef as useRef11,
|
|
1902
1944
|
useState as useState7
|
|
1903
1945
|
} from "react";
|
|
1904
1946
|
import {
|
|
@@ -1975,19 +2017,19 @@ function Dropdown({
|
|
|
1975
2017
|
const [pick, setPick] = useState7(null);
|
|
1976
2018
|
const [query, setQuery] = useState7(() => search && search !== true ? search.query || "" : "");
|
|
1977
2019
|
const [activeKey, setActiveKey] = useState7(null);
|
|
1978
|
-
const root =
|
|
1979
|
-
const panel =
|
|
1980
|
-
const trigger =
|
|
1981
|
-
const field =
|
|
1982
|
-
const landOn =
|
|
1983
|
-
const at =
|
|
2020
|
+
const root = useRef11(null);
|
|
2021
|
+
const panel = useRef11(null);
|
|
2022
|
+
const trigger = useRef11(null);
|
|
2023
|
+
const field = useRef11(null);
|
|
2024
|
+
const landOn = useRef11(null);
|
|
2025
|
+
const at = useRef11("");
|
|
1984
2026
|
const auto = useId7().replace(/:/g, "");
|
|
1985
2027
|
const uid = id ?? auto;
|
|
1986
|
-
const setOpen =
|
|
2028
|
+
const setOpen = useCallback4((next) => {
|
|
1987
2029
|
if (openProp === void 0) setSelfOpen(next);
|
|
1988
2030
|
onOpenChange?.(next);
|
|
1989
2031
|
}, [openProp, onOpenChange]);
|
|
1990
|
-
const close =
|
|
2032
|
+
const close = useRef11(setOpen);
|
|
1991
2033
|
useIsoLayoutEffect(() => {
|
|
1992
2034
|
close.current = setOpen;
|
|
1993
2035
|
});
|
|
@@ -2040,7 +2082,7 @@ function Dropdown({
|
|
|
2040
2082
|
stop.forEach((off) => off());
|
|
2041
2083
|
};
|
|
2042
2084
|
}, [open]);
|
|
2043
|
-
const opened =
|
|
2085
|
+
const opened = useRef11(false);
|
|
2044
2086
|
useIsoLayoutEffect(() => {
|
|
2045
2087
|
const el = panel.current;
|
|
2046
2088
|
if (open) {
|
|
@@ -2079,7 +2121,7 @@ function Dropdown({
|
|
|
2079
2121
|
const given = rows.find((it) => it.selected);
|
|
2080
2122
|
const givenKey = given ? keyOf(given) : null;
|
|
2081
2123
|
const pickedKey = pick && pick.against === givenKey ? pick.key : null;
|
|
2082
|
-
|
|
2124
|
+
useEffect7(() => {
|
|
2083
2125
|
if (pick && pick.against !== givenKey) setPick(null);
|
|
2084
2126
|
}, [givenKey, pick]);
|
|
2085
2127
|
const picked = pickedKey != null ? rows.find((it) => keyOf(it) === pickedKey) : void 0;
|
|
@@ -2093,7 +2135,7 @@ function Dropdown({
|
|
|
2093
2135
|
const rowIds = /* @__PURE__ */ new Map();
|
|
2094
2136
|
rows.forEach((it, i) => rowIds.set(it, `${uid}-opt-${i}`));
|
|
2095
2137
|
const listId = `${uid}-list`;
|
|
2096
|
-
|
|
2138
|
+
useEffect7(() => {
|
|
2097
2139
|
if (!open) return;
|
|
2098
2140
|
const mine = () => close.current(false);
|
|
2099
2141
|
for (const other of [...openNow]) other();
|
|
@@ -2102,7 +2144,7 @@ function Dropdown({
|
|
|
2102
2144
|
openNow.delete(mine);
|
|
2103
2145
|
};
|
|
2104
2146
|
}, [open]);
|
|
2105
|
-
|
|
2147
|
+
useEffect7(() => {
|
|
2106
2148
|
if (!open) return;
|
|
2107
2149
|
const doc = root.current?.ownerDocument ?? document;
|
|
2108
2150
|
const onClick = (e) => {
|
|
@@ -2567,15 +2609,15 @@ function Segmented({ options, value, onChange, label, appearance = "pill", size,
|
|
|
2567
2609
|
}
|
|
2568
2610
|
|
|
2569
2611
|
// src/FilterBar.tsx
|
|
2570
|
-
import { useRef as
|
|
2612
|
+
import { useRef as useRef12, useState as useState8 } from "react";
|
|
2571
2613
|
import { filterChipText, filterChipName, filterChipUnset, filterChipItems, focusNextStop } from "@apliteni/apliteni-ui";
|
|
2572
2614
|
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2573
2615
|
var SEARCH_AT = 10;
|
|
2574
2616
|
var isRow2 = (entry) => entry !== "---" && !entry.separator;
|
|
2575
|
-
function FilterBar({ filters, label = "Filters", clearLabel = "Clear all", add, addLabel = "Filter", disabled = false, busy = false, onRemove, onClear, onChange, onAdd }) {
|
|
2576
|
-
const bar =
|
|
2577
|
-
const focused =
|
|
2578
|
-
const landing =
|
|
2617
|
+
function FilterBar({ filters, label = "Filters", clearLabel = "Clear all", clearVariant = "ghost", add, addLabel = "Filter", disabled = false, busy = false, onRemove, onClear, onChange, onAdd }) {
|
|
2618
|
+
const bar = useRef12(null);
|
|
2619
|
+
const focused = useRef12(null);
|
|
2620
|
+
const landing = useRef12(null);
|
|
2579
2621
|
const [opened, setOpened] = useState8({});
|
|
2580
2622
|
const [adding, setAdding] = useState8(false);
|
|
2581
2623
|
const blocked = disabled || busy;
|
|
@@ -2693,7 +2735,7 @@ function FilterBar({ filters, label = "Filters", clearLabel = "Clear all", add,
|
|
|
2693
2735
|
) }),
|
|
2694
2736
|
filters.length > 0 && /* @__PURE__ */ jsx24("span", { "data-filter-clear": "", onFocus: () => {
|
|
2695
2737
|
focused.current = { id: null, index: 0 };
|
|
2696
|
-
}, children: /* @__PURE__ */ jsx24(Button, { size: "sm", variant:
|
|
2738
|
+
}, children: /* @__PURE__ */ jsx24(Button, { size: "sm", variant: clearVariant, onClick: () => {
|
|
2697
2739
|
if (!blocked) onClear();
|
|
2698
2740
|
}, children: clearLabel }) })
|
|
2699
2741
|
]
|
|
@@ -2726,13 +2768,13 @@ function DrawerSection({ title, headingLevel = 3, className, children, ...rest }
|
|
|
2726
2768
|
}
|
|
2727
2769
|
|
|
2728
2770
|
// src/Snippet.tsx
|
|
2729
|
-
import { useEffect as
|
|
2771
|
+
import { useEffect as useEffect8, useRef as useRef13, useState as useState9 } from "react";
|
|
2730
2772
|
import { Fragment as Fragment7, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2731
2773
|
function Snippet({ label = "shell", code = "", children, copy = true, reveal = false, copyLabel = "Copy code" }) {
|
|
2732
2774
|
const [status, setStatus] = useState9("idle");
|
|
2733
|
-
const request =
|
|
2734
|
-
const timer =
|
|
2735
|
-
|
|
2775
|
+
const request = useRef13(0);
|
|
2776
|
+
const timer = useRef13(void 0);
|
|
2777
|
+
useEffect8(() => {
|
|
2736
2778
|
setStatus("idle");
|
|
2737
2779
|
return () => {
|
|
2738
2780
|
request.current++;
|
|
@@ -2775,15 +2817,15 @@ function Snippet({ label = "shell", code = "", children, copy = true, reveal = f
|
|
|
2775
2817
|
}
|
|
2776
2818
|
|
|
2777
2819
|
// src/Tabs.tsx
|
|
2778
|
-
import { useEffect as
|
|
2820
|
+
import { useEffect as useEffect9, useId as useId8, useRef as useRef14 } from "react";
|
|
2779
2821
|
import { playEntrance } from "@apliteni/apliteni-ui/motion";
|
|
2780
2822
|
import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2781
2823
|
function Tabs({ items, value, onChange, label }) {
|
|
2782
2824
|
const id = useId8();
|
|
2783
|
-
const previous =
|
|
2784
|
-
const activePanel =
|
|
2785
|
-
const buttons =
|
|
2786
|
-
|
|
2825
|
+
const previous = useRef14(value);
|
|
2826
|
+
const activePanel = useRef14(null);
|
|
2827
|
+
const buttons = useRef14([]);
|
|
2828
|
+
useEffect9(() => {
|
|
2787
2829
|
if (previous.current !== value) playEntrance(activePanel.current);
|
|
2788
2830
|
previous.current = value;
|
|
2789
2831
|
}, [value]);
|
|
@@ -2852,14 +2894,14 @@ function Tabs({ items, value, onChange, label }) {
|
|
|
2852
2894
|
}
|
|
2853
2895
|
|
|
2854
2896
|
// src/Timeline.tsx
|
|
2855
|
-
import { useEffect as
|
|
2897
|
+
import { useEffect as useEffect10, useRef as useRef15 } from "react";
|
|
2856
2898
|
import { playEntrance as playEntrance2, prefersReducedMotion } from "@apliteni/apliteni-ui/motion";
|
|
2857
2899
|
import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2858
2900
|
var kindIcons = { person: "user", rule: "bolt", reversal: "refresh" };
|
|
2859
2901
|
function Timeline({ events, className, ...rest }) {
|
|
2860
|
-
const previousIds =
|
|
2861
|
-
const rows =
|
|
2862
|
-
|
|
2902
|
+
const previousIds = useRef15(new Set(events.map((event) => event.id)));
|
|
2903
|
+
const rows = useRef15(/* @__PURE__ */ new Map());
|
|
2904
|
+
useEffect10(() => {
|
|
2863
2905
|
if (previousIds.current.size > 0 && !prefersReducedMotion()) {
|
|
2864
2906
|
for (const event of events) {
|
|
2865
2907
|
if (previousIds.current.has(event.id)) continue;
|
|
@@ -2902,7 +2944,7 @@ function Timeline({ events, className, ...rest }) {
|
|
|
2902
2944
|
}
|
|
2903
2945
|
|
|
2904
2946
|
// src/Field.tsx
|
|
2905
|
-
import { forwardRef as forwardRef6, useEffect as
|
|
2947
|
+
import { forwardRef as forwardRef6, useEffect as useEffect11, useId as useId9, useRef as useRef16, useState as useState10 } from "react";
|
|
2906
2948
|
import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
|
|
2907
2949
|
import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2908
2950
|
function messages(id, hint, error) {
|
|
@@ -2990,10 +3032,10 @@ function SelectField({ label, hint, error, required, className = "", children, .
|
|
|
2990
3032
|
}
|
|
2991
3033
|
function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
|
|
2992
3034
|
const id = useId9();
|
|
2993
|
-
const input =
|
|
3035
|
+
const input = useRef16(null);
|
|
2994
3036
|
const [file, setFile] = useState10(null);
|
|
2995
3037
|
const [dragging, setDragging] = useState10(false);
|
|
2996
|
-
|
|
3038
|
+
useEffect11(() => {
|
|
2997
3039
|
const form = input.current?.form;
|
|
2998
3040
|
const reset = () => {
|
|
2999
3041
|
setFile(null);
|
|
@@ -3052,7 +3094,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
|
|
|
3052
3094
|
}
|
|
3053
3095
|
|
|
3054
3096
|
// src/FileDrop.tsx
|
|
3055
|
-
import { useRef as
|
|
3097
|
+
import { useRef as useRef17, useState as useState11 } from "react";
|
|
3056
3098
|
import { Fragment as Fragment9, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
3057
3099
|
var MARKS = {
|
|
3058
3100
|
uploading: "clock",
|
|
@@ -3086,7 +3128,7 @@ function FileDrop({
|
|
|
3086
3128
|
children,
|
|
3087
3129
|
className
|
|
3088
3130
|
}) {
|
|
3089
|
-
const input =
|
|
3131
|
+
const input = useRef17(null);
|
|
3090
3132
|
const [over, setOver] = useState11(false);
|
|
3091
3133
|
const status = file?.status ?? "uploading";
|
|
3092
3134
|
const word = status === "error" ? file?.error ?? WORDS.error : file?.state ?? WORDS[status];
|
|
@@ -3221,7 +3263,7 @@ var SearchField = forwardRef7(function SearchField2({ ariaLabel, className, ...p
|
|
|
3221
3263
|
});
|
|
3222
3264
|
|
|
3223
3265
|
// src/Toast.tsx
|
|
3224
|
-
import { createContext as createContext2, useCallback as
|
|
3266
|
+
import { createContext as createContext2, useCallback as useCallback5, useContext as useContext2, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useRef as useRef18, useState as useState12 } from "react";
|
|
3225
3267
|
import { createPortal as createPortal4 } from "react-dom";
|
|
3226
3268
|
import { jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
3227
3269
|
var Context = createContext2(null);
|
|
@@ -3232,21 +3274,21 @@ function useToast() {
|
|
|
3232
3274
|
return push;
|
|
3233
3275
|
}
|
|
3234
3276
|
function Notice({ notice, remove }) {
|
|
3235
|
-
const root =
|
|
3277
|
+
const root = useRef18(null);
|
|
3236
3278
|
const [leaving2, setLeaving] = useState12(false);
|
|
3237
|
-
const dismissed =
|
|
3238
|
-
const remaining =
|
|
3279
|
+
const dismissed = useRef18(false);
|
|
3280
|
+
const remaining = useRef18(5e3);
|
|
3239
3281
|
const [hovered, setHovered] = useState12(false);
|
|
3240
3282
|
const [focused, setFocused] = useState12(false);
|
|
3241
3283
|
const paused = hovered || focused;
|
|
3242
3284
|
const tone = notice.tone ?? "success";
|
|
3243
|
-
const dismiss =
|
|
3285
|
+
const dismiss = useCallback5(() => {
|
|
3244
3286
|
if (dismissed.current) return;
|
|
3245
3287
|
dismissed.current = true;
|
|
3246
3288
|
if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) remove();
|
|
3247
3289
|
else setLeaving(true);
|
|
3248
3290
|
}, [remove]);
|
|
3249
|
-
|
|
3291
|
+
useEffect12(() => {
|
|
3250
3292
|
if (leaving2) {
|
|
3251
3293
|
const element = root.current;
|
|
3252
3294
|
const finish = (event) => {
|
|
@@ -3260,7 +3302,7 @@ function Notice({ notice, remove }) {
|
|
|
3260
3302
|
};
|
|
3261
3303
|
}
|
|
3262
3304
|
}, [leaving2, remove]);
|
|
3263
|
-
|
|
3305
|
+
useEffect12(() => {
|
|
3264
3306
|
if (leaving2 || notice.action || paused) return;
|
|
3265
3307
|
const started = Date.now();
|
|
3266
3308
|
const timer = setTimeout(dismiss, remaining.current);
|
|
@@ -3338,9 +3380,9 @@ function usePublishedReach(stack2, count) {
|
|
|
3338
3380
|
}
|
|
3339
3381
|
function Toast({ children }) {
|
|
3340
3382
|
const [entries, setEntries] = useState12([]);
|
|
3341
|
-
const nextId =
|
|
3342
|
-
const stack2 =
|
|
3343
|
-
const push =
|
|
3383
|
+
const nextId = useRef18(0);
|
|
3384
|
+
const stack2 = useRef18(null);
|
|
3385
|
+
const push = useCallback5((notice) => {
|
|
3344
3386
|
const id = nextId.current++;
|
|
3345
3387
|
const remove = () => setEntries((current) => current.filter((entry) => entry.id !== id));
|
|
3346
3388
|
setEntries((current) => [...current, { id, notice, remove }]);
|
|
@@ -3356,11 +3398,11 @@ function Toast({ children }) {
|
|
|
3356
3398
|
}
|
|
3357
3399
|
|
|
3358
3400
|
// src/AppShell.tsx
|
|
3359
|
-
import { useEffect as
|
|
3401
|
+
import { useEffect as useEffect14, useId as useId11, useState as useState14 } from "react";
|
|
3360
3402
|
import { applyTheme, paletteHotkey, prism, railCollapsed, themeIcon, themeName } from "@apliteni/apliteni-ui";
|
|
3361
3403
|
|
|
3362
3404
|
// src/SidebarNav.tsx
|
|
3363
|
-
import { forwardRef as forwardRef8, useEffect as
|
|
3405
|
+
import { forwardRef as forwardRef8, useEffect as useEffect13, useId as useId10, useState as useState13 } from "react";
|
|
3364
3406
|
import { ENTRANCE_FALLBACK_MS } from "@apliteni/apliteni-ui/motion";
|
|
3365
3407
|
import { Fragment as Fragment10, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3366
3408
|
var badgeText = (badge) => typeof badge === "object" ? badge.text : badge;
|
|
@@ -3371,7 +3413,7 @@ function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
|
|
|
3371
3413
|
const [expanded, setExpanded] = useState13();
|
|
3372
3414
|
const open = expanded ?? item.defaultOpen ?? childActive;
|
|
3373
3415
|
const [entering, setEntering] = useState13(false);
|
|
3374
|
-
|
|
3416
|
+
useEffect13(() => {
|
|
3375
3417
|
if (!entering) return;
|
|
3376
3418
|
const timer = setTimeout(() => setEntering(false), ENTRANCE_FALLBACK_MS);
|
|
3377
3419
|
return () => clearTimeout(timer);
|
|
@@ -3487,14 +3529,14 @@ function AppShell({
|
|
|
3487
3529
|
const [theme, setTheme] = useState14("dark");
|
|
3488
3530
|
const current = path(pathname);
|
|
3489
3531
|
const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
|
|
3490
|
-
|
|
3532
|
+
useEffect14(() => {
|
|
3491
3533
|
setCollapsed(railCollapsed() ?? defaultCollapsed);
|
|
3492
3534
|
}, [defaultCollapsed]);
|
|
3493
|
-
|
|
3535
|
+
useEffect14(() => {
|
|
3494
3536
|
setMore(false);
|
|
3495
3537
|
setSearch(false);
|
|
3496
3538
|
}, [pathname]);
|
|
3497
|
-
|
|
3539
|
+
useEffect14(() => {
|
|
3498
3540
|
const root = document.documentElement;
|
|
3499
3541
|
const sync2 = () => setTheme(root.getAttribute("data-theme") === "light" ? "light" : "dark");
|
|
3500
3542
|
sync2();
|
|
@@ -3502,7 +3544,7 @@ function AppShell({
|
|
|
3502
3544
|
observer.observe(root, { attributes: true, attributeFilter: ["data-theme"] });
|
|
3503
3545
|
return () => observer.disconnect();
|
|
3504
3546
|
}, []);
|
|
3505
|
-
|
|
3547
|
+
useEffect14(() => {
|
|
3506
3548
|
const hotkey = (event) => {
|
|
3507
3549
|
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
|
|
3508
3550
|
const target = event.target;
|
|
@@ -3733,7 +3775,7 @@ function ThemeToggle({ labelled = false }) {
|
|
|
3733
3775
|
}
|
|
3734
3776
|
|
|
3735
3777
|
// src/Success.tsx
|
|
3736
|
-
import { forwardRef as forwardRef9, useEffect as
|
|
3778
|
+
import { forwardRef as forwardRef9, useEffect as useEffect15, useRef as useRef19, useState as useState15 } from "react";
|
|
3737
3779
|
import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
3738
3780
|
var SuccessCheck = forwardRef9(function SuccessCheck2({ variant = "line", className, ...rest }, ref) {
|
|
3739
3781
|
const circled = variant === "circled";
|
|
@@ -3781,11 +3823,11 @@ var pieces = [
|
|
|
3781
3823
|
];
|
|
3782
3824
|
function Countdown({ seconds, label = "Redirecting", onDone }) {
|
|
3783
3825
|
const [remaining, setRemaining] = useState15(seconds);
|
|
3784
|
-
const callback =
|
|
3785
|
-
|
|
3826
|
+
const callback = useRef19(onDone);
|
|
3827
|
+
useEffect15(() => {
|
|
3786
3828
|
callback.current = onDone;
|
|
3787
3829
|
}, [onDone]);
|
|
3788
|
-
|
|
3830
|
+
useEffect15(() => {
|
|
3789
3831
|
let count = seconds;
|
|
3790
3832
|
const timer = setInterval(() => {
|
|
3791
3833
|
count -= 1;
|
|
@@ -3857,12 +3899,12 @@ var Success = forwardRef9(function Success2({
|
|
|
3857
3899
|
|
|
3858
3900
|
// src/Chart.tsx
|
|
3859
3901
|
import {
|
|
3860
|
-
useCallback as
|
|
3861
|
-
useEffect as
|
|
3902
|
+
useCallback as useCallback6,
|
|
3903
|
+
useEffect as useEffect16,
|
|
3862
3904
|
useId as useId12,
|
|
3863
3905
|
useLayoutEffect as useLayoutEffect5,
|
|
3864
3906
|
useMemo as useMemo4,
|
|
3865
|
-
useRef as
|
|
3907
|
+
useRef as useRef20,
|
|
3866
3908
|
useState as useState16
|
|
3867
3909
|
} from "react";
|
|
3868
3910
|
import { CHART_FLOOR, bridgeWalk, chartScale, pointTarget } from "@apliteni/apliteni-ui";
|
|
@@ -3905,7 +3947,7 @@ function barPath(m) {
|
|
|
3905
3947
|
}
|
|
3906
3948
|
function useTapFloor() {
|
|
3907
3949
|
const [floor, setFloor] = useState16(TARGET);
|
|
3908
|
-
|
|
3950
|
+
useEffect16(() => {
|
|
3909
3951
|
const query = typeof window === "undefined" ? null : window.matchMedia?.(`(max-width: ${PHONE_MAX2}px) and (pointer: coarse)`);
|
|
3910
3952
|
if (!query) return;
|
|
3911
3953
|
const read = () => setFloor(query.matches ? TAP_FLOOR : TARGET);
|
|
@@ -4028,16 +4070,16 @@ function Chart(props) {
|
|
|
4028
4070
|
const plotHeight = height ?? HEIGHT[variant];
|
|
4029
4071
|
const showTable = table ?? !spark;
|
|
4030
4072
|
const uid = useId12().replace(/[^\w-]/g, "");
|
|
4031
|
-
const frameEl =
|
|
4032
|
-
const scroller =
|
|
4033
|
-
const plot =
|
|
4034
|
-
const tipEl =
|
|
4035
|
-
const svgEl =
|
|
4036
|
-
const ignoreMouseUntil =
|
|
4037
|
-
const touchMoved =
|
|
4038
|
-
const dismissed =
|
|
4039
|
-
const opening =
|
|
4040
|
-
const pointerFocus =
|
|
4073
|
+
const frameEl = useRef20(null);
|
|
4074
|
+
const scroller = useRef20(null);
|
|
4075
|
+
const plot = useRef20(null);
|
|
4076
|
+
const tipEl = useRef20(null);
|
|
4077
|
+
const svgEl = useRef20(null);
|
|
4078
|
+
const ignoreMouseUntil = useRef20(0);
|
|
4079
|
+
const touchMoved = useRef20(false);
|
|
4080
|
+
const dismissed = useRef20(null);
|
|
4081
|
+
const opening = useRef20(false);
|
|
4082
|
+
const pointerFocus = useRef20(false);
|
|
4041
4083
|
const floor = useTapFloor();
|
|
4042
4084
|
const [width, setWidth] = useState16(0);
|
|
4043
4085
|
const [openId, setOpenId] = useState16(null);
|
|
@@ -4058,7 +4100,7 @@ function Chart(props) {
|
|
|
4058
4100
|
const boxHeight = Math.max(plotHeight, floor);
|
|
4059
4101
|
const boxTop = -(boxHeight - plotHeight) / 2;
|
|
4060
4102
|
const span = scale.max - scale.min || 1;
|
|
4061
|
-
const y =
|
|
4103
|
+
const y = useCallback6(
|
|
4062
4104
|
(value) => PAD + (scale.max - value) / span * (plotHeight - PAD * 2),
|
|
4063
4105
|
[scale.max, span, plotHeight]
|
|
4064
4106
|
);
|
|
@@ -4071,7 +4113,7 @@ function Chart(props) {
|
|
|
4071
4113
|
cap * lanes + LANE_GAP * (lanes - 1)
|
|
4072
4114
|
));
|
|
4073
4115
|
const barWidth = (groupWidth - LANE_GAP * (lanes - 1)) / lanes;
|
|
4074
|
-
const centre =
|
|
4116
|
+
const centre = useCallback6((column) => column * colWidth + colWidth / 2, [colWidth]);
|
|
4075
4117
|
const marks = useMemo4(() => {
|
|
4076
4118
|
const out = frame.bars.map((bar) => {
|
|
4077
4119
|
let top = Math.min(y(bar.from), y(bar.to));
|
|
@@ -4145,9 +4187,9 @@ function Chart(props) {
|
|
|
4145
4187
|
return [...out, ...dots];
|
|
4146
4188
|
}, [frame, lines, keys, columns, centre, barWidth, groupWidth, y, floor, colWidth, plotWidth, boxTop, boxHeight]);
|
|
4147
4189
|
const byId = useMemo4(() => new Map(marks.map((m) => [m.id, m])), [marks]);
|
|
4148
|
-
const lead =
|
|
4190
|
+
const lead = useCallback6((column) => marks.find((m) => m.column === column), [marks]);
|
|
4149
4191
|
const open = openId ? byId.get(openId) : void 0;
|
|
4150
|
-
|
|
4192
|
+
useEffect16(() => {
|
|
4151
4193
|
const el = plot.current;
|
|
4152
4194
|
if (!el) return;
|
|
4153
4195
|
const read = () => setWidth(el.clientWidth);
|
|
@@ -4157,20 +4199,20 @@ function Chart(props) {
|
|
|
4157
4199
|
observer.observe(el);
|
|
4158
4200
|
return () => observer.disconnect();
|
|
4159
4201
|
}, []);
|
|
4160
|
-
const syncEdges =
|
|
4202
|
+
const syncEdges = useCallback6(() => {
|
|
4161
4203
|
const el = scroller.current;
|
|
4162
4204
|
if (!el) return;
|
|
4163
4205
|
const last = el.scrollWidth - el.clientWidth;
|
|
4164
4206
|
el.classList.toggle("is-more-before", el.scrollLeft > 1);
|
|
4165
4207
|
el.classList.toggle("is-more-after", el.scrollLeft < last - 1);
|
|
4166
4208
|
}, []);
|
|
4167
|
-
|
|
4209
|
+
useEffect16(syncEdges, [syncEdges, plotWidth, count]);
|
|
4168
4210
|
useLayoutEffect5(() => {
|
|
4169
4211
|
if (!openId || !frameEl.current || !tipEl.current || !svgEl.current) return;
|
|
4170
4212
|
const anchor = [...svgEl.current.querySelectorAll("[data-anchor]")].find((el) => el.getAttribute("data-anchor") === openId);
|
|
4171
4213
|
if (anchor) placeTip(frameEl.current, anchor, tipEl.current, frameEl.current);
|
|
4172
4214
|
}, [openId, plotWidth, plotHeight]);
|
|
4173
|
-
|
|
4215
|
+
useEffect16(() => {
|
|
4174
4216
|
if (!openId) return;
|
|
4175
4217
|
const doc = frameEl.current?.ownerDocument;
|
|
4176
4218
|
const view = doc?.defaultView;
|
|
@@ -4198,7 +4240,7 @@ function Chart(props) {
|
|
|
4198
4240
|
setOpenId(id);
|
|
4199
4241
|
};
|
|
4200
4242
|
const markAt = (target) => target.closest?.("[data-mark]")?.getAttribute("data-mark") ?? null;
|
|
4201
|
-
const announce =
|
|
4243
|
+
const announce = useCallback6((column, picked = pick) => {
|
|
4202
4244
|
const where = columns[column];
|
|
4203
4245
|
if (!where) return;
|
|
4204
4246
|
const values = marks.filter((m) => m.column === column).map((m) => `${m.name} ${format(m.value)}`).join(". ") || "No value";
|
|
@@ -2,7 +2,7 @@ import { button, esc } from './index.js';
|
|
|
2
2
|
import { dropdown, wireDropdown } from './dropdown.js';
|
|
3
3
|
import { filterChipText, filterChipName, filterChipUnset, filterChipItems, focusNextStop } from '../logic/filter-bar.js';
|
|
4
4
|
|
|
5
|
-
export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear all', disabled = false, busy = false } = {}) {
|
|
5
|
+
export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear all', clearVariant = 'ghost', disabled = false, busy = false } = {}) {
|
|
6
6
|
return `<fieldset class="ui-filter-bar" data-filter-bar${disabled || busy ? ' disabled' : ''}${busy ? ' aria-busy="true"' : ''}>`
|
|
7
7
|
+ `<legend class="ui-filter-bar__legend">${esc(label)}</legend>`
|
|
8
8
|
+ filters.map(filter => `<fieldset class="ui-filter-bar__chip" data-filter-id="${esc(filter.id)}"${filter.disabled ? ' disabled' : ''}>`
|
|
@@ -17,11 +17,10 @@ export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear
|
|
|
17
17
|
// row is the only thing in it, and it reads as a bar that has been turned
|
|
18
18
|
// off rather than one with no filters on it. It is the kit's own `one-page`
|
|
19
19
|
// rule — a control with no action to offer is not shown. It returns with
|
|
20
|
-
// the first chip,
|
|
21
|
-
// live action beside two chips has to read as live.
|
|
20
|
+
// the first chip, as a quiet text action.
|
|
22
21
|
// why: docs/components.md#a-filter-row-holds-its-panels
|
|
23
22
|
+ (filters.length
|
|
24
|
-
? `<span data-filter-clear>${button({ label: clearLabel, size: 'sm', variant:
|
|
23
|
+
? `<span data-filter-clear>${button({ label: clearLabel, size: 'sm', variant: clearVariant })}</span>`
|
|
25
24
|
: '')
|
|
26
25
|
+ '</fieldset>';
|
|
27
26
|
}
|
package/src/styles/button.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
* Button — .ui-btn (variants: primary / secondary / ghost / danger)
|
|
2
|
+
* Button — .ui-btn (variants: primary / secondary / ghost / danger / link)
|
|
3
3
|
* (sizes: xs / sm / md / lg, [icon], [disabled], [aria-busy])
|
|
4
4
|
* ========================================================================== */
|
|
5
5
|
|
|
@@ -70,6 +70,22 @@
|
|
|
70
70
|
color: var(--strong);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
.ui-btn--link {
|
|
74
|
+
background: transparent;
|
|
75
|
+
border-color: transparent;
|
|
76
|
+
color: var(--accent);
|
|
77
|
+
padding-inline: 0;
|
|
78
|
+
}
|
|
79
|
+
.ui-btn--link .ui-btn__label-slot { overflow: visible; }
|
|
80
|
+
.ui-btn--link[aria-busy="true"] .ui-btn__label-slot { overflow: hidden; }
|
|
81
|
+
.ui-btn--link .ui-btn__label { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
82
|
+
.ui-btn.ui-btn--link:hover {
|
|
83
|
+
background: transparent;
|
|
84
|
+
border-color: transparent;
|
|
85
|
+
color: var(--strong);
|
|
86
|
+
text-decoration: underline;
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
.ui-btn--danger { color: var(--text); }
|
|
74
90
|
.ui-btn--danger:hover, .ui-btn--danger:focus-visible {
|
|
75
91
|
color: var(--pink);
|
|
@@ -112,6 +128,12 @@
|
|
|
112
128
|
border-color: transparent;
|
|
113
129
|
/* muted-ink: state — unavailable control */ color: var(--disabled-ink-bare);
|
|
114
130
|
}
|
|
131
|
+
.ui-btn--link:disabled,
|
|
132
|
+
.ui-btn--link[aria-disabled="true"] {
|
|
133
|
+
background: transparent;
|
|
134
|
+
border-color: transparent;
|
|
135
|
+
/* muted-ink: state — unavailable link action */ color: var(--disabled-ink-bare);
|
|
136
|
+
}
|
|
115
137
|
/* With no box, the ink is read on whatever is behind it, so the ghost takes an ink
|
|
116
138
|
set for the dullest ground: in dark 7.49 / 6.31 / 7.00 / 5.44 on --bg / --surface /
|
|
117
139
|
--surface-2 / --surface-3, in light 5.85 / 6.50 / 5.49 / 5.70. The enabled ghost now uses body
|
|
@@ -161,6 +183,13 @@
|
|
|
161
183
|
border-color: transparent;
|
|
162
184
|
color: var(--text);
|
|
163
185
|
}
|
|
186
|
+
.ui-btn[aria-busy="true"].ui-btn--link,
|
|
187
|
+
.ui-btn[aria-busy="true"].ui-btn--link:hover,
|
|
188
|
+
.ui-btn[aria-busy="true"].ui-btn--link:active {
|
|
189
|
+
background: transparent; /* code-bg: inherit — busy paint retains the button's containing gap. */
|
|
190
|
+
border-color: transparent;
|
|
191
|
+
color: var(--accent);
|
|
192
|
+
}
|
|
164
193
|
.ui-btn[aria-busy="true"].ui-btn--danger,
|
|
165
194
|
.ui-btn[aria-busy="true"].ui-btn--danger:hover,
|
|
166
195
|
.ui-btn[aria-busy="true"].ui-btn--danger:active {
|
package/src/styles/card.css
CHANGED
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
.ui-card--pad-sm { padding: var(--space-5) var(--space-6); }
|
|
41
41
|
.ui-card--pad-lg { padding: var(--space-8) var(--space-10); }
|
|
42
42
|
|
|
43
|
+
/* A card framing one content-width block ends where the block ends — a 359px table sat in
|
|
44
|
+
a 1232px card (#504) — and is capped, so a wider block still fills the room and scrolls.
|
|
45
|
+
why: docs/foundations.md#spacing-and-rhythm */
|
|
46
|
+
.ui-card--fit { width: fit-content; max-width: 100%; }
|
|
47
|
+
|
|
43
48
|
/* An accent-tinted card, e.g. a highlighted plan / success surface. The accent edge is
|
|
44
49
|
the card's own hairline re-coloured: an inset ring over it would draw two. #295
|
|
45
50
|
The strength is --card-tint, per theme: light mixes down from white, so a tint that
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
.ui-pager {
|
|
11
11
|
display: flex;
|
|
12
12
|
flex-wrap: wrap;
|
|
13
|
+
justify-content: flex-end;
|
|
13
14
|
align-items: center;
|
|
14
15
|
gap: var(--space-3) var(--space-4);
|
|
15
16
|
font-size: var(--text-sm);
|
|
@@ -113,7 +114,8 @@
|
|
|
113
114
|
`margin-top: 16px` for this, which is --space-4; the number moves to the scale
|
|
114
115
|
with it rather than travelling as a literal.
|
|
115
116
|
why: docs/foundations.md#spacing-and-rhythm */
|
|
116
|
-
.ui-table + .ui-pager
|
|
117
|
+
.ui-table + .ui-pager,
|
|
118
|
+
.ui-table-scroll + .ui-pager { margin-top: var(--space-4); }
|
|
117
119
|
|
|
118
120
|
/* A step given an `href` is an anchor, and a host stylesheet's `a:link` is
|
|
119
121
|
(0,1,1) — which outranks `.ui-btn--ghost` at (0,1,0) and paints the enabled
|
package/src/styles/table.css
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
border-collapse: collapse;
|
|
9
9
|
font-size: var(--text-base);
|
|
10
10
|
}
|
|
11
|
+
/* Fit cards keep short tables close to their labels. Other cards keep full-width tables. */
|
|
12
|
+
:where(.ui-card--fit) .ui-table {
|
|
13
|
+
width: auto;
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
}
|
|
11
16
|
.ui-table th {
|
|
12
17
|
text-align: left;
|
|
13
18
|
/* rank: label */
|
|
@@ -280,6 +285,10 @@
|
|
|
280
285
|
width: calc(100% + 2 * var(--space-3));
|
|
281
286
|
margin-inline: calc(-1 * var(--space-3));
|
|
282
287
|
}
|
|
288
|
+
.ui-card--fit > :where(.ui-table--dense, .ui-table--zebra) {
|
|
289
|
+
width: auto;
|
|
290
|
+
max-width: calc(100% + 2 * var(--space-3));
|
|
291
|
+
}
|
|
283
292
|
/* A scroll wrapper owns the bleed for a nested dense table: 4px of focus
|
|
284
293
|
clearance plus the 12px end-cell inset. */
|
|
285
294
|
.ui-card > .ui-table-scroll:has(> .ui-table--dense) {
|
|
@@ -391,6 +400,8 @@ a.ui-identity:hover { text-decoration: underline; }
|
|
|
391
400
|
under it, so whatever the cut lets out is painted over that column. A child's ring
|
|
392
401
|
needs 3px and the cell's padding is 12px, so the room comes from the padding.
|
|
393
402
|
why: docs/components.md#dense-financial-tables, #500, #513 */
|
|
403
|
+
/* A percentage can stretch an intrinsic table beyond its content. */
|
|
404
|
+
.ui-card--fit .ui-table--pinned .ui-table__identity { width: 1px; }
|
|
394
405
|
.ui-table--pinned .ui-table__identity {
|
|
395
406
|
width: 1%;
|
|
396
407
|
max-width: var(--ui-table-identity-max);
|