@columenlabs/lumen-ui 0.1.49 → 0.1.51
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 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/kit-config.d.ts +7 -2
- package/dist/kit-config.js +10 -2
- package/dist/lumen-drawer.d.ts +2 -1
- package/dist/lumen-drawer.js +8 -7
- package/dist/meter-level.d.ts +7 -0
- package/dist/meter-level.js +14 -0
- package/dist/nav.d.ts +1 -1
- package/dist/nav.js +2 -4
- package/dist/share-meter.d.ts +5 -2
- package/dist/share-meter.js +5 -2
- package/dist/summary-cards.js +7 -1
- package/dist/unit-value.d.ts +6 -1
- package/dist/unit-value.js +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ Every export, its props and an example are on the [docs](https://lumen.columenla
|
|
|
78
78
|
- 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.
|
|
79
79
|
- 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.
|
|
80
80
|
- Bars in a column line up: use `ShareMeter` (fixed track, fixed right-aligned number), never a bar sized by its label.
|
|
81
|
+
- Meter levels are the kit's, not the app's: a share of a cap reads `ok` (grey) under 75%, `watch` (amber) from 75%, `alert` (red) from 90%. `ShareMeter` takes its colour from the share unless you pass `color`; `UnitValue share={0.8}` tints its number and dot the same way; usage progress cards follow the same levels. For any other mark (a dot, a chip, a "nearest cap" cell) call `meterLevel(share)` and colour it with `METER_LEVEL_COLOR` or `meterValueTone(share)`. Override per app with `configureLumen({ meterLevels: { watch: 0.8 } })` (each level can move alone) or per meter with `levels={{ watch: 0.5 }}`. Don't hand-roll `share >= 0.75` checks.
|
|
81
82
|
- Table rows are one line. A cell shows one value (with a small bar or tag at most); anything more (other metrics, the allowance, a breakdown) goes in a hover tooltip, never in stacked text or sub-rows.
|
|
82
83
|
- Chart colours are theme tokens (`var(--chart-1)`, `useChartTokens()`), never hex picked for one theme; `LumenEChart` resolves `var(--…)` in its option so sparklines read in light and dark.
|
|
83
84
|
- "No label" filters come built in: `LumenTokenizedFilter` pins a "No <field>" row on any text field where some rows have no value (an `empty` token); don't add your own toggle for it.
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { LumenChoice, type LumenChoiceOption } from './lumen-choice.js';
|
|
|
22
22
|
export { LumenSecret } from './lumen-secret.js';
|
|
23
23
|
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
24
24
|
export { ShareMeter } from './share-meter.js';
|
|
25
|
+
export { METER_LEVEL_COLOR, meterLevel, meterValueTone } from './meter-level.js';
|
|
25
26
|
export { UnitValue } from './unit-value.js';
|
|
26
27
|
export { COVERAGE_LEVELS, CoverageCell, type CoverageJobState, type CoverageLevel, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
27
28
|
export { type CoverageBlock, type CoverageData, type CoverageFact, type CoveragePick, type CoverageRow, type CoverageSummary, LumenCoverageMap, type LumenCoverageMapProps, blockName, blockOf, } from './coverage-map.js';
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { LumenChoice } from './lumen-choice.js';
|
|
|
29
29
|
export { LumenSecret } from './lumen-secret.js';
|
|
30
30
|
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
31
31
|
export { ShareMeter } from './share-meter.js';
|
|
32
|
+
export { METER_LEVEL_COLOR, meterLevel, meterValueTone } from './meter-level.js';
|
|
32
33
|
export { UnitValue } from './unit-value.js';
|
|
33
34
|
export { COVERAGE_LEVELS, CoverageCell, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
34
35
|
export { LumenCoverageMap, blockName, blockOf, } from './coverage-map.js';
|
package/dist/kit-config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MeterLevels } from '@columenlabs/lumen-ui-types/views';
|
|
1
2
|
import type { Prefs } from './prefs.js';
|
|
2
3
|
export interface KitConfig {
|
|
3
4
|
/** The cookie that keeps each person's display preferences. */
|
|
@@ -8,9 +9,13 @@ export interface KitConfig {
|
|
|
8
9
|
marketOff: string;
|
|
9
10
|
/** The app's own defaults for preferences a person has not set (theme, time zone, ...). */
|
|
10
11
|
defaultPrefs?: Partial<Prefs>;
|
|
12
|
+
/** Where every meter turns amber (`watch`) and red (`alert`), as shares of a cap (0 to 1). */
|
|
13
|
+
meterLevels: MeterLevels;
|
|
11
14
|
}
|
|
12
15
|
export declare const KIT: KitConfig;
|
|
13
16
|
/** Runs `fn` after each `configureLumen` (preferences read again from the app's cookie). */
|
|
14
17
|
export declare function onConfigure(fn: () => void): void;
|
|
15
|
-
/** Sets what `c` names; the rest keep the kit's defaults. */
|
|
16
|
-
export declare function configureLumen(c: Partial<KitConfig
|
|
18
|
+
/** Sets what `c` names; the rest keep the kit's defaults (one meter level can move alone). */
|
|
19
|
+
export declare function configureLumen(c: Partial<Omit<KitConfig, 'meterLevels'>> & {
|
|
20
|
+
meterLevels?: Partial<MeterLevels>;
|
|
21
|
+
}): void;
|
package/dist/kit-config.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
// What an app tells the kit about itself (the kit is product-neutral): where
|
|
2
|
+
// people's display preferences live and the line a market widget shows where the app has no feed.
|
|
3
|
+
// Set once at start with `configureLumen`. The assistant's name comes from its provider.
|
|
4
|
+
import { METER_LEVELS } from '@columenlabs/lumen-ui-types/views';
|
|
1
5
|
export const KIT = {
|
|
2
6
|
prefsCookie: 'lumen_prefs',
|
|
3
7
|
marketOff: 'Live market data is not on this app.',
|
|
8
|
+
meterLevels: METER_LEVELS,
|
|
4
9
|
};
|
|
5
10
|
const changed = new Set();
|
|
6
11
|
/** Runs `fn` after each `configureLumen` (preferences read again from the app's cookie). */
|
|
7
12
|
export function onConfigure(fn) {
|
|
8
13
|
changed.add(fn);
|
|
9
14
|
}
|
|
10
|
-
/** Sets what `c` names; the rest keep the kit's defaults. */
|
|
15
|
+
/** Sets what `c` names; the rest keep the kit's defaults (one meter level can move alone). */
|
|
11
16
|
export function configureLumen(c) {
|
|
12
|
-
|
|
17
|
+
const { meterLevels, ...rest } = c;
|
|
18
|
+
Object.assign(KIT, rest);
|
|
19
|
+
if (meterLevels)
|
|
20
|
+
KIT.meterLevels = { ...KIT.meterLevels, ...meterLevels };
|
|
13
21
|
for (const fn of changed)
|
|
14
22
|
fn();
|
|
15
23
|
}
|
package/dist/lumen-drawer.d.ts
CHANGED
|
@@ -24,7 +24,8 @@ export interface LumenDrawerProps {
|
|
|
24
24
|
* edge, 320px to 60% by default. The sheet spans the screen so the resizable group has room; only
|
|
25
25
|
* the handle and the drawer take the pointer, so a click beside it falls through to the overlay
|
|
26
26
|
* and closes it (`!` beats the inline pointer-events Radix sets on a modal's content). Its body
|
|
27
|
-
* holds DrawerSections; its footer
|
|
27
|
+
* holds DrawerSections; its footer sits at the bottom (actions on the right, Close by default; on a
|
|
28
|
+
* phone it shows only when there are actions, as the sheet's X closes it).
|
|
28
29
|
*/
|
|
29
30
|
export declare function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize, minSize, maxSize, }: LumenDrawerProps): import("react").JSX.Element;
|
|
30
31
|
export {};
|
package/dist/lumen-drawer.js
CHANGED
|
@@ -11,18 +11,19 @@ import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTi
|
|
|
11
11
|
* edge, 320px to 60% by default. The sheet spans the screen so the resizable group has room; only
|
|
12
12
|
* the handle and the drawer take the pointer, so a click beside it falls through to the overlay
|
|
13
13
|
* and closes it (`!` beats the inline pointer-events Radix sets on a modal's content). Its body
|
|
14
|
-
* holds DrawerSections; its footer
|
|
14
|
+
* holds DrawerSections; its footer sits at the bottom (actions on the right, Close by default; on a
|
|
15
|
+
* phone it shows only when there are actions, as the sheet's X closes it).
|
|
15
16
|
*/
|
|
16
17
|
export function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', }) {
|
|
17
18
|
const isMobile = useIsMobile();
|
|
18
19
|
const head = (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(SheetTitle, { className: "truncate", children: title }), _jsx(SheetDescription, { className: description ? 'truncate text-muted-foreground text-xs' : 'sr-only', children: description ?? 'Details' })] }));
|
|
19
20
|
const body = _jsx("div", { className: "flex flex-col px-4 pb-4", children: children });
|
|
20
|
-
// Every drawer
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const foot = (_jsxs("footer", { "data-slot": "drawer-footer", className: "flex shrink-0 items-center gap-2 border-t bg-muted/50 px-4 py-2.5
|
|
25
|
-
return (_jsx(Sheet, { open: open, onOpenChange: (o) => !o && onClose(), modal: isMobile, children: isMobile ? (_jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 overflow-hidden", children: [_jsx(SheetHeader, { className: "order-first shrink-0 border-b", children: head }), _jsx("div", { className: "
|
|
21
|
+
// Every drawer ends on a footer, styled like a dialog's: the body scrolls above it and always
|
|
22
|
+
// ends on a clear edge. Without actions of its own it offers Close, except on a phone, where the
|
|
23
|
+
// sheet's X already closes it and a second Close only took room.
|
|
24
|
+
const plain = !footer && !footerStart;
|
|
25
|
+
const foot = (_jsxs("footer", { "data-slot": "drawer-footer", className: "flex shrink-0 items-center gap-2 border-t bg-muted/50 px-4 py-2.5", children: [footerStart && (_jsx("div", { className: "min-w-0 flex-1 truncate text-muted-foreground text-xs", children: footerStart })), _jsx("div", { className: "ml-auto flex items-center gap-2", children: footer ?? (_jsx(SheetClose, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", children: "Close" }) })) })] }));
|
|
26
|
+
return (_jsx(Sheet, { open: open, onOpenChange: (o) => !o && onClose(), modal: isMobile, children: isMobile ? (_jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 overflow-hidden", children: [_jsx(SheetHeader, { className: "order-first shrink-0 border-b", children: head }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), !plain && foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
|
|
26
27
|
// The resize handle's grab zone is wider than the handle, so a drag can start on the
|
|
27
28
|
// overlay; the panels library marks that pointerdown handled, and then it doesn't close.
|
|
28
29
|
onPointerDownOutside: (e) => e.detail.originalEvent.defaultPrevented && e.preventDefault(), className: "pointer-events-none! gap-0 border-0 bg-transparent shadow-none data-[side=right]:left-0 data-[side=right]:w-auto data-[side=right]:border-l-0 data-[side=right]:sm:max-w-none", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { minSize: "40%" }), _jsx(ResizableHandle, { withHandle: true, className: "pointer-events-auto" }), _jsx(ResizablePanel, { defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, "data-slot": "lumen-drawer", className: "pointer-events-auto bg-popover shadow-lg", children: _jsxs("section", { className: "flex h-full flex-col overflow-hidden", children: [_jsxs("header", { className: "flex shrink-0 items-center gap-2 border-b px-4 py-3", children: [head, _jsx(SheetClose, { asChild: true, children: _jsx(IconButton, { icon: XIcon, label: "Close", className: "ml-auto" }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] }) })] }) })) }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MeterLevel, MeterLevels } from '@columenlabs/lumen-ui-types/views';
|
|
2
|
+
/** The level of `share` (0 to 1 of a cap), on the app's levels or this meter's own `levels`. */
|
|
3
|
+
export declare function meterLevel(share: number, levels?: Partial<MeterLevels>): MeterLevel;
|
|
4
|
+
/** Each level's colour, theme tokens: `ok` is a quiet grey so only a meter near its cap draws the eye. */
|
|
5
|
+
export declare const METER_LEVEL_COLOR: Record<MeterLevel, string>;
|
|
6
|
+
/** A share's `UnitValue` tone: none while `ok`. */
|
|
7
|
+
export declare const meterValueTone: (share: number, levels?: Partial<MeterLevels>) => "loss" | "warning" | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { KIT } from './kit-config.js';
|
|
2
|
+
/** The level of `share` (0 to 1 of a cap), on the app's levels or this meter's own `levels`. */
|
|
3
|
+
export function meterLevel(share, levels) {
|
|
4
|
+
const { watch, alert } = { ...KIT.meterLevels, ...levels };
|
|
5
|
+
return share >= alert ? 'alert' : share >= watch ? 'watch' : 'ok';
|
|
6
|
+
}
|
|
7
|
+
/** Each level's colour, theme tokens: `ok` is a quiet grey so only a meter near its cap draws the eye. */
|
|
8
|
+
export const METER_LEVEL_COLOR = {
|
|
9
|
+
ok: 'color-mix(in oklab, var(--muted-foreground) 55%, transparent)',
|
|
10
|
+
watch: 'var(--warning)',
|
|
11
|
+
alert: 'var(--loss)',
|
|
12
|
+
};
|
|
13
|
+
/** A share's `UnitValue` tone: none while `ok`. */
|
|
14
|
+
export const meterValueTone = (share, levels) => ({ ok: undefined, watch: 'warning', alert: 'loss' })[meterLevel(share, levels)];
|
package/dist/nav.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import { type AssistantPageType } from './assistant-screen.js';
|
|
3
3
|
/** A nav icon: a lucide icon, or a view's emoji drawn at icon size. */
|
|
4
4
|
export type NavIcon = ComponentType<{
|
package/dist/nav.js
CHANGED
|
@@ -6,7 +6,6 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
// the name, a click folds it, the fold is remembered in the browser, and `+` never folds.
|
|
7
7
|
import { Link, useRouterState } from '@tanstack/react-router';
|
|
8
8
|
import { ChevronRightIcon, PlusIcon, Trash2Icon } from 'lucide-react';
|
|
9
|
-
import { useState } from 'react';
|
|
10
9
|
import { useAssistantPages } from './assistant-screen.js';
|
|
11
10
|
import { LumenConfirm } from './confirm.js';
|
|
12
11
|
import { useFolded } from './fold.js';
|
|
@@ -67,8 +66,7 @@ function SectionRow({ item, activeId, onRemove, removeLabel, }) {
|
|
|
67
66
|
const isActive = activeId === item.id;
|
|
68
67
|
// The open item shows no trash icon.
|
|
69
68
|
const remove = item.fixed || isActive ? undefined : onRemove;
|
|
70
|
-
const [
|
|
71
|
-
const row = (_jsxs(_Fragment, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.title, isActive: isActive, children: _jsxs(Link, { to: item.to, onClick: subs.length ? () => setOpen((o) => (isActive ? !o : true)) : undefined, children: [_jsx(item.icon, {}), _jsx("span", { children: item.title })] }) }), item.badge !== undefined && item.badge !== null && (_jsx(SidebarMenuBadge, { className: cn(SECTION_BADGE,
|
|
69
|
+
const row = (_jsxs(_Fragment, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.title, isActive: isActive, children: _jsxs(Link, { to: item.to, children: [_jsx(item.icon, {}), _jsx("span", { children: item.title })] }) }), item.badge !== undefined && item.badge !== null && (_jsx(SidebarMenuBadge, { className: cn(SECTION_BADGE,
|
|
72
70
|
// The remove icon shows on hover from md up, and always below it: make room there.
|
|
73
71
|
remove &&
|
|
74
72
|
'max-md:right-7 md:group-focus-within/menu-item:opacity-0 md:group-hover/menu-item:opacity-0'), children: item.badge })), remove && (_jsx(LumenConfirm, { title: `${removeLabel(item)}?`, action: "Remove", onConfirm: () => remove(item), children: _jsx(SidebarMenuAction, { showOnHover: true, "aria-label": removeLabel(item), title: removeLabel(item),
|
|
@@ -76,7 +74,7 @@ function SectionRow({ item, activeId, onRemove, removeLabel, }) {
|
|
|
76
74
|
className: "text-muted-foreground peer-hover/menu-button:text-muted-foreground hover:bg-transparent hover:text-destructive", children: _jsx(Trash2Icon, {}) }) }))] }));
|
|
77
75
|
if (!subs.length)
|
|
78
76
|
return _jsx(SidebarMenuItem, { children: row });
|
|
79
|
-
return (_jsx(Collapsible, { asChild: true,
|
|
77
|
+
return (_jsx(Collapsible, { asChild: true, defaultOpen: item.defaultOpen || subActive || isActive, className: "group/collapsible", children: _jsxs(SidebarMenuItem, { children: [row, _jsx(CollapsibleTrigger, { asChild: true, children: _jsx(SidebarMenuAction, { "aria-label": `Show or hide ${item.title}`, className: "right-auto left-1.5 bg-sidebar opacity-0 group-hover/menu-item:opacity-100 focus-visible:opacity-100 peer-data-[active=true]/menu-button:bg-sidebar-accent [&>svg]:transition-transform data-[state=open]:[&>svg]:rotate-90", children: _jsx(ChevronRightIcon, {}) }) }), _jsx(CollapsibleContent, { children: _jsx(SidebarMenuSub, { className: "mr-0 pr-0", children: subs.map((sub) => {
|
|
80
78
|
const hasBadge = sub.badge !== undefined && sub.badge !== null;
|
|
81
79
|
return (_jsxs(SidebarMenuSubItem, { children: [_jsx(SidebarMenuSubButton, { asChild: true, isActive: activeId === `${item.id}/${sub.id}`, className: cn(hasBadge && 'pr-8'), children: _jsxs(Link, { to: sub.to, title: sub.title, children: [sub.icon ? (_jsx("span", { "aria-hidden": true, className: "flex shrink-0 [&>svg]:size-4", style: { color: sub.color }, children: _jsx(sub.icon, {}) })) : (sub.color && (_jsx("span", { "aria-hidden": true, className: "mx-1 size-2 shrink-0 rounded-full", style: { background: sub.color } }))), _jsx("span", { children: sub.title })] }) }), hasBadge && (_jsx("span", { className: cn('pointer-events-none absolute top-1 right-1 flex h-5 min-w-5 items-center justify-center px-1 text-xs text-muted-foreground', typeof sub.badge === 'number' && 'num'), children: sub.badge }))] }, sub.id));
|
|
82
80
|
}) }) })] }) }));
|
package/dist/share-meter.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import type { MeterLevels } from '@columenlabs/lumen-ui-types/views';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
|
-
export declare function ShareMeter({ value, label, color, wide, className, }: {
|
|
3
|
+
export declare function ShareMeter({ value, label, color, levels, wide, className, }: {
|
|
3
4
|
/** 0 to 1 fills the bar; past 1 it stays full. */
|
|
4
5
|
value: number;
|
|
5
6
|
/** The number beside the bar, e.g. `113%`; left out, the rounded percent. */
|
|
6
7
|
label?: ReactNode;
|
|
7
|
-
/** The bar's colour, a theme token (
|
|
8
|
+
/** The bar's colour, a theme token; left out, the share's level (grey, amber, red). */
|
|
8
9
|
color?: string;
|
|
10
|
+
/** This meter's own levels (e.g. `{ watch: 0.5 }`); left out, the app's `meterLevels`. */
|
|
11
|
+
levels?: Partial<MeterLevels>;
|
|
9
12
|
/** Fills its cell (a list row) instead of the fixed table width. */
|
|
10
13
|
wide?: boolean;
|
|
11
14
|
className?: string;
|
package/dist/share-meter.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from './lib/utils.js';
|
|
3
|
+
import { METER_LEVEL_COLOR, meterLevel } from './meter-level.js';
|
|
3
4
|
import { Progress } from './ui/progress.js';
|
|
4
|
-
export function ShareMeter({ value, label, color
|
|
5
|
-
return (_jsxs("span", { className: cn('inline-flex items-center gap-2', wide && 'w-full', className), children: [_jsx(Progress, { value: Math.max(0, Math.min(value, 1)) * 100, className: cn('h-1.5 shrink-0 [&>[data-slot=progress-indicator]]:bg-(--meter-color)', wide ? 'w-full shrink' : 'w-14'), style: {
|
|
5
|
+
export function ShareMeter({ value, label, color, levels, wide, className, }) {
|
|
6
|
+
return (_jsxs("span", { className: cn('inline-flex items-center gap-2', wide && 'w-full', className), children: [_jsx(Progress, { value: Math.max(0, Math.min(value, 1)) * 100, className: cn('h-1.5 shrink-0 [&>[data-slot=progress-indicator]]:bg-(--meter-color)', wide ? 'w-full shrink' : 'w-14'), style: {
|
|
7
|
+
'--meter-color': color ?? METER_LEVEL_COLOR[meterLevel(value, levels)],
|
|
8
|
+
} }), _jsx("span", { className: "num w-11 shrink-0 text-right text-xs", children: label ?? `${Math.round(value * 100)}%` })] }));
|
|
6
9
|
}
|
package/dist/summary-cards.js
CHANGED
|
@@ -15,6 +15,7 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
15
15
|
import { PolarAngleAxis, RadialBar, RadialBarChart } from 'recharts';
|
|
16
16
|
import { LumenConfirm } from './confirm.js';
|
|
17
17
|
import { IconButton } from './icon-button.js';
|
|
18
|
+
import { KIT } from './kit-config.js';
|
|
18
19
|
import { cn } from './lib/utils.js';
|
|
19
20
|
import { LumenTokenizedFilter, filterByTokens, useLumenFilter, } from './lumen-filter.js';
|
|
20
21
|
import { LumenSelector } from './lumen-selector.js';
|
|
@@ -103,12 +104,17 @@ export function LumenCard({ domain, tile: t, rows, fields, drawn, loading, style
|
|
|
103
104
|
const share = style !== 'number' && target && typeof v === 'number'
|
|
104
105
|
? Math.max(0, Math.min(100, (v / target) * 100))
|
|
105
106
|
: null;
|
|
106
|
-
const tone = meterTone(meter, share, bands);
|
|
107
|
+
const tone = meterTone(meter, share, bands ?? (meter === 'usage' ? usageBands() : undefined));
|
|
107
108
|
const of = target ? (_jsx(ViewValueCell, { value: target, format: figs.main.format, amount: figs.main.amount })) : null;
|
|
108
109
|
if (style === 'progress_circle')
|
|
109
110
|
return (_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(ProgressRing, { value: share, tone: tone }), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: "num truncate text-xl leading-7 font-semibold", children: _jsx(FigureText, { f: figs.main }) }), _jsx("span", { className: "num truncate text-xs text-muted-foreground", children: of ? _jsxs(_Fragment, { children: ["of ", of] }) : 'Set a target in Edit widget' })] })] }));
|
|
110
111
|
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: cn('num truncate text-2xl leading-8 font-semibold', t.sign && typeof v === 'number' && (v > 0 ? 'text-gain' : v < 0 ? 'text-loss' : '')), children: _jsx(FigureText, { f: figs.main }) }), style === 'progress_bar' && (_jsxs("div", { className: "flex flex-col gap-1 pt-1", children: [_jsx(Progress, { value: share ?? 0, className: cn('h-2', tone && TONE_BAR[tone]), "aria-label": "Progress to target" }), _jsx("span", { className: "num text-xs text-muted-foreground", children: of ? (_jsxs(_Fragment, { children: [share === null ? '—' : `${share.toFixed(0)}%`, " of ", of] })) : ('Set a target in Edit widget') })] })), figs.lines.map((l, i) => (_jsx("span", { className: "num line-clamp-2 text-xs text-muted-foreground", children: _jsx(FigureText, { f: l }) }, i)))] }));
|
|
111
112
|
}
|
|
113
|
+
/** A usage meter's bands from the app's `meterLevels`, in %. */
|
|
114
|
+
const usageBands = () => ({
|
|
115
|
+
warn: KIT.meterLevels.watch * 100,
|
|
116
|
+
bad: KIT.meterLevels.alert * 100,
|
|
117
|
+
});
|
|
112
118
|
/** A meter's tone on a bar's fill (theme tokens, no glow). */
|
|
113
119
|
const TONE_BAR = {
|
|
114
120
|
success: '[&>[data-slot=progress-indicator]]:bg-success',
|
package/dist/unit-value.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import type { MeterLevels } from '@columenlabs/lumen-ui-types/views';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
|
-
export declare function UnitValue({ value, unit, tone, title, className, }: {
|
|
3
|
+
export declare function UnitValue({ value, unit, tone: toneProp, share, levels, title, className, }: {
|
|
3
4
|
/** The number, already formatted (`231.4`). */
|
|
4
5
|
value: ReactNode;
|
|
5
6
|
/** Its unit (`GB`, `M`, `/day`); left out, the slot stays empty so the column still lines up. */
|
|
6
7
|
unit?: ReactNode;
|
|
7
8
|
/** Near (`warning`) or past (`loss`) a limit: tints the number and shows a dot. */
|
|
8
9
|
tone?: 'warning' | 'loss';
|
|
10
|
+
/** Its share of a cap (0 to 1): sets `tone` by `meterLevel` (amber from 75%, red from 90%). */
|
|
11
|
+
share?: number;
|
|
12
|
+
/** This value's own levels; left out, the app's `meterLevels`. */
|
|
13
|
+
levels?: Partial<MeterLevels>;
|
|
9
14
|
title?: string;
|
|
10
15
|
className?: string;
|
|
11
16
|
}): import("react").JSX.Element;
|
package/dist/unit-value.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from './lib/utils.js';
|
|
3
|
-
|
|
3
|
+
import { meterValueTone } from './meter-level.js';
|
|
4
|
+
export function UnitValue({ value, unit, tone: toneProp, share, levels, title, className, }) {
|
|
5
|
+
const tone = toneProp ?? (share === undefined ? undefined : meterValueTone(share, levels));
|
|
4
6
|
return (_jsxs("span", { title: title, className: cn('inline-flex w-full items-center justify-end gap-1 whitespace-nowrap', className), children: [_jsx("span", { className: cn('num text-right', tone === 'warning' && 'text-warning', tone === 'loss' && 'text-loss'), children: value }), _jsx("span", { className: "w-10 shrink-0 text-left text-muted-foreground text-xs", children: unit }), _jsx("span", { "aria-hidden": "true", className: cn('size-1.5 shrink-0 rounded-full', tone === 'warning' ? 'bg-warning' : tone === 'loss' ? 'bg-loss' : 'bg-transparent') })] }));
|
|
5
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
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.51",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|