@columenlabs/lumen-ui 0.1.74 → 0.1.76

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/index.d.ts CHANGED
@@ -40,6 +40,7 @@ export { LumenDrawer, type LumenDrawerProps } from './lumen-drawer.js';
40
40
  export { LumenCode, type LumenCodeProps, langOfPath } from './lumen-code.js';
41
41
  export { LumenCodeEditor, type LumenCodeEditorProps, type LumenCodeLang, } from './lumen-code-editor.js';
42
42
  export { RenameField, type RenameFieldProps } from './rename-field.js';
43
+ export { LumenStackedList, type LumenStackedListProps, LumenStackedRow, type LumenStackedRowProps, type StackedColumn, } from './lumen-stacked-list.js';
43
44
  export { ResetButton, type ResetButtonProps } from './reset-button.js';
44
45
  export { type ChartBand, type ChartMarker, GrowthChart, KEY_DATE_KINDS, KeyDateChips, type KeyDateKind, type KeyDateMarker, PALETTE, type Scale, type Series, benchColour, keyDateMarkers, paletteAt, } from './growth-chart.js';
45
46
  export { FoldGroup, NavGroup, type NavItem, type SectionItem, type SectionSubItem, SidebarSection, } from './nav.js';
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ export { LumenDrawer } from './lumen-drawer.js';
47
47
  export { LumenCode, langOfPath } from './lumen-code.js';
48
48
  export { LumenCodeEditor, } from './lumen-code-editor.js';
49
49
  export { RenameField } from './rename-field.js';
50
+ export { LumenStackedList, LumenStackedRow, } from './lumen-stacked-list.js';
50
51
  export { ResetButton } from './reset-button.js';
51
52
  export { GrowthChart, KEY_DATE_KINDS, KeyDateChips, PALETTE, benchColour, keyDateMarkers, paletteAt, } from './growth-chart.js';
