@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,123 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { Upload, FileText, ImageIcon } from 'lucide-react'
|
|
3
|
+
import { DropZone } from './drop-zone'
|
|
4
|
+
|
|
5
|
+
const AppContent = () => (
|
|
6
|
+
<div className="w-[600px] h-[380px] flex items-center justify-center rounded-xl border border-dashed border-border text-muted-foreground text-sm bg-muted">
|
|
7
|
+
App content area — drag files over this window
|
|
8
|
+
</div>
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof DropZone> = {
|
|
12
|
+
title: 'Primitives/DropZone',
|
|
13
|
+
component: DropZone,
|
|
14
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
15
|
+
args: {
|
|
16
|
+
onDrop: (files) => console.log('dropped', files),
|
|
17
|
+
children: <AppContent />,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
22
|
+
type Story = StoryObj<typeof DropZone>
|
|
23
|
+
|
|
24
|
+
export const Idle: Story = {
|
|
25
|
+
name: 'Idle (no drag)',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const WithAcceptFilter: Story = {
|
|
29
|
+
name: 'With Accept Filter (.pdf, .csv)',
|
|
30
|
+
args: {
|
|
31
|
+
accept: '.pdf,.csv',
|
|
32
|
+
title: 'Drop CSV or PDF files',
|
|
33
|
+
description: 'Only .pdf and .csv files will be accepted.',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const Disabled: Story = {
|
|
38
|
+
name: 'Disabled',
|
|
39
|
+
args: { disabled: true },
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Static render of the default drop overlay — the real one only mounts during dragenter. */
|
|
43
|
+
export const OverlayDefault: Story = {
|
|
44
|
+
name: 'Overlay — Default',
|
|
45
|
+
render: () => (
|
|
46
|
+
<div className="relative w-[600px] h-[380px]">
|
|
47
|
+
<AppContent />
|
|
48
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-background/80">
|
|
49
|
+
<div className="rounded-2xl border-2 border-dashed border-border bg-card p-12 text-center max-w-sm w-full mx-4">
|
|
50
|
+
<div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-[var(--accent-surface-soft)]">
|
|
51
|
+
<Upload className="h-8 w-8 text-[var(--accent-text)]" />
|
|
52
|
+
</div>
|
|
53
|
+
<h2 className="text-xl font-bold text-foreground">Drop files to upload</h2>
|
|
54
|
+
<p className="mt-2 text-sm text-muted-foreground">Your files will be securely stored in the workspace.</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const OverlayFiltered: Story = {
|
|
62
|
+
name: 'Overlay — Accept filter (.pdf, .csv)',
|
|
63
|
+
render: () => (
|
|
64
|
+
<div className="relative w-[600px] h-[380px]">
|
|
65
|
+
<AppContent />
|
|
66
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-background/80">
|
|
67
|
+
<div className="rounded-2xl border-2 border-dashed border-border bg-card p-12 text-center max-w-sm w-full mx-4">
|
|
68
|
+
<div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-[var(--accent-surface-soft)]">
|
|
69
|
+
<FileText className="h-8 w-8 text-[var(--accent-text)]" />
|
|
70
|
+
</div>
|
|
71
|
+
<h2 className="text-xl font-bold text-foreground">Drop CSV or PDF files</h2>
|
|
72
|
+
<p className="mt-2 text-sm text-muted-foreground">Only .pdf and .csv files will be accepted.</p>
|
|
73
|
+
<div className="mt-4 flex items-center justify-center gap-2">
|
|
74
|
+
<span className="rounded-full border border-border bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-mono text-[var(--accent-text)]">.pdf</span>
|
|
75
|
+
<span className="rounded-full border border-border bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-mono text-[var(--accent-text)]">.csv</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const OverlayImages: Story = {
|
|
84
|
+
name: 'Overlay — Images',
|
|
85
|
+
render: () => (
|
|
86
|
+
<div className="relative w-[600px] h-[380px]">
|
|
87
|
+
<AppContent />
|
|
88
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-background/80">
|
|
89
|
+
<div className="rounded-2xl border-2 border-dashed border-emerald-500/40 bg-card p-12 text-center max-w-sm w-full mx-4">
|
|
90
|
+
<div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-2xl border border-emerald-500/30 bg-emerald-500/10">
|
|
91
|
+
<ImageIcon className="h-8 w-8 text-emerald-400" />
|
|
92
|
+
</div>
|
|
93
|
+
<h2 className="text-xl font-bold text-foreground">Drop images here</h2>
|
|
94
|
+
<p className="mt-2 text-sm text-muted-foreground">Accepts .png, .jpg, .webp, .gif</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const OverlayCustom: Story = {
|
|
102
|
+
name: 'Overlay — Custom content',
|
|
103
|
+
render: () => (
|
|
104
|
+
<div className="relative w-[600px] h-[380px]">
|
|
105
|
+
<AppContent />
|
|
106
|
+
<DropZone
|
|
107
|
+
onDrop={(files) => console.log('dropped', files)}
|
|
108
|
+
overlay={
|
|
109
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center pointer-events-none bg-background/80">
|
|
110
|
+
<div className="rounded-2xl border-2 border-dashed border-[var(--brand-emerald)]/50 bg-card p-14 text-center">
|
|
111
|
+
<p className="text-2xl font-bold text-[var(--brand-emerald)]">Custom overlay</p>
|
|
112
|
+
<p className="mt-2 text-muted-foreground text-sm">Replace default drop UI with anything.</p>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<div className="w-[600px] h-[380px] flex items-center justify-center rounded-xl border border-dashed border-border text-muted-foreground text-sm bg-muted">
|
|
118
|
+
Drag a real file here to trigger custom overlay
|
|
119
|
+
</div>
|
|
120
|
+
</DropZone>
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DropZone — full-window drag-and-drop file upload overlay.
|
|
3
|
+
*
|
|
4
|
+
* Detects when files are dragged over the browser window and shows a
|
|
5
|
+
* customizable overlay. Products provide the onDrop handler and optional
|
|
6
|
+
* custom overlay content.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* <DropZone onDrop={files => uploadToR2(files)} accept=".pdf,.csv">
|
|
10
|
+
* <YourApp />
|
|
11
|
+
* </DropZone>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useRef, useState, type DragEvent, type ReactNode } from "react";
|
|
15
|
+
import { cn } from "../lib/utils";
|
|
16
|
+
|
|
17
|
+
export interface DropZoneProps {
|
|
18
|
+
/** Called with dropped files */
|
|
19
|
+
onDrop: (files: File[]) => void;
|
|
20
|
+
/** Accepted file types (e.g. ".pdf,.csv,.xlsx") */
|
|
21
|
+
accept?: string;
|
|
22
|
+
/** Whether drop zone is active */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Custom overlay content (replaces default) */
|
|
25
|
+
overlay?: ReactNode;
|
|
26
|
+
/** Overlay title */
|
|
27
|
+
title?: string;
|
|
28
|
+
/** Overlay description */
|
|
29
|
+
description?: string;
|
|
30
|
+
/** Overlay icon (Material Symbols name or ReactNode) */
|
|
31
|
+
icon?: string | ReactNode;
|
|
32
|
+
/** Children wrapped by the drop zone */
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DropZone({
|
|
38
|
+
onDrop,
|
|
39
|
+
accept,
|
|
40
|
+
disabled,
|
|
41
|
+
overlay,
|
|
42
|
+
title = "Drop files to upload",
|
|
43
|
+
description = "Your files will be securely stored in the workspace.",
|
|
44
|
+
icon = "cloud_upload",
|
|
45
|
+
children,
|
|
46
|
+
className,
|
|
47
|
+
}: DropZoneProps) {
|
|
48
|
+
const [dragOver, setDragOver] = useState(false);
|
|
49
|
+
const counter = useRef(0);
|
|
50
|
+
|
|
51
|
+
const isAccepted = useCallback(
|
|
52
|
+
(file: File) => {
|
|
53
|
+
if (!accept) return true;
|
|
54
|
+
const extensions = accept.split(",").map((ext) => ext.trim().toLowerCase());
|
|
55
|
+
const fileName = file.name.toLowerCase();
|
|
56
|
+
return extensions.some((ext) => fileName.endsWith(ext));
|
|
57
|
+
},
|
|
58
|
+
[accept],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const handleDragEnter = useCallback(
|
|
62
|
+
(e: DragEvent) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
if (disabled) return;
|
|
65
|
+
counter.current++;
|
|
66
|
+
if (e.dataTransfer?.types.includes("Files")) setDragOver(true);
|
|
67
|
+
},
|
|
68
|
+
[disabled],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const handleDragLeave = useCallback((e: DragEvent) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
counter.current--;
|
|
74
|
+
if (counter.current === 0) setDragOver(false);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const handleDragOver = useCallback(
|
|
78
|
+
(e: DragEvent) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
if (!disabled) e.dataTransfer.dropEffect = "copy";
|
|
81
|
+
},
|
|
82
|
+
[disabled],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const handleDrop = useCallback(
|
|
86
|
+
(e: DragEvent) => {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
counter.current = 0;
|
|
89
|
+
setDragOver(false);
|
|
90
|
+
if (disabled) return;
|
|
91
|
+
|
|
92
|
+
const allFiles = Array.from(e.dataTransfer?.files || []);
|
|
93
|
+
const accepted = accept ? allFiles.filter(isAccepted) : allFiles;
|
|
94
|
+
if (accepted.length > 0) onDrop(accepted);
|
|
95
|
+
},
|
|
96
|
+
[disabled, accept, isAccepted, onDrop],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
onDragEnter={handleDragEnter}
|
|
102
|
+
onDragLeave={handleDragLeave}
|
|
103
|
+
onDragOver={handleDragOver}
|
|
104
|
+
onDrop={handleDrop}
|
|
105
|
+
className={cn("relative", className)}
|
|
106
|
+
>
|
|
107
|
+
{dragOver &&
|
|
108
|
+
(overlay || (
|
|
109
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center pointer-events-none bg-background">
|
|
110
|
+
<div className="rounded-2xl border-2 border-dashed border-border bg-card p-16 text-center shadow-[var(--shadow-dropdown)] max-w-lg mx-auto">
|
|
111
|
+
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-2xl border border-border bg-[var(--accent-surface-soft)]">
|
|
112
|
+
{typeof icon === "string" ? (
|
|
113
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="h-10 w-10 text-[var(--accent-text)]">
|
|
114
|
+
<title>Upload</title>
|
|
115
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
116
|
+
<polyline points="17 8 12 3 7 8" />
|
|
117
|
+
<line x1="12" x2="12" y1="3" y2="15" />
|
|
118
|
+
</svg>
|
|
119
|
+
) : (
|
|
120
|
+
icon
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
<h2 className="text-2xl font-bold text-foreground">{title}</h2>
|
|
124
|
+
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
{children}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { ChevronDown, MoreHorizontal, Settings, Terminal } from 'lucide-react'
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuGroup,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuLabel,
|
|
9
|
+
DropdownMenuSeparator,
|
|
10
|
+
DropdownMenuShortcut,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from './dropdown-menu'
|
|
13
|
+
|
|
14
|
+
const meta: Meta = {
|
|
15
|
+
title: 'Primitives/DropdownMenu',
|
|
16
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
20
|
+
type Story = StoryObj
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
name: 'Default',
|
|
24
|
+
render: () => (
|
|
25
|
+
<DropdownMenu defaultOpen>
|
|
26
|
+
<DropdownMenuTrigger asChild>
|
|
27
|
+
<button className="flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground hover:bg-muted">
|
|
28
|
+
Options
|
|
29
|
+
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
|
30
|
+
</button>
|
|
31
|
+
</DropdownMenuTrigger>
|
|
32
|
+
<DropdownMenuContent className="w-48">
|
|
33
|
+
<DropdownMenuLabel>Session</DropdownMenuLabel>
|
|
34
|
+
<DropdownMenuSeparator />
|
|
35
|
+
<DropdownMenuGroup>
|
|
36
|
+
<DropdownMenuItem>
|
|
37
|
+
<Terminal className="mr-2 h-4 w-4" />
|
|
38
|
+
Open terminal
|
|
39
|
+
<DropdownMenuShortcut>⌘T</DropdownMenuShortcut>
|
|
40
|
+
</DropdownMenuItem>
|
|
41
|
+
<DropdownMenuItem>
|
|
42
|
+
<Settings className="mr-2 h-4 w-4" />
|
|
43
|
+
Configure
|
|
44
|
+
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
|
|
45
|
+
</DropdownMenuItem>
|
|
46
|
+
</DropdownMenuGroup>
|
|
47
|
+
<DropdownMenuSeparator />
|
|
48
|
+
<DropdownMenuItem className="text-red-400 focus:text-red-400">
|
|
49
|
+
Terminate session
|
|
50
|
+
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
|
51
|
+
</DropdownMenuItem>
|
|
52
|
+
</DropdownMenuContent>
|
|
53
|
+
</DropdownMenu>
|
|
54
|
+
),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const SessionRowMenu: Story = {
|
|
58
|
+
name: 'Session Row Menu',
|
|
59
|
+
render: () => (
|
|
60
|
+
<DropdownMenu defaultOpen>
|
|
61
|
+
<DropdownMenuTrigger asChild>
|
|
62
|
+
<button className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground">
|
|
63
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
64
|
+
</button>
|
|
65
|
+
</DropdownMenuTrigger>
|
|
66
|
+
<DropdownMenuContent className="w-44" align="end">
|
|
67
|
+
<DropdownMenuItem>View logs</DropdownMenuItem>
|
|
68
|
+
<DropdownMenuItem>Open shell</DropdownMenuItem>
|
|
69
|
+
<DropdownMenuItem>Copy session ID</DropdownMenuItem>
|
|
70
|
+
<DropdownMenuSeparator />
|
|
71
|
+
<DropdownMenuItem>Restart</DropdownMenuItem>
|
|
72
|
+
<DropdownMenuSeparator />
|
|
73
|
+
<DropdownMenuItem className="text-red-400 focus:text-red-400">
|
|
74
|
+
Terminate
|
|
75
|
+
</DropdownMenuItem>
|
|
76
|
+
</DropdownMenuContent>
|
|
77
|
+
</DropdownMenu>
|
|
78
|
+
),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const RegionSelector: Story = {
|
|
82
|
+
name: 'Region Selector',
|
|
83
|
+
render: () => (
|
|
84
|
+
<DropdownMenu defaultOpen>
|
|
85
|
+
<DropdownMenuTrigger asChild>
|
|
86
|
+
<button className="flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground hover:bg-muted">
|
|
87
|
+
<span className="h-2 w-2 rounded-full bg-green-400" />
|
|
88
|
+
us-east-1
|
|
89
|
+
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
|
90
|
+
</button>
|
|
91
|
+
</DropdownMenuTrigger>
|
|
92
|
+
<DropdownMenuContent className="w-52">
|
|
93
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal tracking-widest uppercase">
|
|
94
|
+
Regions
|
|
95
|
+
</DropdownMenuLabel>
|
|
96
|
+
<DropdownMenuSeparator />
|
|
97
|
+
<DropdownMenuGroup>
|
|
98
|
+
<DropdownMenuItem>
|
|
99
|
+
<span className="mr-2 h-2 w-2 rounded-full bg-green-400" />
|
|
100
|
+
us-east-1
|
|
101
|
+
<DropdownMenuShortcut className="text-green-400">Online</DropdownMenuShortcut>
|
|
102
|
+
</DropdownMenuItem>
|
|
103
|
+
<DropdownMenuItem>
|
|
104
|
+
<span className="mr-2 h-2 w-2 rounded-full bg-green-400" />
|
|
105
|
+
us-west-2
|
|
106
|
+
<DropdownMenuShortcut className="text-green-400">Online</DropdownMenuShortcut>
|
|
107
|
+
</DropdownMenuItem>
|
|
108
|
+
<DropdownMenuItem>
|
|
109
|
+
<span className="mr-2 h-2 w-2 rounded-full bg-green-400" />
|
|
110
|
+
eu-central-1
|
|
111
|
+
<DropdownMenuShortcut className="text-green-400">Online</DropdownMenuShortcut>
|
|
112
|
+
</DropdownMenuItem>
|
|
113
|
+
<DropdownMenuItem disabled>
|
|
114
|
+
<span className="mr-2 h-2 w-2 rounded-full bg-muted-foreground" />
|
|
115
|
+
ap-southeast-1
|
|
116
|
+
<DropdownMenuShortcut>Soon</DropdownMenuShortcut>
|
|
117
|
+
</DropdownMenuItem>
|
|
118
|
+
</DropdownMenuGroup>
|
|
119
|
+
</DropdownMenuContent>
|
|
120
|
+
</DropdownMenu>
|
|
121
|
+
),
|
|
122
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
2
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
7
|
+
|
|
8
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
9
|
+
|
|
10
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
11
|
+
|
|
12
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
13
|
+
|
|
14
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
15
|
+
|
|
16
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
17
|
+
|
|
18
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
21
|
+
inset?: boolean;
|
|
22
|
+
}
|
|
23
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
24
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none",
|
|
28
|
+
"focus:bg-accent data-[state=open]:bg-muted/50",
|
|
29
|
+
inset && "pl-8",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
36
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
37
|
+
));
|
|
38
|
+
DropdownMenuSubTrigger.displayName =
|
|
39
|
+
DropdownMenuPrimitive.SubTrigger.displayName;
|
|
40
|
+
|
|
41
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
42
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
43
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
44
|
+
>(({ className, ...props }, ref) => (
|
|
45
|
+
<DropdownMenuPrimitive.SubContent
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cn(
|
|
48
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
|
|
49
|
+
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
50
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
51
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
52
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
53
|
+
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
));
|
|
59
|
+
DropdownMenuSubContent.displayName =
|
|
60
|
+
DropdownMenuPrimitive.SubContent.displayName;
|
|
61
|
+
|
|
62
|
+
const DropdownMenuContent = React.forwardRef<
|
|
63
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
65
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
66
|
+
<DropdownMenuPrimitive.Portal>
|
|
67
|
+
<DropdownMenuPrimitive.Content
|
|
68
|
+
ref={ref}
|
|
69
|
+
sideOffset={sideOffset}
|
|
70
|
+
className={cn(
|
|
71
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
|
|
72
|
+
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
73
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
74
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
75
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
76
|
+
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
</DropdownMenuPrimitive.Portal>
|
|
82
|
+
));
|
|
83
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
84
|
+
|
|
85
|
+
const DropdownMenuItem = React.forwardRef<
|
|
86
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
87
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
88
|
+
inset?: boolean;
|
|
89
|
+
}
|
|
90
|
+
>(({ className, inset, ...props }, ref) => (
|
|
91
|
+
<DropdownMenuPrimitive.Item
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn(
|
|
94
|
+
"relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors",
|
|
95
|
+
"focus:bg-muted/50 focus:text-foreground",
|
|
96
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
97
|
+
inset && "pl-8",
|
|
98
|
+
className,
|
|
99
|
+
)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
));
|
|
103
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
104
|
+
|
|
105
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
107
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
108
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
109
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
110
|
+
ref={ref}
|
|
111
|
+
className={cn(
|
|
112
|
+
"relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
|
|
113
|
+
"focus:bg-muted/50 focus:text-foreground",
|
|
114
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
checked={checked}
|
|
118
|
+
{...props}
|
|
119
|
+
>
|
|
120
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
121
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
122
|
+
<Check className="h-4 w-4" />
|
|
123
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
124
|
+
</span>
|
|
125
|
+
{children}
|
|
126
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
127
|
+
));
|
|
128
|
+
DropdownMenuCheckboxItem.displayName =
|
|
129
|
+
DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
130
|
+
|
|
131
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
132
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
133
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
134
|
+
>(({ className, children, ...props }, ref) => (
|
|
135
|
+
<DropdownMenuPrimitive.RadioItem
|
|
136
|
+
ref={ref}
|
|
137
|
+
className={cn(
|
|
138
|
+
"relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
|
|
139
|
+
"focus:bg-muted/50 focus:text-foreground",
|
|
140
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
141
|
+
className,
|
|
142
|
+
)}
|
|
143
|
+
{...props}
|
|
144
|
+
>
|
|
145
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
146
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
147
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
148
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
149
|
+
</span>
|
|
150
|
+
{children}
|
|
151
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
152
|
+
));
|
|
153
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
154
|
+
|
|
155
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
156
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
157
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
158
|
+
inset?: boolean;
|
|
159
|
+
}
|
|
160
|
+
>(({ className, inset, ...props }, ref) => (
|
|
161
|
+
<DropdownMenuPrimitive.Label
|
|
162
|
+
ref={ref}
|
|
163
|
+
className={cn(
|
|
164
|
+
"px-2 py-1.5 font-semibold text-sm",
|
|
165
|
+
inset && "pl-8",
|
|
166
|
+
className,
|
|
167
|
+
)}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
));
|
|
171
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
172
|
+
|
|
173
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
174
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
175
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
176
|
+
>(({ className, ...props }, ref) => (
|
|
177
|
+
<DropdownMenuPrimitive.Separator
|
|
178
|
+
ref={ref}
|
|
179
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
180
|
+
{...props}
|
|
181
|
+
/>
|
|
182
|
+
));
|
|
183
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
184
|
+
|
|
185
|
+
const DropdownMenuShortcut = ({
|
|
186
|
+
className,
|
|
187
|
+
...props
|
|
188
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
189
|
+
return (
|
|
190
|
+
<span
|
|
191
|
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
192
|
+
{...props}
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
197
|
+
|
|
198
|
+
export {
|
|
199
|
+
DropdownMenu,
|
|
200
|
+
DropdownMenuTrigger,
|
|
201
|
+
DropdownMenuContent,
|
|
202
|
+
DropdownMenuItem,
|
|
203
|
+
DropdownMenuCheckboxItem,
|
|
204
|
+
DropdownMenuRadioItem,
|
|
205
|
+
DropdownMenuLabel,
|
|
206
|
+
DropdownMenuSeparator,
|
|
207
|
+
DropdownMenuShortcut,
|
|
208
|
+
DropdownMenuGroup,
|
|
209
|
+
DropdownMenuPortal,
|
|
210
|
+
DropdownMenuSub,
|
|
211
|
+
DropdownMenuSubContent,
|
|
212
|
+
DropdownMenuSubTrigger,
|
|
213
|
+
DropdownMenuRadioGroup,
|
|
214
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { Box, Clock, Search, Terminal, Zap } from 'lucide-react'
|
|
3
|
+
import { EmptyState } from './empty-state'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof EmptyState> = {
|
|
6
|
+
title: 'Primitives/EmptyState',
|
|
7
|
+
component: EmptyState,
|
|
8
|
+
parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof EmptyState>
|
|
13
|
+
|
|
14
|
+
export const NoSessions: Story = {
|
|
15
|
+
name: 'No Sessions',
|
|
16
|
+
args: {
|
|
17
|
+
icon: <Terminal className="h-6 w-6" />,
|
|
18
|
+
title: 'No active sessions',
|
|
19
|
+
description:
|
|
20
|
+
'Provision a sandbox to run code in an isolated container. Sessions spin up in under a second.',
|
|
21
|
+
action: (
|
|
22
|
+
<button className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90">
|
|
23
|
+
New session
|
|
24
|
+
</button>
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const NoResults: Story = {
|
|
30
|
+
name: 'No Search Results',
|
|
31
|
+
args: {
|
|
32
|
+
icon: <Search className="h-6 w-6" />,
|
|
33
|
+
title: 'No sessions found',
|
|
34
|
+
description: 'No sessions match your current filters. Try adjusting the status or date range.',
|
|
35
|
+
action: (
|
|
36
|
+
<button className="rounded-md border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-muted">
|
|
37
|
+
Clear filters
|
|
38
|
+
</button>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const NoLogs: Story = {
|
|
44
|
+
name: 'No Logs',
|
|
45
|
+
args: {
|
|
46
|
+
icon: <Clock className="h-6 w-6" />,
|
|
47
|
+
title: 'No logs yet',
|
|
48
|
+
description: 'Logs will appear here once the session starts executing commands.',
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const NoSnapshots: Story = {
|
|
53
|
+
name: 'No Snapshots',
|
|
54
|
+
args: {
|
|
55
|
+
icon: <Box className="h-6 w-6" />,
|
|
56
|
+
title: 'No snapshots',
|
|
57
|
+
description:
|
|
58
|
+
'Snapshots capture the full filesystem state of a session. Create one to resume from a known point.',
|
|
59
|
+
action: (
|
|
60
|
+
<button className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90">
|
|
61
|
+
Create snapshot
|
|
62
|
+
</button>
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const BillingIdle: Story = {
|
|
68
|
+
name: 'Idle — No Usage',
|
|
69
|
+
args: {
|
|
70
|
+
icon: <Zap className="h-6 w-6" />,
|
|
71
|
+
title: 'No usage this period',
|
|
72
|
+
description: 'You have not run any sandbox sessions in the current billing cycle.',
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const TitleOnly: Story = {
|
|
77
|
+
name: 'Title Only',
|
|
78
|
+
args: {
|
|
79
|
+
title: 'Nothing here yet',
|
|
80
|
+
},
|
|
81
|
+
}
|