@calliopelabs/ui 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +332 -2
- package/dist/appearance/AppearancePicker.d.ts +10 -0
- package/dist/appearance/AppearancePicker.js +75 -0
- package/dist/appearance/AppearanceProvider.d.ts +53 -0
- package/dist/appearance/AppearanceProvider.js +113 -0
- package/dist/appearance/AppearanceScript.d.ts +16 -0
- package/dist/appearance/AppearanceScript.js +14 -0
- package/dist/appearance/appearance.d.ts +90 -0
- package/dist/appearance/appearance.js +151 -0
- package/dist/appearance/boot.d.ts +30 -0
- package/dist/appearance/boot.js +91 -0
- package/dist/appearance/brands.d.ts +94 -0
- package/dist/appearance/brands.js +61 -0
- package/dist/appearance/product.d.ts +89 -0
- package/dist/appearance/product.js +96 -0
- package/dist/data/DataTable.d.ts +66 -0
- package/dist/data/DataTable.js +148 -0
- package/dist/data/List.d.ts +55 -0
- package/dist/data/List.js +41 -0
- package/dist/data/types.d.ts +92 -0
- package/dist/data/types.js +2 -0
- package/dist/data/use-row-selection.d.ts +17 -0
- package/dist/data/use-row-selection.js +51 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +61 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.js +6 -0
- package/dist/lib/local-store.d.ts +12 -0
- package/dist/lib/local-store.js +54 -0
- package/dist/nav/Nav.d.ts +21 -0
- package/dist/nav/Nav.js +97 -0
- package/dist/nav/model.d.ts +77 -0
- package/dist/nav/model.js +72 -0
- package/dist/overlays/ConfirmDialog.d.ts +37 -0
- package/dist/overlays/ConfirmDialog.js +70 -0
- package/dist/overlays/Dialog.d.ts +18 -0
- package/dist/overlays/Dialog.js +39 -0
- package/dist/overlays/Drawer.d.ts +24 -0
- package/dist/overlays/Drawer.js +42 -0
- package/dist/page/Breadcrumbs.d.ts +20 -0
- package/dist/page/Breadcrumbs.js +25 -0
- package/dist/page/PageFrame.d.ts +17 -0
- package/dist/page/PageFrame.js +14 -0
- package/dist/page/PageHeader.d.ts +28 -0
- package/dist/page/PageHeader.js +13 -0
- package/dist/page/Section.d.ts +18 -0
- package/dist/page/Section.js +12 -0
- package/dist/page/TabNav.d.ts +19 -0
- package/dist/page/TabNav.js +16 -0
- package/dist/primitives/Button.d.ts +16 -0
- package/dist/primitives/Button.js +36 -0
- package/dist/primitives/Checkbox.d.ts +8 -0
- package/dist/primitives/Checkbox.js +13 -0
- package/dist/primitives/DropdownMenu.d.ts +11 -0
- package/dist/primitives/DropdownMenu.js +25 -0
- package/dist/primitives/Input.d.ts +2 -0
- package/dist/primitives/Input.js +5 -0
- package/dist/primitives/Popover.d.ts +5 -0
- package/dist/primitives/Popover.js +13 -0
- package/dist/primitives/Skeleton.d.ts +3 -0
- package/dist/primitives/Skeleton.js +6 -0
- package/dist/primitives/Tabs.d.ts +12 -0
- package/dist/primitives/Tabs.js +22 -0
- package/dist/provider/UIProvider.d.ts +29 -0
- package/dist/provider/UIProvider.js +21 -0
- package/dist/provider/messages.d.ts +106 -0
- package/dist/provider/messages.js +104 -0
- package/dist/shell/AppShell.d.ts +43 -0
- package/dist/shell/AppShell.js +62 -0
- package/dist/shell/BrandMark.d.ts +14 -0
- package/dist/shell/BrandMark.js +14 -0
- package/dist/shell/RailPanel.d.ts +39 -0
- package/dist/shell/RailPanel.js +62 -0
- package/dist/shell/RailTree.d.ts +35 -0
- package/dist/shell/RailTree.js +32 -0
- package/dist/shell/Sidebar.d.ts +26 -0
- package/dist/shell/Sidebar.js +21 -0
- package/dist/shell/UserMenu.d.ts +22 -0
- package/dist/shell/UserMenu.js +31 -0
- package/dist/shell/placement.d.ts +10 -0
- package/dist/shell/placement.js +6 -0
- package/dist/shell/rail-state.d.ts +13 -0
- package/dist/shell/rail-state.js +28 -0
- package/dist/states/EmptyState.d.ts +20 -0
- package/dist/states/EmptyState.js +15 -0
- package/dist/states/ErrorState.d.ts +23 -0
- package/dist/states/ErrorState.js +16 -0
- package/dist/states/LoadingState.d.ts +14 -0
- package/dist/states/LoadingState.js +14 -0
- package/dist/status/StatusBadge.d.ts +19 -0
- package/dist/status/StatusBadge.js +13 -0
- package/dist/status/StatusDot.d.ts +14 -0
- package/dist/status/StatusDot.js +7 -0
- package/dist/status/tones.d.ts +17 -0
- package/dist/status/tones.js +29 -0
- package/dist/styles/preset.css +175 -0
- package/dist/styles/theme.css +339 -0
- package/dist/theme/contrast.d.ts +8 -0
- package/dist/theme/contrast.js +18 -0
- package/dist/theme/css.d.ts +23 -0
- package/dist/theme/css.js +116 -0
- package/dist/theme/tokens.d.ts +195 -0
- package/dist/theme/tokens.js +294 -0
- package/package.json +112 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @calliopelabs/ui: the Calliope AI operator UI, shared by Astrolift AI and
|
|
3
|
+
* Zentinelle AI. Styles: `@import "@calliopelabs/ui/preset.css"` after Tailwind.
|
|
4
|
+
*/
|
|
5
|
+
// Environment
|
|
6
|
+
export { UIProvider, useUI, } from "./provider/UIProvider.js";
|
|
7
|
+
export { defaultMessages, mergeMessages, } from "./provider/messages.js";
|
|
8
|
+
export { cn } from "./lib/cn.js";
|
|
9
|
+
// Tokens
|
|
10
|
+
export { ACCENTS, ACCENT_KEYS, DENSITIES, DENSITY_KEYS, GROUNDS, GROUND_KEYS, MODE_TOKENS, SURFACES, } from "./theme/tokens.js";
|
|
11
|
+
export { MIN_NON_TEXT_CONTRAST, MIN_TEXT_CONTRAST, contrastRatio, luminance, } from "./theme/contrast.js";
|
|
12
|
+
// Product and appearance
|
|
13
|
+
export { CORNERS, defineProduct, } from "./appearance/product.js";
|
|
14
|
+
export { astroliftBrand, zentinelleBrand } from "./appearance/brands.js";
|
|
15
|
+
export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance, inkFor, isCustomAccent, modeFor, normalize, normalizePartial, parseCustomAccent, readAppearance, resolveAppearance, writeAppearance, } from "./appearance/appearance.js";
|
|
16
|
+
export { appearanceScript, productStyles } from "./appearance/boot.js";
|
|
17
|
+
export { AppearanceScript } from "./appearance/AppearanceScript.js";
|
|
18
|
+
export { AppearanceProvider, useAppearance, useDensity, useProduct, } from "./appearance/AppearanceProvider.js";
|
|
19
|
+
export { AppearancePicker } from "./appearance/AppearancePicker.js";
|
|
20
|
+
// Navigation
|
|
21
|
+
export { activeFor, areaSwitcher, localizeNav, visibleNav, } from "./nav/model.js";
|
|
22
|
+
export { Nav } from "./nav/Nav.js";
|
|
23
|
+
// Shell
|
|
24
|
+
export { AppShell } from "./shell/AppShell.js";
|
|
25
|
+
export { Sidebar } from "./shell/Sidebar.js";
|
|
26
|
+
export { BrandMark } from "./shell/BrandMark.js";
|
|
27
|
+
export { UserMenu } from "./shell/UserMenu.js";
|
|
28
|
+
export { RailPanel } from "./shell/RailPanel.js";
|
|
29
|
+
export { RailTree, } from "./shell/RailTree.js";
|
|
30
|
+
export { useRailState, useRailWidth } from "./shell/rail-state.js";
|
|
31
|
+
// Page
|
|
32
|
+
export { PageFrame } from "./page/PageFrame.js";
|
|
33
|
+
export { PageHeader } from "./page/PageHeader.js";
|
|
34
|
+
export { Breadcrumbs } from "./page/Breadcrumbs.js";
|
|
35
|
+
export { TabNav } from "./page/TabNav.js";
|
|
36
|
+
export { Section } from "./page/Section.js";
|
|
37
|
+
// Data
|
|
38
|
+
export { DataTable } from "./data/DataTable.js";
|
|
39
|
+
export { List, ListItem } from "./data/List.js";
|
|
40
|
+
export { useRowSelection } from "./data/use-row-selection.js";
|
|
41
|
+
export { DEFAULT_PAGE_SIZE, PAGE_SIZES, } from "./data/types.js";
|
|
42
|
+
// Status
|
|
43
|
+
export { StatusBadge } from "./status/StatusBadge.js";
|
|
44
|
+
export { StatusDot } from "./status/StatusDot.js";
|
|
45
|
+
export { DOT_TONE, IN_FLIGHT, PILL_TONE, STATUS_TONES, TONE_TOKEN, } from "./status/tones.js";
|
|
46
|
+
// States
|
|
47
|
+
export { EmptyState } from "./states/EmptyState.js";
|
|
48
|
+
export { ErrorState } from "./states/ErrorState.js";
|
|
49
|
+
export { LoadingState } from "./states/LoadingState.js";
|
|
50
|
+
// Overlays
|
|
51
|
+
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "./overlays/Dialog.js";
|
|
52
|
+
export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "./overlays/Drawer.js";
|
|
53
|
+
export { ConfirmDialog, } from "./overlays/ConfirmDialog.js";
|
|
54
|
+
// Primitives
|
|
55
|
+
export { Button, buttonVariants } from "./primitives/Button.js";
|
|
56
|
+
export { Input } from "./primitives/Input.js";
|
|
57
|
+
export { Checkbox } from "./primitives/Checkbox.js";
|
|
58
|
+
export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
59
|
+
export { Popover, PopoverContent, PopoverTrigger } from "./primitives/Popover.js";
|
|
60
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
|
|
61
|
+
export { Skeleton } from "./primitives/Skeleton.js";
|
package/dist/lib/cn.d.ts
ADDED
package/dist/lib/cn.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localStorage as a React external store, for per-person display
|
|
3
|
+
* preferences (appearance, rail state). Reads and writes never throw: Safari
|
|
4
|
+
* private mode and embedded webviews throw on storage access. A write that
|
|
5
|
+
* storage refuses is held for the rest of the page, so the choice still
|
|
6
|
+
* applies until reload.
|
|
7
|
+
*/
|
|
8
|
+
/** Dispatched on window after every write in this tab; other tabs hear `storage`. */
|
|
9
|
+
export declare const LOCAL_STORE_EVENT = "calliope-ui:storage";
|
|
10
|
+
export declare function readLocal(key: string): string | null;
|
|
11
|
+
export declare function writeLocal(key: string, value: string | null): void;
|
|
12
|
+
export declare function subscribeLocal(onChange: () => void): () => void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localStorage as a React external store, for per-person display
|
|
3
|
+
* preferences (appearance, rail state). Reads and writes never throw: Safari
|
|
4
|
+
* private mode and embedded webviews throw on storage access. A write that
|
|
5
|
+
* storage refuses is held for the rest of the page, so the choice still
|
|
6
|
+
* applies until reload.
|
|
7
|
+
*/
|
|
8
|
+
/** Dispatched on window after every write in this tab; other tabs hear `storage`. */
|
|
9
|
+
export const LOCAL_STORE_EVENT = "calliope-ui:storage";
|
|
10
|
+
const unsaved = new Map();
|
|
11
|
+
function storage() {
|
|
12
|
+
try {
|
|
13
|
+
return typeof window === "undefined" ? null : window.localStorage;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readLocal(key) {
|
|
20
|
+
if (unsaved.has(key))
|
|
21
|
+
return unsaved.get(key) ?? null;
|
|
22
|
+
try {
|
|
23
|
+
return storage()?.getItem(key) ?? null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function writeLocal(key, value) {
|
|
30
|
+
try {
|
|
31
|
+
const s = storage();
|
|
32
|
+
if (!s)
|
|
33
|
+
throw new Error("storage unavailable");
|
|
34
|
+
if (value === null)
|
|
35
|
+
s.removeItem(key);
|
|
36
|
+
else
|
|
37
|
+
s.setItem(key, value);
|
|
38
|
+
unsaved.delete(key);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
unsaved.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
if (typeof window !== "undefined") {
|
|
44
|
+
window.dispatchEvent(new CustomEvent(LOCAL_STORE_EVENT, { detail: key }));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function subscribeLocal(onChange) {
|
|
48
|
+
window.addEventListener("storage", onChange);
|
|
49
|
+
window.addEventListener(LOCAL_STORE_EVENT, onChange);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener("storage", onChange);
|
|
52
|
+
window.removeEventListener(LOCAL_STORE_EVENT, onChange);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { NavActive, NavModel } from "./model.js";
|
|
3
|
+
export interface NavProps {
|
|
4
|
+
/** The model, already filtered to what the viewer may open (`visibleNav`). */
|
|
5
|
+
nav: NavModel;
|
|
6
|
+
/** The row the current page belongs to (`activeFor`). */
|
|
7
|
+
active: NavActive | null;
|
|
8
|
+
/** Icons only, with each area's functions in a flyout. */
|
|
9
|
+
collapsed?: boolean;
|
|
10
|
+
/** Called when a row is followed, so a drawer holding the nav can close. */
|
|
11
|
+
onNavigate?: () => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The areas and their functions (Astrolift spec 44 §4.1). Expanded, each area
|
|
16
|
+
* is a heading that folds, over its groups and rows; a flat area (Home,
|
|
17
|
+
* Overview) is just its rows. Collapsed, each area is an icon, and hovering,
|
|
18
|
+
* focusing or pressing it opens its rows as a flyout. The current row is the
|
|
19
|
+
* one lit row: an accent tint and a 2px accent edge.
|
|
20
|
+
*/
|
|
21
|
+
export declare function Nav({ nav, active, collapsed, onNavigate, className }: NavProps): React.JSX.Element;
|
package/dist/nav/Nav.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
/**
|
|
9
|
+
* The areas and their functions (Astrolift spec 44 §4.1). Expanded, each area
|
|
10
|
+
* is a heading that folds, over its groups and rows; a flat area (Home,
|
|
11
|
+
* Overview) is just its rows. Collapsed, each area is an icon, and hovering,
|
|
12
|
+
* focusing or pressing it opens its rows as a flyout. The current row is the
|
|
13
|
+
* one lit row: an accent tint and a 2px accent edge.
|
|
14
|
+
*/
|
|
15
|
+
export function Nav({ nav, active, collapsed = false, onNavigate, className }) {
|
|
16
|
+
return (_jsx("div", { className: cn("flex flex-col", collapsed ? "items-center gap-1" : "gap-2", className), children: nav.map((area) => collapsed ? (_jsx(CollapsedArea, { area: area, active: active, onNavigate: onNavigate }, area.key)) : (_jsx(ExpandedArea, { area: area, active: active, onNavigate: onNavigate }, area.key))) }));
|
|
17
|
+
}
|
|
18
|
+
function FunctionLink({ fn, isActive, onNavigate, }) {
|
|
19
|
+
const { Link } = useUI();
|
|
20
|
+
const Icon = fn.icon;
|
|
21
|
+
return (_jsxs(Link, { href: fn.href, onClick: onNavigate, "aria-current": isActive ? "page" : undefined, ...fn.attributes, className: cn("flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", isActive
|
|
22
|
+
? "bg-primary/15 font-medium text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
|
|
23
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground"), children: [Icon && _jsx(Icon, { className: "size-4 shrink-0 opacity-80", "aria-hidden": true }), _jsx("span", { className: "truncate", children: fn.label })] }));
|
|
24
|
+
}
|
|
25
|
+
function Rows({ area, active, onNavigate, }) {
|
|
26
|
+
return (_jsx(_Fragment, { children: area.groups.map((group, i) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [group.label && (_jsx("div", { className: "px-2 pt-2 pb-1 text-2xs text-muted-foreground", children: group.label })), group.functions.map((fn) => (_jsx(FunctionLink, { fn: fn, isActive: active?.area === area.key && active.fn === fn.key, onNavigate: onNavigate }, fn.key)))] }, group.key ?? group.label ?? i))) }));
|
|
27
|
+
}
|
|
28
|
+
function ExpandedArea({ area, active, onNavigate, }) {
|
|
29
|
+
const [open, setOpen] = React.useState(true);
|
|
30
|
+
const regionId = React.useId();
|
|
31
|
+
if (area.flat) {
|
|
32
|
+
return (_jsx("div", { className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) }));
|
|
33
|
+
}
|
|
34
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, "aria-controls": regionId, className: "flex h-7 w-full items-center justify-between rounded-lg px-2 text-2xs font-semibold tracking-wider text-muted-foreground uppercase hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: [area.label, _jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", !open && "-rotate-90"), "aria-hidden": true })] }), _jsx("div", { id: regionId, hidden: !open, className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) })] }));
|
|
35
|
+
}
|
|
36
|
+
/** How long the pointer may be away from a flyout before it closes. */
|
|
37
|
+
const FLYOUT_GRACE_MS = 150;
|
|
38
|
+
function CollapsedArea({ area, active, onNavigate, }) {
|
|
39
|
+
const { Link } = useUI();
|
|
40
|
+
const [open, setOpen] = React.useState(false);
|
|
41
|
+
const pointerOpened = React.useRef(false);
|
|
42
|
+
const closeTimer = React.useRef(null);
|
|
43
|
+
const isActive = active?.area === area.key;
|
|
44
|
+
const Icon = area.icon;
|
|
45
|
+
const only = area.groups.length === 1 && area.groups[0]?.functions.length === 1
|
|
46
|
+
? area.groups[0].functions[0]
|
|
47
|
+
: null;
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
const timer = closeTimer;
|
|
50
|
+
return () => {
|
|
51
|
+
if (timer.current)
|
|
52
|
+
clearTimeout(timer.current);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
const tile = (_jsx("span", { className: cn("flex size-8 items-center justify-center rounded-lg", isActive
|
|
56
|
+
? "bg-primary/15 text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
|
|
57
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: _jsx(Icon, { className: "size-4", "aria-hidden": true }) }));
|
|
58
|
+
// One row: the icon is that row's link, no flyout needed.
|
|
59
|
+
if (only) {
|
|
60
|
+
return (_jsx(Link, { href: only.href, onClick: onNavigate, "aria-label": area.flat ? only.label : area.label, title: area.flat ? only.label : area.label, "aria-current": isActive ? "page" : undefined, ...only.attributes, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: tile }));
|
|
61
|
+
}
|
|
62
|
+
const cancelClose = () => {
|
|
63
|
+
if (closeTimer.current)
|
|
64
|
+
clearTimeout(closeTimer.current);
|
|
65
|
+
closeTimer.current = null;
|
|
66
|
+
};
|
|
67
|
+
const scheduleClose = () => {
|
|
68
|
+
cancelClose();
|
|
69
|
+
closeTimer.current = setTimeout(() => setOpen(false), FLYOUT_GRACE_MS);
|
|
70
|
+
};
|
|
71
|
+
return (_jsxs(Popover, { open: open, onOpenChange: (next) => {
|
|
72
|
+
if (!next)
|
|
73
|
+
pointerOpened.current = false;
|
|
74
|
+
setOpen(next);
|
|
75
|
+
}, children: [_jsx(PopoverTrigger, { "aria-label": area.label, title: area.label, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", onPointerEnter: (event) => {
|
|
76
|
+
if (event.pointerType !== "mouse")
|
|
77
|
+
return;
|
|
78
|
+
cancelClose();
|
|
79
|
+
pointerOpened.current = true;
|
|
80
|
+
setOpen(true);
|
|
81
|
+
}, onPointerLeave: (event) => {
|
|
82
|
+
if (event.pointerType === "mouse")
|
|
83
|
+
scheduleClose();
|
|
84
|
+
}, children: tile }), _jsxs(PopoverContent, { side: "right", align: "start", className: "w-56 gap-0 p-2",
|
|
85
|
+
// Opened by hovering: leave focus where the person is. Opened from the
|
|
86
|
+
// keyboard: move it in, to the first row.
|
|
87
|
+
onOpenAutoFocus: (event) => {
|
|
88
|
+
if (pointerOpened.current)
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
}, onPointerEnter: cancelClose, onPointerLeave: (event) => {
|
|
91
|
+
if (event.pointerType === "mouse")
|
|
92
|
+
scheduleClose();
|
|
93
|
+
}, children: [_jsx("div", { className: "px-2 pb-1 text-2xs font-semibold tracking-wider text-muted-foreground uppercase", children: area.label }), _jsx(Rows, { area: area, active: active, onNavigate: () => {
|
|
94
|
+
setOpen(false);
|
|
95
|
+
onNavigate?.();
|
|
96
|
+
} })] })] }));
|
|
97
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The navigation model (Astrolift spec 44 §4.1 and §4.4; Zentinelle
|
|
4
|
+
* ui-overhaul-ia §4.1): areas, optional groups inside an area, and the
|
|
5
|
+
* functions in each group. A function is one sidebar row; its tabs, views
|
|
6
|
+
* and actions live inside its pages. Pure data plus pure functions, so the
|
|
7
|
+
* sidebar, the breadcrumb switcher and the tests read the same model, and a
|
|
8
|
+
* Server Component can compute the active row.
|
|
9
|
+
*/
|
|
10
|
+
/** Any icon component: lucide-react's, or an app's own SVG. */
|
|
11
|
+
export type NavIcon = React.ComponentType<{
|
|
12
|
+
className?: string;
|
|
13
|
+
"aria-hidden"?: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
export interface NavFunction {
|
|
16
|
+
key: string;
|
|
17
|
+
label: string;
|
|
18
|
+
href: string;
|
|
19
|
+
icon?: NavIcon;
|
|
20
|
+
/**
|
|
21
|
+
* Capabilities that show this row; any one of them is enough. Absent means
|
|
22
|
+
* always shown. The app decides what a capability is: Astrolift passes
|
|
23
|
+
* module keys (`me.modules.<key>.canView`), Zentinelle passes roles.
|
|
24
|
+
*/
|
|
25
|
+
requires?: string | readonly string[];
|
|
26
|
+
/** Other path prefixes this row owns, for pages that sit under several routes. */
|
|
27
|
+
also?: readonly string[];
|
|
28
|
+
/** Extra attributes on the row's link, such as a tour anchor (`data-tour`). */
|
|
29
|
+
attributes?: Readonly<Record<`data-${string}`, string>>;
|
|
30
|
+
}
|
|
31
|
+
export interface NavGroup {
|
|
32
|
+
key?: string;
|
|
33
|
+
label?: string;
|
|
34
|
+
functions: readonly NavFunction[];
|
|
35
|
+
}
|
|
36
|
+
export interface NavArea {
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
icon: NavIcon;
|
|
40
|
+
/** Render the area's functions as top-level rows, with no area heading (Home, Overview). */
|
|
41
|
+
flat?: boolean;
|
|
42
|
+
groups: readonly NavGroup[];
|
|
43
|
+
}
|
|
44
|
+
export type NavModel = readonly NavArea[];
|
|
45
|
+
/** The row the current page belongs to. */
|
|
46
|
+
export interface NavActive {
|
|
47
|
+
area: string;
|
|
48
|
+
fn: string;
|
|
49
|
+
}
|
|
50
|
+
/** The model with every row the viewer may not open removed, and empty groups and areas dropped. */
|
|
51
|
+
export declare function visibleNav(nav: NavModel, can: (capability: string) => boolean): NavArea[];
|
|
52
|
+
/**
|
|
53
|
+
* Which area and function own `pathname`: the function whose href (or `also`
|
|
54
|
+
* prefix) is the longest prefix of it, so /agents/skills/42 is Skills, not
|
|
55
|
+
* Agents. Query strings and fragments are ignored.
|
|
56
|
+
*/
|
|
57
|
+
export declare function activeFor(nav: NavModel, pathname: string): NavActive | null;
|
|
58
|
+
export interface CrumbSwitchOption {
|
|
59
|
+
label: string;
|
|
60
|
+
href: string;
|
|
61
|
+
active?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The first crumb of a page inside an area: the area's name, switching
|
|
65
|
+
* between its functions with the current one checked. Read from the model,
|
|
66
|
+
* so the sidebar and the breadcrumb always agree.
|
|
67
|
+
*/
|
|
68
|
+
export declare function areaSwitcher(nav: NavModel, area: string, activeFn: string): {
|
|
69
|
+
label: string;
|
|
70
|
+
switcher: CrumbSwitchOption[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The model with every label passed through `translate`, for apps whose
|
|
74
|
+
* copy lives in a message catalog. `fallback` is the model's own label;
|
|
75
|
+
* return it when the catalog has no entry.
|
|
76
|
+
*/
|
|
77
|
+
export declare function localizeNav(nav: NavModel, translate: (kind: "area" | "group" | "function", key: string, fallback: string) => string): NavArea[];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
function allowed(fn, can) {
|
|
2
|
+
if (fn.requires === undefined)
|
|
3
|
+
return true;
|
|
4
|
+
const required = typeof fn.requires === "string" ? [fn.requires] : fn.requires;
|
|
5
|
+
return required.some(can);
|
|
6
|
+
}
|
|
7
|
+
/** The model with every row the viewer may not open removed, and empty groups and areas dropped. */
|
|
8
|
+
export function visibleNav(nav, can) {
|
|
9
|
+
return nav
|
|
10
|
+
.map((area) => ({
|
|
11
|
+
...area,
|
|
12
|
+
groups: area.groups
|
|
13
|
+
.map((group) => ({ ...group, functions: group.functions.filter((fn) => allowed(fn, can)) }))
|
|
14
|
+
.filter((group) => group.functions.length > 0),
|
|
15
|
+
}))
|
|
16
|
+
.filter((area) => area.groups.length > 0);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Which area and function own `pathname`: the function whose href (or `also`
|
|
20
|
+
* prefix) is the longest prefix of it, so /agents/skills/42 is Skills, not
|
|
21
|
+
* Agents. Query strings and fragments are ignored.
|
|
22
|
+
*/
|
|
23
|
+
export function activeFor(nav, pathname) {
|
|
24
|
+
const path = pathname.split(/[?#]/)[0] ?? pathname;
|
|
25
|
+
let best = null;
|
|
26
|
+
for (const area of nav) {
|
|
27
|
+
for (const group of area.groups) {
|
|
28
|
+
for (const fn of group.functions) {
|
|
29
|
+
for (const prefix of [fn.href, ...(fn.also ?? [])]) {
|
|
30
|
+
const matches = path === prefix || path.startsWith(`${prefix}/`);
|
|
31
|
+
if (matches && (!best || prefix.length > best.length)) {
|
|
32
|
+
best = { area: area.key, fn: fn.key, length: prefix.length };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return best ? { area: best.area, fn: best.fn } : null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The first crumb of a page inside an area: the area's name, switching
|
|
42
|
+
* between its functions with the current one checked. Read from the model,
|
|
43
|
+
* so the sidebar and the breadcrumb always agree.
|
|
44
|
+
*/
|
|
45
|
+
export function areaSwitcher(nav, area, activeFn) {
|
|
46
|
+
const found = nav.find((a) => a.key === area);
|
|
47
|
+
return {
|
|
48
|
+
label: found?.label ?? area,
|
|
49
|
+
switcher: (found?.groups ?? []).flatMap((group) => group.functions.map((fn) => ({ label: fn.label, href: fn.href, active: fn.key === activeFn }))),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The model with every label passed through `translate`, for apps whose
|
|
54
|
+
* copy lives in a message catalog. `fallback` is the model's own label;
|
|
55
|
+
* return it when the catalog has no entry.
|
|
56
|
+
*/
|
|
57
|
+
export function localizeNav(nav, translate) {
|
|
58
|
+
return nav.map((area) => ({
|
|
59
|
+
...area,
|
|
60
|
+
label: translate("area", area.key, area.label),
|
|
61
|
+
groups: area.groups.map((group) => ({
|
|
62
|
+
...group,
|
|
63
|
+
...(group.label !== undefined
|
|
64
|
+
? { label: translate("group", group.key ?? group.label, group.label) }
|
|
65
|
+
: {}),
|
|
66
|
+
functions: group.functions.map((fn) => ({
|
|
67
|
+
...fn,
|
|
68
|
+
label: translate("function", fn.key, fn.label),
|
|
69
|
+
})),
|
|
70
|
+
})),
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** A required free-text reason, for actions the audit trail must explain. */
|
|
3
|
+
export interface ConfirmReason {
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
/** Shown when the reason is empty. Defaults to "A reason is required." */
|
|
7
|
+
requiredError?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ConfirmDialogProps {
|
|
10
|
+
open: boolean;
|
|
11
|
+
/** Called on cancel, Escape, and after a confirmation that succeeded. Not called while one is pending. */
|
|
12
|
+
onOpenChange: (open: boolean) => void;
|
|
13
|
+
title: React.ReactNode;
|
|
14
|
+
/** The blast radius: what changes, what is recoverable, who can undo it. */
|
|
15
|
+
description?: React.ReactNode;
|
|
16
|
+
confirmLabel?: React.ReactNode;
|
|
17
|
+
cancelLabel?: React.ReactNode;
|
|
18
|
+
/** The destructive treatment, for delete, revoke and abort. */
|
|
19
|
+
destructive?: boolean;
|
|
20
|
+
/** Hold the confirm button until required data is ready. */
|
|
21
|
+
confirmDisabled?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Awaited on confirm, with the trimmed reason ("" without one). While it is
|
|
24
|
+
* pending both buttons are disabled. Resolving `false` keeps the dialog
|
|
25
|
+
* open (the caller reported the failure); any other result closes it. A
|
|
26
|
+
* rejection keeps it open and shows the error's message in the dialog.
|
|
27
|
+
*/
|
|
28
|
+
onConfirm: (reason: string) => Promise<unknown> | unknown;
|
|
29
|
+
reason?: ConfirmReason;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The confirmation for destructive actions (Astrolift's ConfirmDialog, #2126):
|
|
33
|
+
* a themed alert dialog with a pending state and real error feedback, in
|
|
34
|
+
* place of `window.confirm`, which blocks the event loop and can say nothing
|
|
35
|
+
* when the action fails.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive, confirmDisabled, onConfirm, reason, }: ConfirmDialogProps): React.JSX.Element;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Button } from "../primitives/Button.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
import { overlayClassName } from "./Dialog.js";
|
|
9
|
+
/**
|
|
10
|
+
* The confirmation for destructive actions (Astrolift's ConfirmDialog, #2126):
|
|
11
|
+
* a themed alert dialog with a pending state and real error feedback, in
|
|
12
|
+
* place of `window.confirm`, which blocks the event loop and can say nothing
|
|
13
|
+
* when the action fails.
|
|
14
|
+
*/
|
|
15
|
+
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive = false, confirmDisabled = false, onConfirm, reason, }) {
|
|
16
|
+
const { messages } = useUI();
|
|
17
|
+
const t = messages.confirm;
|
|
18
|
+
const [pending, setPending] = React.useState(false);
|
|
19
|
+
const [draft, setDraft] = React.useState("");
|
|
20
|
+
const [error, setError] = React.useState(null);
|
|
21
|
+
const reasonId = React.useId();
|
|
22
|
+
const errorId = `${reasonId}-error`;
|
|
23
|
+
// Cleared on every close, so the next open never shows a cancelled attempt.
|
|
24
|
+
function close() {
|
|
25
|
+
setDraft("");
|
|
26
|
+
setError(null);
|
|
27
|
+
onOpenChange(false);
|
|
28
|
+
}
|
|
29
|
+
async function confirm(event) {
|
|
30
|
+
// Radix closes on Action by default; hold it open while the action runs.
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
if (pending || confirmDisabled)
|
|
33
|
+
return;
|
|
34
|
+
const trimmed = draft.trim();
|
|
35
|
+
if (reason && !trimmed) {
|
|
36
|
+
setError(reason.requiredError ?? t.reasonRequired);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setError(null);
|
|
40
|
+
setPending(true);
|
|
41
|
+
try {
|
|
42
|
+
const result = await onConfirm(trimmed);
|
|
43
|
+
if (result !== false)
|
|
44
|
+
close();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
setError(err instanceof Error && err.message
|
|
48
|
+
? err.message
|
|
49
|
+
: typeof err === "string" && err
|
|
50
|
+
? err
|
|
51
|
+
: t.failed);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setPending(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return (_jsx(AlertDialogPrimitive.Root, { open: open, onOpenChange: (next) => {
|
|
58
|
+
// A stray Escape must not abandon a request in flight.
|
|
59
|
+
if (pending && !next)
|
|
60
|
+
return;
|
|
61
|
+
if (next)
|
|
62
|
+
onOpenChange(true);
|
|
63
|
+
else
|
|
64
|
+
close();
|
|
65
|
+
}, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { "data-slot": "confirm-dialog-overlay", className: overlayClassName() }), _jsxs(AlertDialogPrimitive.Content, { "data-slot": "confirm-dialog", "aria-busy": pending || undefined, ...(description == null ? { "aria-describedby": undefined } : {}), className: "fixed top-1/2 left-1/2 z-50 grid w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 grid-cols-[minmax(0,1fr)] gap-4 rounded-lg bg-popover p-5 text-popover-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-[state=open]:animate-cui-pop-in", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(AlertDialogPrimitive.Title, { className: "font-head text-base font-semibold [overflow-wrap:anywhere]", children: title }), description != null && (_jsx(AlertDialogPrimitive.Description, { className: "text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description }))] }), reason && (_jsxs("div", { className: "grid min-w-0 gap-1.5", children: [_jsx("label", { htmlFor: reasonId, className: "text-sm font-medium", children: reason.label }), _jsx("textarea", { id: reasonId, value: draft, rows: 4, disabled: pending, placeholder: reason.placeholder, "aria-invalid": error != null || undefined, "aria-describedby": error ? errorId : undefined, onChange: (event) => {
|
|
66
|
+
setDraft(event.target.value);
|
|
67
|
+
if (error)
|
|
68
|
+
setError(null);
|
|
69
|
+
}, className: "min-h-20 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50 aria-invalid:border-destructive" })] })), error && (_jsx("p", { id: errorId, role: "alert", className: "rounded-lg border border-danger-border bg-danger-bg px-3 py-2 text-sm [overflow-wrap:anywhere] text-danger-fg", children: error })), _jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [_jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "outline", disabled: pending, children: cancelLabel ?? t.cancel }) }), _jsx(AlertDialogPrimitive.Action, { asChild: true, children: _jsx(Button, { variant: destructive ? "destructive" : "default", disabled: pending || confirmDisabled, onClick: confirm, className: cn(pending && "cursor-progress"), children: pending ? t.working : (confirmLabel ?? t.confirm) }) })] })] })] }) }));
|
|
70
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
export declare function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
|
|
4
|
+
export declare function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element;
|
|
5
|
+
export declare function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element;
|
|
6
|
+
/** The scrim behind a modal; shared with Drawer. */
|
|
7
|
+
export declare function overlayClassName(className?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* A centred modal on the raised surface. Focus moves in on open and back to
|
|
10
|
+
* the trigger on close (Radix); Escape and the scrim close it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function DialogContent({ className, children, hideCloseButton, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
13
|
+
hideCloseButton?: boolean;
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
export declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
16
|
+
export declare function DialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
17
|
+
export declare function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>): React.JSX.Element;
|
|
18
|
+
export declare function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>): React.JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { XIcon } from "lucide-react";
|
|
4
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
7
|
+
export function Dialog(props) {
|
|
8
|
+
return _jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
9
|
+
}
|
|
10
|
+
export function DialogTrigger(props) {
|
|
11
|
+
return _jsx(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
|
|
12
|
+
}
|
|
13
|
+
export function DialogClose(props) {
|
|
14
|
+
return _jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
|
|
15
|
+
}
|
|
16
|
+
/** The scrim behind a modal; shared with Drawer. */
|
|
17
|
+
export function overlayClassName(className) {
|
|
18
|
+
return cn("fixed inset-0 z-50 bg-black/40 data-[state=open]:animate-cui-fade-in supports-backdrop-filter:backdrop-blur-xs", className);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A centred modal on the raised surface. Focus moves in on open and back to
|
|
22
|
+
* the trigger on close (Radix); Escape and the scrim close it.
|
|
23
|
+
*/
|
|
24
|
+
export function DialogContent({ className, children, hideCloseButton = false, ...props }) {
|
|
25
|
+
const { messages } = useUI();
|
|
26
|
+
return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: overlayClassName() }), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 grid-cols-[minmax(0,1fr)] gap-4 overflow-y-auto rounded-lg bg-popover p-6 text-popover-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-[state=open]:animate-cui-pop-in", className), ...props, children: [children, !hideCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close-button", "aria-label": messages.dialog.close, className: "absolute top-3 right-3 rounded-lg p-1 text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: _jsx(XIcon, { className: "size-4", "aria-hidden": true }) }))] })] }));
|
|
27
|
+
}
|
|
28
|
+
export function DialogHeader({ className, ...props }) {
|
|
29
|
+
return (_jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-1.5 pr-6", className), ...props }));
|
|
30
|
+
}
|
|
31
|
+
export function DialogFooter({ className, ...props }) {
|
|
32
|
+
return (_jsx("div", { "data-slot": "dialog-footer", className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props }));
|
|
33
|
+
}
|
|
34
|
+
export function DialogTitle({ className, ...props }) {
|
|
35
|
+
return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn("font-head text-lg font-semibold [overflow-wrap:anywhere]", className), ...props }));
|
|
36
|
+
}
|
|
37
|
+
export function DialogDescription({ className, ...props }) {
|
|
38
|
+
return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("text-sm [overflow-wrap:anywhere] text-muted-foreground", className), ...props }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
export declare function Drawer(props: React.ComponentProps<typeof DrawerPrimitive.Root>): React.JSX.Element;
|
|
4
|
+
export declare function DrawerTrigger(props: React.ComponentProps<typeof DrawerPrimitive.Trigger>): React.JSX.Element;
|
|
5
|
+
export declare function DrawerClose(props: React.ComponentProps<typeof DrawerPrimitive.Close>): React.JSX.Element;
|
|
6
|
+
declare const SIDE: {
|
|
7
|
+
readonly right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=open]:animate-cui-slide-in-right sm:max-w-sm";
|
|
8
|
+
readonly left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=open]:animate-cui-slide-in-left sm:max-w-sm";
|
|
9
|
+
readonly bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=open]:animate-cui-slide-in-bottom";
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A modal panel from an edge: secondary content that should not cost a page
|
|
13
|
+
* (a detail sheet, a filter set), and the shell's rails on narrow screens.
|
|
14
|
+
* The Astrolift UI calls it a sheet.
|
|
15
|
+
*/
|
|
16
|
+
export declare function DrawerContent({ className, children, side, hideCloseButton, ...props }: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
17
|
+
side?: keyof typeof SIDE;
|
|
18
|
+
hideCloseButton?: boolean;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
export declare function DrawerHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
21
|
+
export declare function DrawerFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
22
|
+
export declare function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>): React.JSX.Element;
|
|
23
|
+
export declare function DrawerDescription({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Description>): React.JSX.Element;
|
|
24
|
+
export {};
|