@openconsole/shadcn 0.2.2 → 0.2.5

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 (118) hide show
  1. package/README.md +460 -380
  2. package/components/ai-elements/agent.tsx +141 -0
  3. package/components/ai-elements/artifact.tsx +148 -0
  4. package/components/ai-elements/attachments.tsx +426 -0
  5. package/components/ai-elements/audio-player.tsx +231 -0
  6. package/components/ai-elements/canvas.tsx +26 -0
  7. package/components/ai-elements/chain-of-thought.tsx +222 -0
  8. package/components/ai-elements/checkpoint.tsx +71 -0
  9. package/components/ai-elements/code-block.tsx +562 -0
  10. package/components/ai-elements/commit.tsx +458 -0
  11. package/components/ai-elements/confirmation.tsx +174 -0
  12. package/components/ai-elements/connection.tsx +28 -0
  13. package/components/ai-elements/context.tsx +409 -0
  14. package/components/ai-elements/controls.tsx +18 -0
  15. package/components/ai-elements/conversation.tsx +168 -0
  16. package/components/ai-elements/edge.tsx +143 -0
  17. package/components/ai-elements/environment-variables.tsx +324 -0
  18. package/components/ai-elements/file-tree.tsx +304 -0
  19. package/components/ai-elements/image.tsx +24 -0
  20. package/components/ai-elements/index.ts +51 -0
  21. package/components/ai-elements/inline-citation.tsx +296 -0
  22. package/components/ai-elements/jsx-preview.tsx +310 -0
  23. package/components/ai-elements/message.tsx +360 -0
  24. package/components/ai-elements/mic-selector.tsx +375 -0
  25. package/components/ai-elements/model-selector.tsx +213 -0
  26. package/components/ai-elements/node.tsx +71 -0
  27. package/components/ai-elements/open-in-chat.tsx +370 -0
  28. package/components/ai-elements/package-info.tsx +239 -0
  29. package/components/ai-elements/panel.tsx +15 -0
  30. package/components/ai-elements/persona.tsx +306 -0
  31. package/components/ai-elements/plan.tsx +147 -0
  32. package/components/ai-elements/prompt-input.tsx +1463 -0
  33. package/components/ai-elements/queue.tsx +274 -0
  34. package/components/ai-elements/reasoning.tsx +228 -0
  35. package/components/ai-elements/sandbox.tsx +132 -0
  36. package/components/ai-elements/schema-display.tsx +471 -0
  37. package/components/ai-elements/shimmer.tsx +77 -0
  38. package/components/ai-elements/snippet.tsx +145 -0
  39. package/components/ai-elements/sources.tsx +77 -0
  40. package/components/ai-elements/speech-input.tsx +323 -0
  41. package/components/ai-elements/stack-trace.tsx +528 -0
  42. package/components/ai-elements/suggestion.tsx +57 -0
  43. package/components/ai-elements/task.tsx +87 -0
  44. package/components/ai-elements/terminal.tsx +273 -0
  45. package/components/ai-elements/test-results.tsx +496 -0
  46. package/components/ai-elements/tool.tsx +173 -0
  47. package/components/ai-elements/toolbar.tsx +16 -0
  48. package/components/ai-elements/transcription.tsx +125 -0
  49. package/components/ai-elements/voice-selector.tsx +524 -0
  50. package/components/ai-elements/web-preview.tsx +281 -0
  51. package/components/index.ts +3 -0
  52. package/{accordion.tsx → components/ui/accordion.tsx} +66 -66
  53. package/{alert-dialog.tsx → components/ui/alert-dialog.tsx} +196 -196
  54. package/{alert.tsx → components/ui/alert.tsx} +66 -66
  55. package/{aspect-ratio.tsx → components/ui/aspect-ratio.tsx} +11 -11
  56. package/{avatar.tsx → components/ui/avatar.tsx} +53 -53
  57. package/{badge.tsx → components/ui/badge.tsx} +46 -46
  58. package/{breadcrumb.tsx → components/ui/breadcrumb.tsx} +109 -109
  59. package/{button-group.tsx → components/ui/button-group.tsx} +83 -83
  60. package/{button.tsx → components/ui/button.tsx} +60 -60
  61. package/{calendar.tsx → components/ui/calendar.tsx} +219 -219
  62. package/{card.tsx → components/ui/card.tsx} +92 -92
  63. package/{carousel.tsx → components/ui/carousel.tsx} +241 -241
  64. package/{chart.tsx → components/ui/chart.tsx} +374 -374
  65. package/{checkbox.tsx → components/ui/checkbox.tsx} +32 -32
  66. package/{collapsible.tsx → components/ui/collapsible.tsx} +33 -33
  67. package/{command.tsx → components/ui/command.tsx} +184 -184
  68. package/{context-menu.tsx → components/ui/context-menu.tsx} +252 -252
  69. package/{dialog.tsx → components/ui/dialog.tsx} +143 -143
  70. package/{direction.tsx → components/ui/direction.tsx} +22 -22
  71. package/{drawer.tsx → components/ui/drawer.tsx} +135 -135
  72. package/{dropdown-menu.tsx → components/ui/dropdown-menu.tsx} +257 -257
  73. package/{empty.tsx → components/ui/empty.tsx} +104 -104
  74. package/{field.tsx → components/ui/field.tsx} +248 -248
  75. package/{form.tsx → components/ui/form.tsx} +167 -167
  76. package/{hover-card.tsx → components/ui/hover-card.tsx} +44 -44
  77. package/components/ui/icon.tsx +55 -0
  78. package/components/ui/index.ts +59 -0
  79. package/{input-group.tsx → components/ui/input-group.tsx} +170 -170
  80. package/{input-otp.tsx → components/ui/input-otp.tsx} +77 -77
  81. package/{input.tsx → components/ui/input.tsx} +21 -21
  82. package/{item.tsx → components/ui/item.tsx} +193 -193
  83. package/{kbd.tsx → components/ui/kbd.tsx} +28 -28
  84. package/{label.tsx → components/ui/label.tsx} +24 -24
  85. package/{menubar.tsx → components/ui/menubar.tsx} +276 -276
  86. package/{native-select.tsx → components/ui/native-select.tsx} +62 -62
  87. package/{navigation-menu.tsx → components/ui/navigation-menu.tsx} +168 -168
  88. package/{pagination.tsx → components/ui/pagination.tsx} +127 -127
  89. package/{popover.tsx → components/ui/popover.tsx} +89 -89
  90. package/{progress.tsx → components/ui/progress.tsx} +31 -31
  91. package/{radio-group.tsx → components/ui/radio-group.tsx} +45 -45
  92. package/{resizable.tsx → components/ui/resizable.tsx} +53 -53
  93. package/{scroll-area.tsx → components/ui/scroll-area.tsx} +58 -58
  94. package/{select.tsx → components/ui/select.tsx} +187 -187
  95. package/{separator.tsx → components/ui/separator.tsx} +28 -28
  96. package/{sheet.tsx → components/ui/sheet.tsx} +139 -139
  97. package/{sidebar.tsx → components/ui/sidebar.tsx} +724 -724
  98. package/{skeleton.tsx → components/ui/skeleton.tsx} +13 -13
  99. package/{slider.tsx → components/ui/slider.tsx} +63 -63
  100. package/{sonner.tsx → components/ui/sonner.tsx} +40 -40
  101. package/{spinner.tsx → components/ui/spinner.tsx} +16 -16
  102. package/{switch.tsx → components/ui/switch.tsx} +35 -35
  103. package/{table.tsx → components/ui/table.tsx} +116 -116
  104. package/{tabs.tsx → components/ui/tabs.tsx} +66 -66
  105. package/{textarea.tsx → components/ui/textarea.tsx} +18 -18
  106. package/{toggle-group.tsx → components/ui/toggle-group.tsx} +83 -83
  107. package/{toggle.tsx → components/ui/toggle.tsx} +47 -47
  108. package/{tooltip.tsx → components/ui/tooltip.tsx} +61 -61
  109. package/hooks/index.ts +1 -1
  110. package/hooks/use-mobile.ts +19 -19
  111. package/index.ts +3 -59
  112. package/lib/index.ts +1 -1
  113. package/lib/utils.ts +6 -6
  114. package/package.json +79 -1
  115. package/styles.css +124 -124
  116. package/icon.tsx +0 -21
  117. package/tsconfig.json +0 -12
  118. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,274 @@
