@cloudbase/agent-react-ui 1.0.1-alpha.32

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 (109) hide show
  1. package/README.md +123 -0
  2. package/components.json +21 -0
  3. package/dist/index.css +4241 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.d.mts +59 -0
  6. package/dist/index.d.ts +59 -0
  7. package/dist/index.js +2169 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +2182 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/example/.env.sample +2 -0
  12. package/example/App.tsx +368 -0
  13. package/example/app.css +1 -0
  14. package/example/index.html +12 -0
  15. package/example/main.tsx +9 -0
  16. package/example/vite.config.ts +34 -0
  17. package/package.json +75 -0
  18. package/postcss.config.cjs +3 -0
  19. package/src/components/ai-elements/agent.tsx +140 -0
  20. package/src/components/ai-elements/artifact.tsx +147 -0
  21. package/src/components/ai-elements/attachments.tsx +421 -0
  22. package/src/components/ai-elements/audio-player.tsx +228 -0
  23. package/src/components/ai-elements/canvas.tsx +22 -0
  24. package/src/components/ai-elements/chain-of-thought.tsx +228 -0
  25. package/src/components/ai-elements/checkpoint.tsx +71 -0
  26. package/src/components/ai-elements/code-block.tsx +532 -0
  27. package/src/components/ai-elements/commit.tsx +448 -0
  28. package/src/components/ai-elements/confirmation.tsx +176 -0
  29. package/src/components/ai-elements/connection.tsx +28 -0
  30. package/src/components/ai-elements/context.tsx +408 -0
  31. package/src/components/ai-elements/controls.tsx +18 -0
  32. package/src/components/ai-elements/conversation.tsx +100 -0
  33. package/src/components/ai-elements/edge.tsx +140 -0
  34. package/src/components/ai-elements/environment-variables.tsx +295 -0
  35. package/src/components/ai-elements/file-tree.tsx +258 -0
  36. package/src/components/ai-elements/image.tsx +24 -0
  37. package/src/components/ai-elements/inline-citation.tsx +287 -0
  38. package/src/components/ai-elements/message.tsx +336 -0
  39. package/src/components/ai-elements/mic-selector.tsx +370 -0
  40. package/src/components/ai-elements/model-selector.tsx +211 -0
  41. package/src/components/ai-elements/node.tsx +71 -0
  42. package/src/components/ai-elements/open-in-chat.tsx +365 -0
  43. package/src/components/ai-elements/package-info.tsx +233 -0
  44. package/src/components/ai-elements/panel.tsx +15 -0
  45. package/src/components/ai-elements/persona.tsx +270 -0
  46. package/src/components/ai-elements/plan.tsx +142 -0
  47. package/src/components/ai-elements/prompt-input.tsx +1263 -0
  48. package/src/components/ai-elements/queue.tsx +274 -0
  49. package/src/components/ai-elements/reasoning.tsx +193 -0
  50. package/src/components/ai-elements/sandbox.tsx +126 -0
  51. package/src/components/ai-elements/schema-display.tsx +458 -0
  52. package/src/components/ai-elements/shimmer.tsx +64 -0
  53. package/src/components/ai-elements/snippet.tsx +139 -0
  54. package/src/components/ai-elements/sources.tsx +77 -0
  55. package/src/components/ai-elements/speech-input.tsx +301 -0
  56. package/src/components/ai-elements/stack-trace.tsx +482 -0
  57. package/src/components/ai-elements/suggestion.tsx +53 -0
  58. package/src/components/ai-elements/task.tsx +87 -0
  59. package/src/components/ai-elements/terminal.tsx +261 -0
  60. package/src/components/ai-elements/test-results.tsx +485 -0
  61. package/src/components/ai-elements/tool.tsx +174 -0
  62. package/src/components/ai-elements/toolbar.tsx +16 -0
  63. package/src/components/ai-elements/transcription.tsx +124 -0
  64. package/src/components/ai-elements/voice-selector.tsx +479 -0
  65. package/src/components/ai-elements/web-preview.tsx +263 -0
  66. package/src/components/chat/Chat.tsx +178 -0
  67. package/src/components/chat/Input.tsx +98 -0
  68. package/src/components/chat/Message.tsx +276 -0
  69. package/src/components/chat/index.ts +2 -0
  70. package/src/components/index.ts +1 -0
  71. package/src/components/ui/accordion.tsx +64 -0
  72. package/src/components/ui/alert.tsx +66 -0
  73. package/src/components/ui/avatar.tsx +107 -0
  74. package/src/components/ui/badge.tsx +48 -0
  75. package/src/components/ui/button-group.tsx +83 -0
  76. package/src/components/ui/button.tsx +64 -0
  77. package/src/components/ui/card.tsx +92 -0
  78. package/src/components/ui/carousel.tsx +239 -0
  79. package/src/components/ui/collapsible.tsx +31 -0
  80. package/src/components/ui/command.tsx +184 -0
  81. package/src/components/ui/dialog.tsx +158 -0
  82. package/src/components/ui/dropdown-menu.tsx +257 -0
  83. package/src/components/ui/hover-card.tsx +42 -0
  84. package/src/components/ui/input-group.tsx +168 -0
  85. package/src/components/ui/input.tsx +21 -0
  86. package/src/components/ui/popover.tsx +87 -0
  87. package/src/components/ui/progress.tsx +31 -0
  88. package/src/components/ui/scroll-area.tsx +56 -0
  89. package/src/components/ui/select.tsx +190 -0
  90. package/src/components/ui/separator.tsx +28 -0
  91. package/src/components/ui/spinner.tsx +16 -0
  92. package/src/components/ui/switch.tsx +33 -0
  93. package/src/components/ui/tabs.tsx +91 -0
  94. package/src/components/ui/textarea.tsx +18 -0
  95. package/src/components/ui/tooltip.tsx +61 -0
  96. package/src/css/global.css +123 -0
  97. package/src/css/index.css +1 -0
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/use-copy-to-clipboard.ts +31 -0
  100. package/src/index.ts +4 -0
  101. package/src/lib/utils.ts +6 -0
  102. package/src/locales/context.ts +8 -0
  103. package/src/locales/hooks.ts +20 -0
  104. package/src/locales/index.ts +3 -0
  105. package/src/locales/langs/en.ts +17 -0
  106. package/src/locales/langs/index.ts +12 -0
  107. package/src/locales/langs/zh-cn.ts +18 -0
  108. package/tsconfig.json +21 -0
  109. package/tsup.config.ts +21 -0
