@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,40 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ icon?: React.ReactNode;
6
+ title: string;
7
+ description?: string;
8
+ action?: React.ReactNode;
9
+ }
10
+
11
+ const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
12
+ ({ className, icon, title, description, action, ...props }, ref) => {
13
+ return (
14
+ <div
15
+ ref={ref}
16
+ className={cn(
17
+ "flex flex-col items-center justify-center px-4 py-16 text-center",
18
+ className,
19
+ )}
20
+ {...props}
21
+ >
22
+ {icon && (
23
+ <div className="mb-4 rounded-full bg-muted p-4 text-muted-foreground">
24
+ {icon}
25
+ </div>
26
+ )}
27
+ <h3 className="font-semibold text-lg">{title}</h3>
28
+ {description && (
29
+ <p className="mt-2 max-w-sm text-muted-foreground text-sm">
30
+ {description}
31
+ </p>
32
+ )}
33
+ {action && <div className="mt-6">{action}</div>}
34
+ </div>
35
+ );
36
+ },
37
+ );
38
+ EmptyState.displayName = "EmptyState";
39
+
40
+ export { EmptyState };
@@ -0,0 +1,118 @@
1
+ export { Button, buttonVariants } from "./button";
2
+ export type { ButtonProps } from "./button";
3
+
4
+ export {
5
+ Card,
6
+ CardHeader,
7
+ CardFooter,
8
+ CardTitle,
9
+ CardDescription,
10
+ CardContent,
11
+ } from "./card";
12
+
13
+ export {
14
+ Dialog,
15
+ DialogPortal,
16
+ DialogOverlay,
17
+ DialogClose,
18
+ DialogTrigger,
19
+ DialogContent,
20
+ DialogHeader,
21
+ DialogFooter,
22
+ DialogTitle,
23
+ DialogDescription,
24
+ } from "./dialog";
25
+
26
+ export { Input, Textarea } from "./input";
27
+ export type { InputProps, TextareaProps } from "./input";
28
+
29
+ export { Badge, badgeVariants } from "./badge";
30
+ export type { BadgeProps } from "./badge";
31
+
32
+ export { Avatar, AvatarImage, AvatarFallback } from "./avatar";
33
+
34
+ export {
35
+ DropdownMenu,
36
+ DropdownMenuTrigger,
37
+ DropdownMenuContent,
38
+ DropdownMenuItem,
39
+ DropdownMenuCheckboxItem,
40
+ DropdownMenuRadioItem,
41
+ DropdownMenuLabel,
42
+ DropdownMenuSeparator,
43
+ DropdownMenuShortcut,
44
+ DropdownMenuGroup,
45
+ DropdownMenuPortal,
46
+ DropdownMenuSub,
47
+ DropdownMenuSubContent,
48
+ DropdownMenuSubTrigger,
49
+ DropdownMenuRadioGroup,
50
+ } from "./dropdown-menu";
51
+
52
+ export {
53
+ Select,
54
+ SelectContent,
55
+ SelectGroup,
56
+ SelectItem,
57
+ SelectLabel,
58
+ SelectScrollDownButton,
59
+ SelectScrollUpButton,
60
+ SelectSeparator,
61
+ SelectTrigger,
62
+ SelectValue,
63
+ } from "./select";
64
+
65
+ export {
66
+ Table,
67
+ TableHeader,
68
+ TableBody,
69
+ TableFooter,
70
+ TableHead,
71
+ TableRow,
72
+ TableCell,
73
+ TableCaption,
74
+ } from "./table";
75
+
76
+ export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
77
+
78
+ export { SegmentedControl } from "./segmented-control";
79
+ export type {
80
+ SegmentedControlOption,
81
+ SegmentedControlProps,
82
+ } from "./segmented-control";
83
+
84
+ export { Progress } from "./progress";
85
+
86
+ export { Switch } from "./switch";
87
+
88
+ export { Skeleton, SkeletonCard, SkeletonTable } from "./skeleton";
89
+
90
+ export { ToastContainer, ToastProvider, useToast } from "./toast";
91
+ export type { Toast } from "./toast";
92
+
93
+ export { Label } from "./label";
94
+
95
+ export { EmptyState } from "./empty-state";
96
+ export type { EmptyStateProps } from "./empty-state";
97
+
98
+ export { StatCard } from "./stat-card";
99
+ export type { StatCardProps } from "./stat-card";
100
+
101
+ export { Logo, TangleKnot, type LogoProps } from "@tangle-network/brand";
102
+
103
+ export { TerminalDisplay, TerminalLine, TerminalInput, TerminalCursor } from "./terminal-display";
104
+
105
+ export { DropZone } from "./drop-zone";
106
+ export type { DropZoneProps } from "./drop-zone";
107
+
108
+ export { UploadProgress } from "./upload-progress";
109
+ export type { UploadProgressProps, UploadFile } from "./upload-progress";
110
+
111
+ export { SidebarDropZone } from "./sidebar-drop-zone";
112
+ export type { SidebarDropZoneProps } from "./sidebar-drop-zone";
113
+
114
+ export { ArtifactPane } from "./artifact-pane";
115
+ export type { ArtifactPaneProps } from "./artifact-pane";
116
+
117
+ export { CodeBlock, CopyButton, InlineCode } from "./code-block";
118
+ export type { CodeBlockProps, InlineCodeProps } from "./code-block";
@@ -0,0 +1,113 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Input, Textarea } from './input'
3
+
4
+ const meta: Meta<typeof Input> = {
5
+ title: 'Primitives/Input',
6
+ component: Input,
7
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
8
+ }
9
+
10
+ export default meta
11
+ type Story = StoryObj<typeof Input>
12
+
13
+ export const Default: Story = {
14
+ render: () => <Input placeholder="Enter value..." className="w-72" />,
15
+ }
16
+
17
+ export const WithLabel: Story = {
18
+ render: () => (
19
+ <div className="w-72">
20
+ <Input label="Sandbox name" placeholder="my-sandbox-01" />
21
+ </div>
22
+ ),
23
+ }
24
+
25
+ export const WithHint: Story = {
26
+ render: () => (
27
+ <div className="w-72">
28
+ <Input
29
+ label="Memory limit"
30
+ placeholder="512"
31
+ hint="Value in megabytes. Max 8192."
32
+ />
33
+ </div>
34
+ ),
35
+ }
36
+
37
+ export const WithError: Story = {
38
+ render: () => (
39
+ <div className="w-72">
40
+ <Input
41
+ label="Image tag"
42
+ defaultValue="latest!!"
43
+ error="Image tag contains invalid characters."
44
+ />
45
+ </div>
46
+ ),
47
+ }
48
+
49
+ export const SandboxVariant: Story = {
50
+ render: () => (
51
+ <div className="w-72">
52
+ <Input variant="sandbox" label="API key" placeholder="sk-..." />
53
+ </div>
54
+ ),
55
+ }
56
+
57
+ export const Disabled: Story = {
58
+ render: () => (
59
+ <div className="w-72">
60
+ <Input label="Region" defaultValue="us-east-1" disabled />
61
+ </div>
62
+ ),
63
+ }
64
+
65
+ export const TextareaDefault: Story = {
66
+ name: 'Textarea / Default',
67
+ render: () => (
68
+ <div className="w-72">
69
+ <Textarea placeholder="Describe the sandbox configuration..." />
70
+ </div>
71
+ ),
72
+ }
73
+
74
+ export const TextareaWithLabelAndHint: Story = {
75
+ name: 'Textarea / With Label & Hint',
76
+ render: () => (
77
+ <div className="w-72">
78
+ <Textarea
79
+ label="Startup script"
80
+ placeholder="#!/bin/bash&#10;npm install"
81
+ hint="Runs once when the sandbox boots."
82
+ />
83
+ </div>
84
+ ),
85
+ }
86
+
87
+ export const Overview: Story = {
88
+ name: 'Overview',
89
+ render: () => (
90
+ <div className="flex flex-col gap-6 p-6 w-96">
91
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Input variants</div>
92
+ <Input label="Sandbox name" placeholder="my-sandbox-01" />
93
+ <Input
94
+ variant="sandbox"
95
+ label="Secret key"
96
+ placeholder="sk-live-..."
97
+ hint="Keep this value private."
98
+ />
99
+ <Input
100
+ label="CPU cores"
101
+ defaultValue="99cores"
102
+ error="Must be a number between 1 and 32."
103
+ />
104
+ <Input label="Region" defaultValue="us-east-1" disabled />
105
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest mt-2">Textarea</div>
106
+ <Textarea
107
+ label="Environment variables"
108
+ placeholder="NODE_ENV=production&#10;PORT=3000"
109
+ hint="One variable per line in KEY=VALUE format."
110
+ />
111
+ </div>
112
+ ),
113
+ }
@@ -0,0 +1,136 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ const inputVariants = cva(
7
+ "flex w-full rounded-lg border bg-card px-4 py-2 text-sm transition-all duration-200 placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 file:border-0 file:bg-transparent file:font-medium file:text-sm",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "border-input focus:ring-ring",
12
+ sandbox: "border-border focus:border-[var(--border-accent-hover)] focus:ring-[var(--border-accent)]",
13
+ error: "border-[var(--surface-danger-border)] focus:ring-[var(--surface-danger-border)]",
14
+ },
15
+ size: {
16
+ default: "h-11",
17
+ sm: "h-9 px-3",
18
+ lg: "h-12 px-5",
19
+ }
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ size: "default",
24
+ },
25
+ }
26
+ );
27
+
28
+ export interface InputProps
29
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
30
+ VariantProps<typeof inputVariants> {
31
+ label?: string;
32
+ error?: string;
33
+ hint?: string;
34
+ }
35
+
36
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
37
+ (
38
+ { className, type, variant, size, label, error, hint, id, ...props },
39
+ ref,
40
+ ) => {
41
+ const inputId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
42
+
43
+ const input = (
44
+ <input
45
+ type={type}
46
+ id={inputId}
47
+ className={cn(inputVariants({ variant: error ? "error" : variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+
53
+ if (!label && !error && !hint) return input;
54
+
55
+ return (
56
+ <div className="w-full space-y-1.5">
57
+ {label && (
58
+ <label
59
+ htmlFor={inputId}
60
+ className="block font-medium text-foreground text-sm"
61
+ >
62
+ {label}
63
+ </label>
64
+ )}
65
+ {input}
66
+ {error && <p className="text-[var(--surface-danger-text)] text-sm font-medium">{error}</p>}
67
+ {hint && !error && (
68
+ <p className="text-muted-foreground/70 text-sm">{hint}</p>
69
+ )}
70
+ </div>
71
+ );
72
+ },
73
+ );
74
+ Input.displayName = "Input";
75
+
76
+ export interface TextareaProps
77
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
78
+ variant?: "default" | "sandbox";
79
+ label?: string;
80
+ error?: string;
81
+ hint?: string;
82
+ }
83
+
84
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
85
+ (
86
+ { className, variant = "default", label, error, hint, id, ...props },
87
+ ref,
88
+ ) => {
89
+ const textareaId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
90
+
91
+ const variants = {
92
+ default: "border-input focus:ring-ring",
93
+ sandbox:
94
+ "border-border focus:border-[var(--border-accent-hover)] focus:ring-[var(--border-accent)]",
95
+ };
96
+
97
+ const textarea = (
98
+ <textarea
99
+ id={textareaId}
100
+ className={cn(
101
+ "flex min-h-[120px] w-full resize-y rounded-lg border bg-card px-4 py-3 text-sm transition-all duration-200",
102
+ "placeholder:text-muted-foreground",
103
+ "focus:outline-none focus:ring-2 focus:ring-offset-0",
104
+ "disabled:cursor-not-allowed disabled:opacity-50",
105
+ error ? "border-[var(--surface-danger-border)] focus:ring-[var(--surface-danger-border)]" : variants[variant],
106
+ className,
107
+ )}
108
+ ref={ref}
109
+ {...props}
110
+ />
111
+ );
112
+
113
+ if (!label && !error && !hint) return textarea;
114
+
115
+ return (
116
+ <div className="w-full space-y-1.5">
117
+ {label && (
118
+ <label
119
+ htmlFor={textareaId}
120
+ className="block font-medium text-muted-foreground text-sm"
121
+ >
122
+ {label}
123
+ </label>
124
+ )}
125
+ {textarea}
126
+ {error && <p className="text-[var(--surface-danger-text)] text-sm">{error}</p>}
127
+ {hint && !error && (
128
+ <p className="text-muted-foreground/70 text-sm">{hint}</p>
129
+ )}
130
+ </div>
131
+ );
132
+ },
133
+ );
134
+ Textarea.displayName = "Textarea";
135
+
136
+ export { Input, Textarea };
@@ -0,0 +1,84 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Label } from './label'
3
+ import { Input } from './input'
4
+ import { Switch } from './switch'
5
+
6
+ const meta: Meta<typeof Label> = {
7
+ title: 'Primitives/Label',
8
+ component: Label,
9
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
10
+ args: { children: 'Field label' },
11
+ }
12
+
13
+ export default meta
14
+ type Story = StoryObj<typeof Label>
15
+
16
+ export const Default: Story = {}
17
+
18
+ export const WithInput: Story = {
19
+ render: () => (
20
+ <div className="flex flex-col gap-1.5 w-64">
21
+ <Label htmlFor="sandbox-name">Sandbox name</Label>
22
+ <Input id="sandbox-name" placeholder="my-sandbox-01" />
23
+ </div>
24
+ ),
25
+ }
26
+
27
+ export const WithSwitch: Story = {
28
+ render: () => (
29
+ <div className="flex items-center gap-2">
30
+ <Switch id="auto-sleep" />
31
+ <Label htmlFor="auto-sleep">Enable auto-sleep</Label>
32
+ </div>
33
+ ),
34
+ }
35
+
36
+ export const DisabledPeer: Story = {
37
+ name: 'Disabled (peer state)',
38
+ render: () => (
39
+ <div className="flex items-center gap-2">
40
+ <Switch id="auto-sleep-disabled" disabled />
41
+ <Label htmlFor="auto-sleep-disabled" className="peer">
42
+ Enable auto-sleep
43
+ </Label>
44
+ </div>
45
+ ),
46
+ }
47
+
48
+ export const Overview: Story = {
49
+ name: 'Overview',
50
+ render: () => (
51
+ <div className="flex flex-col gap-6 p-6 w-80">
52
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Standalone</div>
53
+ <Label>Standalone label</Label>
54
+
55
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Paired with input</div>
56
+ <div className="flex flex-col gap-1.5">
57
+ <Label htmlFor="overview-name">Sandbox name</Label>
58
+ <Input id="overview-name" placeholder="my-sandbox-01" />
59
+ </div>
60
+ <div className="flex flex-col gap-1.5">
61
+ <Label htmlFor="overview-image">Container image</Label>
62
+ <Input id="overview-image" placeholder="ubuntu:22.04" />
63
+ </div>
64
+
65
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Paired with switch</div>
66
+ <div className="flex flex-col gap-3">
67
+ <div className="flex items-center gap-2">
68
+ <Switch id="overview-sleep" defaultChecked />
69
+ <Label htmlFor="overview-sleep">Auto-sleep after idle</Label>
70
+ </div>
71
+ <div className="flex items-center gap-2">
72
+ <Switch id="overview-telemetry" />
73
+ <Label htmlFor="overview-telemetry">Send usage telemetry</Label>
74
+ </div>
75
+ <div className="flex items-center gap-2">
76
+ <Switch id="overview-disabled" disabled />
77
+ <Label htmlFor="overview-disabled" className="opacity-50">
78
+ Public access (requires upgrade)
79
+ </Label>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ ),
84
+ }
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import * as LabelPrimitive from "@radix-ui/react-label";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import * as React from "react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const labelVariants = cva(
9
+ "font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
10
+ );
11
+
12
+ const Label = React.forwardRef<
13
+ React.ElementRef<typeof LabelPrimitive.Root>,
14
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ));
22
+ Label.displayName = LabelPrimitive.Root.displayName;
23
+
24
+ export { Label };
@@ -0,0 +1,93 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Progress } from './progress'
3
+
4
+ const meta: Meta<typeof Progress> = {
5
+ title: 'Primitives/Progress',
6
+ component: Progress,
7
+ parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
8
+ args: { value: 60 },
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof Progress>
13
+
14
+ export const Default: Story = {
15
+ render: () => <Progress value={60} className="w-80" />,
16
+ }
17
+
18
+ export const SandboxVariant: Story = {
19
+ name: 'Sandbox variant',
20
+ render: () => <Progress value={60} variant="sandbox" className="w-80" />,
21
+ }
22
+
23
+ export const WithValue: Story = {
24
+ name: 'With value label',
25
+ render: () => (
26
+ <div className="pt-6 w-80">
27
+ <Progress value={73} showValue />
28
+ </div>
29
+ ),
30
+ }
31
+
32
+ export const SandboxWithValue: Story = {
33
+ name: 'Sandbox with value label',
34
+ render: () => (
35
+ <div className="pt-6 w-80">
36
+ <Progress value={73} variant="sandbox" showValue />
37
+ </div>
38
+ ),
39
+ }
40
+
41
+ export const Empty: Story = {
42
+ render: () => <Progress value={0} className="w-80" />,
43
+ }
44
+
45
+ export const Full: Story = {
46
+ render: () => <Progress value={100} className="w-80" />,
47
+ }
48
+
49
+ export const Overview: Story = {
50
+ name: 'Overview',
51
+ render: () => (
52
+ <div className="flex flex-col gap-8 p-6 w-96">
53
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Default variant</div>
54
+ <div className="flex flex-col gap-4">
55
+ {[0, 25, 50, 75, 100].map((v) => (
56
+ <div key={v} className="flex items-center gap-3">
57
+ <span className="text-muted-foreground text-xs w-8 text-right">{v}%</span>
58
+ <Progress value={v} className="flex-1" />
59
+ </div>
60
+ ))}
61
+ </div>
62
+
63
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Sandbox variant</div>
64
+ <div className="flex flex-col gap-4">
65
+ {[0, 25, 50, 75, 100].map((v) => (
66
+ <div key={v} className="flex items-center gap-3">
67
+ <span className="text-muted-foreground text-xs w-8 text-right">{v}%</span>
68
+ <Progress value={v} variant="sandbox" className="flex-1" />
69
+ </div>
70
+ ))}
71
+ </div>
72
+
73
+ <div className="text-muted-foreground text-xs font-mono uppercase tracking-widest">Sandbox task list</div>
74
+ <div className="rounded-xl border border-border bg-card p-4 flex flex-col gap-4">
75
+ {[
76
+ { label: 'Pulling image', value: 100 },
77
+ { label: 'Provisioning volume', value: 100 },
78
+ { label: 'Starting runtime', value: 68 },
79
+ { label: 'Installing packages', value: 12 },
80
+ { label: 'Applying config', value: 0 },
81
+ ].map(({ label, value }) => (
82
+ <div key={label} className="flex flex-col gap-1.5">
83
+ <div className="flex justify-between">
84
+ <span className="text-sm">{label}</span>
85
+ <span className="text-muted-foreground text-xs">{value}%</span>
86
+ </div>
87
+ <Progress value={value} variant="sandbox" />
88
+ </div>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ ),
93
+ }
@@ -0,0 +1,50 @@
1
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils";
4
+
5
+ const Progress = React.forwardRef<
6
+ React.ElementRef<typeof ProgressPrimitive.Root>,
7
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & {
8
+ variant?: "default" | "sandbox";
9
+ showValue?: boolean;
10
+ }
11
+ >(
12
+ (
13
+ { className, value, variant = "default", showValue = false, ...props },
14
+ ref,
15
+ ) => {
16
+ const indicatorVariants = {
17
+ default: "bg-primary",
18
+ sandbox: "bg-[image:var(--accent-gradient-strong)]",
19
+ };
20
+
21
+ return (
22
+ <div className="relative">
23
+ <ProgressPrimitive.Root
24
+ ref={ref}
25
+ className={cn(
26
+ "relative h-2 w-full overflow-hidden rounded-full bg-muted",
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ <ProgressPrimitive.Indicator
32
+ className={cn(
33
+ "h-full w-full flex-1 transition-all duration-300 ease-out",
34
+ indicatorVariants[variant],
35
+ )}
36
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
37
+ />
38
+ </ProgressPrimitive.Root>
39
+ {showValue && (
40
+ <span className="absolute -top-6 right-0 text-muted-foreground text-xs">
41
+ {value}%
42
+ </span>
43
+ )}
44
+ </div>
45
+ );
46
+ },
47
+ );
48
+ Progress.displayName = ProgressPrimitive.Root.displayName;
49
+
50
+ export { Progress };