@tulipfarm/sprout 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +183 -2
- package/dist/components/accordion.d.ts +28 -0
- package/dist/components/accordion.js +26 -0
- package/dist/components/alert-dialog.d.ts +60 -0
- package/dist/components/alert-dialog.js +69 -0
- package/dist/components/alert.d.ts +30 -0
- package/dist/components/alert.js +35 -0
- package/dist/components/aspect-ratio.d.ts +7 -0
- package/dist/components/aspect-ratio.js +6 -0
- package/dist/components/attachment.d.ts +118 -0
- package/dist/components/attachment.js +285 -0
- package/dist/components/avatar.d.ts +32 -0
- package/dist/components/avatar.js +73 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.js +27 -0
- package/dist/components/brand-icon.d.ts +28 -0
- package/dist/components/brand-icon.js +82 -0
- package/dist/components/breadcrumb.d.ts +29 -0
- package/dist/components/breadcrumb.js +44 -0
- package/dist/components/button-group.d.ts +28 -0
- package/dist/components/button-group.js +36 -0
- package/dist/components/button.d.ts +79 -0
- package/dist/components/button.js +93 -0
- package/dist/components/calendar.d.ts +27 -0
- package/dist/components/calendar.js +107 -0
- package/dist/components/canvas.d.ts +78 -0
- package/dist/components/canvas.js +180 -0
- package/dist/components/card.d.ts +43 -0
- package/dist/components/card.js +42 -0
- package/dist/components/carousel.d.ts +57 -0
- package/dist/components/carousel.js +422 -0
- package/dist/components/chart.d.ts +112 -0
- package/dist/components/chart.js +172 -0
- package/dist/components/chat-confirmation.d.ts +42 -0
- package/dist/components/chat-confirmation.js +62 -0
- package/dist/components/chat-input.d.ts +94 -0
- package/dist/components/chat-input.js +279 -0
- package/dist/components/chat-plan.d.ts +21 -0
- package/dist/components/chat-plan.js +35 -0
- package/dist/components/chat-sources.d.ts +64 -0
- package/dist/components/chat-sources.js +66 -0
- package/dist/components/checkbox.d.ts +16 -0
- package/dist/components/checkbox.js +17 -0
- package/dist/components/choice-list.d.ts +53 -0
- package/dist/components/choice-list.js +36 -0
- package/dist/components/code-block.d.ts +35 -0
- package/dist/components/code-block.js +64 -0
- package/dist/components/collapsible.d.ts +24 -0
- package/dist/components/collapsible.js +27 -0
- package/dist/components/combobox.d.ts +95 -0
- package/dist/components/combobox.js +106 -0
- package/dist/components/command.d.ts +94 -0
- package/dist/components/command.js +414 -0
- package/dist/components/context-menu.d.ts +57 -0
- package/dist/components/context-menu.js +60 -0
- package/dist/components/dialog.d.ts +60 -0
- package/dist/components/dialog.js +79 -0
- package/dist/components/direction.d.ts +6 -0
- package/dist/components/direction.js +6 -0
- package/dist/components/dropdown-menu.d.ts +62 -0
- package/dist/components/dropdown-menu.js +59 -0
- package/dist/components/field.d.ts +117 -0
- package/dist/components/field.js +150 -0
- package/dist/components/file-explorer.d.ts +27 -0
- package/dist/components/file-explorer.js +300 -0
- package/dist/components/file-viewer.d.ts +137 -0
- package/dist/components/file-viewer.js +400 -0
- package/dist/components/form.d.ts +87 -0
- package/dist/components/form.js +55 -0
- package/dist/components/glass-banner.d.ts +27 -0
- package/dist/components/glass-banner.js +116 -0
- package/dist/components/gradient-banner.d.ts +37 -0
- package/dist/components/gradient-banner.js +43 -0
- package/dist/components/hover-card.d.ts +27 -0
- package/dist/components/hover-card.js +26 -0
- package/dist/components/input-group.d.ts +68 -0
- package/dist/components/input-group.js +88 -0
- package/dist/components/input-otp.d.ts +38 -0
- package/dist/components/input-otp.js +43 -0
- package/dist/components/input.d.ts +16 -0
- package/dist/components/input.js +18 -0
- package/dist/components/kbd.d.ts +12 -0
- package/dist/components/kbd.js +11 -0
- package/dist/components/label.d.ts +8 -0
- package/dist/components/label.js +10 -0
- package/dist/components/mention-list.d.ts +54 -0
- package/dist/components/mention-list.js +31 -0
- package/dist/components/menubar.d.ts +64 -0
- package/dist/components/menubar.js +68 -0
- package/dist/components/message-actions.d.ts +34 -0
- package/dist/components/message-actions.js +68 -0
- package/dist/components/message-response.d.ts +20 -0
- package/dist/components/message-response.js +46 -0
- package/dist/components/message-scroller.d.ts +71 -0
- package/dist/components/message-scroller.js +217 -0
- package/dist/components/message.d.ts +49 -0
- package/dist/components/message.js +59 -0
- package/dist/components/navigation-menu.d.ts +45 -0
- package/dist/components/navigation-menu.js +54 -0
- package/dist/components/notice-banner.d.ts +14 -0
- package/dist/components/notice-banner.js +11 -0
- package/dist/components/page-content.d.ts +7 -0
- package/dist/components/page-content.js +9 -0
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/pagination.d.ts +29 -0
- package/dist/components/pagination.js +29 -0
- package/dist/components/plan-outline.d.ts +60 -0
- package/dist/components/plan-outline.js +108 -0
- package/dist/components/popover.d.ts +34 -0
- package/dist/components/popover.js +35 -0
- package/dist/components/progress.d.ts +27 -0
- package/dist/components/progress.js +35 -0
- package/dist/components/prompt-list.d.ts +23 -0
- package/dist/components/prompt-list.js +11 -0
- package/dist/components/questionnaire.d.ts +148 -0
- package/dist/components/questionnaire.js +1002 -0
- package/dist/components/radio-group.d.ts +16 -0
- package/dist/components/radio-group.js +21 -0
- package/dist/components/reasoning-effort.d.ts +24 -0
- package/dist/components/reasoning-effort.js +269 -0
- package/dist/components/resizable.d.ts +67 -0
- package/dist/components/resizable.js +536 -0
- package/dist/components/result-card.d.ts +58 -0
- package/dist/components/result-card.js +88 -0
- package/dist/components/review-card.d.ts +62 -0
- package/dist/components/review-card.js +49 -0
- package/dist/components/rich-text-editor.d.ts +51 -0
- package/dist/components/rich-text-editor.js +248 -0
- package/dist/components/schema-diagram.d.ts +52 -0
- package/dist/components/schema-diagram.js +226 -0
- package/dist/components/scroll-area.d.ts +22 -0
- package/dist/components/scroll-area.js +19 -0
- package/dist/components/search-dialog.d.ts +103 -0
- package/dist/components/search-dialog.js +65 -0
- package/dist/components/search-field.d.ts +17 -0
- package/dist/components/search-field.js +27 -0
- package/dist/components/select.d.ts +57 -0
- package/dist/components/select.js +57 -0
- package/dist/components/selection-slot.d.ts +18 -0
- package/dist/components/selection-slot.js +53 -0
- package/dist/components/separator.d.ts +48 -0
- package/dist/components/separator.js +54 -0
- package/dist/components/settings-list.d.ts +26 -0
- package/dist/components/settings-list.js +17 -0
- package/dist/components/sheet.d.ts +70 -0
- package/dist/components/sheet.js +77 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.js +187 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +32 -0
- package/dist/components/slider.d.ts +17 -0
- package/dist/components/slider.js +24 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +43 -0
- package/dist/components/state-panel-samples.d.ts +25 -0
- package/dist/components/state-panel-samples.js +423 -0
- package/dist/components/state-panel.d.ts +35 -0
- package/dist/components/state-panel.js +29 -0
- package/dist/components/state-sample.d.ts +63 -0
- package/dist/components/state-sample.js +42 -0
- package/dist/components/switch.d.ts +12 -0
- package/dist/components/switch.js +14 -0
- package/dist/components/table-nuqs.d.ts +16 -0
- package/dist/components/table-nuqs.js +59 -0
- package/dist/components/table.d.ts +120 -0
- package/dist/components/table.js +663 -0
- package/dist/components/tabs.d.ts +37 -0
- package/dist/components/tabs.js +36 -0
- package/dist/components/textarea.d.ts +18 -0
- package/dist/components/textarea.js +19 -0
- package/dist/components/toast.d.ts +45 -0
- package/dist/components/toast.js +83 -0
- package/dist/components/toggle-group.d.ts +30 -0
- package/dist/components/toggle-group.js +39 -0
- package/dist/components/toggle.d.ts +27 -0
- package/dist/components/toggle.js +21 -0
- package/dist/components/tool-activity.d.ts +79 -0
- package/dist/components/tool-activity.js +226 -0
- package/dist/components/toolbar-status.d.ts +21 -0
- package/dist/components/toolbar-status.js +14 -0
- package/dist/components/tooltip.d.ts +31 -0
- package/dist/components/tooltip.js +99 -0
- package/dist/components/typed-text.d.ts +17 -0
- package/dist/components/typed-text.js +56 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +90 -0
- package/dist/internal/brand-glyphs.d.ts +370 -0
- package/dist/internal/brand-glyphs.js +262 -0
- package/dist/internal/classes.d.ts +3 -0
- package/dist/internal/classes.js +20 -0
- package/dist/internal/code-tokens.d.ts +4 -0
- package/dist/internal/code-tokens.js +34 -0
- package/dist/internal/fieldset.d.ts +6 -0
- package/dist/internal/fieldset.js +38 -0
- package/dist/internal/file-kind.d.ts +4 -0
- package/dist/internal/file-kind.js +62 -0
- package/dist/internal/languages.d.ts +7 -0
- package/dist/internal/languages.js +1010 -0
- package/dist/internal/message-scroller.d.ts +102 -0
- package/dist/internal/message-scroller.js +637 -0
- package/dist/internal/pressable.d.ts +2 -0
- package/dist/internal/pressable.js +29 -0
- package/dist/internal/syntax.d.ts +14 -0
- package/dist/internal/syntax.js +137 -0
- package/dist/styles/accordion.css +102 -0
- package/dist/styles/alert-dialog.css +198 -0
- package/dist/styles/alert.css +149 -0
- package/dist/styles/aspect-ratio.css +8 -0
- package/dist/styles/attachment.css +700 -0
- package/dist/styles/avatar.css +186 -0
- package/dist/styles/badge.css +178 -0
- package/dist/styles/brand-icon.css +104 -0
- package/dist/styles/breadcrumb.css +121 -0
- package/dist/styles/button-group.css +113 -0
- package/dist/styles/button.css +380 -0
- package/dist/styles/calendar.css +295 -0
- package/dist/styles/canvas.css +313 -0
- package/dist/styles/card.css +146 -0
- package/dist/styles/carousel.css +107 -0
- package/dist/styles/chart.css +227 -0
- package/dist/styles/chat-confirmation.css +29 -0
- package/dist/styles/chat-input.css +240 -0
- package/dist/styles/chat-plan.css +109 -0
- package/dist/styles/chat-sources.css +191 -0
- package/dist/styles/checkbox.css +122 -0
- package/dist/styles/choice-list.css +343 -0
- package/dist/styles/code-block.css +211 -0
- package/dist/styles/collapsible.css +72 -0
- package/dist/styles/combobox.css +347 -0
- package/dist/styles/command.css +233 -0
- package/dist/styles/context-menu.css +151 -0
- package/dist/styles/dialog.css +237 -0
- package/dist/styles/dropdown-menu.css +150 -0
- package/dist/styles/field.css +340 -0
- package/dist/styles/file-explorer.css +517 -0
- package/dist/styles/file-viewer.css +375 -0
- package/dist/styles/form.css +155 -0
- package/dist/styles/glass-banner.css +308 -0
- package/dist/styles/gradient-banner.css +256 -0
- package/dist/styles/hover-card.css +95 -0
- package/dist/styles/index.css +268 -0
- package/dist/styles/input-group.css +285 -0
- package/dist/styles/input-otp.css +138 -0
- package/dist/styles/input.css +98 -0
- package/dist/styles/kbd.css +59 -0
- package/dist/styles/label.css +89 -0
- package/dist/styles/mention-list.css +166 -0
- package/dist/styles/menubar.css +226 -0
- package/dist/styles/message-actions.css +83 -0
- package/dist/styles/message-response.css +176 -0
- package/dist/styles/message-scroller.css +136 -0
- package/dist/styles/message.css +310 -0
- package/dist/styles/navigation-menu.css +291 -0
- package/dist/styles/notice-banner.css +68 -0
- package/dist/styles/page-content.css +21 -0
- package/dist/styles/page-header.css +156 -0
- package/dist/styles/pagination.css +72 -0
- package/dist/styles/plan-outline.css +393 -0
- package/dist/styles/popover.css +140 -0
- package/dist/styles/progress.css +93 -0
- package/dist/styles/prompt-list.css +165 -0
- package/dist/styles/questionnaire.css +308 -0
- package/dist/styles/radio-group.css +119 -0
- package/dist/styles/reasoning-effort.css +157 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/result-card.css +215 -0
- package/dist/styles/review-card.css +235 -0
- package/dist/styles/rich-text-editor.css +169 -0
- package/dist/styles/schema-diagram.css +419 -0
- package/dist/styles/scroll-area.css +77 -0
- package/dist/styles/search-dialog.css +280 -0
- package/dist/styles/search-field.css +17 -0
- package/dist/styles/select.css +285 -0
- package/dist/styles/selection-slot.css +31 -0
- package/dist/styles/separator.css +206 -0
- package/dist/styles/settings-list.css +81 -0
- package/dist/styles/sheet.css +361 -0
- package/dist/styles/sidebar.css +363 -0
- package/dist/styles/skeleton.css +80 -0
- package/dist/styles/slider.css +142 -0
- package/dist/styles/spinner.css +39 -0
- package/dist/styles/state-panel-samples.css +41 -0
- package/dist/styles/state-panel.css +173 -0
- package/dist/styles/state-sample.css +285 -0
- package/dist/styles/switch.css +128 -0
- package/dist/styles/table.css +604 -0
- package/dist/styles/tabs.css +223 -0
- package/dist/styles/textarea.css +77 -0
- package/dist/styles/themes/chatgpt.css +125 -0
- package/dist/styles/themes/claude.css +125 -0
- package/dist/styles/themes/cloudflare.css +125 -0
- package/dist/styles/themes/discord.css +125 -0
- package/dist/styles/themes/github.css +125 -0
- package/dist/styles/themes/grok.css +125 -0
- package/dist/styles/themes/linear.css +126 -0
- package/dist/styles/themes/neutral.css +125 -0
- package/dist/styles/themes/notion.css +125 -0
- package/dist/styles/themes/perplexity.css +125 -0
- package/dist/styles/themes/raycast.css +125 -0
- package/dist/styles/themes/spotify.css +125 -0
- package/dist/styles/themes/stripe.css +125 -0
- package/dist/styles/themes/supabase.css +125 -0
- package/dist/styles/themes/tulipfarm.css +115 -0
- package/dist/styles/themes/vercel.css +126 -0
- package/dist/styles/toast.css +197 -0
- package/dist/styles/toggle-group.css +161 -0
- package/dist/styles/toggle.css +119 -0
- package/dist/styles/tool-activity.css +396 -0
- package/dist/styles/toolbar-status.css +52 -0
- package/dist/styles/tooltip.css +122 -0
- package/dist/styles/typed-text.css +17 -0
- package/package.json +452 -4
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { type FileKind } from "../internal/file-kind.js";
|
|
3
|
+
import { type DialogContentProps } from "./dialog.js";
|
|
4
|
+
export type ViewableFileSource = {
|
|
5
|
+
kind: "local";
|
|
6
|
+
file: File;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "remote";
|
|
9
|
+
url: string;
|
|
10
|
+
};
|
|
11
|
+
export interface ViewableFile {
|
|
12
|
+
name: string;
|
|
13
|
+
mediaType: string;
|
|
14
|
+
source: ViewableFileSource;
|
|
15
|
+
}
|
|
16
|
+
export type ViewableFileKind = FileKind;
|
|
17
|
+
export interface FileSheetRendererProps {
|
|
18
|
+
name: string;
|
|
19
|
+
bytes: ArrayBuffer;
|
|
20
|
+
onRetry: () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface FileOfficeRendererProps extends FileSheetRendererProps {
|
|
23
|
+
kind: "document" | "slides";
|
|
24
|
+
}
|
|
25
|
+
export interface FileMarkdownRendererProps {
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Renderers for formats that need a heavy parser. Without one, sheets and
|
|
30
|
+
* Office files show a download prompt and Markdown shows its plain text.
|
|
31
|
+
* Each renderer runs inside Suspense with `LoadingFile` as the fallback,
|
|
32
|
+
* so a lazy component works as is.
|
|
33
|
+
*/
|
|
34
|
+
export interface FileRenderers {
|
|
35
|
+
markdown?: ((props: FileMarkdownRendererProps) => ReactNode) | undefined;
|
|
36
|
+
sheet?: ((props: FileSheetRendererProps) => ReactNode) | undefined;
|
|
37
|
+
office?: ((props: FileOfficeRendererProps) => ReactNode) | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface FileRenderersProviderProps {
|
|
40
|
+
renderers: FileRenderers;
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
/** Registers file renderers once for every viewer below it. */
|
|
44
|
+
export declare function FileRenderersProvider({ renderers, children, }: FileRenderersProviderProps): import("react").JSX.Element;
|
|
45
|
+
export declare const maxViewableFileBytes: number;
|
|
46
|
+
export declare function viewableFileKind(name: string, mediaType: string): ViewableFileKind;
|
|
47
|
+
export interface GoogleDocumentLink {
|
|
48
|
+
url: string;
|
|
49
|
+
embedUrl: string;
|
|
50
|
+
mediaType: string;
|
|
51
|
+
}
|
|
52
|
+
/** Recognizes a docs.google.com Docs, Sheets, or Slides link. */
|
|
53
|
+
export declare function googleDocument(url: string): GoogleDocumentLink | null;
|
|
54
|
+
/** Reads the document link from a .gdoc, .gsheet, or .gslides shortcut. */
|
|
55
|
+
export declare function googleShortcut(text: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* The type to give a same-origin blob URL. Stored media types are
|
|
58
|
+
* untrusted, so anything a browser could run as a document, including SVG,
|
|
59
|
+
* becomes `application/octet-stream`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function renderableType(name: string, mediaType: string): string;
|
|
62
|
+
interface FileViewerBaseProps {
|
|
63
|
+
open: boolean;
|
|
64
|
+
onOpenChange: (open: boolean) => void;
|
|
65
|
+
renderers?: FileRenderers | undefined;
|
|
66
|
+
/** Where focus goes on close. Defaults to the element that had focus when the viewer opened. */
|
|
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;
|
|
85
|
+
}
|
|
86
|
+
interface SingleFileProps {
|
|
87
|
+
file: ViewableFile | null;
|
|
88
|
+
files?: undefined;
|
|
89
|
+
index?: undefined;
|
|
90
|
+
onIndexChange?: undefined;
|
|
91
|
+
}
|
|
92
|
+
interface FileSetProps {
|
|
93
|
+
file?: undefined;
|
|
94
|
+
/** Files to step through with Previous, Next, and the arrow keys. */
|
|
95
|
+
files: readonly ViewableFile[];
|
|
96
|
+
index: number;
|
|
97
|
+
onIndexChange: (index: number) => void;
|
|
98
|
+
}
|
|
99
|
+
export type FileViewerProps = FileViewerBaseProps & (SingleFileProps | FileSetProps);
|
|
100
|
+
/** A full-screen dialog that opens a local or linked file, or steps through several. */
|
|
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;
|
|
102
|
+
export interface ViewableFileContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
103
|
+
file: ViewableFile;
|
|
104
|
+
renderers?: FileRenderers | undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Loads a local or linked file and shows it inline, with reload and download. */
|
|
107
|
+
export declare function ViewableFileContent({ file, renderers, className, ...props }: ViewableFileContentProps): import("react").JSX.Element;
|
|
108
|
+
export interface FileContentProps {
|
|
109
|
+
name: string;
|
|
110
|
+
mediaType: string;
|
|
111
|
+
bytes: ArrayBuffer;
|
|
112
|
+
/** A blob URL for `bytes`, typed with `renderableType`. */
|
|
113
|
+
url: string;
|
|
114
|
+
onRetry: () => void;
|
|
115
|
+
renderers?: FileRenderers | undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Shows bytes you already loaded: images, PDF, text, JSON, Markdown, Google shortcuts, and more. */
|
|
118
|
+
export declare function FileContent({ name, mediaType, bytes, url, onRetry, renderers: own, }: FileContentProps): import("react").JSX.Element;
|
|
119
|
+
export type LoadingFileProps = HTMLAttributes<HTMLDivElement>;
|
|
120
|
+
export declare function LoadingFile({ className, ...props }: LoadingFileProps): import("react").JSX.Element;
|
|
121
|
+
export interface FileErrorProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
122
|
+
message: ReactNode;
|
|
123
|
+
onRetry: () => void;
|
|
124
|
+
}
|
|
125
|
+
export declare function FileError({ message, onRetry, className, ...props }: FileErrorProps): import("react").JSX.Element;
|
|
126
|
+
/** Whether `FilePreview` can show this file: images, text, code, JSON, and Markdown, but not Google shortcuts. */
|
|
127
|
+
export declare function isPreviewableFile(file: ViewableFile): boolean;
|
|
128
|
+
export interface FilePreviewProps extends HTMLAttributes<HTMLDivElement> {
|
|
129
|
+
file: ViewableFile;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A small, non-interactive peek at a file: the image, or the first lines
|
|
133
|
+
* of text and code with syntax colors. Made for hover cards and thumbnails.
|
|
134
|
+
* Renders nothing for files `isPreviewableFile` rejects.
|
|
135
|
+
*/
|
|
136
|
+
export declare function FilePreview({ file, className, ...props }: FilePreviewProps): import("react").JSX.Element | null;
|
|
137
|
+
export {};
|