@tulipfarm/sprout 0.1.0 → 0.2.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/dist/components/attachment.js +2 -2
- package/dist/components/brand-icon.js +4 -0
- package/dist/components/canvas.js +2 -2
- package/dist/components/chat-input.d.ts +94 -0
- package/dist/components/chat-input.js +279 -0
- package/dist/components/code-block.js +2 -33
- package/dist/components/file-explorer.d.ts +27 -0
- package/dist/components/file-explorer.js +300 -0
- package/dist/components/file-viewer.d.ts +18 -1
- package/dist/components/file-viewer.js +35 -8
- package/dist/components/questionnaire.js +2 -1
- package/dist/components/search-dialog.js +5 -4
- package/dist/components/state-panel.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/brand-glyphs.d.ts +28 -0
- package/dist/internal/brand-glyphs.js +24 -0
- package/dist/internal/code-tokens.d.ts +4 -0
- package/dist/internal/code-tokens.js +34 -0
- package/dist/styles/accordion.css +2 -2
- package/dist/styles/alert-dialog.css +2 -2
- package/dist/styles/alert.css +2 -2
- package/dist/styles/attachment.css +4 -24
- package/dist/styles/badge.css +2 -2
- package/dist/styles/brand-icon.css +2 -2
- package/dist/styles/breadcrumb.css +2 -2
- package/dist/styles/button.css +2 -2
- package/dist/styles/calendar.css +3 -3
- package/dist/styles/canvas.css +4 -18
- package/dist/styles/carousel.css +2 -2
- package/dist/styles/chart.css +2 -2
- package/dist/styles/chat-confirmation.css +2 -2
- package/dist/styles/chat-input.css +240 -0
- package/dist/styles/chat-sources.css +2 -2
- package/dist/styles/checkbox.css +2 -3
- package/dist/styles/choice-list.css +2 -2
- package/dist/styles/code-block.css +2 -2
- package/dist/styles/collapsible.css +2 -2
- package/dist/styles/combobox.css +6 -6
- package/dist/styles/command.css +2 -2
- package/dist/styles/context-menu.css +3 -3
- package/dist/styles/dialog.css +2 -2
- package/dist/styles/dropdown-menu.css +1 -1
- package/dist/styles/field.css +2 -3
- package/dist/styles/file-explorer.css +517 -0
- package/dist/styles/file-viewer.css +113 -16
- package/dist/styles/hover-card.css +2 -2
- package/dist/styles/index.css +6 -0
- package/dist/styles/input-group.css +6 -11
- package/dist/styles/input-otp.css +2 -2
- package/dist/styles/input.css +3 -4
- package/dist/styles/mention-list.css +1 -1
- package/dist/styles/menubar.css +3 -3
- package/dist/styles/message-response.css +4 -4
- package/dist/styles/message-scroller.css +2 -2
- package/dist/styles/message.css +4 -4
- package/dist/styles/navigation-menu.css +2 -2
- package/dist/styles/page-header.css +4 -4
- package/dist/styles/plan-outline.css +6 -6
- package/dist/styles/prompt-list.css +2 -2
- package/dist/styles/questionnaire.css +2 -17
- package/dist/styles/radio-group.css +2 -3
- package/dist/styles/reasoning-effort.css +2 -2
- package/dist/styles/review-card.css +2 -2
- package/dist/styles/rich-text-editor.css +2 -2
- package/dist/styles/schema-diagram.css +4 -4
- package/dist/styles/scroll-area.css +2 -2
- package/dist/styles/search-dialog.css +0 -83
- package/dist/styles/select.css +3 -3
- package/dist/styles/separator.css +2 -2
- package/dist/styles/sheet.css +2 -2
- package/dist/styles/sidebar.css +2 -2
- package/dist/styles/slider.css +1 -1
- package/dist/styles/state-panel.css +1 -10
- package/dist/styles/switch.css +2 -2
- package/dist/styles/table.css +10 -10
- package/dist/styles/tabs.css +4 -4
- package/dist/styles/textarea.css +3 -4
- package/dist/styles/themes/tulipfarm.css +89 -99
- package/dist/styles/toast.css +2 -2
- package/dist/styles/toggle-group.css +2 -2
- package/dist/styles/toggle.css +2 -2
- package/dist/styles/tool-activity.css +5 -5
- package/package.json +10 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useDeferredValue, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { ChevronRight } from "reicon-react/icons/ChevronRight";
|
|
5
|
+
import { FileText } from "reicon-react/icons/FileText";
|
|
6
|
+
import { Folder } from "reicon-react/icons/Folder";
|
|
7
|
+
import { FolderOpen } from "reicon-react/icons/FolderOpen";
|
|
8
|
+
import { MinusSquare } from "reicon-react/icons/MinusSquare";
|
|
9
|
+
import { X } from "reicon-react/icons/X";
|
|
10
|
+
import { cn } from "../internal/classes.js";
|
|
11
|
+
import { tokenClass, useCodeTokens } from "../internal/code-tokens.js";
|
|
12
|
+
import { codeLanguageNames } from "../internal/languages.js";
|
|
13
|
+
import { languageForFilename } from "../internal/syntax.js";
|
|
14
|
+
import { SearchField } from "./search-field.js";
|
|
15
|
+
import { StatePanel } from "./state-panel.js";
|
|
16
|
+
/** Search stops after this many matching lines, so one common word cannot flood the list. */
|
|
17
|
+
export const fileExplorerMaxMatches = 500;
|
|
18
|
+
const nameOf = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
19
|
+
const folderOf = (path) => path.slice(0, Math.max(0, path.lastIndexOf("/")));
|
|
20
|
+
const order = (a, b) => a.kind === b.kind
|
|
21
|
+
? a.name.localeCompare(b.name, undefined, { numeric: true })
|
|
22
|
+
: a.kind === "folder"
|
|
23
|
+
? -1
|
|
24
|
+
: 1;
|
|
25
|
+
function ancestors(path) {
|
|
26
|
+
const parts = path.split("/").slice(0, -1);
|
|
27
|
+
return parts.map((_, index) => parts.slice(0, index + 1).join("/"));
|
|
28
|
+
}
|
|
29
|
+
function buildTree(files) {
|
|
30
|
+
const root = [];
|
|
31
|
+
const folders = new Map([["", root]]);
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
for (const folder of ancestors(file.path)) {
|
|
34
|
+
if (folders.has(folder))
|
|
35
|
+
continue;
|
|
36
|
+
const children = [];
|
|
37
|
+
folders.get(folderOf(folder)).push({
|
|
38
|
+
kind: "folder",
|
|
39
|
+
path: folder,
|
|
40
|
+
name: nameOf(folder),
|
|
41
|
+
children,
|
|
42
|
+
});
|
|
43
|
+
folders.set(folder, children);
|
|
44
|
+
}
|
|
45
|
+
folders
|
|
46
|
+
.get(folderOf(file.path))
|
|
47
|
+
.push({ kind: "file", path: file.path, name: nameOf(file.path), file });
|
|
48
|
+
}
|
|
49
|
+
for (const children of folders.values())
|
|
50
|
+
children.sort(order);
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
function visibleRows(nodes, expanded, depth = 0) {
|
|
54
|
+
return nodes.flatMap((node, index) => [
|
|
55
|
+
{ node, depth, size: nodes.length, position: index + 1 },
|
|
56
|
+
...(node.kind === "folder" && expanded.has(node.path)
|
|
57
|
+
? visibleRows(node.children, expanded, depth + 1)
|
|
58
|
+
: []),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
function rangesIn(text, needle) {
|
|
62
|
+
const haystack = text.toLowerCase();
|
|
63
|
+
const ranges = [];
|
|
64
|
+
for (let at = haystack.indexOf(needle); at >= 0; at = haystack.indexOf(needle, at + needle.length))
|
|
65
|
+
ranges.push([at, at + needle.length]);
|
|
66
|
+
return ranges;
|
|
67
|
+
}
|
|
68
|
+
const excerptLead = 32;
|
|
69
|
+
function searchFiles(files, query) {
|
|
70
|
+
const needle = query.toLowerCase();
|
|
71
|
+
const names = files.filter((file) => file.path.toLowerCase().includes(needle));
|
|
72
|
+
const hits = [];
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
if (!("content" in file) || total >= fileExplorerMaxMatches)
|
|
76
|
+
continue;
|
|
77
|
+
const lines = [];
|
|
78
|
+
file.content.split("\n").forEach((raw, index) => {
|
|
79
|
+
if (total >= fileExplorerMaxMatches)
|
|
80
|
+
return;
|
|
81
|
+
const ranges = rangesIn(raw, needle);
|
|
82
|
+
if (!ranges.length)
|
|
83
|
+
return;
|
|
84
|
+
const indent = raw.length - raw.trimStart().length;
|
|
85
|
+
const cut = Math.max(indent, ranges[0][0] - excerptLead);
|
|
86
|
+
const prefix = cut > indent ? "…" : "";
|
|
87
|
+
const shift = cut - prefix.length;
|
|
88
|
+
lines.push({
|
|
89
|
+
line: index + 1,
|
|
90
|
+
text: prefix + raw.slice(cut).trimEnd(),
|
|
91
|
+
ranges: ranges.map(([start, end]) => [start - shift, end - shift]),
|
|
92
|
+
});
|
|
93
|
+
total += 1;
|
|
94
|
+
});
|
|
95
|
+
if (lines.length)
|
|
96
|
+
hits.push({ file, lines });
|
|
97
|
+
}
|
|
98
|
+
return { names, hits, total, capped: total >= fileExplorerMaxMatches };
|
|
99
|
+
}
|
|
100
|
+
/** Splits text pieces at the match ranges and wraps the matches in `mark`. */
|
|
101
|
+
function marked(pieces, ranges) {
|
|
102
|
+
const out = [];
|
|
103
|
+
let offset = 0;
|
|
104
|
+
pieces.forEach((piece, index) => {
|
|
105
|
+
const end = offset + piece.content.length;
|
|
106
|
+
const cuts = new Set([offset, end]);
|
|
107
|
+
for (const [start, stop] of ranges)
|
|
108
|
+
for (const cut of [start, stop])
|
|
109
|
+
if (cut > offset && cut < end)
|
|
110
|
+
cuts.add(cut);
|
|
111
|
+
const sorted = [...cuts].sort((a, b) => a - b);
|
|
112
|
+
for (let at = 0; at < sorted.length - 1; at += 1) {
|
|
113
|
+
const from = sorted[at];
|
|
114
|
+
const text = piece.content.slice(from - offset, sorted[at + 1] - offset);
|
|
115
|
+
const span = (_jsx("span", { className: piece.className, children: text }, `${index}-${from}`));
|
|
116
|
+
out.push(ranges.some(([start, stop]) => from >= start && from < stop) ? (_jsx("mark", { className: "sprout-file-explorer-mark", children: span }, `${index}-${from}`)) : (span));
|
|
117
|
+
}
|
|
118
|
+
offset = end;
|
|
119
|
+
});
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
const glyphs = {
|
|
123
|
+
typescript: ["TS", "info"],
|
|
124
|
+
tsx: ["TSX", "info"],
|
|
125
|
+
javascript: ["JS", "warning"],
|
|
126
|
+
jsx: ["JSX", "warning"],
|
|
127
|
+
json: ["{ }", "warning"],
|
|
128
|
+
jsonc: ["{ }", "warning"],
|
|
129
|
+
json5: ["{ }", "warning"],
|
|
130
|
+
markdown: ["MD", "accent"],
|
|
131
|
+
mdx: ["MDX", "accent"],
|
|
132
|
+
python: ["PY", "info"],
|
|
133
|
+
yaml: ["YML", "danger"],
|
|
134
|
+
toml: ["TML", "danger"],
|
|
135
|
+
css: ["CSS", "accent"],
|
|
136
|
+
scss: ["SCS", "accent"],
|
|
137
|
+
html: ["< >", "danger"],
|
|
138
|
+
xml: ["< >", "warning"],
|
|
139
|
+
shellscript: ["SH", "success"],
|
|
140
|
+
dotenv: ["ENV", "success"],
|
|
141
|
+
sql: ["SQL", "accent"],
|
|
142
|
+
rust: ["RS", "danger"],
|
|
143
|
+
go: ["GO", "info"],
|
|
144
|
+
ruby: ["RB", "danger"],
|
|
145
|
+
};
|
|
146
|
+
/** A 16px tile with a short language label, tinted by language. Decorative. */
|
|
147
|
+
function FileGlyph({ name }) {
|
|
148
|
+
const language = languageForFilename(name);
|
|
149
|
+
const extension = name.includes(".") ? (name.split(".").at(-1) ?? "") : "";
|
|
150
|
+
const [label, tone] = (language && glyphs[language]) ?? [
|
|
151
|
+
extension.slice(0, 3).toUpperCase(),
|
|
152
|
+
"muted",
|
|
153
|
+
];
|
|
154
|
+
if (!label)
|
|
155
|
+
return (_jsx(FileText, { size: 16, "aria-hidden": "true", className: "sprout-file-explorer-glyph" }));
|
|
156
|
+
return (_jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", "data-tone": tone, className: "sprout-file-explorer-glyph", children: [_jsx("rect", { x: "0.5", y: "1.5", width: "15", height: "13", rx: "3", className: "sprout-file-explorer-glyph-tile" }), _jsx("text", { x: "8", y: "10.8", textAnchor: "middle", fontSize: label.length > 2 ? 6 : 7.4, fontWeight: "700", className: "sprout-file-explorer-glyph-label", children: label })] }));
|
|
157
|
+
}
|
|
158
|
+
function CodeView({ file, query, line, }) {
|
|
159
|
+
const language = languageForFilename(file.path);
|
|
160
|
+
const tokens = useCodeTokens(file.content, language);
|
|
161
|
+
const needle = query.toLowerCase();
|
|
162
|
+
const current = useRef(null);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
current.current?.scrollIntoView({ block: "center" });
|
|
165
|
+
}, [line, file.path]);
|
|
166
|
+
const lines = useMemo(() => tokens?.map((row) => row.map((token) => ({
|
|
167
|
+
content: token.content,
|
|
168
|
+
className: tokenClass(token),
|
|
169
|
+
}))) ?? file.content.split("\n").map((content) => [{ content }]), [tokens, file.content]);
|
|
170
|
+
// ponytail: one DOM row per line; virtualize if files past ~20k lines matter.
|
|
171
|
+
return (_jsx("div", { className: "sprout-file-explorer-code", tabIndex: 0, role: "region", "aria-label": file.path, "data-highlighted": tokens ? "" : undefined, children: _jsx("pre", { className: "sprout-file-explorer-pre", children: _jsx("code", { children: lines.map((pieces, index) => {
|
|
172
|
+
const text = pieces.map((piece) => piece.content).join("");
|
|
173
|
+
return (_jsxs("div", { ref: index + 1 === line ? current : undefined, "data-current": index + 1 === line ? "" : undefined, className: "sprout-file-explorer-line", children: [_jsx("span", { "aria-hidden": "true", className: "sprout-file-explorer-line-number", children: index + 1 }), _jsxs("span", { className: "sprout-file-explorer-line-text", children: [needle
|
|
174
|
+
? marked(pieces, rangesIn(text, needle))
|
|
175
|
+
: pieces.map((piece, at) => (_jsx("span", { className: piece.className, children: piece.content }, at))), "\n"] })] }, index));
|
|
176
|
+
}) }) }) }));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A read-only code explorer: a folder tree, open tabs, and a syntax-colored
|
|
180
|
+
* file pane. Search filters file names and lists matching lines across every
|
|
181
|
+
* file; picking a line opens the file there.
|
|
182
|
+
*/
|
|
183
|
+
export function FileExplorer({ files, label = "Files", defaultPath, className, ...props }) {
|
|
184
|
+
const tree = useMemo(() => buildTree(files), [files]);
|
|
185
|
+
const byPath = useMemo(() => new Map(files.map((file) => [file.path, file])), [files]);
|
|
186
|
+
const [expanded, setExpanded] = useState(() => new Set(defaultPath ? ancestors(defaultPath) : []));
|
|
187
|
+
const [tabs, setTabs] = useState(defaultPath && byPath.has(defaultPath) ? [defaultPath] : []);
|
|
188
|
+
const [active, setActive] = useState(tabs[0] ?? null);
|
|
189
|
+
const [focused, setFocused] = useState(active);
|
|
190
|
+
const [line, setLine] = useState(null);
|
|
191
|
+
const [query, setQuery] = useState("");
|
|
192
|
+
const deferred = useDeferredValue(query.trim());
|
|
193
|
+
const results = useMemo(() => (deferred ? searchFiles(files, deferred) : null), [files, deferred]);
|
|
194
|
+
const rows = useMemo(() => visibleRows(tree, expanded), [tree, expanded]);
|
|
195
|
+
const treeRef = useRef(null);
|
|
196
|
+
const id = useId();
|
|
197
|
+
const toggle = (path, open) => setExpanded((current) => {
|
|
198
|
+
const next = new Set(current);
|
|
199
|
+
if (open ?? !next.has(path))
|
|
200
|
+
next.add(path);
|
|
201
|
+
else
|
|
202
|
+
next.delete(path);
|
|
203
|
+
return next;
|
|
204
|
+
});
|
|
205
|
+
const open = (path, at = null) => {
|
|
206
|
+
setTabs((current) => {
|
|
207
|
+
if (current.includes(path))
|
|
208
|
+
return current;
|
|
209
|
+
const next = [...current];
|
|
210
|
+
next.splice(active ? next.indexOf(active) + 1 : next.length, 0, path);
|
|
211
|
+
return next;
|
|
212
|
+
});
|
|
213
|
+
setActive(path);
|
|
214
|
+
setFocused(path);
|
|
215
|
+
setLine(at);
|
|
216
|
+
setExpanded((current) => new Set([...current, ...ancestors(path)]));
|
|
217
|
+
};
|
|
218
|
+
const close = (path) => {
|
|
219
|
+
const index = tabs.indexOf(path);
|
|
220
|
+
const next = tabs.filter((tab) => tab !== path);
|
|
221
|
+
setTabs(next);
|
|
222
|
+
if (active === path) {
|
|
223
|
+
setActive(next[Math.min(index, next.length - 1)] ?? null);
|
|
224
|
+
setLine(null);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const focusRow = (path) => {
|
|
228
|
+
if (!path)
|
|
229
|
+
return;
|
|
230
|
+
setFocused(path);
|
|
231
|
+
treeRef.current
|
|
232
|
+
?.querySelector(`[data-path="${CSS.escape(path)}"]`)
|
|
233
|
+
?.focus();
|
|
234
|
+
};
|
|
235
|
+
const onTreeKeyDown = (event) => {
|
|
236
|
+
const index = rows.findIndex((row) => row.node.path === focused);
|
|
237
|
+
const row = rows[index];
|
|
238
|
+
if (!row)
|
|
239
|
+
return;
|
|
240
|
+
const { node } = row;
|
|
241
|
+
const isOpen = node.kind === "folder" && expanded.has(node.path);
|
|
242
|
+
switch (event.key) {
|
|
243
|
+
case "ArrowDown":
|
|
244
|
+
focusRow(rows[index + 1]?.node.path);
|
|
245
|
+
break;
|
|
246
|
+
case "ArrowUp":
|
|
247
|
+
focusRow(rows[index - 1]?.node.path);
|
|
248
|
+
break;
|
|
249
|
+
case "Home":
|
|
250
|
+
focusRow(rows[0]?.node.path);
|
|
251
|
+
break;
|
|
252
|
+
case "End":
|
|
253
|
+
focusRow(rows.at(-1)?.node.path);
|
|
254
|
+
break;
|
|
255
|
+
case "ArrowRight":
|
|
256
|
+
if (node.kind !== "folder")
|
|
257
|
+
return;
|
|
258
|
+
if (isOpen)
|
|
259
|
+
focusRow(rows[index + 1]?.node.path);
|
|
260
|
+
else
|
|
261
|
+
toggle(node.path, true);
|
|
262
|
+
break;
|
|
263
|
+
case "ArrowLeft":
|
|
264
|
+
if (isOpen)
|
|
265
|
+
toggle(node.path, false);
|
|
266
|
+
else
|
|
267
|
+
focusRow(folderOf(node.path) || undefined);
|
|
268
|
+
break;
|
|
269
|
+
case "Enter":
|
|
270
|
+
case " ":
|
|
271
|
+
if (node.kind === "folder")
|
|
272
|
+
toggle(node.path);
|
|
273
|
+
else
|
|
274
|
+
open(node.path);
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
};
|
|
281
|
+
const activeFile = active ? byPath.get(active) : undefined;
|
|
282
|
+
const language = activeFile ? languageForFilename(activeFile.path) : null;
|
|
283
|
+
const rovingPath = focused && rows.some((row) => row.node.path === focused)
|
|
284
|
+
? focused
|
|
285
|
+
: rows[0]?.node.path;
|
|
286
|
+
return (_jsxs("div", { "data-slot": "file-explorer", ...props, className: cn("sprout-file-explorer", className), children: [_jsxs("aside", { className: "sprout-file-explorer-sidebar", "aria-label": label, children: [_jsxs("div", { className: "sprout-file-explorer-sidebar-header", children: [_jsx("span", { className: "sprout-file-explorer-title", children: label }), _jsx("button", { type: "button", className: "sprout-file-explorer-icon-button", "aria-label": "Collapse all folders", title: "Collapse all folders", disabled: expanded.size === 0, onClick: () => setExpanded(new Set()), children: _jsx(MinusSquare, { size: 14, "aria-hidden": "true" }) })] }), _jsx("div", { className: "sprout-file-explorer-search", children: _jsx(SearchField, { label: `Search ${label.toLowerCase()}`, placeholder: "Search names and text", value: query, maxLength: 120, onChange: setQuery, className: "max-w-none" }) }), results ? (_jsxs("div", { className: "sprout-file-explorer-results", "aria-busy": deferred !== query.trim() || undefined, children: [_jsx("p", { role: "status", className: "sprout-file-explorer-summary", children: results.names.length || results.total
|
|
287
|
+
? `${results.names.length} ${results.names.length === 1 ? "file name" : "file names"}, ${results.capped ? `first ${results.total}` : results.total} ${results.total === 1 ? "line" : "lines"}`
|
|
288
|
+
: `No files or lines match “${deferred}”.` }), results.names.length > 0 && (_jsxs("section", { "aria-labelledby": `${id}-names`, children: [_jsx("h3", { id: `${id}-names`, className: "sprout-file-explorer-group", children: "File names" }), _jsx("ul", { className: "sprout-file-explorer-list", children: results.names.map((file) => (_jsx("li", { children: _jsxs("button", { type: "button", className: "sprout-file-explorer-result", "aria-current": file.path === active || undefined, onClick: () => open(file.path), children: [_jsx(FileGlyph, { name: file.path }), _jsx("span", { className: "sprout-file-explorer-name", children: marked([{ content: nameOf(file.path) }], rangesIn(nameOf(file.path), deferred.toLowerCase())) }), _jsx("span", { className: "sprout-file-explorer-dir", children: folderOf(file.path) })] }) }, file.path))) })] })), results.hits.length > 0 && (_jsxs("section", { "aria-labelledby": `${id}-lines`, children: [_jsx("h3", { id: `${id}-lines`, className: "sprout-file-explorer-group", children: "Text in files" }), _jsx("ul", { className: "sprout-file-explorer-list", children: results.hits.map(({ file, lines }) => (_jsxs("li", { children: [_jsxs("button", { type: "button", className: "sprout-file-explorer-result", onClick: () => open(file.path, lines[0].line), children: [_jsx(FileGlyph, { name: file.path }), _jsx("span", { className: "sprout-file-explorer-name", children: nameOf(file.path) }), _jsx("span", { className: "sprout-file-explorer-dir", children: folderOf(file.path) }), _jsx("span", { className: "sprout-file-explorer-count", children: lines.length })] }), _jsx("ul", { className: "sprout-file-explorer-list", children: lines.map((hit) => (_jsx("li", { children: _jsxs("button", { type: "button", className: "sprout-file-explorer-hit", "aria-label": `${file.path}, line ${hit.line}: ${hit.text}`, "aria-current": (file.path === active && hit.line === line) ||
|
|
289
|
+
undefined, onClick: () => open(file.path, hit.line), children: [_jsx("span", { className: "sprout-file-explorer-hit-line", children: hit.line }), _jsx("span", { className: "sprout-file-explorer-hit-text", children: marked([{ content: hit.text }], hit.ranges) })] }) }, hit.line))) })] }, file.path))) })] }))] })) : (_jsxs("div", { ref: treeRef, role: "tree", "aria-label": label, className: "sprout-file-explorer-tree", onKeyDown: onTreeKeyDown, children: [rows.length === 0 && (_jsx("p", { className: "sprout-file-explorer-summary", children: "No files yet." })), rows.map(({ node, depth, size, position }) => {
|
|
290
|
+
const folder = node.kind === "folder";
|
|
291
|
+
const isOpen = folder && expanded.has(node.path);
|
|
292
|
+
return (_jsxs("div", { role: "treeitem", "data-path": node.path, "data-kind": node.kind, "aria-level": depth + 1, "aria-setsize": size, "aria-posinset": position, "aria-expanded": folder ? isOpen : undefined, "aria-selected": folder ? undefined : node.path === active, tabIndex: node.path === rovingPath ? 0 : -1, className: "sprout-file-explorer-row", style: { "--depth": depth }, onFocus: () => setFocused(node.path), onClick: () => (folder ? toggle(node.path) : open(node.path)), children: [Array.from({ length: depth }, (_, at) => (_jsx("span", { "aria-hidden": "true", className: "sprout-file-explorer-guide" }, at))), _jsx("span", { "aria-hidden": "true", className: "sprout-file-explorer-chevron", children: folder && _jsx(ChevronRight, { size: 12 }) }), folder ? (isOpen ? (_jsx(FolderOpen, { size: 16, "aria-hidden": "true", className: "sprout-file-explorer-folder" })) : (_jsx(Folder, { size: 16, "aria-hidden": "true", className: "sprout-file-explorer-folder" }))) : (_jsx(FileGlyph, { name: node.name })), _jsx("span", { className: "sprout-file-explorer-name", title: node.path, children: node.name })] }, node.path));
|
|
293
|
+
})] }))] }), _jsxs("section", { className: "sprout-file-explorer-editor", "aria-label": "Editor", children: [tabs.length > 0 && (_jsx("div", { className: "sprout-file-explorer-tabs", "aria-label": "Open files", children: tabs.map((path) => (_jsxs("div", { className: "sprout-file-explorer-tab", "data-active": path === active ? "" : undefined, children: [_jsxs("button", { type: "button", className: "sprout-file-explorer-tab-open", "aria-current": path === active || undefined, title: path, onClick: () => {
|
|
294
|
+
setActive(path);
|
|
295
|
+
setLine(null);
|
|
296
|
+
}, onAuxClick: (event) => {
|
|
297
|
+
if (event.button === 1)
|
|
298
|
+
close(path);
|
|
299
|
+
}, children: [_jsx(FileGlyph, { name: path }), nameOf(path)] }), _jsx("button", { type: "button", className: "sprout-file-explorer-tab-close", "aria-label": `Close ${nameOf(path)}`, onClick: () => close(path), children: _jsx(X, { size: 12, "aria-hidden": "true" }) })] }, path))) })), activeFile ? (_jsxs(_Fragment, { children: [_jsx("nav", { className: "sprout-file-explorer-crumbs", "aria-label": "File path", children: _jsx("ol", { children: activeFile.path.split("/").map((part, index, parts) => (_jsxs("li", { "aria-current": index === parts.length - 1 ? "page" : undefined, children: [index > 0 && (_jsx(ChevronRight, { size: 10, "aria-hidden": "true", className: "sprout-file-explorer-crumb-separator" })), part] }, index))) }) }), _jsx("div", { className: "sprout-file-explorer-body", children: "content" in activeFile ? (activeFile.content === "" ? (_jsx("p", { className: "sprout-file-explorer-summary", children: "This file is empty." })) : (_jsx(CodeView, { file: activeFile, query: results ? deferred : "", line: line }, activeFile.path))) : (_jsx(StatePanel, { kind: "unavailable", heading: "h3", title: "File cannot be displayed", description: activeFile.unavailable })) }), _jsxs("div", { className: "sprout-file-explorer-status", children: [_jsx("span", { children: language ? codeLanguageNames[language] : "Plain text" }), "content" in activeFile && (_jsxs("span", { children: [activeFile.content.split("\n").length.toLocaleString(), " lines"] })), line !== null && _jsxs("span", { children: ["Line ", line] }), _jsx("span", { className: "sprout-file-explorer-status-end", children: "Read only" })] })] })) : (_jsx(StatePanel, { kind: "empty", heading: "h3", title: "Open a file", description: "Choose a file in the tree, or search names and text.", icon: FileText }))] })] }));
|
|
300
|
+
}
|
|
@@ -65,6 +65,23 @@ interface FileViewerBaseProps {
|
|
|
65
65
|
renderers?: FileRenderers | undefined;
|
|
66
66
|
/** Where focus goes on close. Defaults to the element that had focus when the viewer opened. */
|
|
67
67
|
finalFocus?: DialogContentProps["finalFocus"];
|
|
68
|
+
/** Your header controls before Download, such as who is viewing and a Share button. */
|
|
69
|
+
actions?: ReactNode;
|
|
70
|
+
/** `DropdownMenuItem`s added to the More actions menu after Reload. */
|
|
71
|
+
menu?: ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* A side panel for comments, activity, sharing, or file details. Adds a
|
|
74
|
+
* header toggle. It stays mounted while hidden and across file steps, so
|
|
75
|
+
* tabs and drafts inside it survive.
|
|
76
|
+
*/
|
|
77
|
+
panel?: ReactNode;
|
|
78
|
+
/** The panel's accessible name and toggle label. Defaults to “Details”. */
|
|
79
|
+
panelLabel?: string;
|
|
80
|
+
/** Controls whether the panel shows. */
|
|
81
|
+
panelOpen?: boolean;
|
|
82
|
+
/** Whether the panel starts open. Defaults to open on screens 1024px and wider. */
|
|
83
|
+
defaultPanelOpen?: boolean;
|
|
84
|
+
onPanelOpenChange?: (open: boolean) => void;
|
|
68
85
|
}
|
|
69
86
|
interface SingleFileProps {
|
|
70
87
|
file: ViewableFile | null;
|
|
@@ -81,7 +98,7 @@ interface FileSetProps {
|
|
|
81
98
|
}
|
|
82
99
|
export type FileViewerProps = FileViewerBaseProps & (SingleFileProps | FileSetProps);
|
|
83
100
|
/** A full-screen dialog that opens a local or linked file, or steps through several. */
|
|
84
|
-
export declare function FileViewer({ file, files, index, onIndexChange, open, onOpenChange, renderers, finalFocus, }: FileViewerProps): import("react").JSX.Element;
|
|
101
|
+
export declare function FileViewer({ file, files, index, onIndexChange, open, onOpenChange, renderers, finalFocus, actions, menu, panel, panelLabel, panelOpen, defaultPanelOpen, onPanelOpenChange, }: FileViewerProps): import("react").JSX.Element;
|
|
85
102
|
export interface ViewableFileContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
86
103
|
file: ViewableFile;
|
|
87
104
|
renderers?: FileRenderers | undefined;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, Suspense, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
2
|
+
import { createContext, Suspense, useContext, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { ArrowUpRight } from "reicon-react/icons/ArrowUpRight";
|
|
4
4
|
import { ChevronLeft } from "reicon-react/icons/ChevronLeft";
|
|
5
5
|
import { ChevronRight } from "reicon-react/icons/ChevronRight";
|
|
6
6
|
import { Download } from "reicon-react/icons/Download";
|
|
7
|
+
import { MoreH } from "reicon-react/icons/MoreH";
|
|
8
|
+
import { SidebarRight } from "reicon-react/icons/SidebarRight";
|
|
7
9
|
import { X } from "reicon-react/icons/X";
|
|
8
10
|
import { cn } from "../internal/classes.js";
|
|
9
11
|
import { fileKind } from "../internal/file-kind.js";
|
|
10
12
|
import { Button } from "./button.js";
|
|
11
13
|
import { CodeBlock, languageForFilename } from "./code-block.js";
|
|
12
14
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from "./dialog.js";
|
|
15
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
13
16
|
import { Spinner } from "./spinner.js";
|
|
14
17
|
import { StatePanel } from "./state-panel.js";
|
|
15
18
|
const RenderersContext = createContext({});
|
|
@@ -94,10 +97,12 @@ export function renderableType(name, mediaType) {
|
|
|
94
97
|
? type
|
|
95
98
|
: "application/octet-stream";
|
|
96
99
|
}
|
|
97
|
-
function useFileContent(file) {
|
|
100
|
+
function useFileContent(file, attempt = 0, enabled = true) {
|
|
98
101
|
const [loaded, setLoaded] = useState({ kind: "loading" });
|
|
99
102
|
const { source, name, mediaType } = file;
|
|
100
103
|
useEffect(() => {
|
|
104
|
+
if (!enabled)
|
|
105
|
+
return;
|
|
101
106
|
let cancelled = false;
|
|
102
107
|
let objectUrl;
|
|
103
108
|
const controller = new AbortController();
|
|
@@ -141,12 +146,21 @@ function useFileContent(file) {
|
|
|
141
146
|
if (objectUrl)
|
|
142
147
|
URL.revokeObjectURL(objectUrl);
|
|
143
148
|
};
|
|
144
|
-
}, [source, name, mediaType]);
|
|
149
|
+
}, [source, name, mediaType, attempt, enabled]);
|
|
145
150
|
return loaded;
|
|
146
151
|
}
|
|
147
152
|
const ownsArrowKeys = 'input, textarea, select, iframe, [contenteditable="true"], [role="region"], [role="slider"]';
|
|
148
153
|
/** A full-screen dialog that opens a local or linked file, or steps through several. */
|
|
149
|
-
export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpenChange, renderers, finalFocus, }) {
|
|
154
|
+
export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpenChange, renderers, finalFocus, actions, menu, panel, panelLabel = "Details", panelOpen, defaultPanelOpen, onPanelOpenChange, }) {
|
|
155
|
+
const panelId = useId();
|
|
156
|
+
const [ownPanelOpen, setOwnPanelOpen] = useState(() => defaultPanelOpen ??
|
|
157
|
+
(typeof window !== "undefined" &&
|
|
158
|
+
!!window.matchMedia?.("(min-width: 1024px)").matches));
|
|
159
|
+
const panelShown = !!panel && (panelOpen ?? ownPanelOpen);
|
|
160
|
+
const togglePanel = () => {
|
|
161
|
+
setOwnPanelOpen(!panelShown);
|
|
162
|
+
onPanelOpenChange?.(!panelShown);
|
|
163
|
+
};
|
|
150
164
|
const current = files ? (files[index] ?? null) : file;
|
|
151
165
|
const count = files?.length ?? 0;
|
|
152
166
|
const refocus = useRef(null);
|
|
@@ -192,7 +206,14 @@ export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpen
|
|
|
192
206
|
event.preventDefault();
|
|
193
207
|
move();
|
|
194
208
|
};
|
|
195
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children:
|
|
209
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "fullscreen", showCloseButton: false, "data-file-viewer": "", "data-panel": panelShown ? "open" : undefined, className: "sprout-file-viewer", finalFocus: finalFocus, onKeyDown: handleKeyDown, children: [current && open && (_jsx(SelectedFile, { file: current, renderers: renderers, step: step, actions: actions, menu: menu, panel: panel
|
|
210
|
+
? {
|
|
211
|
+
id: panelId,
|
|
212
|
+
label: panelLabel,
|
|
213
|
+
open: panelShown,
|
|
214
|
+
toggle: togglePanel,
|
|
215
|
+
}
|
|
216
|
+
: undefined }, step ? index : undefined)), current && open && panel && (_jsx("aside", { id: panelId, "aria-label": panelLabel, hidden: !panelShown, "data-slot": "file-viewer-panel", className: "sprout-file-viewer-panel", children: panel }))] }) }));
|
|
196
217
|
}
|
|
197
218
|
function sourceLabel(file, google) {
|
|
198
219
|
return google
|
|
@@ -201,9 +222,12 @@ function sourceLabel(file, google) {
|
|
|
201
222
|
? "Local file · Not uploaded"
|
|
202
223
|
: "Linked file";
|
|
203
224
|
}
|
|
204
|
-
function SelectedFile({ file, renderers, step, }) {
|
|
225
|
+
function SelectedFile({ file, renderers, step, actions, menu, panel, }) {
|
|
226
|
+
const [attempt, setAttempt] = useState(0);
|
|
227
|
+
const retry = () => setAttempt((value) => value + 1);
|
|
205
228
|
const google = file.source.kind === "remote" ? googleDocument(file.source.url) : null;
|
|
206
|
-
|
|
229
|
+
const loaded = useFileContent(file, attempt, !google);
|
|
230
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { "data-slot": "file-viewer-header", className: "sprout-file-viewer-header", children: [_jsxs("div", { className: "sprout-file-viewer-heading", children: [_jsx(DialogTitle, { children: _jsx("span", { className: "sprout-file-viewer-truncate", title: file.name, children: file.name }) }), _jsx(DialogDescription, { children: _jsx("span", { className: "sprout-file-viewer-truncate", children: sourceLabel(file, !!google) }) })] }), _jsxs("div", { "data-slot": "file-viewer-controls", className: "sprout-file-viewer-controls", children: [step && (_jsxs("div", { "data-slot": "file-viewer-steps", className: "sprout-file-viewer-steps", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous file", "data-step": "previous", disabled: !step.previous, onClick: step.previous, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { className: "sprout-file-viewer-position", "aria-hidden": "true", children: [step.position, " of ", step.count] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next file", "data-step": "next", disabled: !step.next, onClick: step.next, children: _jsx(ChevronRight, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { "aria-live": "polite", className: "sprout-file-viewer-sr-only", children: [file.name, ", file ", step.position, " of ", step.count] })] })), actions && (_jsx("div", { "data-slot": "file-viewer-actions", className: "sprout-file-viewer-actions", children: actions })), google ? (_jsxs(Button, { variant: "outline", size: "sm", nativeButton: false, render: _jsx("a", { href: google.url, target: "_blank", rel: "noopener noreferrer" }), children: [_jsx(ArrowUpRight, { size: 16, "aria-hidden": "true" }), "Open in Google"] })) : loaded.kind === "ready" ? (_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "Download", nativeButton: false, render: _jsx("a", { href: loaded.url, download: file.name }), children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-file-viewer-label", children: "Download" })] })) : (_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "Download", disabled: true, children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-file-viewer-label", children: "Download" })] })), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "More actions" }), children: _jsx(MoreH, { size: 16, "aria-hidden": "true" }) }), _jsxs(DropdownMenuContent, { align: "end", size: "menu", children: [_jsx(DropdownMenuItem, { onClick: retry, children: google ? "Reload viewer" : "Reload file" }), menu && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), menu] }))] })] })] }), _jsxs("div", { "data-slot": "file-viewer-window", className: "sprout-file-viewer-window", children: [panel && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": panel.label, "aria-expanded": panel.open, "aria-controls": panel.id, onClick: panel.toggle, children: _jsx(SidebarRight, { size: 16, "aria-hidden": "true" }) })), _jsx(DialogClose, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Close file viewer" }), children: _jsx(X, { size: 18, "aria-hidden": "true" }) })] })] }), _jsx("div", { "data-slot": "viewable-file-content", className: "sprout-file-viewer-surface", children: google ? (_jsx(EmbeddedFile, { url: google.embedUrl, name: file.name, google: true, onRetry: retry }, `${google.embedUrl}:${attempt}`)) : (_jsx(FileBody, { loaded: loaded, file: file, renderers: renderers, onRetry: retry })) })] }));
|
|
207
231
|
}
|
|
208
232
|
/** Loads a local or linked file and shows it inline, with reload and download. */
|
|
209
233
|
export function ViewableFileContent({ file, renderers, className, ...props }) {
|
|
@@ -214,11 +238,14 @@ export function ViewableFileContent({ file, renderers, className, ...props }) {
|
|
|
214
238
|
}
|
|
215
239
|
function LocalFile({ file, renderers, onRetry, }) {
|
|
216
240
|
const loaded = useFileContent(file);
|
|
241
|
+
return (_jsxs(_Fragment, { children: [loaded.kind === "ready" && (_jsxs("div", { "data-slot": "file-viewer-toolbar", className: "sprout-file-viewer-toolbar", "data-align": "end", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: onRetry, children: "Reload file" }), _jsxs(Button, { variant: "outline", size: "sm", nativeButton: false, render: _jsx("a", { href: loaded.url, download: file.name }), children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), "Download"] })] })), _jsx(FileBody, { loaded: loaded, file: file, renderers: renderers, onRetry: onRetry })] }));
|
|
242
|
+
}
|
|
243
|
+
function FileBody({ loaded, file, renderers, onRetry, }) {
|
|
217
244
|
if (loaded.kind === "loading")
|
|
218
245
|
return _jsx(LoadingFile, {});
|
|
219
246
|
if (loaded.kind === "error")
|
|
220
247
|
return _jsx(FileError, { message: loaded.message, onRetry: onRetry });
|
|
221
|
-
return (
|
|
248
|
+
return (_jsx(FileContent, { name: file.name, mediaType: file.mediaType, bytes: loaded.bytes, url: loaded.url, onRetry: onRetry, renderers: renderers }));
|
|
222
249
|
}
|
|
223
250
|
const textKinds = [
|
|
224
251
|
"text",
|
|
@@ -6,6 +6,7 @@ import { createContext, useCallback, useContext, useId, useLayoutEffect, useMemo
|
|
|
6
6
|
import { Check } from "reicon-react/icons/Check";
|
|
7
7
|
import { cn } from "../internal/classes.js";
|
|
8
8
|
import { buttonVariants, } from "./button.js";
|
|
9
|
+
import { Kbd } from "./kbd.js";
|
|
9
10
|
export const questionnaireItemStatusNames = [
|
|
10
11
|
"unanswered",
|
|
11
12
|
"answered",
|
|
@@ -800,7 +801,7 @@ export function QuestionnaireChoice({ checked, children, className, defaultCheck
|
|
|
800
801
|
props: mergeProps({
|
|
801
802
|
"data-slot": "questionnaire-choice",
|
|
802
803
|
className: cn("sprout-questionnaire-choice", className),
|
|
803
|
-
children: (_jsxs(_Fragment, { children: [_jsx("input", { "data-slot": "questionnaire-choice-input", className: "sprout-questionnaire-choice-input", ref: setInput, id: id, type: type, name: status === "skipped" ? undefined : name, value: value, checked: isChecked, disabled: disabled, required: required && !multiple && !hasInputAnswer, "aria-invalid": invalid || undefined, "aria-keyshortcuts": keyShortcuts(shortcut, !disabled && isChecked), "data-checked": isChecked ? "" : undefined, "data-unchecked": isChecked ? undefined : "", onChange: handleChange }), _jsxs("span", { "aria-hidden": "true", "data-slot": "questionnaire-choice-indicator", className: "sprout-questionnaire-choice-indicator", children: [_jsx("span", { className: "sprout-questionnaire-choice-dot" }), _jsx(Check, { className: "sprout-questionnaire-choice-check" })] }), _jsx("span", { "data-slot": "questionnaire-choice-label", className: "sprout-questionnaire-choice-label", children: children }), shortcut !== null && (_jsx(
|
|
804
|
+
children: (_jsxs(_Fragment, { children: [_jsx("input", { "data-slot": "questionnaire-choice-input", className: "sprout-questionnaire-choice-input", ref: setInput, id: id, type: type, name: status === "skipped" ? undefined : name, value: value, checked: isChecked, disabled: disabled, required: required && !multiple && !hasInputAnswer, "aria-invalid": invalid || undefined, "aria-keyshortcuts": keyShortcuts(shortcut, !disabled && isChecked), "data-checked": isChecked ? "" : undefined, "data-unchecked": isChecked ? undefined : "", onChange: handleChange }), _jsxs("span", { "aria-hidden": "true", "data-slot": "questionnaire-choice-indicator", className: "sprout-questionnaire-choice-indicator", children: [_jsx("span", { className: "sprout-questionnaire-choice-dot" }), _jsx(Check, { className: "sprout-questionnaire-choice-check" })] }), _jsx("span", { "data-slot": "questionnaire-choice-label", className: "sprout-questionnaire-choice-label", children: children }), shortcut !== null && (_jsx(Kbd, { variant: "outline", "aria-hidden": "true", "data-slot": "questionnaire-choice-shortcut", className: "sprout-questionnaire-choice-shortcut", children: shortcut }))] })),
|
|
804
805
|
}, props),
|
|
805
806
|
state,
|
|
806
807
|
stateAttributesMapping: {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Toggle } from "@base-ui/react/toggle";
|
|
4
3
|
import { Children, Fragment, isValidElement, } from "react";
|
|
5
4
|
import { X } from "reicon-react/icons/X";
|
|
6
5
|
import { cn } from "../internal/classes.js";
|
|
7
6
|
import { Button } from "./button.js";
|
|
7
|
+
import { Kbd } from "./kbd.js";
|
|
8
|
+
import { Toggle } from "./toggle.js";
|
|
8
9
|
import { Command, CommandGroup, CommandInput, CommandItem, CommandList, } from "./command.js";
|
|
9
10
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from "./dialog.js";
|
|
10
11
|
/**
|
|
@@ -24,7 +25,7 @@ export function SearchFilters({ label, children }) {
|
|
|
24
25
|
}
|
|
25
26
|
/** A pressable filter chip. */
|
|
26
27
|
export function SearchFilter({ pressed, onPressedChange, icon, children, }) {
|
|
27
|
-
return (_jsxs(Toggle, { "data-slot": "search-filter", pressed: pressed, onPressedChange: (next) => onPressedChange(next),
|
|
28
|
+
return (_jsxs(Toggle, { "data-slot": "search-filter", variant: "outline", size: "sm", pressed: pressed, onPressedChange: (next) => onPressedChange(next), children: [icon, children] }));
|
|
28
29
|
}
|
|
29
30
|
/** The scrolling results area. It keeps a steady height so typing does not resize the dialog. */
|
|
30
31
|
export function SearchList({ label, children }) {
|
|
@@ -42,7 +43,7 @@ export function SearchItemIcon({ children }) {
|
|
|
42
43
|
}
|
|
43
44
|
/** One result. The active item shows an Enter key hint. */
|
|
44
45
|
export function SearchItem({ value, onSelect, media, title, meta, detail, trailing, }) {
|
|
45
|
-
return (_jsxs(CommandItem, { value: value, onSelect: () => onSelect(), className: "sprout-search-item", children: [_jsx("span", { "data-slot": "search-item-media", className: "sprout-search-item-media", children: media }), _jsxs("span", { className: "sprout-search-item-text", children: [_jsxs("span", { className: "sprout-search-item-line", children: [_jsx("span", { "data-slot": "search-item-title", className: "sprout-search-item-title", children: title }), meta && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "sprout-search-item-dot", children: "\u00B7" }), _jsx("span", { "data-slot": "search-item-meta", className: "sprout-search-item-meta", children: meta })] }))] }), detail && (_jsx("span", { "data-slot": "search-item-detail", className: "sprout-search-item-detail", children: detail }))] }), trailing && (_jsx("span", { "data-slot": "search-item-trailing", className: "sprout-search-item-trailing", children: trailing })), _jsx(
|
|
46
|
+
return (_jsxs(CommandItem, { value: value, onSelect: () => onSelect(), className: "sprout-search-item", children: [_jsx("span", { "data-slot": "search-item-media", className: "sprout-search-item-media", children: media }), _jsxs("span", { className: "sprout-search-item-text", children: [_jsxs("span", { className: "sprout-search-item-line", children: [_jsx("span", { "data-slot": "search-item-title", className: "sprout-search-item-title", children: title }), meta && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "sprout-search-item-dot", children: "\u00B7" }), _jsx("span", { "data-slot": "search-item-meta", className: "sprout-search-item-meta", children: meta })] }))] }), detail && (_jsx("span", { "data-slot": "search-item-detail", className: "sprout-search-item-detail", children: detail }))] }), trailing && (_jsx("span", { "data-slot": "search-item-trailing", className: "sprout-search-item-trailing", children: trailing })), _jsx(Kbd, { variant: "outline", "aria-hidden": "true", className: "sprout-search-item-enter", children: "\u21B5" })] }));
|
|
46
47
|
}
|
|
47
48
|
function tiles(media) {
|
|
48
49
|
const nodes = isValidElement(media) && media.type === Fragment
|
|
@@ -60,5 +61,5 @@ export function SearchFooter({ children }) {
|
|
|
60
61
|
}
|
|
61
62
|
/** A key and what it does. */
|
|
62
63
|
export function SearchHint({ keys, children }) {
|
|
63
|
-
return (_jsxs("span", { "data-slot": "search-hint", className: "sprout-search-hint", children: [_jsx(
|
|
64
|
+
return (_jsxs("span", { "data-slot": "search-hint", className: "sprout-search-hint", children: [_jsx(Kbd, { variant: "outline", children: keys }), children] }));
|
|
64
65
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Check } from "reicon-react/icons/Check";
|
|
3
3
|
import { Inbox } from "reicon-react/icons/Inbox";
|
|
4
|
-
import { Loader } from "reicon-react/icons/Loader";
|
|
5
4
|
import { Warning } from "reicon-react/icons/Warning";
|
|
6
5
|
import { cn } from "../internal/classes.js";
|
|
6
|
+
import { Spinner } from "./spinner.js";
|
|
7
7
|
export const statePanelKindNames = [
|
|
8
8
|
"empty",
|
|
9
9
|
"loading",
|
|
@@ -17,7 +17,7 @@ const roles = {
|
|
|
17
17
|
success: "status",
|
|
18
18
|
};
|
|
19
19
|
function Media({ kind, Icon }) {
|
|
20
|
-
return (_jsx("div", { "data-slot": "state-panel-media", className: "sprout-state-panel-media", "aria-hidden": "true", children: kind === "loading" ? (_jsx(
|
|
20
|
+
return (_jsx("div", { "data-slot": "state-panel-media", className: "sprout-state-panel-media", "aria-hidden": "true", children: kind === "loading" ? (_jsx(Spinner, { "aria-hidden": "true" })) : kind === "success" ? (_jsx(Check, { size: 20 })) : kind === "error" ? (_jsx(Warning, { size: 20, className: "sprout-state-panel-danger" })) : (_jsx(Icon, { size: 20 })) }));
|
|
21
21
|
}
|
|
22
22
|
export function StatePanel({ kind, title, description, heading: Heading = "h2", icon = Inbox, children, sample, className, ...props }) {
|
|
23
23
|
const media = icon === null && (kind === "empty" || kind === "unavailable") ? null : (_jsx(Media, { kind: kind, Icon: icon ?? Inbox }));
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./components/card.js";
|
|
|
15
15
|
export * from "./components/carousel.js";
|
|
16
16
|
export * from "./components/chart.js";
|
|
17
17
|
export * from "./components/chat-confirmation.js";
|
|
18
|
+
export * from "./components/chat-input.js";
|
|
18
19
|
export * from "./components/chat-plan.js";
|
|
19
20
|
export * from "./components/chat-sources.js";
|
|
20
21
|
export * from "./components/checkbox.js";
|
|
@@ -28,6 +29,7 @@ export * from "./components/dialog.js";
|
|
|
28
29
|
export * from "./components/direction.js";
|
|
29
30
|
export * from "./components/dropdown-menu.js";
|
|
30
31
|
export * from "./components/field.js";
|
|
32
|
+
export * from "./components/file-explorer.js";
|
|
31
33
|
export * from "./components/file-viewer.js";
|
|
32
34
|
export * from "./components/form.js";
|
|
33
35
|
export * from "./components/glass-banner.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./components/card.js";
|
|
|
15
15
|
export * from "./components/carousel.js";
|
|
16
16
|
export * from "./components/chart.js";
|
|
17
17
|
export * from "./components/chat-confirmation.js";
|
|
18
|
+
export * from "./components/chat-input.js";
|
|
18
19
|
export * from "./components/chat-plan.js";
|
|
19
20
|
export * from "./components/chat-sources.js";
|
|
20
21
|
export * from "./components/checkbox.js";
|
|
@@ -28,6 +29,7 @@ export * from "./components/dialog.js";
|
|
|
28
29
|
export * from "./components/direction.js";
|
|
29
30
|
export * from "./components/dropdown-menu.js";
|
|
30
31
|
export * from "./components/field.js";
|
|
32
|
+
export * from "./components/file-explorer.js";
|
|
31
33
|
export * from "./components/file-viewer.js";
|
|
32
34
|
export * from "./components/form.js";
|
|
33
35
|
export * from "./components/glass-banner.js";
|
|
@@ -37,6 +37,34 @@ export declare const brandGlyphs: {
|
|
|
37
37
|
readonly evenOdd: true;
|
|
38
38
|
}];
|
|
39
39
|
};
|
|
40
|
+
readonly gemini: {
|
|
41
|
+
readonly viewBox: "0 0 24 24";
|
|
42
|
+
readonly paths: readonly [{
|
|
43
|
+
readonly d: "M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z";
|
|
44
|
+
readonly evenOdd: true;
|
|
45
|
+
}];
|
|
46
|
+
};
|
|
47
|
+
readonly mistral: {
|
|
48
|
+
readonly viewBox: "0 0 24 24";
|
|
49
|
+
readonly paths: readonly [{
|
|
50
|
+
readonly d: "M3.428 3.4h3.429v3.428h3.429v3.429h-.002 3.431V6.828h3.427V3.4h3.43v13.714H24v3.429H13.714v-3.428h-3.428v-3.429h-3.43v3.428h3.43v3.429H0v-3.429h3.428V3.4zm10.286 13.715h3.428v-3.429h-3.427v3.429z";
|
|
51
|
+
readonly evenOdd: true;
|
|
52
|
+
}];
|
|
53
|
+
};
|
|
54
|
+
readonly meta: {
|
|
55
|
+
readonly viewBox: "0 0 24 24";
|
|
56
|
+
readonly paths: readonly [{
|
|
57
|
+
readonly d: "M6.897 4c1.915 0 3.516.932 5.43 3.376l.282-.373c.19-.246.383-.484.58-.71l.313-.35C14.588 4.788 15.792 4 17.225 4c1.273 0 2.469.557 3.491 1.516l.218.213c1.73 1.765 2.917 4.71 3.053 8.026l.011.392.002.25c0 1.501-.28 2.759-.818 3.7l-.14.23-.108.153c-.301.42-.664.758-1.086 1.009l-.265.142-.087.04a3.493 3.493 0 01-.302.118 4.117 4.117 0 01-1.33.208c-.524 0-.996-.067-1.438-.215-.614-.204-1.163-.56-1.726-1.116l-.227-.235c-.753-.812-1.534-1.976-2.493-3.586l-1.43-2.41-.544-.895-1.766 3.13-.343.592C7.597 19.156 6.227 20 4.356 20c-1.21 0-2.205-.42-2.936-1.182l-.168-.184c-.484-.573-.837-1.311-1.043-2.189l-.067-.32a8.69 8.69 0 01-.136-1.288L0 14.468c.002-.745.06-1.49.174-2.23l.1-.573c.298-1.53.828-2.958 1.536-4.157l.209-.34c1.177-1.83 2.789-3.053 4.615-3.16L6.897 4zm-.033 2.615l-.201.01c-.83.083-1.606.673-2.252 1.577l-.138.199-.01.018c-.67 1.017-1.185 2.378-1.456 3.845l-.004.022a12.591 12.591 0 00-.207 2.254l.002.188c.004.18.017.36.04.54l.043.291c.092.503.257.908.486 1.208l.117.137c.303.323.698.492 1.17.492 1.1 0 1.796-.676 3.696-3.641l2.175-3.4.454-.701-.139-.198C9.11 7.3 8.084 6.616 6.864 6.616zm10.196-.552l-.176.007c-.635.048-1.223.359-1.82.933l-.196.198c-.439.462-.887 1.064-1.367 1.807l.266.398c.18.274.362.56.55.858l.293.475 1.396 2.335.695 1.114c.583.926 1.03 1.6 1.408 2.082l.213.262c.282.326.529.54.777.673l.102.05c.227.1.457.138.718.138.176.002.35-.023.518-.073.338-.104.61-.32.813-.637l.095-.163.077-.162c.194-.459.29-1.06.29-1.785l-.006-.449c-.08-2.871-.938-5.372-2.2-6.798l-.176-.189c-.67-.683-1.444-1.074-2.27-1.074z";
|
|
58
|
+
readonly evenOdd: true;
|
|
59
|
+
}];
|
|
60
|
+
};
|
|
61
|
+
readonly qwen: {
|
|
62
|
+
readonly viewBox: "0 0 24 24";
|
|
63
|
+
readonly paths: readonly [{
|
|
64
|
+
readonly d: "M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z";
|
|
65
|
+
readonly evenOdd: true;
|
|
66
|
+
}];
|
|
67
|
+
};
|
|
40
68
|
readonly google: {
|
|
41
69
|
readonly viewBox: "0 0 24 24";
|
|
42
70
|
readonly paths: readonly [{
|