1
+ "use client";
2
+
3
+ import { Button } from "../ui/button";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "../ui/collapsible";
9
+ import { ScrollArea } from "../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,228 @@
1
+ "use client";
2
+
3
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "../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 {
17
+ createContext,
18
+ memo,
19
+ useCallback,
20
+ useContext,
21
+ useEffect,
22
+ useMemo,
23
+ useRef,
24
+ useState,
25
+ } from "react";
26
+ import { Streamdown } from "streamdown";
27
+
28
+ import { Shimmer } from "./shimmer";
29
+
30
+ interface ReasoningContextValue {
31
+ isStreaming: boolean;
32
+ isOpen: boolean;
33
+ setIsOpen: (open: boolean) => void;
34
+ duration: number | undefined;
35
+ }
36
+
37
+ const ReasoningContext = createContext<ReasoningContextValue | null>(null);
38
+
39
+ export const useReasoning = () => {
40
+ const context = useContext(ReasoningContext);
41
+ if (!context) {
42
+ throw new Error("Reasoning components must be used within Reasoning");
43
+ }
44
+ return context;
45
+ };
46
+
47
+ export type ReasoningProps = ComponentProps<typeof Collapsible> & {
48
+ isStreaming?: boolean;
49
+ open?: boolean;
50
+ defaultOpen?: boolean;
51
+ onOpenChange?: (open: boolean) => void;
52
+ duration?: number;
53
+ };
54
+
55
+ const AUTO_CLOSE_DELAY = 1000;
56
+ const MS_IN_S = 1000;
57
+
58
+ export const Reasoning = memo(
59
+ ({
60
+ className,
61
+ isStreaming = false,
62
+ open,
63
+ defaultOpen,
64
+ onOpenChange,
65
+ duration: durationProp,
66
+ children,
67
+ ...props
68
+ }: ReasoningProps) => {
69
+ const resolvedDefaultOpen = defaultOpen ?? isStreaming;
70
+ // Track if defaultOpen was explicitly set to false (to prevent auto-open)
71
+ const isExplicitlyClosed = defaultOpen === false;
72
+
73
+ const [isOpen, setIsOpen] = useControllableState<boolean>({
74
+ defaultProp: resolvedDefaultOpen,
75
+ onChange: onOpenChange,
76
+ prop: open,
77
+ });
78
+ const [duration, setDuration] = useControllableState<number | undefined>({
79
+ defaultProp: undefined,
80
+ prop: durationProp,
81
+ });
82
+
83
+ const hasEverStreamedRef = useRef(isStreaming);
84
+ const [hasAutoClosed, setHasAutoClosed] = useState(false);
85
+ const startTimeRef = useRef<number | null>(null);
86
+
87
+ // Track when streaming starts and compute duration
88
+ useEffect(() => {
89
+ if (isStreaming) {
90
+ hasEverStreamedRef.current = true;
91
+ if (startTimeRef.current === null) {
92
+ startTimeRef.current = Date.now();
93
+ }
94
+ } else if (startTimeRef.current !== null) {
95
+ setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
96
+ startTimeRef.current = null;
97
+ }
98
+ }, [isStreaming, setDuration]);
99
+
100
+ // Auto-open when streaming starts (unless explicitly closed)
101
+ useEffect(() => {
102
+ if (isStreaming && !isOpen && !isExplicitlyClosed) {
103
+ setIsOpen(true);
104
+ }
105
+ }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
106
+
107
+ // Auto-close when streaming ends (once only, and only if it ever streamed)
108
+ useEffect(() => {
109
+ if (
110
+ hasEverStreamedRef.current &&
111
+ !isStreaming &&
112
+ isOpen &&
113
+ !hasAutoClosed
114
+ ) {
115
+ const timer = setTimeout(() => {
116
+ setIsOpen(false);
117
+ setHasAutoClosed(true);
118
+ }, AUTO_CLOSE_DELAY);
119
+
120
+ return () => clearTimeout(timer);
121
+ }
122
+
123
+ return undefined;
124
+ }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
125
+
126
+ const handleOpenChange = useCallback(
127
+ (newOpen: boolean) => {
128
+ setIsOpen(newOpen);
129
+ },
130
+ [setIsOpen]
131
+ );
132
+
133
+ const contextValue = useMemo(
134
+ () => ({ duration, isOpen, isStreaming, setIsOpen }),
135
+ [duration, isOpen, isStreaming, setIsOpen]
136
+ );
137
+
138
+ return (
139
+ <ReasoningContext.Provider value={contextValue}>
140
+ <Collapsible
141
+ className={cn("not-prose mb-4", className)}
142
+ onOpenChange={handleOpenChange}
143
+ open={isOpen}
144
+ {...props}
145
+ >
146
+ {children}
147
+ </Collapsible>
148
+ </ReasoningContext.Provider>
149
+ );
150
+ }
151
+ );
152
+
153
+ export type ReasoningTriggerProps = ComponentProps<
154
+ typeof CollapsibleTrigger
155
+ > & {
156
+ getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
157
+ };
158
+
159
+ const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
160
+ if (isStreaming || duration === 0) {
161
+ return <Shimmer duration={1}>Thinking...</Shimmer>;
162
+ }
163
+ if (duration === undefined) {
164
+ return <p>Thought for a few seconds</p>;
165
+ }
166
+ return <p>Thought for {duration} seconds</p>;
167
+ };
168
+
169
+ export const ReasoningTrigger = memo(
170
+ ({
171
+ className,
172
+ children,
173
+ getThinkingMessage = defaultGetThinkingMessage,
174
+ ...props
175
+ }: ReasoningTriggerProps) => {
176
+ const { isStreaming, isOpen, duration } = useReasoning();
177
+
178
+ return (
179
+ <CollapsibleTrigger
180
+ className={cn(
181
+ "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
182
+ className
183
+ )}
184
+ {...props}
185
+ >
186
+ {children ?? (
187
+ <>
188
+ <BrainIcon className="size-4" />
189
+ {getThinkingMessage(isStreaming, duration)}
190
+ <ChevronDownIcon
191
+ className={cn(
192
+ "size-4 transition-transform",
193
+ isOpen ? "rotate-180" : "rotate-0"
194
+ )}
195
+ />
196
+ </>
197
+ )}
198
+ </CollapsibleTrigger>
199
+ );
200
+ }
201
+ );
202
+
203
+ export type ReasoningContentProps = ComponentProps<
204
+ typeof CollapsibleContent
205
+ > & {
206
+ children: string;
207
+ };
208
+
209
+ const streamdownPlugins = { cjk, code, math, mermaid };
210
+
211
+ export const ReasoningContent = memo(
212
+ ({ className, children, ...props }: ReasoningContentProps) => (
213
+ <CollapsibleContent
214
+ className={cn(
215
+ "mt-4 text-sm",
216
+ "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",
217
+ className
218
+ )}
219
+ {...props}
220
+ >
221
+ <Streamdown plugins={streamdownPlugins}>{children}</Streamdown>
222
+ </CollapsibleContent>
223
+ )
224
+ );
225
+
226
+ Reasoning.displayName = "Reasoning";
227
+ ReasoningTrigger.displayName = "ReasoningTrigger";
228
+ ReasoningContent.displayName = "ReasoningContent";
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "../ui/collapsible";
8
+ import {
9
+ Tabs,
10
+ TabsContent,
11
+ TabsList,
12
+ TabsTrigger,
13
+ } from "../ui/tabs";
14
+ import { cn } from "../../lib/utils";
15
+ import type { ToolUIPart } from "ai";
16
+ import { ChevronDownIcon, Code } from "lucide-react";
17
+ import type { ComponentProps } from "react";
18
+
19
+ import { getStatusBadge } from "./tool";
20
+
21
+ export type SandboxRootProps = ComponentProps<typeof Collapsible>;
22
+
23
+ export const Sandbox = ({ className, ...props }: SandboxRootProps) => (
24
+ <Collapsible
25
+ className={cn(
26
+ "not-prose group mb-4 w-full overflow-hidden rounded-md border",
27
+ className
28
+ )}
29
+ defaultOpen
30
+ {...props}
31
+ />
32
+ );
33
+
34
+ export interface SandboxHeaderProps {
35
+ title?: string;
36
+ state: ToolUIPart["state"];
37
+ className?: string;
38
+ }
39
+
40
+ export const SandboxHeader = ({
41
+ className,
42
+ title,
43
+ state,
44
+ ...props
45
+ }: SandboxHeaderProps) => (
46
+ <CollapsibleTrigger
47
+ className={cn(
48
+ "flex w-full items-center justify-between gap-4 p-3",
49
+ className
50
+ )}
51
+ {...props}
52
+ >
53
+ <div className="flex items-center gap-2">
54
+ <Code className="size-4 text-muted-foreground" />
55
+ <span className="font-medium text-sm">{title}</span>
56
+ {getStatusBadge(state)}
57
+ </div>
58
+ <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
59
+ </CollapsibleTrigger>
60
+ );
61
+
62
+ export type SandboxContentProps = ComponentProps<typeof CollapsibleContent>;
63
+
64
+ export const SandboxContent = ({
65
+ className,
66
+ ...props
67
+ }: SandboxContentProps) => (
68
+ <CollapsibleContent
69
+ className={cn(
70
+ "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",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ );
76
+
77
+ export type SandboxTabsProps = ComponentProps<typeof Tabs>;
78
+
79
+ export const SandboxTabs = ({ className, ...props }: SandboxTabsProps) => (
80
+ <Tabs className={cn("w-full gap-0", className)} {...props} />
81
+ );
82
+
83
+ export type SandboxTabsBarProps = ComponentProps<"div">;
84
+
85
+ export const SandboxTabsBar = ({
86
+ className,
87
+ ...props
88
+ }: SandboxTabsBarProps) => (
89
+ <div
90
+ className={cn(
91
+ "flex w-full items-center border-border border-t border-b",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ );
97
+
98
+ export type SandboxTabsListProps = ComponentProps<typeof TabsList>;
99
+
100
+ export const SandboxTabsList = ({
101
+ className,
102
+ ...props
103
+ }: SandboxTabsListProps) => (
104
+ <TabsList
105
+ className={cn("h-auto rounded-none border-0 bg-transparent p-0", className)}
106
+ {...props}
107
+ />
108
+ );
109
+
110
+ export type SandboxTabsTriggerProps = ComponentProps<typeof TabsTrigger>;
111
+
112
+ export const SandboxTabsTrigger = ({
113
+ className,
114
+ ...props
115
+ }: SandboxTabsTriggerProps) => (
116
+ <TabsTrigger
117
+ className={cn(
118
+ "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",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ );
124
+
125
+ export type SandboxTabContentProps = ComponentProps<typeof TabsContent>;
126
+
127
+ export const SandboxTabContent = ({
128
+ className,
129
+ ...props
130
+ }: SandboxTabContentProps) => (
131
+ <TabsContent className={cn("mt-0 text-sm", className)} {...props} />
132
+ );