@columenlabs/lumen-ui 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lumen-layout.js +5 -1
- package/dist/lumen-view.d.ts +9 -1
- package/dist/lumen-view.js +21 -8
- package/dist/ui/tabs.d.ts +1 -1
- package/dist/ui/tabs.js +16 -2
- package/package.json +2 -2
package/dist/lumen-layout.js
CHANGED
|
@@ -250,6 +250,9 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
250
250
|
savedSortRef.current = savedSort;
|
|
251
251
|
setHeldSort(config.sort);
|
|
252
252
|
}, [savedSort, config.sort]);
|
|
253
|
+
// A server's page sorts on the server: it hears each sort held.
|
|
254
|
+
const onServerSort = paging?.onSort;
|
|
255
|
+
useEffect(() => onServerSort?.(heldSort ?? null), [onServerSort, heldSort]);
|
|
253
256
|
const [details, setDetails] = useState(null);
|
|
254
257
|
const onDetails = onOpen ?? (domain.details?.length ? setDetails : undefined);
|
|
255
258
|
// The assistant sees the view, its data domain, its filter, how many rows show, the first of them and the
|
|
@@ -493,7 +496,8 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
493
496
|
const viewCard = mainTableOf(cards);
|
|
494
497
|
const viewOn = (k) => !viewCard || controlOn(viewCard, k);
|
|
495
498
|
const showBox = !hideFilter && viewOn('filter');
|
|
496
|
-
|
|
499
|
+
// A server's page cannot be grouped: a group would bucket the one page only.
|
|
500
|
+
const groupButton = viewOn('group') && !paging ? group.button : null;
|
|
497
501
|
const viewFilter = !showBox && !groupButton ? undefined : (_jsxs(_Fragment, { children: [showBox && (_jsx(LumenTokenizedFilter, { filter: filter, label: `Filter ${config.name}`, className: "min-w-0 flex-1", saved: config.filters.default, link: {
|
|
498
502
|
color: viewColor,
|
|
499
503
|
onPick: (color) => links.setColor(viewCardId, color),
|
package/dist/lumen-view.d.ts
CHANGED
|
@@ -57,6 +57,11 @@ export interface ServerPaging {
|
|
|
57
57
|
onPage: (page: number) => void;
|
|
58
58
|
onSize?: (size: number) => void;
|
|
59
59
|
sizes?: readonly number[];
|
|
60
|
+
/** The columns the server sorts by; the rest take no header sort, and the view groups nothing,
|
|
61
|
+
* since either would order or bucket one page only. */
|
|
62
|
+
sorts?: readonly string[];
|
|
63
|
+
/** Told the sort held (a header click, a revert), to fetch the rows in that order. */
|
|
64
|
+
onSort?: (sort: CustomViewConfig['sort']) => void;
|
|
60
65
|
}
|
|
61
66
|
export interface LumenViewProps<T> {
|
|
62
67
|
/** The view's domain, its formula columns included (`withViewFields`). */
|
|
@@ -142,7 +147,7 @@ export interface AddColumn {
|
|
|
142
147
|
* and ⋯, and its own filter and Group (they narrow this table only); its body
|
|
143
148
|
* the table (grouped one level, or the pivot) with the loading states.
|
|
144
149
|
*/
|
|
145
|
-
export declare function LumenView<T extends RowData>({ domain, config, rows, total, paging: server, groupBy, pivot, subgroup, load: sourceLoad, state, title, selection, onDetails, tapped, groupActions, controls, inLayout: inScroller, cardId, tools, filter, filterCount, strip, link, note, onColumns, onAddColumn, sort, onSort, shown, }: LumenViewProps<T>): import("react").JSX.Element;
|
|
150
|
+
export declare function LumenView<T extends RowData>({ domain, config, rows, total, paging: server, groupBy: pickedGroup, pivot: pickedPivot, subgroup, load: sourceLoad, state, title, selection, onDetails, tapped, groupActions, controls, inLayout: inScroller, cardId, tools, filter, filterCount, strip, link, note, onColumns, onAddColumn, sort, onSort, shown, }: LumenViewProps<T>): import("react").JSX.Element;
|
|
146
151
|
/** A field's colour for its header: its topic's source (a broker green, an app brown) where
|
|
147
152
|
* the group names one, else its group's (Holding, Symbol, Account, Formulas). */
|
|
148
153
|
export declare function colorOf<T>(domain: ViewDomain<T>, f: ViewField<T> | undefined): string | null;
|
|
@@ -175,6 +180,9 @@ export interface LumenTableProps<T> {
|
|
|
175
180
|
sort?: CustomViewConfig['sort'];
|
|
176
181
|
/** Told the sort a header click sets, so a page can hold or save it (rule C). */
|
|
177
182
|
onSort?: (sort: CustomViewConfig['sort']) => void;
|
|
183
|
+
/** The rows come sorted by a server, which sorts by these columns only: a header click asks
|
|
184
|
+
* the server (through `onSort`) and the other headers take no click. */
|
|
185
|
+
serverSorts?: readonly string[];
|
|
178
186
|
/** The + Add column head at the end of the header. */
|
|
179
187
|
add?: AddColumn & {
|
|
180
188
|
field?: {
|
package/dist/lumen-view.js
CHANGED
|
@@ -226,7 +226,7 @@ function downloadCsv({ name, columns, rows, groupBy, sort, hidden, }) {
|
|
|
226
226
|
* and ⋯, and its own filter and Group (they narrow this table only); its body
|
|
227
227
|
* the table (grouped one level, or the pivot) with the loading states.
|
|
228
228
|
*/
|
|
229
|
-
export function LumenView({ domain, config, rows, total = rows.length, paging: server, groupBy = null, pivot = false, subgroup, load: sourceLoad, state, title = config.name, selection, onDetails, tapped, groupActions, controls, inLayout: inScroller = false, cardId = 'view', tools, filter, filterCount, strip, link, note, onColumns, onAddColumn, sort = config.sort, onSort, shown, }) {
|
|
229
|
+
export function LumenView({ domain, config, rows, total = rows.length, paging: server, groupBy: pickedGroup = null, pivot: pickedPivot = false, subgroup, load: sourceLoad, state, title = config.name, selection, onDetails, tapped, groupActions, controls, inLayout: inScroller = false, cardId = 'view', tools, filter, filterCount, strip, link, note, onColumns, onAddColumn, sort = config.sort, onSort, shown, }) {
|
|
230
230
|
// Its controls (Edit widget ▸ Controls): Pager, Columns and Download here.
|
|
231
231
|
const on = (k) => controlOn({ kind: 'table', controls: shown }, k);
|
|
232
232
|
const cell = useGridCell();
|
|
@@ -234,6 +234,9 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
|
|
|
234
234
|
// scrolls with the page.
|
|
235
235
|
const inLayout = inScroller && !cell;
|
|
236
236
|
const columns = pick(domain.fields, config.columns);
|
|
237
|
+
// A server's page groups and pivots nothing: either would cover the one page only.
|
|
238
|
+
const groupBy = server ? null : pickedGroup;
|
|
239
|
+
const pivot = server ? false : pickedPivot;
|
|
237
240
|
// Live columns on screen keep their source reading while the market trades (#861).
|
|
238
241
|
const load = useLiveLoad(sourceLoad, columns.some((c) => {
|
|
239
242
|
const r = freshnessOf(domain, c);
|
|
@@ -280,7 +283,7 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
|
|
|
280
283
|
const compare = openChart && picked.length > 1 ? (_jsx(IconButton, { icon: ChartSpline, label: `Compare ${picked.join(', ')}`, variant: "outline", onClick: () => openChart(picked) })) : null;
|
|
281
284
|
// A server's page: the pager counts from the server's total, or the rows seen so far.
|
|
282
285
|
const pager = server ? (_jsx(LumenPager, { total: server.total ?? server.page * server.size + rows.length, page: server.page, size: server.size, onPage: server.onPage, onSize: server.onSize ?? (() => { }), sizes: server.sizes ?? [server.size], more: server.total === null && server.more })) : paging ? (_jsx(LumenPager, { total: tableRows.length, page: page, size: pageSize, onPage: setPageIndex, onSize: setPageSize, noun: rows.length === total ? 'rows' : `of ${total} rows` })) : (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: count }));
|
|
283
|
-
const table = pivot && config.pivot ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: tableRows, groupBy: groupBy, subgroup: sub, chips: chips, page: paging ? { index: page, size: pageSize } : null, inView: inLayout, fit: fit, onDetails: onDetails, tapped: tapped, selection: selection, groupActions: groupActions, sort: sort, onSort: onSort, total: config.total !== false, add: onAddColumn || onColumns
|
|
286
|
+
const table = pivot && config.pivot ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: tableRows, groupBy: groupBy, subgroup: sub, chips: chips, page: paging ? { index: page, size: pageSize } : null, inView: inLayout, fit: fit, onDetails: onDetails, tapped: tapped, selection: selection, groupActions: groupActions, sort: sort, onSort: onSort, serverSorts: server ? (server.sorts ?? []) : undefined, total: config.total !== false, add: onAddColumn || onColumns
|
|
284
287
|
? { ...onAddColumn, field: onColumns && { domain, columns, onColumns } }
|
|
285
288
|
: undefined }));
|
|
286
289
|
return (_jsx(LumenWidget, { id: cardId, kind: "table", style: "table", title: title, controls: controls, tools: state ? (tools) : (_jsxs(_Fragment, { children: [tools, compare, pager, onColumns && !onAddColumn && on('columns') && (_jsx(ColumnPicker, { domain: domain, columns: columns, onColumns: onColumns })), !pivot && on('download') && (_jsx(IconButton, { icon: DownloadIcon, label: "Download CSV", onClick: () => downloadCsv({
|
|
@@ -349,8 +352,15 @@ export function LumenTable({ load, ...props }) {
|
|
|
349
352
|
return _jsx(LumenTableRows, { ...props });
|
|
350
353
|
return (_jsx("div", { className: "relative", children: _jsx(LumenStates, { load: load, children: _jsx(LumenTableRows, { ...props }) }) }));
|
|
351
354
|
}
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
/** The table's sort from a view's: none when its column is not shown, or the server cannot sort
|
|
356
|
+
* by it. */
|
|
357
|
+
function sortingOf(sort, columns, serverSorts) {
|
|
358
|
+
if (!sort || (serverSorts && !serverSorts.includes(sort.key)))
|
|
359
|
+
return [];
|
|
360
|
+
return columns.some((f) => f.key === sort.key) ? [{ id: sort.key, desc: sort.desc }] : [];
|
|
361
|
+
}
|
|
362
|
+
function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null, chips = [], page = null, inView = false, fit = false, onDetails, tapped, selection, groupActions, sort, onSort, serverSorts, total = true, subgroup = null, add, }) {
|
|
363
|
+
const [sorting, setSorting] = useState(() => sortingOf(sort, columns, serverSorts));
|
|
354
364
|
// A saved sort patched while the table is open (the assistant's card move) applies at once.
|
|
355
365
|
const savedSort = JSON.stringify(sort ?? null);
|
|
356
366
|
const savedSortRef = useRef(savedSort);
|
|
@@ -358,8 +368,8 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
358
368
|
if (savedSortRef.current === savedSort)
|
|
359
369
|
return;
|
|
360
370
|
savedSortRef.current = savedSort;
|
|
361
|
-
setSorting(sort
|
|
362
|
-
}, [savedSort, sort, columns]);
|
|
371
|
+
setSorting(sortingOf(sort, columns, serverSorts));
|
|
372
|
+
}, [savedSort, sort, columns, serverSorts]);
|
|
363
373
|
const paging = page !== null && !groupBy;
|
|
364
374
|
const pagination = {
|
|
365
375
|
pageIndex: paging ? page.index : 0,
|
|
@@ -451,6 +461,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
451
461
|
return computed.value(now[i] ?? f, r) ?? undefined;
|
|
452
462
|
},
|
|
453
463
|
sortUndefined: 'last',
|
|
464
|
+
enableSorting: !serverSorts || serverSorts.includes(f.key),
|
|
454
465
|
header: f.label,
|
|
455
466
|
cell: ({ row }) => {
|
|
456
467
|
const { shown: now, cellOf, inline, chips, computed } = latest.current;
|
|
@@ -463,12 +474,14 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
463
474
|
return [{ key: x.key, node: cellOf(x, row.original, true) }];
|
|
464
475
|
}) })) : (cellOf(field, row.original, chip));
|
|
465
476
|
},
|
|
466
|
-
})), [shownKeys, inlineOn]);
|
|
477
|
+
})), [shownKeys, inlineOn, serverSorts?.join(',')]);
|
|
467
478
|
const table = useTable({
|
|
468
479
|
features: SORTING,
|
|
469
480
|
data: rows,
|
|
470
481
|
columns: defs,
|
|
471
482
|
state: { sorting, pagination },
|
|
483
|
+
// A server's rows come sorted; the table only shows which column it sorted by.
|
|
484
|
+
manualSorting: !!serverSorts,
|
|
472
485
|
onSortingChange: (u) => {
|
|
473
486
|
const next = typeof u === 'function' ? u(sorting) : u;
|
|
474
487
|
setSorting(next);
|
|
@@ -604,7 +617,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
604
617
|
? 'ascending'
|
|
605
618
|
: h.column.getIsSorted() === 'desc'
|
|
606
619
|
? 'descending'
|
|
607
|
-
: undefined, children: [i === 0 && groups && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getToggleSortingHandler(), children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] })] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
|
|
620
|
+
: undefined, children: [i === 0 && groups && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getCanSort() ? h.column.getToggleSortingHandler() : undefined, children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] })] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
|
|
608
621
|
? groups.map(([key, members], gi) => {
|
|
609
622
|
const open = isOpen(key, gi);
|
|
610
623
|
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { className: ROLLUP, "data-group": key, children: [selection && (_jsx(TableCell, { className: cn('w-8 pr-0', PINNED_GROUP), children: _jsx(Checkbox, { checked: members.every((m) => selection.keys.has(keyOf(m.original)))
|
package/dist/ui/tabs.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { Tabs as TabsPrimitive } from 'radix-ui';
|
|
|
2
2
|
import type * as React from 'react';
|
|
3
3
|
declare function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
|
|
4
4
|
declare function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>): React.JSX.Element;
|
|
5
|
-
declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
|
|
5
|
+
declare function TabsTrigger({ className, onMouseDown, onKeyDown, onBlur, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
|
|
6
6
|
declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
|
|
7
7
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/dist/ui/tabs.js
CHANGED
|
@@ -13,8 +13,22 @@ function Tabs({ className, ...props }) {
|
|
|
13
13
|
function TabsList({ className, ...props }) {
|
|
14
14
|
return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", className: cn('inline-flex w-full items-center justify-start gap-6 overflow-x-auto overflow-y-hidden text-muted-foreground [scrollbar-width:none]', className), ...props }));
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Radix focuses a trigger from its own mousedown, which browsers count as :focus-visible, so a
|
|
17
|
+
// clicked tab drew the keyboard ring over its underline. A pointer press marks the trigger until
|
|
18
|
+
// the next key or blur; the ring shows only without that mark.
|
|
19
|
+
const pointerOn = (e) => e.currentTarget.setAttribute('data-pointer', '');
|
|
20
|
+
const pointerOff = (e) => e.currentTarget.removeAttribute('data-pointer');
|
|
21
|
+
function TabsTrigger({ className, onMouseDown, onKeyDown, onBlur, ...props }) {
|
|
22
|
+
return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", onMouseDown: (e) => {
|
|
23
|
+
pointerOn(e);
|
|
24
|
+
onMouseDown?.(e);
|
|
25
|
+
}, onKeyDown: (e) => {
|
|
26
|
+
pointerOff(e);
|
|
27
|
+
onKeyDown?.(e);
|
|
28
|
+
}, onBlur: (e) => {
|
|
29
|
+
pointerOff(e);
|
|
30
|
+
onBlur?.(e);
|
|
31
|
+
}, className: cn("relative inline-flex shrink-0 items-center gap-1.5 px-0 pt-0 pb-2 after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:rounded-full after:bg-transparent text-sm leading-4 font-medium whitespace-nowrap text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:rounded-sm focus-visible:not-data-[pointer]:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground data-[state=active]:after:bg-primary data-[state=active]:[&_svg]:text-primary [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props }));
|
|
18
32
|
}
|
|
19
33
|
function TabsContent({ className, ...props }) {
|
|
20
34
|
return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn('flex-1 outline-none', className), ...props }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
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.41",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|