@tangle-network/ui 1.0.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 +12 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
- package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
- package/dist/auth.d.ts +74 -0
- package/dist/auth.js +15 -0
- package/dist/button-CMQuQEW_.d.ts +17 -0
- package/dist/chat.d.ts +232 -0
- package/dist/chat.js +30 -0
- package/dist/chunk-2NFQRQOD.js +1009 -0
- package/dist/chunk-2VH6PUXD.js +186 -0
- package/dist/chunk-34A66VBG.js +214 -0
- package/dist/chunk-3OI2QKFD.js +0 -0
- package/dist/chunk-4CLN43XT.js +45 -0
- package/dist/chunk-54SQQMMM.js +156 -0
- package/dist/chunk-5Z5ZYMOJ.js +0 -0
- package/dist/chunk-66BNMOVT.js +167 -0
- package/dist/chunk-6BGQA4BQ.js +0 -0
- package/dist/chunk-7UO2ZMRQ.js +133 -0
- package/dist/chunk-BX6AQMUS.js +183 -0
- package/dist/chunk-CD53GZOM.js +59 -0
- package/dist/chunk-CSAIKY36.js +54 -0
- package/dist/chunk-EEE55AVS.js +1201 -0
- package/dist/chunk-GYPQXTJU.js +230 -0
- package/dist/chunk-HFL6R6IF.js +37 -0
- package/dist/chunk-HJKCSXCH.js +737 -0
- package/dist/chunk-LISXUB4D.js +1222 -0
- package/dist/chunk-LQS34IGP.js +0 -0
- package/dist/chunk-MKTSMWVD.js +109 -0
- package/dist/chunk-NKDZ7GZE.js +192 -0
- package/dist/chunk-OEX7NZE3.js +321 -0
- package/dist/chunk-Q56BYXQF.js +61 -0
- package/dist/chunk-Q7EIIWTC.js +0 -0
- package/dist/chunk-REJESC5U.js +117 -0
- package/dist/chunk-RQGKSCEZ.js +0 -0
- package/dist/chunk-RQHJBTEU.js +10 -0
- package/dist/chunk-TMFOPHHN.js +299 -0
- package/dist/chunk-XGKULLYE.js +40 -0
- package/dist/chunk-XIHMJ7ZQ.js +614 -0
- package/dist/chunk-YJ2G3XO5.js +1048 -0
- package/dist/chunk-YNN4O57I.js +754 -0
- package/dist/code-block-DjXf8eOG.d.ts +19 -0
- package/dist/document-editor-pane-A5LT5H4N.js +12 -0
- package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
- package/dist/editor.d.ts +120 -0
- package/dist/editor.js +34 -0
- package/dist/files.d.ts +175 -0
- package/dist/files.js +20 -0
- package/dist/hooks.d.ts +56 -0
- package/dist/hooks.js +41 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +446 -0
- package/dist/markdown.d.ts +15 -0
- package/dist/markdown.js +14 -0
- package/dist/message-BHWbxBtT.d.ts +15 -0
- package/dist/openui.d.ts +115 -0
- package/dist/openui.js +12 -0
- package/dist/parts-dj7AcUg0.d.ts +36 -0
- package/dist/primitives.d.ts +332 -0
- package/dist/primitives.js +191 -0
- package/dist/run-PfLmDAox.d.ts +41 -0
- package/dist/run.d.ts +69 -0
- package/dist/run.js +36 -0
- package/dist/sdk-hooks.d.ts +285 -0
- package/dist/sdk-hooks.js +31 -0
- package/dist/stores.d.ts +17 -0
- package/dist/stores.js +76 -0
- package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
- package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
- package/dist/tool-previews.d.ts +48 -0
- package/dist/tool-previews.js +21 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +45 -0
- package/dist/utils.js +32 -0
- package/package.json +193 -0
- package/src/auth/auth.tsx +228 -0
- package/src/auth/index.ts +13 -0
- package/src/auth/login-layout.tsx +46 -0
- package/src/chat/agent-timeline.stories.tsx +429 -0
- package/src/chat/agent-timeline.tsx +360 -0
- package/src/chat/chat-container.tsx +486 -0
- package/src/chat/chat-input.stories.tsx +142 -0
- package/src/chat/chat-input.tsx +389 -0
- package/src/chat/chat-message.stories.tsx +237 -0
- package/src/chat/chat-message.tsx +129 -0
- package/src/chat/index.ts +18 -0
- package/src/chat/message-list.stories.tsx +336 -0
- package/src/chat/message-list.tsx +79 -0
- package/src/chat/thinking-indicator.stories.tsx +56 -0
- package/src/chat/thinking-indicator.tsx +30 -0
- package/src/chat/user-message.stories.tsx +92 -0
- package/src/chat/user-message.tsx +43 -0
- package/src/editor/document-editor-pane.tsx +351 -0
- package/src/editor/editor-provider.tsx +428 -0
- package/src/editor/editor-toolbar.tsx +130 -0
- package/src/editor/index.ts +31 -0
- package/src/editor/markdown-conversion.ts +21 -0
- package/src/editor/markdown-document-editor.tsx +137 -0
- package/src/editor/tiptap-editor.tsx +331 -0
- package/src/editor/use-editor.ts +221 -0
- package/src/files/file-artifact-pane.tsx +183 -0
- package/src/files/file-preview.tsx +342 -0
- package/src/files/file-tabs.tsx +71 -0
- package/src/files/file-tree.tsx +258 -0
- package/src/files/index.ts +17 -0
- package/src/files/rich-file-tree.stories.tsx +104 -0
- package/src/files/rich-file-tree.test.tsx +42 -0
- package/src/files/rich-file-tree.tsx +232 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-auth.ts +153 -0
- package/src/hooks/use-auto-scroll.ts +59 -0
- package/src/hooks/use-dropdown-menu.ts +40 -0
- package/src/hooks/use-live-time.test.tsx +40 -0
- package/src/hooks/use-live-time.ts +27 -0
- package/src/hooks/use-realtime-session.ts +319 -0
- package/src/hooks/use-run-collapse-state.ts +25 -0
- package/src/hooks/use-run-groups.ts +111 -0
- package/src/hooks/use-sdk-session.ts +575 -0
- package/src/hooks/use-sse-stream.ts +475 -0
- package/src/hooks/use-tool-call-stream.ts +96 -0
- package/src/index.ts +14 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown/code-block.tsx +198 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/markdown.stories.tsx +190 -0
- package/src/markdown/markdown.tsx +62 -0
- package/src/openui/index.ts +20 -0
- package/src/openui/openui-artifact-renderer.tsx +542 -0
- package/src/primitives/artifact-pane.tsx +91 -0
- package/src/primitives/avatar.stories.tsx +95 -0
- package/src/primitives/avatar.tsx +47 -0
- package/src/primitives/badge.stories.tsx +57 -0
- package/src/primitives/badge.tsx +97 -0
- package/src/primitives/button.stories.tsx +48 -0
- package/src/primitives/button.tsx +115 -0
- package/src/primitives/card.stories.tsx +53 -0
- package/src/primitives/card.tsx +98 -0
- package/src/primitives/code-block.stories.tsx +115 -0
- package/src/primitives/code-block.tsx +22 -0
- package/src/primitives/design-tokens.stories.tsx +162 -0
- package/src/primitives/dialog.stories.tsx +176 -0
- package/src/primitives/dialog.tsx +137 -0
- package/src/primitives/drop-zone.stories.tsx +123 -0
- package/src/primitives/drop-zone.tsx +131 -0
- package/src/primitives/dropdown-menu.stories.tsx +122 -0
- package/src/primitives/dropdown-menu.tsx +214 -0
- package/src/primitives/empty-state.stories.tsx +81 -0
- package/src/primitives/empty-state.tsx +40 -0
- package/src/primitives/index.ts +118 -0
- package/src/primitives/input.stories.tsx +113 -0
- package/src/primitives/input.tsx +136 -0
- package/src/primitives/label.stories.tsx +84 -0
- package/src/primitives/label.tsx +24 -0
- package/src/primitives/progress.stories.tsx +93 -0
- package/src/primitives/progress.tsx +50 -0
- package/src/primitives/segmented-control.test.tsx +328 -0
- package/src/primitives/segmented-control.tsx +154 -0
- package/src/primitives/select.stories.tsx +164 -0
- package/src/primitives/select.tsx +158 -0
- package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
- package/src/primitives/sidebar-drop-zone.tsx +149 -0
- package/src/primitives/skeleton.stories.tsx +79 -0
- package/src/primitives/skeleton.tsx +55 -0
- package/src/primitives/stat-card.stories.tsx +137 -0
- package/src/primitives/stat-card.tsx +97 -0
- package/src/primitives/switch.stories.tsx +85 -0
- package/src/primitives/switch.tsx +28 -0
- package/src/primitives/table.stories.tsx +170 -0
- package/src/primitives/table.tsx +116 -0
- package/src/primitives/tabs.stories.tsx +180 -0
- package/src/primitives/tabs.tsx +71 -0
- package/src/primitives/terminal-display.stories.tsx +191 -0
- package/src/primitives/terminal-display.tsx +189 -0
- package/src/primitives/theme-toggle.stories.tsx +32 -0
- package/src/primitives/theme-toggle.tsx +96 -0
- package/src/primitives/toast.stories.tsx +155 -0
- package/src/primitives/toast.tsx +190 -0
- package/src/primitives/upload-progress.stories.tsx +120 -0
- package/src/primitives/upload-progress.tsx +110 -0
- package/src/run/expanded-tool-detail.stories.tsx +182 -0
- package/src/run/expanded-tool-detail.tsx +186 -0
- package/src/run/index.ts +13 -0
- package/src/run/inline-thinking-item.stories.tsx +136 -0
- package/src/run/inline-thinking-item.tsx +120 -0
- package/src/run/inline-tool-item.stories.tsx +222 -0
- package/src/run/inline-tool-item.tsx +190 -0
- package/src/run/run-group.stories.tsx +322 -0
- package/src/run/run-group.tsx +569 -0
- package/src/run/run-item-primitives.tsx +17 -0
- package/src/run/tool-call-feed.stories.tsx +294 -0
- package/src/run/tool-call-feed.tsx +192 -0
- package/src/run/tool-call-step.stories.tsx +198 -0
- package/src/run/tool-call-step.tsx +240 -0
- package/src/sdk-hooks.ts +38 -0
- package/src/stores/active-sessions-store.ts +455 -0
- package/src/stores/chat-store.ts +43 -0
- package/src/stores/index.ts +2 -0
- package/src/tool-previews/command-preview.tsx +116 -0
- package/src/tool-previews/diff-preview.tsx +85 -0
- package/src/tool-previews/glob-results-preview.tsx +98 -0
- package/src/tool-previews/grep-results-preview.tsx +157 -0
- package/src/tool-previews/index.ts +22 -0
- package/src/tool-previews/preview-primitives.tsx +84 -0
- package/src/tool-previews/question-preview.tsx +101 -0
- package/src/tool-previews/web-search-preview.tsx +117 -0
- package/src/tool-previews/write-file-preview.tsx +80 -0
- package/src/types/branding.ts +11 -0
- package/src/types/index.ts +5 -0
- package/src/types/message.ts +13 -0
- package/src/types/parts.ts +51 -0
- package/src/types/run.ts +56 -0
- package/src/types/tool-display.ts +41 -0
- package/src/utils/copy-text.ts +30 -0
- package/src/utils/format.test.ts +43 -0
- package/src/utils/format.ts +56 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/time-ago.ts +9 -0
- package/src/utils/tool-display.ts +238 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
memo,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
11
|
+
import { Check, Copy } from "lucide-react";
|
|
12
|
+
import { cn } from "../lib/utils";
|
|
13
|
+
|
|
14
|
+
// Theme-aware syntax highlighting — reads CSS custom properties at render time.
|
|
15
|
+
// Override --syntax-* tokens in tokens.css per theme.
|
|
16
|
+
function getSyntaxTheme(): { [key: string]: React.CSSProperties } {
|
|
17
|
+
const el = typeof document !== "undefined" ? document.documentElement : null;
|
|
18
|
+
const v = (name: string, fallback: string) =>
|
|
19
|
+
el ? getComputedStyle(el).getPropertyValue(name).trim() || fallback : fallback;
|
|
20
|
+
|
|
21
|
+
const comment = v("--syntax-comment", "#6B7094");
|
|
22
|
+
const keyword = v("--syntax-keyword", "#A78FFF");
|
|
23
|
+
const string = v("--syntax-string", "#10b981");
|
|
24
|
+
const fn = v("--syntax-function", "#6D9FFF");
|
|
25
|
+
const number = v("--syntax-number", "#FFB347");
|
|
26
|
+
const meta = v("--syntax-meta", "#8263FF");
|
|
27
|
+
const error = v("--syntax-error", "#FF4D6D");
|
|
28
|
+
const variable = v("--syntax-variable", "#C4C0D8");
|
|
29
|
+
const fg = v("--syntax-foreground", "#E8E6F6");
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"hljs-comment": { color: comment, fontStyle: "italic" },
|
|
33
|
+
"hljs-quote": { color: comment, fontStyle: "italic" },
|
|
34
|
+
"hljs-doctag": { color: comment },
|
|
35
|
+
"hljs-keyword": { color: keyword },
|
|
36
|
+
"hljs-selector-tag": { color: keyword },
|
|
37
|
+
"hljs-literal": { color: keyword },
|
|
38
|
+
"hljs-type": { color: keyword },
|
|
39
|
+
"hljs-class": { color: keyword },
|
|
40
|
+
"hljs-string": { color: string },
|
|
41
|
+
"hljs-template-tag": { color: string },
|
|
42
|
+
"hljs-template-variable": { color: string },
|
|
43
|
+
"hljs-addition": { color: string },
|
|
44
|
+
"hljs-regexp": { color: string },
|
|
45
|
+
"hljs-title": { color: fn },
|
|
46
|
+
"hljs-section": { color: fn },
|
|
47
|
+
"hljs-built_in": { color: fn },
|
|
48
|
+
"hljs-name": { color: fn },
|
|
49
|
+
"hljs-function": { color: fn },
|
|
50
|
+
"hljs-selector-id": { color: fn },
|
|
51
|
+
"hljs-selector-class": { color: fn },
|
|
52
|
+
"hljs-attribute": { color: fn },
|
|
53
|
+
"hljs-number": { color: number },
|
|
54
|
+
"hljs-symbol": { color: number },
|
|
55
|
+
"hljs-bullet": { color: number },
|
|
56
|
+
"hljs-link": { color: number, textDecoration: "underline" },
|
|
57
|
+
"hljs-meta": { color: meta },
|
|
58
|
+
"hljs-selector-pseudo": { color: meta },
|
|
59
|
+
"hljs-deletion": { color: error },
|
|
60
|
+
"hljs-params": { color: variable },
|
|
61
|
+
"hljs-variable": { color: variable },
|
|
62
|
+
"hljs-tag": { color: variable },
|
|
63
|
+
"hljs-attr": { color: variable },
|
|
64
|
+
"hljs-subst": { color: variable },
|
|
65
|
+
"hljs-strong": { fontWeight: "bold" },
|
|
66
|
+
"hljs-emphasis": { fontStyle: "italic" },
|
|
67
|
+
"hljs": { color: fg, background: "transparent" },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// tangleLight removed — getSyntaxTheme() reads --syntax-* CSS vars which are overridden
|
|
72
|
+
// per theme in tokens.css (vault, dawn themes set light values).
|
|
73
|
+
|
|
74
|
+
export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
|
|
75
|
+
code: string;
|
|
76
|
+
language?: string;
|
|
77
|
+
showLineNumbers?: boolean;
|
|
78
|
+
/** Force light theme; defaults to dark */
|
|
79
|
+
light?: boolean;
|
|
80
|
+
children?: ReactNode;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const LIGHT_THEMES = new Set(["vault", "dawn"]);
|
|
84
|
+
|
|
85
|
+
function detectLightTheme() {
|
|
86
|
+
return (
|
|
87
|
+
typeof document !== "undefined" &&
|
|
88
|
+
LIGHT_THEMES.has(document.documentElement.getAttribute("data-sandbox-theme") ?? "")
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function useIsLightTheme(): boolean {
|
|
93
|
+
const [isLight, setIsLight] = useState(detectLightTheme);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (typeof document === "undefined") return;
|
|
97
|
+
setIsLight(detectLightTheme());
|
|
98
|
+
const observer = new MutationObserver(() => setIsLight(detectLightTheme()));
|
|
99
|
+
observer.observe(document.documentElement, {
|
|
100
|
+
attributes: true,
|
|
101
|
+
attributeFilter: ["data-sandbox-theme"],
|
|
102
|
+
});
|
|
103
|
+
return () => observer.disconnect();
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
return isLight;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const CodeBlock = memo(
|
|
110
|
+
({ code, language, showLineNumbers = false, light: lightProp, className, children, ...props }: CodeBlockProps) => {
|
|
111
|
+
const isLight = useIsLightTheme();
|
|
112
|
+
const light = lightProp ?? isLight;
|
|
113
|
+
const theme = getSyntaxTheme();
|
|
114
|
+
const bg = "bg-card border-border";
|
|
115
|
+
const headerBg = light ? "bg-muted/50 border-border" : "bg-background border-border";
|
|
116
|
+
const langColor = "text-muted-foreground";
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
className={cn("group relative overflow-hidden rounded-lg border font-mono", bg, className)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
{language && (
|
|
124
|
+
<div className={cn("flex items-center justify-between border-b px-3 py-1", headerBg)}>
|
|
125
|
+
<span className={cn("text-[calc(var(--font-size-xs)-1px)] font-mono font-medium uppercase tracking-widest", langColor)}>
|
|
126
|
+
{language}
|
|
127
|
+
</span>
|
|
128
|
+
{children}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
{!language && children && (
|
|
132
|
+
<div className="absolute right-2 top-2 z-10 flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100">
|
|
133
|
+
{children}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
<SyntaxHighlighter
|
|
137
|
+
language={language ?? "text"}
|
|
138
|
+
style={theme}
|
|
139
|
+
showLineNumbers={showLineNumbers}
|
|
140
|
+
lineNumberStyle={{
|
|
141
|
+
color: light ? "#8B92B8" : "#4A4D6A",
|
|
142
|
+
minWidth: "2.5em",
|
|
143
|
+
paddingRight: "1em",
|
|
144
|
+
}}
|
|
145
|
+
customStyle={{
|
|
146
|
+
margin: 0,
|
|
147
|
+
padding: "var(--code-padding-y, 0.625rem) var(--code-padding-x, 0.75rem)",
|
|
148
|
+
background: "transparent",
|
|
149
|
+
fontSize: "var(--code-font-size, 0.8125rem)",
|
|
150
|
+
lineHeight: "var(--code-line-height, 1.5)",
|
|
151
|
+
overflowX: "auto",
|
|
152
|
+
}}
|
|
153
|
+
codeTagProps={{ style: { fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)" } }}
|
|
154
|
+
wrapLines={false}
|
|
155
|
+
>
|
|
156
|
+
{code}
|
|
157
|
+
</SyntaxHighlighter>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
CodeBlock.displayName = "CodeBlock";
|
|
163
|
+
|
|
164
|
+
/** Copy-to-clipboard button for use inside CodeBlock. */
|
|
165
|
+
export const CopyButton = memo(({ text }: { text: string }) => {
|
|
166
|
+
const [copied, setCopied] = useState(false);
|
|
167
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
return () => { if (timerRef.current) clearTimeout(timerRef.current); };
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
const handleCopy = useCallback(async () => {
|
|
174
|
+
try {
|
|
175
|
+
await navigator.clipboard.writeText(text);
|
|
176
|
+
setCopied(true);
|
|
177
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
178
|
+
timerRef.current = setTimeout(() => setCopied(false), 2000);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.warn("Clipboard write failed:", err);
|
|
181
|
+
}
|
|
182
|
+
}, [text]);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<button
|
|
186
|
+
onClick={handleCopy}
|
|
187
|
+
className="flex items-center justify-center w-6 h-6 rounded-md bg-muted border border-border hover:border-primary/20 transition-colors"
|
|
188
|
+
title="Copy to clipboard"
|
|
189
|
+
>
|
|
190
|
+
{copied ? (
|
|
191
|
+
<Check className="w-3.5 h-3.5 text-emerald-500" />
|
|
192
|
+
) : (
|
|
193
|
+
<Copy className="w-3.5 h-3.5 text-muted-foreground" />
|
|
194
|
+
)}
|
|
195
|
+
</button>
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
CopyButton.displayName = "CopyButton";
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { Markdown } from './markdown'
|
|
3
|
+
import { CodeBlock, CopyButton } from './code-block'
|
|
4
|
+
|
|
5
|
+
// ─── Markdown ────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const markdownMeta: Meta<typeof Markdown> = {
|
|
8
|
+
title: 'Markdown/Markdown',
|
|
9
|
+
component: Markdown,
|
|
10
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => (
|
|
13
|
+
<div className="w-[680px] p-6 rounded-xl bg-card text-foreground">
|
|
14
|
+
<Story />
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default markdownMeta
|
|
21
|
+
type Story = StoryObj<typeof Markdown>
|
|
22
|
+
|
|
23
|
+
export const Prose: Story = {
|
|
24
|
+
name: 'Prose',
|
|
25
|
+
args: {
|
|
26
|
+
children: `# Getting Started
|
|
27
|
+
|
|
28
|
+
Welcome to **sandbox-ui**. This library provides a set of composable components
|
|
29
|
+
for building agent-powered interfaces.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
\`\`\`bash
|
|
34
|
+
pnpm add @tangle/sandbox-ui
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Import the components you need:
|
|
40
|
+
|
|
41
|
+
\`\`\`tsx
|
|
42
|
+
import { ChatInput, DropZone, UploadProgress } from '@tangle/sandbox-ui'
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
> **Note:** Components assume a Tailwind CSS v4 setup with the design tokens
|
|
46
|
+
> provided by the sandbox theme.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- Composable chat input with drag-and-drop
|
|
51
|
+
- File upload progress indicators
|
|
52
|
+
- Full-window and sidebar drop zones
|
|
53
|
+
- Dashboard primitives for sandbox monitoring
|
|
54
|
+
`,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const InlineCode: Story = {
|
|
59
|
+
name: 'Inline code',
|
|
60
|
+
args: {
|
|
61
|
+
children: `Use the \`onDrop\` prop to handle dropped files. The \`accept\` prop takes a comma-separated
|
|
62
|
+
list of extensions like \`.pdf,.csv,.xlsx\`. When \`disabled\` is \`true\`, all drag events are ignored.`,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Table: Story = {
|
|
67
|
+
name: 'GFM table',
|
|
68
|
+
args: {
|
|
69
|
+
children: `| Prop | Type | Default | Description |
|
|
70
|
+
|------|------|---------|-------------|
|
|
71
|
+
| \`onDrop\` | \`(files: File[]) => void\` | — | Drop handler |
|
|
72
|
+
| \`accept\` | \`string\` | \`undefined\` | Accepted extensions |
|
|
73
|
+
| \`disabled\` | \`boolean\` | \`false\` | Disable drop zone |
|
|
74
|
+
| \`title\` | \`string\` | \`"Drop files to upload"\` | Overlay title |
|
|
75
|
+
`,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const CodeFences: Story = {
|
|
80
|
+
name: 'Code fences (multiple languages)',
|
|
81
|
+
args: {
|
|
82
|
+
children: `### TypeScript
|
|
83
|
+
|
|
84
|
+
\`\`\`typescript
|
|
85
|
+
interface DropZoneProps {
|
|
86
|
+
onDrop: (files: File[]) => void
|
|
87
|
+
accept?: string
|
|
88
|
+
disabled?: boolean
|
|
89
|
+
children: ReactNode
|
|
90
|
+
}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
### Shell
|
|
94
|
+
|
|
95
|
+
\`\`\`bash
|
|
96
|
+
pnpm build && pnpm storybook
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
### JSON
|
|
100
|
+
|
|
101
|
+
\`\`\`json
|
|
102
|
+
{
|
|
103
|
+
"name": "@tangle/sandbox-ui",
|
|
104
|
+
"version": "0.4.0",
|
|
105
|
+
"type": "module"
|
|
106
|
+
}
|
|
107
|
+
\`\`\`
|
|
108
|
+
`,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const ListsAndTaskItems: Story = {
|
|
113
|
+
name: 'Lists & task items',
|
|
114
|
+
args: {
|
|
115
|
+
children: `## Checklist
|
|
116
|
+
|
|
117
|
+
- [x] Drop zone overlay
|
|
118
|
+
- [x] Sidebar drop zone
|
|
119
|
+
- [x] Upload progress indicators
|
|
120
|
+
- [ ] Resumable uploads
|
|
121
|
+
- [ ] Multi-part S3 upload
|
|
122
|
+
|
|
123
|
+
## Unordered list
|
|
124
|
+
|
|
125
|
+
- Item one
|
|
126
|
+
- Item two
|
|
127
|
+
- Nested item
|
|
128
|
+
- Another nested item
|
|
129
|
+
- Item three
|
|
130
|
+
|
|
131
|
+
## Ordered list
|
|
132
|
+
|
|
133
|
+
1. Install dependencies
|
|
134
|
+
2. Configure theme tokens
|
|
135
|
+
3. Wrap your app with providers
|
|
136
|
+
4. Import components
|
|
137
|
+
`,
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const BlockquoteAndHR: Story = {
|
|
142
|
+
name: 'Blockquote & HR',
|
|
143
|
+
args: {
|
|
144
|
+
children: `> This component is designed to be a drop-in replacement for any file upload flow.
|
|
145
|
+
> It handles edge cases like nested drag events, accept filtering, and disabled states.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
Regular paragraph after a horizontal rule.
|
|
150
|
+
`,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── CodeBlock ───────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
export const CodeBlockOnly: Story = {
|
|
157
|
+
name: 'CodeBlock standalone',
|
|
158
|
+
render: () => (
|
|
159
|
+
<div className="w-[680px] p-6 rounded-xl bg-card space-y-4">
|
|
160
|
+
<CodeBlock code={`import { DropZone } from '@tangle/sandbox-ui'\n\nfunction App() {\n return (\n <DropZone onDrop={files => upload(files)}>\n <Dashboard />\n </DropZone>\n )\n}`} language="tsx">
|
|
161
|
+
<CopyButton text="import { DropZone } from '@tangle/sandbox-ui'" />
|
|
162
|
+
</CodeBlock>
|
|
163
|
+
<CodeBlock code={`pnpm add @tangle/sandbox-ui`} language="bash">
|
|
164
|
+
<CopyButton text="pnpm add @tangle/sandbox-ui" />
|
|
165
|
+
</CodeBlock>
|
|
166
|
+
</div>
|
|
167
|
+
),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const CodeBlockNoLanguage: Story = {
|
|
171
|
+
name: 'CodeBlock — no language',
|
|
172
|
+
render: () => (
|
|
173
|
+
<div className="w-[680px] p-6 rounded-xl bg-card">
|
|
174
|
+
<CodeBlock code="DROP TABLE users; -- don't run this" />
|
|
175
|
+
</div>
|
|
176
|
+
),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const CodeBlockLight: Story = {
|
|
180
|
+
name: 'CodeBlock — light theme',
|
|
181
|
+
parameters: { backgrounds: { default: 'light' } },
|
|
182
|
+
render: () => (
|
|
183
|
+
<div className="w-[680px] p-6 rounded-xl bg-white space-y-4">
|
|
184
|
+
<CodeBlock light code={`interface SandboxConfig {\n model: string\n timeout: number\n env: Record<string, string>\n}\n\n// Create a new sandbox\nconst sandbox = await Sandbox.create(config)`} language="typescript">
|
|
185
|
+
<CopyButton text="interface SandboxConfig" />
|
|
186
|
+
</CodeBlock>
|
|
187
|
+
<CodeBlock light code={`pnpm add @tangle-network/sandbox-ui`} language="bash" />
|
|
188
|
+
</div>
|
|
189
|
+
),
|
|
190
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import rehypeSanitize from "rehype-sanitize";
|
|
5
|
+
import { CodeBlock, CopyButton } from "./code-block";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
export interface MarkdownProps {
|
|
9
|
+
children: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Renders Markdown content with GFM support, XSS sanitisation, and
|
|
15
|
+
* custom code block rendering via our CodeBlock component.
|
|
16
|
+
*/
|
|
17
|
+
export const Markdown = memo(({ children, className }: MarkdownProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className={cn("prose prose-sm dark:prose-invert max-w-none", className)}
|
|
21
|
+
>
|
|
22
|
+
<ReactMarkdown
|
|
23
|
+
remarkPlugins={[remarkGfm]}
|
|
24
|
+
rehypePlugins={[rehypeSanitize]}
|
|
25
|
+
components={{
|
|
26
|
+
pre({ children: preChildren }) {
|
|
27
|
+
return <>{preChildren}</>;
|
|
28
|
+
},
|
|
29
|
+
code({ className: codeClass, children: codeChildren, ...rest }) {
|
|
30
|
+
const match = /language-(\w+)/.exec(codeClass || "");
|
|
31
|
+
const language = match?.[1];
|
|
32
|
+
const code = String(codeChildren).replace(/\n$/, "");
|
|
33
|
+
|
|
34
|
+
// Inline code (no language fence)
|
|
35
|
+
if (!language && !code.includes("\n")) {
|
|
36
|
+
return (
|
|
37
|
+
<code
|
|
38
|
+
className={cn(
|
|
39
|
+
"px-1.5 py-0.5 rounded border border-border bg-card text-[var(--code-keyword)] text-[0.85em] font-mono",
|
|
40
|
+
codeClass,
|
|
41
|
+
)}
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
44
|
+
{codeChildren}
|
|
45
|
+
</code>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<CodeBlock code={code} language={language}>
|
|
51
|
+
<CopyButton text={code} />
|
|
52
|
+
</CodeBlock>
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</ReactMarkdown>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
Markdown.displayName = "Markdown";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
OpenUIArtifactRenderer,
|
|
3
|
+
type OpenUIArtifactRendererProps,
|
|
4
|
+
type OpenUIAction,
|
|
5
|
+
type OpenUIPrimitive,
|
|
6
|
+
type OpenUIComponentNode,
|
|
7
|
+
type OpenUIHeadingNode,
|
|
8
|
+
type OpenUITextNode,
|
|
9
|
+
type OpenUIBadgeNode,
|
|
10
|
+
type OpenUIStatNode,
|
|
11
|
+
type OpenUIKeyValueNode,
|
|
12
|
+
type OpenUICodeNode,
|
|
13
|
+
type OpenUIMarkdownNode,
|
|
14
|
+
type OpenUITableNode,
|
|
15
|
+
type OpenUIActionsNode,
|
|
16
|
+
type OpenUISeparatorNode,
|
|
17
|
+
type OpenUIStackNode,
|
|
18
|
+
type OpenUIGridNode,
|
|
19
|
+
type OpenUICardNode,
|
|
20
|
+
} from "./openui-artifact-renderer";
|