@columenlabs/lumen-ui 0.1.109 → 0.1.111

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 CHANGED
@@ -78,6 +78,7 @@ Every export, its props and an example are on the [docs](https://lumen.columenla
78
78
  - Numbers use tabular figures and a true minus `−`; a gain or loss always shows a sign or arrow.
79
79
  - 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.
80
80
  - 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.
81
+ - Group, sub-group and Total rows read on one line at every width, a phone's cards included: the group's name shortens in an ellipsis (full name on hover) and its roll-up figure stays on the right. The view marks them `data-rollup` and theme.css keeps them to one line, so a `mark`, `rollRender` or `groupActions` an app passes never wraps them.
81
82
  - 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.
82
83
  - 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.
83
84
  - 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.
@@ -680,11 +680,11 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
680
680
  const foldRow = (key, members, scope) => {
681
681
  const k = `under:${scope}\u0000${key}`;
682
682
  const ms = members.map((m) => m.original);
683
- return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k, className: "cursor-pointer hover:bg-muted has-aria-expanded:hover:bg-muted", onClick: () => setOpenParts((o) => {
683
+ return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k, "data-rollup": "", className: "cursor-pointer hover:bg-muted has-aria-expanded:hover:bg-muted", onClick: () => setOpenParts((o) => {
684
684
  const n = new Set(o);
685
685
  n.has(k) ? n.delete(k) : n.add(k);
686
686
  return n;
687
- }), 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()}`), subgroup?.mark?.(key, ms), _jsx("span", { className: "font-medium", title: titleOf(key, ms) === key ? undefined : key, children: titleOf(key, ms) }), _jsx("span", { className: "num text-xs text-muted-foreground", children: ms.length })] })) : (rollCell(f, ms, 'fold')) }, f.key))), add && _jsx(TableCell, {})] }), openParts.has(k) && ms.map((p) => partRow(p, subgroup?.name))] }, k));
687
+ }), children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, groupBy && i === 0 && 'pl-8'), children: i === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1 whitespace-nowrap", children: [chevron(k, `${key} ${openLabel()}`), subgroup?.mark?.(key, ms), _jsx("span", { className: "truncate font-medium", title: key, children: titleOf(key, ms) }), _jsx("span", { className: "num shrink-0 text-xs text-muted-foreground", children: ms.length })] })) : (rollCell(f, ms, 'fold')) }, f.key))), add && _jsx(TableCell, {})] }), openParts.has(k) && ms.map((p) => partRow(p, subgroup?.name))] }, k));
688
688
  };
689
689
  const rowsOf = (list, scope) => {
690
690
  const under = folds ? subgroup?.under : undefined;
@@ -750,22 +750,22 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
750
750
  (!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
751
751
  ? groups.map(([key, members], gi) => {
752
752
  const open = isOpen(key, gi);
753
- 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)))
753
+ return (_jsxs(Fragment, { children: [_jsxs(TableRow, { className: ROLLUP, "data-group": key, "data-rollup": "", children: [selection && (_jsx(TableCell, { "data-card": "select", className: cn('w-8 pr-0', PINNED_GROUP), children: _jsx(Checkbox, { checked: members.every((m) => selection.keys.has(keyOf(m.original)))
754
754
  ? true
755
755
  : members.some((m) => selection.keys.has(keyOf(m.original)))
756
756
  ? 'indeterminate'
757
- : 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
758
- ? serverGroups.onToggle(key)
759
- : setFlipped((c) => {
760
- const n = new Set(c);
761
- n.has(key) ? n.delete(key) : n.add(key);
762
- return n;
763
- }), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(GroupMark, { field: groupBy, value: key }), key, _jsx("span", { className: "num font-normal text-muted-foreground", children: serverGroups
764
- ? (served.get(key)?.n?.toLocaleString() ?? '')
765
- : members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
757
+ : 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 ? (_jsx(TableCell, { colSpan: leadSpan, "data-card": "lead", className: PINNED_GROUP, children: _jsxs("span", { className: "flex max-w-(--lead-col-max) min-w-0 items-center whitespace-nowrap", children: [_jsxs(Button, { variant: "ghost", size: "xs", title: key, className: "-mx-2 min-w-0 hover:bg-transparent aria-expanded:bg-transparent", "aria-expanded": open, "aria-label": `${open ? 'Collapse' : 'Expand'} ${key}`, onClick: () => serverGroups
758
+ ? serverGroups.onToggle(key)
759
+ : setFlipped((c) => {
760
+ const n = new Set(c);
761
+ n.has(key) ? n.delete(key) : n.add(key);
762
+ return n;
763
+ }), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(GroupMark, { field: groupBy, value: key }), _jsx("span", { className: "truncate", children: key }), _jsx("span", { className: "num shrink-0 font-normal text-muted-foreground", children: serverGroups
764
+ ? (served.get(key)?.n?.toLocaleString() ?? '')
765
+ : members.length })] }), groupActions?.(key, members.map((m) => m.original))] }) }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
766
766
  rollCell(f, members.map((m) => m.original), 'group') }, f.key))), add && _jsx(TableCell, { className: PINNED_GROUP })] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
767
767
  })
768
- : 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)), add && _jsx(TableCell, {})] }))] })] }) }) }));
768
+ : 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, "data-rollup": "", children: [selection && _jsx(TableCell, {}), rollCells(rows).map((cell, i) => i === 0 && !(shown[0] && rollsUp(shown[0])) ? (_jsx(TableCell, { "data-card": "lead", children: "Total" }, "total")) : (cell)), add && _jsx(TableCell, {})] }))] })] }) }) }));
769
769
  }
770
770
  /** One value rolled up into a rows × columns grid, with totals where the roll-up adds. */
771
771
  function PivotTable({ domain, rows, pivot, }) {
package/dist/theme.css CHANGED
@@ -515,6 +515,43 @@
515
515
  margin-left: 0.25em;
516
516
  font-size: inherit;
517
517
  }
518
+ /* Group, fold and Total rows (`data-rollup`) are one line at every width: the name shortens
519
+ in an ellipsis, the row's one figure keeps its width on the right, and the rest waits in the
520
+ open rows. Whatever an app draws in them (a mark, a roll-up, group actions) never wraps.
521
+ The group band pins as a whole row here: a cell can't pin inside a one-line flex row. */
522
+ table[data-cards] tr[data-rollup] {
523
+ flex-wrap: nowrap;
524
+ align-items: center;
525
+ white-space: nowrap;
526
+ }
527
+ table[data-cards] tr[data-rollup]::after {
528
+ display: none;
529
+ }
530
+ table[data-cards] tr[data-rollup] > td {
531
+ white-space: nowrap;
532
+ }
533
+ table[data-cards] tr[data-rollup] > td[data-card="lead"] {
534
+ flex: 1 1 0;
535
+ min-width: 0;
536
+ overflow: hidden;
537
+ }
538
+ table[data-cards]
539
+ tr[data-rollup]
540
+ > td:is([data-card="num"], [data-card="text"], :not([data-card])) {
541
+ display: none;
542
+ }
543
+ table[data-cards] tr[data-group] {
544
+ position: sticky;
545
+ top: var(--pin-top, 0px);
546
+ z-index: 10;
547
+ background: color-mix(in oklab, var(--color-muted) 50%, var(--color-card));
548
+ box-shadow: inset 0 1px 0 var(--color-border), inset 0 -1px 0 var(--color-border);
549
+ }
550
+ table[data-cards] tr[data-group] > td {
551
+ position: static;
552
+ background: transparent;
553
+ box-shadow: none;
554
+ }
518
555
  /* A bare count names what it counts (`cardUnit`): no header says it on a card. */
519
556
  table[data-cards] td[data-card-unit]::after {
520
557
  content: attr(data-card-unit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  "@codemirror/lang-javascript": "6.2.5",
50
50
  "@codemirror/lang-json": "6.0.2",
51
51
  "@codemirror/lang-sql": "6.10.0",
52
- "@columenlabs/lumen-ui-types": "0.1.109",
52
+ "@columenlabs/lumen-ui-types": "0.1.111",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",