@@ -0,0 +1,274 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@/components/ui/collapsible";
9
+ import { ScrollArea } from "@/components/ui/scroll-area";
10
+ import { cn } from "@/lib/utils";
11
+ import { ChevronDownIcon, PaperclipIcon } from "lucide-react";
12
+ import type { ComponentProps } from "react";
13
+
14
+ export interface QueueMessagePart {
15
+ type: string;
16
+ text?: string;
17
+ url?: string;
18
+ filename?: string;
19
+ mediaType?: string;
20
+ }
21
+
22
+ export interface QueueMessage {
23
+ id: string;
24
+ parts: QueueMessagePart[];
25
+ }
26
+
27
+ export interface QueueTodo {
28
+ id: string;
29
+ title: string;
30
+ description?: string;
31
+ status?: "pending" | "completed";
32
+ }
33
+
34
+ export type QueueItemProps = ComponentProps<"li">;
35
+
36
+ export const QueueItem = ({ className, ...props }: QueueItemProps) => (
37
+ <li
38
+ className={cn(
39
+ "group flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors hover:bg-muted",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ );
45
+
46
+ export type QueueItemIndicatorProps = ComponentProps<"span"> & {
47
+ completed?: boolean;
48
+ };
49
+
50
+ export const QueueItemIndicator = ({
51
+ completed = false,
52
+ className,
53
+ ...props
54
+ }: QueueItemIndicatorProps) => (
55
+ <span
56
+ className={cn(
57
+ "mt-0.5 inline-block size-2.5 rounded-full border",
58
+ completed
59
+ ? "border-muted-foreground/20 bg-muted-foreground/10"
60
+ : "border-muted-foreground/50",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ );
66
+
67
+ export type QueueItemContentProps = ComponentProps<"span"> & {
68
+ completed?: boolean;
69
+ };
70
+
71
+ export const QueueItemContent = ({
72
+ completed = false,
73
+ className,
74
+ ...props
75
+ }: QueueItemContentProps) => (
76
+ <span
77
+ className={cn(
78
+ "line-clamp-1 grow break-words",
79
+ completed
80
+ ? "text-muted-foreground/50 line-through"
81
+ : "text-muted-foreground",
82
+ className
83
+ )}
84
+ {...props}
85
+ />
86
+ );
87
+
88
+ export type QueueItemDescriptionProps = ComponentProps<"div"> & {
89
+ completed?: boolean;
90
+ };
91
+
92
+ export const QueueItemDescription = ({
93
+ completed = false,
94
+ className,
95
+ ...props
96
+ }: QueueItemDescriptionProps) => (
97
+ <div
98
+ className={cn(
99
+ "ml-6 text-xs",
100
+ completed
101
+ ? "text-muted-foreground/40 line-through"
102
+ : "text-muted-foreground",
103
+ className
104
+ )}
105
+ {...props}
106
+ />
107
+ );
108
+
109
+ export type QueueItemActionsProps = ComponentProps<"div">;
110
+
111
+ export const QueueItemActions = ({
112
+ className,
113
+ ...props
114
+ }: QueueItemActionsProps) => (
115
+ <div className={cn("flex gap-1", className)} {...props} />
116
+ );
117
+
118
+ export type QueueItemActionProps = Omit<
119
+ ComponentProps<typeof Button>,
120
+ "variant" | "size"
121
+ >;
122
+
123
+ export const QueueItemAction = ({
124
+ className,
125
+ ...props
126
+ }: QueueItemActionProps) => (
127
+ <Button
128
+ className={cn(
129
+ "size-auto rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-muted-foreground/10 hover:text-foreground group-hover:opacity-100",
130
+ className
131
+ )}
132
+ size="icon"
133
+ type="button"
134
+ variant="ghost"
135
+ {...props}
136
+ />
137
+ );
138
+
139
+ export type QueueItemAttachmentProps = ComponentProps<"div">;
140
+
141
+ export const QueueItemAttachment = ({
142
+ className,
143
+ ...props
144
+ }: QueueItemAttachmentProps) => (
145
+ <div className={cn("mt-1 flex flex-wrap gap-2", className)} {...props} />
146
+ );
147
+
148
+ export type QueueItemImageProps = ComponentProps<"img">;
149
+
150
+ export const QueueItemImage = ({
151
+ className,
152
+ ...props
153
+ }: QueueItemImageProps) => (
154
+ <img
155
+ alt=""
156
+ className={cn("h-8 w-8 rounded border object-cover", className)}
157
+ height={32}
158
+ width={32}
159
+ {...props}
160
+ />
161
+ );
162
+
163
+ export type QueueItemFileProps = ComponentProps<"span">;
164
+
165
+ export const QueueItemFile = ({
166
+ children,
167
+ className,
168
+ ...props
169
+ }: QueueItemFileProps) => (
170
+ <span
171
+ className={cn(
172
+ "flex items-center gap-1 rounded border bg-muted px-2 py-1 text-xs",
173
+ className
174
+ )}
175
+ {...props}
176
+ >
177
+ <PaperclipIcon size={12} />
178
+ <span className="max-w-[100px] truncate">{children}</span>
179
+ </span>
180
+ );
181
+
182
+ export type QueueListProps = ComponentProps<typeof ScrollArea>;
183
+
184
+ export const QueueList = ({
185
+ children,
186
+ className,
187
+ ...props
188
+ }: QueueListProps) => (
189
+ <ScrollArea className={cn("mt-2 -mb-1", className)} {...props}>
190
+ <div className="max-h-40 pr-4">
191
+ <ul>{children}</ul>
192
+ </div>
193
+ </ScrollArea>
194
+ );
195
+
196
+ // QueueSection - collapsible section container
197
+ export type QueueSectionProps = ComponentProps<typeof Collapsible>;
198
+
199
+ export const QueueSection = ({
200
+ className,
201
+ defaultOpen = true,
202
+ ...props
203
+ }: QueueSectionProps) => (
204
+ <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
205
+ );
206
+
207
+ // QueueSectionTrigger - section header/trigger
208
+ export type QueueSectionTriggerProps = ComponentProps<"button">;
209
+
210
+ export const QueueSectionTrigger = ({
211
+ children,
212
+ className,
213
+ ...props
214
+ }: QueueSectionTriggerProps) => (
215
+ <CollapsibleTrigger asChild>
216
+ <button
217
+ className={cn(
218
+ "group flex w-full items-center justify-between rounded-md bg-muted/40 px-3 py-2 text-left font-medium text-muted-foreground text-sm transition-colors hover:bg-muted",
219
+ className
220
+ )}
221
+ type="button"
222
+ {...props}
223
+ >
224
+ {children}
225
+ </button>
226
+ </CollapsibleTrigger>
227
+ );
228
+
229
+ // QueueSectionLabel - label content with icon and count
230
+ export type QueueSectionLabelProps = ComponentProps<"span"> & {
231
+ count?: number;
232
+ label: string;
233
+ icon?: React.ReactNode;
234
+ };
235
+
236
+ export const QueueSectionLabel = ({
237
+ count,
238
+ label,
239
+ icon,
240
+ className,
241
+ ...props
242
+ }: QueueSectionLabelProps) => (
243
+ <span className={cn("flex items-center gap-2", className)} {...props}>
244
+ <ChevronDownIcon className="size-4 transition-transform group-data-[state=closed]:-rotate-90" />
245
+ {icon}
246
+ <span>
247
+ {count} {label}
248
+ </span>
249
+ </span>
250
+ );
251
+
252
+ // QueueSectionContent - collapsible content area
253
+ export type QueueSectionContentProps = ComponentProps<
254
+ typeof CollapsibleContent
255
+ >;
256
+
257
+ export const QueueSectionContent = ({
258
+ className,
259
+ ...props
260
+ }: QueueSectionContentProps) => (
261
+ <CollapsibleContent className={cn(className)} {...props} />
262
+ );
263
+
264
+ export type QueueProps = ComponentProps<"div">;
265
+
266
+ export const Queue = ({ className, ...props }: QueueProps) => (
267
+ <div
268
+ className={cn(
269
+ "flex flex-col gap-2 rounded-xl border border-border bg-background px-3 pt-2 pb-2 shadow-xs",
270
+ className
271
+ )}
272
+ {...props}
273
+ />
274
+ );
@@ -0,0 +1,193 @@
1
+ "use client";
2
+
3
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@/components/ui/collapsible";
9
+ import { cn } from "@/lib/utils";
10
+ import { cjk } from "@streamdown/cjk";
11
+ import { code } from "@streamdown/code";
12
+ import { math } from "@streamdown/math";
13
+ import { mermaid } from "@streamdown/mermaid";
14
+ import { BrainIcon, ChevronDownIcon } from "lucide-react";
15
+ import type { ComponentProps, ReactNode } from "react";
16
+ import { createContext, memo, useContext, useEffect, useState } from "react";
17
+ import { Streamdown } from "streamdown";
18
+ import { Shimmer } from "./shimmer";
19
+
20
+ interface ReasoningContextValue {
21
+ isStreaming: boolean;
22
+ isOpen: boolean;
23
+ setIsOpen: (open: boolean) => void;
24
+ duration: number | undefined;
25
+ }
26
+
27
+ const ReasoningContext = createContext<ReasoningContextValue | null>(null);
28
+
29
+ export const useReasoning = () => {
30
+ const context = useContext(ReasoningContext);
31
+ if (!context) {
32
+ throw new Error("Reasoning components must be used within Reasoning");
33
+ }
34
+ return context;
35
+ };
36
+
37
+ export type ReasoningProps = ComponentProps<typeof Collapsible> & {
38
+ isStreaming?: boolean;
39
+ open?: boolean;
40
+ defaultOpen?: boolean;
41
+ onOpenChange?: (open: boolean) => void;
42
+ duration?: number;
43
+ };
44
+
45
+ const AUTO_CLOSE_DELAY = 1000;
46
+ const MS_IN_S = 1000;
47
+
48
+ export const Reasoning = memo(
49
+ ({
50
+ className,
51
+ isStreaming = false,
52
+ open,
53
+ defaultOpen = true,
54
+ onOpenChange,
55
+ duration: durationProp,
56
+ children,
57
+ ...props
58
+ }: ReasoningProps) => {
59
+ const [isOpen, setIsOpen] = useControllableState({
60
+ prop: open,
61
+ defaultProp: defaultOpen,
62
+ onChange: onOpenChange,
63
+ });
64
+ const [duration, setDuration] = useControllableState({
65
+ prop: durationProp,
66
+ defaultProp: undefined,
67
+ });
68
+
69
+ const [hasAutoClosed, setHasAutoClosed] = useState(false);
70
+ const [startTime, setStartTime] = useState<number | null>(null);
71
+
72
+ // Track duration when streaming starts and ends
73
+ useEffect(() => {
74
+ if (isStreaming) {
75
+ if (startTime === null) {
76
+ setStartTime(Date.now());
77
+ }
78
+ } else if (startTime !== null) {
79
+ setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
80
+ setStartTime(null);
81
+ }
82
+ }, [isStreaming, startTime, setDuration]);
83
+
84
+ // Auto-open when streaming starts, auto-close when streaming ends (once only)
85
+ useEffect(() => {
86
+ if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
87
+ // Add a small delay before closing to allow user to see the content
88
+ const timer = setTimeout(() => {
89
+ setIsOpen(false);
90
+ setHasAutoClosed(true);
91
+ }, AUTO_CLOSE_DELAY);
92
+
93
+ return () => clearTimeout(timer);
94
+ }
95
+ }, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed]);
96
+
97
+ const handleOpenChange = (newOpen: boolean) => {
98
+ setIsOpen(newOpen);
99
+ };
100
+
101
+ return (
102
+ <ReasoningContext.Provider
103
+ value={{ isStreaming, isOpen, setIsOpen, duration }}
104
+ >
105
+ <Collapsible
106
+ className={cn("not-prose mb-4", className)}
107
+ onOpenChange={handleOpenChange}
108
+ open={isOpen}
109
+ {...props}
110
+ >
111
+ {children}
112
+ </Collapsible>
113
+ </ReasoningContext.Provider>
114
+ );
115
+ }
116
+ );
117
+
118
+ export type ReasoningTriggerProps = ComponentProps<
119
+ typeof CollapsibleTrigger
120
+ > & {
121
+ getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
122
+ };
123
+
124
+ const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
125
+ if (isStreaming || duration === 0) {
126
+ return <Shimmer duration={1}>Thinking...</Shimmer>;
127
+ }
128
+ if (duration === undefined) {
129
+ return <p>Thought for a few seconds</p>;
130
+ }
131
+ return <p>Thought for {duration} seconds</p>;
132
+ };
133
+
134
+ export const ReasoningTrigger = memo(
135
+ ({
136
+ className,
137
+ children,
138
+ getThinkingMessage = defaultGetThinkingMessage,
139
+ ...props
140
+ }: ReasoningTriggerProps) => {
141
+ const { isStreaming, isOpen, duration } = useReasoning();
142
+
143
+ return (
144
+ <CollapsibleTrigger
145
+ className={cn(
146
+ "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
147
+ className
148
+ )}
149
+ {...props}
150
+ >
151
+ {children ?? (
152
+ <>
153
+ <BrainIcon className="size-4" />
154
+ {getThinkingMessage(isStreaming, duration)}
155
+ <ChevronDownIcon
156
+ className={cn(
157
+ "size-4 transition-transform",
158
+ isOpen ? "rotate-180" : "rotate-0"
159
+ )}
160
+ />
161
+ </>
162
+ )}
163
+ </CollapsibleTrigger>
164
+ );
165
+ }
166
+ );
167
+
168
+ export type ReasoningContentProps = ComponentProps<
169
+ typeof CollapsibleContent
170
+ > & {
171
+ children: string;
172
+ };
173
+
174
+ export const ReasoningContent = memo(
175
+ ({ className, children, ...props }: ReasoningContentProps) => (
176
+ <CollapsibleContent
177
+ className={cn(
178
+ "mt-4 text-sm",
179
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
180
+ className
181
+ )}
182
+ {...props}
183
+ >
184
+ <Streamdown plugins={{ code, mermaid, math, cjk }} {...props}>
185
+ {children}
186
+ </Streamdown>
187
+ </CollapsibleContent>
188
+ )
189
+ );
190
+
191
+ Reasoning.displayName = "Reasoning";
192
+ ReasoningTrigger.displayName = "ReasoningTrigger";
193
+ ReasoningContent.displayName = "ReasoningContent";
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "@/components/ui/collapsible";
8
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
9
+ import { cn } from "@/lib/utils";
10
+ import type { ToolUIPart } from "ai";
11
+ import { ChevronDownIcon, Code } from "lucide-react";
12
+ import type { ComponentProps } from "react";
13
+ import { getStatusBadge } from "./tool";
14
+
15
+ export type SandboxRootProps = ComponentProps<typeof Collapsible>;
16
+
17
+ export const Sandbox = ({ className, ...props }: SandboxRootProps) => (
18
+ <Collapsible
19
+ className={cn(
20
+ "not-prose group mb-4 w-full overflow-hidden rounded-md border",
21
+ className
22
+ )}
23
+ defaultOpen
24
+ {...props}
25
+ />
26
+ );
27
+
28
+ export interface SandboxHeaderProps {
29
+ title?: string;
30
+ state: ToolUIPart["state"];
31
+ className?: string;
32
+ }
33
+
34
+ export const SandboxHeader = ({
35
+ className,
36
+ title,
37
+ state,
38
+ ...props
39
+ }: SandboxHeaderProps) => (
40
+ <CollapsibleTrigger
41
+ className={cn(
42
+ "flex w-full items-center justify-between gap-4 p-3",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <div className="flex items-center gap-2">
48
+ <Code className="size-4 text-muted-foreground" />
49
+ <span className="font-medium text-sm">{title}</span>
50
+ {getStatusBadge(state)}
51
+ </div>
52
+ <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
53
+ </CollapsibleTrigger>
54
+ );
55
+
56
+ export type SandboxContentProps = ComponentProps<typeof CollapsibleContent>;
57
+
58
+ export const SandboxContent = ({
59
+ className,
60
+ ...props
61
+ }: SandboxContentProps) => (
62
+ <CollapsibleContent
63
+ className={cn(
64
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ );
70
+
71
+ export type SandboxTabsProps = ComponentProps<typeof Tabs>;
72
+
73
+ export const SandboxTabs = ({ className, ...props }: SandboxTabsProps) => (
74
+ <Tabs className={cn("w-full gap-0", className)} {...props} />
75
+ );
76
+
77
+ export type SandboxTabsBarProps = ComponentProps<"div">;
78
+
79
+ export const SandboxTabsBar = ({
80
+ className,
81
+ ...props
82
+ }: SandboxTabsBarProps) => (
83
+ <div
84
+ className={cn(
85
+ "flex w-full items-center border-border border-t border-b",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ );
91
+
92
+ export type SandboxTabsListProps = ComponentProps<typeof TabsList>;
93
+
94
+ export const SandboxTabsList = ({
95
+ className,
96
+ ...props
97
+ }: SandboxTabsListProps) => (
98
+ <TabsList
99
+ className={cn("h-auto rounded-none border-0 bg-transparent p-0", className)}
100
+ {...props}
101
+ />
102
+ );
103
+
104
+ export type SandboxTabsTriggerProps = ComponentProps<typeof TabsTrigger>;
105
+
106
+ export const SandboxTabsTrigger = ({
107
+ className,
108
+ ...props
109
+ }: SandboxTabsTriggerProps) => (
110
+ <TabsTrigger
111
+ className={cn(
112
+ "rounded-none border-0 border-transparent border-b-2 px-4 py-2 font-medium text-muted-foreground text-sm transition-colors data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:text-foreground data-[state=active]:shadow-none",
113
+ className
114
+ )}
115
+ {...props}
116
+ />
117
+ );
118
+
119
+ export type SandboxTabContentProps = ComponentProps<typeof TabsContent>;
120
+
121
+ export const SandboxTabContent = ({
122
+ className,
123
+ ...props
124
+ }: SandboxTabContentProps) => (
125
+ <TabsContent className={cn("mt-0 text-sm", className)} {...props} />
126
+ );