@columenlabs/lumen-ui 0.1.25 → 0.1.27
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 +82 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kit-config.d.ts +3 -0
- package/dist/prefs.d.ts +2 -0
- package/dist/prefs.js +17 -9
- package/dist/schedules.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# @columenlabs/lumen-ui
|
|
2
2
|
|
|
3
|
-
Lumen's components: the app frame
|
|
3
|
+
Lumen's React components for data apps: the app frame, pages and views over your rows, widgets, charts, drawers and pickers, all on one theme. You give it data and a view config; it draws the screen. Built on [shadcn/ui](https://ui.shadcn.com) (Radix UI, Base UI, Tailwind CSS 4). Its view model is [`@columenlabs/lumen-ui-types`](https://www.npmjs.com/package/@columenlabs/lumen-ui-types).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- Docs and live examples: https://lumen.columenlabs.com/docs/
|
|
6
|
+
- For agents: https://lumen.columenlabs.com/llms.txt
|
|
7
|
+
- Source: https://github.com/columenlabs/lumen (`packages/ui`). MIT.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Pin both packages to the same exact version.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add @columenlabs/lumen-ui @columenlabs/lumen-ui-types
|
|
15
|
+
bun add react react-dom @tanstack/react-query @tanstack/react-router # peers
|
|
16
|
+
bun add -d tailwindcss @tailwindcss/vite tw-animate-css shadcn
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Set up
|
|
20
|
+
|
|
21
|
+
`src/index.css`:
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
@import "tailwindcss";
|
|
25
|
+
@import "tw-animate-css";
|
|
26
|
+
@import "shadcn/tailwind.css";
|
|
27
|
+
@import "@columenlabs/lumen-ui/theme.css";
|
|
28
|
+
@source "../node_modules/@columenlabs/lumen-ui/dist";
|
|
29
|
+
|
|
30
|
+
/* your accent, per app (LumenShell sets data-app on <html>) */
|
|
31
|
+
:root[data-app="myapp"] { --primary: oklch(0.55 0.15 160); }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
At start, before anything renders, tell the kit about your app. The kit names no product: your names and data come in here.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { configureLumen } from '@columenlabs/lumen-ui';
|
|
38
|
+
import { registerDomains } from '@columenlabs/lumen-ui-types/registry';
|
|
39
|
+
|
|
40
|
+
configureLumen({ prefsCookie: 'myapp_prefs' }); // where display preferences live
|
|
41
|
+
registerDomains({ /* your joins, sources and domain keys */ });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Wrap your routes in the shell:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { LumenShell } from '@columenlabs/lumen-ui';
|
|
48
|
+
|
|
49
|
+
<LumenShell app="myapp" appLabel="My app" brand={<Logo />} nav={nav} user={{ email, name }}>
|
|
50
|
+
<Outlet />
|
|
51
|
+
</LumenShell>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Your own parts go in `LumenShell`'s slots: `brand`, `headerActions`, `panel` (a side panel such as an assistant), `wrap` (providers around the page), `shared`, `profile` (the profile menu) and `statusEnd` (the right end of the status bar).
|
|
55
|
+
|
|
56
|
+
Use `createDeskQueryClient()` for the app's TanStack Query client: data stays fresh 5 minutes and cached 30.
|
|
57
|
+
|
|
58
|
+
## What's inside
|
|
59
|
+
|
|
60
|
+
**Frame.** `LumenShell` (sidebar with icons-only collapse and a drawer on phone, header, status bar, Settings, profile menu, ⌘K Find), `LumenAppSwitcher`, `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`.
|
|
61
|
+
|
|
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).
|
|
63
|
+
|
|
64
|
+
**Charts.** `LumenChart` (one or more symbols' prices, with indicators), `LumenEChart`, `LumenGeoMap`, growth and price charts, `StackedBars`, `StackedColumns`, `RankedBars`.
|
|
65
|
+
|
|
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`.
|
|
67
|
+
|
|
68
|
+
**Primitives.** The shadcn parts under `@columenlabs/lumen-ui/ui/*` (`ui/button`, `ui/badge`, `ui/input`, ...) and `theme.css` (light and dark tokens).
|
|
69
|
+
|
|
70
|
+
Every export, its props and an example are on the [docs](https://lumen.columenlabs.com/docs/) and in [`llms-full.txt`](https://lumen.columenlabs.com/llms-full.txt).
|
|
71
|
+
|
|
72
|
+
## Rules the kit follows, and apps should too
|
|
73
|
+
|
|
74
|
+
- Pickers are `LumenSelector`, side panels `LumenDrawer`, tabs `LumenTabs`, destructive confirms `LumenConfirm`; reach for the raw `ui/*` part only when no Lumen part fits.
|
|
75
|
+
- Every card body sits in `LumenBoundary`, so one crash never blanks a page.
|
|
76
|
+
- Numbers use tabular figures and a true minus `−`; a gain or loss always shows a sign or arrow.
|
|
77
|
+
- A Save blocked by its form's checks is a `SaveButton` that lists each issue, never a bare `disabled`.
|
|
78
|
+
|
|
79
|
+
## Notices
|
|
80
|
+
|
|
81
|
+
Charts use TradingView Lightweight Charts™ with its logo off. Please link to https://www.tradingview.com/ somewhere in your app (see NOTICE).
|
|
82
|
+
|
|
83
|
+
## Versions
|
|
84
|
+
|
|
85
|
+
0.x: any minor may break things until 1.0. Every merge to Lumen's `main` publishes the next patch of every Lumen package; see the [releases](https://github.com/columenlabs/lumen/releases).
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { type ChartBand, type ChartMarker, GrowthChart, KEY_DATE_KINDS, KeyDateC
|
|
|
35
35
|
export { FoldGroup, NavGroup, type NavItem, type SectionItem, type SectionSubItem, SidebarSection, } from './nav.js';
|
|
36
36
|
export { type OverviewPageProps, type OverviewStage, type OverviewTone, type OverviewWidget, OverviewList, OverviewPage, ToneDot, } from './overview.js';
|
|
37
37
|
export { type Crumb, IconButton, type IconButtonProps, Page, PageBody, PageHeader, type PageHeaderProps, } from './page.js';
|
|
38
|
-
export { DEFAULT_PREFS, type Prefs, TIME_ZONES, type Theme, type TimeZoneId, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
|
|
38
|
+
export { DEFAULT_PREFS, deviceTimeZone, type Prefs, TIME_ZONES, type Theme, type TimeZoneId, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
|
|
39
39
|
export { KIT, type KitConfig, configureLumen } from './kit-config.js';
|
|
40
40
|
export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, type KeyTestResult, TestBadge, errorText, useClock, } from './integrations.js';
|
|
41
41
|
export { LumenPager } from './pager.js';
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export { GrowthChart, KEY_DATE_KINDS, KeyDateChips, PALETTE, benchColour, keyDat
|
|
|
42
42
|
export { FoldGroup, NavGroup, SidebarSection, } from './nav.js';
|
|
43
43
|
export { OverviewList, OverviewPage, ToneDot, } from './overview.js';
|
|
44
44
|
export { IconButton, Page, PageBody, PageHeader, } from './page.js';
|
|
45
|
-
export { DEFAULT_PREFS, TIME_ZONES, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
|
|
45
|
+
export { DEFAULT_PREFS, deviceTimeZone, TIME_ZONES, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
|
|
46
46
|
export { KIT, configureLumen } from './kit-config.js';
|
|
47
47
|
export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, TestBadge, errorText, useClock, } from './integrations.js';
|
|
48
48
|
export { LumenPager } from './pager.js';
|
package/dist/kit-config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Prefs } from './prefs.js';
|
|
1
2
|
export interface KitConfig {
|
|
2
3
|
/** The cookie that keeps each person's display preferences. */
|
|
3
4
|
prefsCookie: string;
|
|
@@ -5,6 +6,8 @@ export interface KitConfig {
|
|
|
5
6
|
cookieDomain?: (host: string) => string | undefined;
|
|
6
7
|
/** What a market widget says where the app has no live feed. */
|
|
7
8
|
marketOff: string;
|
|
9
|
+
/** The app's own defaults for preferences a person has not set (theme, time zone, ...). */
|
|
10
|
+
defaultPrefs?: Partial<Prefs>;
|
|
8
11
|
}
|
|
9
12
|
export declare const KIT: KitConfig;
|
|
10
13
|
/** Runs `fn` after each `configureLumen` (preferences read again from the app's cookie). */
|
package/dist/prefs.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ export declare const ASSISTANT_WIDTH: {
|
|
|
62
62
|
};
|
|
63
63
|
/** Eastern by default, as an app's zone picker had it (the market's own zone). */
|
|
64
64
|
export declare const DEFAULT_PREFS: Prefs;
|
|
65
|
+
/** The device's own zone when the picker lists it (`America/Los_Angeles`), else undefined. */
|
|
66
|
+
export declare function deviceTimeZone(): TimeZoneId | undefined;
|
|
65
67
|
/** The theme on screen: `system` follows the device. */
|
|
66
68
|
export declare const resolvedTheme: (p?: Prefs) => 'light' | 'dark';
|
|
67
69
|
export declare function setPref<K extends keyof Prefs>(key: K, value: Prefs[K]): void;
|
package/dist/prefs.js
CHANGED
|
@@ -38,6 +38,13 @@ export const DEFAULT_PREFS = {
|
|
|
38
38
|
hideAmounts: false,
|
|
39
39
|
observability: false,
|
|
40
40
|
};
|
|
41
|
+
/** The kit's defaults with the app's own (`configureLumen` `defaultPrefs`) over them. */
|
|
42
|
+
const defaults = () => ({ ...DEFAULT_PREFS, ...KIT.defaultPrefs });
|
|
43
|
+
/** The device's own zone when the picker lists it (`America/Los_Angeles`), else undefined. */
|
|
44
|
+
export function deviceTimeZone() {
|
|
45
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
46
|
+
return TIME_ZONES.find((z) => z.id === zone)?.id;
|
|
47
|
+
}
|
|
41
48
|
const YEAR = 60 * 60 * 24 * 365;
|
|
42
49
|
const listeners = new Set();
|
|
43
50
|
/** The app's shared cookie domain (`configureLumen`); host-only when it names none. */
|
|
@@ -46,26 +53,27 @@ function cookieDomain() {
|
|
|
46
53
|
return domain ? `; domain=${domain}` : '';
|
|
47
54
|
}
|
|
48
55
|
function read() {
|
|
56
|
+
const d = defaults();
|
|
49
57
|
try {
|
|
50
58
|
const raw = document.cookie
|
|
51
59
|
.split('; ')
|
|
52
60
|
.find((c) => c.startsWith(`${KIT.prefsCookie}=`))
|
|
53
61
|
?.slice(KIT.prefsCookie.length + 1);
|
|
54
62
|
if (!raw)
|
|
55
|
-
return
|
|
63
|
+
return d;
|
|
56
64
|
const v = JSON.parse(decodeURIComponent(raw));
|
|
57
65
|
return {
|
|
58
|
-
theme: THEMES.includes(v.theme) ? v.theme :
|
|
59
|
-
glass: v.glass ===
|
|
60
|
-
font: FONTS.find((f) => f.id === v.font)?.id ??
|
|
61
|
-
fontSize: FONT_SIZES.find((f) => f.id === v.fontSize)?.id ??
|
|
62
|
-
timeZone: TIME_ZONES.find((z) => z.id === v.timeZone)?.id ??
|
|
63
|
-
hideAmounts: v.hideAmounts ===
|
|
64
|
-
observability: v.observability ===
|
|
66
|
+
theme: THEMES.includes(v.theme) ? v.theme : d.theme,
|
|
67
|
+
glass: typeof v.glass === 'boolean' ? v.glass : d.glass,
|
|
68
|
+
font: FONTS.find((f) => f.id === v.font)?.id ?? d.font,
|
|
69
|
+
fontSize: FONT_SIZES.find((f) => f.id === v.fontSize)?.id ?? d.fontSize,
|
|
70
|
+
timeZone: TIME_ZONES.find((z) => z.id === v.timeZone)?.id ?? d.timeZone,
|
|
71
|
+
hideAmounts: typeof v.hideAmounts === 'boolean' ? v.hideAmounts : d.hideAmounts,
|
|
72
|
+
observability: typeof v.observability === 'boolean' ? v.observability : d.observability,
|
|
65
73
|
};
|
|
66
74
|
}
|
|
67
75
|
catch {
|
|
68
|
-
return
|
|
76
|
+
return d;
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
let state = typeof document === 'undefined' ? DEFAULT_PREFS : read();
|
package/dist/schedules.js
CHANGED
|
@@ -11,7 +11,7 @@ import { LumenConfirm } from './confirm.js';
|
|
|
11
11
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
12
12
|
import { LumenSelector } from './lumen-selector.js';
|
|
13
13
|
import { IconButton } from './page.js';
|
|
14
|
-
import { usePrefs } from './prefs.js';
|
|
14
|
+
import { timeZoneLabel, usePrefs } from './prefs.js';
|
|
15
15
|
import { useShell } from './shell.js';
|
|
16
16
|
import { Badge } from './ui/badge.js';
|
|
17
17
|
import { Button } from './ui/button.js';
|
|
@@ -183,10 +183,10 @@ export function SchedulesSection({ schedules, error, onSave, onRun, onRuns, rece
|
|
|
183
183
|
return _jsx("p", { className: "text-sm text-loss", children: error });
|
|
184
184
|
if (!schedules)
|
|
185
185
|
return _jsx(Skeleton, { className: "h-48 w-full" });
|
|
186
|
-
return (_jsxs(_Fragment, { children: [isMobile ? (_jsx("div", { className: "grid grid-cols-1 divide-y rounded-lg border", children: schedules.map((s) => (_jsxs("button", { type: "button", className: "grid grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 p-3 text-left hover:bg-muted/50", onClick: () => setOpen(s.id), children: [_jsx("span", { className: "font-medium", children: s.name }), _jsx(OnSwitch, { s: s, onSave: onSave }), _jsxs("span", { className: "col-span-2 text-xs text-muted-foreground", children: [s.kind === 'daily' ? '' : `Every ${everyText(s.every)} · `, when(s)] }), _jsx("span", { className: "col-span-2 text-xs", children: s.waiting ? _jsx("span", { className: "text-warning", children: s.waiting }) : _jsx(Last, { s: s }) })] }, s.id))) })) : (_jsx("div", { className: "overflow-x-auto overflow-y-hidden rounded-lg border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: "On" }), _jsx(TableHead, { children: "Schedule" }), _jsx(TableHead, { children: "When" }), _jsx(TableHead, { children: "Last run" }), onRuns ? _jsx(TableHead, { className: "w-0" }) : null] }) }), _jsx(TableBody, { children: schedules.map((s) => (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => setOpen(s.id), children: [_jsx(TableCell, { children: _jsx(OnSwitch, { s: s, onSave: onSave }) }), _jsx(TableCell, { className: "font-medium", children: s.name }), _jsxs(TableCell, { className: "
|
|
186
|
+
return (_jsxs(_Fragment, { children: [isMobile ? (_jsx("div", { className: "grid grid-cols-1 divide-y rounded-lg border", children: schedules.map((s) => (_jsxs("button", { type: "button", className: "grid grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 p-3 text-left hover:bg-muted/50", onClick: () => setOpen(s.id), children: [_jsx("span", { className: "font-medium", children: s.name }), _jsx(OnSwitch, { s: s, onSave: onSave }), _jsxs("span", { className: "col-span-2 text-xs text-muted-foreground", children: [s.kind === 'daily' ? '' : `Every ${everyText(s.every)} · `, when(s)] }), _jsx("span", { className: "col-span-2 text-xs", children: s.waiting ? _jsx("span", { className: "text-warning", children: s.waiting }) : _jsx(Last, { s: s }) })] }, s.id))) })) : (_jsx("div", { className: "overflow-x-auto overflow-y-hidden rounded-lg border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: "On" }), _jsx(TableHead, { children: "Schedule" }), _jsx(TableHead, { children: "When" }), _jsx(TableHead, { children: "Last run" }), onRuns ? _jsx(TableHead, { className: "w-0" }) : null] }) }), _jsx(TableBody, { children: schedules.map((s) => (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => setOpen(s.id), children: [_jsx(TableCell, { children: _jsx(OnSwitch, { s: s, onSave: onSave }) }), _jsx(TableCell, { className: "font-medium", children: s.name }), _jsxs(TableCell, { className: "min-w-48 whitespace-normal text-muted-foreground", children: [s.kind === 'daily' ? '' : `Every ${everyText(s.every)} · `, when(s)] }), _jsx(TableCell, { className: "max-w-48", children: s.waiting ? (_jsx(Badge, { variant: "outline", className: "text-warning", title: s.waiting, children: "Not connected" })) : (_jsx(Last, { s: s, compact: true })) }), onRuns ? (_jsx(TableCell, { children: _jsx(IconButton, { icon: ListIcon, label: `${s.name} runs`, onClick: (e) => {
|
|
187
187
|
e.stopPropagation();
|
|
188
188
|
toRuns?.(s.id);
|
|
189
|
-
} }) })) : null] }, s.id))) })] }) })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["
|
|
189
|
+
} }) })) : null] }, s.id))) })] }) })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Last runs shown in ", timeZoneLabel(timeZone), ".", onRuns ? ' Each run is a line in Logs.' : ''] }), briefs ? (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setAdding(true), children: [_jsx(PlusIcon, {}), "Brief"] })) : null] }), briefs ? (_jsx(Sheet, { open: adding, onOpenChange: setAdding, children: _jsx(SheetContent, { side: isMobile ? 'bottom' : 'right', className: "gap-4", children: _jsx(NewBrief, { create: async (b, time) => {
|
|
190
190
|
const id = await briefs.create(b);
|
|
191
191
|
await onSave(id, time);
|
|
192
192
|
return id;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@codemirror/lang-javascript": "6.2.5",
|
|
42
42
|
"@codemirror/lang-json": "6.0.2",
|
|
43
43
|
"@codemirror/lang-sql": "6.10.0",
|
|
44
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
44
|
+
"@columenlabs/lumen-ui-types": "0.1.27",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|