@columenlabs/lumen-ui 0.4.2 → 0.4.4

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,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  /** The visual viewport while a phone's keyboard covers part of the layout viewport. */
2
3
  export interface KeyboardFit {
3
4
  /** Top of what is visible, from the layout viewport's top (iOS pans the page to the focused field). */
@@ -8,8 +9,15 @@ export interface KeyboardFit {
8
9
  below: number;
9
10
  }
10
11
  /**
11
- * Where a fixed modal must sit while the keyboard is open. iOS shrinks the visual viewport but not
12
- * the layout viewport fixed elements use, so a modal centred or bottom-anchored there runs under the
13
- * keyboard or off the top. Undefined while no keyboard is up.
12
+ * Where a fixed overlay must sit while the keyboard is open. iOS shrinks the visual viewport but not
13
+ * the layout viewport fixed elements use, so an overlay sized to the layout viewport runs under the
14
+ * keyboard. Undefined while no keyboard is up.
14
15
  */
15
16
  export declare function useKeyboardFit(): KeyboardFit | undefined;
17
+ /**
18
+ * The one phone rule for every overlay (Dialog, AlertDialog, a bottom Sheet): a full screen pinned to
19
+ * the top, its actions in a bar at the top, so the fields a person types in already sit high and the
20
+ * keyboard covers only what is below them. With the keyboard up the screen ends at the keyboard.
21
+ * The classes are `phoneScreen`; this gives the style.
22
+ */
23
+ export declare function usePhoneScreen(style?: React.CSSProperties): React.CSSProperties | undefined;
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
+ import { useIsMobile } from './use-mobile.js';
2
3
  /**
3
- * Where a fixed modal must sit while the keyboard is open. iOS shrinks the visual viewport but not
4
- * the layout viewport fixed elements use, so a modal centred or bottom-anchored there runs under the
5
- * keyboard or off the top. Undefined while no keyboard is up.
4
+ * Where a fixed overlay must sit while the keyboard is open. iOS shrinks the visual viewport but not
5
+ * the layout viewport fixed elements use, so an overlay sized to the layout viewport runs under the
6
+ * keyboard. Undefined while no keyboard is up.
6
7
  */
7
8
  export function useKeyboardFit() {
8
9
  const [fit, setFit] = React.useState();
@@ -27,3 +28,14 @@ export function useKeyboardFit() {
27
28
  }, []);
28
29
  return fit;
29
30
  }
31
+ /**
32
+ * The one phone rule for every overlay (Dialog, AlertDialog, a bottom Sheet): a full screen pinned to
33
+ * the top, its actions in a bar at the top, so the fields a person types in already sit high and the
34
+ * keyboard covers only what is below them. With the keyboard up the screen ends at the keyboard.
35
+ * The classes are `phoneScreen`; this gives the style.
36
+ */
37
+ export function usePhoneScreen(style) {
38
+ const phone = useIsMobile();
39
+ const kb = useKeyboardFit();
40
+ return phone && kb ? { ...style, top: kb.top, height: kb.height, maxHeight: kb.height } : style;
41
+ }
@@ -19,8 +19,10 @@ export function LumenDrawer({ open, onClose, title, description, children, foote
19
19
  const body = _jsx("div", { className: "flex flex-col px-4 pb-4", children: children });
20
20
  // Every drawer has a footer, styled like a dialog's: the body scrolls above
21
21
  // it and always ends on a clear edge. Without actions of its own it offers Close.
22
- 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" }) })) })] }));
23
- 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: "shrink-0 border-b", children: head }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
22
+ // On a phone the drawer is a full screen and the footer a bar under the header, so the
23
+ // keyboard never covers its actions (Dialog does the same).
24
+ 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 max-md:order-first max-md:border-t-0 max-md:border-b", 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" }) })) })] }));
25
+ 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 }), foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
24
26
  // The resize handle's grab zone is wider than the handle, so a drag can start on the
25
27
  // overlay; the panels library marks that pointerdown handled, and then it doesn't close.
26
28
  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] }) })] }) })) }));
@@ -1,6 +1,7 @@
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 { usePhoneScreen } from '../hooks/use-keyboard-fit.js';
4
5
  import { Button } from './button.js';
