@columenlabs/lumen-ui 0.1.152 → 0.1.154

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { HashIcon, TypeIcon } from 'lucide-react';
2
+ import { HashIcon, PlusIcon, TypeIcon } from 'lucide-react';
3
3
  import { useEffect, useState } from 'react';
4
4
  import { useAssistantName } from './assistant-context.js';
5
5
  import { SaveButton } from './form-issues.js';
@@ -26,7 +26,7 @@ export function AddFormulaDialog({ open, onOpenChange, domain, formulas, onAdd,
26
26
  }, [open]);
27
27
  const issue = formulaIssues([...formulas, draft], domain)[formulas.length] ?? null;
28
28
  const readable = formulaOptions(domain, formulas);
29
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[calc(100svh-2rem)] flex-col gap-0 [--dialog-pad:0px] sm:max-w-2xl max-md:h-svh max-md:max-h-svh max-md:max-w-full max-md:rounded-none", children: [_jsxs(DialogHeader, { className: "border-b px-4 py-3", children: [_jsx(DialogTitle, { className: "text-sm", children: "Add a formula column" }), _jsx(DialogDescription, { children: "A column computed from the other columns, like weight or upside. Saved with the view." })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(FormulaForm, { formula: draft, issue: issue, fields: readable, onChange: (p) => setDraft({ ...draft, ...p }) }) }), _jsxs(DialogFooter, { className: "border-t px-4 py-3", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(SaveButton, { label: "Add column", icon: null, issues: issue
29
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[calc(100svh-2rem)] flex-col gap-0 [--dialog-pad:0px] sm:max-w-2xl max-md:h-svh max-md:max-h-svh max-md:max-w-full max-md:rounded-none", children: [_jsxs(DialogHeader, { className: "border-b px-4 py-3", children: [_jsx(DialogTitle, { className: "text-sm", children: "Add a formula column" }), _jsx(DialogDescription, { children: "A column computed from the other columns, like weight or upside. Saved with the view." })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(FormulaForm, { formula: draft, issue: issue, fields: readable, onChange: (p) => setDraft({ ...draft, ...p }) }) }), _jsxs(DialogFooter, { className: "border-t px-4 py-3", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(SaveButton, { label: "Add column", icon: PlusIcon, issues: issue
30
30
  ? [
31
31
  {
32
32
  where: `Formula ${draft.label || draft.key}`,
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
- import { CoinsIcon, GlobeIcon, InboxIcon, KeyRoundIcon, LayersIcon, Link2Icon, LogInIcon, ReceiptIcon, SparklesIcon, WrenchIcon, } from 'lucide-react';
3
+ import { CoinsIcon, GlobeIcon, InboxIcon, KeyRoundIcon, LayersIcon, Link2Icon, LogInIcon, ReceiptIcon, SendIcon, SparklesIcon, WrenchIcon, } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
5
  import { ConnectionLogo, LumenConnection, } from '../connection.js';
6
6
  import { LumenExpandList, LumenExpandRow } from '../expand-list.js';
@@ -298,7 +298,7 @@ function Allowance({ client }) {
298
298
  : [{ where: 'What for', message: 'Say what it is for', target: whyId }]),
299
299
  ];
300
300
  const ask = () => void apply('request', () => requestMore(Number(pick), why.trim()), 'Request sent').then((ok) => ok && setWhy(''));
301
- return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-semibold text-2xl tabular-nums", children: n(a.left) }), _jsxs("span", { className: "text-muted-foreground text-sm", children: ["left of ", n(a.total), a.period === 'month' ? ' this month' : ''] })] }), _jsx(ShareMeter, { value: a.total > 0 ? a.used / a.total : 1, wide: true })] }), r && (_jsx(Row, { label: `Asked for ${n(r.amount)} · ${day(r.at)}`, info: r.why || undefined, children: _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [r.status === 'granted' && r.granted !== undefined && (_jsxs("span", { className: "tabular-nums", children: ["+", n(r.granted)] })), _jsx(RequestBadge, { r: r })] }) })), r?.status === 'open' ? (_jsx("p", { className: "pt-3 text-muted-foreground text-sm", children: "You can ask again once this one is decided." })) : (_jsxs("div", { className: "flex flex-col gap-2 pt-3", children: [_jsx(Input, { id: whyId, "aria-label": "What for", placeholder: "What it is for", className: "h-8 w-full", maxLength: 500, value: why, onChange: (e) => setWhy(e.target.value) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenSelector, { label: "Amount", size: "sm", className: "w-40", value: pick, options: a.amounts.map((x) => ({ value: String(x), label: n(x) })), onChange: (v) => setAmount(v) }), _jsx(SaveButton, { issues: issues, label: "Request more", icon: null, busy: busy === 'request', onSave: ask })] })] }))] }));
301
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-semibold text-2xl tabular-nums", children: n(a.left) }), _jsxs("span", { className: "text-muted-foreground text-sm", children: ["left of ", n(a.total), a.period === 'month' ? ' this month' : ''] })] }), _jsx(ShareMeter, { value: a.total > 0 ? a.used / a.total : 1, wide: true })] }), r && (_jsx(Row, { label: `Asked for ${n(r.amount)} · ${day(r.at)}`, info: r.why || undefined, children: _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [r.status === 'granted' && r.granted !== undefined && (_jsxs("span", { className: "tabular-nums", children: ["+", n(r.granted)] })), _jsx(RequestBadge, { r: r })] }) })), r?.status === 'open' ? (_jsx("p", { className: "pt-3 text-muted-foreground text-sm", children: "You can ask again once this one is decided." })) : (_jsxs("div", { className: "flex flex-col gap-2 pt-3", children: [_jsx(Input, { id: whyId, "aria-label": "What for", placeholder: "What it is for", className: "h-8 w-full", maxLength: 500, value: why, onChange: (e) => setWhy(e.target.value) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenSelector, { label: "Amount", size: "sm", className: "w-40", value: pick, options: a.amounts.map((x) => ({ value: String(x), label: n(x) })), onChange: (v) => setAmount(v) }), _jsx(SaveButton, { issues: issues, label: "Request more", icon: SendIcon, busy: busy === 'request', onSave: ask })] })] }))] }));
302
302
  } }));
