@tangle-network/ui 11.11.8 → 11.13.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/CHANGELOG.md +24 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/auth/auth.d.ts +53 -0
- package/dist/{auth-DOETfnd9.js → auth/auth.js} +7 -37
- package/dist/auth/login-layout.d.ts +20 -0
- package/dist/auth/login-layout.js +36 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +2 -1
- package/dist/chat/agent-timeline.d.ts +83 -0
- package/dist/chat/agent-timeline.js +236 -0
- package/dist/chat/chat-container.d.ts +51 -0
- package/dist/chat/chat-container.js +309 -0
- package/dist/chat/chat-message.d.ts +22 -0
- package/dist/chat/chat-message.js +38 -0
- package/dist/chat/message-list.d.ts +32 -0
- package/dist/chat/message-list.js +37 -0
- package/dist/chat/thinking-indicator.d.ts +6 -0
- package/dist/chat/thinking-indicator.js +38 -0
- package/dist/chat/user-message.d.ts +21 -0
- package/dist/chat/user-message.js +42 -0
- package/dist/chat.d.ts +6 -1
- package/dist/chat.js +6 -1
- package/dist/editor/document-editor-pane.d.ts +37 -0
- package/dist/editor/document-editor-pane.js +199 -0
- package/dist/editor/editor-lazy.js +37 -0
- package/dist/editor/editor-loading.js +21 -0
- package/dist/editor/editor-peers.d.ts +6 -0
- package/dist/editor/editor-peers.js +155 -0
- package/dist/{document-editor-pane-DHM8DDhb.d.ts → editor/editor-provider.d.ts} +10 -47
- package/dist/editor/editor-provider.js +253 -0
- package/dist/editor/editor-toolbar.d.ts +11 -0
- package/dist/editor/editor-toolbar.js +106 -0
- package/dist/editor/markdown-conversion.js +23 -0
- package/dist/editor/markdown-document-editor.js +103 -0
- package/dist/editor/tiptap-editor.d.ts +45 -0
- package/dist/editor/tiptap-editor.js +271 -0
- package/dist/editor/use-editor.d.ts +67 -0
- package/dist/editor/use-editor.js +158 -0
- package/dist/editor.d.ts +6 -119
- package/dist/editor.js +5 -1
- package/dist/files/file-artifact-pane.d.ts +47 -0
- package/dist/files/file-artifact-pane.js +114 -0
- package/dist/files/file-format.d.ts +51 -0
- package/dist/{file-format-BgXbr-Ib.js → files/file-format.js} +1 -1
- package/dist/files/file-preview.d.ts +27 -0
- package/dist/files/file-preview.js +359 -0
- package/dist/files/file-tabs.d.ts +19 -0
- package/dist/files/file-tabs.js +48 -0
- package/dist/files/file-tree.d.ts +31 -0
- package/dist/files/file-tree.js +135 -0
- package/dist/files/rich-file-tree.d.ts +62 -0
- package/dist/files/rich-file-tree.js +122 -0
- package/dist/files.d.ts +6 -1
- package/dist/files.js +6 -2
- package/dist/hooks/use-auth.d.ts +40 -0
- package/dist/hooks/use-auth.js +91 -0
- package/dist/hooks/use-auto-scroll.d.ts +11 -0
- package/dist/hooks/use-auto-scroll.js +48 -0
- package/dist/hooks/use-dropdown-menu.d.ts +12 -0
- package/dist/hooks/use-dropdown-menu.js +31 -0
- package/dist/hooks/use-live-time.d.ts +7 -0
- package/dist/hooks/use-live-time.js +22 -0
- package/dist/hooks/use-realtime-session.d.ts +32 -0
- package/dist/hooks/use-realtime-session.js +235 -0
- package/dist/hooks/use-run-collapse-state.d.ts +13 -0
- package/dist/hooks/use-run-collapse-state.js +24 -0
- package/dist/hooks/use-run-groups.d.ts +11 -0
- package/dist/hooks/use-run-groups.js +85 -0
- package/dist/hooks/use-sdk-session.d.ts +59 -0
- package/dist/hooks/use-sdk-session.js +322 -0
- package/dist/hooks/use-sse-stream.d.ts +124 -0
- package/dist/hooks/use-sse-stream.js +229 -0
- package/dist/hooks/use-tool-call-stream.d.ts +23 -0
- package/dist/hooks/use-tool-call-stream.js +75 -0
- package/dist/hooks.d.ts +11 -49
- package/dist/hooks.js +10 -110
- package/dist/index.d.ts +99 -25
- package/dist/index.js +86 -36
- package/dist/lib/focus.d.ts +55 -0
- package/dist/{focus-BcE_SHqz.js → lib/focus.js} +1 -1
- package/dist/lib/utils.d.ts +4 -0
- package/dist/{utils-C_uf36nf.js → lib/utils.js} +1 -1
- package/dist/{code-block-B9miUPht.d.ts → markdown/code-block.d.ts} +5 -6
- package/dist/{code-block-B36KVUSN.js → markdown/code-block.js} +22 -47
- package/dist/{markdown-C0atSabr.d.ts → markdown/markdown.d.ts} +4 -5
- package/dist/{markdown-Db08KAYF.js → markdown/markdown.js} +3 -3
- package/dist/markdown.d.ts +2 -2
- package/dist/markdown.js +2 -2
- package/dist/{openui-artifact-renderer-fkBQaoLO.d.ts → openui/openui-artifact-renderer.d.ts} +20 -21
- package/dist/{openui-artifact-renderer-DG_i2lOR.js → openui/openui-artifact-renderer.js} +9 -7
- package/dist/{schema-BpW9rSSG.js → openui/schema-compiled.js} +2 -354
- package/dist/{schema-DAstk-J4.d.ts → openui/schema-data.d.ts} +3 -24
- package/dist/openui/schema-data.js +206 -0
- package/dist/openui-schema.d.ts +22 -3
- package/dist/openui-schema.js +138 -1
- package/dist/openui.d.ts +3 -2
- package/dist/openui.js +3 -2
- package/dist/{artifact-pane-BL_DFWNl.d.ts → primitives/artifact-pane.d.ts} +3 -4
- package/dist/{artifact-pane-DMXT9a8U.js → primitives/artifact-pane.js} +2 -2
- package/dist/primitives/avatar.d.ts +8 -0
- package/dist/primitives/avatar.js +25 -0
- package/dist/primitives/badge.d.ts +22 -0
- package/dist/primitives/badge.js +47 -0
- package/dist/{button-yoIv77e2.d.ts → primitives/button.d.ts} +2 -2
- package/dist/primitives/button.js +113 -0
- package/dist/primitives/card.d.ts +18 -0
- package/dist/primitives/card.js +52 -0
- package/dist/primitives/code-block.d.ts +6 -0
- package/dist/primitives/code-block.js +13 -0
- package/dist/primitives/dialog.d.ts +23 -0
- package/dist/primitives/dialog.js +59 -0
- package/dist/primitives/drop-zone.d.ts +23 -0
- package/dist/primitives/drop-zone.js +104 -0
- package/dist/primitives/dropdown-menu.d.ts +29 -0
- package/dist/{dropdown-menu-BpcLa5vF.js → primitives/dropdown-menu.js} +2 -23
- package/dist/primitives/empty-state.d.ts +11 -0
- package/dist/primitives/empty-state.js +32 -0
- package/dist/primitives/heading.d.ts +23 -0
- package/dist/primitives/heading.js +38 -0
- package/dist/primitives/input.d.ts +23 -0
- package/dist/primitives/input.js +110 -0
- package/dist/primitives/label.d.ts +6 -0
- package/dist/primitives/label.js +16 -0
- package/dist/primitives/metric-strip.d.ts +48 -0
- package/dist/primitives/metric-strip.js +52 -0
- package/dist/primitives/page-header.d.ts +25 -0
- package/dist/primitives/page-header.js +47 -0
- package/dist/primitives/page-shell.d.ts +14 -0
- package/dist/primitives/page-shell.js +17 -0
- package/dist/primitives/progress.d.ts +9 -0
- package/dist/primitives/progress.js +29 -0
- package/dist/primitives/segmented-control.d.ts +40 -0
- package/dist/primitives/segmented-control.js +57 -0
- package/dist/primitives/select.d.ts +15 -0
- package/dist/primitives/select.js +74 -0
- package/dist/primitives/sidebar-drop-zone.d.ts +21 -0
- package/dist/primitives/sidebar-drop-zone.js +88 -0
- package/dist/primitives/skeleton.d.ts +10 -0
- package/dist/primitives/skeleton.js +50 -0
- package/dist/primitives/stat-card.d.ts +16 -0
- package/dist/primitives/stat-card.js +60 -0
- package/dist/primitives/status-pill.d.ts +44 -0
- package/dist/primitives/status-pill.js +119 -0
- package/dist/primitives/switch.d.ts +6 -0
- package/dist/primitives/switch.js +16 -0
- package/dist/primitives/table.d.ts +22 -0
- package/dist/primitives/table.js +63 -0
- package/dist/primitives/tabs.d.ts +13 -0
- package/dist/{tabs-BVHR6NkO.js → primitives/tabs.js} +3 -3
- package/dist/primitives/terminal-display.d.ts +25 -0
- package/dist/primitives/terminal-display.js +109 -0
- package/dist/primitives/text-shimmer.d.ts +16 -0
- package/dist/{text-shimmer-BUUo6I-g.js → primitives/text-shimmer.js} +2 -2
- package/dist/primitives/theme-toggle.d.ts +11 -0
- package/dist/primitives/theme-toggle.js +154 -0
- package/dist/primitives/toast.d.ts +29 -0
- package/dist/primitives/toast.js +142 -0
- package/dist/primitives/toolbar.d.ts +55 -0
- package/dist/primitives/toolbar.js +37 -0
- package/dist/primitives/upload-progress.d.ts +23 -0
- package/dist/primitives/upload-progress.js +72 -0
- package/dist/primitives.d.ts +35 -5
- package/dist/primitives.js +35 -9
- package/dist/{index-COgqsFAQ.d.ts → redaction/redacted-document.d.ts} +6 -7
- package/dist/{redaction-CGGB2ENG.js → redaction/redacted-document.js} +2 -2
- package/dist/redaction.d.ts +1 -1
- package/dist/redaction.js +1 -1
- package/dist/run/expandable-content.js +41 -0
- package/dist/run/expanded-tool-detail.d.ts +12 -0
- package/dist/run/expanded-tool-detail.js +163 -0
- package/dist/run/inline-thinking-item.d.ts +11 -0
- package/dist/run/inline-thinking-item.js +60 -0
- package/dist/run/inline-tool-item.d.ts +25 -0
- package/dist/run/inline-tool-item.js +63 -0
- package/dist/run/run-group.d.ts +26 -0
- package/dist/{run-group-BWGeesgs.js → run/run-group.js} +9 -60
- package/dist/run/run-item-primitives.d.ts +5 -0
- package/dist/{run-ZsIKPA6J.js → run/run-item-primitives.js} +2 -2
- package/dist/run/run-row-shell.d.ts +53 -0
- package/dist/run/run-row-shell.js +114 -0
- package/dist/{tool-call-feed-BeB1_4pX.d.ts → run/tool-call-feed.d.ts} +7 -12
- package/dist/{tool-call-feed-BpmMLpPX.js → run/tool-call-feed.js} +4 -4
- package/dist/run/tool-call-step.d.ts +5 -0
- package/dist/run/tool-call-step.js +82 -0
- package/dist/run.d.ts +8 -2
- package/dist/run.js +7 -4
- package/dist/sdk-hooks.d.ts +8 -1
- package/dist/sdk-hooks.js +8 -2
- package/dist/stores/active-sessions-store.d.ts +83 -0
- package/dist/{active-sessions-store-Gt1R0rFG.js → stores/active-sessions-store.js} +1 -1
- package/dist/stores/chat-store.d.ts +14 -0
- package/dist/stores/chat-store.js +33 -0
- package/dist/stores.d.ts +3 -16
- package/dist/stores.js +2 -33
- package/dist/tool-previews/command-preview.d.ts +11 -0
- package/dist/tool-previews/command-preview.js +91 -0
- package/dist/tool-previews/diff-preview.d.ts +7 -0
- package/dist/tool-previews/diff-preview.js +76 -0
- package/dist/tool-previews/glob-results-preview.d.ts +7 -0
- package/dist/tool-previews/glob-results-preview.js +53 -0
- package/dist/tool-previews/grep-results-preview.d.ts +7 -0
- package/dist/tool-previews/grep-results-preview.js +99 -0
- package/dist/tool-previews/preview-primitives.js +58 -0
- package/dist/tool-previews/question-preview.d.ts +7 -0
- package/dist/tool-previews/question-preview.js +63 -0
- package/dist/tool-previews/web-search-preview.d.ts +7 -0
- package/dist/tool-previews/web-search-preview.js +78 -0
- package/dist/tool-previews/write-file-preview.d.ts +11 -0
- package/dist/tool-previews/write-file-preview.js +52 -0
- package/dist/tool-previews.d.ts +7 -1
- package/dist/tool-previews.js +7 -1
- package/dist/{branding-26d5ARIr.d.ts → types/branding.d.ts} +2 -3
- package/dist/{message-vhUI0c5G.d.ts → types/message.d.ts} +2 -3
- package/dist/{parts-5mhy9yJ3.d.ts → types/parts.d.ts} +8 -9
- package/dist/{run-OyWmIDqu.d.ts → types/run.d.ts} +9 -10
- package/dist/{tool-display-DgmL5rIU.d.ts → types/tool-display.d.ts} +5 -6
- package/dist/types.d.ts +5 -5
- package/dist/utils/copy-text.d.ts +7 -0
- package/dist/{utils-Bl_CZ0Qp.js → utils/copy-text.js} +1 -11
- package/dist/utils/format.d.ts +22 -0
- package/dist/{format-DfcLWqMa.js → utils/format.js} +1 -1
- package/dist/utils/time-ago.d.ts +3 -0
- package/dist/utils/time-ago.js +11 -0
- package/dist/utils/tool-display.d.ts +16 -0
- package/dist/{tool-display-BAuAVa43.js → utils/tool-display.js} +1 -1
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +6 -5
- package/package.json +2 -2
- package/src/editor/document-editor-pane.tsx +4 -1
- package/src/files/file-artifact-pane.test.tsx +23 -0
- package/src/files/file-artifact-pane.tsx +3 -0
- package/src/files/rich-file-tree.stories.tsx +1 -1
- package/src/files/rich-file-tree.tsx +12 -9
- package/src/markdown/code-block.test.tsx +46 -2
- package/src/markdown/code-block.tsx +23 -60
- package/src/markdown/markdown.test.tsx +3 -2
- package/src/primitives/button.stories.tsx +37 -1
- package/src/primitives/button.test.tsx +230 -0
- package/src/primitives/button.tsx +62 -14
- package/src/primitives/card-anatomy.stories.tsx +51 -0
- package/src/primitives/card.test.tsx +71 -0
- package/src/primitives/card.tsx +25 -13
- package/src/primitives/heading.stories.tsx +35 -0
- package/src/primitives/heading.test.tsx +56 -0
- package/src/primitives/heading.tsx +76 -0
- package/src/primitives/index.ts +8 -0
- package/src/primitives/input.stories.tsx +28 -0
- package/src/primitives/input.test.tsx +161 -0
- package/src/primitives/input.tsx +34 -28
- package/src/primitives/page-header.stories.tsx +51 -0
- package/src/primitives/page-header.test.tsx +48 -0
- package/src/primitives/page-header.tsx +35 -35
- package/src/primitives/page-shell.stories.tsx +89 -0
- package/src/primitives/page-shell.test.tsx +24 -0
- package/src/primitives/page-shell.tsx +26 -0
- package/src/primitives/table-wrapper.stories.tsx +54 -0
- package/src/primitives/table.test.tsx +72 -0
- package/src/primitives/table.tsx +33 -12
- package/src/primitives/theme-toggle.test.tsx +167 -0
- package/src/primitives/theme-toggle.tsx +111 -39
- package/dist/active-sessions-store-nl70ZnMC.d.ts +0 -84
- package/dist/button-C87nEe2R.js +0 -78
- package/dist/chat-DDvF2oGQ.js +0 -672
- package/dist/diff-preview-BcK3nRXb.js +0 -533
- package/dist/document-editor-pane-CsFbH2qo.js +0 -1279
- package/dist/files--Unn___t.js +0 -752
- package/dist/index-2UntyjBb.d.ts +0 -52
- package/dist/index-BgUHilZm.d.ts +0 -108
- package/dist/index-CPDET_s1.d.ts +0 -549
- package/dist/index-Cg6gYAqh.d.ts +0 -73
- package/dist/index-Cs7j8V7l.d.ts +0 -203
- package/dist/index-CyGSLGVF.d.ts +0 -234
- package/dist/index-DjKxO357.d.ts +0 -128
- package/dist/primitives-Bx59fT-U.js +0 -1239
- package/dist/table-Bss73DhB.js +0 -146
- package/dist/tool-call-step-DNebqhmX.js +0 -434
- package/dist/use-auto-scroll-C5Pjeqon.js +0 -153
- package/dist/use-sse-stream-DjqRuDft.js +0 -884
- package/dist/use-sse-stream-Dm6R5YWz.d.ts +0 -282
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { focusRing } from "../lib/focus.js";
|
|
2
|
+
import { ArtifactPane } from "../primitives/artifact-pane.js";
|
|
3
|
+
import { detectFileFormat } from "./file-format.js";
|
|
4
|
+
import { FilePreview } from "./file-preview.js";
|
|
5
|
+
import { FileTabs } from "./file-tabs.js";
|
|
6
|
+
import { Suspense, lazy } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Download, X } from "lucide-react";
|
|
9
|
+
//#region src/files/file-artifact-pane.tsx
|
|
10
|
+
const LazyDocumentEditorPane = lazy(async () => {
|
|
11
|
+
return { default: (await import("../editor/document-editor-pane.js")).DocumentEditorPane };
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* FileArtifactPane — opinionated artifact frame for file previews with tabs and
|
|
15
|
+
* header actions. The body is `FilePreview`, so every kind it renders (image,
|
|
16
|
+
* PDF, video, audio, CSV, spreadsheet, markdown, code, text) renders here too;
|
|
17
|
+
* `onDownload` drives both the header button and the body's download cards.
|
|
18
|
+
*/
|
|
19
|
+
function FileArtifactPane({ filename, content, blobUrl, mimeType, size, onClose, onDownload, urlTransform, path, tabs = [], activeTabId, onTabSelect, onTabClose, eyebrow = "Artifact", meta, toolbar, footer, className, headerClassName, hideTitleBlock, editor }) {
|
|
20
|
+
const paneTabs = tabs.length > 0 && onTabSelect && onTabClose ? /* @__PURE__ */ jsx(FileTabs, {
|
|
21
|
+
tabs,
|
|
22
|
+
activeId: activeTabId,
|
|
23
|
+
onSelect: onTabSelect,
|
|
24
|
+
onClose: onTabClose
|
|
25
|
+
}) : void 0;
|
|
26
|
+
const isEditableMarkdown = (detectFileFormat(filename, mimeType) === "markdown" || (path ? detectFileFormat(path, mimeType) === "markdown" : false)) && editor?.enabled;
|
|
27
|
+
const headerActions = onDownload || onClose ? /* @__PURE__ */ jsxs(Fragment$1, { children: [onDownload && /* @__PURE__ */ jsx("button", {
|
|
28
|
+
type: "button",
|
|
29
|
+
"aria-label": `Download ${filename}`,
|
|
30
|
+
onClick: onDownload,
|
|
31
|
+
className: `rounded-[var(--radius-sm)] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
32
|
+
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
33
|
+
}), onClose && /* @__PURE__ */ jsx("button", {
|
|
34
|
+
type: "button",
|
|
35
|
+
"aria-label": `Close ${filename}`,
|
|
36
|
+
onClick: onClose,
|
|
37
|
+
className: `rounded-[var(--radius-sm)] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
38
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
39
|
+
})] }) : void 0;
|
|
40
|
+
if (isEditableMarkdown) return /* @__PURE__ */ jsx(Suspense, {
|
|
41
|
+
fallback: /* @__PURE__ */ jsx(ArtifactPane, {
|
|
42
|
+
eyebrow,
|
|
43
|
+
title: filename,
|
|
44
|
+
subtitle: path,
|
|
45
|
+
meta,
|
|
46
|
+
toolbar,
|
|
47
|
+
footer,
|
|
48
|
+
className,
|
|
49
|
+
tabs: paneTabs,
|
|
50
|
+
headerActions,
|
|
51
|
+
headerClassName,
|
|
52
|
+
hideTitleBlock,
|
|
53
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
54
|
+
className: "flex min-h-[12rem] items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-border bg-muted text-sm text-muted-foreground",
|
|
55
|
+
children: "Loading editor…"
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
children: /* @__PURE__ */ jsx(LazyDocumentEditorPane, {
|
|
59
|
+
eyebrow,
|
|
60
|
+
title: filename,
|
|
61
|
+
subtitle: path,
|
|
62
|
+
meta,
|
|
63
|
+
toolbar,
|
|
64
|
+
footer,
|
|
65
|
+
className,
|
|
66
|
+
tabs: paneTabs,
|
|
67
|
+
headerActions,
|
|
68
|
+
headerClassName,
|
|
69
|
+
hideTitleBlock,
|
|
70
|
+
markdown: content ?? "",
|
|
71
|
+
savedMarkdown: editor.savedContent,
|
|
72
|
+
urlTransform,
|
|
73
|
+
mode: editor.mode,
|
|
74
|
+
defaultMode: editor.defaultMode,
|
|
75
|
+
onModeChange: editor.onModeChange,
|
|
76
|
+
backend: editor.backend,
|
|
77
|
+
collaboration: editor.collaboration,
|
|
78
|
+
placeholder: editor.placeholder,
|
|
79
|
+
autoFocus: editor.autoFocus,
|
|
80
|
+
readOnly: editor.readOnly,
|
|
81
|
+
saving: editor.saving,
|
|
82
|
+
saveLabel: editor.saveLabel,
|
|
83
|
+
onChange: editor.onChange,
|
|
84
|
+
onSave: editor.onSave,
|
|
85
|
+
previewClassName: editor.previewClassName,
|
|
86
|
+
editorClassName: editor.editorClassName
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
return /* @__PURE__ */ jsx(ArtifactPane, {
|
|
90
|
+
eyebrow,
|
|
91
|
+
title: filename,
|
|
92
|
+
subtitle: path,
|
|
93
|
+
meta,
|
|
94
|
+
toolbar,
|
|
95
|
+
footer,
|
|
96
|
+
className,
|
|
97
|
+
tabs: paneTabs,
|
|
98
|
+
headerActions,
|
|
99
|
+
headerClassName,
|
|
100
|
+
hideTitleBlock,
|
|
101
|
+
children: /* @__PURE__ */ jsx(FilePreview, {
|
|
102
|
+
filename,
|
|
103
|
+
content,
|
|
104
|
+
blobUrl,
|
|
105
|
+
mimeType,
|
|
106
|
+
size,
|
|
107
|
+
urlTransform,
|
|
108
|
+
onDownload,
|
|
109
|
+
hideHeader: true
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { FileArtifactPane };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/files/file-format.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared file-format detection for every file surface — the preview pane, the
|
|
4
|
+
* artifact pane, and code rendering. Centralising extension/MIME → format logic
|
|
5
|
+
* keeps chat and artifact views consistent and avoids the same mapping drifting
|
|
6
|
+
* across components.
|
|
7
|
+
*/
|
|
8
|
+
export type FileFormat = "pdf" | "image" | "video" | "audio" | "csv" | "spreadsheet" | "code" | "json" | "yaml" | "markdown" | "text" | "unknown";
|
|
9
|
+
/**
|
|
10
|
+
* How a file surface renders a file. Every format maps to its own renderer;
|
|
11
|
+
* `unknown` becomes `binary`, which renders string content as text and offers a
|
|
12
|
+
* download for anything else.
|
|
13
|
+
*/
|
|
14
|
+
export type FilePreviewKind = Exclude<FileFormat, "unknown"> | "binary";
|
|
15
|
+
/**
|
|
16
|
+
* Lowercased trailing extension. Returns "" for a name with no extension
|
|
17
|
+
* ("README", "json" → ""), and the post-dot name for a dotfile (".bashrc" →
|
|
18
|
+
* "bashrc"). Directory components are ignored so dots in a directory name don't
|
|
19
|
+
* leak in ("my.config/file" → "").
|
|
20
|
+
*/
|
|
21
|
+
export declare function fileExtension(filename: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a filename + optional MIME type to the renderer format. A specific,
|
|
24
|
+
* authoritative MIME type wins over the extension; otherwise the extension
|
|
25
|
+
* decides; a generic text/plain payload is the final fallback.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectFileFormat(filename: string, mimeType?: string): FileFormat;
|
|
28
|
+
/**
|
|
29
|
+
* The preview renderer for a file, from its name and optional MIME type only.
|
|
30
|
+
* Same precedence as `detectFileFormat` (MIME outranks extension); a file that
|
|
31
|
+
* matches nothing is `binary`. Content is not an input: the preview renders a
|
|
32
|
+
* `binary` file's string content as text and offers a download otherwise.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveFilePreviewKind(filename: string, mimeType?: string): FilePreviewKind;
|
|
35
|
+
/** Human-facing label for a detected format or preview kind. */
|
|
36
|
+
export declare function getFormatLabel(format: FileFormat | FilePreviewKind): string;
|
|
37
|
+
/**
|
|
38
|
+
* Map a filename (or path) to a highlight.js language id for CodeBlock. Returns
|
|
39
|
+
* undefined when there is no confident mapping; CodeBlock then renders themed
|
|
40
|
+
* monospace and lets the highlighter auto-detect, so callers never need a
|
|
41
|
+
* bespoke language table.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSyntaxLanguage(filename: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Highlight language for a file already classified as a code-like format.
|
|
46
|
+
* `json`/`yaml` are their own highlight language even when detected purely from
|
|
47
|
+
* a MIME type on an extensionless file (where the extension can't reveal it);
|
|
48
|
+
* any other code format keys off the extension.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getCodeLanguage(filename: string, format: FileFormat | FilePreviewKind): string | undefined;
|
|
51
|
+
//#endregion
|
|
@@ -157,4 +157,4 @@ function getCodeLanguage(filename, format) {
|
|
|
157
157
|
return getSyntaxLanguage(filename);
|
|
158
158
|
}
|
|
159
159
|
//#endregion
|
|
160
|
-
export {
|
|
160
|
+
export { detectFileFormat, fileExtension, getCodeLanguage, getFormatLabel, getSyntaxLanguage, resolveFilePreviewKind };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UrlTransform } from "react-markdown";
|
|
2
|
+
//#region src/files/file-preview.d.ts
|
|
3
|
+
export interface FilePreviewProps {
|
|
4
|
+
filename: string;
|
|
5
|
+
content?: string;
|
|
6
|
+
/** Object URL or remote URL for binary content: images, PDF, video, audio. */
|
|
7
|
+
blobUrl?: string;
|
|
8
|
+
mimeType?: string;
|
|
9
|
+
/** Size in bytes; download cards show it when known. */
|
|
10
|
+
size?: number;
|
|
11
|
+
/** Transform Markdown links and images through the shared URL policy. */
|
|
12
|
+
urlTransform?: UrlTransform;
|
|
13
|
+
onClose?: () => void;
|
|
14
|
+
onDownload?: () => void;
|
|
15
|
+
hideHeader?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Rows rendered by the CSV preview; the count line reports the total. */
|
|
19
|
+
export declare const CSV_PREVIEW_ROW_LIMIT = 500;
|
|
20
|
+
/**
|
|
21
|
+
* Split CSV text into rows of cells (RFC 4180): a quoted cell may hold commas,
|
|
22
|
+
* line breaks, and doubled quotes. Unquoted cells are trimmed; blank lines and
|
|
23
|
+
* a trailing line break add no row.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseCsv(content: string): string[][];
|
|
26
|
+
export declare function FilePreview({ filename, content, blobUrl, mimeType, size, urlTransform, onClose, onDownload, hideHeader, className }: FilePreviewProps): import("react").JSX.Element;
|
|
27
|
+
//#endregion
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { focusRingInset } from "../lib/focus.js";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Button } from "../primitives/button.js";
|
|
4
|
+
import { CodeBlock, CopyButton } from "../markdown/code-block.js";
|
|
5
|
+
import { formatBytes } from "../utils/format.js";
|
|
6
|
+
import { fileExtension, getCodeLanguage, getFormatLabel, resolveFilePreviewKind } from "./file-format.js";
|
|
7
|
+
import { Markdown } from "../markdown/markdown.js";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { Download, FileSpreadsheet, FileText, Music, X } from "lucide-react";
|
|
11
|
+
//#region src/files/file-preview.tsx
|
|
12
|
+
/**
|
|
13
|
+
* FilePreview — universal file renderer.
|
|
14
|
+
*
|
|
15
|
+
* Renders any file type:
|
|
16
|
+
* - PDF: embedded viewer at full pane height
|
|
17
|
+
* - Images: fit to the pane on a checker ground; click toggles natural size
|
|
18
|
+
* - Video / audio: native players
|
|
19
|
+
* - CSV: sticky-header table, capped at CSV_PREVIEW_ROW_LIMIT rows
|
|
20
|
+
* - XLSX / XLS: download card
|
|
21
|
+
* - Code (py/json/yaml/ts/js): syntax-highlighted, line-numbered viewer
|
|
22
|
+
* - Markdown: rendered prose
|
|
23
|
+
* - Text: monospace preview
|
|
24
|
+
* - Anything else: text when content is a string, otherwise a download card
|
|
25
|
+
*/
|
|
26
|
+
/** Rows rendered by the CSV preview; the count line reports the total. */
|
|
27
|
+
const CSV_PREVIEW_ROW_LIMIT = 500;
|
|
28
|
+
const NEEDS_DOWNLOAD_LINK = "This file needs a download link to preview.";
|
|
29
|
+
const NO_INLINE_PREVIEW = "This file type has no inline preview.";
|
|
30
|
+
const NO_INLINE_CONTENT = "This file has no inline content to preview yet.";
|
|
31
|
+
function CodePreview({ content, filename, kind }) {
|
|
32
|
+
const lineCount = content.split("\n").length;
|
|
33
|
+
const language = getCodeLanguage(filename, kind);
|
|
34
|
+
const labelToken = fileExtension(filename) || language || "txt";
|
|
35
|
+
return /* @__PURE__ */ jsx(CodeBlock, {
|
|
36
|
+
code: content,
|
|
37
|
+
language,
|
|
38
|
+
label: `${labelToken} · ${lineCount} lines`,
|
|
39
|
+
showLineNumbers: true,
|
|
40
|
+
className: "max-h-[70vh] overflow-auto",
|
|
41
|
+
children: /* @__PURE__ */ jsx(CopyButton, { text: content })
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Split CSV text into rows of cells (RFC 4180): a quoted cell may hold commas,
|
|
46
|
+
* line breaks, and doubled quotes. Unquoted cells are trimmed; blank lines and
|
|
47
|
+
* a trailing line break add no row.
|
|
48
|
+
*/
|
|
49
|
+
function parseCsv(content) {
|
|
50
|
+
const rows = [];
|
|
51
|
+
let row = [];
|
|
52
|
+
let cell = "";
|
|
53
|
+
let quoted = false;
|
|
54
|
+
let inQuotes = false;
|
|
55
|
+
const endCell = () => {
|
|
56
|
+
row.push(quoted ? cell : cell.trim());
|
|
57
|
+
cell = "";
|
|
58
|
+
quoted = false;
|
|
59
|
+
};
|
|
60
|
+
const endRow = () => {
|
|
61
|
+
endCell();
|
|
62
|
+
if (row.length > 1 || row[0] !== "") rows.push(row);
|
|
63
|
+
row = [];
|
|
64
|
+
};
|
|
65
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
66
|
+
const char = content[index];
|
|
67
|
+
if (inQuotes) {
|
|
68
|
+
if (char === "\"") {
|
|
69
|
+
if (content[index + 1] === "\"") {
|
|
70
|
+
cell += "\"";
|
|
71
|
+
index += 1;
|
|
72
|
+
} else inQuotes = false;
|
|
73
|
+
} else cell += char;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "\"") {
|
|
77
|
+
inQuotes = true;
|
|
78
|
+
quoted = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === ",") {
|
|
82
|
+
endCell();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (char === "\r" || char === "\n") {
|
|
86
|
+
if (char === "\r" && content[index + 1] === "\n") index += 1;
|
|
87
|
+
endRow();
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
cell += char;
|
|
91
|
+
}
|
|
92
|
+
if (cell.length > 0 || quoted || row.length > 0) endRow();
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
95
|
+
function CsvPreview({ content }) {
|
|
96
|
+
const [headers = [], ...body] = parseCsv(content);
|
|
97
|
+
if (headers.length === 0) return null;
|
|
98
|
+
const visible = body.slice(0, 500);
|
|
99
|
+
const columnCount = Math.max(headers.length, ...visible.map((cells) => cells.length));
|
|
100
|
+
const capped = body.length > visible.length;
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
102
|
+
className: "flex min-h-0 flex-1 flex-col gap-2",
|
|
103
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
104
|
+
className: "min-h-0 overflow-auto rounded-[var(--radius-md)] border border-border",
|
|
105
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
106
|
+
className: "w-full text-sm",
|
|
107
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: Array.from({ length: columnCount }, (_, column) => /* @__PURE__ */ jsx("th", {
|
|
108
|
+
className: "sticky top-0 whitespace-nowrap border-b border-border bg-card px-3 py-2 text-left text-xs font-semibold text-foreground",
|
|
109
|
+
children: headers[column] ?? ""
|
|
110
|
+
}, column)) }) }), /* @__PURE__ */ jsx("tbody", { children: visible.map((cells, rowIndex) => /* @__PURE__ */ jsx("tr", {
|
|
111
|
+
className: "border-b border-border hover:bg-accent",
|
|
112
|
+
children: Array.from({ length: columnCount }, (_, column) => /* @__PURE__ */ jsx("td", {
|
|
113
|
+
className: "whitespace-nowrap px-3 py-1.5 font-mono text-xs text-foreground",
|
|
114
|
+
children: cells[column] ?? ""
|
|
115
|
+
}, column))
|
|
116
|
+
}, rowIndex)) })]
|
|
117
|
+
})
|
|
118
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
119
|
+
className: "shrink-0 text-xs text-muted-foreground",
|
|
120
|
+
children: [capped ? `Showing ${visible.length} of ${body.length} rows` : `${body.length} ${body.length === 1 ? "row" : "rows"}`, ` · ${columnCount} ${columnCount === 1 ? "column" : "columns"}`]
|
|
121
|
+
})]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const CHECKER_STYLE = {
|
|
125
|
+
backgroundColor: "var(--color-background)",
|
|
126
|
+
backgroundImage: "repeating-conic-gradient(var(--color-muted) 0% 25%, transparent 0% 50%)",
|
|
127
|
+
backgroundSize: "16px 16px"
|
|
128
|
+
};
|
|
129
|
+
function ImagePreview({ src, filename }) {
|
|
130
|
+
const [naturalSize, setNaturalSize] = useState(false);
|
|
131
|
+
return /* @__PURE__ */ jsx("div", {
|
|
132
|
+
className: "flex min-h-[12rem] flex-1 overflow-auto rounded-[var(--radius-md)] border border-border",
|
|
133
|
+
style: CHECKER_STYLE,
|
|
134
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
135
|
+
type: "button",
|
|
136
|
+
onClick: () => setNaturalSize((value) => !value),
|
|
137
|
+
"aria-pressed": naturalSize,
|
|
138
|
+
"aria-label": naturalSize ? "Fit image to pane" : "Show image at natural size",
|
|
139
|
+
className: cn("flex items-center justify-center", focusRingInset, naturalSize ? "m-auto cursor-zoom-out" : "h-full w-full cursor-zoom-in p-4"),
|
|
140
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
141
|
+
src,
|
|
142
|
+
alt: filename,
|
|
143
|
+
draggable: false,
|
|
144
|
+
className: naturalSize ? "max-w-none" : "max-h-full max-w-full object-contain"
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function PdfPreview({ blobUrl, filename, size, onDownload }) {
|
|
150
|
+
return /* @__PURE__ */ jsx("object", {
|
|
151
|
+
data: blobUrl,
|
|
152
|
+
type: "application/pdf",
|
|
153
|
+
title: filename,
|
|
154
|
+
className: "min-h-[24rem] w-full flex-1 rounded-[var(--radius-md)]",
|
|
155
|
+
children: /* @__PURE__ */ jsx(DownloadCard, {
|
|
156
|
+
filename,
|
|
157
|
+
size,
|
|
158
|
+
sentence: "This browser does not show PDFs inline.",
|
|
159
|
+
onDownload,
|
|
160
|
+
className: "h-full"
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function VideoPreview({ src, filename }) {
|
|
165
|
+
return /* @__PURE__ */ jsx("div", {
|
|
166
|
+
className: "flex min-h-[12rem] flex-1 items-center justify-center overflow-hidden rounded-[var(--radius-md)] border border-border bg-background",
|
|
167
|
+
children: /* @__PURE__ */ jsx("video", {
|
|
168
|
+
controls: true,
|
|
169
|
+
preload: "metadata",
|
|
170
|
+
src,
|
|
171
|
+
"aria-label": filename,
|
|
172
|
+
className: "max-h-full max-w-full"
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function AudioPreview({ src, filename }) {
|
|
177
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
178
|
+
className: "flex flex-col items-center gap-4 rounded-[var(--radius-md)] border border-border bg-background px-6 py-10",
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ jsx(Music, { className: "h-10 w-10 text-muted-foreground opacity-60" }),
|
|
181
|
+
/* @__PURE__ */ jsx("p", {
|
|
182
|
+
className: "max-w-full truncate text-sm font-medium text-foreground",
|
|
183
|
+
children: filename
|
|
184
|
+
}),
|
|
185
|
+
/* @__PURE__ */ jsx("audio", {
|
|
186
|
+
controls: true,
|
|
187
|
+
preload: "metadata",
|
|
188
|
+
src,
|
|
189
|
+
"aria-label": filename,
|
|
190
|
+
className: "w-full max-w-md"
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function TextPreview({ content }) {
|
|
196
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
197
|
+
className: "bg-card rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]",
|
|
198
|
+
children: content
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function MarkdownPreview({ content, urlTransform }) {
|
|
202
|
+
return /* @__PURE__ */ jsx("div", {
|
|
203
|
+
className: "rounded-[var(--radius-md)] border border-border bg-background p-5",
|
|
204
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
205
|
+
urlTransform,
|
|
206
|
+
children: content
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* One muted sentence plus the filename, its size when known, and a Download
|
|
212
|
+
* button when the host wires `onDownload`. Serves spreadsheets, binaries, and
|
|
213
|
+
* every kind whose source (blob or text) is missing.
|
|
214
|
+
*/
|
|
215
|
+
function DownloadCard({ filename, size, sentence, icon: Icon = FileText, onDownload, className }) {
|
|
216
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
217
|
+
className: cn("flex flex-col items-center justify-center rounded-[var(--radius-md)] border border-dashed border-border bg-background px-6 py-16 text-center", className),
|
|
218
|
+
children: [
|
|
219
|
+
/* @__PURE__ */ jsx(Icon, { className: "mb-3 h-12 w-12 text-muted-foreground opacity-40" }),
|
|
220
|
+
/* @__PURE__ */ jsx("p", {
|
|
221
|
+
className: "max-w-full truncate text-sm font-medium text-foreground",
|
|
222
|
+
children: filename
|
|
223
|
+
}),
|
|
224
|
+
size !== void 0 && /* @__PURE__ */ jsx("p", {
|
|
225
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
226
|
+
children: formatBytes(size)
|
|
227
|
+
}),
|
|
228
|
+
/* @__PURE__ */ jsx("p", {
|
|
229
|
+
className: "mt-3 max-w-md text-sm text-muted-foreground",
|
|
230
|
+
children: sentence
|
|
231
|
+
}),
|
|
232
|
+
onDownload && /* @__PURE__ */ jsxs(Button, {
|
|
233
|
+
type: "button",
|
|
234
|
+
variant: "outline",
|
|
235
|
+
size: "sm",
|
|
236
|
+
className: "mt-5",
|
|
237
|
+
onClick: onDownload,
|
|
238
|
+
children: [/* @__PURE__ */ jsx(Download, {}), "Download"]
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function PreviewBody({ kind, filename, content, blobUrl, size, urlTransform, onDownload }) {
|
|
244
|
+
const hasText = typeof content === "string";
|
|
245
|
+
const card = (sentence, icon) => /* @__PURE__ */ jsx(DownloadCard, {
|
|
246
|
+
filename,
|
|
247
|
+
size,
|
|
248
|
+
sentence,
|
|
249
|
+
icon,
|
|
250
|
+
onDownload
|
|
251
|
+
});
|
|
252
|
+
switch (kind) {
|
|
253
|
+
case "pdf":
|
|
254
|
+
if (blobUrl) return /* @__PURE__ */ jsx(PdfPreview, {
|
|
255
|
+
blobUrl,
|
|
256
|
+
filename,
|
|
257
|
+
size,
|
|
258
|
+
onDownload
|
|
259
|
+
});
|
|
260
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
261
|
+
case "image":
|
|
262
|
+
if (blobUrl) return /* @__PURE__ */ jsx(ImagePreview, {
|
|
263
|
+
src: blobUrl,
|
|
264
|
+
filename
|
|
265
|
+
});
|
|
266
|
+
if (hasText) return /* @__PURE__ */ jsx(CodePreview, {
|
|
267
|
+
content,
|
|
268
|
+
filename,
|
|
269
|
+
kind: "code"
|
|
270
|
+
});
|
|
271
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
272
|
+
case "video":
|
|
273
|
+
if (blobUrl) return /* @__PURE__ */ jsx(VideoPreview, {
|
|
274
|
+
src: blobUrl,
|
|
275
|
+
filename
|
|
276
|
+
});
|
|
277
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
278
|
+
case "audio":
|
|
279
|
+
if (blobUrl) return /* @__PURE__ */ jsx(AudioPreview, {
|
|
280
|
+
src: blobUrl,
|
|
281
|
+
filename
|
|
282
|
+
});
|
|
283
|
+
return card(NEEDS_DOWNLOAD_LINK, Music);
|
|
284
|
+
case "csv":
|
|
285
|
+
if (hasText) return /* @__PURE__ */ jsx(CsvPreview, { content });
|
|
286
|
+
return card(NO_INLINE_CONTENT, FileSpreadsheet);
|
|
287
|
+
case "code":
|
|
288
|
+
case "json":
|
|
289
|
+
case "yaml":
|
|
290
|
+
if (hasText) return /* @__PURE__ */ jsx(CodePreview, {
|
|
291
|
+
content,
|
|
292
|
+
filename,
|
|
293
|
+
kind
|
|
294
|
+
});
|
|
295
|
+
return card(NO_INLINE_CONTENT);
|
|
296
|
+
case "markdown":
|
|
297
|
+
if (hasText) return /* @__PURE__ */ jsx(MarkdownPreview, {
|
|
298
|
+
content,
|
|
299
|
+
urlTransform
|
|
300
|
+
});
|
|
301
|
+
return card(NO_INLINE_CONTENT);
|
|
302
|
+
case "text":
|
|
303
|
+
if (hasText) return /* @__PURE__ */ jsx(TextPreview, { content });
|
|
304
|
+
return card(NO_INLINE_CONTENT);
|
|
305
|
+
case "spreadsheet": return card("Download to open this workbook in a spreadsheet app.", FileSpreadsheet);
|
|
306
|
+
case "binary":
|
|
307
|
+
if (hasText) return /* @__PURE__ */ jsx(TextPreview, { content });
|
|
308
|
+
return card(blobUrl ? NO_INLINE_PREVIEW : NEEDS_DOWNLOAD_LINK);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function FilePreview({ filename, content, blobUrl, mimeType, size, urlTransform, onClose, onDownload, hideHeader = false, className }) {
|
|
312
|
+
const kind = resolveFilePreviewKind(filename, mimeType);
|
|
313
|
+
const previewLabel = getFormatLabel(kind);
|
|
314
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
315
|
+
className: cn("flex flex-col h-full", className),
|
|
316
|
+
children: [!hideHeader && /* @__PURE__ */ jsxs("div", {
|
|
317
|
+
className: "flex items-center gap-2 px-3 py-2 border-b border-border shrink-0",
|
|
318
|
+
children: [
|
|
319
|
+
/* @__PURE__ */ jsxs("div", {
|
|
320
|
+
className: "min-w-0 flex-1",
|
|
321
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
322
|
+
className: "truncate text-sm font-medium text-foreground",
|
|
323
|
+
children: filename
|
|
324
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
325
|
+
className: "mt-0.5 text-[11px] uppercase tracking-[0.12em] text-muted-foreground",
|
|
326
|
+
children: previewLabel
|
|
327
|
+
})]
|
|
328
|
+
}),
|
|
329
|
+
onDownload && /* @__PURE__ */ jsx("button", {
|
|
330
|
+
type: "button",
|
|
331
|
+
onClick: onDownload,
|
|
332
|
+
"aria-label": `Download ${filename}`,
|
|
333
|
+
className: "p-1.5 rounded-[var(--radius-sm)] hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
|
|
334
|
+
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
335
|
+
}),
|
|
336
|
+
onClose && /* @__PURE__ */ jsx("button", {
|
|
337
|
+
type: "button",
|
|
338
|
+
onClick: onClose,
|
|
339
|
+
"aria-label": `Close ${filename}`,
|
|
340
|
+
className: "p-1.5 rounded-[var(--radius-sm)] hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
|
|
341
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
342
|
+
})
|
|
343
|
+
]
|
|
344
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
345
|
+
className: "flex min-h-0 flex-1 flex-col overflow-auto p-3",
|
|
346
|
+
children: /* @__PURE__ */ jsx(PreviewBody, {
|
|
347
|
+
kind,
|
|
348
|
+
filename,
|
|
349
|
+
content,
|
|
350
|
+
blobUrl,
|
|
351
|
+
size,
|
|
352
|
+
urlTransform,
|
|
353
|
+
onDownload
|
|
354
|
+
})
|
|
355
|
+
})]
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
export { CSV_PREVIEW_ROW_LIMIT, FilePreview, parseCsv };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/files/file-tabs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* FileTabs — tab bar for open files in the preview panel.
|
|
4
|
+
*/
|
|
5
|
+
export interface FileTabData {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
dirty?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface FileTabsProps {
|
|
12
|
+
tabs: FileTabData[];
|
|
13
|
+
activeId?: string;
|
|
14
|
+
onSelect: (id: string) => void;
|
|
15
|
+
onClose: (id: string) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function FileTabs({ tabs, activeId, onSelect, onClose, className }: FileTabsProps): import("react").JSX.Element | null;
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { focusRing, focusRingInset } from "../lib/focus.js";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { FileCode, FileSpreadsheet, FileText, X } from "lucide-react";
|
|
5
|
+
//#region src/files/file-tabs.tsx
|
|
6
|
+
/**
|
|
7
|
+
* FileTabs — tab bar for open files in the preview panel.
|
|
8
|
+
*/
|
|
9
|
+
function getTabIcon(name) {
|
|
10
|
+
const ext = name.split(".").pop()?.toLowerCase() || "";
|
|
11
|
+
if (["pdf"].includes(ext)) return FileText;
|
|
12
|
+
if (["csv", "xlsx"].includes(ext)) return FileSpreadsheet;
|
|
13
|
+
return FileCode;
|
|
14
|
+
}
|
|
15
|
+
function FileTabs({ tabs, activeId, onSelect, onClose, className }) {
|
|
16
|
+
if (tabs.length === 0) return null;
|
|
17
|
+
return /* @__PURE__ */ jsx("div", {
|
|
18
|
+
className: cn("flex items-center border-b border-border bg-background overflow-x-auto", className),
|
|
19
|
+
children: tabs.map((tab) => {
|
|
20
|
+
const isActive = tab.id === activeId;
|
|
21
|
+
const Icon = getTabIcon(tab.name);
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
23
|
+
className: cn("group flex items-center border-r border-border shrink-0", isActive ? "bg-card text-foreground border-b-2 border-b-primary" : "text-muted-foreground hover:bg-muted/50"),
|
|
24
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: () => onSelect(tab.id),
|
|
27
|
+
className: cn("flex min-w-0 items-center gap-1.5 px-3 py-1.5 text-xs transition-colors hover:text-foreground", focusRingInset),
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3 shrink-0" }),
|
|
30
|
+
/* @__PURE__ */ jsx("span", {
|
|
31
|
+
className: "max-w-[120px] truncate",
|
|
32
|
+
children: tab.name
|
|
33
|
+
}),
|
|
34
|
+
tab.dirty && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-primary" })
|
|
35
|
+
]
|
|
36
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
37
|
+
type: "button",
|
|
38
|
+
"aria-label": `Close ${tab.name}`,
|
|
39
|
+
onClick: () => onClose(tab.id),
|
|
40
|
+
className: cn("mr-1 rounded p-0.5 opacity-0 transition-opacity hover:bg-accent focus-visible:opacity-100 group-hover:opacity-100", focusRing),
|
|
41
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
42
|
+
})]
|
|
43
|
+
}, tab.id);
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { FileTabs };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/files/file-tree.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* FileTree — directory browser with file type icons.
|
|
4
|
+
*
|
|
5
|
+
* Renders a hierarchical file system view. Clicking a file
|
|
6
|
+
* triggers onSelect with the full path.
|
|
7
|
+
*/
|
|
8
|
+
export interface FileNode {
|
|
9
|
+
name: string;
|
|
10
|
+
path: string;
|
|
11
|
+
type: "file" | "directory";
|
|
12
|
+
children?: FileNode[];
|
|
13
|
+
size?: number;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FileTreeProps {
|
|
17
|
+
root: FileNode;
|
|
18
|
+
selectedPath?: string;
|
|
19
|
+
onSelect?: (path: string, node: FileNode) => void;
|
|
20
|
+
className?: string;
|
|
21
|
+
defaultExpanded?: boolean;
|
|
22
|
+
visibility?: FileTreeVisibilityOptions;
|
|
23
|
+
}
|
|
24
|
+
export interface FileTreeVisibilityOptions {
|
|
25
|
+
hiddenPaths?: string[];
|
|
26
|
+
hiddenPathPrefixes?: string[];
|
|
27
|
+
isVisible?: (node: FileNode) => boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function filterFileTree(root: FileNode, visibility?: FileTreeVisibilityOptions): FileNode | null;
|
|
30
|
+
export declare function FileTree({ root, selectedPath, onSelect, className, defaultExpanded, visibility }: FileTreeProps): import("react").JSX.Element | null;
|
|
31
|
+
//#endregion
|