5
6
  function AlertDialog({ ...props }) {
6
7
  return _jsx(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
@@ -15,13 +16,15 @@ function AlertDialogOverlay({ className, ...props }) {
15
16
  return (_jsx(AlertDialogPrimitive.Overlay, { "data-slot": "alert-dialog-overlay", className: cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50', className), ...props }));
16
17
  }
17
18
  function AlertDialogContent({ className, size = 'default', ...props }) {
18
- return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, { "data-slot": "alert-dialog-content", "data-size": size, className: cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none', className), ...props })] }));
19
+ // On a phone it is a full screen pinned to the top, its footer a bar at the top (usePhoneScreen).
20
+ const style = usePhoneScreen(props.style);
21
+ return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, { "data-slot": "alert-dialog-content", "data-size": size, className: cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none max-md:inset-0 max-md:h-full 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(1rem+env(safe-area-inset-top))] max-md:ring-0', className), ...props, style: style })] }));
19
22
  }
20
23
  function AlertDialogHeader({ className, ...props }) {
21
24
  return (_jsx("div", { "data-slot": "alert-dialog-header", className: cn('grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]', className), ...props }));
22
25
  }
23
26
  function AlertDialogFooter({ className, ...props }) {
24
- return (_jsx("div", { "data-slot": "alert-dialog-footer", className: cn('bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end', className), ...props }));
27
+ return (_jsx("div", { "data-slot": "alert-dialog-footer", className: cn('bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end max-md:order-first max-md:-mt-4 max-md:mb-0 max-md:flex max-md:flex-row max-md:justify-end max-md:rounded-none max-md:border-t-0 max-md:border-b', className), ...props }));
25
28
  }
26
29
  function AlertDialogMedia({ className, ...props }) {
27
30
  return (_jsx("div", { "data-slot": "alert-dialog-media", className: cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className), ...props }));
@@ -13,7 +13,7 @@ declare function DialogHeader({ className, ...props }: React.ComponentProps<'div
13
13
  * the focus rings of its fields are never cut by the scroll box. It and the
14
14
  * footer bleed by `--dialog-pad`, the content's padding, so a dialog that changes its padding sets
15
15
  * that variable (`[--dialog-pad:0px]`), never `p-0`, and they still meet its edges. */
16
- declare const dialogBody = "-mx-(--dialog-pad) max-h-[60svh] min-h-0 overflow-y-auto px-(--dialog-pad)";
16
+ declare const dialogBody = "-mx-(--dialog-pad) max-h-[60svh] min-h-0 overflow-y-auto px-(--dialog-pad) max-md:max-h-none max-md:overflow-visible";
17
17
  declare function DialogBody({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
18
18
  declare function DialogFooter({ className, showCloseButton, children, ...props }: React.ComponentProps<'div'> & {
19
19
  showCloseButton?: boolean;
package/dist/ui/dialog.js CHANGED
@@ -3,7 +3,7 @@ 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 { useKeyboardFit } from '../hooks/use-keyboard-fit.js';
6
+ import { usePhoneScreen } from '../hooks/use-keyboard-fit.js';
7
7
  function Dialog({ ...props }) {
8
8
  return _jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
9
9
  }
@@ -20,12 +20,9 @@ function DialogOverlay({ className, ...props }) {
20
20
  return (_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: cn('fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0', className), ...props }));
21
21
  }
22
22
  function DialogContent({ className, children, showCloseButton = true, ...props }) {
23
- // With a phone's keyboard up, centre it in what is left above the keyboard and fit it there.
24
- const kb = useKeyboardFit();
25
- const style = kb
26
- ? { ...props.style, top: kb.top + kb.height / 2, maxHeight: kb.height - 16 }
27
- : props.style;
28
- 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 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", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
23
+ // On a phone it is a full screen pinned to the top, its footer a bar at the top (usePhoneScreen).
24
+ 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 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" })] }) }))] })] }));
29
26
  }
30
27
  function DialogHeader({ className, ...props }) {
31
28
  return (_jsx("div", { "data-slot": "dialog-header", className: cn('flex flex-col gap-2', className), ...props }));
@@ -34,12 +31,12 @@ function DialogHeader({ className, ...props }) {
34
31
  * the focus rings of its fields are never cut by the scroll box. It and the
35
32
  * footer bleed by `--dialog-pad`, the content's padding, so a dialog that changes its padding sets
36
33
  * that variable (`[--dialog-pad:0px]`), never `p-0`, and they still meet its edges. */
37
- const dialogBody = '-mx-(--dialog-pad) max-h-[60svh] min-h-0 overflow-y-auto px-(--dialog-pad)';
34
+ const dialogBody = '-mx-(--dialog-pad) max-h-[60svh] min-h-0 overflow-y-auto px-(--dialog-pad) max-md:max-h-none max-md:overflow-visible';
38
35
  function DialogBody({ className, ...props }) {
39
36
  return _jsx("div", { "data-slot": "dialog-body", className: cn(dialogBody, className), ...props });
40
37
  }
41
38
  function DialogFooter({ className, showCloseButton = false, children, ...props }) {
42
- return (_jsxs("div", { "data-slot": "dialog-footer", className: cn('-mx-(--dialog-pad) -mb-(--dialog-pad) flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end', className), ...props, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Close" }) }))] }));
39
+ return (_jsxs("div", { "data-slot": "dialog-footer", className: cn('-mx-(--dialog-pad) -mb-(--dialog-pad) flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end max-md:order-first max-md:-mt-(--dialog-pad) max-md:mb-0 max-md:flex-row max-md:justify-end max-md:rounded-none max-md:border-t-0 max-md:border-b max-md:pr-12', className), ...props, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Close" }) }))] }));
43
40
  }
44
41
  function DialogTitle({ className, ...props }) {
45
42
  return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn('font-heading text-base leading-none font-medium', className), ...props }));
package/dist/ui/sheet.js CHANGED
@@ -4,6 +4,7 @@ import { Dialog as SheetPrimitive } from 'radix-ui';
4
4
  import { Button } from './button.js';
5
5
  import { XIcon } from 'lucide-react';
6
6
  import { useKeyboardFit } from '../hooks/use-keyboard-fit.js';
7
+ import { useIsMobile } from '../hooks/use-mobile.js';
7
8
  function Sheet({ ...props }) {
8
9
  return _jsx(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
9
10
  }
@@ -20,24 +21,27 @@ function SheetOverlay({ className, ...props }) {
20
21
  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 }));
21
22
  }
22
23
  function SheetContent({ className, children, side = 'right', showCloseButton = true, ...props }) {
23
- // With a phone's keyboard up, a bottom sheet sits on the keyboard and fits what is left above it;
24
- // a side sheet spans only the visible part.
24
+ // On a phone a bottom sheet is a full screen pinned to the top, like a Dialog (usePhoneScreen), so
25
+ // the fields a person types in sit high. With the keyboard up a top or full screen sheet ends at
26
+ // the keyboard; a side sheet spans only the visible part.
25
27
  const kb = useKeyboardFit();
28
+ const phone = useIsMobile();
29
+ const screen = side === 'top' || (side === 'bottom' && phone);
26
30
  const style = !kb
27
31
  ? props.style
28
- : side === 'bottom'
29
- ? { ...props.style, bottom: kb.below, maxHeight: kb.height - 8 }
30
- : side === 'top'
31
- ? { ...props.style, top: kb.top, maxHeight: kb.height - 8 }
32
+ : screen
33
+ ? { ...props.style, top: kb.top, maxHeight: kb.height, height: kb.height }
34
+ : side === 'bottom'
35
+ ? { ...props.style, bottom: kb.below, maxHeight: kb.height - 8 }
32
36
  : { ...props.style, top: kb.top, height: kb.height };
33
- return (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { "data-slot": "sheet-content", "data-side": side, className: cn('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', className), ...props, style: style,
37
+ return (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { "data-slot": "sheet-content", "data-side": side, 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 max-md:data-[side=bottom]:top-0 max-md:data-[side=bottom]:h-full max-md:data-[side=bottom]:max-h-none max-md:data-[side=bottom]:rounded-none max-md:data-[side=bottom]:border-t-0 max-md:data-[side=bottom]:pt-[env(safe-area-inset-top)] 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', className), ...props, style: style,
34
38
  // A click or focus in the docked the assistant panel leaves the sheet open, so the person can ask
35
39
  // about what it shows (LumenShell).
36
40
  onInteractOutside: (e) => {
37
41
  if (e.target?.closest?.('[data-assistant-docked]'))
38
42
  e.preventDefault();
39
43
  props.onInteractOutside?.(e);
40
- }, children: [children, showCloseButton && (_jsx(SheetPrimitive.Close, { "data-slot": "sheet-close", asChild: true, children: _jsxs(Button, { variant: "ghost", className: "absolute top-3 right-3", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
44
+ }, children: [children, showCloseButton && (_jsx(SheetPrimitive.Close, { "data-slot": "sheet-close", asChild: true, children: _jsxs(Button, { variant: "ghost", className: "absolute top-3 right-3 max-md:group-data-[side=bottom]/sheet:top-[calc(0.75rem+env(safe-area-inset-top))]", size: "icon-sm", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
41
45
  }
42
46
  function SheetHeader({ className, ...props }) {
43
47
  return (_jsx("div", { "data-slot": "sheet-header", className: cn('gap-0.5 p-4 flex flex-col', className), ...props }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "@codemirror/lang-javascript": "6.2.5",
42
42
  "@codemirror/lang-json": "6.0.2",
43
43
  "@codemirror/lang-sql": "6.10.0",
44
- "@columenlabs/lumen-ui-types": "0.4.2",
44
+ "@columenlabs/lumen-ui-types": "0.4.4",
45
45
  "@dnd-kit/core": "6.3.1",
46
46
  "@dnd-kit/sortable": "10.0.0",
47
47
  "@dnd-kit/utilities": "3.2.2",