@columenlabs/lumen-ui 0.1.76 → 0.1.77
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/hooks/use-back-close.d.ts +19 -0
- package/dist/hooks/use-back-close.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lumen-drawer.js +1 -1
- package/dist/lumen-filter.js +1 -1
- package/dist/lumen-selector.js +2 -2
- package/dist/tasks.js +1 -1
- package/dist/ui/alert-dialog.d.ts +1 -1
- package/dist/ui/alert-dialog.js +5 -2
- package/dist/ui/dialog.d.ts +1 -1
- package/dist/ui/dialog.js +9 -4
- package/dist/ui/sheet.d.ts +3 -2
- package/dist/ui/sheet.js +85 -14
- package/package.json +2 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone back closes the open overlay (a Sheet, Dialog or AlertDialog), never the page under it.
|
|
3
|
+
* Opening pushes one history entry on the same URL; back pops it and closes. Closed any other way
|
|
4
|
+
* (X, a tap outside, a swipe, Esc, a pick) the entry is popped again, but only while it is still
|
|
5
|
+
* the current one, so a link followed from inside the overlay keeps its navigation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useBackClose(open: boolean, close: () => void): void;
|
|
8
|
+
/**
|
|
9
|
+
* An overlay root's open state, controlled or not, with `close()` for its parts (the grab handle)
|
|
10
|
+
* and phone back wired in (useBackClose).
|
|
11
|
+
*/
|
|
12
|
+
export declare function useOverlayOpen({ open: given, defaultOpen, onOpenChange, }: {
|
|
13
|
+
open?: boolean;
|
|
14
|
+
defaultOpen?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
}): {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onOpenChange: (o: boolean) => void;
|
|
19
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useIsMobile } from './use-mobile.js';
|
|
3
|
+
const KEY = '__lumenOverlay';
|
|
4
|
+
/**
|
|
5
|
+
* Phone back closes the open overlay (a Sheet, Dialog or AlertDialog), never the page under it.
|
|
6
|
+
* Opening pushes one history entry on the same URL; back pops it and closes. Closed any other way
|
|
7
|
+
* (X, a tap outside, a swipe, Esc, a pick) the entry is popped again, but only while it is still
|
|
8
|
+
* the current one, so a link followed from inside the overlay keeps its navigation.
|
|
9
|
+
*/
|
|
10
|
+
export function useBackClose(open, close) {
|
|
11
|
+
const phone = useIsMobile();
|
|
12
|
+
const closeRef = React.useRef(close);
|
|
13
|
+
closeRef.current = close;
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
if (!phone || !open || typeof window === 'undefined')
|
|
16
|
+
return;
|
|
17
|
+
const id = Math.random().toString(36).slice(2);
|
|
18
|
+
let pushed = false;
|
|
19
|
+
let popped = false;
|
|
20
|
+
const onPop = () => {
|
|
21
|
+
if (window.history.state?.[KEY] === id)
|
|
22
|
+
return;
|
|
23
|
+
popped = true;
|
|
24
|
+
closeRef.current();
|
|
25
|
+
};
|
|
26
|
+
// Pushed a tick later, so a mount that is undone at once (React's strict mode) pushes nothing.
|
|
27
|
+
const timer = window.setTimeout(() => {
|
|
28
|
+
window.history.pushState({ ...window.history.state, [KEY]: id }, '');
|
|
29
|
+
pushed = true;
|
|
30
|
+
window.addEventListener('popstate', onPop);
|
|
31
|
+
}, 0);
|
|
32
|
+
return () => {
|
|
33
|
+
window.clearTimeout(timer);
|
|
34
|
+
window.removeEventListener('popstate', onPop);
|
|
35
|
+
if (pushed && !popped && window.history.state?.[KEY] === id)
|
|
36
|
+
window.history.back();
|
|
37
|
+
};
|
|
38
|
+
}, [phone, open]);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* An overlay root's open state, controlled or not, with `close()` for its parts (the grab handle)
|
|
42
|
+
* and phone back wired in (useBackClose).
|
|
43
|
+
*/
|
|
44
|
+
export function useOverlayOpen({ open: given, defaultOpen, onOpenChange, }) {
|
|
45
|
+
const [inner, setInner] = React.useState(defaultOpen ?? false);
|
|
46
|
+
const open = given ?? inner;
|
|
47
|
+
const change = React.useCallback((o) => {
|
|
48
|
+
setInner(o);
|
|
49
|
+
onOpenChange?.(o);
|
|
50
|
+
}, [onOpenChange]);
|
|
51
|
+
useBackClose(open, () => change(false));
|
|
52
|
+
return { open, onOpenChange: change };
|
|
53
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export { SourcesLine, useSourceHealth } from './source-health.js';
|
|
|
80
80
|
export { TILE_WIDTH, Tile, TileChip, type TileOption, TileRow, TileSpark, TileToggleGroup, } from './tile.js';
|
|
81
81
|
export { type FilterField, type FilterToken, LumenTokenizedFilter, type LumenFilterState, useLumenFilter, } from './lumen-filter.js';
|
|
82
82
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
83
|
+
export { useBackClose } from './hooks/use-back-close.js';
|
|
83
84
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
84
85
|
export { type AppViewState, useAppView } from './app-views.js';
|
|
85
86
|
export { type ScreenMap, type ScreenOutcome, type ScreenStatus, type WorkspaceOnScreen, type AssistantMove, type AssistantMoveHandlers, type AssistantPage, type AssistantPageType, AssistantScreenProvider, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
package/dist/index.js
CHANGED
|
@@ -87,6 +87,7 @@ export { SourcesLine, useSourceHealth } from './source-health.js';
|
|
|
87
87
|
export { TILE_WIDTH, Tile, TileChip, TileRow, TileSpark, TileToggleGroup, } from './tile.js';
|
|
88
88
|
export { LumenTokenizedFilter, useLumenFilter, } from './lumen-filter.js';
|
|
89
89
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
90
|
+
export { useBackClose } from './hooks/use-back-close.js';
|
|
90
91
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
91
92
|
export { useAppView } from './app-views.js';
|
|
92
93
|
export { AssistantScreenProvider, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
package/dist/lumen-drawer.js
CHANGED
|
@@ -23,7 +23,7 @@ export function LumenDrawer({ open, onClose, title, description, children, foote
|
|
|
23
23
|
// sheet's X already closes it and a second Close only took room.
|
|
24
24
|
const plain = !footer && !footerStart;
|
|
25
25
|
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(SheetClose, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", children: "Close" }) })) })] }));
|
|
26
|
-
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", 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,
|
|
26
|
+
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,
|
|
27
27
|
// The resize handle's grab zone is wider than the handle, so a drag can start on the
|
|
28
28
|
// overlay; the panels library marks that pointerdown handled, and then it doesn't close.
|
|
29
29
|
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: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { minSize: "40%" }), _jsx(ResizableHandle, { withHandle: true, className: "pointer-events-auto" }), _jsx(ResizablePanel, { defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, "data-slot": "lumen-drawer", className: "pointer-events-auto bg-popover shadow-lg", 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/dist/lumen-filter.js
CHANGED
|
@@ -565,7 +565,7 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
565
565
|
setField(null);
|
|
566
566
|
}, children: _jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 rounded-t-xl",
|
|
567
567
|
// The keyboard would cover the menu: search waits for a tap.
|
|
568
|
-
onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs(SheetHeader, { className: "flex-row items-center pb-2", children: [_jsx(SheetTitle, { children: "Filters" }), _jsx(SheetDescription, { className: "sr-only", children: "Pick values to filter the rows." }), tokens.length > 0 && (_jsx(Button, { variant: "ghost", size: "sm", className: "
|
|
568
|
+
onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs(SheetHeader, { className: "flex-row items-center pb-2", children: [_jsx(SheetTitle, { children: "Filters" }), _jsx(SheetDescription, { className: "sr-only", children: "Pick values to filter the rows." }), tokens.length > 0 && (_jsx(Button, { variant: "ghost", size: "sm", className: "ml-auto font-normal text-muted-foreground", onClick: clear, children: "Clear all" }))] }), _jsxs(CommandPrimitive, { shouldFilter: false, loop: true, className: "flex min-h-0 flex-1 flex-col gap-2 px-3 pb-[max(1rem,env(safe-area-inset-bottom))]", children: [_jsx(CommandInput, { value: field ? search : text, onValueChange: field ? setSearch : setText, onKeyDown: onKeyDown, placeholder: searchHint, "aria-label": label, className: "text-base" }), _jsx(CommandList, { className: "max-h-none min-h-0 flex-1", "aria-label": "Filter suggestions", children: menu }), footer] })] }) })] }));
|
|
569
569
|
}
|
|
570
570
|
return (_jsxs(CommandPrimitive, { shouldFilter: false, className: cn('relative w-full', className), loop: true, children: [sizes, _jsxs(Popover, { open: open, onOpenChange: (o) => {
|
|
571
571
|
setOpen(o);
|
package/dist/lumen-selector.js
CHANGED
|
@@ -264,9 +264,9 @@ export function LumenSelector(props) {
|
|
|
264
264
|
if (phone) {
|
|
265
265
|
// The trigger opens a bottom sheet; the list is the combobox inline in it.
|
|
266
266
|
const opener = trigger === 'icon' ? (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": label, disabled: disabled, className: className, onClick: () => setOpen(true), children: LeadIcon && _jsx(LeadIcon, {}) })) : (_jsx("button", { type: "button", id: id, "data-size": size, "aria-label": label, disabled: disabled, className: cn(FIELD, multiple && 'h-auto min-h-8 flex-wrap justify-start py-1', trigger === 'chip' && 'rounded-full', className), onClick: () => setOpen(true), children: multiple ? (values.length ? (_jsxs("span", { className: "flex flex-1 flex-wrap gap-1", children: [values.slice(0, CHIPS).map((v) => (_jsx("span", { className: "rounded-sm bg-muted px-1.5 text-xs leading-[calc(--spacing(5.25))] font-medium", children: labelOf(v) }, v))), values.length > CHIPS && (_jsxs("span", { className: "rounded-sm bg-muted px-1.5 text-xs leading-[calc(--spacing(5.25))]", children: ["+", values.length - CHIPS] }))] })) : (_jsx("span", { className: "flex-1 text-left text-muted-foreground", children: placeholder }))) : (fieldInside) }));
|
|
267
|
-
return (_jsxs(_Fragment, { children: [opener, _jsx(Sheet, { open: open, onOpenChange: setOpen, children: _jsxs(SheetContent, { side: "bottom",
|
|
267
|
+
return (_jsxs(_Fragment, { children: [opener, _jsx(Sheet, { open: open, onOpenChange: setOpen, children: _jsxs(SheetContent, { side: "bottom", "aria-labelledby": sheetTitle,
|
|
268
268
|
// No ring on the first button: the person taps what they want.
|
|
269
|
-
onOpenAutoFocus: (e) => e.preventDefault(), className: "group/selector flex
|
|
269
|
+
onOpenAutoFocus: (e) => e.preventDefault(), className: "group/selector flex flex-col gap-2", children: [_jsxs("div", { className: "mt-2 flex min-h-8 items-center justify-between gap-2 pr-12 pl-4", children: [_jsx(SheetTitle, { id: sheetTitle, className: "truncate", children: label ?? placeholder }), multiple && (_jsx(Button, { size: "sm", onClick: () => setOpen(false), children: "Done" }))] }), _jsx(SheetDescription, { className: "sr-only", children: multiple ? 'Pick any, then Done' : 'Pick one' }), _jsxs(Base.Root, { ...rootProps, inline: true, open: true, onOpenChange: () => undefined, multiple: multiple, value: multiple ? [...values] : (values[0] ?? null), onValueChange: (v) => pick(v), children: [multiple && values.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5 px-4", children: values.map((v) => (_jsx("span", { className: "rounded-sm bg-muted px-2 py-1 text-[13px]", children: labelOf(v) }, v))) })), searchable && searchBox, list] }), multiple && (_jsxs("div", { className: "flex items-center justify-between border-t px-4 pt-3 pb-5", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [values.length, " picked"] }), clearAll] }))] }) })] }));
|
|
270
270
|
}
|
|
271
271
|
const popup = (_jsx(Base.Portal, { container: host ?? undefined, children: _jsx(Base.Positioner, { side: side, sideOffset: 4, align: align, anchor: inChips ? chipsRef : undefined, className: "pointer-events-auto isolate z-[60]", children: _jsxs(Base.Popup, { "data-slot": "lumen-selector", className: "group/selector flex max-h-(--available-height) max-w-(--available-width) flex-col min-w-[max(var(--anchor-width),14rem)] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", children: [!inChips && searchBox, list, multiple && (_jsxs("div", { className: "flex shrink-0 items-center gap-3 border-t px-2 py-1.5", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [values.length, " picked", max !== undefined ? ` of ${max}` : ''] }), clearAll, _jsx(Button, { size: "xs", className: "ml-auto", onClick: () => setOpen(false), children: "Done" })] }))] }) }) }));
|
|
272
272
|
return (_jsxs(Base.Root, { ...rootProps, multiple: multiple, value: multiple ? [...values] : (values[0] ?? null), onValueChange: (v) => pick(v), children: [inChips ? (_jsxs(Base.Chips, { ref: chipsRef, className: cn('flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent px-1 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 dark:bg-input/30', className), children: [chips, _jsx(Base.Input, { id: id, "aria-label": label, disabled: disabled, readOnly: !searchable, placeholder: values.length ? '' : placeholder, className: "min-w-16 flex-1 bg-transparent px-1 outline-none placeholder:text-muted-foreground" })] })) : trigger === 'icon' ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Base.Trigger, { ref: triggerRef, render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": label, className: className }), children: LeadIcon && _jsx(LeadIcon, {}) }) }), _jsx(TooltipContent, { children: label })] })) : (_jsx(Base.Trigger, { ref: triggerRef, id: id, "data-size": size, "aria-label": label, className: cn(FIELD, trigger === 'chip' && 'rounded-full', className), children: fieldInside })), _jsx("span", { ref: markRef, hidden: true }), popup] }));
|
package/dist/tasks.js
CHANGED
|
@@ -186,7 +186,7 @@ export function TasksButton() {
|
|
|
186
186
|
const trigger = (_jsxs(Button, { variant: "ghost", size: list.active ? 'sm' : 'icon-sm', className: cn('relative shrink-0', list.active > 0 && 'gap-1.5 px-2'), "aria-label": label, "data-slot": "tasks-button", children: [list.active > 0 ? _jsx(Ring, { share: share }) : _jsx(ListChecksIcon, {}), list.active > 0 && _jsx("span", { className: "font-num text-xs tabular-nums", children: list.active }), list.unseen > 0 && (_jsx("span", { "aria-hidden": true, "data-slot": "tasks-unseen", className: "absolute top-1 right-0.5 size-1.5 rounded-full bg-edge" }))] }));
|
|
187
187
|
const body = _jsx(TaskPanel, { list: list, onClose: () => setOpen(false) });
|
|
188
188
|
if (mobile) {
|
|
189
|
-
return (_jsxs(Sheet, { open: open, onOpenChange: onOpen, children: [_jsx(SheetTrigger, { asChild: true, children: trigger }), _jsxs(SheetContent, { side: "bottom", className: "max-h-[75svh] gap-0 rounded-t-2xl p-0", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs(SheetHeader, { className: "flex-row items-baseline gap-2 border-b px-4 py-3", children: [_jsx(SheetTitle, { children: "Tasks" }), _jsx(SheetDescription, { children: "last 7 days" }), _jsxs("span", { className: "
|
|
189
|
+
return (_jsxs(Sheet, { open: open, onOpenChange: onOpen, children: [_jsx(SheetTrigger, { asChild: true, children: trigger }), _jsxs(SheetContent, { side: "bottom", className: "max-h-[75svh] gap-0 rounded-t-2xl p-0", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs(SheetHeader, { className: "flex-row items-baseline gap-2 border-b px-4 py-3", children: [_jsx(SheetTitle, { children: "Tasks" }), _jsx(SheetDescription, { children: "last 7 days" }), _jsxs("span", { className: "ml-auto flex items-center gap-2 self-center", children: [list.active > 0 && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [list.active, " running"] })), _jsx(ClearAll, { list: list })] })] }), body] })] }));
|
|
190
190
|
}
|
|
191
191
|
return (_jsxs(Popover, { open: open, onOpenChange: onOpen, children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: trigger }) }), _jsx(TooltipContent, { children: "Tasks" })] }), _jsxs(PopoverContent, { align: "end", className: "w-100 gap-0 p-0", "aria-label": "Tasks", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs("div", { className: "flex items-baseline gap-2 px-3.5 pt-3 pb-2", children: [_jsx("span", { className: "text-sm font-semibold", children: "Tasks" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "last 7 days" }), _jsxs("span", { className: "ml-auto flex items-center gap-2 self-center", children: [list.active > 0 && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [list.active, " running"] })), _jsx(ClearAll, { list: list })] })] }), body] })] }));
|
|
192
192
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
|
|
2
2
|
import type * as React from 'react';
|
|
3
3
|
import { Button } from './button.js';
|
|
4
|
-
declare function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>): React.JSX.Element;
|
|
4
|
+
declare function AlertDialog({ open: given, defaultOpen, onOpenChange: changed, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>): React.JSX.Element;
|
|
5
5
|
declare function AlertDialogTrigger({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>): React.JSX.Element;
|
|
6
6
|
declare function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>): React.JSX.Element;
|
|
7
7
|
declare function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>): React.JSX.Element;
|
package/dist/ui/alert-dialog.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from '../lib/utils.js';
|
|
3
3
|
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
|
|
4
|
+
import { useOverlayOpen } from '../hooks/use-back-close.js';
|
|
4
5
|
import { usePhoneScreen } from '../hooks/use-keyboard-fit.js';
|
|
5
6
|
import { Button } from './button.js';
|
|
6
|
-
function AlertDialog({ ...props }) {
|
|
7
|
-
|
|
7
|
+
function AlertDialog({ open: given, defaultOpen, onOpenChange: changed, ...props }) {
|
|
8
|
+
// Phone back closes it, as its Cancel does (useBackClose).
|
|
9
|
+
const state = useOverlayOpen({ open: given, defaultOpen, onOpenChange: changed });
|
|
10
|
+
return _jsx(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...state, ...props });
|
|
8
11
|
}
|
|
9
12
|
function AlertDialogTrigger({ ...props }) {
|
|
10
13
|
return _jsx(AlertDialogPrimitive.Trigger, { "data-slot": "alert-dialog-trigger", ...props });
|
package/dist/ui/dialog.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Dialog as DialogPrimitive } from 'radix-ui';
|
|
2
2
|
import type * as React from 'react';
|
|
3
|
-
declare function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
|
|
3
|
+
declare function Dialog({ open: given, defaultOpen, onOpenChange: changed, ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
|
|
4
4
|
declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element;
|
|
5
5
|
declare function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>): React.JSX.Element;
|
|
6
6
|
declare function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element;
|
package/dist/ui/dialog.js
CHANGED
|
@@ -3,9 +3,12 @@ import { cn } from '../lib/utils.js';
|
|
|
3
3
|
import { Dialog as DialogPrimitive } from 'radix-ui';
|
|
4
4
|
import { Button } from './button.js';
|
|
5
5
|
import { XIcon } from 'lucide-react';
|
|
6
|
+
import { useOverlayOpen } from '../hooks/use-back-close.js';
|
|
6
7
|
import { usePhoneScreen } from '../hooks/use-keyboard-fit.js';
|
|
7
|
-
function Dialog({ ...props }) {
|
|
8
|
-
|
|
8
|
+
function Dialog({ open: given, defaultOpen, onOpenChange: changed, ...props }) {
|
|
9
|
+
// Phone back closes it (useBackClose).
|
|
10
|
+
const state = useOverlayOpen({ open: given, defaultOpen, onOpenChange: changed });
|
|
11
|
+
return _jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...state, ...props });
|
|
9
12
|
}
|
|
10
13
|
function DialogTrigger({ ...props }) {
|
|
11
14
|
return _jsx(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
|
|
@@ -22,10 +25,12 @@ function DialogOverlay({ className, ...props }) {
|
|
|
22
25
|
function DialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
23
26
|
// On a phone it is a full screen pinned to the top, its footer a bar at the top (usePhoneScreen).
|
|
24
27
|
const style = usePhoneScreen(props.style);
|
|
25
|
-
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn('fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-(--dialog-pad) text-sm [--dialog-pad:--spacing(4)] text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm max-md:inset-0 max-md:h-full max-md:max-h-none max-md:w-full max-md:max-w-none max-md:translate-x-0 max-md:translate-y-0 max-md:content-start max-md:overflow-y-auto max-md:rounded-none max-md:pt-[calc(var(--dialog-pad)+env(safe-area-inset-top))] max-md:ring-0 max-md:end-fade max-md:[--end-fade:var(--dialog-pad)] data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95', className), ...props, style: style, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: _jsxs(Button, { variant: "ghost", className: "absolute top-2 right-2 max-md:top-[calc(1.25rem+env(safe-area-inset-top))]", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
|
|
28
|
+
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", "data-close": showCloseButton || undefined, className: cn('group/dialog fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-(--dialog-pad) text-sm [--dialog-pad:--spacing(4)] text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm max-md:inset-0 max-md:h-full max-md:max-h-none max-md:w-full max-md:max-w-none max-md:translate-x-0 max-md:translate-y-0 max-md:content-start max-md:overflow-y-auto max-md:rounded-none max-md:pt-[calc(var(--dialog-pad)+env(safe-area-inset-top))] max-md:ring-0 max-md:end-fade max-md:[--end-fade:var(--dialog-pad)] data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95', className), ...props, style: style, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: _jsxs(Button, { variant: "ghost", className: "absolute top-2 right-2 max-md:top-[calc(1.25rem+env(safe-area-inset-top))]", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
|
|
26
29
|
}
|
|
27
30
|
function DialogHeader({ className, ...props }) {
|
|
28
|
-
return (_jsx("div", { "data-slot": "dialog-header",
|
|
31
|
+
return (_jsx("div", { "data-slot": "dialog-header",
|
|
32
|
+
// Its title stops short of the dialog's X, never running under it.
|
|
33
|
+
className: cn('flex flex-col gap-2 group-data-close/dialog:pr-8', className), ...props }));
|
|
29
34
|
}
|
|
30
35
|
/** A dialog's scrolling part: it reaches the dialog's edges, so its scroll bar sits on the frame and
|
|
31
36
|
* the focus rings of its fields are never cut by the scroll box. It and the
|
package/dist/ui/sheet.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Dialog as SheetPrimitive } from 'radix-ui';
|
|
2
|
-
import
|
|
3
|
-
declare function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>): React.JSX.Element;
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
declare function Sheet({ open: given, defaultOpen, onOpenChange: changed, ...props }: React.ComponentProps<typeof SheetPrimitive.Root>): React.JSX.Element;
|
|
4
4
|
declare function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>): React.JSX.Element;
|
|
5
5
|
declare function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>): React.JSX.Element;
|
|
6
6
|
declare function SheetContent({ className, children, side, showCloseButton, ...props }: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
7
7
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
8
|
+
/** Left out on desktop only: a phone's bottom sheet always has its X. */
|
|
8
9
|
showCloseButton?: boolean;
|
|
9
10
|
}): React.JSX.Element;
|
|
10
11
|
declare function SheetHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
package/dist/ui/sheet.js
CHANGED
|
@@ -3,10 +3,64 @@ import { cn } from '../lib/utils.js';
|
|
|
3
3
|
import { Dialog as SheetPrimitive } from 'radix-ui';
|
|
4
4
|
import { Button } from './button.js';
|
|
5
5
|
import { XIcon } from 'lucide-react';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { useOverlayOpen } from '../hooks/use-back-close.js';
|
|
6
8
|
import { useKeyboardFit } from '../hooks/use-keyboard-fit.js';
|
|
7
9
|
import { useIsMobile } from '../hooks/use-mobile.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
/** Closes the sheet from inside it (the grab handle's swipe). */
|
|
11
|
+
const SheetCloseContext = React.createContext(() => undefined);
|
|
12
|
+
function Sheet({ open: given, defaultOpen, onOpenChange: changed, ...props }) {
|
|
13
|
+
// Phone back closes it (useBackClose).
|
|
14
|
+
const { open, onOpenChange } = useOverlayOpen({
|
|
15
|
+
open: given,
|
|
16
|
+
defaultOpen,
|
|
17
|
+
onOpenChange: changed,
|
|
18
|
+
});
|
|
19
|
+
const close = React.useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
20
|
+
return (_jsx(SheetCloseContext.Provider, { value: close, children: _jsx(SheetPrimitive.Root, { "data-slot": "sheet", open: open, onOpenChange: onOpenChange, ...props }) }));
|
|
21
|
+
}
|
|
22
|
+
/** How far a swipe down on the grab handle must pull the sheet before letting go closes it. */
|
|
23
|
+
const SWIPE_CLOSE = 80;
|
|
24
|
+
/**
|
|
25
|
+
* A bottom sheet's grab handle on a phone: a wide strip at its top that drags the sheet down, and
|
|
26
|
+
* a pull past SWIPE_CLOSE (or a quick flick) closes it; a shorter one springs back.
|
|
27
|
+
*/
|
|
28
|
+
function SheetGrab() {
|
|
29
|
+
const close = React.useContext(SheetCloseContext);
|
|
30
|
+
const drag = React.useRef(null);
|
|
31
|
+
const move = (dy, el) => {
|
|
32
|
+
el.style.transform = dy > 0 ? `translateY(${dy}px)` : '';
|
|
33
|
+
};
|
|
34
|
+
return (_jsx("div", { "data-slot": "sheet-grab", "aria-hidden": true, className: "absolute inset-x-0 top-0 z-10 flex h-6 cursor-grab touch-none justify-center pt-2", onPointerDown: (e) => {
|
|
35
|
+
const el = e.currentTarget.closest('[data-slot=sheet-content]');
|
|
36
|
+
if (!el)
|
|
37
|
+
return;
|
|
38
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
39
|
+
el.style.transition = 'none';
|
|
40
|
+
drag.current = { y: e.clientY, t: e.timeStamp, el };
|
|
41
|
+
}, onPointerMove: (e) => {
|
|
42
|
+
if (drag.current)
|
|
43
|
+
move(e.clientY - drag.current.y, drag.current.el);
|
|
44
|
+
}, onPointerUp: (e) => {
|
|
45
|
+
const d = drag.current;
|
|
46
|
+
drag.current = null;
|
|
47
|
+
if (!d)
|
|
48
|
+
return;
|
|
49
|
+
const dy = e.clientY - d.y;
|
|
50
|
+
const flick = dy > 24 && dy / Math.max(1, e.timeStamp - d.t) > 0.5;
|
|
51
|
+
d.el.style.transition = '';
|
|
52
|
+
if (dy > SWIPE_CLOSE || flick)
|
|
53
|
+
close();
|
|
54
|
+
else
|
|
55
|
+
move(0, d.el);
|
|
56
|
+
}, onPointerCancel: () => {
|
|
57
|
+
const d = drag.current;
|
|
58
|
+
drag.current = null;
|
|
59
|
+
if (d) {
|
|
60
|
+
d.el.style.transition = '';
|
|
61
|
+
move(0, d.el);
|
|
62
|
+
}
|
|
63
|
+
}, children: _jsx("span", { className: "h-1 w-9 rounded-full bg-border" }) }));
|
|
10
64
|
}
|
|
11
65
|
function SheetTrigger({ ...props }) {
|
|
12
66
|
return _jsx(SheetPrimitive.Trigger, { "data-slot": "sheet-trigger", ...props });
|
|
@@ -21,36 +75,53 @@ function SheetOverlay({ className, ...props }) {
|
|
|
21
75
|
return (_jsx(SheetPrimitive.Overlay, { "data-slot": "sheet-overlay", className: cn('bg-black/10 supports-backdrop-filter:backdrop-blur-xs fixed inset-y-0 left-0 right-(--assistant-docked,0px) z-50 duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0', className), ...props }));
|
|
22
76
|
}
|
|
23
77
|
function SheetContent({ className, children, side = 'right', showCloseButton = true, ...props }) {
|
|
24
|
-
// On a phone a bottom sheet
|
|
25
|
-
//
|
|
26
|
-
// the keyboard
|
|
78
|
+
// On a phone a bottom sheet rises from the bottom and stops short of the top, so the dimmed page
|
|
79
|
+
// above it shows and a tap there closes it; it carries a grab handle (swipe down to close), its
|
|
80
|
+
// X and the safe areas. With the keyboard up a sheet ends at the keyboard; a top sheet or a phone
|
|
81
|
+
// bottom sheet starts at the top of what is visible.
|
|
27
82
|
const kb = useKeyboardFit();
|
|
28
83
|
const phone = useIsMobile();
|
|
29
|
-
const
|
|
84
|
+
const card = side === 'bottom' && phone;
|
|
85
|
+
const closable = showCloseButton || card;
|
|
30
86
|
const style = !kb
|
|
31
87
|
? props.style
|
|
32
|
-
:
|
|
88
|
+
: side === 'top'
|
|
33
89
|
? { ...props.style, top: kb.top, maxHeight: kb.height, height: kb.height }
|
|
34
|
-
:
|
|
35
|
-
? {
|
|
36
|
-
|
|
37
|
-
|
|
90
|
+
: card
|
|
91
|
+
? {
|
|
92
|
+
...props.style,
|
|
93
|
+
top: kb.top + CARD_GAP,
|
|
94
|
+
bottom: 'auto',
|
|
95
|
+
maxHeight: kb.height - CARD_GAP,
|
|
96
|
+
height: kb.height - CARD_GAP,
|
|
97
|
+
}
|
|
98
|
+
: side === 'bottom'
|
|
99
|
+
? { ...props.style, bottom: kb.below, maxHeight: kb.height - 8 }
|
|
100
|
+
: { ...props.style, top: kb.top, height: kb.height };
|
|
101
|
+
return (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { "data-slot": "sheet-content", "data-side": side, "data-card": card || undefined, "data-close": closable || undefined, className: cn('group/sheet bg-popover text-popover-foreground fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-(--assistant-docked,0px) data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=left]:pt-[env(safe-area-inset-top)] data-[side=right]:pt-[env(safe-area-inset-top)]', className,
|
|
102
|
+
// After the caller's classes: a phone's bottom sheet always keeps its shape and room.
|
|
103
|
+
card &&
|
|
104
|
+
'max-h-[calc(100dvh-env(safe-area-inset-top)-2.75rem)] rounded-t-2xl border-t-0 pt-5'), ...props, style: style,
|
|
38
105
|
// A click or focus in the docked the assistant panel leaves the sheet open, so the person can ask
|
|
39
106
|
// about what it shows (LumenShell).
|
|
40
107
|
onInteractOutside: (e) => {
|
|
41
108
|
if (e.target?.closest?.('[data-assistant-docked]'))
|
|
42
109
|
e.preventDefault();
|
|
43
110
|
props.onInteractOutside?.(e);
|
|
44
|
-
}, children: [children,
|
|
111
|
+
}, children: [card && _jsx(SheetGrab, {}), children, card && (_jsx("div", { "aria-hidden": true, className: "h-[env(safe-area-inset-bottom)] shrink-0" })), closable && (_jsx(SheetPrimitive.Close, { "data-slot": "sheet-close", asChild: true, children: _jsxs(Button, { variant: "ghost", className: "absolute top-3 right-3 z-20 group-data-card/sheet:top-8 group-data-[side=left]/sheet:top-[calc(0.75rem+env(safe-area-inset-top))] group-data-[side=right]/sheet:top-[calc(0.75rem+env(safe-area-inset-top))]", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
|
|
45
112
|
}
|
|
113
|
+
/** Room a phone's bottom sheet leaves above it, px: the dimmed page a tap closes it on. */
|
|
114
|
+
const CARD_GAP = 44;
|
|
46
115
|
function SheetHeader({ className, ...props }) {
|
|
47
|
-
return (_jsx("div", { "data-slot": "sheet-header",
|
|
116
|
+
return (_jsx("div", { "data-slot": "sheet-header",
|
|
117
|
+
// Its title stops short of the sheet's X, never running under it.
|
|
118
|
+
className: cn('gap-0.5 p-4 flex flex-col group-data-close/sheet:pr-12', className), ...props }));
|
|
48
119
|
}
|
|
49
120
|
function SheetFooter({ className, ...props }) {
|
|
50
121
|
return (_jsx("div", { "data-slot": "sheet-footer", className: cn('gap-2 p-4 mt-auto flex flex-col', className), ...props }));
|
|
51
122
|
}
|
|
52
123
|
function SheetTitle({ className, ...props }) {
|
|
53
|
-
return (_jsx(SheetPrimitive.Title, { "data-slot": "sheet-title", className: cn('text-foreground text-base font-medium cn-font-heading', className), ...props }));
|
|
124
|
+
return (_jsx(SheetPrimitive.Title, { "data-slot": "sheet-title", className: cn('text-foreground text-base font-medium cn-font-heading min-w-0', className), ...props }));
|
|
54
125
|
}
|
|
55
126
|
function SheetDescription({ className, ...props }) {
|
|
56
127
|
return (_jsx(SheetPrimitive.Description, { "data-slot": "sheet-description", className: cn('text-muted-foreground text-sm', 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.77",
|
|
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.
|
|
48
|
+
"@columenlabs/lumen-ui-types": "0.1.77",
|
|
49
49
|
"@dnd-kit/core": "6.3.1",
|
|
50
50
|
"@dnd-kit/sortable": "10.0.0",
|
|
51
51
|
"@dnd-kit/utilities": "3.2.2",
|