@columenlabs/lumen-ui 0.1.113 → 0.1.115
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 +1 -1
- package/dist/hooks/use-hover-open.d.ts +23 -0
- package/dist/hooks/use-hover-open.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lumen-view.js +6 -5
- package/dist/status-health.d.ts +1 -1
- package/dist/status-health.js +3 -19
- package/dist/status-messages.d.ts +25 -0
- package/dist/status-messages.js +72 -0
- package/dist/theme.css +23 -50
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,7 +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
|
-
-
|
|
81
|
+
- Rows never wrap. Every row (data, group, sub-group, Total) reads on one line at every width. On a phone each row is a one-line card: its name (ellipsis, full name on hover) and one value on the right, the sorted column, else the first number or date. The other values are in the row's drawer or panel. Pick which value a phone shows by sorting (Sort by) or by marking the others `hideOnPhone`.
|
|
82
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.
|
|
83
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.
|
|
84
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.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Focus stays on the trigger: moving it into the pop-up, or back on close, would dismiss a
|
|
2
|
+
* neighbour's pop-up the mouse just opened (Radix closes a pop-up on focus outside). */
|
|
3
|
+
declare const keepFocus: (e: Event) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Open state for a status bar pop-up that opens on hover with a mouse and on tap with a finger or
|
|
6
|
+
* keyboard (a `Popover`'s own trigger). Spread `hover` on the trigger and `content` on the
|
|
7
|
+
* `PopoverContent`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useHoverOpen(): {
|
|
10
|
+
open: boolean;
|
|
11
|
+
setOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
12
|
+
hover: {
|
|
13
|
+
onPointerEnter: (e: React.PointerEvent) => void;
|
|
14
|
+
onPointerLeave: (e: React.PointerEvent) => void;
|
|
15
|
+
};
|
|
16
|
+
content: {
|
|
17
|
+
onPointerEnter: (e: React.PointerEvent) => void;
|
|
18
|
+
onPointerLeave: (e: React.PointerEvent) => void;
|
|
19
|
+
onOpenAutoFocus: typeof keepFocus;
|
|
20
|
+
onCloseAutoFocus: typeof keepFocus;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
/** How long a pop-up stays after the mouse leaves, so it can travel from trigger to pop-up. */
|
|
3
|
+
const HOVER_CLOSE_MS = 150;
|
|
4
|
+
/** Focus stays on the trigger: moving it into the pop-up, or back on close, would dismiss a
|
|
5
|
+
* neighbour's pop-up the mouse just opened (Radix closes a pop-up on focus outside). */
|
|
6
|
+
const keepFocus = (e) => e.preventDefault();
|
|
7
|
+
/**
|
|
8
|
+
* Open state for a status bar pop-up that opens on hover with a mouse and on tap with a finger or
|
|
9
|
+
* keyboard (a `Popover`'s own trigger). Spread `hover` on the trigger and `content` on the
|
|
10
|
+
* `PopoverContent`.
|
|
11
|
+
*/
|
|
12
|
+
export function useHoverOpen() {
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
const timer = useRef(undefined);
|
|
15
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
16
|
+
const hover = (on) => (e) => {
|
|
17
|
+
if (e.pointerType !== 'mouse')
|
|
18
|
+
return;
|
|
19
|
+
clearTimeout(timer.current);
|
|
20
|
+
if (on)
|
|
21
|
+
setOpen(true);
|
|
22
|
+
else
|
|
23
|
+
timer.current = setTimeout(() => setOpen(false), HOVER_CLOSE_MS);
|
|
24
|
+
};
|
|
25
|
+
const handlers = { onPointerEnter: hover(true), onPointerLeave: hover(false) };
|
|
26
|
+
return {
|
|
27
|
+
open,
|
|
28
|
+
setOpen,
|
|
29
|
+
hover: handlers,
|
|
30
|
+
content: { ...handlers, onOpenAutoFocus: keepFocus, onCloseAutoFocus: keepFocus },
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ export { LumenShell, type LumenShellProps, type ShellUser, StatusItem, firstName
|
|
|
78
78
|
export { apiJson } from './api-fetch.js';
|
|
79
79
|
export { cn } from './lib/utils.js';
|
|
80
80
|
export { type HealthProbe, StatusHealth } from './status-health.js';
|
|
81
|
+
export { type StatusMessage, type StatusMessageTone, StatusMessages, groupStatusMessages, } from './status-messages.js';
|
|
81
82
|
export { TimeZoneSelect } from './status.js';
|
|
82
83
|
export { type AccountFacts, type JoinReaders, JoinsProvider, SourceStatusItems, YOURS_COLOR, editedCells, editedKey, frameColor, useFrameEdits, useJoins, usedJoinKeys, } from './joins.js';
|
|
83
84
|
export { OriginLegend, originsOf } from './origin-legend.js';
|
package/dist/index.js
CHANGED
|
@@ -85,6 +85,7 @@ export { LumenShell, StatusItem, firstName, initials, maskAmount, useAmountsHidd
|
|
|
85
85
|
export { apiJson } from './api-fetch.js';
|
|
86
86
|
export { cn } from './lib/utils.js';
|
|
87
87
|
export { StatusHealth } from './status-health.js';
|
|
88
|
+
export { StatusMessages, groupStatusMessages, } from './status-messages.js';
|
|
88
89
|
export { TimeZoneSelect } from './status.js';
|
|
89
90
|
export { JoinsProvider, SourceStatusItems, YOURS_COLOR, editedCells, editedKey, frameColor, useFrameEdits, useJoins, usedJoinKeys, } from './joins.js';
|
|
90
91
|
export { OriginLegend, originsOf } from './origin-legend.js';
|
package/dist/lumen-view.js
CHANGED
|
@@ -440,12 +440,13 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
440
440
|
// Inline chips sit on the first column, next to the row's name, not in their own.
|
|
441
441
|
const inline = visible.filter((f, i) => i > 0 && chips.some((c) => c.key === f.key && c.placement === 'inline'));
|
|
442
442
|
const shown = visible.filter((f) => !inline.includes(f));
|
|
443
|
-
// On a narrow phone each row reads as a card (theme.css, `data-card`): its name and
|
|
444
|
-
// (the sorted one, else the first number or date
|
|
445
|
-
//
|
|
443
|
+
// On a narrow phone each row reads as a card on ONE line (theme.css, `data-card`): its name and
|
|
444
|
+
// one value (the sorted one, else the first number or date, else the next column). The rest
|
|
445
|
+
// waits in the row's detail, never on a second line.
|
|
446
446
|
const short = (f) => f.format !== 'text' || (rows[0] !== undefined && isDateText(f.value(rows[0])));
|
|
447
|
-
const
|
|
448
|
-
|
|
447
|
+
const onCard = shown.filter((f, i) => i > 0 && !f.hideOnPhone);
|
|
448
|
+
const meta = (onCard.find((f) => f.key === sorting[0]?.id) ?? onCard.find(short) ?? onCard[0])
|
|
449
|
+
?.key;
|
|
449
450
|
const cardOf = (f) => f === shown[0] ? 'lead' : f.key === meta ? 'meta' : short(f) ? 'num' : 'text';
|
|
450
451
|
const col = (f, ...more) => ({
|
|
451
452
|
...colProps(f, ...more),
|
package/dist/status-health.d.ts
CHANGED
package/dist/status-health.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
// pop-up"): one chip with the worst state of every service the app checks, opening a list of each
|
|
4
|
-
// service and its latency, on hover with a mouse and on tap with a finger or keyboard.
|
|
5
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useHoverOpen } from './hooks/use-hover-open.js';
|
|
6
3
|
import { cn } from './lib/utils.js';
|
|
7
4
|
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover.js';
|
|
8
|
-
/** How long the list stays after the mouse leaves, so it can travel from chip to list. */
|
|
9
|
-
const HOVER_CLOSE_MS = 150;
|
|
10
5
|
/** Keeps the list off the window's edge on a narrow screen. */
|
|
11
6
|
const COLLISION_PAD = 8;
|
|
12
7
|
const dot = (ok) => cn('size-2 shrink-0 rounded-full', ok === null ? 'bg-muted-foreground/40' : ok ? 'bg-gain' : 'bg-destructive');
|
|
@@ -22,20 +17,9 @@ function summary(probes) {
|
|
|
22
17
|
return { ok: true, text: 'All up' };
|
|
23
18
|
}
|
|
24
19
|
export function StatusHealth({ probes, checked, error, }) {
|
|
25
|
-
const
|
|
26
|
-
const timer = useRef(undefined);
|
|
27
|
-
useEffect(() => () => clearTimeout(timer.current), []);
|
|
28
|
-
const hover = (on) => (e) => {
|
|
29
|
-
if (e.pointerType !== 'mouse')
|
|
30
|
-
return;
|
|
31
|
-
clearTimeout(timer.current);
|
|
32
|
-
if (on)
|
|
33
|
-
setOpen(true);
|
|
34
|
-
else
|
|
35
|
-
timer.current = setTimeout(() => setOpen(false), HOVER_CLOSE_MS);
|
|
36
|
-
};
|
|
20
|
+
const { open, setOpen, hover, content: pop } = useHoverOpen();
|
|
37
21
|
if (!probes.length && !error)
|
|
38
22
|
return null;
|
|
39
23
|
const s = error ? { ok: false, text: 'Check failed' } : summary(probes);
|
|
40
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { "aria-label": `Services: ${s.text}`, className: "flex h-6 shrink-0 items-center gap-1.5 rounded-md px-1.5 hover:bg-muted hover:text-foreground",
|
|
24
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { "aria-label": `Services: ${s.text}`, className: "flex h-6 shrink-0 items-center gap-1.5 rounded-md px-1.5 hover:bg-muted hover:text-foreground", ...hover, children: [_jsx("span", { "aria-hidden": true, className: dot(s.ok) }), "Services", _jsx("span", { className: cn('num', s.ok === false ? 'text-destructive' : 'text-foreground/80'), children: s.text })] }), _jsxs(PopoverContent, { side: "top", align: "end", collisionPadding: COLLISION_PAD, className: "w-auto min-w-56 gap-1.5 text-xs", ...pop, children: [_jsx("ul", { "aria-label": "Services", className: "flex flex-col gap-1", children: probes.map((p) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: dot(p.ok) }), _jsxs("span", { className: "flex-1", children: [p.name, p.note && _jsx("span", { className: "ml-1.5 text-muted-foreground", children: p.note })] }), _jsx("span", { className: cn('num', p.ok === false ? 'text-destructive' : 'text-foreground/80'), children: p.ok === false ? 'down' : p.ms != null ? `${p.ms} ms` : '—' })] }, p.name))) }), error && _jsx("p", { className: "text-destructive", children: error }), checked && (_jsxs("p", { className: "border-t pt-1.5 text-muted-foreground", children: ["Checked ", _jsx("span", { className: "num", children: checked })] }))] })] }));
|
|
41
25
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type StatusMessageTone = 'info' | 'success' | 'warning' | 'error';
|
|
2
|
+
export interface StatusMessage {
|
|
3
|
+
text: string;
|
|
4
|
+
/** Colours the text and its dot: error red, warning amber, success green; info has no dot. */
|
|
5
|
+
tone?: StatusMessageTone;
|
|
6
|
+
/** How many times it was posted, for an app that counts repeats itself; repeats in the list add up too. */
|
|
7
|
+
count?: number;
|
|
8
|
+
}
|
|
9
|
+
interface Group extends Required<StatusMessage> {
|
|
10
|
+
count: number;
|
|
11
|
+
}
|
|
12
|
+
/** One entry per distinct message (same tone, same words), counted, most severe first. */
|
|
13
|
+
export declare function groupStatusMessages(messages: readonly StatusMessage[]): Group[];
|
|
14
|
+
/**
|
|
15
|
+
* The app's messages in the status bar. Each message is one line that ends in an ellipsis past
|
|
16
|
+
* `--status-message-max`, with its full text on hover or tap; a repeated message shows once with
|
|
17
|
+
* `×N`; past `max` messages (one on a phone), a `+N` chip lists the rest.
|
|
18
|
+
*/
|
|
19
|
+
export declare function StatusMessages({ messages, max, className, }: {
|
|
20
|
+
messages: readonly StatusMessage[];
|
|
21
|
+
/** How many messages show at once on a wider screen; the rest go behind the `+N` chip. */
|
|
22
|
+
max?: number;
|
|
23
|
+
className?: string;
|
|
24
|
+
}): import("react").JSX.Element | null;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useHoverOpen } from './hooks/use-hover-open.js';
|
|
3
|
+
import { useIsMobile } from './hooks/use-mobile.js';
|
|
4
|
+
import { cn } from './lib/utils.js';
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover.js';
|
|
6
|
+
/** Errors first, then warnings, so the message that matters most is the one in view. */
|
|
7
|
+
const RANK = { error: 0, warning: 1, success: 2, info: 3 };
|
|
8
|
+
const TEXT = {
|
|
9
|
+
error: 'text-destructive',
|
|
10
|
+
warning: 'text-warning',
|
|
11
|
+
success: undefined,
|
|
12
|
+
info: undefined,
|
|
13
|
+
};
|
|
14
|
+
const DOT = {
|
|
15
|
+
error: 'bg-destructive',
|
|
16
|
+
warning: 'bg-warning',
|
|
17
|
+
success: 'bg-gain',
|
|
18
|
+
info: undefined,
|
|
19
|
+
};
|
|
20
|
+
/** One entry per distinct message (same tone, same words), counted, most severe first. */
|
|
21
|
+
export function groupStatusMessages(messages) {
|
|
22
|
+
const groups = new Map();
|
|
23
|
+
for (const m of messages) {
|
|
24
|
+
const text = m.text.replace(/\s+/g, ' ').trim();
|
|
25
|
+
if (!text)
|
|
26
|
+
continue;
|
|
27
|
+
const tone = m.tone ?? 'info';
|
|
28
|
+
const key = `${tone}\u0000${text}`;
|
|
29
|
+
const count = Math.max(1, m.count ?? 1);
|
|
30
|
+
const g = groups.get(key);
|
|
31
|
+
if (g)
|
|
32
|
+
g.count += count;
|
|
33
|
+
else
|
|
34
|
+
groups.set(key, { text, tone, count });
|
|
35
|
+
}
|
|
36
|
+
// Map keeps first-seen order; the sort is stable, so equal tones keep it.
|
|
37
|
+
return [...groups.values()].sort((a, b) => RANK[a.tone] - RANK[b.tone]);
|
|
38
|
+
}
|
|
39
|
+
const label = (g) => (g.count > 1 ? `${g.text} (×${g.count})` : g.text);
|
|
40
|
+
function Dot({ tone }) {
|
|
41
|
+
const color = DOT[tone];
|
|
42
|
+
return color ? _jsx("span", { "aria-hidden": true, className: cn('size-2 shrink-0 rounded-full', color) }) : null;
|
|
43
|
+
}
|
|
44
|
+
/** Keeps the pop-up off the window's edge on a narrow screen. */
|
|
45
|
+
const COLLISION_PAD = 8;
|
|
46
|
+
/**
|
|
47
|
+
* A status bar trigger whose pop-up opens on hover with a mouse and on tap with a finger or
|
|
48
|
+
* keyboard, like the services chip, so a phone can read the full text too (a tooltip never opens
|
|
49
|
+
* on touch). Styled on its own classes only: a trigger owns `data-state`.
|
|
50
|
+
*/
|
|
51
|
+
function Pop({ label: name, className, children, content, }) {
|
|
52
|
+
const { open, setOpen, hover, content: pop } = useHoverOpen();
|
|
53
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { "aria-label": name, className: className, ...hover, children: children }), _jsx(PopoverContent, { side: "top", align: "start", collisionPadding: COLLISION_PAD, className: "w-auto max-w-sm gap-1.5 text-xs break-words", ...pop, children: content })] }));
|
|
54
|
+
}
|
|
55
|
+
const TRIGGER = 'flex h-6 items-center gap-1.5 rounded-md px-1.5 hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none';
|
|
56
|
+
function Line({ group }) {
|
|
57
|
+
return (_jsxs(Pop, { className: cn(TRIGGER, 'min-w-0 max-w-(--status-message-max)'), content: _jsx("p", { className: TEXT[group.tone], children: label(group) }), children: [_jsx(Dot, { tone: group.tone }), _jsx("span", { className: cn('min-w-0 truncate', TEXT[group.tone]), children: group.text }), group.count > 1 && (_jsxs("span", { className: "num shrink-0 text-foreground/80", "aria-label": `${group.count} times`, children: ["\u00D7", group.count] }))] }));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The app's messages in the status bar. Each message is one line that ends in an ellipsis past
|
|
61
|
+
* `--status-message-max`, with its full text on hover or tap; a repeated message shows once with
|
|
62
|
+
* `×N`; past `max` messages (one on a phone), a `+N` chip lists the rest.
|
|
63
|
+
*/
|
|
64
|
+
export function StatusMessages({ messages, max = 2, className, }) {
|
|
65
|
+
const phone = useIsMobile();
|
|
66
|
+
const groups = groupStatusMessages(messages);
|
|
67
|
+
if (!groups.length)
|
|
68
|
+
return null;
|
|
69
|
+
const shown = groups.slice(0, phone ? 1 : Math.max(1, max));
|
|
70
|
+
const rest = groups.slice(shown.length);
|
|
71
|
+
return (_jsxs("span", { "aria-live": "polite", className: cn('flex min-w-0 items-center gap-2', className), children: [shown.map((g) => (_jsx(Line, { group: g }, `${g.tone}:${g.text}`))), rest.length > 0 && (_jsxs(Pop, { label: `${rest.length} more messages`, className: cn(TRIGGER, 'num shrink-0 text-foreground/80'), content: _jsx("ul", { className: "flex flex-col gap-1", children: rest.map((g) => (_jsxs("li", { className: "flex items-start gap-1.5", children: [_jsx("span", { className: "flex h-4 items-center", children: _jsx(Dot, { tone: g.tone }) }), _jsx("span", { className: TEXT[g.tone], children: label(g) })] }, `${g.tone}:${g.text}`))) }), children: ["+", rest.length] }))] }));
|
|
72
|
+
}
|
package/dist/theme.css
CHANGED
|
@@ -88,6 +88,9 @@
|
|
|
88
88
|
--shell-assistant-width: 26rem;
|
|
89
89
|
--shell-header-height: 48px;
|
|
90
90
|
--shell-status-height: 32px;
|
|
91
|
+
/* A status bar message ends in an ellipsis here (StatusMessages), so it fits beside the bar's
|
|
92
|
+
other items; the full text opens on hover or tap. A phone shows one message, a wider screen two. */
|
|
93
|
+
--status-message-max: 45vw;
|
|
91
94
|
--tile-width: 8.5rem;
|
|
92
95
|
--shell-gutter: 12px;
|
|
93
96
|
--shell-section-gap: 12px;
|
|
@@ -209,6 +212,7 @@
|
|
|
209
212
|
@media (min-width: 768px) {
|
|
210
213
|
:root {
|
|
211
214
|
--shell-gutter: 16px;
|
|
215
|
+
--status-message-max: min(28rem, 30vw);
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
|
|
@@ -439,9 +443,11 @@
|
|
|
439
443
|
}
|
|
440
444
|
|
|
441
445
|
/* A LumenView table on a narrow phone (`data-cards`): each row is a card, so nothing runs off
|
|
442
|
-
the right edge
|
|
443
|
-
|
|
444
|
-
|
|
446
|
+
the right edge, and every row is ONE line, whatever an app draws in it: the row's name (shortens
|
|
447
|
+
in an ellipsis), then one figure (`data-card="meta"`, the sorted one, else the first number or
|
|
448
|
+
date) on the right. The other values wait in the row's detail (its drawer or panel), never on a
|
|
449
|
+
second line. Group, fold and Total rows (`data-rollup`) read the same way. More and empty rows
|
|
450
|
+
are one cell across the card. */
|
|
445
451
|
@media (max-width: 639px) {
|
|
446
452
|
table[data-cards],
|
|
447
453
|
table[data-cards] > tbody,
|
|
@@ -458,55 +464,46 @@
|
|
|
458
464
|
}
|
|
459
465
|
table[data-cards] tr {
|
|
460
466
|
display: flex;
|
|
461
|
-
flex-wrap:
|
|
462
|
-
align-items:
|
|
467
|
+
flex-wrap: nowrap;
|
|
468
|
+
align-items: center;
|
|
463
469
|
column-gap: 0.75rem;
|
|
464
|
-
row-gap: 0.125rem;
|
|
465
470
|
padding: 0.5rem var(--cell-x);
|
|
471
|
+
white-space: nowrap;
|
|
466
472
|
}
|
|
467
473
|
table[data-cards] td {
|
|
468
474
|
padding: 0 !important;
|
|
469
475
|
width: auto !important;
|
|
470
476
|
min-width: 0;
|
|
477
|
+
white-space: nowrap;
|
|
471
478
|
}
|
|
472
479
|
table[data-cards] td[colspan] {
|
|
473
480
|
flex: 1 1 100%;
|
|
474
481
|
}
|
|
475
|
-
|
|
482
|
+
/* A row's panel (`row-panel`) is a box of its own under the row, not a row: it keeps its lines. */
|
|
483
|
+
table[data-cards] tr[data-slot="row-panel"],
|
|
484
|
+
table[data-cards] tr[data-slot="row-panel"] > td {
|
|
485
|
+
white-space: normal;
|
|
486
|
+
}
|
|
487
|
+
table[data-cards] td:not([data-card]):not([colspan]),
|
|
488
|
+
table[data-cards] td:is([data-card="num"], [data-card="text"]) {
|
|
476
489
|
display: none;
|
|
477
490
|
}
|
|
478
491
|
table[data-cards] td[data-card="select"] {
|
|
479
492
|
order: 0;
|
|
480
493
|
flex: none;
|
|
481
|
-
align-self: center;
|
|
482
494
|
}
|
|
483
495
|
table[data-cards] td[data-card="lead"] {
|
|
484
496
|
order: 1;
|
|
485
497
|
flex: 1 1 0;
|
|
486
498
|
overflow: hidden;
|
|
499
|
+
text-overflow: ellipsis;
|
|
487
500
|
font-weight: 500;
|
|
488
501
|
}
|
|
489
502
|
table[data-cards] td[data-card="meta"] {
|
|
490
503
|
order: 2;
|
|
491
504
|
flex: none;
|
|
492
|
-
|
|
493
|
-
table[data-cards] tr:has(> td[data-card="lead"])::after {
|
|
494
|
-
content: "";
|
|
495
|
-
order: 3;
|
|
496
|
-
flex-basis: 100%;
|
|
497
|
-
}
|
|
498
|
-
table[data-cards] td:is([data-card="num"], [data-card="text"]) {
|
|
499
|
-
order: 4;
|
|
500
|
-
font-size: 0.8125rem;
|
|
501
|
-
color: var(--muted-foreground);
|
|
502
|
-
}
|
|
503
|
-
table[data-cards] td[data-card="num"] {
|
|
504
|
-
flex: none;
|
|
505
|
-
}
|
|
506
|
-
table[data-cards] td[data-card="text"] {
|
|
507
|
-
flex: 0 1 auto;
|
|
505
|
+
max-width: 50%;
|
|
508
506
|
overflow: hidden;
|
|
509
|
-
text-overflow: ellipsis;
|
|
510
507
|
}
|
|
511
508
|
table[data-cards] [data-slot="unit-value-unit"] {
|
|
512
509
|
position: static;
|
|
@@ -515,31 +512,7 @@
|
|
|
515
512
|
margin-left: 0.25em;
|
|
516
513
|
font-size: inherit;
|
|
517
514
|
}
|
|
518
|
-
/*
|
|
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
|
-
}
|
|
515
|
+
/* The group band pins as a whole row here: a cell can't pin inside a one-line flex row. */
|
|
543
516
|
table[data-cards] tr[data-group] {
|
|
544
517
|
position: sticky;
|
|
545
518
|
top: var(--pin-top, 0px);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
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.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.115",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|