303
303
  }
304
304
  /** One request, opened in place to grant (any amount) or decline. */
@@ -13,12 +13,15 @@ export interface FormIssue {
13
13
  export declare function goToIssue(issue: FormIssue): void;
14
14
  /**
15
15
  * Save, and before it, while anything needs fixing, a chip with the count that lists each issue.
16
- * Save stays tappable then and takes the person to the first issue instead of saving.
16
+ * Save stays tappable then and takes the person to the first issue instead of saving. Save is an
17
+ * icon (DESIGN.md, Actions: icon first): `label` is its tooltip and accessible name, never text.
17
18
  */
18
19
  export declare function SaveButton({ issues, onSave, label, icon: Icon, busy, disabled, type, className, }: {
19
20
  issues: FormIssue[];
20
21
  onSave?: () => void;
22
+ /** The tooltip and accessible name. */
21
23
  label?: string;
24
+ /** `null` is the check, as left out. */
22
25
  icon?: ComponentType<{
23
26
  className?: string;
24
27
  }> | null;
@@ -4,8 +4,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
4
4
  // issue: the editor opens its section or tab (`reveal`), then the field scrolls into view, takes
5
5
  // the cursor and flashes red once. Nothing saves while an issue stands. Every editor and form in
6
6
  // the suite uses this in place of a silent disabled Save.
7
- import { CheckIcon, TriangleAlertIcon } from 'lucide-react';
7
+ import { CheckIcon, LoaderCircleIcon, TriangleAlertIcon } from 'lucide-react';
8
8
  import { useState } from 'react';
9
+ import { IconButton } from './icon-button.js';
9
10
  import { cn } from './lib/utils.js';
10
11
  import { Button } from './ui/button.js';
11
12
  import { Popover, PopoverContent, PopoverTrigger } from './ui/popover.js';
@@ -37,7 +38,8 @@ export function goToIssue(issue) {
37
38
  }
38
39
  /**
39
40
  * Save, and before it, while anything needs fixing, a chip with the count that lists each issue.
40
- * Save stays tappable then and takes the person to the first issue instead of saving.
41
+ * Save stays tappable then and takes the person to the first issue instead of saving. Save is an
42
+ * icon (DESIGN.md, Actions: icon first): `label` is its tooltip and accessible name, never text.
41
43
  */
42
44
  export function SaveButton({ issues, onSave, label = 'Save', icon: Icon = CheckIcon, busy = false, disabled = false, type = 'button', className, }) {
43
45
  const [open, setOpen] = useState(false);
@@ -47,14 +49,14 @@ export function SaveButton({ issues, onSave, label = 'Save', icon: Icon = CheckI
47
49
  setOpen(false);
48
50
  goToIssue(issue);
49
51
  };
50
- return (_jsxs(_Fragment, { children: [blocked && (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(TriangleAlertIcon, {}), issues.length, " to fix"] }) }), _jsxs(PopoverContent, { align: "end", className: "w-80 gap-1 p-1", children: [_jsxs("p", { className: "px-2 pt-1 text-xs text-muted-foreground", children: ["Fix ", issues.length === 1 ? 'this' : 'these', " to save. Tap one to go to it."] }), issues.map((issue, i) => (_jsxs(Button
52
+ return (_jsxs(_Fragment, { children: [blocked && (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `${issues.length} to fix`, title: `${issues.length} to fix`, className: "num text-destructive hover:text-destructive", children: [_jsx(TriangleAlertIcon, {}), issues.length] }) }), _jsxs(PopoverContent, { align: "end", className: "w-80 gap-1 p-1", children: [_jsxs("p", { className: "px-2 pt-1 text-xs text-muted-foreground", children: ["Fix ", issues.length === 1 ? 'this' : 'these', " to save. Tap one to go to it."] }), issues.map((issue, i) => (_jsxs(Button
51
53
  // biome-ignore lint/suspicious/noArrayIndexKey: two issues can share a place and message.
52
- , { type: "button", variant: "ghost", size: "sm", className: "h-auto w-full flex-col items-start gap-0 px-2 py-1.5 text-left font-normal whitespace-normal", onClick: () => go(issue), children: [_jsx("span", { className: "text-xs font-medium", children: issue.where }), _jsx("span", { className: "text-xs text-destructive", children: issue.message })] }, i)))] })] })), _jsxs(Button, { type: blocked ? 'button' : type, size: "sm", disabled: disabled || busy, "aria-disabled": blocked || undefined, className: cn(blocked && 'opacity-50', className), onClick: (e) => {
54
+ , { type: "button", variant: "ghost", size: "sm", className: "h-auto w-full flex-col items-start gap-0 px-2 py-1.5 text-left font-normal whitespace-normal", onClick: () => go(issue), children: [_jsx("span", { className: "text-xs font-medium", children: issue.where }), _jsx("span", { className: "text-xs text-destructive", children: issue.message })] }, i)))] })] })), _jsx(IconButton, { icon: busy ? LoaderCircleIcon : (Icon ?? CheckIcon), label: blocked ? `${label}: ${issues.length} to fix` : label, variant: "default", size: "icon-sm", type: blocked ? 'button' : type, busy: busy, disabled: disabled || busy, "aria-disabled": blocked || undefined, className: cn(blocked && 'opacity-50', className), onClick: (e) => {
53
55
  if (first) {
54
56
  e.preventDefault();
55
57
  go(first);
56
58
  }
57
59
  else if (type === 'button')
58
60
  onSave?.();
59
- }, children: [Icon && _jsx(Icon, {}), label] })] }));
61
+ } })] }));
60
62
  }
