@columenlabs/lumen-ui 0.1.130 → 0.1.132
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/dist/lumen-widget-charts.js +8 -5
- package/dist/page.d.ts +4 -1
- package/dist/page.js +5 -2
- package/dist/status-messages.d.ts +7 -7
- package/dist/status-messages.js +11 -16
- package/dist/theme.css +3 -3
- package/package.json +2 -2
|
@@ -42,7 +42,7 @@ export function StackedColumns({ rows, x, parts, label, height = 220, tick = Str
|
|
|
42
42
|
const total = (r) => parts.reduce((n, p) => n + (Number(r[p.key]) || 0), 0);
|
|
43
43
|
// The highest part with a value carries the column's rounded cap.
|
|
44
44
|
const top = (r) => parts.findLast((p) => Number(r[p.key]) > 0)?.key;
|
|
45
|
-
const ticks = niceTicks(Math.max(0, ...rows.map(total)));
|
|
45
|
+
const ticks = niceTicks(Math.max(0, ...rows.map(total)), 4, rows.every((r) => Number.isInteger(total(r))));
|
|
46
46
|
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ChartContainer, { config: config, role: "img", "aria-label": label, className: "aspect-auto w-full", style: { height }, children: _jsxs(BarChart, { data: rows, margin: { left: 0, right: 0, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false, strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: x, tickLine: false, axisLine: false, tickMargin: 8, minTickGap: 12, tickFormatter: (v) => tick(String(v)) }), _jsx(YAxis, { tickLine: false, axisLine: false, domain: [0, ticks.at(-1) ?? 'auto'], ticks: ticks, width: axisWidth(ticks.map(fmt)), tick: axisTick(fmt) }), _jsx(ChartTooltip, { cursor: { fill: 'var(--muted)', opacity: 0.5 }, content: _jsx(ChartTooltipContent, { labelFormatter: (_, payload) => {
|
|
47
47
|
const r = payload?.[0]?.payload;
|
|
48
48
|
return r ? (_jsxs("span", { className: "flex w-full gap-3", children: [_jsx("span", { children: tick(String(r[x])) }), _jsx("span", { className: "num ml-auto", children: fmt(total(r)) })] })) : null;
|
|
@@ -54,7 +54,8 @@ export function TimeLines({ rows, x, lines, label, height = 220, tick = String,
|
|
|
54
54
|
const config = Object.fromEntries(lines.map((l) => [l.key, { label: l.name, color: l.fill }]));
|
|
55
55
|
const axis = (right) => {
|
|
56
56
|
const keys = lines.filter((l) => !!l.right === right).map((l) => l.key);
|
|
57
|
-
|
|
57
|
+
const values = rows.flatMap((r) => keys.map((k) => Number(r[k]) || 0));
|
|
58
|
+
return niceTicks(Math.max(0, ...values), 4, values.every(Number.isInteger));
|
|
58
59
|
};
|
|
59
60
|
const left = axis(false);
|
|
60
61
|
const hasRight = lines.some((l) => l.right);
|
|
@@ -113,14 +114,16 @@ export function AllocationBar({ items, total, config, tooltip, share, }) {
|
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
115
116
|
* Round axis steps (1, 2, 2.5 or 5 times a power of ten) from 0 to just above `max`, about four
|
|
116
|
-
* of them, so a value axis reads 0, 100k, 200k, 300k rather than 95k, 190k, 285k.
|
|
117
|
+
* of them, so a value axis reads 0, 100k, 200k, 300k rather than 95k, 190k, 285k. When every value
|
|
118
|
+
* is a whole number (a count), so is every step: 0, 1, 2 rather than 0, 0.5, 1, 1.5.
|
|
117
119
|
*/
|
|
118
|
-
function niceTicks(max, count = 4) {
|
|
120
|
+
function niceTicks(max, count = 4, whole = false) {
|
|
119
121
|
if (!(max > 0))
|
|
120
122
|
return [0, 1];
|
|
121
123
|
const raw = max / count;
|
|
122
124
|
const pow = 10 ** Math.floor(Math.log10(raw));
|
|
123
|
-
const
|
|
125
|
+
const steps = whole ? [1, 2, 5, 10] : [1, 2, 2.5, 5, 10];
|
|
126
|
+
const step = Math.max(whole ? 1 : 0, (steps.find((m) => m * pow >= raw) ?? 10) * pow);
|
|
124
127
|
const n = Math.ceil(max / step - 1e-9);
|
|
125
128
|
return Array.from({ length: n + 1 }, (_, i) => Math.round(i * step * 1e6) / 1e6);
|
|
126
129
|
}
|
package/dist/page.d.ts
CHANGED
|
@@ -21,7 +21,10 @@ export interface PageHeaderProps {
|
|
|
21
21
|
}
|
|
22
22
|
/** The fixed-height header; `Page` renders it, or a page with several states renders it itself. */
|
|
23
23
|
export declare function PageHeader({ crumbs, actions, layout, meta, status }: PageHeaderProps): import("react").JSX.Element;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* The page body: the shell's gutter, section gap and max width. A plain block, since the shell's
|
|
26
|
+
* inset is the page's one `main` landmark.
|
|
27
|
+
*/
|
|
25
28
|
export declare function PageBody({ className, children }: {
|
|
26
29
|
className?: string;
|
|
27
30
|
children: ReactNode;
|
package/dist/page.js
CHANGED
|
@@ -36,9 +36,12 @@ export function PageHeader({ crumbs, actions, layout, meta, status }) {
|
|
|
36
36
|
isMobile &&
|
|
37
37
|
'[&_[data-slot=button]]:size-8 [&_[data-slot=button]]:gap-0 [&_[data-slot=button]]:px-0 [&_[data-slot=button]]:text-[0px]'), children: [actions && (_jsxs(_Fragment, { children: [_jsx("div", { "data-slot": "shell-page-actions", className: "flex items-center gap-0.5", children: actions }), _jsx(Separator, { orientation: "vertical", className: "mx-2 data-vertical:h-5 data-vertical:self-center" })] })), _jsxs("div", { "data-slot": "shell-actions", className: "flex items-center gap-0.5", children: [layout, _jsx(LumenFinderButton, {}), hideAmounts && (_jsx(IconButton, { icon: hidden ? EyeOffIcon : EyeIcon, label: hidden ? 'Show amounts' : 'Hide amounts', "aria-pressed": hidden, onClick: () => setPref('hideAmounts', !hidden) })), refresh && (_jsx(IconButton, { icon: RefreshCwIcon, busy: refreshing, label: "Refresh (skip cache)", disabled: refreshing, onClick: () => void refresh() })), headerActions] })] })] }), status && statusSlot && createPortal(status, statusSlot)] }));
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The page body: the shell's gutter, section gap and max width. A plain block, since the shell's
|
|
41
|
+
* inset is the page's one `main` landmark.
|
|
42
|
+
*/
|
|
40
43
|
export function PageBody({ className, children }) {
|
|
41
|
-
return (_jsx("
|
|
44
|
+
return (_jsx("div", { className: cn('mx-auto flex w-full max-w-(--shell-max-width) min-w-0 flex-1 flex-col gap-(--shell-section-gap) p-(--shell-gutter)', className), children: children }));
|
|
42
45
|
}
|
|
43
46
|
export function Page({ className, children, ...header }) {
|
|
44
47
|
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { ...header }), _jsx(PageBody, { className: className, children: children })] }));
|
|
@@ -9,17 +9,17 @@ export interface StatusMessage {
|
|
|
9
9
|
interface Group extends Required<StatusMessage> {
|
|
10
10
|
count: number;
|
|
11
11
|
}
|
|
12
|
-
/** One entry per distinct message (same tone, same words), counted,
|
|
12
|
+
/** One entry per distinct message (same tone, same words), counted, in the order given. */
|
|
13
13
|
export declare function groupStatusMessages(messages: readonly StatusMessage[]): Group[];
|
|
14
14
|
/**
|
|
15
|
-
* The app's messages in the status bar.
|
|
16
|
-
* `--status-message-max`, with its full text on hover or tap; a
|
|
17
|
-
*
|
|
15
|
+
* The app's messages in the status bar, newest first. Only the latest shows, on one line that ends
|
|
16
|
+
* in an ellipsis past `--status-message-max`, with its full text on hover or tap; a new message
|
|
17
|
+
* takes its place rather than adding a second line. A repeat shows once with `×N`, and the earlier
|
|
18
|
+
* messages sit behind a `+N` chip that lists them.
|
|
18
19
|
*/
|
|
19
|
-
export declare function StatusMessages({ messages,
|
|
20
|
+
export declare function StatusMessages({ messages, className, }: {
|
|
21
|
+
/** Newest first. */
|
|
20
22
|
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
23
|
className?: string;
|
|
24
24
|
}): import("react").JSX.Element | null;
|
|
25
25
|
export {};
|
package/dist/status-messages.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useHoverOpen } from './hooks/use-hover-open.js';
|
|
3
|
-
import { useIsMobile } from './hooks/use-mobile.js';
|
|
4
3
|
import { cn } from './lib/utils.js';
|
|
5
4
|
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
5
|
const TEXT = {
|
|
9
6
|
error: 'text-destructive',
|
|
10
7
|
warning: 'text-warning',
|
|
@@ -17,7 +14,7 @@ const DOT = {
|
|
|
17
14
|
success: 'bg-gain',
|
|
18
15
|
info: undefined,
|
|
19
16
|
};
|
|
20
|
-
/** One entry per distinct message (same tone, same words), counted,
|
|
17
|
+
/** One entry per distinct message (same tone, same words), counted, in the order given. */
|
|
21
18
|
export function groupStatusMessages(messages) {
|
|
22
19
|
const groups = new Map();
|
|
23
20
|
for (const m of messages) {
|
|
@@ -33,8 +30,8 @@ export function groupStatusMessages(messages) {
|
|
|
33
30
|
else
|
|
34
31
|
groups.set(key, { text, tone, count });
|
|
35
32
|
}
|
|
36
|
-
// Map keeps first-seen order
|
|
37
|
-
return [...groups.values()]
|
|
33
|
+
// Map keeps first-seen order, so the latest message stays first.
|
|
34
|
+
return [...groups.values()];
|
|
38
35
|
}
|
|
39
36
|
const label = (g) => (g.count > 1 ? `${g.text} (×${g.count})` : g.text);
|
|
40
37
|
function Dot({ tone }) {
|
|
@@ -57,16 +54,14 @@ function Line({ group }) {
|
|
|
57
54
|
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
55
|
}
|
|
59
56
|
/**
|
|
60
|
-
* The app's messages in the status bar.
|
|
61
|
-
* `--status-message-max`, with its full text on hover or tap; a
|
|
62
|
-
*
|
|
57
|
+
* The app's messages in the status bar, newest first. Only the latest shows, on one line that ends
|
|
58
|
+
* in an ellipsis past `--status-message-max`, with its full text on hover or tap; a new message
|
|
59
|
+
* takes its place rather than adding a second line. A repeat shows once with `×N`, and the earlier
|
|
60
|
+
* messages sit behind a `+N` chip that lists them.
|
|
63
61
|
*/
|
|
64
|
-
export function StatusMessages({ messages,
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
if (!groups.length)
|
|
62
|
+
export function StatusMessages({ messages, className, }) {
|
|
63
|
+
const [latest, ...earlier] = groupStatusMessages(messages);
|
|
64
|
+
if (!latest)
|
|
68
65
|
return null;
|
|
69
|
-
|
|
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] }))] }));
|
|
66
|
+
return (_jsxs("span", { "aria-live": "polite", className: cn('flex min-w-0 items-center gap-2', className), children: [_jsx(Line, { group: latest }), earlier.length > 0 && (_jsxs(Pop, { label: `${earlier.length} earlier messages`, className: cn(TRIGGER, 'num shrink-0 text-foreground/80'), content: _jsx("ul", { className: "flex flex-col gap-1", children: earlier.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: ["+", earlier.length] }))] }));
|
|
72
67
|
}
|
package/dist/theme.css
CHANGED
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
--shell-header-height: 48px;
|
|
90
90
|
--shell-status-height: 32px;
|
|
91
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.
|
|
93
|
-
--status-message-max:
|
|
92
|
+
other items; the full text opens on hover or tap. */
|
|
93
|
+
--status-message-max: 55vw;
|
|
94
94
|
--tile-width: 8.5rem;
|
|
95
95
|
--shell-gutter: 12px;
|
|
96
96
|
--shell-section-gap: 12px;
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
@media (min-width: 768px) {
|
|
216
216
|
:root {
|
|
217
217
|
--shell-gutter: 16px;
|
|
218
|
-
--status-message-max: min(
|
|
218
|
+
--status-message-max: min(40rem, 45vw);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.132",
|
|
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.132",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|