@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.
Files changed (220) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
  5. package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
  6. package/dist/auth.d.ts +74 -0
  7. package/dist/auth.js +15 -0
  8. package/dist/button-CMQuQEW_.d.ts +17 -0
  9. package/dist/chat.d.ts +232 -0
  10. package/dist/chat.js +30 -0
  11. package/dist/chunk-2NFQRQOD.js +1009 -0
  12. package/dist/chunk-2VH6PUXD.js +186 -0
  13. package/dist/chunk-34A66VBG.js +214 -0
  14. package/dist/chunk-3OI2QKFD.js +0 -0
  15. package/dist/chunk-4CLN43XT.js +45 -0
  16. package/dist/chunk-54SQQMMM.js +156 -0
  17. package/dist/chunk-5Z5ZYMOJ.js +0 -0
  18. package/dist/chunk-66BNMOVT.js +167 -0
  19. package/dist/chunk-6BGQA4BQ.js +0 -0
  20. package/dist/chunk-7UO2ZMRQ.js +133 -0
  21. package/dist/chunk-BX6AQMUS.js +183 -0
  22. package/dist/chunk-CD53GZOM.js +59 -0
  23. package/dist/chunk-CSAIKY36.js +54 -0
  24. package/dist/chunk-EEE55AVS.js +1201 -0
  25. package/dist/chunk-GYPQXTJU.js +230 -0
  26. package/dist/chunk-HFL6R6IF.js +37 -0
  27. package/dist/chunk-HJKCSXCH.js +737 -0
  28. package/dist/chunk-LISXUB4D.js +1222 -0
  29. package/dist/chunk-LQS34IGP.js +0 -0
  30. package/dist/chunk-MKTSMWVD.js +109 -0
  31. package/dist/chunk-NKDZ7GZE.js +192 -0
  32. package/dist/chunk-OEX7NZE3.js +321 -0
  33. package/dist/chunk-Q56BYXQF.js +61 -0
  34. package/dist/chunk-Q7EIIWTC.js +0 -0
  35. package/dist/chunk-REJESC5U.js +117 -0
  36. package/dist/chunk-RQGKSCEZ.js +0 -0
  37. package/dist/chunk-RQHJBTEU.js +10 -0
  38. package/dist/chunk-TMFOPHHN.js +299 -0
  39. package/dist/chunk-XGKULLYE.js +40 -0
  40. package/dist/chunk-XIHMJ7ZQ.js +614 -0
  41. package/dist/chunk-YJ2G3XO5.js +1048 -0
  42. package/dist/chunk-YNN4O57I.js +754 -0
  43. package/dist/code-block-DjXf8eOG.d.ts +19 -0
  44. package/dist/document-editor-pane-A5LT5H4N.js +12 -0
  45. package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
  46. package/dist/editor.d.ts +120 -0
  47. package/dist/editor.js +34 -0
  48. package/dist/files.d.ts +175 -0
  49. package/dist/files.js +20 -0
  50. package/dist/hooks.d.ts +56 -0
  51. package/dist/hooks.js +41 -0
  52. package/dist/index.d.ts +43 -0
  53. package/dist/index.js +446 -0
  54. package/dist/markdown.d.ts +15 -0
  55. package/dist/markdown.js +14 -0
  56. package/dist/message-BHWbxBtT.d.ts +15 -0
  57. package/dist/openui.d.ts +115 -0
  58. package/dist/openui.js +12 -0
  59. package/dist/parts-dj7AcUg0.d.ts +36 -0
  60. package/dist/primitives.d.ts +332 -0
  61. package/dist/primitives.js +191 -0
  62. package/dist/run-PfLmDAox.d.ts +41 -0
  63. package/dist/run.d.ts +69 -0
  64. package/dist/run.js +36 -0
  65. package/dist/sdk-hooks.d.ts +285 -0
  66. package/dist/sdk-hooks.js +31 -0
  67. package/dist/stores.d.ts +17 -0
  68. package/dist/stores.js +76 -0
  69. package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
  70. package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
  71. package/dist/tool-previews.d.ts +48 -0
  72. package/dist/tool-previews.js +21 -0
  73. package/dist/types.d.ts +19 -0
  74. package/dist/types.js +1 -0
  75. package/dist/utils.d.ts +45 -0
  76. package/dist/utils.js +32 -0
  77. package/package.json +193 -0
  78. package/src/auth/auth.tsx +228 -0
  79. package/src/auth/index.ts +13 -0
  80. package/src/auth/login-layout.tsx +46 -0
  81. package/src/chat/agent-timeline.stories.tsx +429 -0
  82. package/src/chat/agent-timeline.tsx +360 -0
  83. package/src/chat/chat-container.tsx +486 -0
  84. package/src/chat/chat-input.stories.tsx +142 -0
  85. package/src/chat/chat-input.tsx +389 -0
  86. package/src/chat/chat-message.stories.tsx +237 -0
  87. package/src/chat/chat-message.tsx +129 -0
  88. package/src/chat/index.ts +18 -0
  89. package/src/chat/message-list.stories.tsx +336 -0
  90. package/src/chat/message-list.tsx +79 -0
  91. package/src/chat/thinking-indicator.stories.tsx +56 -0
  92. package/src/chat/thinking-indicator.tsx +30 -0
  93. package/src/chat/user-message.stories.tsx +92 -0
  94. package/src/chat/user-message.tsx +43 -0
  95. package/src/editor/document-editor-pane.tsx +351 -0
  96. package/src/editor/editor-provider.tsx +428 -0
  97. package/src/editor/editor-toolbar.tsx +130 -0
  98. package/src/editor/index.ts +31 -0
  99. package/src/editor/markdown-conversion.ts +21 -0
  100. package/src/editor/markdown-document-editor.tsx +137 -0
  101. package/src/editor/tiptap-editor.tsx +331 -0
  102. package/src/editor/use-editor.ts +221 -0
  103. package/src/files/file-artifact-pane.tsx +183 -0
  104. package/src/files/file-preview.tsx +342 -0
  105. package/src/files/file-tabs.tsx +71 -0
  106. package/src/files/file-tree.tsx +258 -0
  107. package/src/files/index.ts +17 -0
  108. package/src/files/rich-file-tree.stories.tsx +104 -0
  109. package/src/files/rich-file-tree.test.tsx +42 -0
  110. package/src/files/rich-file-tree.tsx +232 -0
  111. package/src/hooks/index.ts +10 -0
  112. package/src/hooks/use-auth.ts +153 -0
  113. package/src/hooks/use-auto-scroll.ts +59 -0
  114. package/src/hooks/use-dropdown-menu.ts +40 -0
  115. package/src/hooks/use-live-time.test.tsx +40 -0
  116. package/src/hooks/use-live-time.ts +27 -0
  117. package/src/hooks/use-realtime-session.ts +319 -0
  118. package/src/hooks/use-run-collapse-state.ts +25 -0
  119. package/src/hooks/use-run-groups.ts +111 -0
  120. package/src/hooks/use-sdk-session.ts +575 -0
  121. package/src/hooks/use-sse-stream.ts +475 -0
  122. package/src/hooks/use-tool-call-stream.ts +96 -0
  123. package/src/index.ts +14 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/markdown/code-block.tsx +198 -0
  126. package/src/markdown/index.ts +2 -0
  127. package/src/markdown/markdown.stories.tsx +190 -0
  128. package/src/markdown/markdown.tsx +62 -0
  129. package/src/openui/index.ts +20 -0
  130. package/src/openui/openui-artifact-renderer.tsx +542 -0
  131. package/src/primitives/artifact-pane.tsx +91 -0
  132. package/src/primitives/avatar.stories.tsx +95 -0
  133. package/src/primitives/avatar.tsx +47 -0
  134. package/src/primitives/badge.stories.tsx +57 -0
  135. package/src/primitives/badge.tsx +97 -0
  136. package/src/primitives/button.stories.tsx +48 -0
  137. package/src/primitives/button.tsx +115 -0
  138. package/src/primitives/card.stories.tsx +53 -0
  139. package/src/primitives/card.tsx +98 -0
  140. package/src/primitives/code-block.stories.tsx +115 -0
  141. package/src/primitives/code-block.tsx +22 -0
  142. package/src/primitives/design-tokens.stories.tsx +162 -0
  143. package/src/primitives/dialog.stories.tsx +176 -0
  144. package/src/primitives/dialog.tsx +137 -0
  145. package/src/primitives/drop-zone.stories.tsx +123 -0
  146. package/src/primitives/drop-zone.tsx +131 -0
  147. package/src/primitives/dropdown-menu.stories.tsx +122 -0
  148. package/src/primitives/dropdown-menu.tsx +214 -0
  149. package/src/primitives/empty-state.stories.tsx +81 -0
  150. package/src/primitives/empty-state.tsx +40 -0
  151. package/src/primitives/index.ts +118 -0
  152. package/src/primitives/input.stories.tsx +113 -0
  153. package/src/primitives/input.tsx +136 -0
  154. package/src/primitives/label.stories.tsx +84 -0
  155. package/src/primitives/label.tsx +24 -0
  156. package/src/primitives/progress.stories.tsx +93 -0
  157. package/src/primitives/progress.tsx +50 -0
  158. package/src/primitives/segmented-control.test.tsx +328 -0
  159. package/src/primitives/segmented-control.tsx +154 -0
  160. package/src/primitives/select.stories.tsx +164 -0
  161. package/src/primitives/select.tsx +158 -0
  162. package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
  163. package/src/primitives/sidebar-drop-zone.tsx +149 -0
  164. package/src/primitives/skeleton.stories.tsx +79 -0
  165. package/src/primitives/skeleton.tsx +55 -0
  166. package/src/primitives/stat-card.stories.tsx +137 -0
  167. package/src/primitives/stat-card.tsx +97 -0
  168. package/src/primitives/switch.stories.tsx +85 -0
  169. package/src/primitives/switch.tsx +28 -0
  170. package/src/primitives/table.stories.tsx +170 -0
  171. package/src/primitives/table.tsx +116 -0
  172. package/src/primitives/tabs.stories.tsx +180 -0
  173. package/src/primitives/tabs.tsx +71 -0
  174. package/src/primitives/terminal-display.stories.tsx +191 -0
  175. package/src/primitives/terminal-display.tsx +189 -0
  176. package/src/primitives/theme-toggle.stories.tsx +32 -0
  177. package/src/primitives/theme-toggle.tsx +96 -0
  178. package/src/primitives/toast.stories.tsx +155 -0
  179. package/src/primitives/toast.tsx +190 -0
  180. package/src/primitives/upload-progress.stories.tsx +120 -0
  181. package/src/primitives/upload-progress.tsx +110 -0
  182. package/src/run/expanded-tool-detail.stories.tsx +182 -0
  183. package/src/run/expanded-tool-detail.tsx +186 -0
  184. package/src/run/index.ts +13 -0
  185. package/src/run/inline-thinking-item.stories.tsx +136 -0
  186. package/src/run/inline-thinking-item.tsx +120 -0
  187. package/src/run/inline-tool-item.stories.tsx +222 -0
  188. package/src/run/inline-tool-item.tsx +190 -0
  189. package/src/run/run-group.stories.tsx +322 -0
  190. package/src/run/run-group.tsx +569 -0
  191. package/src/run/run-item-primitives.tsx +17 -0
  192. package/src/run/tool-call-feed.stories.tsx +294 -0
  193. package/src/run/tool-call-feed.tsx +192 -0
  194. package/src/run/tool-call-step.stories.tsx +198 -0
  195. package/src/run/tool-call-step.tsx +240 -0
  196. package/src/sdk-hooks.ts +38 -0
  197. package/src/stores/active-sessions-store.ts +455 -0
  198. package/src/stores/chat-store.ts +43 -0
  199. package/src/stores/index.ts +2 -0
  200. package/src/tool-previews/command-preview.tsx +116 -0
  201. package/src/tool-previews/diff-preview.tsx +85 -0
  202. package/src/tool-previews/glob-results-preview.tsx +98 -0
  203. package/src/tool-previews/grep-results-preview.tsx +157 -0
  204. package/src/tool-previews/index.ts +22 -0
  205. package/src/tool-previews/preview-primitives.tsx +84 -0
  206. package/src/tool-previews/question-preview.tsx +101 -0
  207. package/src/tool-previews/web-search-preview.tsx +117 -0
  208. package/src/tool-previews/write-file-preview.tsx +80 -0
  209. package/src/types/branding.ts +11 -0
  210. package/src/types/index.ts +5 -0
  211. package/src/types/message.ts +13 -0
  212. package/src/types/parts.ts +51 -0
  213. package/src/types/run.ts +56 -0
  214. package/src/types/tool-display.ts +41 -0
  215. package/src/utils/copy-text.ts +30 -0
  216. package/src/utils/format.test.ts +43 -0
  217. package/src/utils/format.ts +56 -0
  218. package/src/utils/index.ts +10 -0
  219. package/src/utils/time-ago.ts +9 -0
  220. package/src/utils/tool-display.ts +238 -0
