@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,71 @@
|
|
|
1
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
const Tabs = TabsPrimitive.Root;
|
|
6
|
+
|
|
7
|
+
const TabsList = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
|
|
10
|
+
variant?: "default" | "pills" | "underline";
|
|
11
|
+
}
|
|
12
|
+
>(({ className, variant = "default", ...props }, ref) => {
|
|
13
|
+
const variants = {
|
|
14
|
+
default:
|
|
15
|
+
"inline-flex h-10 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
|
16
|
+
pills: "inline-flex items-center gap-2",
|
|
17
|
+
underline: "inline-flex items-center gap-4 border-b border-border",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<TabsPrimitive.List
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(variants[variant], className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
29
|
+
|
|
30
|
+
const TabsTrigger = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
|
|
33
|
+
variant?: "default" | "pills" | "underline";
|
|
34
|
+
}
|
|
35
|
+
>(({ className, variant = "default", ...props }, ref) => {
|
|
36
|
+
const variants = {
|
|
37
|
+
default:
|
|
38
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
39
|
+
pills:
|
|
40
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:bg-muted",
|
|
41
|
+
underline:
|
|
42
|
+
"inline-flex items-center justify-center whitespace-nowrap pb-3 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:text-foreground data-[state=inactive]:text-muted-foreground",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<TabsPrimitive.Trigger
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(variants[variant], className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
54
|
+
|
|
55
|
+
const TabsContent = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
57
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<TabsPrimitive.Content
|
|
60
|
+
ref={ref}
|
|
61
|
+
className={cn(
|
|
62
|
+
"mt-4 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
63
|
+
"data-[state=active]:fade-in-0 data-[state=active]:slide-in-from-bottom-2 data-[state=active]:animate-in",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
));
|
|
69
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
70
|
+
|
|
71
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
TerminalCursor,
|
|
5
|
+
TerminalDisplay,
|
|
6
|
+
TerminalInput,
|
|
7
|
+
TerminalLine,
|
|
8
|
+
} from './terminal-display'
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof TerminalDisplay> = {
|
|
11
|
+
title: 'Primitives/TerminalDisplay',
|
|
12
|
+
component: TerminalDisplay,
|
|
13
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
type Story = StoryObj<typeof TerminalDisplay>
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
name: 'Default',
|
|
21
|
+
render: () => (
|
|
22
|
+
<TerminalDisplay title="bash — sess_01j9x8k2m" className="w-[640px]">
|
|
23
|
+
<TerminalLine type="command">npm install</TerminalLine>
|
|
24
|
+
<TerminalLine type="output">
|
|
25
|
+
{'added 312 packages, and audited 313 packages in 4s'}
|
|
26
|
+
</TerminalLine>
|
|
27
|
+
<TerminalLine type="output">{'found 0 vulnerabilities'}</TerminalLine>
|
|
28
|
+
<TerminalLine type="command">npm run build</TerminalLine>
|
|
29
|
+
<TerminalLine type="output">
|
|
30
|
+
{'\n> sandbox-app@1.0.0 build\n> tsc && vite build'}
|
|
31
|
+
</TerminalLine>
|
|
32
|
+
<TerminalLine type="success">
|
|
33
|
+
{'✓ built in 2.14s'}
|
|
34
|
+
</TerminalLine>
|
|
35
|
+
<TerminalLine type="command">
|
|
36
|
+
node dist/index.js<TerminalCursor />
|
|
37
|
+
</TerminalLine>
|
|
38
|
+
</TerminalDisplay>
|
|
39
|
+
),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const SandboxVariant: Story = {
|
|
43
|
+
name: 'Sandbox Variant',
|
|
44
|
+
render: () => (
|
|
45
|
+
<TerminalDisplay
|
|
46
|
+
variant="sandbox"
|
|
47
|
+
title="agent — sess_01j9x8k2m"
|
|
48
|
+
className="w-[640px]"
|
|
49
|
+
>
|
|
50
|
+
<TerminalLine type="info" timestamp="14:02:11">
|
|
51
|
+
Session initialized · node:20-alpine · us-east-1
|
|
52
|
+
</TerminalLine>
|
|
53
|
+
<TerminalLine type="command">
|
|
54
|
+
git clone https://github.com/acme/api-service.git
|
|
55
|
+
</TerminalLine>
|
|
56
|
+
<TerminalLine type="output">
|
|
57
|
+
{'Cloning into \'api-service\'...\nremote: Enumerating objects: 1847, done.'}
|
|
58
|
+
</TerminalLine>
|
|
59
|
+
<TerminalLine type="output">
|
|
60
|
+
{'Receiving objects: 100% (1847/1847), 2.31 MiB | 14.2 MiB/s, done.'}
|
|
61
|
+
</TerminalLine>
|
|
62
|
+
<TerminalLine type="command">cd api-service && npm ci</TerminalLine>
|
|
63
|
+
<TerminalLine type="output">
|
|
64
|
+
{'added 421 packages in 6s'}
|
|
65
|
+
</TerminalLine>
|
|
66
|
+
<TerminalLine type="command">npm test -- --reporter=dot</TerminalLine>
|
|
67
|
+
<TerminalLine type="output">
|
|
68
|
+
{'............................................'}
|
|
69
|
+
</TerminalLine>
|
|
70
|
+
<TerminalLine type="success">
|
|
71
|
+
{'✓ 44 tests passed (2.8s)'}
|
|
72
|
+
</TerminalLine>
|
|
73
|
+
<TerminalLine type="thinking">
|
|
74
|
+
Analyzing test coverage...
|
|
75
|
+
</TerminalLine>
|
|
76
|
+
</TerminalDisplay>
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const WithErrors: Story = {
|
|
81
|
+
name: 'With Errors',
|
|
82
|
+
render: () => (
|
|
83
|
+
<TerminalDisplay title="bash — sess_01j9x7r9" className="w-[640px]">
|
|
84
|
+
<TerminalLine type="command">python main.py</TerminalLine>
|
|
85
|
+
<TerminalLine type="output">Loading model weights...</TerminalLine>
|
|
86
|
+
<TerminalLine type="error">
|
|
87
|
+
{'Traceback (most recent call last):\n File "main.py", line 12, in <module>\n model = load_checkpoint(args.ckpt)'}
|
|
88
|
+
</TerminalLine>
|
|
89
|
+
<TerminalLine type="error">
|
|
90
|
+
{'FileNotFoundError: [Errno 2] No such file or directory: \'model.pt\''}
|
|
91
|
+
</TerminalLine>
|
|
92
|
+
<TerminalLine type="warning">
|
|
93
|
+
Checkpoint not found. Run download_weights.sh first.
|
|
94
|
+
</TerminalLine>
|
|
95
|
+
<TerminalLine type="command">
|
|
96
|
+
./download_weights.sh<TerminalCursor />
|
|
97
|
+
</TerminalLine>
|
|
98
|
+
</TerminalDisplay>
|
|
99
|
+
),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const AgentSession: Story = {
|
|
103
|
+
name: 'Agent Session',
|
|
104
|
+
render: () => (
|
|
105
|
+
<TerminalDisplay
|
|
106
|
+
variant="sandbox"
|
|
107
|
+
title="Claude — task execution"
|
|
108
|
+
className="w-[640px]"
|
|
109
|
+
maxHeight="320px"
|
|
110
|
+
>
|
|
111
|
+
<TerminalLine type="info" timestamp="14:49:58">
|
|
112
|
+
Task: Write and run unit tests for auth module
|
|
113
|
+
</TerminalLine>
|
|
114
|
+
<TerminalLine type="thinking">Reading src/auth/jwt.ts...</TerminalLine>
|
|
115
|
+
<TerminalLine type="output">Found 4 exported functions to test</TerminalLine>
|
|
116
|
+
<TerminalLine type="command">
|
|
117
|
+
{'cat > src/auth/__tests__/jwt.test.ts << \'EOF\''}
|
|
118
|
+
</TerminalLine>
|
|
119
|
+
<TerminalLine type="output">
|
|
120
|
+
{'Writing 6 test cases for: signToken, verifyToken, refreshToken, revokeToken'}
|
|
121
|
+
</TerminalLine>
|
|
122
|
+
<TerminalLine type="command">npx vitest run src/auth/__tests__/jwt.test.ts</TerminalLine>
|
|
123
|
+
<TerminalLine type="output">
|
|
124
|
+
{'✓ signToken returns a valid JWT (12ms)\n✓ verifyToken accepts valid tokens (3ms)\n✓ verifyToken rejects expired tokens (2ms)\n✓ refreshToken issues new token (8ms)\n✗ revokeToken marks token invalid (5ms)'}
|
|
125
|
+
</TerminalLine>
|
|
126
|
+
<TerminalLine type="error">
|
|
127
|
+
AssertionError: Expected token to be revoked but found status: "active"
|
|
128
|
+
</TerminalLine>
|
|
129
|
+
<TerminalLine type="thinking">
|
|
130
|
+
Investigating revocation logic...
|
|
131
|
+
</TerminalLine>
|
|
132
|
+
<TerminalLine type="output">
|
|
133
|
+
Found bug: redis.del() call missing await. Patching...
|
|
134
|
+
</TerminalLine>
|
|
135
|
+
<TerminalLine type="command">npx vitest run src/auth/__tests__/jwt.test.ts</TerminalLine>
|
|
136
|
+
<TerminalLine type="success">
|
|
137
|
+
{'✓ 6 tests passed (31ms)'}
|
|
138
|
+
</TerminalLine>
|
|
139
|
+
<TerminalLine type="info">Task complete. Patch written to src/auth/jwt.ts:L84.</TerminalLine>
|
|
140
|
+
</TerminalDisplay>
|
|
141
|
+
),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const NoHeader: Story = {
|
|
145
|
+
name: 'No Header',
|
|
146
|
+
render: () => (
|
|
147
|
+
<TerminalDisplay showHeader={false} className="w-[480px]">
|
|
148
|
+
<TerminalLine type="command">ls -la /workspace</TerminalLine>
|
|
149
|
+
<TerminalLine type="output">
|
|
150
|
+
{'total 48\ndrwxr-xr-x 8 node node 4096 Mar 30 14:02 .\ndrwxr-xr-x 3 root root 4096 Mar 30 14:00 ..\n-rw-r--r-- 1 node node 842 Mar 30 14:01 package.json\ndrwxr-xr-x 4 node node 4096 Mar 30 14:02 src'}
|
|
151
|
+
</TerminalLine>
|
|
152
|
+
<TerminalLine type="command">
|
|
153
|
+
{'cat package.json | jq \'.scripts\''}
|
|
154
|
+
</TerminalLine>
|
|
155
|
+
<TerminalLine type="output">
|
|
156
|
+
{'{\n "build": "tsc && vite build",\n "test": "vitest",\n "dev": "vite"\n}'}
|
|
157
|
+
</TerminalLine>
|
|
158
|
+
</TerminalDisplay>
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const WithInput: Story = {
|
|
163
|
+
name: 'With Input',
|
|
164
|
+
render: () => {
|
|
165
|
+
const [lines, setLines] = useState<{ type: 'info' | 'command' | 'output' | 'error' | 'success' | 'warning' | 'thinking'; text: string }[]>([
|
|
166
|
+
{ type: 'info', text: 'Session ready · node:20-alpine' },
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className="flex flex-col gap-2 w-[560px]">
|
|
171
|
+
<TerminalDisplay title="interactive shell" maxHeight="240px">
|
|
172
|
+
{lines.map((l, i) => (
|
|
173
|
+
<TerminalLine key={i} type={l.type}>
|
|
174
|
+
{l.text}
|
|
175
|
+
</TerminalLine>
|
|
176
|
+
))}
|
|
177
|
+
</TerminalDisplay>
|
|
178
|
+
<TerminalInput
|
|
179
|
+
placeholder="Enter a command..."
|
|
180
|
+
onSubmit={(cmd) => {
|
|
181
|
+
setLines((prev) => [
|
|
182
|
+
...prev,
|
|
183
|
+
{ type: 'command', text: cmd },
|
|
184
|
+
{ type: 'output', text: `[${cmd}] executed` },
|
|
185
|
+
])
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
},
|
|
191
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
export interface TerminalDisplayProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: "default" | "sandbox";
|
|
7
|
+
title?: string;
|
|
8
|
+
showHeader?: boolean;
|
|
9
|
+
autoScroll?: boolean;
|
|
10
|
+
maxHeight?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TerminalDisplay = React.forwardRef<HTMLDivElement, TerminalDisplayProps>(
|
|
14
|
+
(
|
|
15
|
+
{
|
|
16
|
+
className,
|
|
17
|
+
variant = "default",
|
|
18
|
+
title = "Terminal",
|
|
19
|
+
showHeader = true,
|
|
20
|
+
autoScroll = true,
|
|
21
|
+
maxHeight = "400px",
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
},
|
|
25
|
+
ref,
|
|
26
|
+
) => {
|
|
27
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (autoScroll && containerRef.current) {
|
|
31
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
32
|
+
}
|
|
33
|
+
}, [autoScroll]);
|
|
34
|
+
|
|
35
|
+
const variants = {
|
|
36
|
+
default: "border-border",
|
|
37
|
+
sandbox: "border-border shadow-[var(--shadow-accent)]",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(
|
|
44
|
+
"overflow-hidden rounded-xl border bg-background font-mono text-sm",
|
|
45
|
+
variants[variant],
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{showHeader && (
|
|
51
|
+
<div className="flex items-center border-b border-border bg-card px-4 py-3">
|
|
52
|
+
<span className="text-muted-foreground text-xs">{title}</span>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
<div
|
|
56
|
+
ref={containerRef}
|
|
57
|
+
className="overflow-auto p-4"
|
|
58
|
+
style={{ maxHeight }}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
TerminalDisplay.displayName = "TerminalDisplay";
|
|
67
|
+
|
|
68
|
+
export interface TerminalLineProps
|
|
69
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
70
|
+
type?:
|
|
71
|
+
| "input"
|
|
72
|
+
| "output"
|
|
73
|
+
| "error"
|
|
74
|
+
| "success"
|
|
75
|
+
| "info"
|
|
76
|
+
| "thinking"
|
|
77
|
+
| "command"
|
|
78
|
+
| "warning";
|
|
79
|
+
prompt?: string;
|
|
80
|
+
timestamp?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const TerminalLine = React.forwardRef<HTMLDivElement, TerminalLineProps>(
|
|
84
|
+
(
|
|
85
|
+
{ className, type = "output", prompt = "$", timestamp, children, ...props },
|
|
86
|
+
ref,
|
|
87
|
+
) => {
|
|
88
|
+
const typeStyles = {
|
|
89
|
+
input: "text-foreground",
|
|
90
|
+
output: "text-foreground",
|
|
91
|
+
error: "text-[var(--surface-danger-text)]",
|
|
92
|
+
success: "text-[var(--surface-success-text)]",
|
|
93
|
+
info: "text-[var(--surface-info-text)]",
|
|
94
|
+
thinking: "text-[var(--surface-warning-text)] animate-pulse",
|
|
95
|
+
command: "text-foreground",
|
|
96
|
+
warning: "text-[var(--surface-warning-text)]",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={cn(
|
|
103
|
+
"flex items-start gap-2 py-0.5 leading-relaxed",
|
|
104
|
+
typeStyles[type],
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
{(type === "input" || type === "command") && (
|
|
110
|
+
<span className="shrink-0 select-none text-[var(--surface-success-text)]">{prompt}</span>
|
|
111
|
+
)}
|
|
112
|
+
{type === "thinking" && (
|
|
113
|
+
<span className="shrink-0 select-none">...</span>
|
|
114
|
+
)}
|
|
115
|
+
{timestamp && (
|
|
116
|
+
<span className="shrink-0 select-none text-muted-foreground opacity-50">
|
|
117
|
+
[{timestamp}]
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
<span className="whitespace-pre-wrap break-all">{children}</span>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
TerminalLine.displayName = "TerminalLine";
|
|
126
|
+
|
|
127
|
+
const TerminalCursor = React.forwardRef<
|
|
128
|
+
HTMLSpanElement,
|
|
129
|
+
React.HTMLAttributes<HTMLSpanElement>
|
|
130
|
+
>(({ className, ...props }, ref) => (
|
|
131
|
+
<span
|
|
132
|
+
ref={ref}
|
|
133
|
+
className={cn(
|
|
134
|
+
"ml-0.5 inline-block h-4 w-2 animate-pulse bg-foreground",
|
|
135
|
+
className,
|
|
136
|
+
)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
));
|
|
140
|
+
TerminalCursor.displayName = "TerminalCursor";
|
|
141
|
+
|
|
142
|
+
export interface TerminalInputProps
|
|
143
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onSubmit"> {
|
|
144
|
+
onSubmit?: (value: string) => void;
|
|
145
|
+
variant?: "default" | "sandbox";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const TerminalInput = React.forwardRef<HTMLInputElement, TerminalInputProps>(
|
|
149
|
+
({ className, onSubmit, variant = "default", ...props }, ref) => {
|
|
150
|
+
const [value, setValue] = React.useState("");
|
|
151
|
+
|
|
152
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
153
|
+
if (e.key === "Enter" && value.trim() && onSubmit) {
|
|
154
|
+
onSubmit(value.trim());
|
|
155
|
+
setValue("");
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const variants = {
|
|
160
|
+
default: "border-border focus-within:border-border",
|
|
161
|
+
sandbox: "border-border focus-within:border-[var(--border-accent-hover)]",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div
|
|
166
|
+
className={cn(
|
|
167
|
+
"flex items-center rounded-lg border bg-background px-4 py-2.5 font-mono text-sm transition-colors",
|
|
168
|
+
variants[variant],
|
|
169
|
+
className,
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
<span className="mr-2 select-none text-[var(--surface-success-text)]">$</span>
|
|
173
|
+
<input
|
|
174
|
+
ref={ref}
|
|
175
|
+
type="text"
|
|
176
|
+
value={value}
|
|
177
|
+
onChange={(e) => setValue(e.target.value)}
|
|
178
|
+
onKeyDown={handleKeyDown}
|
|
179
|
+
className="flex-1 bg-transparent text-foreground outline-none placeholder:text-muted-foreground"
|
|
180
|
+
{...props}
|
|
181
|
+
/>
|
|
182
|
+
<TerminalCursor />
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
TerminalInput.displayName = "TerminalInput";
|
|
188
|
+
|
|
189
|
+
export { TerminalDisplay, TerminalLine, TerminalCursor, TerminalInput };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { ThemeToggle } from './theme-toggle'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ThemeToggle> = {
|
|
5
|
+
title: 'Primitives/ThemeToggle',
|
|
6
|
+
component: ThemeToggle,
|
|
7
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default meta
|
|
11
|
+
type Story = StoryObj<typeof ThemeToggle>
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {}
|
|
14
|
+
|
|
15
|
+
export const InToolbar: Story = {
|
|
16
|
+
name: 'In Toolbar',
|
|
17
|
+
render: () => (
|
|
18
|
+
<div className="flex items-center gap-1 rounded-lg border border-border bg-card px-2 py-1">
|
|
19
|
+
<button className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground">
|
|
20
|
+
Sessions
|
|
21
|
+
</button>
|
|
22
|
+
<button className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground">
|
|
23
|
+
Logs
|
|
24
|
+
</button>
|
|
25
|
+
<button className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground">
|
|
26
|
+
Billing
|
|
27
|
+
</button>
|
|
28
|
+
<div className="mx-2 h-4 w-px bg-border" />
|
|
29
|
+
<ThemeToggle />
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
type Theme = "light" | "dark" | "system";
|
|
4
|
+
|
|
5
|
+
function getSystemTheme(): "light" | "dark" {
|
|
6
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
7
|
+
? "dark"
|
|
8
|
+
: "light";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function applyTheme(theme: Theme) {
|
|
12
|
+
const resolved = theme === "system" ? getSystemTheme() : theme;
|
|
13
|
+
document.documentElement.classList.toggle("dark", resolved === "dark");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useTheme() {
|
|
17
|
+
const [theme, setThemeState] = useState<Theme>(() => {
|
|
18
|
+
if (typeof window === "undefined") return "system";
|
|
19
|
+
return (localStorage.getItem("theme") as Theme) ?? "system";
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const setTheme = useCallback((next: Theme) => {
|
|
23
|
+
setThemeState(next);
|
|
24
|
+
if (next === "system") {
|
|
25
|
+
localStorage.removeItem("theme");
|
|
26
|
+
} else {
|
|
27
|
+
localStorage.setItem("theme", next);
|
|
28
|
+
}
|
|
29
|
+
applyTheme(next);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
applyTheme(theme);
|
|
34
|
+
if (theme !== "system") return;
|
|
35
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
36
|
+
const handler = () => applyTheme("system");
|
|
37
|
+
mq.addEventListener("change", handler);
|
|
38
|
+
return () => mq.removeEventListener("change", handler);
|
|
39
|
+
}, [theme]);
|
|
40
|
+
|
|
41
|
+
return { theme, setTheme };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const iconClass = "h-4 w-4";
|
|
45
|
+
|
|
46
|
+
function SunIcon() {
|
|
47
|
+
return (
|
|
48
|
+
<svg
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
strokeWidth={2}
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
className={iconClass}
|
|
57
|
+
>
|
|
58
|
+
<circle cx={12} cy={12} r={5} />
|
|
59
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
|
|
60
|
+
</svg>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function MoonIcon() {
|
|
65
|
+
return (
|
|
66
|
+
<svg
|
|
67
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
68
|
+
viewBox="0 0 24 24"
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth={2}
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
strokeLinejoin="round"
|
|
74
|
+
className={iconClass}
|
|
75
|
+
>
|
|
76
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
77
|
+
</svg>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function ThemeToggle() {
|
|
82
|
+
const { theme, setTheme } = useTheme();
|
|
83
|
+
const resolved =
|
|
84
|
+
theme === "system" ? getSystemTheme() : theme;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={() => setTheme(resolved === "dark" ? "light" : "dark")}
|
|
90
|
+
className="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
91
|
+
aria-label={`Switch to ${resolved === "dark" ? "light" : "dark"} mode`}
|
|
92
|
+
>
|
|
93
|
+
{resolved === "dark" ? <SunIcon /> : <MoonIcon />}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|