@calliopelabs/ui 0.0.0-stage → 0.3.1

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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +334 -2
  3. package/dist/appearance/AppearancePicker.d.ts +10 -0
  4. package/dist/appearance/AppearancePicker.js +75 -0
  5. package/dist/appearance/AppearanceProvider.d.ts +53 -0
  6. package/dist/appearance/AppearanceProvider.js +113 -0
  7. package/dist/appearance/AppearanceScript.d.ts +16 -0
  8. package/dist/appearance/AppearanceScript.js +14 -0
  9. package/dist/appearance/appearance.d.ts +90 -0
  10. package/dist/appearance/appearance.js +151 -0
  11. package/dist/appearance/boot.d.ts +30 -0
  12. package/dist/appearance/boot.js +91 -0
  13. package/dist/appearance/brands.d.ts +187 -0
  14. package/dist/appearance/brands.js +150 -0
  15. package/dist/appearance/product.d.ts +89 -0
  16. package/dist/appearance/product.js +96 -0
  17. package/dist/data/DataTable.d.ts +66 -0
  18. package/dist/data/DataTable.js +148 -0
  19. package/dist/data/List.d.ts +55 -0
  20. package/dist/data/List.js +41 -0
  21. package/dist/data/types.d.ts +92 -0
  22. package/dist/data/types.js +2 -0
  23. package/dist/data/use-row-selection.d.ts +17 -0
  24. package/dist/data/use-row-selection.js +51 -0
  25. package/dist/index.d.ts +50 -0
  26. package/dist/index.js +61 -0
  27. package/dist/lib/cn.d.ts +3 -0
  28. package/dist/lib/cn.js +6 -0
  29. package/dist/lib/local-store.d.ts +12 -0
  30. package/dist/lib/local-store.js +54 -0
  31. package/dist/nav/Nav.d.ts +21 -0
  32. package/dist/nav/Nav.js +97 -0
  33. package/dist/nav/model.d.ts +77 -0
  34. package/dist/nav/model.js +72 -0
  35. package/dist/overlays/ConfirmDialog.d.ts +37 -0
  36. package/dist/overlays/ConfirmDialog.js +70 -0
  37. package/dist/overlays/Dialog.d.ts +18 -0
  38. package/dist/overlays/Dialog.js +39 -0
  39. package/dist/overlays/Drawer.d.ts +24 -0
  40. package/dist/overlays/Drawer.js +42 -0
  41. package/dist/page/Breadcrumbs.d.ts +20 -0
  42. package/dist/page/Breadcrumbs.js +25 -0
  43. package/dist/page/PageFrame.d.ts +17 -0
  44. package/dist/page/PageFrame.js +14 -0
  45. package/dist/page/PageHeader.d.ts +28 -0
  46. package/dist/page/PageHeader.js +13 -0
  47. package/dist/page/Section.d.ts +18 -0
  48. package/dist/page/Section.js +12 -0
  49. package/dist/page/TabNav.d.ts +19 -0
  50. package/dist/page/TabNav.js +16 -0
  51. package/dist/primitives/Button.d.ts +16 -0
  52. package/dist/primitives/Button.js +36 -0
  53. package/dist/primitives/Checkbox.d.ts +8 -0
  54. package/dist/primitives/Checkbox.js +13 -0
  55. package/dist/primitives/DropdownMenu.d.ts +11 -0
  56. package/dist/primitives/DropdownMenu.js +25 -0
  57. package/dist/primitives/Input.d.ts +2 -0
  58. package/dist/primitives/Input.js +5 -0
  59. package/dist/primitives/Popover.d.ts +5 -0
  60. package/dist/primitives/Popover.js +13 -0
  61. package/dist/primitives/Skeleton.d.ts +3 -0
  62. package/dist/primitives/Skeleton.js +6 -0
  63. package/dist/primitives/Tabs.d.ts +12 -0
  64. package/dist/primitives/Tabs.js +22 -0
  65. package/dist/provider/UIProvider.d.ts +29 -0
  66. package/dist/provider/UIProvider.js +21 -0
  67. package/dist/provider/messages.d.ts +106 -0
  68. package/dist/provider/messages.js +104 -0
  69. package/dist/shell/AppShell.d.ts +43 -0
  70. package/dist/shell/AppShell.js +62 -0
  71. package/dist/shell/BrandMark.d.ts +14 -0
  72. package/dist/shell/BrandMark.js +14 -0
  73. package/dist/shell/RailPanel.d.ts +39 -0
  74. package/dist/shell/RailPanel.js +62 -0
  75. package/dist/shell/RailTree.d.ts +35 -0
  76. package/dist/shell/RailTree.js +32 -0
  77. package/dist/shell/Sidebar.d.ts +26 -0
  78. package/dist/shell/Sidebar.js +21 -0
  79. package/dist/shell/UserMenu.d.ts +22 -0
  80. package/dist/shell/UserMenu.js +31 -0
  81. package/dist/shell/placement.d.ts +10 -0
  82. package/dist/shell/placement.js +6 -0
  83. package/dist/shell/rail-state.d.ts +13 -0
  84. package/dist/shell/rail-state.js +28 -0
  85. package/dist/states/EmptyState.d.ts +20 -0
  86. package/dist/states/EmptyState.js +15 -0
  87. package/dist/states/ErrorState.d.ts +23 -0
  88. package/dist/states/ErrorState.js +16 -0
  89. package/dist/states/LoadingState.d.ts +14 -0
  90. package/dist/states/LoadingState.js +14 -0
  91. package/dist/status/StatusBadge.d.ts +19 -0
  92. package/dist/status/StatusBadge.js +13 -0
  93. package/dist/status/StatusDot.d.ts +14 -0
  94. package/dist/status/StatusDot.js +7 -0
  95. package/dist/status/tones.d.ts +17 -0
  96. package/dist/status/tones.js +29 -0
  97. package/dist/styles/preset.css +175 -0
  98. package/dist/styles/theme.css +339 -0
  99. package/dist/theme/contrast.d.ts +8 -0
  100. package/dist/theme/contrast.js +18 -0
  101. package/dist/theme/css.d.ts +23 -0
  102. package/dist/theme/css.js +116 -0
  103. package/dist/theme/tokens.d.ts +195 -0
  104. package/dist/theme/tokens.js +294 -0
  105. package/package.json +112 -4
