@columenlabs/lumen-ui 0.1.51 → 0.1.53
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/details-table.d.ts +3 -1
- package/dist/details-table.js +2 -2
- package/dist/summary-cards.d.ts +8 -2
- package/dist/summary-cards.js +18 -3
- package/dist/widget-settings.js +13 -9
- package/package.json +2 -2
package/dist/details-table.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
export interface DetailsTableProps {
|
|
3
|
-
/** `grow` marks the column that takes the spare width; the last column does by default.
|
|
3
|
+
/** `grow` marks the column that takes the spare width; the last column does by default.
|
|
4
|
+
* `hideOnPhone` drops a column on a phone, so the rest fit without scrolling sideways. */
|
|
4
5
|
head: {
|
|
5
6
|
label: string;
|
|
6
7
|
right?: boolean;
|
|
7
8
|
grow?: boolean;
|
|
9
|
+
hideOnPhone?: boolean;
|
|
8
10
|
}[];
|
|
9
11
|
rows: {
|
|
10
12
|
key: string;
|
package/dist/details-table.js
CHANGED
|
@@ -4,8 +4,8 @@ import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, Table
|
|
|
4
4
|
export function DetailsTable({ head, rows, foot, selected, onSelect }) {
|
|
5
5
|
const marked = head.findIndex((h) => h.grow);
|
|
6
6
|
const grow = marked >= 0 ? marked : head.length - 1;
|
|
7
|
-
const cell = (i) => cn('num', head[i]?.right && 'text-right', i !== grow && 'w-px');
|
|
8
|
-
return (_jsx("div", { className: "-mx-1 overflow-x-auto overflow-y-hidden rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: head.map((h, i) => (_jsx(TableHead, { className: cn(h.right && 'text-right', i !== grow && 'w-px'), children: h.label }, h.label))) }) }), _jsx(TableBody, { children: rows.map((r) => (_jsx(TableRow, { "data-state": r.key === selected ? 'selected' : undefined, "aria-current": r.key === selected || undefined, tabIndex: onSelect ? 0 : undefined, className: cn(onSelect &&
|
|
7
|
+
const cell = (i) => cn('num', head[i]?.right && 'text-right', i !== grow && 'w-px', head[i]?.hideOnPhone && 'max-md:hidden');
|
|
8
|
+
return (_jsx("div", { className: "-mx-1 overflow-x-auto overflow-y-hidden rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: head.map((h, i) => (_jsx(TableHead, { className: cn(h.right && 'text-right', i !== grow && 'w-px', h.hideOnPhone && 'max-md:hidden'), children: h.label }, h.label))) }) }), _jsx(TableBody, { children: rows.map((r) => (_jsx(TableRow, { "data-state": r.key === selected ? 'selected' : undefined, "aria-current": r.key === selected || undefined, tabIndex: onSelect ? 0 : undefined, className: cn(onSelect &&
|
|
9
9
|
'cursor-pointer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring'), onClick: onSelect && (() => onSelect(r.key)), onKeyDown: onSelect &&
|
|
10
10
|
((e) => {
|
|
11
11
|
if (e.key !== 'Enter' && e.key !== ' ')
|
package/dist/summary-cards.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type CardStyle, type MeterKind, type ViewTile } from '@columenlabs/lumen-ui-types/views';
|
|
1
|
+
import { type CardStyle, type MeterKind, type ViewAgg, type ViewTile } from '@columenlabs/lumen-ui-types/views';
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import { type FilterField } from './lumen-filter.js';
|
|
4
|
-
import { type ViewDomain } from './view-model.js';
|
|
4
|
+
import { type ViewDomain, type ViewField } from './view-model.js';
|
|
5
5
|
/**
|
|
6
6
|
* A LumenCard's number and lines: its frame shows the label, so the body is
|
|
7
7
|
* the main number and its lines; a domain's own card keeps the app's drawing without its frame.
|
|
@@ -36,6 +36,12 @@ export declare function SummaryTiles<T>({ domain, tiles, rows, fields, tile, loa
|
|
|
36
36
|
/** While the view's rows load, each card holds its place. */
|
|
37
37
|
loading?: boolean;
|
|
38
38
|
}): import("react").JSX.Element | null;
|
|
39
|
+
/**
|
|
40
|
+
* The roll-up a card over `f` starts with, so any field makes a sensible number: text counts its
|
|
41
|
+
* different values, a percent or weight takes its highest (shares never add up), the rest sum.
|
|
42
|
+
* Given the card's `agg`, keeps it when it still fits the field.
|
|
43
|
+
*/
|
|
44
|
+
export declare function cardAgg<T>(f: ViewField<T> | undefined, agg?: ViewAgg): ViewAgg;
|
|
39
45
|
/** What is wrong with each of the cards: a key, a label or a field; null where nothing. */
|
|
40
46
|
export declare function summaryCardIssues(tiles: ViewTile[], fields: string[], domainTiles: string[]): (string | null)[];
|
|
41
47
|
/**
|
package/dist/summary-cards.js
CHANGED
|
@@ -134,14 +134,29 @@ export function SummaryTiles({ domain, tiles, rows, fields, tile, loading, }) {
|
|
|
134
134
|
const shown = tiles.filter((t) => isOwnTile(t) || tile);
|
|
135
135
|
if (!shown.length)
|
|
136
136
|
return null;
|
|
137
|
-
|
|
137
|
+
// Each card fills its column, as a header's metric cards do (`TileRow fill`), not the fixed tile width.
|
|
138
|
+
const grid = 'grid grid-cols-2 gap-(--shell-section-gap) md:auto-cols-fr md:grid-flow-col lg:*:*:w-auto';
|
|
138
139
|
if (loading)
|
|
139
140
|
return (_jsx("div", { "data-slot": "view-tiles", "aria-busy": "true", className: grid, children: shown.map((t) => (_jsx(Skeleton, { className: "h-[4.5rem] w-full rounded-xl" }, t.key))) }));
|
|
140
141
|
return (_jsx("div", { "data-slot": "view-tiles", className: cn(grid, '[&>*:last-child:nth-child(odd)]:col-span-2 md:[&>*:last-child:nth-child(odd)]:col-span-1'), children: shown.map((t) => (_jsx("div", { className: "contents", children: isOwnTile(t) ? (_jsx(OwnTile, { domain: domain, tile: t, rows: rows, fields: fields })) : (tile?.(t.key, rows)) }, t.key))) }));
|
|
141
142
|
}
|
|
142
143
|
// ── Edit ───────────────────────────────────────────────────────────────────────────────────────
|
|
143
144
|
const dragOptions = { activationConstraint: { distance: 4 } };
|
|
144
|
-
|
|
145
|
+
// A number the page draws its own way (a meter, a unit) still reads its raw value, so it rolls up.
|
|
146
|
+
const isNumber = (f) => f.format !== 'text';
|
|
147
|
+
/**
|
|
148
|
+
* The roll-up a card over `f` starts with, so any field makes a sensible number: text counts its
|
|
149
|
+
* different values, a percent or weight takes its highest (shares never add up), the rest sum.
|
|
150
|
+
* Given the card's `agg`, keeps it when it still fits the field.
|
|
151
|
+
*/
|
|
152
|
+
export function cardAgg(f, agg) {
|
|
153
|
+
const share = f?.format === 'percent' || f?.format === 'weight';
|
|
154
|
+
if (agg && (isCountAgg(agg) || (f && isNumber(f) && !(share && agg === 'sum'))))
|
|
155
|
+
return agg;
|
|
156
|
+
if (!f || !isNumber(f))
|
|
157
|
+
return 'distinct';
|
|
158
|
+
return share ? 'max' : 'sum';
|
|
159
|
+
}
|
|
145
160
|
/** What is wrong with each of the cards: a key, a label or a field; null where nothing. */
|
|
146
161
|
export function summaryCardIssues(tiles, fields, domainTiles) {
|
|
147
162
|
return tiles.map((t, i) => {
|
|
@@ -211,7 +226,7 @@ export function SummaryCardsEditor({ domain, tiles, onChange, rows, fields, prob
|
|
|
211
226
|
const builtIns = domain.tiles ?? [];
|
|
212
227
|
const [current, setCurrent] = useState(tiles.find(isOwnTile)?.key ?? tiles[0]?.key ?? null);
|
|
213
228
|
const nums = domain.fields.filter(isNumber);
|
|
214
|
-
const pickable = domain.fields
|
|
229
|
+
const pickable = domain.fields;
|
|
215
230
|
const fieldOf = (k) => domain.fields.find((f) => f.key === k);
|
|
216
231
|
const i = tiles.findIndex((t) => t.key === current);
|
|
217
232
|
const picked = tiles[i];
|
package/dist/widget-settings.js
CHANGED
|
@@ -10,7 +10,7 @@ import { ALLOCATION_MAX, isOwnTile, isRowCard, isTableCard, trendBuckets, widget
|
|
|
10
10
|
import { ArrowDownIcon, ArrowUpIcon } from 'lucide-react';
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { LumenSelector } from './lumen-selector.js';
|
|
13
|
-
import { SummaryCardsEditor, summaryCardIssues } from './summary-cards.js';
|
|
13
|
+
import { SummaryCardsEditor, cardAgg, summaryCardIssues } from './summary-cards.js';
|
|
14
14
|
import { Input } from './ui/input.js';
|
|
15
15
|
import { Label } from './ui/label.js';
|
|
16
16
|
import { Switch } from './ui/switch.js';
|
|
@@ -21,7 +21,8 @@ export function EditorRow({ label, children }) {
|
|
|
21
21
|
return (_jsxs("div", { className: "grid grid-cols-[5rem_minmax(0,1fr)] items-center gap-2", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: label }), _jsx("div", { className: "min-w-0", children: children })] }));
|
|
22
22
|
}
|
|
23
23
|
export const isTextField = (f) => f.format === 'text' && !f.render;
|
|
24
|
-
|
|
24
|
+
// A number the page draws its own way (a meter, a unit) still reads its raw value, so it rolls up.
|
|
25
|
+
export const isNumberField = (f) => f.format !== 'text';
|
|
25
26
|
const DATE_LIKE = /(^|_)(date|day|at|time|month|week|year)($|_)/i;
|
|
26
27
|
/** A trend's date candidates: text fields, the date-like ones first. */
|
|
27
28
|
export const dateFields = (domain) => {
|
|
@@ -45,7 +46,8 @@ const freeTileKey = (tiles, taken) => {
|
|
|
45
46
|
};
|
|
46
47
|
/** A widget's slots, in the order the editor lists them; none for a chart of symbols. */
|
|
47
48
|
export function widgetSlots(card, domain) {
|
|
48
|
-
const
|
|
49
|
+
const fieldOf = (k) => domain.fields.find((f) => f.key === k);
|
|
50
|
+
const label = (k) => fieldOf(k)?.label ?? k;
|
|
49
51
|
const text = { accepts: isTextField, why: 'Takes a text field' };
|
|
50
52
|
const number = { accepts: isNumberField, why: 'Takes a number' };
|
|
51
53
|
if ('by' in card)
|
|
@@ -118,18 +120,19 @@ export function widgetSlots(card, domain) {
|
|
|
118
120
|
id: 'number',
|
|
119
121
|
label: 'Number',
|
|
120
122
|
max: 1,
|
|
121
|
-
|
|
123
|
+
accepts: () => true,
|
|
124
|
+
why: '',
|
|
122
125
|
keys: isOwnTile(t) ? [t.field] : [],
|
|
123
126
|
set: ([field]) => field
|
|
124
127
|
? {
|
|
125
128
|
...card,
|
|
126
129
|
tile: isOwnTile(t)
|
|
127
|
-
? { ...t, field }
|
|
130
|
+
? { ...t, field, agg: cardAgg(fieldOf(field), t.agg) }
|
|
128
131
|
: {
|
|
129
132
|
key: t.key,
|
|
130
133
|
label: label(field),
|
|
131
134
|
field,
|
|
132
|
-
agg:
|
|
135
|
+
agg: cardAgg(fieldOf(field)),
|
|
133
136
|
filter: [],
|
|
134
137
|
sign: false,
|
|
135
138
|
lines: [],
|
|
@@ -147,9 +150,10 @@ export function widgetSlots(card, domain) {
|
|
|
147
150
|
id: 'cards',
|
|
148
151
|
label: 'Cards',
|
|
149
152
|
max: MAX_TILES,
|
|
150
|
-
|
|
153
|
+
accepts: () => true,
|
|
154
|
+
why: '',
|
|
151
155
|
keys: tiles.filter(isOwnTile).map((t) => t.field),
|
|
152
|
-
// A field ticked adds a card of its
|
|
156
|
+
// A field ticked adds a card of its roll-up (`cardAgg`); one unticked takes its cards out.
|
|
153
157
|
set: (keys) => {
|
|
154
158
|
const kept = tiles.filter((t) => !isOwnTile(t) || keys.includes(t.field));
|
|
155
159
|
const next = [...kept];
|
|
@@ -160,7 +164,7 @@ export function widgetSlots(card, domain) {
|
|
|
160
164
|
key: freeTileKey(next, builtIns),
|
|
161
165
|
label: label(field),
|
|
162
166
|
field,
|
|
163
|
-
agg:
|
|
167
|
+
agg: cardAgg(fieldOf(field)),
|
|
164
168
|
filter: [],
|
|
165
169
|
sign: false,
|
|
166
170
|
lines: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
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.53",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|