52
53
  export { FoldGroup, NavGroup, SidebarSection, } from './nav.js';
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface StackedColumn {
3
+ key: string;
4
+ /** The header over the column; the list draws a header row when any column or `lead` has one. */
5
+ label?: string;
6
+ /** Any CSS track size: `9rem`, `minmax(6rem, auto)`. */
7
+ width: string;
8
+ /** `end` for numbers and controls, so header, values and totals share one edge. */
9
+ align?: 'start' | 'end';
10
+ /** Shown on a phone, stacked at the row's end; other columns are left out there. */
11
+ phone?: boolean;
12
+ }
13
+ export interface LumenStackedListProps {
14
+ columns: StackedColumn[];
15
+ /** The header over the names. */
16
+ lead?: string;
17
+ /** One level that never folds (a short list in a card): no chevron space, compact rows. */
18
+ flat?: boolean;
19
+ children: ReactNode;
20
+ 'aria-label'?: string;
21
+ className?: string;
22
+ }
23
+ export declare function LumenStackedList({ columns, lead, flat, children, className, ...aria }: LumenStackedListProps): import("react").JSX.Element;
24
+ export interface LumenStackedRowProps {
25
+ /** The row's name: drawn on one line (an ellipsis past `--lead-col-max`) and its accessible name. */
26
+ name: string;
27
+ /** Drawn instead of `name`, such as a rename field; `name` stays the accessible name. */
28
+ title?: ReactNode;
29
+ /** Right after the name: a source chip, a ✎ mark. */
30
+ adornments?: ReactNode;
31
+ /** One muted line under the name. */
32
+ meta?: ReactNode;
33
+ /** One value per column, by `StackedColumn.key`. */
34
+ cells?: Record<string, ReactNode>;
35
+ /** Icons after the name, shown on hover or focus only. */
36
+ actions?: ReactNode;
37
+ /** What needs the person (a sign-in that broke): shown at the top while the row is open. */
38
+ notice?: ReactNode;
39
+ /** Rows under this one: LumenStackedRows. */
40
+ children?: ReactNode;
41
+ defaultOpen?: boolean;
42
+ open?: boolean;
43
+ onOpenChange?: (open: boolean) => void;
44
+ /** `muted` dims the row (an item that is off). */
45
+ tone?: 'default' | 'muted';
46
+ }
47
+ export declare function LumenStackedRow({ name, title, adornments, meta, cells, actions, notice, children, defaultOpen, open: openProp, onOpenChange, tone, }: LumenStackedRowProps): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // LumenStackedList: rows that nest and fold, with columns that line up at every level (a source,
3
+ // the groups inside it, the items inside those). Each row is one line: a name (with chips or a mark
4
+ // after it) over an optional muted meta line, then the list's columns. A row with children or a
5
+ // notice has a chevron and starts folded; its notice (what needs the person) shows only while it is
6
+ // open. Row actions sit after the name and show on hover or focus only (DESIGN.md, Actions: icon
7
+ // first). Nothing about what the rows mean lives here: an app fills names, cells and actions.
8
+ // On a phone the columns marked `phone` stack at the row's end and the rest are left out.
9
+ import { ChevronRightIcon } from 'lucide-react';
10
+ import { createContext, useContext, useMemo, useState, } from 'react';
11
+ import { cn } from './lib/utils.js';
12
+ import { Collapsible, CollapsibleContent } from './ui/collapsible.js';
13
+ const Ctx = createContext(null);
14
+ export function LumenStackedList({ columns, lead, flat = false, children, className, ...aria }) {
15
+ const ctx = useMemo(() => ({ columns, depth: 0, flat }), [columns, flat]);
16
+ const style = {
17
+ '--stack-cols': `minmax(0,1fr) ${columns.map((c) => c.width).join(' ')}`,
18
+ };
19
+ const head = lead !== undefined || columns.some((c) => c.label);
20
+ return (_jsx(Ctx.Provider, { value: ctx, children: _jsxs("div", { className: cn('flex min-w-0 flex-col text-sm', className), style: style, children: [head && (_jsxs("div", { "aria-hidden": true, className: "grid grid-cols-(--stack-cols) items-center gap-x-3 px-3 pb-1.5 text-xs font-medium text-muted-foreground max-sm:hidden", children: [_jsx("span", { className: cn(!flat && 'pl-7.5'), children: lead }), _jsx(Cells, { columns: columns, cells: Object.fromEntries(columns.map((c) => [c.key, c.label])) })] })), _jsx("ul", { className: "flex flex-col [&>li:first-child>[data-stack-row]]:border-t-0", ...aria, children: children })] }) }));
21
+ }
22
+ function Cells({ columns, cells, }) {
23
+ return (_jsx("div", { className: "contents max-sm:flex max-sm:flex-col max-sm:items-end max-sm:gap-0.5", children: columns.map((c) => (_jsx("div", { className: cn('flex min-w-0 items-center', c.align === 'end' && 'justify-end text-right', !c.phone && 'max-sm:hidden'), children: cells?.[c.key] }, c.key))) }));
24
+ }
25
+ const isControl = (t) => t instanceof Element &&
26
+ t.closest('button, a, input, select, [role="button"], [role="combobox"], [data-stack-skip]') !==
27
+ null;
28
+ export function LumenStackedRow({ name, title, adornments, meta, cells, actions, notice, children, defaultOpen = false, open: openProp, onOpenChange, tone = 'default', }) {
29
+ const ctx = useContext(Ctx);
30
+ if (!ctx)
31
+ throw new Error('LumenStackedRow outside LumenStackedList');
32
+ const { columns, depth, flat } = ctx;
33
+ const [own, setOwn] = useState(defaultOpen);
34
+ const open = openProp ?? own;
35
+ const setOpen = (o) => {
36
+ if (openProp === undefined)
37
+ setOwn(o);
38
+ onOpenChange?.(o);
39
+ };
40
+ const has = Boolean(children) || Boolean(notice);
41
+ const inner = useMemo(() => ({ columns, depth: depth + 1, flat }), [columns, depth, flat]);
42
+ const indent = { paddingInlineStart: `calc(${depth} * var(--spacing) * 6)` };
43
+ const line = (_jsxs("div", { "data-stack-row": true, className: cn('group/stack grid grid-cols-(--stack-cols) items-center gap-x-3 border-t px-3 max-sm:grid-cols-[minmax(0,1fr)_auto]', depth === 0 && !flat ? 'min-h-14 py-2' : 'min-h-10 py-1.5', depth === 1 && 'bg-muted/40', has && 'cursor-pointer', tone === 'muted' && 'text-muted-foreground'), onClick: has ? (e) => !isControl(e.target) && setOpen(!open) : undefined, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", style: indent, children: [has ? (_jsx("button", { type: "button", "aria-expanded": open, "aria-label": `${open ? 'Fold' : 'Unfold'} ${name}`, className: "grid size-6 shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-muted", onClick: () => setOpen(!open), children: _jsx(ChevronRightIcon, { className: cn('size-4 transition-transform', open && 'rotate-90') }) })) : flat ? null : (_jsx("span", { className: "w-6 shrink-0", "aria-hidden": true })), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [title ?? (_jsx("span", { title: name, className: cn('max-w-(--lead-col-max) truncate', depth < 2 && 'font-medium'), children: name })), adornments, actions && (_jsx("span", { className: "flex shrink-0 items-center opacity-0 transition-opacity group-focus-within/stack:opacity-100 group-hover/stack:opacity-100", children: actions }))] }), meta && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: meta })] })] }), _jsx(Cells, { columns: columns, cells: cells })] }));
44
+ if (!has)
45
+ return _jsx("li", { children: line });
46
+ return (_jsx("li", { children: _jsxs(Collapsible, { open: open, onOpenChange: setOpen, children: [line, _jsxs(CollapsibleContent, { children: [notice && (_jsx("div", { className: "border-t px-3 py-2", style: {
47
+ paddingInlineStart: `calc(${depth + 1} * var(--spacing) * 6 + var(--spacing) * 3)`,
48
+ }, children: notice })), children && (_jsx(Ctx.Provider, { value: inner, children: _jsx("ul", { className: "flex flex-col", children: children }) }))] })] }) }));
49
+ }
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // A name renamed in place (for a broker link accounts; shared so
3
3
  // An app's bank accounts rename the same way): Enter or leaving the field saves, Escape undoes,
4
- // empty or ↺ goes back to the source's own name, shown as the placeholder.
4
+ // empty or ↺ goes back to the source's own name, shown as the placeholder. Quiet at rest
5
+ // (DESIGN.md, Actions: icon first): a renamed name keeps a ✎ mark, the rest shows on hover or focus.
5
6
  import { PencilIcon, RotateCcwIcon } from 'lucide-react';
6
7
  import { useState } from 'react';
8
+ import { cn } from './lib/utils.js';
7
9
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './ui/input-group.js';
8
10
  export function RenameField({ value, original, originLabel, onSave }) {
9
11
  const [typed, setTyped] = useState(value);
@@ -22,5 +24,5 @@ export function RenameField({ value, original, originLabel, onSave }) {
22
24
  setTyped(value);
23
25
  requestAnimationFrame(() => e.currentTarget?.blur());
24
26
  }
25
- }, className: "field-sizing-content min-w-0 px-2 text-sm" }), _jsxs(InputGroupAddon, { align: "inline-end", children: [_jsx(PencilIcon, { className: "size-3" }), value !== original && (_jsx(InputGroupButton, { size: "icon-xs", "aria-label": `Reset to ${original}`, title: `Reset to ${original}`, onClick: () => onSave(null), children: _jsx(RotateCcwIcon, {}) }))] })] }));
27
+ }, className: "field-sizing-content min-w-0 px-2 text-sm" }), _jsxs(InputGroupAddon, { align: "inline-end", children: [_jsx(PencilIcon, { className: cn('size-3 group-focus-within/input-group:opacity-100 group-hover/input-group:opacity-100', value === original && 'opacity-0') }), value !== original && (_jsx(InputGroupButton, { className: "opacity-0 group-focus-within/input-group:opacity-100 group-hover/input-group:opacity-100", size: "icon-xs", "aria-label": `Reset to ${original}`, title: `Reset to ${original}`, onClick: () => onSave(null), children: _jsx(RotateCcwIcon, {}) }))] })] }));
26
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.74",
3
+ "version": "0.1.76",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "@codemirror/lang-javascript": "6.2.5",
46
46
  "@codemirror/lang-json": "6.0.2",
47
47
  "@codemirror/lang-sql": "6.10.0",
48
- "@columenlabs/lumen-ui-types": "0.1.74",
48
+ "@columenlabs/lumen-ui-types": "0.1.76",
49
49
  "@dnd-kit/core": "6.3.1",
50
50
  "@dnd-kit/sortable": "10.0.0",
51
51
  "@dnd-kit/utilities": "3.2.2",