@calliopelabs/ui 0.16.0 → 0.18.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/README.md +94 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/primitives/Menubar.d.ts +21 -0
- package/dist/primitives/Menubar.js +43 -0
- package/dist/provider/messages.d.ts +148 -0
- package/dist/provider/messages.js +178 -0
- package/dist/shell/RailPanel.js +1 -1
- package/dist/workbench/ActivityBar.d.ts +37 -0
- package/dist/workbench/ActivityBar.js +57 -0
- package/dist/workbench/BottomPanel.d.ts +30 -0
- package/dist/workbench/BottomPanel.js +20 -0
- package/dist/workbench/CodeEditor.d.ts +48 -0
- package/dist/workbench/CodeEditor.js +153 -0
- package/dist/workbench/CommandPalette.d.ts +49 -0
- package/dist/workbench/CommandPalette.js +114 -0
- package/dist/workbench/DataChart.d.ts +45 -0
- package/dist/workbench/DataChart.js +146 -0
- package/dist/workbench/DataGrid.d.ts +89 -0
- package/dist/workbench/DataGrid.js +590 -0
- package/dist/workbench/DataProfile.d.ts +17 -0
- package/dist/workbench/DataProfile.js +51 -0
- package/dist/workbench/EditorTabs.d.ts +62 -0
- package/dist/workbench/EditorTabs.js +115 -0
- package/dist/workbench/GridCell.d.ts +19 -0
- package/dist/workbench/GridCell.js +50 -0
- package/dist/workbench/GridDetails.d.ts +31 -0
- package/dist/workbench/GridDetails.js +78 -0
- package/dist/workbench/MenuBar.d.ts +49 -0
- package/dist/workbench/MenuBar.js +25 -0
- package/dist/workbench/PaneGroup.d.ts +50 -0
- package/dist/workbench/PaneGroup.js +146 -0
- package/dist/workbench/PendingChangesBar.d.ts +20 -0
- package/dist/workbench/PendingChangesBar.js +19 -0
- package/dist/workbench/Shortcuts.d.ts +50 -0
- package/dist/workbench/Shortcuts.js +109 -0
- package/dist/workbench/SqlEditor.d.ts +21 -0
- package/dist/workbench/SqlEditor.js +79 -0
- package/dist/workbench/StatusBar.d.ts +28 -0
- package/dist/workbench/StatusBar.js +26 -0
- package/dist/workbench/TruncationBanner.d.ts +11 -0
- package/dist/workbench/TruncationBanner.js +12 -0
- package/dist/workbench/WorkbenchShell.d.ts +36 -0
- package/dist/workbench/WorkbenchShell.js +20 -0
- package/dist/workbench/download.d.ts +11 -0
- package/dist/workbench/download.js +28 -0
- package/dist/workbench/editor-theme.d.ts +42 -0
- package/dist/workbench/editor-theme.js +113 -0
- package/dist/workbench/grid-format.d.ts +59 -0
- package/dist/workbench/grid-format.js +169 -0
- package/dist/workbench/grid-model.d.ts +75 -0
- package/dist/workbench/grid-model.js +259 -0
- package/dist/workbench/grid-selection.d.ts +69 -0
- package/dist/workbench/grid-selection.js +183 -0
- package/dist/workbench/grid-stats.d.ts +58 -0
- package/dist/workbench/grid-stats.js +185 -0
- package/dist/workbench/index.d.ts +28 -0
- package/dist/workbench/index.js +31 -0
- package/dist/workbench/keys.d.ts +23 -0
- package/dist/workbench/keys.js +105 -0
- package/dist/workbench/palette.d.ts +17 -0
- package/dist/workbench/palette.js +45 -0
- package/dist/workbench/pending.d.ts +92 -0
- package/dist/workbench/pending.js +207 -0
- package/dist/workbench/sql-completion.d.ts +107 -0
- package/dist/workbench/sql-completion.js +475 -0
- package/dist/workflow/WorkflowRuntimeControls.d.ts +21 -0
- package/dist/workflow/WorkflowRuntimeControls.js +54 -0
- package/dist/workflow/runtime-controls.d.ts +29 -0
- package/dist/workflow/runtime-controls.js +10 -0
- package/package.json +13 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
6
|
+
import { EmptyState } from "../states/EmptyState.js";
|
|
7
|
+
import { columnSignature } from "./grid-model.js";
|
|
8
|
+
import { formatStatValue, profileColumn } from "./grid-stats.js";
|
|
9
|
+
import { TruncationBanner } from "./TruncationBanner.js";
|
|
10
|
+
/** Distinct counts and frequencies are a pass per column: past this the profile samples a prefix, and says so. */
|
|
11
|
+
const PROFILE_ROW_CAP = 10_000;
|
|
12
|
+
/**
|
|
13
|
+
* A card per column: completeness, distinct values, range, mean and median
|
|
14
|
+
* for numbers, and the most frequent values. Honest about its sample: a
|
|
15
|
+
* profile of the fetched rows is not a profile of the table.
|
|
16
|
+
*/
|
|
17
|
+
export function DataProfile(props) {
|
|
18
|
+
return _jsx(ProfileSurface, { ...props }, columnSignature(props.columns));
|
|
19
|
+
}
|
|
20
|
+
function ProfileSurface({ columns, rows, truncated, rowCap = PROFILE_ROW_CAP, className, }) {
|
|
21
|
+
const { messages } = useUI();
|
|
22
|
+
const t = messages.workbench;
|
|
23
|
+
const names = React.useMemo(() => columns.map((c) => (typeof c === "string" ? c : c.name)), [columns]);
|
|
24
|
+
const sample = React.useMemo(() => (rows.length > rowCap ? rows.slice(0, rowCap) : rows), [rows, rowCap]);
|
|
25
|
+
const profiles = React.useMemo(() => names.map((name) => profileColumn(name, sample.map((row) => row[name]))), [names, sample]);
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
return _jsx(EmptyState, { title: t.profileNoData, className: className });
|
|
28
|
+
}
|
|
29
|
+
const nulls = profiles.reduce((sum, p) => sum + p.nulls, 0);
|
|
30
|
+
const notes = [truncated, rows.length > rowCap ? t.profileCap(rowCap, rows.length) : null].filter(Boolean);
|
|
31
|
+
return (_jsxs("section", { "aria-label": t.profileLabel, "data-slot": "data-profile", className: cn("@container flex min-h-0 flex-col", className), children: [notes.length > 0 && _jsx(TruncationBanner, { children: notes.join(" ") }), _jsx("p", { className: "shrink-0 border-b bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground", children: t.profileSummary(sample.length, names.length, nulls) }), _jsx("ul", { className: "grid min-h-0 flex-1 grid-cols-1 content-start gap-3 overflow-auto p-3 @md:grid-cols-2 @3xl:grid-cols-3", children: profiles.map((profile) => (_jsx(ProfileCard, { profile: profile }, profile.name))) })] }));
|
|
32
|
+
}
|
|
33
|
+
function ProfileCard({ profile }) {
|
|
34
|
+
const { messages } = useUI();
|
|
35
|
+
const t = messages.workbench;
|
|
36
|
+
const complete = profile.count > 0 ? 1 - profile.nulls / profile.count : 0;
|
|
37
|
+
const rows = [
|
|
38
|
+
[t.gridStat("rows"), profile.count.toLocaleString()],
|
|
39
|
+
[t.gridStat("nulls"), profile.nulls.toLocaleString()],
|
|
40
|
+
[t.gridStat("distinct"), profile.distinct.toLocaleString()],
|
|
41
|
+
];
|
|
42
|
+
if (profile.min !== undefined)
|
|
43
|
+
rows.push([t.gridStat("min"), formatStatValue(profile.min)]);
|
|
44
|
+
if (profile.max !== undefined)
|
|
45
|
+
rows.push([t.gridStat("max"), formatStatValue(profile.max)]);
|
|
46
|
+
if (profile.mean !== undefined)
|
|
47
|
+
rows.push([t.profileMean, formatStatValue(profile.mean)]);
|
|
48
|
+
if (profile.median !== undefined)
|
|
49
|
+
rows.push([t.profileMedian, formatStatValue(profile.median)]);
|
|
50
|
+
return (_jsxs("li", { className: "min-w-0 space-y-3 rounded-lg border bg-card p-3 text-card-foreground", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "min-w-0 flex-1 truncate font-mono text-sm font-medium", title: profile.name, children: profile.name }), _jsx("span", { className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground", children: t.profileKind(profile.kind) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: t.profileComplete(complete) }), _jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-muted", role: "presentation", children: _jsx("div", { className: "h-full rounded-full bg-chart-1", style: { width: `${complete * 100}%` } }) })] }), _jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-1 text-xs", children: rows.map(([label, value]) => (_jsxs("div", { className: "flex min-w-0 justify-between gap-2", children: [_jsx("dt", { className: "text-muted-foreground", children: label }), _jsx("dd", { className: "truncate tabular-nums", title: value, children: value })] }, label))) }), profile.top.length > 0 && (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: t.profileTopValues }), _jsx("ol", { className: "space-y-1", children: profile.top.map((top) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsxs("span", { className: "relative h-5 min-w-0 flex-1 overflow-hidden rounded-sm bg-muted", children: [_jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 bg-chart-1/25", style: { width: `${top.share * 100}%` } }), _jsx("span", { className: "relative block truncate px-1.5 leading-5", title: top.value, children: top.value })] }), _jsx("span", { className: "w-12 shrink-0 text-right text-muted-foreground tabular-nums", children: top.count.toLocaleString() })] }, top.value))) })] }))] }));
|
|
51
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface EditorTab {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
/** Unsaved changes: a dot in place of the close button, and "unsaved" in its name. */
|
|
7
|
+
dirty?: boolean;
|
|
8
|
+
/** Defaults to true. A welcome or home tab sets false. */
|
|
9
|
+
closable?: boolean;
|
|
10
|
+
/** Shown on hover: a full path, a server and database. Defaults to the title. */
|
|
11
|
+
hint?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface EditorTabsProps {
|
|
14
|
+
tabs: readonly EditorTab[];
|
|
15
|
+
/** The tab on screen. */
|
|
16
|
+
activeId: string | null;
|
|
17
|
+
onSelect: (id: string) => void;
|
|
18
|
+
/** Close a tab: its close button, a middle click, or Delete on it. */
|
|
19
|
+
onClose?: (id: string) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Move a tab from one index to another: dragging it, or Alt+Shift with
|
|
22
|
+
* Left or Right. Leave it out and the tabs stay in place.
|
|
23
|
+
*/
|
|
24
|
+
onReorder?: (from: number, to: number) => void;
|
|
25
|
+
/** Context-menu items (`ContextMenuItem`s) for a tab: close others, split, duplicate. */
|
|
26
|
+
contextMenu?: (tab: EditorTab) => React.ReactNode;
|
|
27
|
+
/** Controls after the strip: new tab, close group, a panel toggle. */
|
|
28
|
+
actions?: React.ReactNode;
|
|
29
|
+
/** The id of the panel the active tab shows; `EditorGroup` sets it. */
|
|
30
|
+
panelId?: string;
|
|
31
|
+
/** This group has the focus among several split groups: an accent line marks it. */
|
|
32
|
+
focused?: boolean;
|
|
33
|
+
/** The strip's name. Defaults to "Open editors". */
|
|
34
|
+
label?: string;
|
|
35
|
+
/** Prefix for the tabs' element ids. Defaults to a generated one. */
|
|
36
|
+
idPrefix?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
/** A tab's element id, for a panel's `aria-labelledby`. */
|
|
40
|
+
export declare function editorTabId(prefix: string, tabId: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* The editor tab strip of an IDE layout, with the ARIA tabs pattern: one tab
|
|
43
|
+
* stop, Left and Right move and show, Home and End jump. Tabs carry an
|
|
44
|
+
* unsaved marker, close by button, middle click or Delete, reorder by drag
|
|
45
|
+
* or Alt+Shift+Left/Right, and the strip scrolls with a menu of every tab
|
|
46
|
+
* once it overflows. Host-driven: it holds no tab state of its own.
|
|
47
|
+
*/
|
|
48
|
+
export declare function EditorTabs({ tabs, activeId, onSelect, onClose, onReorder, contextMenu, actions, panelId, focused, label, idPrefix, className, }: EditorTabsProps): React.JSX.Element;
|
|
49
|
+
export interface EditorGroupProps extends Omit<EditorTabsProps, "panelId" | "idPrefix"> {
|
|
50
|
+
/** The active tab's content. */
|
|
51
|
+
children?: React.ReactNode;
|
|
52
|
+
/** Shown when no tab is open. */
|
|
53
|
+
empty?: React.ReactNode;
|
|
54
|
+
/** The group was clicked or focused: a split layout makes it the active group. */
|
|
55
|
+
onFocusGroup?: () => void;
|
|
56
|
+
panelClassName?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Tabs and the panel under them: one editor group. A split layout puts
|
|
60
|
+
* several in a `PaneGroup`, marking the one with focus.
|
|
61
|
+
*/
|
|
62
|
+
export declare function EditorGroup({ children, empty, onFocusGroup, panelClassName, className, ...tabs }: EditorGroupProps): React.JSX.Element;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDownIcon, XIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from "../primitives/ContextMenu.js";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../primitives/DropdownMenu.js";
|
|
8
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
9
|
+
const DRAG_TYPE = "application/x-calliope-editor-tab";
|
|
10
|
+
/** A tab's element id, for a panel's `aria-labelledby`. */
|
|
11
|
+
export function editorTabId(prefix, tabId) {
|
|
12
|
+
return `${prefix}-tab-${tabId}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The editor tab strip of an IDE layout, with the ARIA tabs pattern: one tab
|
|
16
|
+
* stop, Left and Right move and show, Home and End jump. Tabs carry an
|
|
17
|
+
* unsaved marker, close by button, middle click or Delete, reorder by drag
|
|
18
|
+
* or Alt+Shift+Left/Right, and the strip scrolls with a menu of every tab
|
|
19
|
+
* once it overflows. Host-driven: it holds no tab state of its own.
|
|
20
|
+
*/
|
|
21
|
+
export function EditorTabs({ tabs, activeId, onSelect, onClose, onReorder, contextMenu, actions, panelId, focused = false, label, idPrefix, className, }) {
|
|
22
|
+
const { messages } = useUI();
|
|
23
|
+
const m = messages.workbench;
|
|
24
|
+
const generated = React.useId();
|
|
25
|
+
const prefix = idPrefix ?? generated;
|
|
26
|
+
const refs = React.useRef(new Map());
|
|
27
|
+
const [dropIndex, setDropIndex] = React.useState(null);
|
|
28
|
+
const tabStop = tabs.some((t) => t.id === activeId) ? activeId : tabs[0]?.id;
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
if (activeId)
|
|
31
|
+
refs.current.get(activeId)?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
32
|
+
}, [activeId]);
|
|
33
|
+
const focusTab = (index) => {
|
|
34
|
+
const tab = tabs[index];
|
|
35
|
+
if (!tab)
|
|
36
|
+
return;
|
|
37
|
+
onSelect(tab.id);
|
|
38
|
+
refs.current.get(tab.id)?.focus();
|
|
39
|
+
};
|
|
40
|
+
const onKeyDown = (index, tab) => (event) => {
|
|
41
|
+
const last = tabs.length - 1;
|
|
42
|
+
if (event.altKey && event.shiftKey && onReorder) {
|
|
43
|
+
const to = event.key === "ArrowLeft" ? index - 1 : event.key === "ArrowRight" ? index + 1 : -1;
|
|
44
|
+
if (to >= 0 && to <= last) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
onReorder(index, to);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (event.key === "ArrowRight")
|
|
51
|
+
focusTab(index === last ? 0 : index + 1);
|
|
52
|
+
else if (event.key === "ArrowLeft")
|
|
53
|
+
focusTab(index === 0 ? last : index - 1);
|
|
54
|
+
else if (event.key === "Home")
|
|
55
|
+
focusTab(0);
|
|
56
|
+
else if (event.key === "End")
|
|
57
|
+
focusTab(last);
|
|
58
|
+
else if (event.key === "Delete" && onClose && tab.closable !== false)
|
|
59
|
+
onClose(tab.id);
|
|
60
|
+
else
|
|
61
|
+
return;
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
};
|
|
64
|
+
const strip = tabs.map((tab, index) => {
|
|
65
|
+
const active = tab.id === activeId;
|
|
66
|
+
const closable = tab.closable !== false && onClose !== undefined;
|
|
67
|
+
const item = (_jsxs("div", { role: "presentation", draggable: onReorder !== undefined, onDragStart: (event) => {
|
|
68
|
+
event.dataTransfer.setData(DRAG_TYPE, String(index));
|
|
69
|
+
event.dataTransfer.effectAllowed = "move";
|
|
70
|
+
}, onDragOver: (event) => {
|
|
71
|
+
if (!event.dataTransfer.types.includes(DRAG_TYPE))
|
|
72
|
+
return;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
setDropIndex(index);
|
|
75
|
+
}, onDragLeave: () => setDropIndex((i) => (i === index ? null : i)), onDrop: (event) => {
|
|
76
|
+
const from = Number(event.dataTransfer.getData(DRAG_TYPE));
|
|
77
|
+
setDropIndex(null);
|
|
78
|
+
if (!Number.isNaN(from) && from !== index) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
onReorder?.(from, index);
|
|
81
|
+
}
|
|
82
|
+
}, onMouseDown: (event) => {
|
|
83
|
+
if (event.button === 1 && closable) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
onClose?.(tab.id);
|
|
86
|
+
}
|
|
87
|
+
}, className: cn("group relative flex h-full max-w-52 min-w-28 shrink-0 items-center border-r text-xs transition-colors", active
|
|
88
|
+
? "bg-background text-foreground"
|
|
89
|
+
: "bg-surface-1 text-muted-foreground hover:bg-surface-2 hover:text-foreground", dropIndex === index && "ring-2 ring-ring ring-inset"), children: [active && (_jsx("span", { "aria-hidden": true, className: cn("absolute inset-x-0 top-0 h-0.5", focused ? "bg-primary" : "bg-ring") })), _jsxs("button", { ref: (el) => {
|
|
90
|
+
if (el)
|
|
91
|
+
refs.current.set(tab.id, el);
|
|
92
|
+
else
|
|
93
|
+
refs.current.delete(tab.id);
|
|
94
|
+
}, type: "button", role: "tab", id: editorTabId(prefix, tab.id), "aria-selected": active, "aria-controls": active ? panelId : undefined, tabIndex: tab.id === tabStop ? 0 : -1, title: tab.hint ?? tab.title, onClick: () => onSelect(tab.id), onKeyDown: onKeyDown(index, tab), className: "flex h-full min-w-0 flex-1 items-center gap-1.5 pr-1 pl-3 outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: [tab.icon && (_jsx("span", { "aria-hidden": true, className: "flex shrink-0 [&_svg]:size-3.5", children: tab.icon })), _jsx("span", { className: "truncate", children: tab.title }), tab.dirty && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: "sr-only", children: m.unsaved })] }))] }), _jsxs("span", { className: "relative mr-1.5 flex size-4 shrink-0 items-center justify-center", children: [tab.dirty && (_jsx("span", { "aria-hidden": true, className: cn("size-2 rounded-full bg-foreground/60", closable && "group-hover:hidden") })), closable && (_jsx("button", { type: "button", tabIndex: -1, "aria-hidden": true, onClick: (event) => {
|
|
95
|
+
event.stopPropagation();
|
|
96
|
+
onClose?.(tab.id);
|
|
97
|
+
}, className: cn("absolute inset-0 flex items-center justify-center rounded-lg hover:bg-accent hover:text-accent-foreground", tab.dirty
|
|
98
|
+
? "hidden group-hover:flex"
|
|
99
|
+
: active
|
|
100
|
+
? "opacity-70"
|
|
101
|
+
: "opacity-0 group-hover:opacity-70"), children: _jsx(XIcon, { className: "size-3" }) }))] })] }));
|
|
102
|
+
return contextMenu ? (_jsxs(ContextMenu, { modal: false, children: [_jsx(ContextMenuTrigger, { asChild: true, children: item }), _jsx(ContextMenuContent, { children: contextMenu(tab) })] }, tab.id)) : (_jsx(React.Fragment, { children: item }, tab.id));
|
|
103
|
+
});
|
|
104
|
+
return (_jsxs("div", { "data-slot": "editor-tabs", className: cn("flex h-9 shrink-0 items-stretch border-b bg-surface-1", className), children: [_jsx("div", { role: "tablist", "aria-label": label ?? m.openEditors, className: "flex min-w-0 flex-1 items-stretch overflow-x-auto overflow-y-hidden", children: strip }), tabs.length > 1 && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": m.allTabs(tabs.length), className: "flex items-center gap-0.5 px-1.5 text-muted-foreground outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: [_jsx(ChevronDownIcon, { "aria-hidden": true, className: "size-3.5" }), _jsx("span", { "aria-hidden": true, className: "text-2xs tabular-nums", children: tabs.length })] }) }), _jsx(DropdownMenuContent, { align: "end", className: "max-h-[60vh] max-w-80", children: tabs.map((tab) => (_jsxs(DropdownMenuItem, { className: cn("text-xs", tab.id === activeId && "font-medium"), onSelect: () => focusTab(tabs.indexOf(tab)), children: [tab.icon && (_jsx("span", { "aria-hidden": true, className: "flex [&_svg]:size-3.5", children: tab.icon })), _jsx("span", { className: "truncate", children: tab.title }), tab.dirty && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: "sr-only", children: m.unsaved }), _jsx("span", { "aria-hidden": true, className: "ml-auto size-1.5 shrink-0 rounded-full bg-foreground/60" })] }))] }, tab.id))) })] })), actions && _jsx("div", { className: "flex items-stretch border-l", children: actions })] }));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Tabs and the panel under them: one editor group. A split layout puts
|
|
108
|
+
* several in a `PaneGroup`, marking the one with focus.
|
|
109
|
+
*/
|
|
110
|
+
export function EditorGroup({ children, empty, onFocusGroup, panelClassName, className, ...tabs }) {
|
|
111
|
+
const prefix = React.useId();
|
|
112
|
+
const panelId = `${prefix}-panel`;
|
|
113
|
+
const active = tabs.tabs.find((t) => t.id === tabs.activeId);
|
|
114
|
+
return (_jsxs("div", { "data-slot": "editor-group", className: cn("flex h-full min-h-0 flex-col", className), onFocusCapture: onFocusGroup, onPointerDownCapture: onFocusGroup, children: [_jsx(EditorTabs, { ...tabs, idPrefix: prefix, panelId: panelId }), active ? (_jsx("div", { role: "tabpanel", id: panelId, "aria-labelledby": editorTabId(prefix, active.id), className: cn("min-h-0 flex-1 overflow-hidden", panelClassName), children: children })) : (_jsx("div", { className: cn("min-h-0 flex-1 overflow-hidden", panelClassName), children: empty }))] }));
|
|
115
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { type ColumnType, type GridColumnMeta } from "./grid-model.js";
|
|
3
|
+
export interface ColumnTypeIconProps {
|
|
4
|
+
type: ColumnType;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/** A column type's glyph, named by the type. Identity, not status: it takes no colour. */
|
|
8
|
+
export declare function ColumnTypeIcon({ type, className }: ColumnTypeIconProps): React.JSX.Element;
|
|
9
|
+
export interface ColumnKeyIconsProps {
|
|
10
|
+
column: Pick<GridColumnMeta, "primaryKey" | "foreignKey" | "references">;
|
|
11
|
+
}
|
|
12
|
+
/** A column's primary and foreign key marks, each named. */
|
|
13
|
+
export declare function ColumnKeyIcons({ column }: ColumnKeyIconsProps): React.JSX.Element;
|
|
14
|
+
export interface GridCellValueProps {
|
|
15
|
+
value: unknown;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/** How one value reads in a cell: NULL, empty text and binary are marked so they never pass for data. */
|
|
19
|
+
export declare function GridCellValue({ value, className }: GridCellValueProps): React.JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { BinaryIcon, BracesIcon, CalendarClockIcon, CalendarIcon, CircleHelpIcon, ClockIcon, FingerprintIcon, HashIcon, KeyRoundIcon, Link2Icon, SigmaIcon, ToggleLeftIcon, TypeIcon, } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
6
|
+
import { asBinary } from "./grid-model.js";
|
|
7
|
+
const TYPE_ICON = {
|
|
8
|
+
integer: HashIcon,
|
|
9
|
+
number: SigmaIcon,
|
|
10
|
+
text: TypeIcon,
|
|
11
|
+
boolean: ToggleLeftIcon,
|
|
12
|
+
datetime: CalendarClockIcon,
|
|
13
|
+
date: CalendarIcon,
|
|
14
|
+
time: ClockIcon,
|
|
15
|
+
json: BracesIcon,
|
|
16
|
+
binary: BinaryIcon,
|
|
17
|
+
uuid: FingerprintIcon,
|
|
18
|
+
other: CircleHelpIcon,
|
|
19
|
+
};
|
|
20
|
+
/** A column type's glyph, named by the type. Identity, not status: it takes no colour. */
|
|
21
|
+
export function ColumnTypeIcon({ type, className }) {
|
|
22
|
+
const { messages } = useUI();
|
|
23
|
+
const Icon = TYPE_ICON[type];
|
|
24
|
+
return (_jsx("span", { role: "img", "aria-label": messages.workbench.gridType(type), className: cn("inline-flex shrink-0", className), children: _jsx(Icon, { "aria-hidden": true, className: "size-3" }) }));
|
|
25
|
+
}
|
|
26
|
+
/** A column's primary and foreign key marks, each named. */
|
|
27
|
+
export function ColumnKeyIcons({ column }) {
|
|
28
|
+
const { messages } = useUI();
|
|
29
|
+
const t = messages.workbench;
|
|
30
|
+
return (_jsxs(_Fragment, { children: [column.primaryKey && (_jsx("span", { role: "img", "aria-label": t.gridPrimaryKey, className: "inline-flex shrink-0", children: _jsx(KeyRoundIcon, { "aria-hidden": true, className: "size-3" }) })), column.foreignKey && (_jsx("span", { role: "img", "aria-label": t.gridForeignKey(column.references ?? null), className: "inline-flex shrink-0", children: _jsx(Link2Icon, { "aria-hidden": true, className: "size-3" }) }))] }));
|
|
31
|
+
}
|
|
32
|
+
/** How one value reads in a cell: NULL, empty text and binary are marked so they never pass for data. */
|
|
33
|
+
export function GridCellValue({ value, className }) {
|
|
34
|
+
const { messages } = useUI();
|
|
35
|
+
const t = messages.workbench;
|
|
36
|
+
if (value === null || value === undefined) {
|
|
37
|
+
return (_jsx("span", { className: cn("text-muted-foreground italic select-none", className), children: t.gridNull }));
|
|
38
|
+
}
|
|
39
|
+
const binary = asBinary(value);
|
|
40
|
+
if (binary) {
|
|
41
|
+
return (_jsxs("span", { className: cn("truncate text-muted-foreground italic", className), children: ["<", t.gridBinary(binary.size), ">"] }));
|
|
42
|
+
}
|
|
43
|
+
if (value === "") {
|
|
44
|
+
return (_jsxs("span", { className: cn("text-muted-foreground italic select-none", className), children: ["<", t.gridEmptyText, ">"] }));
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "object") {
|
|
47
|
+
return (_jsx("span", { className: cn("truncate text-muted-foreground", className), children: JSON.stringify(value) }));
|
|
48
|
+
}
|
|
49
|
+
return _jsx("span", { className: cn("truncate", className), children: String(value) });
|
|
50
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type GridColumnMeta, type GridRow } from "./grid-model.js";
|
|
3
|
+
export interface CellDetailDialogProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
column: string;
|
|
7
|
+
value: unknown;
|
|
8
|
+
onCopy?: (text: string) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* One cell's whole value: JSON pretty-printed (also text that holds JSON),
|
|
12
|
+
* bytes as their size with a download, everything copyable.
|
|
13
|
+
*/
|
|
14
|
+
export declare function CellDetailDialog({ open, onOpenChange, column, value, onCopy, }: CellDetailDialogProps): React.JSX.Element;
|
|
15
|
+
export interface RowDetailDrawerProps {
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
row: GridRow;
|
|
19
|
+
columns: readonly GridColumnMeta[];
|
|
20
|
+
/** 1-based position in the view. */
|
|
21
|
+
rowNumber: number;
|
|
22
|
+
rowTotal: number;
|
|
23
|
+
/** Previous (-1) or next (1) row, without closing. */
|
|
24
|
+
onNavigate: (delta: -1 | 1) => void;
|
|
25
|
+
onCopy?: (text: string) => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One row, read top to bottom: thirty columns do not fit across a screen.
|
|
29
|
+
* Previous and next walk the view without closing, to compare rows.
|
|
30
|
+
*/
|
|
31
|
+
export declare function RowDetailDrawer({ open, onOpenChange, row, columns, rowNumber, rowTotal, onNavigate, onCopy, }: RowDetailDrawerProps): React.JSX.Element;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, CopyIcon, DownloadIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../overlays/Dialog.js";
|
|
6
|
+
import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, } from "../overlays/Drawer.js";
|
|
7
|
+
import { Button } from "../primitives/Button.js";
|
|
8
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
9
|
+
import { downloadBase64 } from "./download.js";
|
|
10
|
+
import { ColumnKeyIcons, ColumnTypeIcon, GridCellValue } from "./GridCell.js";
|
|
11
|
+
import { asBinary } from "./grid-model.js";
|
|
12
|
+
/** Pretty-prints JSON, or text that holds JSON; null for anything else. */
|
|
13
|
+
function prettyJson(value) {
|
|
14
|
+
if (typeof value === "object" && value !== null)
|
|
15
|
+
return JSON.stringify(value, null, 2);
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
|
|
20
|
+
return null;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** A copy button that says "Copied" for a moment. Reports through `onCopied`. */
|
|
29
|
+
function CopyButton({ text, label, onCopied, }) {
|
|
30
|
+
const { messages } = useUI();
|
|
31
|
+
const [copied, setCopied] = React.useState(false);
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (!copied)
|
|
34
|
+
return;
|
|
35
|
+
const timer = window.setTimeout(() => setCopied(false), 1500);
|
|
36
|
+
return () => window.clearTimeout(timer);
|
|
37
|
+
}, [copied]);
|
|
38
|
+
return (_jsxs(Button, { variant: "ghost", size: "xs", onClick: () => {
|
|
39
|
+
void navigator.clipboard?.writeText(text).then(() => {
|
|
40
|
+
setCopied(true);
|
|
41
|
+
onCopied?.(text);
|
|
42
|
+
}, () => { });
|
|
43
|
+
}, children: [copied ? _jsx(CheckIcon, { "aria-hidden": true }) : _jsx(CopyIcon, { "aria-hidden": true }), copied ? messages.workbench.gridCopied : label] }));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One cell's whole value: JSON pretty-printed (also text that holds JSON),
|
|
47
|
+
* bytes as their size with a download, everything copyable.
|
|
48
|
+
*/
|
|
49
|
+
export function CellDetailDialog({ open, onOpenChange, column, value, onCopy, }) {
|
|
50
|
+
const { messages } = useUI();
|
|
51
|
+
const t = messages.workbench;
|
|
52
|
+
const binary = asBinary(value);
|
|
53
|
+
const body = value === null || value === undefined
|
|
54
|
+
? t.gridNull
|
|
55
|
+
: binary
|
|
56
|
+
? binary.base64
|
|
57
|
+
: (prettyJson(value) ?? String(value));
|
|
58
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { className: "font-mono text-sm", children: column }), _jsx(DialogDescription, { className: "text-xs", children: binary
|
|
59
|
+
? t.gridBytes(binary.size, binary.truncated === true)
|
|
60
|
+
: t.gridCharacters(body.length) })] }), _jsxs("div", { className: "flex justify-end gap-1", children: [binary && binary.base64 !== "" && (_jsxs(Button, { variant: "ghost", size: "xs", onClick: () => downloadBase64(binary.base64, `${column}.bin`), children: [_jsx(DownloadIcon, { "aria-hidden": true }), t.gridDownload] })), _jsx(CopyButton, { text: body, label: t.gridCopyValue, onCopied: onCopy })] }), _jsx("pre", {
|
|
61
|
+
// A long value scrolls: focusable so a keyboard can reach and scroll it.
|
|
62
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
63
|
+
tabIndex: 0, "aria-label": t.contentsOf(column), className: "max-h-96 overflow-auto rounded-lg border bg-muted/40 p-3 font-mono text-xs break-all whitespace-pre-wrap outline-none focus-visible:ring-2 focus-visible:ring-ring/50", children: body })] }) }));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One row, read top to bottom: thirty columns do not fit across a screen.
|
|
67
|
+
* Previous and next walk the view without closing, to compare rows.
|
|
68
|
+
*/
|
|
69
|
+
export function RowDetailDrawer({ open, onOpenChange, row, columns, rowNumber, rowTotal, onNavigate, onCopy, }) {
|
|
70
|
+
const { messages } = useUI();
|
|
71
|
+
const t = messages.workbench;
|
|
72
|
+
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, children: _jsxs(DrawerContent, { side: "right", className: "gap-0 sm:max-w-md", children: [_jsxs(DrawerHeader, { className: "border-b", children: [_jsx(DrawerTitle, { className: "text-sm", children: t.gridRowOf(rowNumber, rowTotal) }), _jsx(DrawerDescription, { className: "text-xs", children: t.gridColumns(columns.length) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon-xs", "aria-label": t.gridPreviousRow, disabled: rowNumber <= 1, onClick: () => onNavigate(-1), children: _jsx(ChevronUpIcon, { "aria-hidden": true }) }), _jsx(Button, { variant: "ghost", size: "icon-xs", "aria-label": t.gridNextRow, disabled: rowNumber >= rowTotal, onClick: () => onNavigate(1), children: _jsx(ChevronDownIcon, { "aria-hidden": true }) }), _jsx(CopyButton, { text: JSON.stringify(row, null, 2), label: t.gridCopyRow, onCopied: onCopy })] })] }), _jsx("dl", { className: "flex-1 space-y-3 overflow-auto px-4 py-3", children: columns.map((column) => {
|
|
73
|
+
const value = row[column.name];
|
|
74
|
+
const json = prettyJson(value);
|
|
75
|
+
const binary = asBinary(value);
|
|
76
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsxs("dt", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(ColumnTypeIcon, { type: column.type }), _jsx("span", { className: "min-w-0 truncate font-medium text-foreground", children: column.name }), _jsx(ColumnKeyIcons, { column: column }), _jsxs("span", { className: "ml-auto shrink-0", children: [column.dataType ?? t.gridType(column.type), column.nullable === false ? `, ${t.gridNotNull}` : ""] })] }), _jsx("dd", { className: "text-xs", children: binary ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(GridCellValue, { value: value }), binary.base64 !== "" && (_jsxs(Button, { variant: "ghost", size: "xs", onClick: () => downloadBase64(binary.base64, `${column.name}.bin`), children: [_jsx(DownloadIcon, { "aria-hidden": true }), t.gridDownload] }))] })) : json !== null || (typeof value === "string" && value.length > 120) ? (_jsx("pre", { className: "max-h-48 overflow-auto rounded-lg border bg-muted/40 p-2 font-mono break-all whitespace-pre-wrap", children: json ?? String(value) })) : (_jsx(GridCellValue, { value: value, className: "font-mono break-all" })) })] }, column.name));
|
|
77
|
+
}) })] }) }));
|
|
78
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
export type MenuEntry = {
|
|
3
|
+
type?: "item";
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Its chord, shown beside it (`"Mod+N"`). Binding it is `useShortcuts`' job. */
|
|
7
|
+
shortcut?: string;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
onSelect: () => void;
|
|
12
|
+
} | {
|
|
13
|
+
type: "checkbox";
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
checked: boolean;
|
|
17
|
+
shortcut?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
onCheckedChange: (checked: boolean) => void;
|
|
20
|
+
} | {
|
|
21
|
+
type: "label";
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "separator";
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
export interface WorkbenchMenu {
|
|
29
|
+
id: string;
|
|
30
|
+
/** The word in the bar ("File"). */
|
|
31
|
+
label: string;
|
|
32
|
+
items: readonly MenuEntry[];
|
|
33
|
+
}
|
|
34
|
+
export interface MenuBarProps {
|
|
35
|
+
menus: readonly WorkbenchMenu[];
|
|
36
|
+
/** Before the menus: the product mark, a home link. */
|
|
37
|
+
start?: React.ReactNode;
|
|
38
|
+
/** Right-aligned: the product name, a connection, the person. */
|
|
39
|
+
end?: React.ReactNode;
|
|
40
|
+
/** Draw shortcuts with Mac glyphs. Detected from the platform when left out. */
|
|
41
|
+
mac?: boolean;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The menu bar across the top of an IDE layout, from data: each menu a list
|
|
46
|
+
* of commands, toggles, labels and separators. It is the ARIA menubar
|
|
47
|
+
* (Menubar primitive): one tab stop, arrows between and within menus.
|
|
48
|
+
*/
|
|
49
|
+
export declare function MenuBar({ menus, start, end, mac, className }: MenuBarProps): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarLabel, MenubarMenu, MenubarSeparator, MenubarShortcut, MenubarTrigger, } from "../primitives/Menubar.js";
|
|
5
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
6
|
+
import { formatShortcut, isMacPlatform } from "./keys.js";
|
|
7
|
+
/**
|
|
8
|
+
* The menu bar across the top of an IDE layout, from data: each menu a list
|
|
9
|
+
* of commands, toggles, labels and separators. It is the ARIA menubar
|
|
10
|
+
* (Menubar primitive): one tab stop, arrows between and within menus.
|
|
11
|
+
*/
|
|
12
|
+
export function MenuBar({ menus, start, end, mac, className }) {
|
|
13
|
+
const { messages } = useUI();
|
|
14
|
+
const isMac = mac ?? isMacPlatform();
|
|
15
|
+
const shortcut = (keys) => keys ? _jsx(MenubarShortcut, { children: formatShortcut(keys, isMac) }) : null;
|
|
16
|
+
return (_jsxs("div", { "data-slot": "workbench-menu-bar", className: cn("flex h-8 shrink-0 items-center gap-1 border-b bg-surface-1 px-1 text-foreground", className), children: [start, _jsx(Menubar, { "aria-label": messages.workbench.menuBar, children: menus.map((menu) => (_jsxs(MenubarMenu, { value: menu.id, children: [_jsx(MenubarTrigger, { children: menu.label }), _jsx(MenubarContent, { children: menu.items.map((entry) => {
|
|
17
|
+
if (entry.type === "separator")
|
|
18
|
+
return _jsx(MenubarSeparator, {}, entry.id);
|
|
19
|
+
if (entry.type === "label")
|
|
20
|
+
return _jsx(MenubarLabel, { children: entry.label }, entry.id);
|
|
21
|
+
if (entry.type === "checkbox")
|
|
22
|
+
return (_jsxs(MenubarCheckboxItem, { checked: entry.checked, disabled: entry.disabled, onCheckedChange: (checked) => entry.onCheckedChange(checked === true), children: [entry.label, shortcut(entry.shortcut)] }, entry.id));
|
|
23
|
+
return (_jsxs(MenubarItem, { disabled: entry.disabled, variant: entry.destructive ? "destructive" : "default", onSelect: entry.onSelect, children: [entry.icon && (_jsx("span", { "aria-hidden": true, className: "flex", children: entry.icon })), entry.label, shortcut(entry.shortcut)] }, entry.id));
|
|
24
|
+
}) })] }, menu.id))) }), end && (_jsx("div", { className: "ml-auto flex items-center gap-2 pr-2 text-xs text-muted-foreground", children: end }))] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** A size bound: a percentage of the group (`20`) or pixels (`"200px"`). */
|
|
3
|
+
export type PaneSize = number | `${number}px`;
|
|
4
|
+
export interface PaneProps {
|
|
5
|
+
/** Stable within the group: the stored size's key. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** What the pane is, for its resize handle's name ("Resize Explorer"). */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Percent of the group at first paint. Panes without one share what is left. */
|
|
10
|
+
defaultSize?: number;
|
|
11
|
+
minSize?: PaneSize;
|
|
12
|
+
maxSize?: PaneSize;
|
|
13
|
+
/**
|
|
14
|
+
* Hidden but still mounted, so its state (an unsaved draft, a scroll
|
|
15
|
+
* position) survives; its neighbours take the room.
|
|
16
|
+
*/
|
|
17
|
+
collapsed?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One pane of a `PaneGroup`. Outside a group it is a plain block; inside
|
|
23
|
+
* one, the group lays it out and puts a resize handle between it and the
|
|
24
|
+
* next visible pane.
|
|
25
|
+
*/
|
|
26
|
+
export declare function Pane({ className, children }: PaneProps): React.JSX.Element;
|
|
27
|
+
export interface PaneGroupProps {
|
|
28
|
+
/** `horizontal` puts panes side by side; `vertical` stacks them. */
|
|
29
|
+
orientation?: "horizontal" | "vertical";
|
|
30
|
+
/** Remember sizes per person under this key (localStorage). */
|
|
31
|
+
storageKey?: string;
|
|
32
|
+
/** Sizes changed (a drag ended, or a keyboard step): percent per pane id. */
|
|
33
|
+
onSizesChange?: (sizes: Record<string, number>) => void;
|
|
34
|
+
className?: string;
|
|
35
|
+
/** `Pane`s; anything else is ignored. */
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
type Sizes = Record<string, number>;
|
|
39
|
+
/** Every pane's size, from the stored sizes over the defaults. */
|
|
40
|
+
export declare function resolvePaneSizes(panes: readonly Pick<PaneProps, "id" | "defaultSize">[], stored?: Sizes): Sizes;
|
|
41
|
+
/**
|
|
42
|
+
* Resizable panes with the ARIA window-splitter pattern: each handle is a
|
|
43
|
+
* focusable `separator` with its value as the pane's share of the group;
|
|
44
|
+
* the arrow keys step it, Home and End take it to its bounds, and the
|
|
45
|
+
* pointer drags it. Sizes are flex weights, so a collapsed pane's room goes
|
|
46
|
+
* to its neighbours and comes back when it reopens. With `storageKey` they
|
|
47
|
+
* are remembered per person.
|
|
48
|
+
*/
|
|
49
|
+
export declare function PaneGroup({ orientation, storageKey, onSizesChange, className, children, }: PaneGroupProps): React.JSX.Element;
|
|
50
|
+
export {};
|