@burtson-labs/ui 0.8.1 → 0.9.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.
- package/README.md +1 -1
- package/dist/components/composer.d.ts +12 -2
- package/dist/components/composer.js +2 -2
- package/dist/components/composer.js.map +1 -1
- package/dist/components/context-menu.d.ts +30 -0
- package/dist/components/context-menu.js +131 -0
- package/dist/components/context-menu.js.map +1 -0
- package/dist/components/dropdown-menu.js +5 -6
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/editor-tabs.d.ts +47 -0
- package/dist/components/editor-tabs.js +221 -0
- package/dist/components/editor-tabs.js.map +1 -0
- package/dist/components/menubar.d.ts +30 -0
- package/dist/components/menubar.js +140 -0
- package/dist/components/menubar.js.map +1 -0
- package/dist/components/onboarding-checklist.d.ts +26 -0
- package/dist/components/onboarding-checklist.js +106 -0
- package/dist/components/onboarding-checklist.js.map +1 -0
- package/dist/components/popover.d.ts +2 -0
- package/dist/components/popover.js +3 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/resizable.d.ts +33 -0
- package/dist/components/resizable.js +54 -0
- package/dist/components/resizable.js.map +1 -0
- package/dist/components/tour.d.ts +96 -0
- package/dist/components/tour.js +367 -0
- package/dist/components/tour.js.map +1 -0
- package/dist/components/tree-view.d.ts +76 -0
- package/dist/components/tree-view.js +286 -0
- package/dist/components/tree-view.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -3
- package/dist/styles.css +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface TreeNode {
|
|
3
|
+
/** Stable identity. Keep it across renders (a path, a database id). */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Plain text: the accessible name and the typeahead key. */
|
|
6
|
+
label: string;
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
/** Undefined: a leaf, unless `hasChildren` says the children load later. */
|
|
9
|
+
children?: TreeNode[];
|
|
10
|
+
/** Expandable before its children are known; expanding calls onLoadChildren. */
|
|
11
|
+
hasChildren?: boolean;
|
|
12
|
+
/** Children are being fetched; shows a loading row while expanded. */
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** Trailing detail, such as a git status letter or a count. */
|
|
16
|
+
meta?: React.ReactNode;
|
|
17
|
+
/** Extra classes on the row, for app states such as an ignored file or a highlight. */
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TreeViewProps extends Omit<React.ComponentProps<'div'>, 'children' | 'onSelect' | 'aria-label'> {
|
|
21
|
+
nodes: TreeNode[];
|
|
22
|
+
/** Required: what the tree holds, e.g. "Files". */
|
|
23
|
+
'aria-label': string;
|
|
24
|
+
expanded: string[];
|
|
25
|
+
onExpandedChange: (ids: string[]) => void;
|
|
26
|
+
selected: string[];
|
|
27
|
+
onSelectedChange: (ids: string[]) => void;
|
|
28
|
+
/** `multiple`: Shift extends a range, Ctrl/⌘ toggles, Space toggles. */
|
|
29
|
+
selectionMode?: 'single' | 'multiple';
|
|
30
|
+
/** Move selection with focus on arrow keys (a file explorer); default off. */
|
|
31
|
+
selectionFollowsFocus?: boolean;
|
|
32
|
+
/** Enter or double-click: open the file, run the item. */
|
|
33
|
+
onAction?: (id: string) => void;
|
|
34
|
+
/**
|
|
35
|
+
* A plain click on a row without children also runs `onAction`, the way a
|
|
36
|
+
* file explorer opens a file on one click. Default off.
|
|
37
|
+
*/
|
|
38
|
+
actionOnClick?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* A plain click on an expandable row toggles it. Defaults to on for single
|
|
41
|
+
* selection; turn it on for a multi-select explorer, where Shift and Ctrl/⌘
|
|
42
|
+
* clicks still only select.
|
|
43
|
+
*/
|
|
44
|
+
expandOnClick?: boolean;
|
|
45
|
+
/** Draw a line per ancestor level while the pointer is over the tree. */
|
|
46
|
+
indentGuides?: boolean;
|
|
47
|
+
/** Called once when a node with `hasChildren` and no children expands. */
|
|
48
|
+
onLoadChildren?: (id: string) => void;
|
|
49
|
+
/** Replace the label, e.g. with a rename input. Keys typed in an input never move focus. */
|
|
50
|
+
renderLabel?: (node: TreeNode) => React.ReactNode;
|
|
51
|
+
/** `compact` 24px rows for desktop explorers, `default` 28px. */
|
|
52
|
+
density?: 'compact' | 'default';
|
|
53
|
+
/** Shown when `nodes` is empty. */
|
|
54
|
+
empty?: React.ReactNode;
|
|
55
|
+
/** Right-click or the context-menu key on a row. */
|
|
56
|
+
onRowContextMenu?: (id: string, event: React.MouseEvent | React.KeyboardEvent) => void;
|
|
57
|
+
}
|
|
58
|
+
interface Row {
|
|
59
|
+
node: TreeNode;
|
|
60
|
+
level: number;
|
|
61
|
+
parentId: string | null;
|
|
62
|
+
setSize: number;
|
|
63
|
+
posInSet: number;
|
|
64
|
+
expandable: boolean;
|
|
65
|
+
expanded: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** The rows a person can see, in order, with the ARIA position data for each. */
|
|
68
|
+
export declare function flattenTree(nodes: TreeNode[], expanded: ReadonlySet<string>): Row[];
|
|
69
|
+
/**
|
|
70
|
+
* A file-explorer style tree. Everything is controlled: the app owns which
|
|
71
|
+
* nodes are expanded and selected, and file operations are callbacks. Rows
|
|
72
|
+
* render flat (role="treeitem" with aria-level), one tab stop for the tree,
|
|
73
|
+
* arrows to move, Right/Left to open and close, Home/End, typeahead.
|
|
74
|
+
*/
|
|
75
|
+
declare function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChange, selectionMode, selectionFollowsFocus, onAction, actionOnClick, expandOnClick, indentGuides, onLoadChildren, renderLabel, density, empty, onRowContextMenu, className, ...props }: TreeViewProps): React.JSX.Element;
|
|
76
|
+
export { TreeView };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Spinner } from "./spinner.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import ChevronRight from "@burtson-labs/icons/react/chevron-right";
|
|
6
|
+
//#region src/components/tree-view.tsx
|
|
7
|
+
var isExpandable = (n) => (n.children?.length ?? 0) > 0 || Boolean(n.hasChildren);
|
|
8
|
+
/** The rows a person can see, in order, with the ARIA position data for each. */
|
|
9
|
+
function flattenTree(nodes, expanded) {
|
|
10
|
+
const rows = [];
|
|
11
|
+
const walk = (list, level, parentId) => {
|
|
12
|
+
list.forEach((node, i) => {
|
|
13
|
+
const expandable = isExpandable(node);
|
|
14
|
+
const open = expandable && expanded.has(node.id);
|
|
15
|
+
rows.push({
|
|
16
|
+
node,
|
|
17
|
+
level,
|
|
18
|
+
parentId,
|
|
19
|
+
setSize: list.length,
|
|
20
|
+
posInSet: i + 1,
|
|
21
|
+
expandable,
|
|
22
|
+
expanded: open
|
|
23
|
+
});
|
|
24
|
+
if (open && node.children) walk(node.children, level + 1, node.id);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
walk(nodes, 1, null);
|
|
28
|
+
return rows;
|
|
29
|
+
}
|
|
30
|
+
var isTypingTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));
|
|
31
|
+
/**
|
|
32
|
+
* A file-explorer style tree. Everything is controlled: the app owns which
|
|
33
|
+
* nodes are expanded and selected, and file operations are callbacks. Rows
|
|
34
|
+
* render flat (role="treeitem" with aria-level), one tab stop for the tree,
|
|
35
|
+
* arrows to move, Right/Left to open and close, Home/End, typeahead.
|
|
36
|
+
*/
|
|
37
|
+
function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChange, selectionMode = "single", selectionFollowsFocus = false, onAction, actionOnClick = false, expandOnClick, indentGuides = false, onLoadChildren, renderLabel, density = "default", empty, onRowContextMenu, className, ...props }) {
|
|
38
|
+
const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);
|
|
39
|
+
const selectedSet = React.useMemo(() => new Set(selected), [selected]);
|
|
40
|
+
const rows = React.useMemo(() => flattenTree(nodes, expandedSet), [nodes, expandedSet]);
|
|
41
|
+
const index = React.useMemo(() => new Map(rows.map((r, i) => [r.node.id, i])), [rows]);
|
|
42
|
+
const [focusedId, setFocusedId] = React.useState(null);
|
|
43
|
+
const anchorId = React.useRef(null);
|
|
44
|
+
const rowRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
45
|
+
const treeRef = React.useRef(null);
|
|
46
|
+
const hadFocus = React.useRef(false);
|
|
47
|
+
const lastIds = React.useRef([]);
|
|
48
|
+
const lastParents = React.useRef(/* @__PURE__ */ new Map());
|
|
49
|
+
const typeahead = React.useRef({
|
|
50
|
+
text: "",
|
|
51
|
+
at: 0
|
|
52
|
+
});
|
|
53
|
+
const tabStopId = (focusedId && index.has(focusedId) ? focusedId : null) ?? rows.find((r) => selectedSet.has(r.node.id))?.node.id ?? rows[0]?.node.id ?? null;
|
|
54
|
+
const focusRow = React.useCallback((id) => {
|
|
55
|
+
setFocusedId(id);
|
|
56
|
+
rowRefs.current.get(id)?.focus();
|
|
57
|
+
rowRefs.current.get(id)?.scrollIntoView?.({ block: "nearest" });
|
|
58
|
+
}, []);
|
|
59
|
+
React.useLayoutEffect(() => {
|
|
60
|
+
if (focusedId && !index.has(focusedId)) {
|
|
61
|
+
let next = null;
|
|
62
|
+
let parent = lastParents.current.get(focusedId) ?? null;
|
|
63
|
+
while (parent && !index.has(parent)) parent = lastParents.current.get(parent) ?? null;
|
|
64
|
+
const was = Math.max(0, lastIds.current.indexOf(focusedId));
|
|
65
|
+
next = parent ?? rows[Math.min(was, rows.length - 1)]?.node.id ?? null;
|
|
66
|
+
setFocusedId(next);
|
|
67
|
+
const active = document.activeElement;
|
|
68
|
+
if (next && hadFocus.current && (!active || active === document.body)) rowRefs.current.get(next)?.focus();
|
|
69
|
+
}
|
|
70
|
+
lastIds.current = rows.map((r) => r.node.id);
|
|
71
|
+
lastParents.current = new Map(rows.map((r) => [r.node.id, r.parentId]));
|
|
72
|
+
}, [
|
|
73
|
+
rows,
|
|
74
|
+
index,
|
|
75
|
+
focusedId
|
|
76
|
+
]);
|
|
77
|
+
const setExpanded = (row, open) => {
|
|
78
|
+
if (!row.expandable || row.expanded === open) return;
|
|
79
|
+
const id = row.node.id;
|
|
80
|
+
onExpandedChange(open ? [...expanded, id] : expanded.filter((e) => e !== id));
|
|
81
|
+
if (open && !row.node.children && row.node.hasChildren) onLoadChildren?.(id);
|
|
82
|
+
};
|
|
83
|
+
const selectRange = (toId) => {
|
|
84
|
+
const from = index.get(anchorId.current ?? toId) ?? 0;
|
|
85
|
+
const to = index.get(toId) ?? 0;
|
|
86
|
+
const [a, b] = from < to ? [from, to] : [to, from];
|
|
87
|
+
onSelectedChange(rows.slice(a, b + 1).filter((r) => !r.node.disabled).map((r) => r.node.id));
|
|
88
|
+
};
|
|
89
|
+
const select = (id, mode) => {
|
|
90
|
+
if (rows[index.get(id) ?? -1]?.node.disabled) return;
|
|
91
|
+
if (selectionMode === "single" || mode === "replace") {
|
|
92
|
+
anchorId.current = id;
|
|
93
|
+
onSelectedChange([id]);
|
|
94
|
+
} else if (mode === "toggle") {
|
|
95
|
+
anchorId.current = id;
|
|
96
|
+
onSelectedChange(selectedSet.has(id) ? selected.filter((s) => s !== id) : [...selected, id]);
|
|
97
|
+
} else selectRange(id);
|
|
98
|
+
};
|
|
99
|
+
const moveTo = (i, extend) => {
|
|
100
|
+
const row = rows[Math.max(0, Math.min(rows.length - 1, i))];
|
|
101
|
+
if (!row) return;
|
|
102
|
+
focusRow(row.node.id);
|
|
103
|
+
if (selectionMode === "multiple" && extend) selectRange(row.node.id);
|
|
104
|
+
else if (selectionFollowsFocus && !row.node.disabled) select(row.node.id, "replace");
|
|
105
|
+
};
|
|
106
|
+
const onKeyDown = (event) => {
|
|
107
|
+
props.onKeyDown?.(event);
|
|
108
|
+
if (event.defaultPrevented || isTypingTarget(event.target)) return;
|
|
109
|
+
const id = tabStopId;
|
|
110
|
+
if (!id) return;
|
|
111
|
+
const i = index.get(id) ?? 0;
|
|
112
|
+
const row = rows[i];
|
|
113
|
+
if (!row) return;
|
|
114
|
+
const mod = event.metaKey || event.ctrlKey;
|
|
115
|
+
let handled = true;
|
|
116
|
+
switch (event.key) {
|
|
117
|
+
case "ArrowDown":
|
|
118
|
+
moveTo(i + 1, event.shiftKey);
|
|
119
|
+
break;
|
|
120
|
+
case "ArrowUp":
|
|
121
|
+
moveTo(i - 1, event.shiftKey);
|
|
122
|
+
break;
|
|
123
|
+
case "Home":
|
|
124
|
+
moveTo(0, event.shiftKey);
|
|
125
|
+
break;
|
|
126
|
+
case "End":
|
|
127
|
+
moveTo(rows.length - 1, event.shiftKey);
|
|
128
|
+
break;
|
|
129
|
+
case "ArrowRight":
|
|
130
|
+
if (row.expandable && !row.expanded) setExpanded(row, true);
|
|
131
|
+
else if (row.expanded && rows[i + 1]?.parentId === row.node.id) moveTo(i + 1, event.shiftKey);
|
|
132
|
+
break;
|
|
133
|
+
case "ArrowLeft":
|
|
134
|
+
if (row.expanded) setExpanded(row, false);
|
|
135
|
+
else if (row.parentId) moveTo(index.get(row.parentId) ?? i, event.shiftKey);
|
|
136
|
+
break;
|
|
137
|
+
case "Enter":
|
|
138
|
+
if (row.node.disabled) break;
|
|
139
|
+
select(row.node.id, "replace");
|
|
140
|
+
onAction?.(row.node.id);
|
|
141
|
+
break;
|
|
142
|
+
case " ":
|
|
143
|
+
select(row.node.id, selectionMode === "multiple" ? event.shiftKey ? "range" : "toggle" : "replace");
|
|
144
|
+
break;
|
|
145
|
+
case "a":
|
|
146
|
+
if (mod && selectionMode === "multiple") onSelectedChange(rows.filter((r) => !r.node.disabled).map((r) => r.node.id));
|
|
147
|
+
else handled = false;
|
|
148
|
+
break;
|
|
149
|
+
case "ContextMenu":
|
|
150
|
+
onRowContextMenu?.(row.node.id, event);
|
|
151
|
+
break;
|
|
152
|
+
default: handled = false;
|
|
153
|
+
}
|
|
154
|
+
if (!handled && event.key.length === 1 && !mod && !event.altKey) {
|
|
155
|
+
const now = event.timeStamp;
|
|
156
|
+
const t = typeahead.current;
|
|
157
|
+
t.text = now - t.at > 600 ? event.key : t.text + event.key;
|
|
158
|
+
t.at = now;
|
|
159
|
+
const query = t.text.toLowerCase();
|
|
160
|
+
const start = t.text.length === 1 ? i + 1 : i;
|
|
161
|
+
for (let k = 0; k < rows.length; k++) {
|
|
162
|
+
const r = rows[(start + k) % rows.length];
|
|
163
|
+
if (r && r.node.label.toLowerCase().startsWith(query)) {
|
|
164
|
+
moveTo(index.get(r.node.id) ?? 0, false);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
handled = true;
|
|
169
|
+
}
|
|
170
|
+
if (handled) {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
event.stopPropagation();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
if (rows.length === 0 && empty) return /* @__PURE__ */ jsx("div", {
|
|
176
|
+
"data-slot": "tree-view",
|
|
177
|
+
"data-empty": true,
|
|
178
|
+
className: cn("text-[13px]", className),
|
|
179
|
+
...props,
|
|
180
|
+
children: empty
|
|
181
|
+
});
|
|
182
|
+
return /* @__PURE__ */ jsx("div", {
|
|
183
|
+
ref: treeRef,
|
|
184
|
+
role: "tree",
|
|
185
|
+
tabIndex: -1,
|
|
186
|
+
"data-slot": "tree-view",
|
|
187
|
+
"data-density": density,
|
|
188
|
+
"aria-multiselectable": selectionMode === "multiple" || void 0,
|
|
189
|
+
className: cn("group/tree flex flex-col py-1 text-[13px] select-none", className),
|
|
190
|
+
...props,
|
|
191
|
+
onKeyDown,
|
|
192
|
+
onFocus: (event) => {
|
|
193
|
+
hadFocus.current = true;
|
|
194
|
+
if (event.target === event.currentTarget && tabStopId) rowRefs.current.get(tabStopId)?.focus();
|
|
195
|
+
props.onFocus?.(event);
|
|
196
|
+
},
|
|
197
|
+
onBlur: (event) => {
|
|
198
|
+
if (!treeRef.current?.contains(event.relatedTarget)) hadFocus.current = false;
|
|
199
|
+
props.onBlur?.(event);
|
|
200
|
+
},
|
|
201
|
+
children: rows.map((row) => {
|
|
202
|
+
const { node } = row;
|
|
203
|
+
const isSelected = selectedSet.has(node.id);
|
|
204
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
205
|
+
ref: (el) => {
|
|
206
|
+
if (el) rowRefs.current.set(node.id, el);
|
|
207
|
+
else rowRefs.current.delete(node.id);
|
|
208
|
+
},
|
|
209
|
+
role: "treeitem",
|
|
210
|
+
"data-slot": "tree-item",
|
|
211
|
+
"data-id": node.id,
|
|
212
|
+
"aria-level": row.level,
|
|
213
|
+
"aria-setsize": row.setSize,
|
|
214
|
+
"aria-posinset": row.posInSet,
|
|
215
|
+
"aria-expanded": row.expandable ? row.expanded : void 0,
|
|
216
|
+
"aria-selected": isSelected,
|
|
217
|
+
"aria-disabled": node.disabled || void 0,
|
|
218
|
+
"aria-busy": row.expanded && node.loading || void 0,
|
|
219
|
+
"aria-label": node.label,
|
|
220
|
+
tabIndex: node.id === tabStopId ? 0 : -1,
|
|
221
|
+
"data-state": isSelected ? "selected" : void 0,
|
|
222
|
+
style: { paddingInlineStart: `${(row.level - 1) * 12 + 4}px` },
|
|
223
|
+
className: cn("relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 outline-none group-data-[density=compact]/tree:h-6", "hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground", "focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] aria-disabled:opacity-50", node.className),
|
|
224
|
+
onFocus: () => setFocusedId(node.id),
|
|
225
|
+
onClick: (event) => {
|
|
226
|
+
if (isTypingTarget(event.target)) return;
|
|
227
|
+
select(node.id, event.shiftKey ? "range" : event.metaKey || event.ctrlKey ? "toggle" : "replace");
|
|
228
|
+
const plain = !event.shiftKey && !event.metaKey && !event.ctrlKey;
|
|
229
|
+
if (plain && row.expandable && (expandOnClick ?? selectionMode === "single")) setExpanded(row, !row.expanded);
|
|
230
|
+
if (plain && !row.expandable && actionOnClick && !node.disabled) onAction?.(node.id);
|
|
231
|
+
},
|
|
232
|
+
onDoubleClick: (event) => {
|
|
233
|
+
if (!isTypingTarget(event.target) && !node.disabled) onAction?.(node.id);
|
|
234
|
+
},
|
|
235
|
+
onContextMenu: onRowContextMenu ? (event) => onRowContextMenu(node.id, event) : void 0,
|
|
236
|
+
children: [
|
|
237
|
+
indentGuides && Array.from({ length: row.level - 1 }, (_, level) => /* @__PURE__ */ jsx("span", {
|
|
238
|
+
"aria-hidden": true,
|
|
239
|
+
"data-slot": "tree-guide",
|
|
240
|
+
style: { insetInlineStart: `${level * 12 + 12}px` },
|
|
241
|
+
className: "pointer-events-none absolute inset-y-0 w-px bg-border opacity-0 transition-opacity group-hover/tree:opacity-100"
|
|
242
|
+
}, level)),
|
|
243
|
+
/* @__PURE__ */ jsx("span", {
|
|
244
|
+
"aria-hidden": true,
|
|
245
|
+
className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground",
|
|
246
|
+
onClick: (event) => {
|
|
247
|
+
if (!row.expandable) return;
|
|
248
|
+
event.stopPropagation();
|
|
249
|
+
setExpanded(row, !row.expanded);
|
|
250
|
+
},
|
|
251
|
+
children: row.expandable && /* @__PURE__ */ jsx(ChevronRight, { className: cn("size-3.5 motion-safe:transition-transform", row.expanded && "rotate-90") })
|
|
252
|
+
}),
|
|
253
|
+
node.icon && /* @__PURE__ */ jsx("span", {
|
|
254
|
+
"aria-hidden": true,
|
|
255
|
+
className: "flex shrink-0 text-muted-foreground [&_svg]:size-4",
|
|
256
|
+
children: node.icon
|
|
257
|
+
}),
|
|
258
|
+
/* @__PURE__ */ jsx("span", {
|
|
259
|
+
className: "min-w-0 flex-1 truncate",
|
|
260
|
+
children: renderLabel ? renderLabel(node) : node.label
|
|
261
|
+
}),
|
|
262
|
+
node.meta && /* @__PURE__ */ jsx("span", {
|
|
263
|
+
className: "shrink-0 text-xs text-muted-foreground",
|
|
264
|
+
children: node.meta
|
|
265
|
+
})
|
|
266
|
+
]
|
|
267
|
+
}), row.expanded && node.loading && !node.children?.length && /* @__PURE__ */ jsxs("div", {
|
|
268
|
+
role: "presentation",
|
|
269
|
+
"data-slot": "tree-loading",
|
|
270
|
+
style: { paddingInlineStart: `${row.level * 12 + 8}px` },
|
|
271
|
+
className: "flex h-7 items-center gap-2 text-muted-foreground group-data-[density=compact]/tree:h-6",
|
|
272
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
273
|
+
className: "size-3.5",
|
|
274
|
+
label: `Loading ${node.label}`
|
|
275
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
276
|
+
"aria-hidden": true,
|
|
277
|
+
children: "Loading…"
|
|
278
|
+
})]
|
|
279
|
+
})] }, node.id);
|
|
280
|
+
})
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
284
|
+
export { TreeView, flattenTree };
|
|
285
|
+
|
|
286
|
+
//# sourceMappingURL=tree-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-view.js","names":[],"sources":["../../src/components/tree-view.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Spinner } from './spinner';\n\nexport interface TreeNode {\n /** Stable identity. Keep it across renders (a path, a database id). */\n id: string;\n /** Plain text: the accessible name and the typeahead key. */\n label: string;\n icon?: React.ReactNode;\n /** Undefined: a leaf, unless `hasChildren` says the children load later. */\n children?: TreeNode[];\n /** Expandable before its children are known; expanding calls onLoadChildren. */\n hasChildren?: boolean;\n /** Children are being fetched; shows a loading row while expanded. */\n loading?: boolean;\n disabled?: boolean;\n /** Trailing detail, such as a git status letter or a count. */\n meta?: React.ReactNode;\n /** Extra classes on the row, for app states such as an ignored file or a highlight. */\n className?: string;\n}\n\nexport interface TreeViewProps extends Omit<\n React.ComponentProps<'div'>,\n 'children' | 'onSelect' | 'aria-label'\n> {\n nodes: TreeNode[];\n /** Required: what the tree holds, e.g. \"Files\". */\n 'aria-label': string;\n expanded: string[];\n onExpandedChange: (ids: string[]) => void;\n selected: string[];\n onSelectedChange: (ids: string[]) => void;\n /** `multiple`: Shift extends a range, Ctrl/⌘ toggles, Space toggles. */\n selectionMode?: 'single' | 'multiple';\n /** Move selection with focus on arrow keys (a file explorer); default off. */\n selectionFollowsFocus?: boolean;\n /** Enter or double-click: open the file, run the item. */\n onAction?: (id: string) => void;\n /**\n * A plain click on a row without children also runs `onAction`, the way a\n * file explorer opens a file on one click. Default off.\n */\n actionOnClick?: boolean;\n /**\n * A plain click on an expandable row toggles it. Defaults to on for single\n * selection; turn it on for a multi-select explorer, where Shift and Ctrl/⌘\n * clicks still only select.\n */\n expandOnClick?: boolean;\n /** Draw a line per ancestor level while the pointer is over the tree. */\n indentGuides?: boolean;\n /** Called once when a node with `hasChildren` and no children expands. */\n onLoadChildren?: (id: string) => void;\n /** Replace the label, e.g. with a rename input. Keys typed in an input never move focus. */\n renderLabel?: (node: TreeNode) => React.ReactNode;\n /** `compact` 24px rows for desktop explorers, `default` 28px. */\n density?: 'compact' | 'default';\n /** Shown when `nodes` is empty. */\n empty?: React.ReactNode;\n /** Right-click or the context-menu key on a row. */\n onRowContextMenu?: (id: string, event: React.MouseEvent | React.KeyboardEvent) => void;\n}\n\ninterface Row {\n node: TreeNode;\n level: number;\n parentId: string | null;\n setSize: number;\n posInSet: number;\n expandable: boolean;\n expanded: boolean;\n}\n\nconst isExpandable = (n: TreeNode) => (n.children?.length ?? 0) > 0 || Boolean(n.hasChildren);\n\n/** The rows a person can see, in order, with the ARIA position data for each. */\nexport function flattenTree(nodes: TreeNode[], expanded: ReadonlySet<string>): Row[] {\n const rows: Row[] = [];\n const walk = (list: TreeNode[], level: number, parentId: string | null) => {\n list.forEach((node, i) => {\n const expandable = isExpandable(node);\n const open = expandable && expanded.has(node.id);\n rows.push({\n node,\n level,\n parentId,\n setSize: list.length,\n posInSet: i + 1,\n expandable,\n expanded: open,\n });\n if (open && node.children) walk(node.children, level + 1, node.id);\n });\n };\n walk(nodes, 1, null);\n return rows;\n}\n\nconst isTypingTarget = (target: EventTarget | null) =>\n target instanceof HTMLElement &&\n (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));\n\n/**\n * A file-explorer style tree. Everything is controlled: the app owns which\n * nodes are expanded and selected, and file operations are callbacks. Rows\n * render flat (role=\"treeitem\" with aria-level), one tab stop for the tree,\n * arrows to move, Right/Left to open and close, Home/End, typeahead.\n */\nfunction TreeView({\n nodes,\n expanded,\n onExpandedChange,\n selected,\n onSelectedChange,\n selectionMode = 'single',\n selectionFollowsFocus = false,\n onAction,\n actionOnClick = false,\n expandOnClick,\n indentGuides = false,\n onLoadChildren,\n renderLabel,\n density = 'default',\n empty,\n onRowContextMenu,\n className,\n ...props\n}: TreeViewProps) {\n const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);\n const selectedSet = React.useMemo(() => new Set(selected), [selected]);\n const rows = React.useMemo(() => flattenTree(nodes, expandedSet), [nodes, expandedSet]);\n const index = React.useMemo(() => new Map(rows.map((r, i) => [r.node.id, i])), [rows]);\n\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const anchorId = React.useRef<string | null>(null);\n const rowRefs = React.useRef(new Map<string, HTMLDivElement>());\n const treeRef = React.useRef<HTMLDivElement>(null);\n const hadFocus = React.useRef(false);\n const lastIds = React.useRef<string[]>([]);\n const lastParents = React.useRef(new Map<string, string | null>());\n const typeahead = React.useRef({ text: '', at: 0 });\n\n // The tab stop: the focused row if it is still visible, else the first\n // selected visible row, else the first row.\n const tabStopId =\n (focusedId && index.has(focusedId) ? focusedId : null) ??\n rows.find((r) => selectedSet.has(r.node.id))?.node.id ??\n rows[0]?.node.id ??\n null;\n\n const focusRow = React.useCallback((id: string) => {\n setFocusedId(id);\n rowRefs.current.get(id)?.focus();\n rowRefs.current.get(id)?.scrollIntoView?.({ block: 'nearest' });\n }, []);\n\n // Focus recovery: when the focused row disappears (deleted, or a parent\n // collapsed), move to its nearest visible ancestor, else the row now at its\n // old position. Only takes DOM focus if the tree had it.\n React.useLayoutEffect(() => {\n if (focusedId && !index.has(focusedId)) {\n let next: string | null = null;\n let parent = lastParents.current.get(focusedId) ?? null;\n while (parent && !index.has(parent)) parent = lastParents.current.get(parent) ?? null;\n const was = Math.max(0, lastIds.current.indexOf(focusedId));\n next = parent ?? rows[Math.min(was, rows.length - 1)]?.node.id ?? null;\n setFocusedId(next);\n const active = document.activeElement;\n if (next && hadFocus.current && (!active || active === document.body))\n rowRefs.current.get(next)?.focus();\n }\n lastIds.current = rows.map((r) => r.node.id);\n lastParents.current = new Map(rows.map((r) => [r.node.id, r.parentId]));\n }, [rows, index, focusedId]);\n\n const setExpanded = (row: Row, open: boolean) => {\n if (!row.expandable || row.expanded === open) return;\n const id = row.node.id;\n onExpandedChange(open ? [...expanded, id] : expanded.filter((e) => e !== id));\n if (open && !row.node.children && row.node.hasChildren) onLoadChildren?.(id);\n };\n\n const selectRange = (toId: string) => {\n const from = index.get(anchorId.current ?? toId) ?? 0;\n const to = index.get(toId) ?? 0;\n const [a, b] = from < to ? [from, to] : [to, from];\n onSelectedChange(\n rows\n .slice(a, b + 1)\n .filter((r) => !r.node.disabled)\n .map((r) => r.node.id),\n );\n };\n\n const select = (id: string, mode: 'replace' | 'toggle' | 'range') => {\n if (rows[index.get(id) ?? -1]?.node.disabled) return;\n if (selectionMode === 'single' || mode === 'replace') {\n anchorId.current = id;\n onSelectedChange([id]);\n } else if (mode === 'toggle') {\n anchorId.current = id;\n onSelectedChange(selectedSet.has(id) ? selected.filter((s) => s !== id) : [...selected, id]);\n } else selectRange(id);\n };\n\n const moveTo = (i: number, extend: boolean) => {\n const row = rows[Math.max(0, Math.min(rows.length - 1, i))];\n if (!row) return;\n focusRow(row.node.id);\n if (selectionMode === 'multiple' && extend) selectRange(row.node.id);\n else if (selectionFollowsFocus && !row.node.disabled) select(row.node.id, 'replace');\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n props.onKeyDown?.(event);\n if (event.defaultPrevented || isTypingTarget(event.target)) return;\n const id = tabStopId;\n if (!id) return;\n const i = index.get(id) ?? 0;\n const row = rows[i];\n if (!row) return;\n const mod = event.metaKey || event.ctrlKey;\n let handled = true;\n switch (event.key) {\n case 'ArrowDown':\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowUp':\n moveTo(i - 1, event.shiftKey);\n break;\n case 'Home':\n moveTo(0, event.shiftKey);\n break;\n case 'End':\n moveTo(rows.length - 1, event.shiftKey);\n break;\n case 'ArrowRight':\n if (row.expandable && !row.expanded) setExpanded(row, true);\n else if (row.expanded && rows[i + 1]?.parentId === row.node.id)\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowLeft':\n if (row.expanded) setExpanded(row, false);\n else if (row.parentId) moveTo(index.get(row.parentId) ?? i, event.shiftKey);\n break;\n case 'Enter':\n if (row.node.disabled) break;\n select(row.node.id, 'replace');\n onAction?.(row.node.id);\n break;\n case ' ':\n select(\n row.node.id,\n selectionMode === 'multiple' ? (event.shiftKey ? 'range' : 'toggle') : 'replace',\n );\n break;\n case 'a':\n if (mod && selectionMode === 'multiple') {\n onSelectedChange(rows.filter((r) => !r.node.disabled).map((r) => r.node.id));\n } else handled = false;\n break;\n case 'ContextMenu':\n onRowContextMenu?.(row.node.id, event);\n break;\n default:\n handled = false;\n }\n if (!handled && event.key.length === 1 && !mod && !event.altKey) {\n // Typeahead: jump to the next row whose label starts with what was typed.\n const now = event.timeStamp;\n const t = typeahead.current;\n t.text = now - t.at > 600 ? event.key : t.text + event.key;\n t.at = now;\n const query = t.text.toLowerCase();\n const start = t.text.length === 1 ? i + 1 : i;\n for (let k = 0; k < rows.length; k++) {\n const r = rows[(start + k) % rows.length];\n if (r && r.node.label.toLowerCase().startsWith(query)) {\n moveTo(index.get(r.node.id) ?? 0, false);\n break;\n }\n }\n handled = true;\n }\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n };\n\n if (rows.length === 0 && empty) {\n return (\n <div data-slot=\"tree-view\" data-empty className={cn('text-[13px]', className)} {...props}>\n {empty}\n </div>\n );\n }\n\n return (\n <div\n ref={treeRef}\n role=\"tree\"\n tabIndex={-1}\n data-slot=\"tree-view\"\n data-density={density}\n aria-multiselectable={selectionMode === 'multiple' || undefined}\n className={cn('group/tree flex flex-col py-1 text-[13px] select-none', className)}\n {...props}\n onKeyDown={onKeyDown}\n onFocus={(event) => {\n hadFocus.current = true;\n // Focus landing on the tree itself (a click on empty space) goes to the tab stop.\n if (event.target === event.currentTarget && tabStopId)\n rowRefs.current.get(tabStopId)?.focus();\n props.onFocus?.(event);\n }}\n onBlur={(event) => {\n if (!treeRef.current?.contains(event.relatedTarget)) hadFocus.current = false;\n props.onBlur?.(event);\n }}\n >\n {rows.map((row) => {\n const { node } = row;\n const isSelected = selectedSet.has(node.id);\n return (\n <React.Fragment key={node.id}>\n {/* Keys are handled once on the tree, for whichever row has focus. */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}\n <div\n ref={(el) => {\n if (el) rowRefs.current.set(node.id, el);\n else rowRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n data-slot=\"tree-item\"\n data-id={node.id}\n aria-level={row.level}\n aria-setsize={row.setSize}\n aria-posinset={row.posInSet}\n aria-expanded={row.expandable ? row.expanded : undefined}\n aria-selected={isSelected}\n aria-disabled={node.disabled || undefined}\n aria-busy={(row.expanded && node.loading) || undefined}\n aria-label={node.label}\n tabIndex={node.id === tabStopId ? 0 : -1}\n data-state={isSelected ? 'selected' : undefined}\n style={{ paddingInlineStart: `${(row.level - 1) * 12 + 4}px` }}\n className={cn(\n 'relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 outline-none group-data-[density=compact]/tree:h-6',\n 'hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground',\n 'focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] aria-disabled:opacity-50',\n node.className,\n )}\n onFocus={() => setFocusedId(node.id)}\n onClick={(event) => {\n if (isTypingTarget(event.target)) return;\n select(\n node.id,\n event.shiftKey ? 'range' : event.metaKey || event.ctrlKey ? 'toggle' : 'replace',\n );\n const plain = !event.shiftKey && !event.metaKey && !event.ctrlKey;\n if (plain && row.expandable && (expandOnClick ?? selectionMode === 'single'))\n setExpanded(row, !row.expanded);\n if (plain && !row.expandable && actionOnClick && !node.disabled)\n onAction?.(node.id);\n }}\n onDoubleClick={(event) => {\n if (!isTypingTarget(event.target) && !node.disabled) onAction?.(node.id);\n }}\n onContextMenu={\n onRowContextMenu ? (event) => onRowContextMenu(node.id, event) : undefined\n }\n >\n {indentGuides &&\n Array.from({ length: row.level - 1 }, (_, level) => (\n <span\n key={level}\n aria-hidden\n data-slot=\"tree-guide\"\n style={{ insetInlineStart: `${level * 12 + 12}px` }}\n className=\"pointer-events-none absolute inset-y-0 w-px bg-border opacity-0 transition-opacity group-hover/tree:opacity-100\"\n />\n ))}\n {/* A pointer shortcut only: Right and Left expand and collapse from the keyboard. */}\n <span\n aria-hidden\n className=\"flex size-4 shrink-0 items-center justify-center text-muted-foreground\"\n onClick={(event) => {\n if (!row.expandable) return;\n event.stopPropagation();\n setExpanded(row, !row.expanded);\n }}\n >\n {row.expandable && (\n <ChevronRight\n className={cn(\n 'size-3.5 motion-safe:transition-transform',\n row.expanded && 'rotate-90',\n )}\n />\n )}\n </span>\n {node.icon && (\n <span aria-hidden className=\"flex shrink-0 text-muted-foreground [&_svg]:size-4\">\n {node.icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">\n {renderLabel ? renderLabel(node) : node.label}\n </span>\n {node.meta && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{node.meta}</span>\n )}\n </div>\n {row.expanded && node.loading && !node.children?.length && (\n <div\n role=\"presentation\"\n data-slot=\"tree-loading\"\n style={{ paddingInlineStart: `${row.level * 12 + 8}px` }}\n className=\"flex h-7 items-center gap-2 text-muted-foreground group-data-[density=compact]/tree:h-6\"\n >\n <Spinner className=\"size-3.5\" label={`Loading ${node.label}`} />\n <span aria-hidden>Loading…</span>\n </div>\n )}\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n\nexport { TreeView };\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,10 +15,12 @@ export * from './components/collapsible';
|
|
|
15
15
|
export * from './components/combobox';
|
|
16
16
|
export * from './components/command';
|
|
17
17
|
export * from './components/composer';
|
|
18
|
+
export * from './components/context-menu';
|
|
18
19
|
export * from './components/conversation';
|
|
19
20
|
export * from './components/copy-button';
|
|
20
21
|
export * from './components/dialog';
|
|
21
22
|
export * from './components/dropdown-menu';
|
|
23
|
+
export * from './components/editor-tabs';
|
|
22
24
|
export * from './components/empty-state';
|
|
23
25
|
export * from './components/field';
|
|
24
26
|
export * from './components/icon-button';
|
|
@@ -26,14 +28,17 @@ export * from './components/input';
|
|
|
26
28
|
export * from './components/kbd';
|
|
27
29
|
export * from './components/label';
|
|
28
30
|
export * from './components/markdown';
|
|
31
|
+
export * from './components/menubar';
|
|
29
32
|
export * from './components/message';
|
|
30
33
|
export * from './components/navigation-menu';
|
|
34
|
+
export * from './components/onboarding-checklist';
|
|
31
35
|
export * from './components/page-header';
|
|
32
36
|
export * from './components/pagination';
|
|
33
37
|
export * from './components/popover';
|
|
34
38
|
export * from './components/progress';
|
|
35
39
|
export * from './components/radio-group';
|
|
36
40
|
export * from './components/reasoning';
|
|
41
|
+
export * from './components/resizable';
|
|
37
42
|
export * from './components/scroll-area';
|
|
38
43
|
export * from './components/secret-input';
|
|
39
44
|
export * from './components/select';
|
|
@@ -54,3 +59,5 @@ export * from './components/toaster';
|
|
|
54
59
|
export * from './components/tool-call';
|
|
55
60
|
export * from './components/toolbar';
|
|
56
61
|
export * from './components/tooltip';
|
|
62
|
+
export * from './components/tour';
|
|
63
|
+
export * from './components/tree-view';
|
package/dist/index.js
CHANGED
|
@@ -15,28 +15,33 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./component
|
|
|
15
15
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "./components/dialog.js";
|
|
16
16
|
import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from "./components/command.js";
|
|
17
17
|
import { Input, fieldClasses } from "./components/input.js";
|
|
18
|
-
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, surfaceClasses } from "./components/popover.js";
|
|
18
|
+
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, menuItemClasses, surfaceClasses } from "./components/popover.js";
|
|
19
19
|
import { Spinner } from "./components/spinner.js";
|
|
20
20
|
import { Combobox } from "./components/combobox.js";
|
|
21
21
|
import { Composer, Suggestions } from "./components/composer.js";
|
|
22
|
+
import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "./components/context-menu.js";
|
|
22
23
|
import { Conversation } from "./components/conversation.js";
|
|
23
24
|
import { IconButton } from "./components/icon-button.js";
|
|
24
25
|
import { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, toastVariants } from "./components/toast.js";
|
|
25
26
|
import { Toaster, toast } from "./components/toaster.js";
|
|
26
27
|
import { CopyButton } from "./components/copy-button.js";
|
|
27
28
|
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./components/dropdown-menu.js";
|
|
29
|
+
import { EditorTabs } from "./components/editor-tabs.js";
|
|
28
30
|
import { EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle } from "./components/empty-state.js";
|
|
29
31
|
import { Field, FieldDescription, FieldError, FieldHeader, FieldHint, FieldLabel } from "./components/field.js";
|
|
30
32
|
import { Kbd, KbdGroup } from "./components/kbd.js";
|
|
31
33
|
import { Label } from "./components/label.js";
|
|
32
34
|
import { CodeBlock, Markdown, parseMarkdown } from "./components/markdown.js";
|
|
35
|
+
import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from "./components/menubar.js";
|
|
33
36
|
import { Message } from "./components/message.js";
|
|
34
37
|
import { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, navigationMenuTriggerStyle } from "./components/navigation-menu.js";
|
|
38
|
+
import { Progress } from "./components/progress.js";
|
|
39
|
+
import { OnboardingChecklist } from "./components/onboarding-checklist.js";
|
|
35
40
|
import { PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderEyebrow, PageHeaderMain, PageHeaderTitle } from "./components/page-header.js";
|
|
36
41
|
import { Pagination, paginationRange } from "./components/pagination.js";
|
|
37
|
-
import { Progress } from "./components/progress.js";
|
|
38
42
|
import { RadioGroup, RadioGroupItem } from "./components/radio-group.js";
|
|
39
43
|
import { Reasoning, StreamingIndicator } from "./components/reasoning.js";
|
|
44
|
+
import { ResizablePanel, ResizablePanelGroup, ResizeHandle, useGroupRef, usePanelRef } from "./components/resizable.js";
|
|
40
45
|
import { ScrollArea, ScrollBar } from "./components/scroll-area.js";
|
|
41
46
|
import { SecretInput } from "./components/secret-input.js";
|
|
42
47
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "./components/select.js";
|
|
@@ -54,4 +59,6 @@ import { Textarea } from "./components/textarea.js";
|
|
|
54
59
|
import { ToolApproval, ToolCall } from "./components/tool-call.js";
|
|
55
60
|
import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarSpacer } from "./components/toolbar.js";
|
|
56
61
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/tooltip.js";
|
|
57
|
-
|
|
62
|
+
import { Spotlight, Tour, TourAnchor, findTourTarget, tourTarget, waitForTourTarget } from "./components/tour.js";
|
|
63
|
+
import { TreeView, flattenTree } from "./components/tree-view.js";
|
|
64
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellHeader, AppShellMain, AppShellSidebar, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Card, CardAction, CardContent, CardDescription, CardEyebrow, CardFooter, CardHeader, CardTitle, Checkbox, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Composer, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, Conversation, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorTabs, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle, Field, FieldDescription, FieldError, FieldHeader, FieldHint, FieldLabel, IconButton, Input, Kbd, KbdGroup, Label, Markdown, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Message, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, OnboardingChecklist, PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderEyebrow, PageHeaderMain, PageHeaderTitle, Pagination, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, Reasoning, ResizablePanel, ResizablePanelGroup, ResizeHandle, ScrollArea, ScrollBar, SecretInput, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Skeleton, Slider, Spinner, Spotlight, StatCard, Status, StatusDot, Steps, StreamingIndicator, Suggestions, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, ToolApproval, ToolCall, Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarSpacer, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Tour, TourAnchor, TreeView, alertVariants, badgeVariants, brand, buttonVariants, cardVariants, cn, dark, fieldClasses, findTourTarget, flattenTree, fontMono, fontSans, isApplePlatform, light, menuItemClasses, modKeyLabel, motion, navigationMenuTriggerStyle, paginationRange, parseMarkdown, radius, shadow, shortcutLabel, statusDotVariants, surfaceClasses, toast, toastVariants, tokens, tourTarget, useGroupRef, usePanelRef, waitForTourTarget };
|