@@ -9,7 +9,7 @@ export interface LumenDrawerProps {
9
9
  description?: ReactNode;
10
10
  /** Stacked DrawerSections; the drawer pads and scrolls them. */
11
11
  children: ReactNode;
12
- /** The footer's actions, right-aligned (a destructive action, Cancel and Save); Close when left out. */
12
+ /** The footer's actions, right-aligned, icon first (`IconButton`, `SaveButton`); no footer when left out. */
13
13
  footer?: ReactNode;
14
14
  /** Left side of the footer: a count or status line ("12 releases"). */
15
15
  footerStart?: ReactNode;
@@ -6,7 +6,6 @@ import { useIsMobile } from './hooks/use-mobile.js';
6
6
  import { IconButton } from './icon-button.js';
7
7
  import { lazyPart } from './lazy-part.js';
8
8
  import { ResizeEdge, tabState, useDockSlot } from './side-dock.js';
9
- import { Button } from './ui/button.js';
10
9
  import { pickerOpen } from './ui/overlay-guard.js';
11
10
  import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from './ui/sheet.js';
12
11
  /** The resizable panels load with the first drawer opened; until then it opens at its default width. */
@@ -34,16 +33,14 @@ export function LumenDrawer({ open, onClose, title, description, children, foote
34
33
  }, [isMobile]);
35
34
  const head = (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(SheetTitle, { className: "truncate", title: typeof title === 'string' ? title : undefined, children: title }), _jsx(SheetDescription, { className: description ? 'truncate text-muted-foreground text-xs' : 'sr-only', title: typeof description === 'string' ? description : undefined, children: description ?? 'Details' })] }));