@@ -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 {};
@@ -0,0 +1,42 @@
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 DrawerPrimitive } from "radix-ui";
5
+ import { cn } from "../lib/cn.js";
6
+ import { useUI } from "../provider/UIProvider.js";
7
+ import { overlayClassName } from "./Dialog.js";
8
+ export function Drawer(props) {
9
+ return _jsx(DrawerPrimitive.Root, { "data-slot": "drawer", ...props });
10
+ }
11
+ export function DrawerTrigger(props) {
12
+ return _jsx(DrawerPrimitive.Trigger, { "data-slot": "drawer-trigger", ...props });
13
+ }
14
+ export function DrawerClose(props) {
15
+ return _jsx(DrawerPrimitive.Close, { "data-slot": "drawer-close", ...props });
16
+ }
17
+ const SIDE = {
18
+ 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",
19
+ 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",
20
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=open]:animate-cui-slide-in-bottom",
21
+ };
22
+ /**
23
+ * A modal panel from an edge: secondary content that should not cost a page
24
+ * (a detail sheet, a filter set), and the shell's rails on narrow screens.
25
+ * The Astrolift UI calls it a sheet.
26
+ */
27
+ export function DrawerContent({ className, children, side = "right", hideCloseButton = false, ...props }) {
28
+ const { messages } = useUI();
29
+ return (_jsxs(DrawerPrimitive.Portal, { children: [_jsx(DrawerPrimitive.Overlay, { "data-slot": "drawer-overlay", className: overlayClassName() }), _jsxs(DrawerPrimitive.Content, { "data-slot": "drawer-content", "data-side": side, className: cn("fixed z-50 flex flex-col gap-4 overflow-y-auto border-border bg-background text-sm text-foreground shadow-lg outline-none", SIDE[side], className), ...props, children: [children, !hideCloseButton && (_jsx(DrawerPrimitive.Close, { "data-slot": "drawer-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 }) }))] })] }));
30
+ }
31
+ export function DrawerHeader({ className, ...props }) {
32
+ return (_jsx("div", { "data-slot": "drawer-header", className: cn("flex flex-col gap-1 p-4 pr-10", className), ...props }));
33
+ }
34
+ export function DrawerFooter({ className, ...props }) {
35
+ return (_jsx("div", { "data-slot": "drawer-footer", className: cn("mt-auto flex flex-col gap-2 p-4", className), ...props }));
36
+ }
37
+ export function DrawerTitle({ className, ...props }) {
38
+ return (_jsx(DrawerPrimitive.Title, { "data-slot": "drawer-title", className: cn("font-head text-base font-semibold text-foreground", className), ...props }));
39
+ }
40
+ export function DrawerDescription({ className, ...props }) {
41
+ return (_jsx(DrawerPrimitive.Description, { "data-slot": "drawer-description", className: cn("text-sm text-muted-foreground", className), ...props }));
42
+ }
@@ -0,0 +1,20 @@
1
+ import type { CrumbSwitchOption } from "../nav/model.js";
2
+ export interface Crumb {
3
+ label: string;
4
+ /** Absent on the last crumb, the page itself. */
5
+ href?: string;
6
+ /** The sideways move: the area's other functions (`areaSwitcher`), or the person's projects. */
7
+ switcher?: readonly CrumbSwitchOption[];
8
+ }
9
+ export interface BreadcrumbsProps {
10
+ crumbs: readonly Crumb[];
11
+ /** The landmark's name. Defaults to "Breadcrumb". */
12
+ label?: string;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Where the page sits. Crumbs come from the navigation model, never from
17
+ * path segments (a segment is not always a page). The first crumb usually
18
+ * switches between the area's functions.
19
+ */
20
+ export declare function Breadcrumbs({ crumbs, label, className }: BreadcrumbsProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { CheckIcon, ChevronDownIcon } from "lucide-react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../primitives/DropdownMenu.js";
6
+ import { useUI } from "../provider/UIProvider.js";
7
+ /**
8
+ * Where the page sits. Crumbs come from the navigation model, never from
9
+ * path segments (a segment is not always a page). The first crumb usually
10
+ * switches between the area's functions.
11
+ */
12
+ export function Breadcrumbs({ crumbs, label, className }) {
13
+ const { messages } = useUI();
14
+ return (_jsx("nav", { "aria-label": label ?? messages.breadcrumb.label, className: cn("min-w-0", className), children: _jsx("ol", { className: "flex min-w-0 flex-wrap items-center gap-1.5 text-sm text-muted-foreground", children: crumbs.map((crumb, i) => (_jsxs("li", { className: "flex min-w-0 items-center gap-1.5", children: [i > 0 && (_jsx("span", { "aria-hidden": true, className: "text-muted-foreground/60", children: "\u203A" })), _jsx(CrumbItem, { crumb: crumb, isLast: i === crumbs.length - 1 })] }, `${crumb.label}-${i}`))) }) }));
15
+ }
16
+ function CrumbItem({ crumb, isLast }) {
17
+ const { Link, messages } = useUI();
18
+ if (crumb.switcher && crumb.switcher.length > 0) {
19
+ return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { "aria-label": messages.breadcrumb.switchFrom(crumb.label), className: "inline-flex min-w-0 items-center gap-1 rounded-lg hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: [_jsx("span", { className: "truncate", children: crumb.label }), _jsx(ChevronDownIcon, { className: "size-3.5 shrink-0", "aria-hidden": true })] }), _jsx(DropdownMenuContent, { className: "min-w-48", children: crumb.switcher.map((option) => (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { href: option.href, "aria-current": option.active ? "page" : undefined, children: [_jsx(CheckIcon, { className: cn("size-3.5", !option.active && "invisible"), "aria-hidden": true }), option.label] }) }, option.href))) })] }));
20
+ }
21
+ if (isLast || !crumb.href) {
22
+ return (_jsx("span", { "aria-current": isLast ? "page" : undefined, className: cn("min-w-0 truncate", isLast && "text-foreground"), children: crumb.label }));
23
+ }
24
+ return (_jsx(Link, { href: crumb.href, className: "min-w-0 truncate rounded-lg hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: crumb.label }));
25
+ }
@@ -0,0 +1,17 @@
1
+ import type * as React from "react";
2
+ import { type PageHeaderProps } from "./PageHeader.js";
3
+ export interface PageFrameProps extends PageHeaderProps {
4
+ /** The filter bar, under the header: search, filter selects, the list's actions. */
5
+ filters?: React.ReactNode;
6
+ /** A notice the reader must see before the body (a one-time reveal). Not a second list. */
7
+ notice?: React.ReactNode;
8
+ children: React.ReactNode;
9
+ }
10
+ /**
11
+ * A page: its header (breadcrumb, title, actions, tabs), an optional filter
12
+ * bar and notice, then the body. The body is made of Sections holding Cards,
13
+ * tables or charts, two container levels at most (Astrolift's container
14
+ * rule). A page fits about two screens; more becomes a tab, a section that
15
+ * loads when opened, or a drawer.
16
+ */
17
+ export declare function PageFrame({ filters, notice, children, className, ...header }: PageFrameProps): React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { PageHeader } from "./PageHeader.js";
5
+ /**
6
+ * A page: its header (breadcrumb, title, actions, tabs), an optional filter
7
+ * bar and notice, then the body. The body is made of Sections holding Cards,
8
+ * tables or charts, two container levels at most (Astrolift's container
9
+ * rule). A page fits about two screens; more becomes a tab, a section that
10
+ * loads when opened, or a drawer.
11
+ */
12
+ export function PageFrame({ filters, notice, children, className, ...header }) {
13
+ return (_jsxs("div", { "data-slot": "page-frame", className: cn("flex min-w-0 flex-1 flex-col gap-4", className), children: [_jsx(PageHeader, { ...header }), notice, filters && (_jsx("div", { "data-slot": "page-filters", className: "flex min-w-0 flex-wrap items-center gap-2", children: filters })), _jsx("div", { className: "flex min-w-0 flex-col gap-6", children: children })] }));
14
+ }
@@ -0,0 +1,28 @@
1
+ import type * as React from "react";
2
+ import { type Crumb } from "./Breadcrumbs.js";
3
+ import { type TabNavItem } from "./TabNav.js";
4
+ export interface PageHeaderProps {
5
+ crumbs?: readonly Crumb[];
6
+ title: React.ReactNode;
7
+ /** A status badge beside the title. */
8
+ status?: React.ReactNode;
9
+ /** One line of context beside the title: environment, region, model. */
10
+ context?: React.ReactNode;
11
+ /** Supporting copy under the title row. */
12
+ description?: React.ReactNode;
13
+ /** The page's one primary action. */
14
+ primaryAction?: React.ReactNode;
15
+ /** The `⋯` menu, beside the primary action. */
16
+ menu?: React.ReactNode;
17
+ /** At most one row of tabs: the views of a list, or an entity's own tabs. */
18
+ tabs?: readonly TabNavItem[];
19
+ /** Names the tab row. Defaults to "Page". */
20
+ tabsLabel?: string;
21
+ className?: string;
22
+ }
23
+ /**
24
+ * Every page's header, in three rows (Astrolift spec 44 §4.4): the
25
+ * breadcrumb, the title row with its status, context and actions, and at
26
+ * most one row of tabs. Pure.
27
+ */
28
+ export declare function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel, className, }: PageHeaderProps): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { Breadcrumbs } from "./Breadcrumbs.js";
5
+ import { TabNav } from "./TabNav.js";
6
+ /**
7
+ * Every page's header, in three rows (Astrolift spec 44 §4.4): the
8
+ * breadcrumb, the title row with its status, context and actions, and at
9
+ * most one row of tabs. Pure.
10
+ */
11
+ export function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel = "Page", className, }) {
12
+ return (_jsxs("header", { "data-slot": "page-header", className: cn("flex min-w-0 flex-col gap-3", className), children: [crumbs && crumbs.length > 0 && _jsx(Breadcrumbs, { crumbs: crumbs }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [_jsx("h1", { className: "min-w-0 truncate font-head text-xl font-semibold", children: title }), status, context && (_jsx("span", { className: "min-w-0 truncate text-sm text-muted-foreground", children: context })), (primaryAction || menu) && (_jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [primaryAction, menu] }))] }), description && (_jsx("div", { className: "max-w-2xl text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description })), tabs && tabs.length > 0 && (_jsx(TabNav, { tabs: tabs, label: tabsLabel, className: "-mx-3 md:-mx-6" }))] }));
13
+ }
@@ -0,0 +1,18 @@
1
+ import type * as React from "react";
2
+ export interface SectionProps extends Omit<React.ComponentProps<"section">, "title"> {
3
+ title?: React.ReactNode;
4
+ description?: React.ReactNode;
5
+ /** Actions beside the title. */
6
+ action?: React.ReactNode;
7
+ /** A hairline under the heading; spacing alone by default. */
8
+ divided?: boolean;
9
+ /** For the document outline. Defaults to h2. */
10
+ headingLevel?: "h2" | "h3";
11
+ children: React.ReactNode;
12
+ }
13
+ /**
14
+ * A page region, expressed as a heading rather than a border: the top level
15
+ * of `PageFrame > Section > (Card | table | chart)`. Never wrap a bordered box
16
+ * around another, and never put a Section inside a Card.
17
+ */
18
+ export declare function Section({ title, description, action, divided, headingLevel, className, children, ...props }: SectionProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ /**
4
+ * A page region, expressed as a heading rather than a border: the top level
5
+ * of `PageFrame > Section > (Card | table | chart)`. Never wrap a bordered box
6
+ * around another, and never put a Section inside a Card.
7
+ */
8
+ export function Section({ title, description, action, divided = false, headingLevel = "h2", className, children, ...props }) {
9
+ const Heading = headingLevel;
10
+ const hasHeader = Boolean(title || description || action);
11
+ return (_jsxs("section", { "data-slot": "section", className: cn("flex min-w-0 flex-col gap-4", className), ...props, children: [hasHeader && (_jsxs("div", { className: cn("flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between", divided && "border-b pb-3"), children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [title && _jsx(Heading, { className: "text-base font-semibold tracking-tight", children: title }), description && _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), action && _jsx("div", { className: "flex flex-wrap items-center gap-2", children: action })] })), children] }));
12
+ }
@@ -0,0 +1,19 @@
1
+ export interface TabNavItem {
2
+ key: string;
3
+ label: string;
4
+ href: string;
5
+ active: boolean;
6
+ }
7
+ export interface TabNavProps {
8
+ tabs: readonly TabNavItem[];
9
+ /** The landmark's name ("Agent", "Views"). */
10
+ label: string;
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Tabs that are pages: an entity's Overview, Activity, Settings; a list's
15
+ * views. Links, so each can be opened in a new tab and shared; the current
16
+ * one carries `aria-current="page"`. One row at most per page, named by
17
+ * function. On a narrow screen the row scrolls sideways under an edge fade.
18
+ */
19
+ export declare function TabNav({ tabs, label, className }: TabNavProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { useUI } from "../provider/UIProvider.js";
5
+ /**
6
+ * Tabs that are pages: an entity's Overview, Activity, Settings; a list's
7
+ * views. Links, so each can be opened in a new tab and shared; the current
8
+ * one carries `aria-current="page"`. One row at most per page, named by
9
+ * function. On a narrow screen the row scrolls sideways under an edge fade.
10
+ */
11
+ export function TabNav({ tabs, label, className }) {
12
+ const { Link } = useUI();
13
+ return (_jsx("nav", { "aria-label": label, className: cn("flex min-w-0 [scrollbar-width:none] gap-1 overflow-x-auto border-b border-border [mask-image:linear-gradient(to_right,transparent_0,black_1.5rem,black_calc(100%-3rem),transparent_100%)] px-3 md:px-6", className), children: tabs.map((tab) => (_jsxs(Link, { href: tab.href, "aria-current": tab.active ? "page" : undefined, className: cn("relative shrink-0 rounded-lg px-3 py-2.5 text-sm transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", tab.active
14
+ ? "font-medium text-foreground"
15
+ : "text-muted-foreground hover:text-foreground"), children: [tab.label, tab.active && (_jsx("span", { "aria-hidden": true, className: "absolute inset-x-1 -bottom-px h-0.5 rounded-full bg-primary" }))] }, tab.key))) }));
16
+ }
@@ -0,0 +1,16 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ /**
4
+ * Astrolift's button (shadcn), on the family tokens. Every size takes the
5
+ * corners setting (`rounded-lg` is `--radius`), so a 0px or 10px choice
6
+ * reaches buttons as it reaches panels.
7
+ */
8
+ export declare const buttonVariants: (props?: ({
9
+ variant?: "link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | null | undefined;
10
+ size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
11
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
12
+ export interface ButtonProps extends React.ComponentProps<"button">, VariantProps<typeof buttonVariants> {
13
+ /** Render the child (a link, usually) with the button's styling instead of a <button>. */
14
+ asChild?: boolean;
15
+ }
16
+ export declare function Button({ className, variant, size, asChild, ...props }: ButtonProps): React.JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cva } from "class-variance-authority";
3
+ import { Slot } from "radix-ui";
4
+ import { cn } from "../lib/cn.js";
5
+ /**
6
+ * Astrolift's button (shadcn), on the family tokens. Every size takes the
7
+ * corners setting (`rounded-lg` is `--radius`), so a 0px or 10px choice
8
+ * reaches buttons as it reaches panels.
9
+ */
10
+ export const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-primary text-primary-foreground hover:bg-primary/85",
14
+ outline: "border-border bg-transparent hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
15
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary",
16
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
17
+ destructive: "bg-danger/10 text-danger-fg hover:bg-danger/15 focus-visible:border-danger-border focus-visible:ring-danger/20",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ },
20
+ size: {
21
+ default: "h-8 gap-1.5 px-2.5",
22
+ xs: "h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
23
+ sm: "h-7 gap-1 px-2.5 text-[0.8rem] [&_svg:not([class*='size-'])]:size-3.5",
24
+ lg: "h-9 gap-1.5 px-3",
25
+ icon: "size-8",
26
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
27
+ "icon-sm": "size-7",
28
+ "icon-lg": "size-9",
29
+ },
30
+ },
31
+ defaultVariants: { variant: "default", size: "default" },
32
+ });
33
+ export function Button({ className, variant, size, asChild = false, ...props }) {
34
+ const Component = asChild ? Slot.Root : "button";
35
+ return (_jsx(Component, { "data-slot": "button", "data-variant": variant ?? "default", className: cn(buttonVariants({ variant, size }), className), ...props }));
36
+ }
@@ -0,0 +1,8 @@
1
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ /**
4
+ * The selection box for data surfaces. Radix rather than a native input:
5
+ * `indeterminate` has no HTML attribute, so the header's "some selected"
6
+ * state cannot be expressed in JSX on a native box.
7
+ */
8
+ export declare function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { CheckIcon, MinusIcon } from "lucide-react";
4
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
5
+ import { cn } from "../lib/cn.js";
6
+ /**
7
+ * The selection box for data surfaces. Radix rather than a native input:
8
+ * `indeterminate` has no HTML attribute, so the header's "some selected"
9
+ * state cannot be expressed in JSX on a native box.
10
+ */
11
+ export function Checkbox({ className, ...props }) {
12
+ return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("peer size-4 shrink-0 rounded-sm border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "flex items-center justify-center text-current", children: props.checked === "indeterminate" ? (_jsx(MinusIcon, { className: "size-3.5", "aria-hidden": true })) : (_jsx(CheckIcon, { className: "size-3.5", "aria-hidden": true })) }) }));
13
+ }
@@ -0,0 +1,11 @@
1
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ export declare function DropdownMenu(props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>): React.JSX.Element;
4
+ export declare function DropdownMenuTrigger(props: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>): React.JSX.Element;
5
+ export declare function DropdownMenuContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>): React.JSX.Element;
6
+ export declare function DropdownMenuGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.Group>): React.JSX.Element;
7
+ export declare function DropdownMenuItem({ className, variant, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
8
+ variant?: "default" | "destructive";
9
+ }): React.JSX.Element;
10
+ export declare function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label>): React.JSX.Element;
11
+ export declare function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>): React.JSX.Element;