@columenlabs/lumen-ui 0.1.124 → 0.1.125
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-trend.js +24 -9
- package/dist/lumen-widget.d.ts +1 -1
- package/dist/ui/chart.d.ts +5 -1
- package/dist/ui/chart.js +11 -3
- package/dist/widget-settings.d.ts +1 -1
- package/package.json +2 -2
package/dist/lumen-trend.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Trend widgets: up to 4 number fields over a date field of the view's rows, rolled up per day, week,
|
|
3
|
-
// month or year, drawn as a line, area, stacked area, bars over time, combo, a spark
|
|
3
|
+
// month or year, drawn as a line, area, stacked area, bars over time, stacked bars, combo, a spark
|
|
4
|
+
// or a tracker.
|
|
4
5
|
// With the allocation, donut, bars and card styles they cover every Tremor visualization, drawn on
|
|
5
6
|
// our own Recharts in ChartContainer. Worked out in the browser from the rows; nothing is fetched.
|
|
6
7
|
import { formatBytes, isDollarFormat, } from '@columenlabs/lumen-ui-types/views';
|
|
7
|
-
import { useMemo } from 'react';
|
|
8
|
+
import { useMemo, useState } from 'react';
|
|
8
9
|
import { Area, AreaChart, Bar, BarChart, CartesianGrid, ComposedChart, Line, LineChart, XAxis, YAxis, } from 'recharts';
|
|
9
10
|
import { cn } from './lib/utils.js';
|
|
10
11
|
import { Headline } from './lumen-widget-charts.js';
|
|
@@ -115,6 +116,8 @@ function tick(at, bucket) {
|
|
|
115
116
|
export function TrendBody({ style, date, values, bucket, agg, rows, read, legend = true, }) {
|
|
116
117
|
const hidden = useAmountsHidden();
|
|
117
118
|
const points = useMemo(() => trendPoints(rows, date, values, bucket, agg, read), [rows, date, values, bucket, agg, read]);
|
|
119
|
+
// Series hidden by a tap on the legend; the last one shown can't be hidden.
|
|
120
|
+
const [off, setOff] = useState([]);
|
|
118
121
|
const first = values[0];
|
|
119
122
|
if (!points.length || !first)
|
|
120
123
|
return _jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "No dated rows to draw." });
|
|
@@ -124,6 +127,12 @@ export function TrendBody({ style, date, values, bucket, agg, rows, read, legend
|
|
|
124
127
|
`v${i}`,
|
|
125
128
|
{ label: agg === 'count' ? `${f.label} (count)` : f.label, color: COLORS[i] },
|
|
126
129
|
]));
|
|
130
|
+
const keys = values.map((_, i) => `v${i}`);
|
|
131
|
+
const on = keys.filter((k) => !off.includes(k));
|
|
132
|
+
const toggle = (k) => setOff((o) => o.includes(k) ? o.filter((x) => x !== k) : o.length + 1 < keys.length ? [...o, k] : o);
|
|
133
|
+
// Stacked parts add up: the headline and tooltip read their total.
|
|
134
|
+
const stacks = (style === 'stacked' || style === 'stacked_bars') && on.length > 1;
|
|
135
|
+
const totalOf = (p) => on.reduce((t, k) => t + Number(p?.[k] ?? 0), 0);
|
|
127
136
|
const last = Number(points[points.length - 1]?.v0 ?? 0);
|
|
128
137
|
const start = Number(points[0]?.v0 ?? 0);
|
|
129
138
|
if (style === 'tracker') {
|
|
@@ -143,17 +152,23 @@ export function TrendBody({ style, date, values, bucket, agg, rows, read, legend
|
|
|
143
152
|
_jsx(CartesianGrid, { vertical: false, strokeDasharray: "3 3" }, "g"),
|
|
144
153
|
_jsx(XAxis, { dataKey: "at", tickLine: false, axisLine: false, minTickGap: 24, tickFormatter: (v) => tick(String(v), bucket), fontSize: 11 }, "x"),
|
|
145
154
|
_jsx(YAxis, { tickLine: false, axisLine: false, width: 48, tickFormatter: (v) => fmt(Number(v)), fontSize: 11 }, "y"),
|
|
146
|
-
_jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { labelFormatter: (v) => tick(String(v), bucket), formatter: (v, name) => {
|
|
155
|
+
_jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { labelFormatter: (v) => tick(String(v), bucket), formatter: (v, name, item) => {
|
|
147
156
|
const i = Number(String(name).slice(1));
|
|
148
157
|
const f = values[i] ?? first;
|
|
149
|
-
return (_jsxs("span", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "size-2 shrink-0 rounded-full", style: { background: COLORS[i] } }), _jsx("span", { className: "truncate", children: config[String(name)]?.label }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(Number(v), f) })] }));
|
|
158
|
+
return (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "size-2 shrink-0 rounded-full", style: { background: COLORS[i] } }), _jsx("span", { className: "truncate", children: config[String(name)]?.label }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(Number(v), f) })] }), stacks && String(name) === on[on.length - 1] && (_jsxs("span", { className: "flex w-full items-center gap-2 border-t pt-1.5 font-medium", children: [_jsx("span", { className: "size-2 shrink-0" }), _jsx("span", { className: "truncate", children: "Total" }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(totalOf(item.payload), f) })] }))] }));
|
|
150
159
|
} }) }, "t"),
|
|
151
160
|
...(legend && values.length > 1
|
|
152
|
-
? [_jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }, "l")]
|
|
161
|
+
? [_jsx(ChartLegend, { content: _jsx(ChartLegendContent, { off: off, onSeries: toggle }) }, "l")]
|
|
153
162
|
: []),
|
|
154
163
|
];
|
|
155
|
-
const
|
|
164
|
+
const end = points[points.length - 1];
|
|
165
|
+
const lead0 = on[0] ?? 'v0';
|
|
166
|
+
const lead = (_jsx(Headline, { value: fmt(stacks ? totalOf(end) : Number(end?.[lead0] ?? 0)), caption: `${stacks ? 'Total' : config[lead0]?.label} · ${tick(String(end?.at), bucket)}` }));
|
|
156
167
|
const box = 'aspect-auto h-56 w-full';
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
// Room above the top gridline, so its label sits on the line rather than below it (a bar near
|
|
169
|
+
// the top read as reaching the label's value).
|
|
170
|
+
const margin = { top: 8, left: 0, right: 4 };
|
|
171
|
+
// A stack's top part rounds its data end; a 2px gap of the card's colour parts the segments.
|
|
172
|
+
const top = on[on.length - 1];
|
|
173
|
+
return (_jsxs("div", { className: "flex flex-col", children: [lead, _jsx(ChartContainer, { config: config, className: box, children: style === 'bars' || style === 'stacked_bars' ? (_jsxs(BarChart, { data: points, margin: margin, children: [axes, keys.map((k) => style === 'stacked_bars' ? (_jsx(Bar, { dataKey: k, hide: off.includes(k), stackId: "a", fill: `var(--color-${k})`, stroke: "var(--card)", strokeWidth: on.length > 1 ? 2 : 0, radius: k === top ? [2, 2, 0, 0] : 0, isAnimationActive: false }, k)) : (_jsx(Bar, { dataKey: k, hide: off.includes(k), fill: `var(--color-${k})`, radius: 2, isAnimationActive: false }, k)))] })) : style === 'combo' ? (_jsxs(ComposedChart, { data: points, margin: margin, children: [axes, keys.map((k, i) => i === 0 ? (_jsx(Bar, { dataKey: k, hide: off.includes(k), fill: `var(--color-${k})`, radius: 2, isAnimationActive: false }, k)) : (_jsx(Line, { dataKey: k, hide: off.includes(k), type: "monotone", stroke: `var(--color-${k})`, strokeWidth: 1.5, dot: false, isAnimationActive: false }, k)))] })) : style === 'area' || style === 'stacked' ? (_jsxs(AreaChart, { data: points, margin: margin, children: [axes, keys.map((k) => (_jsx(Area, { dataKey: k, hide: off.includes(k), type: "monotone", stackId: style === 'stacked' ? 'a' : undefined, stroke: `var(--color-${k})`, fill: `var(--color-${k})`, fillOpacity: 0.15, strokeWidth: 1.5, isAnimationActive: false }, k)))] })) : (_jsxs(LineChart, { data: points, margin: margin, children: [axes, keys.map((k) => (_jsx(Line, { dataKey: k, hide: off.includes(k), type: "monotone", stroke: `var(--color-${k})`, strokeWidth: 1.5, dot: false, isAnimationActive: false }, k)))] })) })] }));
|
|
159
174
|
}
|
package/dist/lumen-widget.d.ts
CHANGED
|
@@ -690,7 +690,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
690
690
|
bucket: "day" | "month" | "week" | "year";
|
|
691
691
|
agg: "count" | "last" | "mean" | "sum";
|
|
692
692
|
kind: "chart";
|
|
693
|
-
style: "area" | "bars" | "combo" | "line" | "stacked";
|
|
693
|
+
style: "area" | "bars" | "combo" | "line" | "stacked" | "stacked_bars";
|
|
694
694
|
} | {
|
|
695
695
|
id: string;
|
|
696
696
|
title: string | null;
|
package/dist/ui/chart.d.ts
CHANGED
|
@@ -37,8 +37,12 @@ declare function ChartTooltipContent({ active, payload, className, indicator, hi
|
|
|
37
37
|
labelKey?: string;
|
|
38
38
|
} & Omit<RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>, 'accessibilityLayer'>): React.JSX.Element | null;
|
|
39
39
|
declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
|
|
40
|
-
declare function ChartLegendContent({ className, hideIcon, payload, verticalAlign, nameKey, }: React.ComponentProps<'div'> & {
|
|
40
|
+
declare function ChartLegendContent({ className, hideIcon, payload, verticalAlign, nameKey, off, onSeries, }: React.ComponentProps<'div'> & {
|
|
41
41
|
hideIcon?: boolean;
|
|
42
42
|
nameKey?: string;
|
|
43
|
+
/** Series keys hidden by a tap on the legend; they stay listed, dimmed. */
|
|
44
|
+
off?: string[];
|
|
45
|
+
/** Given, each item is a button that shows or hides its series. */
|
|
46
|
+
onSeries?: (key: string) => void;
|
|
43
47
|
} & RechartsPrimitive.DefaultLegendContentProps): React.JSX.Element | null;
|
|
44
48
|
export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, };
|
package/dist/ui/chart.js
CHANGED
|
@@ -81,7 +81,7 @@ function ChartTooltipContent({ active, payload, className, indicator = 'dot', hi
|
|
|
81
81
|
}) })] }));
|
|
82
82
|
}
|
|
83
83
|
const ChartLegend = RechartsPrimitive.Legend;
|
|
84
|
-
function ChartLegendContent({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey, }) {
|
|
84
|
+
function ChartLegendContent({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey, off, onSeries, }) {
|
|
85
85
|
const { config } = useChart();
|
|
86
86
|
if (!payload?.length) {
|
|
87
87
|
return null;
|
|
@@ -91,8 +91,16 @@ function ChartLegendContent({ className, hideIcon = false, payload, verticalAlig
|
|
|
91
91
|
.map((item, index) => {
|
|
92
92
|
const key = `${nameKey ?? item.dataKey ?? 'value'}`;
|
|
93
93
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const series = String(item.dataKey ?? key);
|
|
95
|
+
const hidden = off?.includes(series) ?? false;
|
|
96
|
+
const Item = onSeries ? 'button' : 'div';
|
|
97
|
+
return (_jsxs(Item, { title: typeof itemConfig?.label === 'string' ? itemConfig.label : undefined, ...(onSeries && {
|
|
98
|
+
type: 'button',
|
|
99
|
+
'aria-pressed': !hidden,
|
|
100
|
+
onClick: () => onSeries(series),
|
|
101
|
+
}), className: cn('flex max-w-full min-w-0 items-center gap-1.5 sm:max-w-64 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground', onSeries &&
|
|
102
|
+
'min-h-6 cursor-pointer rounded-sm px-1 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none', hidden && 'text-muted-foreground line-through'), children: [itemConfig?.icon && !hideIcon ? (_jsx(itemConfig.icon, {})) : (_jsx("div", { className: "h-2 w-2 shrink-0 rounded-[2px]", style: {
|
|
103
|
+
backgroundColor: hidden ? 'var(--muted-foreground)' : item.color,
|
|
96
104
|
} })), _jsx("span", { className: "truncate", children: itemConfig?.label })] }, index));
|
|
97
105
|
}) }));
|
|
98
106
|
}
|
|
@@ -514,5 +514,5 @@ export declare const readsRows: (card: ViewWidget) => card is {
|
|
|
514
514
|
bucket: "day" | "month" | "week" | "year";
|
|
515
515
|
agg: "count" | "last" | "mean" | "sum";
|
|
516
516
|
kind: "chart";
|
|
517
|
-
style: "area" | "bars" | "combo" | "line" | "stacked";
|
|
517
|
+
style: "area" | "bars" | "combo" | "line" | "stacked" | "stacked_bars";
|
|
518
518
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.125",
|
|
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.125",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|