@columenlabs/lumen-ui 0.1.54 → 0.1.55
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/README.md +2 -0
- package/dist/lumen-view.js +65 -38
- package/dist/theme.css +18 -0
- package/dist/unit-value.d.ts +3 -1
- package/dist/unit-value.js +12 -1
- package/dist/view-model.d.ts +6 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -75,6 +75,8 @@ Every export, its props and an example are on the [docs](https://lumen.columenla
|
|
|
75
75
|
- Every card body sits in `LumenBoundary`, so one crash never blanks a page.
|
|
76
76
|
- Numbers use tabular figures and a true minus `−`; a gain or loss always shows a sign or arrow.
|
|
77
77
|
- A page's header numbers are metric cards: `TileRow` of `Tile` (or `TileToggleGroup` when they pick what the page shows), with `fill` so they share the row, each with `spark` set to its daily history where it has one. Don't build a card of your own for this.
|
|
78
|
+
- Column widths: the first column (a row's name) grows only to `--lead-col-max` (18rem, theme.css); past it the cell truncates on one line and a plain text value shows in full on hover (a rendered cell brings its own hover card). Cap any other column with the field's `maxWidth` (`'12rem'`), or lift the cap with `maxWidth: false`. In a rendered name cell, put the name in a `truncate` span so the ellipsis lands on it, not on a chip after it.
|
|
79
|
+
- Column alignment is the table's, not the cell's. Text left, numbers right, and a number column has one right edge for its header, values, dashes and Total. `UnitValue` puts the unit in a fixed slot after the number (`--unit-slot`, 2rem) and its status dot just left of the number. In a LumenView table the first `UnitValue` drawn in a column marks it (`data-units` on every cell of that column, through the `UnitColumns` context and the cell's `data-col`); theme.css then widens that column's right gutter by the slot and hangs each unit in it. So a plain `—`, `Free` or `$0.21` an app draws in the same column sits on the digits' edge, and the header sits over the digits. Don't hand-place units, dots or padding to line a column up; if a column is off, draw its numbers through `UnitValue`. Outside a LumenView table (a hover card, a tile) `UnitValue` keeps its slot inline.
|
|
78
80
|
- A metered column (use against an allowance) shows one number per cell with `UnitValue` (right-aligned number, fixed muted unit slot, status dot), never `value [pct]`. Offer both readings through the field's `showAs` (Amount / % of allowance, picked per column in Edit view, saved as `shows`); default amounts for sizes (storage) and shares for capped counts. Put the other reading, the allowance and the cost formula in a hover card. Totals use `rollRender`: sum amounts with their unit; a share is the rows' total over their allowance, never a sum of percents.
|
|
79
81
|
- A floating bulk-action bar over a table's selection: Apply clears the selection and the bar goes away; the toast's Undo restores the change, not the selection. Cancel keeps the selection.
|
|
80
82
|
- Bars in a column line up: use `ShareMeter` (fixed track, fixed right-aligned number), never a bar sized by its label.
|
package/dist/lumen-view.js
CHANGED
|
@@ -42,6 +42,7 @@ import { Skeleton } from './ui/skeleton.js';
|
|
|
42
42
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table.js';
|
|
43
43
|
import { ToggleGroup, ToggleGroupItem } from './ui/toggle-group.js';
|
|
44
44
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
45
|
+
import { UnitColumns } from './unit-value.js';
|
|
45
46
|
import { EditCell, EditInputCell, EditManyCell } from './view-edit-cell.js';
|
|
46
47
|
import { ViewEditDialog } from './view-editor.js';
|
|
47
48
|
import { fieldOptions } from './view-formulas.js';
|
|
@@ -343,6 +344,27 @@ const alignOf = (format) => (format === 'text' ? 'text-left' : 'text-right');
|
|
|
343
344
|
/** An ISO date, alone or with a time after it: `2026-10-14`, `2026-10-14 16:17`. */
|
|
344
345
|
const isDateText = (v) => typeof v === 'string' && v.length <= 25 && /^\d{4}-\d{2}-\d{2}(?:$|[ T])/.test(v);
|
|
345
346
|
const colOf = (f) => cn(alignOf(f?.format ?? 'text'), f?.hideOnPhone && 'max-md:hidden');
|
|
347
|
+
/**
|
|
348
|
+
* A column's cell props: its classes, its key (`data-col`), and `data-units` once a `UnitValue`
|
|
349
|
+
* has drawn in it. Every cell of such a column (header, dash, Total) then right-aligns on the
|
|
350
|
+
* numbers' edge while the units hang in the gutter (theme.css), so one column has one edge.
|
|
351
|
+
*/
|
|
352
|
+
const colPropsOf = (units) => (f, ...more) => ({
|
|
353
|
+
className: cn(colOf(f), ...more),
|
|
354
|
+
'data-col': f?.key,
|
|
355
|
+
'data-units': f && units.has(f.key) ? '' : undefined,
|
|
356
|
+
});
|
|
357
|
+
/**
|
|
358
|
+
* A column's widest cell (`maxWidth`): the first column defaults to `--lead-col-max` (18rem in
|
|
359
|
+
* theme.css), so one long name never pushes the other columns off screen; `false` lifts it.
|
|
360
|
+
*/
|
|
361
|
+
const capOf = (f, i) => f.maxWidth === false ? undefined : (f.maxWidth ?? (i === 0 ? 'var(--lead-col-max)' : undefined));
|
|
362
|
+
/** Holds a cell to its column's cap on one line: text past it ends in an ellipsis. */
|
|
363
|
+
function CappedCell({ max, title, children, }) {
|
|
364
|
+
if (!max)
|
|
365
|
+
return children;
|
|
366
|
+
return (_jsx("div", { className: "flex min-w-0 *:min-w-0 *:max-w-full *:truncate", style: { maxWidth: max }, title: title, children: children }));
|
|
367
|
+
}
|
|
346
368
|
/** The flat or grouped table: sortable headers, a Total row, group rows that collapse. */
|
|
347
369
|
// Sorting, and paging of flat tables (the page is LumenView's, set right of the row count).
|
|
348
370
|
const SORTING = tableFeatures({
|
|
@@ -384,6 +406,10 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
384
406
|
pageSize: paging ? page.size : Math.max(rows.length, 1),
|
|
385
407
|
};
|
|
386
408
|
// Groups whose state differs from where they start: open, or past OPEN_ALL rows only the first.
|
|
409
|
+
// Columns a UnitValue has drawn in (`UnitColumns`): their cells share the numbers' edge.
|
|
410
|
+
const [units, setUnits] = useState(() => new Set());
|
|
411
|
+
const markUnits = useCallback((key) => setUnits((s) => (s.has(key) ? s : new Set(s).add(key))), []);
|
|
412
|
+
const col = colPropsOf(units);
|
|
387
413
|
const [flipped, setFlipped] = useState(new Set());
|
|
388
414
|
const [more, setMore] = useState(new Map());
|
|
389
415
|
// The grouped field is the group row's label, so its column hides.
|
|
@@ -475,12 +501,13 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
475
501
|
const { shown: now, cellOf, inline, chips, computed } = latest.current;
|
|
476
502
|
const field = now[i] ?? f;
|
|
477
503
|
const chip = chips.some((c) => c.key === field.key);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
504
|
+
const v = computed.value(field, row.original);
|
|
505
|
+
return (_jsx(CappedCell, { max: capOf(field, i), title: !field.render && typeof v === 'string' ? v : undefined, children: i === 0 && inline.length ? (_jsx(FirstCell, { value: cellOf(field, row.original, chip), chips: inline.flatMap((x) => {
|
|
506
|
+
const v = computed.value(x, row.original);
|
|
507
|
+
if (!x.edit && (v === null || v === ''))
|
|
508
|
+
return [];
|
|
509
|
+
return [{ key: x.key, node: cellOf(x, row.original, true) }];
|
|
510
|
+
}) })) : (cellOf(field, row.original, chip)) }));
|
|
484
511
|
},
|
|
485
512
|
})), [shownKeys, inlineOn, serverSorts?.join(',')]);
|
|
486
513
|
const table = useTable({
|
|
@@ -560,7 +587,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
560
587
|
// drawn; spanning the hidden ones too pushed the group's totals right of their headers.
|
|
561
588
|
const leadSpan = Math.max(1, lead - (isMobile ? shown.slice(0, lead).filter((f) => f.hideOnPhone).length : 0));
|
|
562
589
|
const rollCell = (f, members) => rollsUp(f) && f.rollRender ? (f.rollRender(members)) : rollsUp(f) ? (_jsx(ViewValueCell, { value: computed.roll(f, members), format: f.aggregate && isCountAgg(f.aggregate) ? 'rounded' : f.format, amount: f.aggregate !== 'count' && f.amount, compact: f.compact })) : null;
|
|
563
|
-
const rollCells = (members) => shown.map((f) => (_jsx(TableCell, {
|
|
590
|
+
const rollCells = (members) => shown.map((f) => (_jsx(TableCell, { ...col(f), children: rollCell(f, members) }, f.key)));
|
|
564
591
|
// A row's chevron opens one level under it, closed by default: its panel, else the view's
|
|
565
592
|
// sub-group ("Each row opens"; None opens nothing).
|
|
566
593
|
const partsOf = (row) => (domain.panel ? [] : (subgroup?.rows?.(row) ?? []));
|
|
@@ -577,9 +604,9 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
577
604
|
return n;
|
|
578
605
|
});
|
|
579
606
|
}, children: _jsx(ChevronRightIcon, { className: cn('transition-transform', openParts.has(k) && 'rotate-90') }) }));
|
|
580
|
-
const partRow = (p, name) => (_jsxs(TableRow, { className: cn('bg-muted/30 text-muted-foreground hover:bg-muted/40', onDetails && 'cursor-pointer'), onClick: onDetails ? () => onDetails(p) : undefined, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, {
|
|
581
|
-
|
|
582
|
-
|
|
607
|
+
const partRow = (p, name) => (_jsxs(TableRow, { className: cn('bg-muted/30 text-muted-foreground hover:bg-muted/40', onDetails && 'cursor-pointer'), onClick: onDetails ? () => onDetails(p) : undefined, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, i === 0 && 'pl-12'), children: i === 0 ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CornerDownRightIcon, { className: "size-3.5 shrink-0" }), _jsx(CappedCell, { max: capOf(f, 0), children: name
|
|
608
|
+
? name(p)
|
|
609
|
+
: cellOf(f, p, chips.some((c) => c.key === f.key)) })] })) : (cellOf(f, p, chips.some((c) => c.key === f.key))) }, f.key)))] }, `part:${keyOf(p)}`));
|
|
583
610
|
// The whole row opens the details drawer.
|
|
584
611
|
const dataRow = (r) => (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": domain.rowKey(r.original), "data-state": tapped?.(r.original) ? 'selected' : undefined, className: cn('group/row', onDetails && 'cursor-pointer'), tabIndex: onDetails ? 0 : undefined, onClick: onDetails ? () => onDetails(r.original) : undefined, onKeyDown: onDetails
|
|
585
612
|
? (e) => {
|
|
@@ -590,14 +617,14 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
590
617
|
onDetails(r.original);
|
|
591
618
|
}
|
|
592
619
|
}
|
|
593
|
-
: undefined, children: [selectCell(keyOf(r.original), keyOf(r.original)), r.getAllCells().map((c, i) => (_jsx(TableCell, {
|
|
620
|
+
: undefined, children: [selectCell(keyOf(r.original), keyOf(r.original)), r.getAllCells().map((c, i) => (_jsx(TableCell, { ...col(shown[i], groupBy && i === 0 && 'pl-8'), style: { background: shown[i]?.tint?.(r.original) }, children: i === 0 && nests ? (_jsxs("span", { className: "flex items-center gap-1", children: [opens(r.original) ? (chevron(keyOf(r.original), openLabel())) : (_jsx("span", { className: "-ml-1 size-6 shrink-0" })), flexRender(c.column.columnDef.cell, c.getContext())] })) : (flexRender(c.column.columnDef.cell, c.getContext())) }, c.id)))] }, domain.rowKey(r.original)), openParts.has(keyOf(r.original)) &&
|
|
594
621
|
partsOf(r.original).map((p) => partRow(p, subgroup?.name)), openParts.has(keyOf(r.original)) && domain.panel ? (_jsx(TableRow, { "data-slot": "row-panel", className: "bg-muted/20 hover:bg-muted/20", children: _jsx(TableCell, { colSpan: shown.length + (selection ? 1 : 0), className: "p-0 whitespace-normal", children: _jsx("div", { className: "sticky left-0 w-[min(56rem,calc(100vw-2rem))] max-w-full px-4 py-3 md:pl-10", children: panelOf(r.original) }) }) }, `panel:${keyOf(r.original)}`)) : null] }, domain.rowKey(r.original)));
|
|
595
622
|
// Rows sharing the sub-group's `under` key fold into one rolled-up line that opens them; a key
|
|
596
623
|
// with one row keeps that row (NVDA opens Shares, then each option).
|
|
597
624
|
const foldRow = (key, members, scope) => {
|
|
598
625
|
const k = `under:${scope}\u0000${key}`;
|
|
599
626
|
const ms = members.map((m) => m.original);
|
|
600
|
-
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, {
|
|
627
|
+
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, groupBy && i === 0 && 'pl-8'), children: i === 0 ? (_jsxs("span", { className: "flex items-center gap-1", children: [chevron(k, `${key} ${openLabel()}`), _jsx("span", { className: "font-medium", children: key }), _jsx("span", { className: "num text-xs text-muted-foreground", children: ms.length })] })) : (rollCell(f, ms)) }, f.key)))] }), openParts.has(k) && ms.map((p) => partRow(p, subgroup?.name))] }, k));
|
|
601
628
|
};
|
|
602
629
|
const rowsOf = (list, scope) => {
|
|
603
630
|
const under = folds ? subgroup?.under : undefined;
|
|
@@ -644,32 +671,32 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
644
671
|
};
|
|
645
672
|
const box = useRef(null);
|
|
646
673
|
useFloatingHead(box, !inView);
|
|
647
|
-
return (_jsx("div", { ref: box, className: cn(inView ? IN_VIEW : 'overflow-x-auto overflow-y-hidden [--pin-top:0px]', fit && FIT), children: _jsxs(Table, { children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((hg) => (_jsxs(TableRow, { children: [selection && (_jsx(TableHead, { className: cn('w-8 pr-0', PINNED_HEAD), children: _jsx(Checkbox, { checked: allOn ? true : someOn ? 'indeterminate' : false, onCheckedChange: (on) => toggle(allKeys, on === true), "aria-label": "Select every row" }) })), hg.headers.map((h, i) => (_jsxs(TableHead, {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
674
|
+
return (_jsx("div", { ref: box, className: cn(inView ? IN_VIEW : 'overflow-x-auto overflow-y-hidden [--pin-top:0px]', fit && FIT), children: _jsx(UnitColumns.Provider, { value: markUnits, children: _jsxs(Table, { children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((hg) => (_jsxs(TableRow, { children: [selection && (_jsx(TableHead, { className: cn('w-8 pr-0', PINNED_HEAD), children: _jsx(Checkbox, { checked: allOn ? true : someOn ? 'indeterminate' : false, onCheckedChange: (on) => toggle(allKeys, on === true), "aria-label": "Select every row" }) })), hg.headers.map((h, i) => (_jsxs(TableHead, { ...col(shown[i], PINNED_HEAD), style: ruleOf(colorOf(domain, shown[i])), onPointerDown: pressStart, onPointerUp: pressEnd, onPointerLeave: pressEnd, onPointerCancel: pressEnd, onContextMenu: (e) => {
|
|
675
|
+
if (press.current)
|
|
676
|
+
e.preventDefault();
|
|
677
|
+
}, "aria-sort": h.column.getIsSorted() === 'asc'
|
|
678
|
+
? 'ascending'
|
|
679
|
+
: h.column.getIsSorted() === 'desc'
|
|
680
|
+
? 'descending'
|
|
681
|
+
: undefined, children: [i === 0 && groups && (!serverGroups || serverGroups.onToggleAll) && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getCanSort() ? h.column.getToggleSortingHandler() : undefined, children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] })] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && !serverGroups?.list?.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
|
|
682
|
+
? groups.map(([key, members], gi) => {
|
|
683
|
+
const open = isOpen(key, gi);
|
|
684
|
+
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { className: ROLLUP, "data-group": key, children: [selection && (_jsx(TableCell, { className: cn('w-8 pr-0', PINNED_GROUP), children: _jsx(Checkbox, { checked: members.every((m) => selection.keys.has(keyOf(m.original)))
|
|
685
|
+
? true
|
|
686
|
+
: members.some((m) => selection.keys.has(keyOf(m.original)))
|
|
687
|
+
? 'indeterminate'
|
|
688
|
+
: false, onCheckedChange: (on) => toggle(members.map((m) => keyOf(m.original)), on === true), "aria-label": `Select every row of ${key}` }) })), shown.slice(lead - 1).map((f, i) => i === 0 ? (_jsxs(TableCell, { colSpan: leadSpan, className: PINNED_GROUP, children: [_jsxs(Button, { variant: "ghost", size: "xs", className: "-mx-2 hover:bg-transparent aria-expanded:bg-transparent", "aria-expanded": open, "aria-label": `${open ? 'Collapse' : 'Expand'} ${key}`, onClick: () => serverGroups
|
|
689
|
+
? serverGroups.onToggle(key)
|
|
690
|
+
: setFlipped((c) => {
|
|
691
|
+
const n = new Set(c);
|
|
692
|
+
n.has(key) ? n.delete(key) : n.add(key);
|
|
693
|
+
return n;
|
|
694
|
+
}), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(groupBy.icon, {}), key, _jsx("span", { className: "num font-normal text-muted-foreground", children: serverGroups
|
|
695
|
+
? (served.get(key)?.n?.toLocaleString() ?? '')
|
|
696
|
+
: members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
|
|
697
|
+
rollCell(f, members.map((m) => m.original)) }, f.key)))] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
|
|
698
|
+
})
|
|
699
|
+
: drawn(sorted, ''), serverGroups?.more && !serverGroups.pages && (_jsx(TableRow, { "data-more": true, children: _jsx(TableCell, { colSpan: span, className: "py-1", children: _jsxs(Button, { variant: "ghost", size: "xs", onClick: serverGroups.more, children: [_jsx(ChevronsDownIcon, {}), "More groups"] }) }) })), hasTotal && rows.length > 0 && (_jsxs(TableRow, { className: cn(ROLLUP, 'border-t-2'), "data-total": true, children: [selection && _jsx(TableCell, {}), rollCells(rows).map((cell, i) => i === 0 && !(shown[0] && rollsUp(shown[0])) ? (_jsx(TableCell, { children: "Total" }, "total")) : (cell))] }))] })] }) }) }));
|
|
673
700
|
}
|
|
674
701
|
/** One value rolled up into a rows × columns grid, with totals where the roll-up adds. */
|
|
675
702
|
function PivotTable({ domain, rows, pivot, }) {
|
package/dist/theme.css
CHANGED
|
@@ -95,6 +95,10 @@
|
|
|
95
95
|
--row-h: 28px;
|
|
96
96
|
--cell-x: 8px;
|
|
97
97
|
--cell-y: 4px;
|
|
98
|
+
/* A UnitValue's unit slot (`GB`, `%`, `/day`), hung in the gutter of a units column. */
|
|
99
|
+
--unit-slot: 2rem;
|
|
100
|
+
/* A table's first (name) column stops growing here; longer names end in an ellipsis. */
|
|
101
|
+
--lead-col-max: 18rem;
|
|
98
102
|
|
|
99
103
|
--background: #f4f6fa;
|
|
100
104
|
--foreground: #121826;
|
|
@@ -388,6 +392,20 @@
|
|
|
388
392
|
}
|
|
389
393
|
}
|
|
390
394
|
|
|
395
|
+
/* A table column a UnitValue drew in (`data-units`, set by LumenView): every cell, header and
|
|
396
|
+
Total included, right-aligns on the numbers' edge; each UnitValue's unit slot hangs in the
|
|
397
|
+
widened gutter (out of flow, so no wrapper an app puts round it can shift it). One column, one edge, whatever an app's render draws beside it. */
|
|
398
|
+
[data-units] {
|
|
399
|
+
padding-right: calc(var(--cell-x) + var(--unit-slot));
|
|
400
|
+
}
|
|
401
|
+
[data-units] [data-slot="unit-value"] {
|
|
402
|
+
position: relative;
|
|
403
|
+
}
|
|
404
|
+
[data-units] [data-slot="unit-value-unit"] {
|
|
405
|
+
position: absolute;
|
|
406
|
+
left: 100%;
|
|
407
|
+
}
|
|
408
|
+
|
|
391
409
|
@layer utilities {
|
|
392
410
|
.bg-gap-hatch {
|
|
393
411
|
background: repeating-linear-gradient(135deg, var(--gap) 0 3px, var(--gap-soft) 3px 6px);
|
package/dist/unit-value.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { MeterLevels } from '@columenlabs/lumen-ui-types/views';
|
|
2
|
-
import type
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
/** Set by a table: marks the column (its cell's `data-col`) a UnitValue draws in. */
|
|
4
|
+
export declare const UnitColumns: import("react").Context<((col: string) => void) | null>;
|
|
3
5
|
export declare function UnitValue({ value, unit, tone: toneProp, share, levels, title, className, }: {
|
|
4
6
|
/** The number, already formatted (`231.4`). */
|
|
5
7
|
value: ReactNode;
|
package/dist/unit-value.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useLayoutEffect, useRef } from 'react';
|
|
2
3
|
import { cn } from './lib/utils.js';
|
|
3
4
|
import { meterValueTone } from './meter-level.js';
|
|
5
|
+
/** Set by a table: marks the column (its cell's `data-col`) a UnitValue draws in. */
|
|
6
|
+
export const UnitColumns = createContext(null);
|
|
4
7
|
export function UnitValue({ value, unit, tone: toneProp, share, levels, title, className, }) {
|
|
5
8
|
const tone = toneProp ?? (share === undefined ? undefined : meterValueTone(share, levels));
|
|
6
|
-
|
|
9
|
+
const mark = useContext(UnitColumns);
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
// Before paint, so the column never draws on two edges, even for a frame.
|
|
12
|
+
useLayoutEffect(() => {
|
|
13
|
+
const col = ref.current?.closest('[data-col]')?.getAttribute('data-col');
|
|
14
|
+
if (mark && col)
|
|
15
|
+
mark(col);
|
|
16
|
+
}, [mark]);
|
|
17
|
+
return (_jsxs("span", { ref: ref, "data-slot": "unit-value", title: title, className: cn('inline-flex w-full items-center justify-end whitespace-nowrap', className), children: [_jsxs("span", { className: cn('num relative text-right', tone === 'warning' && 'text-warning', tone === 'loss' && 'text-loss'), children: [tone && (_jsx("span", { "aria-hidden": "true", className: cn('-translate-y-1/2 absolute top-1/2 right-full mr-1 size-1.5 rounded-full', tone === 'warning' ? 'bg-warning' : 'bg-loss') })), value] }), _jsx("span", { "data-slot": "unit-value-unit", className: "w-(--unit-slot) shrink-0 pl-1 text-left text-muted-foreground text-xs", children: unit })] }));
|
|
7
18
|
}
|
package/dist/view-model.d.ts
CHANGED
|
@@ -129,6 +129,12 @@ export interface ViewField<T> {
|
|
|
129
129
|
}> | undefined;
|
|
130
130
|
/** The cell's background, a theme colour mix (a P/L heat grid). */
|
|
131
131
|
tint?: (row: T) => string | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* The column's widest cell, a CSS length (`14rem`): past it the cell ends in an ellipsis on one
|
|
134
|
+
* line, the full text in its hover. The first column defaults to `--lead-col-max` (18rem);
|
|
135
|
+
* `false` lets it grow.
|
|
136
|
+
*/
|
|
137
|
+
maxWidth?: string | false;
|
|
132
138
|
/** Left out on phones, where the table has no room for it (a returns table's longer windows). */
|
|
133
139
|
hideOnPhone?: boolean;
|
|
134
140
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@codemirror/lang-javascript": "6.2.5",
|
|
42
42
|
"@codemirror/lang-json": "6.0.2",
|
|
43
43
|
"@codemirror/lang-sql": "6.10.0",
|
|
44
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
44
|
+
"@columenlabs/lumen-ui-types": "0.1.55",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|