@columenlabs/lumen-ui 0.1.122 → 0.1.124
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.d.ts +3 -0
- package/dist/lumen-layout.js +5 -4
- package/dist/lumen-view-card.d.ts +3 -1
- package/dist/lumen-view-card.js +2 -2
- package/dist/lumen-view.d.ts +6 -1
- package/dist/lumen-view.js +30 -4
- package/dist/theme.css +33 -0
- package/package.json +2 -2
package/dist/lumen-layout.d.ts
CHANGED
|
@@ -110,6 +110,9 @@ export interface LumenLayoutViewProps<T> {
|
|
|
110
110
|
selection?: LumenViewProps<T>['selection'];
|
|
111
111
|
/** A row click opens it (a page of its own) instead of the details drawer. */
|
|
112
112
|
onOpen?: (row: T) => void;
|
|
113
|
+
/** The key of the row `onOpen` shows in a drawer of the app's own (a docked one), so the table
|
|
114
|
+
* marks it selected; the layout's own details drawer marks its row itself. */
|
|
115
|
+
openKey?: string | null;
|
|
113
116
|
/** The `rowLink` value tapped when the page opens, so linked cards start narrowed to one row
|
|
114
117
|
* (a playbook's Results open on its Default variant). */
|
|
115
118
|
rowPick?: string;
|
package/dist/lumen-layout.js
CHANGED
|
@@ -211,12 +211,12 @@ function CardsLayout({ crumbs, actions, meta, status, intro, cards, lead, fit })
|
|
|
211
211
|
* columns, sort and grouping over the view's rows (and formulas), its own chips and tapped row,
|
|
212
212
|
* shared with the cards of its colour like any card. A Columns change saves into its layout.
|
|
213
213
|
*/
|
|
214
|
-
function LayoutTable({ card, base, config, rows, load, state, onDetails, controls, onLayout, }) {
|
|
214
|
+
function LayoutTable({ card, base, config, rows, load, state, onDetails, openKey, controls, onLayout, }) {
|
|
215
215
|
const own = useMemo(() => tableConfigOf(config, { ...card, table: card.table ?? tableLayoutOf(config) }), [config, card]);
|
|
216
216
|
const domain = useMemo(() => withViewFields(base, own), [base, own]);
|
|
217
|
-
return (_jsx(LumenViewCard, { id: card.id, domain: domain, config: own, rows: rows, load: load, state: state, title: card.title ?? config.name, onDetails: onDetails, controls: controls, shown: card.controls, onColumns: onLayout && ((columns) => onLayout(tableLayoutOf({ ...own, columns }))) }));
|
|
217
|
+
return (_jsx(LumenViewCard, { id: card.id, domain: domain, config: own, rows: rows, load: load, state: state, title: card.title ?? config.name, onDetails: onDetails, openKey: openKey, controls: controls, shown: card.controls, onColumns: onLayout && ((columns) => onLayout(tableLayoutOf({ ...own, columns }))) }));
|
|
218
218
|
}
|
|
219
|
-
function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead, domain: base, config, rows, onConfigChange: saveConfig, editable: canEdit, editor, onDuplicate, onDelete, onReset, load, state, tile, rowTitle, viewId, onShownRows, onSummaryRows, selection, onOpen, rowPick: initialPick, groupActions, onTokens, paging, viewTools, columnPicker, hideFilter, save: saveMode = 'document', }) {
|
|
219
|
+
function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead, domain: base, config, rows, onConfigChange: saveConfig, editable: canEdit, editor, onDuplicate, onDelete, onReset, load, state, tile, rowTitle, viewId, onShownRows, onSummaryRows, selection, onOpen, openKey: openedKey, rowPick: initialPick, groupActions, onTokens, paging, viewTools, columnPicker, hideFilter, save: saveMode = 'document', }) {
|
|
220
220
|
const onConfigChange = canEdit === false ? undefined : saveConfig;
|
|
221
221
|
// The registry's join fields for the keys the rows carry (Symbol, Account), then the view's own
|
|
222
222
|
// formulas and formats.
|
|
@@ -260,6 +260,7 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
260
260
|
useEffect(() => onServerSort?.(heldSort ?? null), [onServerSort, heldSort]);
|
|
261
261
|
const [details, setDetails] = useState(null);
|
|
262
262
|
const onDetails = onOpen ?? (domain.details?.length ? setDetails : undefined);
|
|
263
|
+
const openKey = onOpen ? (openedKey ?? null) : details === null ? null : domain.rowKey(details);
|
|
263
264
|
// The assistant sees the view, its data domain, its filter, how many rows show, the first of them and the
|
|
264
265
|
// row open in the drawer (it saw only a row count and could not answer).
|
|
265
266
|
useAssistantNote('view', {
|
|
@@ -551,7 +552,7 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
551
552
|
return {
|
|
552
553
|
...place,
|
|
553
554
|
wide: isTableCard(c) && c.w === GRID_COLS && c.h === undefined,
|
|
554
|
-
node: c.source ? (_jsx(SourcedCard, { card: c, name: config.name, controls: controls })) : isTableCard(c) && c.id !== viewCardId ? (_jsx(LayoutTable, { card: c, base: joins.domain, config: config, rows: c.filter.length ? filterByTokens(rows, widgetFields, c.filter) : rows, load: scope(c.id, load), state: state, onDetails: onDetails, controls: controls, onLayout: onConfigChange ? (table) => putCard({ ...c, table }) : undefined })) : isTableCard(c) ? (_jsx(LumenView, { domain: domain, config: config, rows: viewRows, total: rows.length, paging: paging, groupBy: group.groupField, pivot: pivot, subgroup: group.sub, load: scope(c.id, load), state: state, title: c.title ?? config.name, selection: selection, onDetails: rowPick.onTap ?? onDetails, tapped: rowPick.tapped, groupActions: groupActions, controls: controls, inLayout: true, tools: viewTools, filter: viewFilter, filterCount: filter.tokens.length, onClearFilter: filter.clear, strip: _jsx(ConnectOffer, { domain: domain, rows: rows, config: config, read: joins.read, cardKey: viewId ?? config.name, onConnect: onConfigChange ? connect : undefined }), link: cardLink(c.id, viewFollows, viewColor ? filter.tokens.length : 0), shown: c.controls, onColumns: onConfigChange && columnPicker !== false
|
|
555
|
+
node: c.source ? (_jsx(SourcedCard, { card: c, name: config.name, controls: controls })) : isTableCard(c) && c.id !== viewCardId ? (_jsx(LayoutTable, { card: c, base: joins.domain, config: config, rows: c.filter.length ? filterByTokens(rows, widgetFields, c.filter) : rows, load: scope(c.id, load), state: state, onDetails: onDetails, openKey: openKey, controls: controls, onLayout: onConfigChange ? (table) => putCard({ ...c, table }) : undefined })) : isTableCard(c) ? (_jsx(LumenView, { domain: domain, config: config, rows: viewRows, total: rows.length, paging: paging, groupBy: group.groupField, pivot: pivot, subgroup: group.sub, load: scope(c.id, load), state: state, title: c.title ?? config.name, selection: selection, onDetails: rowPick.onTap ?? onDetails, tapped: rowPick.tapped, openKey: openKey, groupActions: groupActions, controls: controls, inLayout: true, tools: viewTools, filter: viewFilter, filterCount: filter.tokens.length, onClearFilter: filter.clear, strip: _jsx(ConnectOffer, { domain: domain, rows: rows, config: config, read: joins.read, cardKey: viewId ?? config.name, onConnect: onConfigChange ? connect : undefined }), link: cardLink(c.id, viewFollows, viewColor ? filter.tokens.length : 0), shown: c.controls, onColumns: onConfigChange && columnPicker !== false
|
|
555
556
|
? (columns) => onConfigChange({ ...config, columns })
|
|
556
557
|
: undefined, onAddColumn: addColumn, sort: heldSort, onSort: setHeldSort })) : (_jsx(LumenSavedWidget, { widget: c, title: cardTitle(c, config.name), domain: domain, rows: narrow(rows, followed(c.id)), link: cardLink(c.id, followed(c.id)), fields: widgetFields, merge: mergeFlat, onDetails: onDetails, load: scope(c.id, load), tiles: 'tile' in c
|
|
557
558
|
? cardOf(c)
|
|
@@ -22,6 +22,8 @@ export interface LumenViewCardProps<T> {
|
|
|
22
22
|
state?: ReactNode;
|
|
23
23
|
/** A row click (pick a version, open a drawer). */
|
|
24
24
|
onDetails?: (row: T) => void;
|
|
25
|
+
/** The row whose details are open, marked selected (LumenView `openKey`). */
|
|
26
|
+
openKey?: string | null;
|
|
25
27
|
/** Its place in the layout: the layout's `frame` hands these over. */
|
|
26
28
|
controls?: CardControls;
|
|
27
29
|
/** Its saved controls (Edit widget ▸ Controls), on a saved page's table. */
|
|
@@ -32,4 +34,4 @@ export interface LumenViewCardProps<T> {
|
|
|
32
34
|
* header picked (`{key: 'book', value: 'Live'}`), like a code card's `publishes`. */
|
|
33
35
|
publishes?: FilterToken[];
|
|
34
36
|
}
|
|
35
|
-
export declare function LumenViewCard<T extends RowData>({ id, domain: base, config: saved, rows, load, title, tools, note, state, onDetails, controls, shown: savedControls, onColumns, publishes, }: LumenViewCardProps<T>): import("react").JSX.Element;
|
|
37
|
+
export declare function LumenViewCard<T extends RowData>({ id, domain: base, config: saved, rows, load, title, tools, note, state, onDetails, openKey, controls, shown: savedControls, onColumns, publishes, }: LumenViewCardProps<T>): import("react").JSX.Element;
|
package/dist/lumen-view-card.js
CHANGED
|
@@ -8,7 +8,7 @@ import { followedBy, followsText, keyFilterFields, useLinks, usePublishLinks, us
|
|
|
8
8
|
import { LumenView } from './lumen-view.js';
|
|
9
9
|
import { cardFilterFields } from './lumen-widget.js';
|
|
10
10
|
import { filterFieldsOf } from './view-model.js';
|
|
11
|
-
export function LumenViewCard({ id, domain: base, config: saved, rows, load, title, tools, note, state, onDetails, controls, shown: savedControls, onColumns, publishes, }) {
|
|
11
|
+
export function LumenViewCard({ id, domain: base, config: saved, rows, load, title, tools, note, state, onDetails, openKey, controls, shown: savedControls, onColumns, publishes, }) {
|
|
12
12
|
const [config, setConfig] = useState(saved);
|
|
13
13
|
// A saved layout that changes (an edit, the assistant move) is drawn at once.
|
|
14
14
|
const savedKey = JSON.stringify(saved);
|
|
@@ -43,7 +43,7 @@ export function LumenViewCard({ id, domain: base, config: saved, rows, load, tit
|
|
|
43
43
|
linked: color ? filter.tokens.length : 0,
|
|
44
44
|
};
|
|
45
45
|
const on = (k) => controlOn({ kind: 'table', controls: savedControls }, k);
|
|
46
|
-
return (_jsx(LumenView, { domain: domain, config: config, rows: shown, total: rows.length, groupBy: group.groupField, pivot: group.pivot, subgroup: group.sub, load: load, state: state, onDetails: pick.onTap ?? onDetails, tapped: pick.tapped, title: title ?? config.name, inLayout: true, cardId: cardId, controls: controls, tools: tools, note: note, filter: _jsxs(_Fragment, { children: [on('filter') && (_jsx(LumenTokenizedFilter, { filter: filter, label: `Filter ${config.name}`, className: "min-w-0 flex-1", link: link || undefined })), on('group') && group.button] }), filterCount: filter.tokens.length, onClearFilter: filter.clear, link: link || undefined, shown: savedControls, onColumns: (columns) => {
|
|
46
|
+
return (_jsx(LumenView, { domain: domain, config: config, rows: shown, total: rows.length, groupBy: group.groupField, pivot: group.pivot, subgroup: group.sub, load: load, state: state, onDetails: pick.onTap ?? onDetails, tapped: pick.tapped, openKey: openKey, title: title ?? config.name, inLayout: true, cardId: cardId, controls: controls, tools: tools, note: note, filter: _jsxs(_Fragment, { children: [on('filter') && (_jsx(LumenTokenizedFilter, { filter: filter, label: `Filter ${config.name}`, className: "min-w-0 flex-1", link: link || undefined })), on('group') && group.button] }), filterCount: filter.tokens.length, onClearFilter: filter.clear, link: link || undefined, shown: savedControls, onColumns: (columns) => {
|
|
47
47
|
setConfig((c) => ({ ...c, columns }));
|
|
48
48
|
onColumns?.(columns);
|
|
49
49
|
} }));
|
package/dist/lumen-view.d.ts
CHANGED
|
@@ -132,6 +132,10 @@ export interface LumenViewProps<T> {
|
|
|
132
132
|
onDetails?: (row: T) => void;
|
|
133
133
|
/** Rows picked by a tap (a linked table's row filter): a muted fill, no border. */
|
|
134
134
|
tapped?: (row: T) => boolean;
|
|
135
|
+
/** The key of the row whose details are open (a docked drawer beside the table): the row reads
|
|
136
|
+
* as selected (an accent fill and edge bar, `aria-selected`) and scrolls into view when it
|
|
137
|
+
* shows. ↑ and ↓ on a focused row move to the next one, and open it while a row is open. */
|
|
138
|
+
openKey?: string | null;
|
|
135
139
|
/** Buttons at the end of a group row's label (pin a group), given its key and rows. */
|
|
136
140
|
groupActions?: (key: string, rows: T[]) => ReactNode;
|
|
137
141
|
/** Its place in a LumenLayout: the layout's edit state and the card's ⋯ menu. */
|
|
@@ -184,7 +188,7 @@ export interface AddColumn {
|
|
|
184
188
|
* and ⋯, and its own filter and Group (they narrow this table only); its body
|
|
185
189
|
* the table (grouped one level, or the pivot) with the loading states.
|
|
186
190
|
*/
|
|
187
|
-
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, onClearFilter, strip, link, note, onColumns, onAddColumn, sort, onSort, shown, }: LumenViewProps<T>): import("react").JSX.Element;
|
|
191
|
+
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, openKey, groupActions, controls, inLayout: inScroller, cardId, tools, filter, filterCount, onClearFilter, strip, link, note, onColumns, onAddColumn, sort, onSort, shown, }: LumenViewProps<T>): import("react").JSX.Element;
|
|
188
192
|
/** A field's colour for its header: its topic's source (a broker green, an app brown) where
|
|
189
193
|
* the group names one, else its group's (Holding, Symbol, Account, Formulas). */
|
|
190
194
|
export declare function colorOf<T>(domain: ViewDomain<T>, f: ViewField<T> | undefined): string | null;
|
|
@@ -211,6 +215,7 @@ export interface LumenTableProps<T> {
|
|
|
211
215
|
fit?: boolean;
|
|
212
216
|
onDetails?: (row: T) => void;
|
|
213
217
|
tapped?: LumenViewProps<T>['tapped'];
|
|
218
|
+
openKey?: LumenViewProps<T>['openKey'];
|
|
214
219
|
selection?: LumenViewProps<T>['selection'];
|
|
215
220
|
groupActions?: LumenViewProps<T>['groupActions'];
|
|
216
221
|
/** The view's default sort; a header click changes it for the visit. */
|
package/dist/lumen-view.js
CHANGED
|
@@ -224,7 +224,7 @@ async function downloadCsv({ name, columns, rows, groupBy, sort, hidden, }) {
|
|
|
224
224
|
* and ⋯, and its own filter and Group (they narrow this table only); its body
|
|
225
225
|
* the table (grouped one level, or the pivot) with the loading states.
|
|
226
226
|
*/
|
|
227
|
-
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, onClearFilter, strip, link, note, onColumns, onAddColumn, sort = config.sort, onSort, shown, }) {
|
|
227
|
+
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, openKey, groupActions, controls, inLayout: inScroller = false, cardId = 'view', tools, filter, filterCount, onClearFilter, strip, link, note, onColumns, onAddColumn, sort = config.sort, onSort, shown, }) {
|
|
228
228
|
// Its controls (Edit widget ▸ Controls): Pager, Columns and Download here.
|
|
229
229
|
const on = (k) => controlOn({ kind: 'table', controls: shown }, k);
|
|
230
230
|
const cell = useGridCell();
|
|
@@ -338,7 +338,7 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
|
|
|
338
338
|
const pager = turn ? (_jsx(LumenPager, { ...turn })) : served ? (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: served.list ? `${served.list.length.toLocaleString()}${served.more ? '+' : ''} groups` : '' })) : (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: count }));
|
|
339
339
|
// The page footer (`pagination.footer`): under the table while there is more than one page.
|
|
340
340
|
const foot = turn && pagination.footer && !state && (turn.more || turn.total > turn.size) ? (_jsx(LumenPageFooter, { total: turn.total, page: turn.page, size: turn.size, onPage: turn.onPage, more: turn.more })) : null;
|
|
341
|
-
const table = pivot && config.pivot && !state ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: state ? [] : 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: sorted, serverSorts: server ? (server.sorts ?? []) : undefined, serverGroups: state ? null : served, emptyState: state, onClearFilter: filtered ? onClearFilter : undefined, total: config.total !== false, add: onAddColumn || onColumns
|
|
341
|
+
const table = pivot && config.pivot && !state ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: state ? [] : tableRows, groupBy: groupBy, subgroup: sub, chips: chips, page: paging ? { index: page, size: pageSize } : null, inView: inLayout, fit: fit, onDetails: onDetails, tapped: tapped, openKey: openKey, selection: selection, groupActions: groupActions, sort: sort, onSort: sorted, serverSorts: server ? (server.sorts ?? []) : undefined, serverGroups: state ? null : served, emptyState: state, onClearFilter: filtered ? onClearFilter : undefined, total: config.total !== false, add: onAddColumn || onColumns
|
|
342
342
|
? { ...onAddColumn, field: onColumns && { domain, columns, onColumns } }
|
|
343
343
|
: undefined }));
|
|
344
344
|
return (_jsx(LumenWidget, { id: cardId, kind: "table", style: "table", title: title, phoneTitle: false, controls: controls, tools: blank ? (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",
|
|
@@ -445,7 +445,7 @@ function sortingOf(sort, columns, serverSorts) {
|
|
|
445
445
|
return [];
|
|
446
446
|
return columns.some((f) => f.key === sort.key) ? [{ id: sort.key, desc: sort.desc }] : [];
|
|
447
447
|
}
|
|
448
|
-
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, serverGroups = null, emptyState, onClearFilter, }) {
|
|
448
|
+
function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null, chips = [], page = null, inView = false, fit = false, onDetails, tapped, openKey = null, selection, groupActions, sort, onSort, serverSorts, total = true, subgroup = null, add, serverGroups = null, emptyState, onClearFilter, }) {
|
|
449
449
|
const isMobile = useIsMobile();
|
|
450
450
|
const [sorting, setSorting] = useState(() => sortingOf(sort, columns, serverSorts));
|
|
451
451
|
// A saved sort patched while the table is open (the assistant's card move) applies at once.
|
|
@@ -734,8 +734,30 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
734
734
|
const partRow = (p, name) => (_jsxs(TableRow, { className: cn('bg-muted/30 text-muted-foreground hover:bg-muted', onDetails && 'cursor-pointer'), onClick: onDetails ? () => onDetails(p) : undefined, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, i === 0 && 'pl-12'), children: i === 0 ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CornerDownRightIcon, { className: "size-3.5 shrink-0" }), _jsx(CappedCell, { max: capOf(f, 0), children: name
|
|
735
735
|
? name(p)
|
|
736
736
|
: cellOf(f, p, chips.some((c) => c.key === f.key)) })] })) : (cellOf(f, p, chips.some((c) => c.key === f.key))) }, f.key))), add && _jsx(TableCell, {})] }, `part:${keyOf(p)}`));
|
|
737
|
+
// ↑ and ↓ step to the next data row (past part and panel rows); while a row's details are
|
|
738
|
+
// open, the step opens that row too, so the docked drawer follows the focus.
|
|
739
|
+
const step = (from, down) => {
|
|
740
|
+
let n = down ? from.nextElementSibling : from.previousElementSibling;
|
|
741
|
+
while (n && !(n instanceof HTMLElement && n.dataset.row !== undefined))
|
|
742
|
+
n = down ? n.nextElementSibling : n.previousElementSibling;
|
|
743
|
+
if (!(n instanceof HTMLElement))
|
|
744
|
+
return;
|
|
745
|
+
n.focus();
|
|
746
|
+
const next = openKey === null ? undefined : sorted.find((x) => keyOf(x.original) === n.dataset.row);
|
|
747
|
+
if (next && onDetails)
|
|
748
|
+
onDetails(next.original);
|
|
749
|
+
};
|
|
750
|
+
// The open row scrolls into view when it shows: opened by keyboard, or a page that holds it.
|
|
751
|
+
const pageKeys = sorted.map((r) => keyOf(r.original)).join('\n');
|
|
752
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: scrolls when the open row or the shown rows change, not on every render.
|
|
753
|
+
useEffect(() => {
|
|
754
|
+
if (openKey === null)
|
|
755
|
+
return;
|
|
756
|
+
const el = box.current?.querySelector(`tr[data-row="${CSS.escape(openKey)}"]`);
|
|
757
|
+
el?.scrollIntoView({ block: 'nearest' });
|
|
758
|
+
}, [openKey, pageKeys, page?.index]);
|
|
737
759
|
// The whole row opens the details drawer.
|
|
738
|
-
const dataRow = (r) => (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": domain.rowKey(r.original), "data-state": tapped?.(r.original) ? 'selected' : undefined, className: cn('group/row', onDetails && 'cursor-pointer'), tabIndex: onDetails ? 0 : undefined, onClick: onDetails ? () => onDetails(r.original) : undefined, onKeyDown: onDetails
|
|
760
|
+
const dataRow = (r) => (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": domain.rowKey(r.original), "data-state": tapped?.(r.original) ? 'selected' : undefined, "aria-selected": openKey === null ? undefined : openKey === keyOf(r.original), className: cn('group/row', onDetails && 'cursor-pointer'), tabIndex: onDetails ? 0 : undefined, onClick: onDetails ? () => onDetails(r.original) : undefined, onKeyDown: onDetails
|
|
739
761
|
? (e) => {
|
|
740
762
|
if (e.target !== e.currentTarget)
|
|
741
763
|
return;
|
|
@@ -743,6 +765,10 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
743
765
|
e.preventDefault();
|
|
744
766
|
onDetails(r.original);
|
|
745
767
|
}
|
|
768
|
+
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
769
|
+
e.preventDefault();
|
|
770
|
+
step(e.currentTarget, e.key === 'ArrowDown');
|
|
771
|
+
}
|
|
746
772
|
}
|
|
747
773
|
: undefined, children: [selectCell(keyOf(r.original), keyOf(r.original)), r.getAllCells().map((c, i) => (_jsx(TableCell, { ...col(shown[i], groupBy && i === 0 && 'pl-8'), style: { background: shown[i]?.tint?.(r.original) }, children: i === 0 && nests ? (_jsxs("span", { className: "flex items-center gap-1", children: [opens(r.original) ? (chevron(keyOf(r.original), openLabel())) : (_jsx("span", { className: "-ml-1 size-6 shrink-0" })), flexRender(c.column.columnDef.cell, c.getContext())] })) : i === 0 && flat ? (flatName(r.original)) : (flexRender(c.column.columnDef.cell, c.getContext())) }, c.id))), add && _jsx(TableCell, {})] }, domain.rowKey(r.original)), openParts.has(keyOf(r.original)) &&
|
|
748
774
|
partsOf(r.original).map((p) => partRow(p, subgroup?.name)), openParts.has(keyOf(r.original)) && domain.panel ? (_jsx(TableRow, { "data-slot": "row-panel", className: "bg-muted/20 hover:bg-muted/20", children: _jsx(TableCell, { colSpan: span, className: "p-0 whitespace-normal", children: _jsx("div", { className: "sticky left-0 w-[min(56rem,calc(100vw-2rem))] max-w-full px-4 py-3 md:pl-10", children: panelOf(r.original) }) }) }, `panel:${keyOf(r.original)}`)) : null] }, domain.rowKey(r.original)));
|
package/dist/theme.css
CHANGED
|
@@ -118,6 +118,9 @@
|
|
|
118
118
|
--popover: #fbfcfe;
|
|
119
119
|
--popover-foreground: #121826;
|
|
120
120
|
--primary: #2563eb;
|
|
121
|
+
/* The open row's fill (LumenView `openKey`). */
|
|
122
|
+
--row-open: color-mix(in oklab, var(--primary) 14%, var(--card));
|
|
123
|
+
--row-open-hover: color-mix(in oklab, var(--primary) 20%, var(--card));
|
|
121
124
|
--primary-foreground: #f5f8ff;
|
|
122
125
|
--secondary: #e9edf5;
|
|
123
126
|
--secondary-foreground: #121826;
|
|
@@ -225,6 +228,8 @@
|
|
|
225
228
|
--popover: #121826;
|
|
226
229
|
--popover-foreground: #e6eaf2;
|
|
227
230
|
--primary: #5b8cff;
|
|
231
|
+
--row-open: color-mix(in oklab, var(--primary) 24%, var(--card));
|
|
232
|
+
--row-open-hover: color-mix(in oklab, var(--primary) 30%, var(--card));
|
|
228
233
|
--primary-foreground: #0b0f17;
|
|
229
234
|
--secondary: #1c2435;
|
|
230
235
|
--secondary-foreground: #e6eaf2;
|
|
@@ -535,6 +540,34 @@
|
|
|
535
540
|
}
|
|
536
541
|
}
|
|
537
542
|
|
|
543
|
+
/* The row whose details are open (LumenView `openKey`, a docked drawer beside the table): an
|
|
544
|
+
accent fill and an accent bar on its leading edge, readable in light and dark. It scrolls clear
|
|
545
|
+
of the pinned header and the page footer when it comes into view. */
|
|
546
|
+
tr[aria-selected="true"] {
|
|
547
|
+
scroll-margin-top: calc(var(--pin-top, 0px) + var(--row-h));
|
|
548
|
+
scroll-margin-bottom: var(--row-h);
|
|
549
|
+
}
|
|
550
|
+
tr[aria-selected="true"] > td {
|
|
551
|
+
background-color: var(--row-open);
|
|
552
|
+
}
|
|
553
|
+
tr[aria-selected="true"]:hover > td {
|
|
554
|
+
background-color: var(--row-open-hover);
|
|
555
|
+
}
|
|
556
|
+
tr[aria-selected="true"] > td:first-child {
|
|
557
|
+
box-shadow: inset 3px 0 0 var(--primary);
|
|
558
|
+
}
|
|
559
|
+
/* A phone's card row is one flex box: the fill and bar go on the row itself. */
|
|
560
|
+
@media (max-width: 639px) {
|
|
561
|
+
table[data-cards] tr[aria-selected="true"] {
|
|
562
|
+
background-color: var(--row-open);
|
|
563
|
+
box-shadow: inset 3px 0 0 var(--primary);
|
|
564
|
+
}
|
|
565
|
+
table[data-cards] tr[aria-selected="true"] > td {
|
|
566
|
+
background-color: transparent;
|
|
567
|
+
box-shadow: none;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
538
571
|
@layer utilities {
|
|
539
572
|
.bg-gap-hatch {
|
|
540
573
|
background: repeating-linear-gradient(135deg, var(--gap) 0 3px, var(--gap-soft) 3px 6px);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.124",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.124",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|