@columenlabs/lumen-ui 0.1.153 → 0.1.155

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
@@ -38,7 +38,7 @@ export { type CoverageBlock, type CoverageData, type CoverageFact, type Coverage
38
38
  export { type CalendarFact, LumenCoverageCalendarMap, type LumenCoverageCalendarMapProps, } from './coverage-calendar-map.js';
39
39
  export { DetailsTable, type DetailsTableProps, FactsTable } from './details-table.js';
40
40
  export { DrawerSection, type DrawerSectionProps } from './drawer-section.js';
41
- export { LumenDrawer, type LumenDrawerProps } from './lumen-drawer.js';
41
+ export { type DrawerAction, LumenDrawer, type LumenDrawerProps } from './lumen-drawer.js';
42
42
  export { LumenCode, type LumenCodeProps, langOfPath } from './lumen-code.js';
43
43
  export { LumenCodeEditor, type LumenCodeEditorProps, type LumenCodeLang, } from './lumen-code-editor.js';
44
44
  export { RenameField, type RenameFieldProps } from './rename-field.js';
@@ -1,4 +1,13 @@
1
1
  import { type ReactNode } from 'react';
2
+ import { type IconButtonProps } from './icon-button.js';
3
+ /**
4
+ * One of a drawer's actions, as data: the drawer draws it as an icon button with its name in the
5
+ * tooltip, so a drawer's actions are never text. Destructive ones go first, as the footer lists them.
6
+ */
7
+ export type DrawerAction = Pick<IconButtonProps, 'icon' | 'label' | 'onClick' | 'busy' | 'disabled' | 'pressed' | 'to' | 'href'> & {
8
+ /** Delete, remove: drawn in the destructive colour. */
9
+ destructive?: boolean;
10
+ };
2
11
  /** A drawer's width on desktop, as the resizable panel takes it ("32%", "320px"). */
3
12
  type Size = `${number}%` | `${number}px`;
4
13
  export interface LumenDrawerProps {
@@ -9,7 +18,9 @@ export interface LumenDrawerProps {
9
18
  description?: ReactNode;
10
19
  /** Stacked DrawerSections; the drawer pads and scrolls them. */
11
20
  children: ReactNode;
12
- /** The footer's actions, right-aligned, icon first (`IconButton`, `SaveButton`); no footer when left out. */
21
+ /** The footer's actions as data, right-aligned in order, before `footer`: always icons. */
22
+ actions?: readonly DrawerAction[];
23
+ /** Last in the footer, after `actions`: the `SaveButton`. No footer when this, `actions` and `footerStart` are left out. */
13
24
  footer?: ReactNode;
14
25
  /** Left side of the footer: a count or status line ("12 releases"). */
15
26
  footerStart?: ReactNode;
@@ -34,5 +45,5 @@ export interface LumenDrawerProps {
34
45
  * phone it shows only when there are actions, as the sheet's X closes it). With `dock` it sits beside
35
46
  * the page on desktop instead (DockedDrawer), for a list whose rows each open it.
36
47
  */
37
- export declare function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize, minSize, maxSize, dock, }: LumenDrawerProps): import("react").JSX.Element | null;
48
+ export declare function LumenDrawer({ open, onClose, title, description, children, actions, footer, footerStart, defaultSize, minSize, maxSize, dock, }: LumenDrawerProps): import("react").JSX.Element | null;
38
49
  export {};
@@ -21,7 +21,7 @@ const DrawerPanels = lazyPart(() => loadPanels().then((m) => m.DrawerPanels));
21
21
  * phone it shows only when there are actions, as the sheet's X closes it). With `dock` it sits beside
22
22
  * the page on desktop instead (DockedDrawer), for a list whose rows each open it.
23
23
  */
24
- export function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', dock, }) {
24
+ export function LumenDrawer({ open, onClose, title, description, children, actions, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', dock, }) {
25
25
  const isMobile = useIsMobile();
26
26
  const slot = useDockSlot();
27
27
  // Fetched once the page is idle, so the first drawer opened on a desktop has its panels ready.
@@ -35,7 +35,8 @@ export function LumenDrawer({ open, onClose, title, description, children, foote
35
35
  const body = _jsx("div", { className: "flex flex-col px-4 pb-4", children: children });
36
36
  // A drawer with actions ends on a footer, styled like a dialog's: the body scrolls above it and
37
37
  // ends on a clear edge. Without actions it has none: the header's × already closes it.
38
- const foot = (footer || footerStart) && (_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 })] }));
38
+ const acts = actions?.length ? actions : null;
39
+ const foot = (acts || footer || footerStart) && (_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 })), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [acts?.map(({ destructive, ...a }) => (_jsx(IconButton, { ...a, className: destructive ? 'text-destructive hover:text-destructive' : undefined }, a.label))), footer] })] }));
39
40
  if (dock && !isMobile && slot)
