@columenlabs/lumen-ui 0.1.73 → 0.1.74
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 +6 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/lumen-auth.d.ts +12 -0
- package/dist/lumen-auth.js +26 -0
- package/dist/lumen-bot-check.d.ts +8 -0
- package/dist/lumen-bot-check.js +10 -0
- package/dist/lumen-echart.js +1 -1
- package/dist/lumen-file-field.d.ts +13 -0
- package/dist/lumen-file-field.js +50 -0
- package/dist/lumen-layout.d.ts +2 -0
- package/dist/lumen-layout.js +1 -1
- package/dist/lumen-widget-charts.d.ts +36 -5
- package/dist/lumen-widget-charts.js +30 -4
- package/dist/lumen-widget.js +2 -1
- package/dist/overview.js +1 -1
- package/dist/tasks.js +3 -3
- package/dist/theme.css +10 -3
- package/dist/ui/chart.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ bun add -d tailwindcss @tailwindcss/vite tw-animate-css shadcn
|
|
|
29
29
|
|
|
30
30
|
/* your accent, per app (LumenShell sets data-app on <html>) */
|
|
31
31
|
:root[data-app="myapp"] { --primary: oklch(0.55 0.15 160); }
|
|
32
|
+
/* optional: numbers in the text font instead of mono (code stays mono via `font-code`) */
|
|
33
|
+
:root[data-app="myapp"] { --num-font: var(--app-font); }
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
At start, before anything renders, tell the kit about your app. The kit names no product: your names and data come in here.
|
|
@@ -57,13 +59,13 @@ Use `createDeskQueryClient()` for the app's TanStack Query client: data stays fr
|
|
|
57
59
|
|
|
58
60
|
## What's inside
|
|
59
61
|
|
|
60
|
-
**Frame.** `LumenShell` (sidebar with icons-only collapse and a drawer on phone, header, status bar, Settings, profile menu, ⌘K Find), `LumenAppSwitcher` and `LumenSuiteSwitcher` (groups, apps on other sites, a list read from an address), `WhatsNew` (release notes in a drawer), `TasksButton` and `TasksProvider` (background work in the header), `NotificationsSettings`, `McpSettings`, sign-in screens (`LumenAuthStage`, `LumenSecondStep`, `LumenSignInMethods`, `LumenBackupCodes`), `StatusHealth`, `LumenBoundary` (keeps a crash inside its card), `LumenPageState` (loading, empty and error), `lumenToast`.
|
|
62
|
+
**Frame.** `LumenShell` (sidebar with icons-only collapse and a drawer on phone, header, status bar, Settings, profile menu, ⌘K Find), `LumenAppSwitcher` and `LumenSuiteSwitcher` (groups, apps on other sites, a list read from an address), `WhatsNew` (release notes in a drawer), `TasksButton` and `TasksProvider` (background work in the header), `NotificationsSettings`, `McpSettings`, sign-in screens (`LumenAuthStage`, `LumenSecondStep`, `LumenSignInMethods`, `LumenBackupCodes`, `LumenBotCheck` for Cloudflare Turnstile, `authErrorText` for errors in plain words), `StatusHealth`, `LumenBoundary` (keeps a crash inside its card), `LumenPageState` (loading, empty and error), `lumenToast`.
|
|
61
63
|
|
|
62
|
-
**Pages and views.** `LumenLayout` (a page of cards from one view config: filter, tiles, widgets; Edit layout and Edit widget when you pass `onConfigChange`), `LumenGrid` (the 6-column grid under it), `LumenView` and `LumenViewCard` (a table that filters, groups, pivots and totals the rows you load), `LumenTable`, `LumenTokenizedFilter`, `LumenWidget` and `LumenSavedWidget` (the frame every card sits in, with its link square and controls), `LumenCard` (one number and its lines), `LumenPager`, `DetailsTable`, `LumenTree`, `useAppView` (a page whose layout each person can change and reset).
|
|
64
|
+
**Pages and views.** `LumenLayout` (a page of cards from one view config: filter, tiles, widgets; Edit layout and Edit widget when you pass `onConfigChange`; a card with `header: false` is its body alone), `LumenGrid` (the 6-column grid under it), `LumenView` and `LumenViewCard` (a table that filters, groups, pivots and totals the rows you load), `LumenTable`, `LumenTokenizedFilter`, `LumenWidget` and `LumenSavedWidget` (the frame every card sits in, with its link square and controls), `LumenCard` (one number and its lines), `LumenPager`, `DetailsTable`, `LumenTree`, `useAppView` (a page whose layout each person can change and reset).
|
|
63
65
|
|
|
64
|
-
**Charts.** `LumenChart` (one or more symbols' prices, with indicators), `LumenEChart`, `LumenGeoMap`, growth and price charts, `StackedBars`, `StackedColumns`, `RankedBars
|
|
66
|
+
**Charts.** `LumenChart` (one or more symbols' prices, with indicators), `LumenEChart`, `LumenGeoMap`, growth and price charts, `StackedBars`, `StackedColumns`, `TimeLines` (lines over time, with an optional right axis), `RankedBars` (with each bar's share of the total or of the first bar, and an empty state).
|
|
65
67
|
|
|
66
|
-
**Pickers and panels.** `LumenSelector` (every picker), `LumenDrawer` with `DrawerSection` (a side panel on desktop, a sheet on phone), `LumenTabs`, `LumenConfirm` (destructive confirms), `LumenForm`, `LumenDateInput`, `LumenChoice`, `LumenSecret`, `RenameField`, `ResetButton`, `SaveButton` with `FormIssue` (a Save that says why it can't), `IconPicker`.
|
|
68
|
+
**Pickers and panels.** `LumenSelector` (every picker), `LumenDrawer` with `DrawerSection` (a side panel on desktop, a sheet on phone), `LumenTabs`, `LumenConfirm` (destructive confirms), `LumenForm`, `LumenDateInput`, `LumenChoice`, `LumenSecret`, `LumenFileField` (drop or pick a file), `RenameField`, `ResetButton`, `SaveButton` with `FormIssue` (a Save that says why it can't), `IconPicker`.
|
|
67
69
|
|
|
68
70
|
**Primitives.** The shadcn parts under `@columenlabs/lumen-ui/ui/*` (`ui/button`, `ui/badge`, `ui/input`, ...) and `theme.css` (light and dark tokens).
|
|
69
71
|
|
package/dist/index.d.ts
CHANGED
|
@@ -16,12 +16,14 @@ export { LumenGeoMap } from './lumen-geo-map.js';
|
|
|
16
16
|
export { LumenWidget, type CardControls, type CardLink, LumenSavedWidget } from './lumen-widget.js';
|
|
17
17
|
export { LumenConfirm, type LumenConfirmProps } from './confirm.js';
|
|
18
18
|
export { InfoLabel, InfoTip } from './info-tip.js';
|
|
19
|
-
export { type LumenAuthPoint, LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, type LumenAuthStageProps, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, } from './lumen-auth.js';
|
|
19
|
+
export { type LumenAuthPoint, LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, type LumenAuthStageProps, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, type LumenAuthError, authErrorText, } from './lumen-auth.js';
|
|
20
|
+
export { LumenBotCheck } from './lumen-bot-check.js';
|
|
20
21
|
export { LumenBackupCodes, type LumenCodeMethod, type LumenPasskey, type LumenResult, LumenSecondStep, type LumenSecondStepProps, LumenSignInMethods, type LumenSignInMethodsProps, } from './lumen-security.js';
|
|
21
22
|
export { LumenDateInput, type LumenDateInputProps, stepDate, } from './lumen-date-input.js';
|
|
22
23
|
export { LumenChoice, type LumenChoiceOption } from './lumen-choice.js';
|
|
24
|
+
export { LumenFileField, type LumenFileFieldProps } from './lumen-file-field.js';
|
|
23
25
|
export { LumenSecret } from './lumen-secret.js';
|
|
24
|
-
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
26
|
+
export { RankedBars, StackedBars, StackedColumns, TimeLines } from './lumen-widget-charts.js';
|
|
25
27
|
export { type ShareSegment, ShareMeter } from './share-meter.js';
|
|
26
28
|
export { type Pool, poolShare } from './pool-share.js';
|
|
27
29
|
export { METER_LEVEL_COLOR, meterLevel, meterValueTone } from './meter-level.js';
|
package/dist/index.js
CHANGED
|
@@ -23,12 +23,14 @@ export { LumenGeoMap } from './lumen-geo-map.js';
|
|
|
23
23
|
export { LumenWidget, LumenSavedWidget } from './lumen-widget.js';
|
|
24
24
|
export { LumenConfirm } from './confirm.js';
|
|
25
25
|
export { InfoLabel, InfoTip } from './info-tip.js';
|
|
26
|
-
export { LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, } from './lumen-auth.js';
|
|
26
|
+
export { LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, authErrorText, } from './lumen-auth.js';
|
|
27
|
+
export { LumenBotCheck } from './lumen-bot-check.js';
|
|
27
28
|
export { LumenBackupCodes, LumenSecondStep, LumenSignInMethods, } from './lumen-security.js';
|
|
28
29
|
export { LumenDateInput, stepDate, } from './lumen-date-input.js';
|
|
29
30
|
export { LumenChoice } from './lumen-choice.js';
|
|
31
|
+
export { LumenFileField } from './lumen-file-field.js';
|
|
30
32
|
export { LumenSecret } from './lumen-secret.js';
|
|
31
|
-
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
33
|
+
export { RankedBars, StackedBars, StackedColumns, TimeLines } from './lumen-widget-charts.js';
|
|
32
34
|
export { ShareMeter } from './share-meter.js';
|
|
33
35
|
export { poolShare } from './pool-share.js';
|
|
34
36
|
export { METER_LEVEL_COLOR, meterLevel, meterValueTone } from './meter-level.js';
|
package/dist/lumen-auth.d.ts
CHANGED
|
@@ -65,4 +65,16 @@ export declare function LumenStepBar({ at, of }: {
|
|
|
65
65
|
at: number;
|
|
66
66
|
of: number;
|
|
67
67
|
}): import("react").JSX.Element;
|
|
68
|
+
/** An error from the sign-in server (a `code` such as Better Auth's, a status, a message). */
|
|
69
|
+
export type LumenAuthError = {
|
|
70
|
+
code?: string;
|
|
71
|
+
message?: string;
|
|
72
|
+
status?: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A sign-in error in plain words, for `LumenAuthProblem`. Known codes get the kit's line, a 429 asks
|
|
76
|
+
* the person to wait, and anything else shows the server's message or `fallback`. An app passes
|
|
77
|
+
* `own` for codes of its own, checked first.
|
|
78
|
+
*/
|
|
79
|
+
export declare function authErrorText(e: LumenAuthError | null | undefined, fallback?: string, own?: Record<string, string>): string;
|
|
68
80
|
export {};
|
package/dist/lumen-auth.js
CHANGED
|
@@ -51,3 +51,29 @@ export function LumenCodeField({ id, value, onChange, length = 6, error, hint, o
|
|
|
51
51
|
export function LumenStepBar({ at, of }) {
|
|
52
52
|
return (_jsxs("div", { className: "mb-5 flex flex-col gap-1.5", children: [_jsx("div", { className: "flex gap-1.5", children: Array.from({ length: of }, (_, i) => i + 1).map((n) => (_jsx("span", { className: `h-1 flex-1 rounded-full ${n <= at ? 'bg-primary' : 'bg-muted'}` }, n))) }), _jsxs("span", { className: "text-center text-muted-foreground text-xs", children: ["Step ", at, " of ", of] })] }));
|
|
53
53
|
}
|
|
54
|
+
const AUTH_ERROR_TEXT = {
|
|
55
|
+
INVALID_EMAIL_OR_PASSWORD: 'That email and password don’t match.',
|
|
56
|
+
INVALID_CODE: 'That code doesn’t match. Check the latest one and try again.',
|
|
57
|
+
INVALID_OTP: 'That code doesn’t match. Check the latest one and try again.',
|
|
58
|
+
INVALID_TWO_FACTOR_AUTHENTICATION: 'That code doesn’t match. Check the latest one and try again.',
|
|
59
|
+
OTP_HAS_EXPIRED: 'That code has expired. Send a new one.',
|
|
60
|
+
OTP_EXPIRED: 'That code has expired. Send a new one.',
|
|
61
|
+
TOO_MANY_ATTEMPTS: 'Too many tries. Send a new code.',
|
|
62
|
+
TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE: 'Too many tries. Send a new code.',
|
|
63
|
+
INVALID_TWO_FACTOR_COOKIE: 'That took too long. Sign in again.',
|
|
64
|
+
MISSING_RESPONSE: 'The bot check didn’t pass. Wait a moment and try again.',
|
|
65
|
+
VERIFICATION_FAILED: 'The bot check didn’t pass. Wait a moment and try again.',
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* A sign-in error in plain words, for `LumenAuthProblem`. Known codes get the kit's line, a 429 asks
|
|
69
|
+
* the person to wait, and anything else shows the server's message or `fallback`. An app passes
|
|
70
|
+
* `own` for codes of its own, checked first.
|
|
71
|
+
*/
|
|
72
|
+
export function authErrorText(e, fallback = 'That didn’t work. Try again.', own = {}) {
|
|
73
|
+
const known = e?.code ? (own[e.code] ?? AUTH_ERROR_TEXT[e.code]) : undefined;
|
|
74
|
+
if (known)
|
|
75
|
+
return known;
|
|
76
|
+
if (e?.status === 429)
|
|
77
|
+
return 'Too many tries. Wait a minute and try again.';
|
|
78
|
+
return e?.message || fallback;
|
|
79
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Renders nothing without a site key, so an app can ship it before its keys are set. */
|
|
2
|
+
export declare function LumenBotCheck({ siteKey, onToken, version, }: {
|
|
3
|
+
siteKey: string | null | undefined;
|
|
4
|
+
/** The token to send with the form, or null when it expired or failed. */
|
|
5
|
+
onToken: (token: string | null) => void;
|
|
6
|
+
/** Bump to get a fresh token after a try. */
|
|
7
|
+
version: number;
|
|
8
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// The bot check on signed-out forms: Cloudflare Turnstile, out of sight unless it is unsure, when
|
|
3
|
+
// it asks for one click. Each token works once, so a form bumps `version` after every try.
|
|
4
|
+
import { Turnstile } from '@marsidev/react-turnstile';
|
|
5
|
+
/** Renders nothing without a site key, so an app can ship it before its keys are set. */
|
|
6
|
+
export function LumenBotCheck({ siteKey, onToken, version, }) {
|
|
7
|
+
if (!siteKey)
|
|
8
|
+
return null;
|
|
9
|
+
return (_jsx(Turnstile, { siteKey: siteKey, options: { size: 'flexible', appearance: 'interaction-only' }, onSuccess: onToken, onExpire: () => onToken(null), onError: () => onToken(null) }, version));
|
|
10
|
+
}
|
package/dist/lumen-echart.js
CHANGED
|
@@ -15,7 +15,7 @@ function readTokens() {
|
|
|
15
15
|
primary: v('--primary', '#5b8cff'),
|
|
16
16
|
series: Array.from({ length: 10 }, (_, i) => v(`--chart-${i + 1}`, '#5b8cff')),
|
|
17
17
|
font: getComputedStyle(document.body).fontFamily || 'sans-serif',
|
|
18
|
-
mono: v('--font
|
|
18
|
+
mono: v('--num-font', 'monospace'),
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
/** The theme's chart colours; new ones when light and dark flip. */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface LumenFileFieldProps {
|
|
2
|
+
/** The chosen file, or null for none. */
|
|
3
|
+
file: File | null;
|
|
4
|
+
onFile: (file: File | null) => void;
|
|
5
|
+
/** File types the picker offers, as the input's `accept` (`.csv,text/csv`). */
|
|
6
|
+
accept?: string;
|
|
7
|
+
/** What the field takes, for screen readers and the empty field ("CSV file"). */
|
|
8
|
+
label: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A drop zone with a Choose button; the chosen file's name and size, and × to clear it. */
|
|
13
|
+
export declare function LumenFileField({ file, onFile, accept, label, disabled, className, }: LumenFileFieldProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// A file to bring in (a CSV, a statement): drop it on the field or pick it. The app holds the
|
|
3
|
+
// chosen file, so clearing the field after an upload is the app setting `file` back to null.
|
|
4
|
+
import { FileUpIcon, XIcon } from 'lucide-react';
|
|
5
|
+
import { useId, useRef, useState } from 'react';
|
|
6
|
+
import { IconButton } from './icon-button.js';
|
|
7
|
+
import { cn } from './lib/utils.js';
|
|
8
|
+
import { Button } from './ui/button.js';
|
|
9
|
+
/** A drop zone with a Choose button; the chosen file's name and size, and × to clear it. */
|
|
10
|
+
export function LumenFileField({ file, onFile, accept, label, disabled, className, }) {
|
|
11
|
+
const id = useId();
|
|
12
|
+
const input = useRef(null);
|
|
13
|
+
const [over, setOver] = useState(false);
|
|
14
|
+
const allowed = (f) => {
|
|
15
|
+
if (!accept)
|
|
16
|
+
return true;
|
|
17
|
+
const name = f.name.toLowerCase();
|
|
18
|
+
return accept.split(',').some((a) => {
|
|
19
|
+
const t = a.trim().toLowerCase();
|
|
20
|
+
if (t.startsWith('.'))
|
|
21
|
+
return name.endsWith(t);
|
|
22
|
+
if (t.endsWith('/*'))
|
|
23
|
+
return f.type.startsWith(t.slice(0, -1));
|
|
24
|
+
return f.type === t;
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
const drop = (e) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
setOver(false);
|
|
30
|
+
const f = e.dataTransfer.files[0];
|
|
31
|
+
if (!disabled && f && allowed(f))
|
|
32
|
+
onFile(f);
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs("div", { onDragOver: (e) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
if (!disabled)
|
|
37
|
+
setOver(true);
|
|
38
|
+
}, onDragLeave: () => setOver(false), onDrop: drop, className: cn('flex min-w-0 items-center gap-2 rounded-md border border-dashed px-3 py-2 text-sm', over && 'border-primary bg-primary/5', disabled && 'opacity-50', className), children: [_jsx(FileUpIcon, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }), file ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 flex-1 truncate", title: file.name, children: file.name }), _jsx("span", { className: "num shrink-0 text-xs text-muted-foreground", children: fileSize(file.size) }), _jsx(IconButton, { icon: XIcon, size: "icon-xs", label: "Clear file", disabled: disabled, onClick: () => onFile(null) })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: "Drop a file or choose one" }), _jsx(Button, { type: "button", size: "sm", variant: "outline", disabled: disabled, onClick: () => input.current?.click(), children: "Choose" })] })), _jsx("input", { ref: input, id: id, type: "file", accept: accept, "aria-label": label, disabled: disabled, className: "sr-only", onChange: (e) => {
|
|
39
|
+
const f = e.target.files?.[0] ?? null;
|
|
40
|
+
// Emptied at once, so picking the same file again still reads it.
|
|
41
|
+
e.target.value = '';
|
|
42
|
+
if (f)
|
|
43
|
+
onFile(f);
|
|
44
|
+
} })] }));
|
|
45
|
+
}
|
|
46
|
+
const fileSize = (n) => n < 1024
|
|
47
|
+
? `${n} B`
|
|
48
|
+
: n < 1024 ** 2
|
|
49
|
+
? `${(n / 1024).toFixed(0)} KB`
|
|
50
|
+
: `${(n / 1024 ** 2).toFixed(1)} MB`;
|
package/dist/lumen-layout.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface LumenLayoutCard {
|
|
|
26
26
|
rows?: number;
|
|
27
27
|
/** Buttons or links in the card's header, before its ↻ ("All jobs"). */
|
|
28
28
|
tools?: ReactNode;
|
|
29
|
+
/** false: the card is its body alone, no title row (Edit layout still shows it). */
|
|
30
|
+
header?: boolean;
|
|
29
31
|
/** ⓘ How to read: a few lines on what the card shows and what to look for. */
|
|
30
32
|
help?: ReactNode;
|
|
31
33
|
/** What ✦ Ask the assistant adds to its question (the record to read); false: no ✦ on this card. */
|
package/dist/lumen-layout.js
CHANGED
|
@@ -62,7 +62,7 @@ function CodeCard({ card, inScroll, controls, }) {
|
|
|
62
62
|
}
|
|
63
63
|
: undefined);
|
|
64
64
|
const children = typeof card.children === 'function' ? card.children(follows) : card.children;
|
|
65
|
-
return (_jsx(LumenWidget, { id: card.id, kind: null, style: card.kind === 'table' ? 'table' : undefined, icon: card.icon ?? (card.kind === 'table' ? TableIcon : undefined), title: card.title, controls: controls, load: card.load, rows: card.rows, tools: card.tools, help: card.help, assistant: card.assistant, link: link, pad: card.kind !== 'table', pinned: inScroll && card.kind === 'table', children: card.load ? (_jsx(LumenStates, { load: card.load, kind: card.kind === 'table' ? 'table' : 'widget', skeleton: card.skeleton, children: children })) : (children) }));
|
|
65
|
+
return (_jsx(LumenWidget, { id: card.id, kind: null, style: card.kind === 'table' ? 'table' : undefined, icon: card.icon ?? (card.kind === 'table' ? TableIcon : undefined), title: card.title, controls: controls, shown: card.header === false ? { header: false } : undefined, load: card.load, rows: card.rows, tools: card.tools, help: card.help, assistant: card.assistant, link: link, pad: card.kind !== 'table', pinned: inScroll && card.kind === 'table', children: card.load ? (_jsx(LumenStates, { load: card.load, kind: card.kind === 'table' ? 'table' : 'widget', skeleton: card.skeleton, children: children })) : (children) }));
|
|
66
66
|
}
|
|
67
67
|
/** The chips of its colour a code card follows: those on the keys it `reads`. */
|
|
68
68
|
function useCardFollows(card) {
|
|
@@ -50,13 +50,44 @@ export declare function StackedColumns({ rows, x, parts, label, height, tick, fm
|
|
|
50
50
|
/** The parts' names under the columns. */
|
|
51
51
|
legend?: boolean;
|
|
52
52
|
}): import("react").JSX.Element;
|
|
53
|
+
/** Lines over time (visits, sign-ups per day), each its own colour; a line with `right` reads off a
|
|
54
|
+
* second axis on the right, for a count far smaller than the others. */
|
|
55
|
+
export declare function TimeLines({ rows, x, lines, label, height, tick, fmt, legend, }: {
|
|
56
|
+
/** One row per period, oldest first: the `x` field plus a number (or null for no data) for
|
|
57
|
+
* each line's key. */
|
|
58
|
+
rows: Record<string, number | string | null>[];
|
|
59
|
+
/** The field that names each period (a date, a month). */
|
|
60
|
+
x: string;
|
|
61
|
+
lines: {
|
|
62
|
+
name: string;
|
|
63
|
+
key: string;
|
|
64
|
+
fill: string;
|
|
65
|
+
right?: boolean;
|
|
66
|
+
}[];
|
|
67
|
+
/** What the chart shows, for screen readers. */
|
|
68
|
+
label: string;
|
|
69
|
+
height?: number;
|
|
70
|
+
/** How a period's name reads under the chart (a date as `10-22`). */
|
|
71
|
+
tick?: (v: string) => string;
|
|
72
|
+
fmt?: Fmt;
|
|
73
|
+
/** The lines' names under the chart. */
|
|
74
|
+
legend?: boolean;
|
|
75
|
+
}): import("react").JSX.Element;
|
|
53
76
|
/** Horizontal bars as a ranked list: a full track per row, each bar its own colour. */
|
|
54
|
-
export declare function RankedBars({ id, items, signed, fmt, share, }: {
|
|
55
|
-
|
|
77
|
+
export declare function RankedBars({ id, items, signed, fmt, share, empty, label, }: {
|
|
78
|
+
/** Keeps the bars' gradients apart when one page draws several; one is made when left out. */
|
|
79
|
+
id?: string;
|
|
56
80
|
items: Item[];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
/** Bars can go below zero (a loss); otherwise they start at zero. */
|
|
82
|
+
signed?: boolean;
|
|
83
|
+
fmt?: Fmt;
|
|
84
|
+
/** The small figure before each value: its % of all bars (`total`), of the first bar
|
|
85
|
+
* (`first`, for a funnel), the app's own text, or `false` for none. */
|
|
86
|
+
share?: 'total' | 'first' | false | ((v: number) => string);
|
|
87
|
+
/** Shown in place of the bars when there are none. */
|
|
88
|
+
empty?: ReactNode;
|
|
89
|
+
/** What the bars show, for screen readers. */
|
|
90
|
+
label?: string;
|
|
60
91
|
}): import("react").JSX.Element;
|
|
61
92
|
/** A donut with its total in the middle; hovering a slice or its legend line shows its share. */
|
|
62
93
|
export declare function Donut({ items, total, fmt, share, caption, legend, }: {
|
|
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
// Chart widgets: each leads with a headline number; bars are a ranked
|
|
3
3
|
// list on full tracks (or split into parts by a text field), the donut carries its total in the
|
|
4
4
|
// middle, allocation names its wide slices. Flat marks, no glow. Recharts only.
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
import { Bar, BarChart, CartesianGrid, Cell, Label, LabelList, Pie, PieChart, Rectangle, XAxis, YAxis, usePlotArea, } from 'recharts';
|
|
5
|
+
import { useId, useState } from 'react';
|
|
6
|
+
import { Bar, BarChart, CartesianGrid, Cell, Label, LabelList, Line, LineChart, Pie, PieChart, Rectangle, XAxis, YAxis, usePlotArea, } from 'recharts';
|
|
7
7
|
import { cn } from './lib/utils.js';
|
|
8
8
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from './ui/chart.js';
|
|
9
9
|
/** Parts a split bar shows on their own; the rest fold into Other. */
|
|
@@ -48,13 +48,39 @@ export function StackedColumns({ rows, x, parts, label, height = 220, tick = Str
|
|
|
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;
|
|
49
49
|
}, formatter: (v, name) => (_jsxs("span", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "size-2 shrink-0 rounded-sm", style: { background: config[String(name)]?.color } }), _jsx("span", { children: config[String(name)]?.label }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(Number(v)) })] })) }) }), parts.map((p) => (_jsx(Bar, { dataKey: p.key, stackId: "a", fill: p.fill, isAnimationActive: false, shape: (b) => (_jsx(Rectangle, { ...b, radius: top(b.payload) === p.key ? CAP : 0 })) }, p.key)))] }) }), legend && (_jsx("ul", { className: "flex flex-wrap justify-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: parts.map((p) => (_jsxs("li", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-sm", style: { background: p.fill } }), p.name] }, p.key))) }))] }));
|
|
50
50
|
}
|
|
51
|
+
/** Lines over time (visits, sign-ups per day), each its own colour; a line with `right` reads off a
|
|
52
|
+
* second axis on the right, for a count far smaller than the others. */
|
|
53
|
+
export function TimeLines({ rows, x, lines, label, height = 220, tick = String, fmt = (v) => v.toLocaleString(), legend = true, }) {
|
|
54
|
+
const config = Object.fromEntries(lines.map((l) => [l.key, { label: l.name, color: l.fill }]));
|
|
55
|
+
const axis = (right) => {
|
|
56
|
+
const keys = lines.filter((l) => !!l.right === right).map((l) => l.key);
|
|
57
|
+
return niceTicks(Math.max(0, ...rows.flatMap((r) => keys.map((k) => Number(r[k]) || 0))));
|
|
58
|
+
};
|
|
59
|
+
const left = axis(false);
|
|
60
|
+
const hasRight = lines.some((l) => l.right);
|
|
61
|
+
const right = hasRight ? axis(true) : [];
|
|
62
|
+
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(LineChart, { 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, { yAxisId: "left", tickLine: false, axisLine: false, domain: [0, left.at(-1) ?? 'auto'], ticks: left, width: axisWidth(left.map(fmt)), tickFormatter: (v) => fmt(Number(v)) }), hasRight && (_jsx(YAxis, { yAxisId: "right", orientation: "right", tickLine: false, axisLine: false, domain: [0, right.at(-1) ?? 'auto'], ticks: right, width: axisWidth(right.map(fmt)), tickFormatter: (v) => fmt(Number(v)) })), _jsx(ChartTooltip, { cursor: { stroke: 'var(--border)' }, content: _jsx(ChartTooltipContent, { labelFormatter: (_, payload) => {
|
|
63
|
+
const r = payload?.[0]?.payload;
|
|
64
|
+
return r ? tick(String(r[x])) : null;
|
|
65
|
+
}, formatter: (v, name) => (_jsxs("span", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "size-2 shrink-0 rounded-sm", style: { background: config[String(name)]?.color } }), _jsx("span", { children: config[String(name)]?.label }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(Number(v)) })] })) }) }), lines.map((l) => (_jsx(Line, { dataKey: l.key, yAxisId: l.right ? 'right' : 'left', type: "monotone", stroke: l.fill, strokeWidth: 2, dot: false, connectNulls: false, isAnimationActive: false }, l.key)))] }) }), legend && (_jsx("ul", { className: "flex flex-wrap justify-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: lines.map((l) => (_jsxs("li", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "h-0.5 w-3 rounded-full", style: { background: l.fill } }), l.name] }, l.key))) }))] }));
|
|
66
|
+
}
|
|
51
67
|
/** Horizontal bars as a ranked list: a full track per row, each bar its own colour. */
|
|
52
|
-
export function RankedBars({ id, items, signed, fmt, share, }) {
|
|
68
|
+
export function RankedBars({ id, items, signed = false, fmt = (v) => v.toLocaleString(), share = 'total', empty = 'None yet.', label, }) {
|
|
69
|
+
const ownId = useId();
|
|
70
|
+
const gid = id ?? ownId;
|
|
71
|
+
if (!items.length)
|
|
72
|
+
return _jsx("p", { className: "p-3 text-sm text-muted-foreground", children: empty });
|
|
73
|
+
const base = share === 'first' ? (items[0]?.v ?? 0) : items.reduce((t, i) => t + Math.abs(i.v), 0);
|
|
74
|
+
const shareText = typeof share === 'function'
|
|
75
|
+
? share
|
|
76
|
+
: share === false
|
|
77
|
+
? () => ''
|
|
78
|
+
: (v) => (base ? `${Math.round((v / base) * 100)}%` : '');
|
|
53
79
|
const ROW = 34;
|
|
54
80
|
const lo = Math.min(0, ...items.map((i) => i.v));
|
|
55
81
|
const hi = Math.max(0, ...items.map((i) => i.v));
|
|
56
82
|
const config = { v: { label: 'Value', color: 'var(--chart-1)' } };
|
|
57
|
-
return (_jsx(ChartContainer, { config: config, className: "aspect-auto w-full", style: { height: items.length * ROW + 14 }, children: _jsxs(BarChart, { data: items, layout: "vertical", margin: { left: 0, right: 0, top: 14, bottom: 0 }, barCategoryGap: 0, children: [_jsx("defs", { children: items.map((it, i) => (_jsxs("linearGradient", { id: `rb-${
|
|
83
|
+
return (_jsx(ChartContainer, { config: config, className: "aspect-auto w-full", style: { height: items.length * ROW + 14 }, role: label ? 'img' : undefined, "aria-label": label, children: _jsxs(BarChart, { data: items, layout: "vertical", margin: { left: 0, right: 0, top: 14, bottom: 0 }, barCategoryGap: 0, children: [_jsx("defs", { children: items.map((it, i) => (_jsxs("linearGradient", { id: `rb-${gid}-${i}`, x1: "0", x2: "1", y1: "0", y2: "0", children: [_jsx("stop", { offset: "0%", stopColor: it.fill, stopOpacity: it.v < 0 ? 1 : 0.35 }), _jsx("stop", { offset: "100%", stopColor: it.fill, stopOpacity: it.v < 0 ? 0.35 : 1 })] }, it.name))) }), _jsx(XAxis, { type: "number", hide: true, domain: signed ? [lo, hi] : [0, hi] }), _jsx(YAxis, { type: "category", dataKey: "name", hide: true }), _jsxs(Bar, { dataKey: "v", barSize: 10, radius: 5, background: { fill: 'var(--muted)', radius: 5 }, isAnimationActive: false, children: [items.map((it, i) => (_jsx(Cell, { fill: `url(#rb-${gid}-${i})`, style: { color: it.fill } }, it.name))), _jsx(LabelList, { dataKey: "v", content: _jsx(RankLabel, { items: items, fmt: fmt, share: shareText }) })] })] }) }));
|
|
58
84
|
}
|
|
59
85
|
/** A donut with its total in the middle; hovering a slice or its legend line shows its share. */
|
|
60
86
|
export function Donut({ items, total, fmt, share, caption, legend = true, }) {
|
package/dist/lumen-widget.js
CHANGED
|
@@ -329,7 +329,8 @@ export function LumenWidget({ id, kind, icon, title, note, controls, tools, filt
|
|
|
329
329
|
const layout = !!controls?.layout;
|
|
330
330
|
// A small card (one number) shows only its title until the page is in Edit layout.
|
|
331
331
|
const bare = compact && !layout;
|
|
332
|
-
|
|
332
|
+
// A page's own card (kind null) has every control unless `shown` turns one off (its header).
|
|
333
|
+
const on = (k) => kind === null ? shown?.[k] !== false : controlOn({ kind, style, controls: shown }, k);
|
|
333
334
|
// A page's own card (kind null) says it is a table when it draws one (`style` table).
|
|
334
335
|
const styleId = kind
|
|
335
336
|
? widgetStyleId({ kind, style })
|
package/dist/overview.js
CHANGED
|
@@ -48,7 +48,7 @@ function Ring({ ring, size }) {
|
|
|
48
48
|
if (!viewBox || !('cx' in viewBox))
|
|
49
49
|
return null;
|
|
50
50
|
const many = ring.rings.length > 1;
|
|
51
|
-
return (_jsxs("text", { x: viewBox.cx, y: viewBox.cy, textAnchor: "middle", children: [_jsx("tspan", { x: viewBox.cx, dy: many ? '-0.1em' : '0.35em', className: "fill-foreground font-
|
|
51
|
+
return (_jsxs("text", { x: viewBox.cx, y: viewBox.cy, textAnchor: "middle", children: [_jsx("tspan", { x: viewBox.cx, dy: many ? '-0.1em' : '0.35em', className: "fill-foreground font-num text-[11px]", children: r.text }), many && (_jsx("tspan", { x: viewBox.cx, dy: "1.2em", className: "fill-muted-foreground text-[9px]", children: r.label }))] }));
|
|
52
52
|
} })] }) }) }, r.label));
|
|
53
53
|
}), ring.detail && _jsx("div", { className: "min-w-36 flex-1", children: ring.detail })] }));
|
|
54
54
|
}
|
package/dist/tasks.js
CHANGED
|
@@ -183,7 +183,7 @@ export function TasksButton() {
|
|
|
183
183
|
]
|
|
184
184
|
.filter(Boolean)
|
|
185
185
|
.join(', ');
|
|
186
|
-
const trigger = (_jsxs(Button, { variant: "ghost", size: list.active ? 'sm' : 'icon-sm', className: cn('relative shrink-0', list.active > 0 && 'gap-1.5 px-2'), "aria-label": label, "data-slot": "tasks-button", children: [list.active > 0 ? _jsx(Ring, { share: share }) : _jsx(ListChecksIcon, {}), list.active > 0 && _jsx("span", { className: "font-
|
|
186
|
+
const trigger = (_jsxs(Button, { variant: "ghost", size: list.active ? 'sm' : 'icon-sm', className: cn('relative shrink-0', list.active > 0 && 'gap-1.5 px-2'), "aria-label": label, "data-slot": "tasks-button", children: [list.active > 0 ? _jsx(Ring, { share: share }) : _jsx(ListChecksIcon, {}), list.active > 0 && _jsx("span", { className: "font-num text-xs tabular-nums", children: list.active }), list.unseen > 0 && (_jsx("span", { "aria-hidden": true, "data-slot": "tasks-unseen", className: "absolute top-1 right-0.5 size-1.5 rounded-full bg-edge" }))] }));
|
|
187
187
|
const body = _jsx(TaskPanel, { list: list, onClose: () => setOpen(false) });
|
|
188
188
|
if (mobile) {
|
|
189
189
|
return (_jsxs(Sheet, { open: open, onOpenChange: onOpen, children: [_jsx(SheetTrigger, { asChild: true, children: trigger }), _jsxs(SheetContent, { side: "bottom", className: "max-h-[75svh] gap-0 rounded-t-2xl p-0", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs(SheetHeader, { className: "flex-row items-baseline gap-2 border-b px-4 py-3", children: [_jsx(SheetTitle, { children: "Tasks" }), _jsx(SheetDescription, { children: "last 7 days" }), _jsxs("span", { className: "mr-8 ml-auto flex items-center gap-2 self-center", children: [list.active > 0 && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [list.active, " running"] })), _jsx(ClearAll, { list: list })] })] }), body] })] }));
|
|
@@ -223,12 +223,12 @@ function TaskRow({ task: t, onClose }) {
|
|
|
223
223
|
return (_jsxs("div", { "data-slot": "task-row", "data-state": t.state, className: cn('flex flex-col gap-1.5 border-t px-3.5 py-2.5', unseen && 'bg-muted/40'), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [unseen && _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-edge" }), apps && (_jsx(Badge, { variant: "outline", className: "shrink-0 text-[11px]", children: apps[t.app] ?? t.app })), _jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold", title: t.title, children: t.title }), isActive(t) && t.cancellable ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: XIcon, label: "Cancel task", size: "icon-xs", className: "text-muted-foreground hover:text-destructive", onClick: () => setConfirm(true) }), _jsx(LumenConfirm, { open: confirm, onOpenChange: setConfirm, title: `Cancel ${t.title}?`, description: "It stops at its next step; what it finished stays.", action: "Cancel task", onConfirm: () => void tasks?.cancel(t.id) })] })) : t.link ? (_jsx(IconButton, { icon: isActive(t) ? ChevronRightIcon : ArrowUpRightIcon, label: "Open", size: "icon-xs", onClick: () => {
|
|
224
224
|
onClose();
|
|
225
225
|
tasks?.open(t);
|
|
226
|
-
} })) : null, !isActive(t) && (_jsx(IconButton, { icon: XIcon, label: "Clear", size: "icon-xs", className: "text-muted-foreground hover:text-destructive", onClick: () => void tasks?.clear(t.id) }))] }), t.state === 'running' && t.total ? (_jsxs(_Fragment, { children: [_jsx(Progress, { value: pct ?? 0 }), _jsxs("div", { className: "flex gap-2.5 font-
|
|
226
|
+
} })) : null, !isActive(t) && (_jsx(IconButton, { icon: XIcon, label: "Clear", size: "icon-xs", className: "text-muted-foreground hover:text-destructive", onClick: () => void tasks?.clear(t.id) }))] }), t.state === 'running' && t.total ? (_jsxs(_Fragment, { children: [_jsx(Progress, { value: pct ?? 0 }), _jsxs("div", { className: "flex gap-2.5 font-num text-[11.5px] text-muted-foreground tabular-nums", children: [_jsxs("span", { children: [done.toLocaleString('en-US'), " / ", t.total.toLocaleString('en-US'), t.unit ? ` ${t.unit}` : ''] }), _jsxs("span", { children: [pct, "%"] }), _jsx("span", { className: "ml-auto", children: timeLeft(t, now) })] })] })) : t.state === 'running' ? (_jsxs(_Fragment, { children: [_jsx(Progress, {}), _jsxs("div", { className: "font-num text-[11.5px] text-muted-foreground tabular-nums", children: [done ? `${done.toLocaleString('en-US')}${t.unit ? ` ${t.unit}` : ''} · ` : '', "running ", took(Date.parse(t.started_at ?? t.created_at), now)] })] })) : t.state === 'queued' ? (_jsx(Line, { icon: _jsx(Clock3Icon, { className: "size-3" }), children: "Queued" })) : t.state === 'done' ? (_jsx(Line, { icon: _jsx(CheckIcon, { className: "size-3 text-gain", strokeWidth: 2.5 }), mono: true, children: [t.summary, tookLine(t), ago(t.finished_at, now)].filter(Boolean).join(' · ') })) : (_jsx(Line, { icon: _jsx(CircleAlertIcon, { className: "size-3" }), className: t.state === 'failed' ? 'text-loss' : undefined, children: [t.state === 'cancelled' ? 'Cancelled' : (t.error ?? 'Failed'), ago(t.finished_at, now)]
|
|
227
227
|
.filter(Boolean)
|
|
228
228
|
.join(' · ') }))] }));
|
|
229
229
|
}
|
|
230
230
|
function Line({ icon, mono, className, children, }) {
|
|
231
|
-
return (_jsxs("div", { className: cn('flex min-w-0 items-center gap-1.5 text-[11.5px] text-muted-foreground', mono && 'font-
|
|
231
|
+
return (_jsxs("div", { className: cn('flex min-w-0 items-center gap-1.5 text-[11.5px] text-muted-foreground', mono && 'font-num tabular-nums', className), children: [_jsx("span", { className: "shrink-0", children: icon }), _jsx("span", { className: "min-w-0 truncate", children: children })] }));
|
|
232
232
|
}
|
|
233
233
|
/** Ticks once a second while a task runs, so "left" and "running" count. */
|
|
234
234
|
function useNow(live) {
|
package/dist/theme.css
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
--font-heading: var(--font-sans);
|
|
17
17
|
--font-sans: var(--app-font);
|
|
18
18
|
--font-mono: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
|
19
|
+
/* `font-code`: code, keys and one-time codes. Always mono, whatever numbers use. */
|
|
20
|
+
--font-code: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
|
21
|
+
/* `font-num` (and `.num`): numbers in tables, tiles and charts. */
|
|
22
|
+
--font-num: var(--num-font);
|
|
19
23
|
--color-gain: var(--gain);
|
|
20
24
|
--color-kind-declarative: var(--kind-declarative);
|
|
21
25
|
--color-kind-script: var(--kind-script);
|
|
@@ -90,6 +94,9 @@
|
|
|
90
94
|
--shell-max-width: 1400px;
|
|
91
95
|
/* The text font, picked in Settings ▸ Appearance (`data-font` on <html>, prefs.ts). */
|
|
92
96
|
--app-font: "Manrope Variable", Manrope, ui-sans-serif, system-ui, sans-serif;
|
|
97
|
+
/* The numbers font. An app that wants its numbers in the text font sets
|
|
98
|
+
`--num-font: var(--app-font)`; tabular figures keep them lined up either way. */
|
|
99
|
+
--num-font: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
|
93
100
|
/* Compact density, the only density. */
|
|
94
101
|
--card-pad: 12px;
|
|
95
102
|
--row-h: 28px;
|
|
@@ -455,7 +462,7 @@
|
|
|
455
462
|
}
|
|
456
463
|
|
|
457
464
|
.num {
|
|
458
|
-
font-family: var(--font
|
|
465
|
+
font-family: var(--num-font);
|
|
459
466
|
font-variant-numeric: tabular-nums;
|
|
460
467
|
}
|
|
461
468
|
}
|
|
@@ -595,7 +602,7 @@ body[data-spotlight="nav"] [data-spotlit] {
|
|
|
595
602
|
margin: 0;
|
|
596
603
|
padding: 0.5rem 0.75rem;
|
|
597
604
|
background: transparent !important;
|
|
598
|
-
font-family: var(--font-
|
|
605
|
+
font-family: var(--font-code);
|
|
599
606
|
counter-reset: line;
|
|
600
607
|
}
|
|
601
608
|
.lumen-code.lumen-code-numbers pre {
|
|
@@ -626,7 +633,7 @@ body[data-spotlight="nav"] [data-spotlit] {
|
|
|
626
633
|
}
|
|
627
634
|
.lumen-code-editor .cm-editor {
|
|
628
635
|
height: 100%;
|
|
629
|
-
font-family: var(--font-
|
|
636
|
+
font-family: var(--font-code);
|
|
630
637
|
}
|
|
631
638
|
.lumen-code-editor .cm-editor.cm-focused {
|
|
632
639
|
outline: none;
|
package/dist/ui/chart.js
CHANGED
|
@@ -75,7 +75,7 @@ function ChartTooltipContent({ active, payload, className, indicator = 'dot', hi
|
|
|
75
75
|
}), style: {
|
|
76
76
|
'--color-bg': indicatorColor,
|
|
77
77
|
'--color-border': indicatorColor,
|
|
78
|
-
} }))), _jsxs("div", { className: cn('flex flex-1 justify-between leading-none', nestLabel ? 'items-end' : 'items-center'), children: [_jsxs("div", { className: "grid gap-1.5", children: [nestLabel ? tooltipLabel : null, _jsx("span", { className: "text-muted-foreground", children: itemConfig?.label ?? item.name })] }), item.value != null && (_jsx("span", { className: "font-
|
|
78
|
+
} }))), _jsxs("div", { className: cn('flex flex-1 justify-between leading-none', nestLabel ? 'items-end' : 'items-center'), children: [_jsxs("div", { className: "grid gap-1.5", children: [nestLabel ? tooltipLabel : null, _jsx("span", { className: "text-muted-foreground", children: itemConfig?.label ?? item.name })] }), item.value != null && (_jsx("span", { className: "font-num font-medium text-foreground tabular-nums", children: typeof item.value === 'number'
|
|
79
79
|
? item.value.toLocaleString()
|
|
80
80
|
: String(item.value) }))] })] })) }, index));
|
|
81
81
|
}) })] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@codemirror/lang-javascript": "6.2.5",
|
|
46
46
|
"@codemirror/lang-json": "6.0.2",
|
|
47
47
|
"@codemirror/lang-sql": "6.10.0",
|
|
48
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
48
|
+
"@columenlabs/lumen-ui-types": "0.1.74",
|
|
49
49
|
"@dnd-kit/core": "6.3.1",
|
|
50
50
|
"@dnd-kit/sortable": "10.0.0",
|
|
51
51
|
"@dnd-kit/utilities": "3.2.2",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@fontsource-variable/inter": "^5.3.0",
|
|
54
54
|
"@fontsource-variable/jetbrains-mono": "^5.3.0",
|
|
55
55
|
"@fontsource-variable/manrope": "^5.3.0",
|
|
56
|
+
"@marsidev/react-turnstile": "1.3.1",
|
|
56
57
|
"@tanstack/react-table": "^9.2.4",
|
|
57
58
|
"@uiw/codemirror-theme-github": "4.25.12",
|
|
58
59
|
"@uiw/react-codemirror": "4.25.12",
|