@@ -0,0 +1,97 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import { Card } from "./card";
4
+
5
+ export interface StatCardProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ variant?: "default" | "sandbox";
7
+ title: string;
8
+ value: string | number;
9
+ subtitle?: string;
10
+ icon?: React.ReactNode;
11
+ trend?: {
12
+ value: number;
13
+ label?: string;
14
+ };
15
+ }
16
+
17
+ const StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(
18
+ (
19
+ {
20
+ className,
21
+ variant = "default",
22
+ title,
23
+ value,
24
+ subtitle,
25
+ icon,
26
+ trend,
27
+ ...props
28
+ },
29
+ ref,
30
+ ) => {
31
+ const iconColors = {
32
+ default: "text-muted-foreground",
33
+ sandbox: "text-[var(--accent-text)]",
34
+ };
35
+
36
+ const trendColors = {
37
+ positive: "text-[var(--surface-success-text)]",
38
+ negative: "text-[var(--surface-danger-text)]",
39
+ neutral: "text-muted-foreground",
40
+ };
41
+
42
+ const trendStatus = trend
43
+ ? trend.value > 0
44
+ ? "positive"
45
+ : trend.value < 0
46
+ ? "negative"
47
+ : "neutral"
48
+ : null;
49
+
50
+ return (
51
+ <Card
52
+ ref={ref}
53
+ variant={variant}
54
+ className={cn("p-6", className)}
55
+ {...props}
56
+ >
57
+ <div className="flex items-start justify-between">
58
+ <div className="space-y-1">
59
+ <p className="text-muted-foreground text-sm">{title}</p>
60
+ <p className="font-bold text-3xl tracking-tight">{value}</p>
61
+ {subtitle && (
62
+ <p className="text-muted-foreground opacity-70 text-xs">{subtitle}</p>
63
+ )}
64
+ {trend && trendStatus && (
65
+ <div
66
+ className={cn(
67
+ "flex items-center gap-1 text-sm",
68
+ trendColors[trendStatus],
69
+ )}
70
+ >
71
+ {trend.value > 0 ? "↑" : trend.value < 0 ? "↓" : "→"}
72
+ <span>{Math.abs(trend.value)}%</span>
73
+ {trend.label && (
74
+ <span className="text-muted-foreground">{trend.label}</span>
75
+ )}
76
+ </div>
77
+ )}
78
+ </div>
79
+ {icon && (
80
+ <div
81
+ className={cn(
82
+ "rounded-lg bg-muted/50 p-2",
83
+ variant === "sandbox" && "bg-[var(--accent-surface-soft)]",
84
+ iconColors[variant],
85
+ )}
86
+ >
87
+ {icon}
88
+ </div>
89
+ )}
90
+ </div>
91
+ </Card>
92
+ );
93
+ },
94
+ );
95
+ StatCard.displayName = "StatCard";
96
+
97
+ export { StatCard };
@@ -0,0 +1,85 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Switch } from './switch'
3
+ import { Label } from './label'
4
+
5
+ const meta: Meta<typeof Switch> = {
6
+ title: 'Primitives/Switch',
7
+ component: Switch,
8
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Switch>
13
+
14
+ export const Unchecked: Story = {
15
+ args: { defaultChecked: false },
16
+ }
17
+
18
+ export const Checked: Story = {
19
+ args: { defaultChecked: true },
20
+ }
21
+
22
+ export const Disabled: Story = {
23
+ args: { disabled: true },
24
+ }
25
+
26
+ export const DisabledChecked: Story = {
27
+ name: 'Disabled (checked)',
28
+ args: { disabled: true, defaultChecked: true },
29
+ }
30
+
31
+ export const WithLabel: Story = {
32
+ render: () => (
33
+ <div className="flex items-center gap-2">
34
+ <Switch id="auto-sleep" defaultChecked />
35
+ <Label htmlFor="auto-sleep">Auto-sleep after 30 min idle</Label>
36
+ </div>
37
+ ),
38
+ }
39
+
40
+ export const Overview: Story = {
41
+ name: 'Overview',
42
+ render: () => (
43
+ <div className="flex flex-col gap-6 p-6 w-80">
44
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">States</div>
45
+ <div className="flex flex-col gap-4">
46
+ <div className="flex items-center justify-between">
47
+ <span className="text-sm text-muted-foreground">Off</span>
48
+ <Switch />
49
+ </div>
50
+ <div className="flex items-center justify-between">
51
+ <span className="text-sm text-muted-foreground">On</span>
52
+ <Switch defaultChecked />
53
+ </div>
54
+ <div className="flex items-center justify-between">
55
+ <span className="text-sm text-muted-foreground">Disabled off</span>
56
+ <Switch disabled />
57
+ </div>
58
+ <div className="flex items-center justify-between">
59
+ <span className="text-sm text-muted-foreground">Disabled on</span>
60
+ <Switch disabled defaultChecked />
61
+ </div>
62
+ </div>
63
+
64
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest mt-2">Settings panel</div>
65
+ <div className="rounded-xl border border-border bg-card p-4 flex flex-col gap-4">
66
+ {[
67
+ { id: 'auto-sleep', label: 'Auto-sleep after idle', checked: true },
68
+ { id: 'public-access', label: 'Public network access', checked: false },
69
+ { id: 'telemetry', label: 'Usage telemetry', checked: true },
70
+ { id: 'snapshots', label: 'Automatic snapshots', checked: false, disabled: true },
71
+ ].map(({ id, label, checked, disabled }) => (
72
+ <div key={id} className="flex items-center justify-between">
73
+ <Label
74
+ htmlFor={id}
75
+ className={disabled ? 'opacity-50' : ''}
76
+ >
77
+ {label}
78
+ </Label>
79
+ <Switch id={id} defaultChecked={checked} disabled={disabled} />
80
+ </div>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ ),
85
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Switch = React.forwardRef<
8
+ React.ElementRef<typeof SwitchPrimitives.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <SwitchPrimitives.Root
12
+ className={cn(
13
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
14
+ className,
15
+ )}
16
+ {...props}
17
+ ref={ref}
18
+ >
19
+ <SwitchPrimitives.Thumb
20
+ className={cn(
21
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
22
+ )}
23
+ />
24
+ </SwitchPrimitives.Root>
25
+ ));
26
+ Switch.displayName = SwitchPrimitives.Root.displayName;
27
+
28
+ export { Switch };
@@ -0,0 +1,170 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Badge } from './badge'
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCaption,
7
+ TableCell,
8
+ TableFooter,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from './table'
13
+
14
+ const meta: Meta = {
15
+ title: 'Primitives/Table',
16
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
17
+ }
18
+
19
+ export default meta
20
+ type Story = StoryObj
21
+
22
+ type SessionStatus = 'running' | 'stopped' | 'creating' | 'warm'
23
+
24
+ const sessions: {
25
+ id: string
26
+ image: string
27
+ region: string
28
+ status: SessionStatus
29
+ cpu: string
30
+ memory: string
31
+ started: string
32
+ duration: string
33
+ }[] = [
34
+ {
35
+ id: 'sess_01j9x8k2m4n3p',
36
+ image: 'node:20-alpine',
37
+ region: 'us-east-1',
38
+ status: 'running',
39
+ cpu: '42%',
40
+ memory: '256 MB',
41
+ started: '14:02:11',
42
+ duration: '47m',
43
+ },
44
+ {
45
+ id: 'sess_01j9x7r9a1b2c',
46
+ image: 'python:3.12-slim',
47
+ region: 'us-east-1',
48
+ status: 'running',
49
+ cpu: '8%',
50
+ memory: '128 MB',
51
+ started: '13:55:44',
52
+ duration: '54m',
53
+ },
54
+ {
55
+ id: 'sess_01j9x6q8z0y1x',
56
+ image: 'golang:1.22-alpine',
57
+ region: 'eu-central-1',
58
+ status: 'warm',
59
+ cpu: '0%',
60
+ memory: '64 MB',
61
+ started: '13:40:02',
62
+ duration: '1h 9m',
63
+ },
64
+ {
65
+ id: 'sess_01j9x5p7w9v0u',
66
+ image: 'node:20-alpine',
67
+ region: 'us-west-2',
68
+ status: 'creating',
69
+ cpu: '—',
70
+ memory: '—',
71
+ started: '14:49:58',
72
+ duration: '< 1s',
73
+ },
74
+ {
75
+ id: 'sess_01j9x4o6v8u9t',
76
+ image: 'rust:1.77-slim',
77
+ region: 'us-east-1',
78
+ status: 'stopped',
79
+ cpu: '0%',
80
+ memory: '0 MB',
81
+ started: '11:22:30',
82
+ duration: '3h 27m',
83
+ },
84
+ ]
85
+
86
+ export const SessionsTable: Story = {
87
+ name: 'Sessions Table',
88
+ render: () => (
89
+ <div className="w-[900px] rounded-xl border border-border bg-card">
90
+ <Table>
91
+ <TableHeader>
92
+ <TableRow>
93
+ <TableHead>Session ID</TableHead>
94
+ <TableHead>Image</TableHead>
95
+ <TableHead>Region</TableHead>
96
+ <TableHead>Status</TableHead>
97
+ <TableHead>CPU</TableHead>
98
+ <TableHead>Memory</TableHead>
99
+ <TableHead>Started</TableHead>
100
+ <TableHead>Duration</TableHead>
101
+ </TableRow>
102
+ </TableHeader>
103
+ <TableBody>
104
+ {sessions.map((s) => (
105
+ <TableRow key={s.id}>
106
+ <TableCell className="font-mono text-xs text-muted-foreground">{s.id}</TableCell>
107
+ <TableCell className="font-mono text-xs">{s.image}</TableCell>
108
+ <TableCell className="text-xs text-muted-foreground">{s.region}</TableCell>
109
+ <TableCell>
110
+ <Badge variant={s.status} dot>
111
+ {s.status.charAt(0).toUpperCase() + s.status.slice(1)}
112
+ </Badge>
113
+ </TableCell>
114
+ <TableCell className="text-xs tabular-nums">{s.cpu}</TableCell>
115
+ <TableCell className="text-xs tabular-nums">{s.memory}</TableCell>
116
+ <TableCell className="font-mono text-xs text-muted-foreground">{s.started}</TableCell>
117
+ <TableCell className="text-xs tabular-nums">{s.duration}</TableCell>
118
+ </TableRow>
119
+ ))}
120
+ </TableBody>
121
+ <TableFooter>
122
+ <TableRow>
123
+ <TableCell colSpan={3} className="text-xs text-muted-foreground">
124
+ {sessions.length} sessions
125
+ </TableCell>
126
+ <TableCell className="text-xs">
127
+ <span className="text-green-400 font-medium">2 running</span>
128
+ {' · '}
129
+ <span className="text-muted-foreground">1 warm · 1 creating · 1 stopped</span>
130
+ </TableCell>
131
+ <TableCell colSpan={4} />
132
+ </TableRow>
133
+ </TableFooter>
134
+ </Table>
135
+ </div>
136
+ ),
137
+ }
138
+
139
+ export const SimpleTable: Story = {
140
+ name: 'Simple Table',
141
+ render: () => (
142
+ <div className="w-[480px] rounded-xl border border-border bg-card">
143
+ <Table>
144
+ <TableCaption>Recent billing activity</TableCaption>
145
+ <TableHeader>
146
+ <TableRow>
147
+ <TableHead>Date</TableHead>
148
+ <TableHead>Sessions</TableHead>
149
+ <TableHead className="text-right">Cost</TableHead>
150
+ </TableRow>
151
+ </TableHeader>
152
+ <TableBody>
153
+ {[
154
+ { date: '2026-03-29', sessions: 312, cost: '$4.82' },
155
+ { date: '2026-03-28', sessions: 287, cost: '$4.43' },
156
+ { date: '2026-03-27', sessions: 401, cost: '$6.19' },
157
+ { date: '2026-03-26', sessions: 198, cost: '$3.06' },
158
+ { date: '2026-03-25', sessions: 223, cost: '$3.44' },
159
+ ].map((row) => (
160
+ <TableRow key={row.date}>
161
+ <TableCell className="font-mono text-xs text-muted-foreground">{row.date}</TableCell>
162
+ <TableCell className="tabular-nums text-xs">{row.sessions}</TableCell>
163
+ <TableCell className="text-right tabular-nums text-xs font-medium">{row.cost}</TableCell>
164
+ </TableRow>
165
+ ))}
166
+ </TableBody>
167
+ </Table>
168
+ </div>
169
+ ),
170
+ }
@@ -0,0 +1,116 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="relative w-full overflow-auto">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full caption-bottom text-sm", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+ ));
16
+ Table.displayName = "Table";
17
+
18
+ const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
23
+ ));
24
+ TableHeader.displayName = "TableHeader";
25
+
26
+ const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <tbody
31
+ ref={ref}
32
+ className={cn("[&_tr:last-child]:border-0", className)}
33
+ {...props}
34
+ />
35
+ ));
36
+ TableBody.displayName = "TableBody";
37
+
38
+ const TableFooter = React.forwardRef<
39
+ HTMLTableSectionElement,
40
+ React.HTMLAttributes<HTMLTableSectionElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <tfoot
43
+ ref={ref}
44
+ className={cn(
45
+ "border-t bg-card font-medium [&>tr]:last:border-b-0",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ ));
51
+ TableFooter.displayName = "TableFooter";
52
+
53
+ const TableRow = React.forwardRef<
54
+ HTMLTableRowElement,
55
+ React.HTMLAttributes<HTMLTableRowElement>
56
+ >(({ className, ...props }, ref) => (
57
+ <tr
58
+ ref={ref}
59
+ className={cn(
60
+ "border-border border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted/50",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ ));
66
+ TableRow.displayName = "TableRow";
67
+
68
+ const TableHead = React.forwardRef<
69
+ HTMLTableCellElement,
70
+ React.ThHTMLAttributes<HTMLTableCellElement>
71
+ >(({ className, ...props }, ref) => (
72
+ <th
73
+ ref={ref}
74
+ className={cn(
75
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ ));
81
+ TableHead.displayName = "TableHead";
82
+
83
+ const TableCell = React.forwardRef<
84
+ HTMLTableCellElement,
85
+ React.TdHTMLAttributes<HTMLTableCellElement>
86
+ >(({ className, ...props }, ref) => (
87
+ <td
88
+ ref={ref}
89
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
90
+ {...props}
91
+ />
92
+ ));
93
+ TableCell.displayName = "TableCell";
94
+
95
+ const TableCaption = React.forwardRef<
96
+ HTMLTableCaptionElement,
97
+ React.HTMLAttributes<HTMLTableCaptionElement>
98
+ >(({ className, ...props }, ref) => (
99
+ <caption
100
+ ref={ref}
101
+ className={cn("mt-4 text-muted-foreground text-sm", className)}
102
+ {...props}
103
+ />
104
+ ));
105
+ TableCaption.displayName = "TableCaption";
106
+
107
+ export {
108
+ Table,
109
+ TableHeader,
110
+ TableBody,
111
+ TableFooter,
112
+ TableHead,
113
+ TableRow,
114
+ TableCell,
115
+ TableCaption,
116
+ };
@@ -0,0 +1,180 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs'
3
+
4
+ const meta: Meta = {
5
+ title: 'Primitives/Tabs',
6
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
7
+ }
8
+
9
+ export default meta
10
+ type Story = StoryObj
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <Tabs defaultValue="overview" className="w-96">
15
+ <TabsList>
16
+ <TabsTrigger value="overview">Overview</TabsTrigger>
17
+ <TabsTrigger value="logs">Logs</TabsTrigger>
18
+ <TabsTrigger value="metrics">Metrics</TabsTrigger>
19
+ </TabsList>
20
+ <TabsContent value="overview">
21
+ <p className="text-sm text-muted-foreground">Sandbox is running. Uptime: 4h 12m.</p>
22
+ </TabsContent>
23
+ <TabsContent value="logs">
24
+ <p className="text-sm text-muted-foreground font-mono">stdout: server listening on :3000</p>
25
+ </TabsContent>
26
+ <TabsContent value="metrics">
27
+ <p className="text-sm text-muted-foreground">CPU: 14% · Memory: 312 MB / 512 MB</p>
28
+ </TabsContent>
29
+ </Tabs>
30
+ ),
31
+ }
32
+
33
+ export const Pills: Story = {
34
+ render: () => (
35
+ <Tabs defaultValue="all" className="w-96">
36
+ <TabsList variant="pills">
37
+ <TabsTrigger value="all" variant="pills">All</TabsTrigger>
38
+ <TabsTrigger value="running" variant="pills">Running</TabsTrigger>
39
+ <TabsTrigger value="stopped" variant="pills">Stopped</TabsTrigger>
40
+ <TabsTrigger value="deleted" variant="pills">Deleted</TabsTrigger>
41
+ </TabsList>
42
+ <TabsContent value="all">
43
+ <p className="text-sm text-muted-foreground">Showing all 12 sandboxes.</p>
44
+ </TabsContent>
45
+ <TabsContent value="running">
46
+ <p className="text-sm text-muted-foreground">3 sandboxes currently running.</p>
47
+ </TabsContent>
48
+ <TabsContent value="stopped">
49
+ <p className="text-sm text-muted-foreground">7 sandboxes stopped.</p>
50
+ </TabsContent>
51
+ <TabsContent value="deleted">
52
+ <p className="text-sm text-muted-foreground">2 sandboxes deleted.</p>
53
+ </TabsContent>
54
+ </Tabs>
55
+ ),
56
+ }
57
+
58
+ export const Underline: Story = {
59
+ render: () => (
60
+ <Tabs defaultValue="settings" className="w-96">
61
+ <TabsList variant="underline">
62
+ <TabsTrigger value="settings" variant="underline">Settings</TabsTrigger>
63
+ <TabsTrigger value="env" variant="underline">Environment</TabsTrigger>
64
+ <TabsTrigger value="secrets" variant="underline">Secrets</TabsTrigger>
65
+ <TabsTrigger value="networking" variant="underline">Networking</TabsTrigger>
66
+ </TabsList>
67
+ <TabsContent value="settings">
68
+ <p className="text-sm text-muted-foreground">General sandbox settings.</p>
69
+ </TabsContent>
70
+ <TabsContent value="env">
71
+ <p className="text-sm text-muted-foreground font-mono">NODE_ENV=production</p>
72
+ </TabsContent>
73
+ <TabsContent value="secrets">
74
+ <p className="text-sm text-muted-foreground">3 secrets configured.</p>
75
+ </TabsContent>
76
+ <TabsContent value="networking">
77
+ <p className="text-sm text-muted-foreground">Port 3000 exposed publicly.</p>
78
+ </TabsContent>
79
+ </Tabs>
80
+ ),
81
+ }
82
+
83
+ export const WithDisabledTab: Story = {
84
+ name: 'With Disabled Tab',
85
+ render: () => (
86
+ <Tabs defaultValue="overview" className="w-96">
87
+ <TabsList>
88
+ <TabsTrigger value="overview">Overview</TabsTrigger>
89
+ <TabsTrigger value="logs">Logs</TabsTrigger>
90
+ <TabsTrigger value="billing" disabled>Billing</TabsTrigger>
91
+ </TabsList>
92
+ <TabsContent value="overview">
93
+ <p className="text-sm text-muted-foreground">Sandbox overview.</p>
94
+ </TabsContent>
95
+ <TabsContent value="logs">
96
+ <p className="text-sm text-muted-foreground font-mono">No recent log output.</p>
97
+ </TabsContent>
98
+ </Tabs>
99
+ ),
100
+ }
101
+
102
+ export const Overview: Story = {
103
+ name: 'Overview',
104
+ render: () => (
105
+ <div className="flex flex-col gap-10 p-6 w-[480px]">
106
+ <div>
107
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest mb-4">Default (segmented)</div>
108
+ <Tabs defaultValue="overview">
109
+ <TabsList>
110
+ <TabsTrigger value="overview">Overview</TabsTrigger>
111
+ <TabsTrigger value="logs">Logs</TabsTrigger>
112
+ <TabsTrigger value="metrics">Metrics</TabsTrigger>
113
+ <TabsTrigger value="config">Config</TabsTrigger>
114
+ </TabsList>
115
+ <TabsContent value="overview">
116
+ <div className="rounded-lg border border-border bg-card p-4 text-sm text-muted-foreground">
117
+ Status: Running · Region: us-east-1 · Uptime: 4h 12m
118
+ </div>
119
+ </TabsContent>
120
+ <TabsContent value="logs">
121
+ <div className="rounded-lg border border-border bg-card p-4 font-mono text-sm text-muted-foreground">
122
+ [INFO] server listening on :3000<br />
123
+ [INFO] connected to database
124
+ </div>
125
+ </TabsContent>
126
+ <TabsContent value="metrics">
127
+ <div className="rounded-lg border border-border bg-card p-4 text-sm text-muted-foreground">
128
+ CPU: 14% · Memory: 312 MB / 512 MB · Net I/O: 1.2 MB/s
129
+ </div>
130
+ </TabsContent>
131
+ <TabsContent value="config">
132
+ <div className="rounded-lg border border-border bg-card p-4 font-mono text-sm text-muted-foreground">
133
+ image: ubuntu:22.04 · cpu: 2 · memory: 512
134
+ </div>
135
+ </TabsContent>
136
+ </Tabs>
137
+ </div>
138
+
139
+ <div>
140
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest mb-4">Pills</div>
141
+ <Tabs defaultValue="all">
142
+ <TabsList variant="pills">
143
+ <TabsTrigger value="all" variant="pills">All</TabsTrigger>
144
+ <TabsTrigger value="running" variant="pills">Running</TabsTrigger>
145
+ <TabsTrigger value="stopped" variant="pills">Stopped</TabsTrigger>
146
+ </TabsList>
147
+ <TabsContent value="all">
148
+ <p className="text-sm text-muted-foreground mt-2">Showing all 12 sandboxes.</p>
149
+ </TabsContent>
150
+ <TabsContent value="running">
151
+ <p className="text-sm text-muted-foreground mt-2">3 sandboxes running.</p>
152
+ </TabsContent>
153
+ <TabsContent value="stopped">
154
+ <p className="text-sm text-muted-foreground mt-2">7 sandboxes stopped.</p>
155
+ </TabsContent>
156
+ </Tabs>
157
+ </div>
158
+
159
+ <div>
160
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest mb-4">Underline</div>
161
+ <Tabs defaultValue="settings">
162
+ <TabsList variant="underline">
163
+ <TabsTrigger value="settings" variant="underline">Settings</TabsTrigger>
164
+ <TabsTrigger value="env" variant="underline">Environment</TabsTrigger>
165
+ <TabsTrigger value="secrets" variant="underline">Secrets</TabsTrigger>
166
+ </TabsList>
167
+ <TabsContent value="settings">
168
+ <p className="text-sm text-muted-foreground">General sandbox settings.</p>
169
+ </TabsContent>
170
+ <TabsContent value="env">
171
+ <p className="text-sm text-muted-foreground font-mono">NODE_ENV=production</p>
172
+ </TabsContent>
173
+ <TabsContent value="secrets">
174
+ <p className="text-sm text-muted-foreground">3 secrets configured.</p>
175
+ </TabsContent>
176
+ </Tabs>
177
+ </div>
178
+ </div>
179
+ ),
180
+ }