36
35
  const body = _jsx("div", { className: "flex flex-col px-4 pb-4", children: children });
37
- // Every drawer ends on a footer, styled like a dialog's: the body scrolls above it and always
38
- // ends on a clear edge. Without actions of its own it offers Close, except on a phone, where the
39
- // sheet's X already closes it and a second Close only took room.
40
- const plain = !footer && !footerStart;
41
- const foot = (_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 ?? (_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onClose, children: "Close" })) })] }));
36
+ // A drawer with actions ends on a footer, styled like a dialog's: the body scrolls above it and
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 })] }));
42
39
  if (dock && !isMobile && slot)
43
40
  return open
44
41
  ? createPortal(_jsx(DockedDrawer, { dock: dock, title: title, description: description, onClose: onClose, defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, body: body, foot: foot }), slot)
45
42
  : null;
46
- return (_jsx(Sheet, { open: open, onOpenChange: (o) => !o && onClose(), modal: isMobile, children: isMobile ? (_jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 overflow-hidden", children: [_jsx(SheetHeader, { className: "order-first shrink-0 border-b pt-3", children: head }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), !plain && foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
43
+ return (_jsx(Sheet, { open: open, onOpenChange: (o) => !o && onClose(), modal: isMobile, children: isMobile ? (_jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 overflow-hidden", children: [_jsx(SheetHeader, { className: "order-first shrink-0 border-b pt-3", children: head }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
47
44
  // The resize handle's grab zone is wider than the handle, so a drag can start on the
48
45
  // overlay; the panels library marks that pointerdown handled, and then it doesn't close.
49
46
  onPointerDownOutside: (e) => e.detail.originalEvent.defaultPrevented && e.preventDefault(), className: "pointer-events-none! gap-0 border-0 bg-transparent shadow-none data-[side=right]:left-0 data-[side=right]:w-auto data-[side=right]:border-l-0 data-[side=right]:sm:max-w-none", children: _jsx(DrawerPanels, { defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, children: _jsxs("section", { className: "flex h-full flex-col overflow-hidden", children: [_jsxs("header", { className: "flex shrink-0 items-center gap-2 border-b px-4 py-3", children: [head, _jsx(SheetClose, { asChild: true, children: _jsx(IconButton, { icon: XIcon, label: "Close", className: "ml-auto" }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] }) }) })) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.152",
3
+ "version": "0.1.154",
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.152",
52
+ "@columenlabs/lumen-ui-types": "0.1.154",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",