40
41
  return open
41
42
  ? createPortal(_jsx(DockedDrawer, { dock: dock, title: title, description: description, onClose: onClose, defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, body: body, foot: foot }), slot)
@@ -9,7 +9,9 @@ export interface LumenFormField {
9
9
  /** `text` by default. `secret` hides what is typed; `select` and `multi` are LumenSelector. */
10
10
  kind?: 'text' | 'secret' | 'number' | 'date' | 'textarea' | 'select' | 'multi';
11
11
  required?: boolean;
12
- /** A line under the field. */
12
+ /** More about the field, behind an (i) beside its label (`InfoTip`). Most fields need none. */
13
+ info?: ReactNode;
14
+ /** @deprecated Use `info`: drawn the same way, as an (i) beside the label, never a line under the field. */
13
15
  hint?: ReactNode;
14
16
  placeholder?: string;
15
17
  /** `select` and `multi`. */
@@ -1,14 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // LumenForm:
3
- // a list of fields in, a laid-out form out. Label, input, hint and error sit the same way in every
3
+ // a list of fields in, a laid-out form out. Label, (i), input and error sit the same way in every
4
4
  // app; each field's own check runs as the person types, and Save is LumenShell's SaveButton, which
5
5
  // lists what stands in the way and takes the person to it. A secret already saved shows masked and
6
6
  // is kept while its box stays empty. LumenForm only puts the kit's parts together.
7
+ import { XIcon } from 'lucide-react';
7
8
  import { useId, useState } from 'react';
8
9
  import { SaveButton } from './form-issues.js';
10
+ import { IconButton } from './icon-button.js';
11
+ import { InfoTip } from './info-tip.js';
9
12
  import { LumenSelector } from './lumen-selector.js';
10
- import { Button } from './ui/button.js';
11
- import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from './ui/field.js';
13
+ import { Field, FieldError, FieldGroup, FieldLabel } from './ui/field.js';
12
14
  import { Input } from './ui/input.js';
13
15
  import { Textarea } from './ui/textarea.js';
14
16
  const isEmpty = (v) => v === undefined || (Array.isArray(v) ? v.length === 0 : v.trim() === '');
@@ -76,6 +78,6 @@ export function LumenForm({ fields, initial, onSave, onCancel, saveLabel = 'Save
76
78
  save();
77
79
  }, children: [_jsx(FieldGroup, { children: fields.map((f) => {
78
80
  const invalid = touched.has(f.id) && !!errors[f.id];
79
- return (_jsxs(Field, { "data-invalid": invalid || undefined, children: [_jsxs(FieldLabel, { htmlFor: domId(f.id), children: [f.label, f.required && _jsx("span", { className: "font-normal text-muted-foreground", children: "required" })] }), input(f), f.hint && !invalid && _jsx(FieldDescription, { children: f.hint }), invalid && _jsx(FieldError, { children: errors[f.id] })] }, f.id));
80
- }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 border-t pt-3", children: [footer, _jsx("div", { className: "flex-1" }), onCancel && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onCancel, children: "Cancel" })), _jsx("span", { className: "contents", onClickCapture: touchAll, children: _jsx(SaveButton, { issues: issues, busy: busy, label: saveLabel, onSave: save }) })] })] }));
81
+ return (_jsxs(Field, { "data-invalid": invalid || undefined, children: [_jsxs(FieldLabel, { htmlFor: domId(f.id), children: [f.label, f.required && _jsx("span", { className: "font-normal text-muted-foreground", children: "required" }), (f.info ?? f.hint) && (_jsx(InfoTip, { label: `About ${f.label}`, children: f.info ?? f.hint }))] }), input(f), invalid && _jsx(FieldError, { children: errors[f.id] })] }, f.id));
82
+ }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 border-t pt-3", children: [footer, _jsx("div", { className: "flex-1" }), onCancel && _jsx(IconButton, { type: "button", icon: XIcon, label: "Cancel", onClick: onCancel }), _jsx("span", { className: "contents", onClickCapture: touchAll, children: _jsx(SaveButton, { issues: issues, busy: busy, label: saveLabel, onSave: save }) })] })] }));
81
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.153",
3
+ "version": "0.1.155",
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.153",
52
+ "@columenlabs/lumen-ui-types": "0.1.155",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",