@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.
- package/README.md +460 -380
- package/components/ai-elements/agent.tsx +141 -0
- package/components/ai-elements/artifact.tsx +148 -0
- package/components/ai-elements/attachments.tsx +426 -0
- package/components/ai-elements/audio-player.tsx +231 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +222 -0
- package/components/ai-elements/checkpoint.tsx +71 -0
- package/components/ai-elements/code-block.tsx +562 -0
- package/components/ai-elements/commit.tsx +458 -0
- package/components/ai-elements/confirmation.tsx +174 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +409 -0
- package/components/ai-elements/controls.tsx +18 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/edge.tsx +143 -0
- package/components/ai-elements/environment-variables.tsx +324 -0
- package/components/ai-elements/file-tree.tsx +304 -0
- package/components/ai-elements/image.tsx +24 -0
- package/components/ai-elements/index.ts +51 -0
- package/components/ai-elements/inline-citation.tsx +296 -0
- package/components/ai-elements/jsx-preview.tsx +310 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/mic-selector.tsx +375 -0
- package/components/ai-elements/model-selector.tsx +213 -0
- package/components/ai-elements/node.tsx +71 -0
- package/components/ai-elements/open-in-chat.tsx +370 -0
- package/components/ai-elements/package-info.tsx +239 -0
- package/components/ai-elements/panel.tsx +15 -0
- package/components/ai-elements/persona.tsx +306 -0
- package/components/ai-elements/plan.tsx +147 -0
- package/components/ai-elements/prompt-input.tsx +1463 -0
- package/components/ai-elements/queue.tsx +274 -0
- package/components/ai-elements/reasoning.tsx +228 -0
- package/components/ai-elements/sandbox.tsx +132 -0
- package/components/ai-elements/schema-display.tsx +471 -0
- package/components/ai-elements/shimmer.tsx +77 -0
- package/components/ai-elements/snippet.tsx +145 -0
- package/components/ai-elements/sources.tsx +77 -0
- package/components/ai-elements/speech-input.tsx +323 -0
- package/components/ai-elements/stack-trace.tsx +528 -0
- package/components/ai-elements/suggestion.tsx +57 -0
- package/components/ai-elements/task.tsx +87 -0
- package/components/ai-elements/terminal.tsx +273 -0
- package/components/ai-elements/test-results.tsx +496 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/ai-elements/toolbar.tsx +16 -0
- package/components/ai-elements/transcription.tsx +125 -0
- package/components/ai-elements/voice-selector.tsx +524 -0
- package/components/ai-elements/web-preview.tsx +281 -0
- package/components/index.ts +3 -0
- package/{accordion.tsx → components/ui/accordion.tsx} +66 -66
- package/{alert-dialog.tsx → components/ui/alert-dialog.tsx} +196 -196
- package/{alert.tsx → components/ui/alert.tsx} +66 -66
- package/{aspect-ratio.tsx → components/ui/aspect-ratio.tsx} +11 -11
- package/{avatar.tsx → components/ui/avatar.tsx} +53 -53
- package/{badge.tsx → components/ui/badge.tsx} +46 -46
- package/{breadcrumb.tsx → components/ui/breadcrumb.tsx} +109 -109
- package/{button-group.tsx → components/ui/button-group.tsx} +83 -83
- package/{button.tsx → components/ui/button.tsx} +60 -60
- package/{calendar.tsx → components/ui/calendar.tsx} +219 -219
- package/{card.tsx → components/ui/card.tsx} +92 -92
- package/{carousel.tsx → components/ui/carousel.tsx} +241 -241
- package/{chart.tsx → components/ui/chart.tsx} +374 -374
- package/{checkbox.tsx → components/ui/checkbox.tsx} +32 -32
- package/{collapsible.tsx → components/ui/collapsible.tsx} +33 -33
- package/{command.tsx → components/ui/command.tsx} +184 -184
- package/{context-menu.tsx → components/ui/context-menu.tsx} +252 -252
- package/{dialog.tsx → components/ui/dialog.tsx} +143 -143
- package/{direction.tsx → components/ui/direction.tsx} +22 -22
- package/{drawer.tsx → components/ui/drawer.tsx} +135 -135
- package/{dropdown-menu.tsx → components/ui/dropdown-menu.tsx} +257 -257
- package/{empty.tsx → components/ui/empty.tsx} +104 -104
- package/{field.tsx → components/ui/field.tsx} +248 -248
- package/{form.tsx → components/ui/form.tsx} +167 -167
- package/{hover-card.tsx → components/ui/hover-card.tsx} +44 -44
- package/components/ui/icon.tsx +55 -0
- package/components/ui/index.ts +59 -0
- package/{input-group.tsx → components/ui/input-group.tsx} +170 -170
- package/{input-otp.tsx → components/ui/input-otp.tsx} +77 -77
- package/{input.tsx → components/ui/input.tsx} +21 -21
- package/{item.tsx → components/ui/item.tsx} +193 -193
- package/{kbd.tsx → components/ui/kbd.tsx} +28 -28
- package/{label.tsx → components/ui/label.tsx} +24 -24
- package/{menubar.tsx → components/ui/menubar.tsx} +276 -276
- package/{native-select.tsx → components/ui/native-select.tsx} +62 -62
- package/{navigation-menu.tsx → components/ui/navigation-menu.tsx} +168 -168
- package/{pagination.tsx → components/ui/pagination.tsx} +127 -127
- package/{popover.tsx → components/ui/popover.tsx} +89 -89
- package/{progress.tsx → components/ui/progress.tsx} +31 -31
- package/{radio-group.tsx → components/ui/radio-group.tsx} +45 -45
- package/{resizable.tsx → components/ui/resizable.tsx} +53 -53
- package/{scroll-area.tsx → components/ui/scroll-area.tsx} +58 -58
- package/{select.tsx → components/ui/select.tsx} +187 -187
- package/{separator.tsx → components/ui/separator.tsx} +28 -28
- package/{sheet.tsx → components/ui/sheet.tsx} +139 -139
- package/{sidebar.tsx → components/ui/sidebar.tsx} +724 -724
- package/{skeleton.tsx → components/ui/skeleton.tsx} +13 -13
- package/{slider.tsx → components/ui/slider.tsx} +63 -63
- package/{sonner.tsx → components/ui/sonner.tsx} +40 -40
- package/{spinner.tsx → components/ui/spinner.tsx} +16 -16
- package/{switch.tsx → components/ui/switch.tsx} +35 -35
- package/{table.tsx → components/ui/table.tsx} +116 -116
- package/{tabs.tsx → components/ui/tabs.tsx} +66 -66
- package/{textarea.tsx → components/ui/textarea.tsx} +18 -18
- package/{toggle-group.tsx → components/ui/toggle-group.tsx} +83 -83
- package/{toggle.tsx → components/ui/toggle.tsx} +47 -47
- package/{tooltip.tsx → components/ui/tooltip.tsx} +61 -61
- package/hooks/index.ts +1 -1
- package/hooks/use-mobile.ts +19 -19
- package/index.ts +3 -59
- package/lib/index.ts +1 -1
- package/lib/utils.ts +6 -6
- package/package.json +79 -1
- package/styles.css +124 -124
- package/icon.tsx +0 -21
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "../ui/badge";
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "../ui/collapsible";
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
11
|
+
import type { ComponentProps, HTMLAttributes } from "react";
|
|
12
|
+
import { createContext, useContext, useMemo } from "react";
|
|
13
|
+
|
|
14
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
15
|
+
|
|
16
|
+
interface SchemaParameter {
|
|
17
|
+
name: string;
|
|
18
|
+
type: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
description?: string;
|
|
21
|
+
location?: "path" | "query" | "header";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SchemaProperty {
|
|
25
|
+
name: string;
|
|
26
|
+
type: string;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
description?: string;
|
|
29
|
+
properties?: SchemaProperty[];
|
|
30
|
+
items?: SchemaProperty;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SchemaDisplayContextType {
|
|
34
|
+
method: HttpMethod;
|
|
35
|
+
path: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
parameters?: SchemaParameter[];
|
|
38
|
+
requestBody?: SchemaProperty[];
|
|
39
|
+
responseBody?: SchemaProperty[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const SchemaDisplayContext = createContext<SchemaDisplayContextType>({
|
|
43
|
+
method: "GET",
|
|
44
|
+
path: "",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const methodStyles: Record<HttpMethod, string> = {
|
|
48
|
+
DELETE: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400",
|
|
49
|
+
GET: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
|
|
50
|
+
PATCH:
|
|
51
|
+
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400",
|
|
52
|
+
POST: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
|
|
53
|
+
PUT: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type SchemaDisplayHeaderProps = HTMLAttributes<HTMLDivElement>;
|
|
57
|
+
|
|
58
|
+
export const SchemaDisplayHeader = ({
|
|
59
|
+
className,
|
|
60
|
+
children,
|
|
61
|
+
...props
|
|
62
|
+
}: SchemaDisplayHeaderProps) => (
|
|
63
|
+
<div
|
|
64
|
+
className={cn("flex items-center gap-3 border-b px-4 py-3", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export type SchemaDisplayMethodProps = ComponentProps<typeof Badge>;
|
|
72
|
+
|
|
73
|
+
export const SchemaDisplayMethod = ({
|
|
74
|
+
className,
|
|
75
|
+
children,
|
|
76
|
+
...props
|
|
77
|
+
}: SchemaDisplayMethodProps) => {
|
|
78
|
+
const { method } = useContext(SchemaDisplayContext);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Badge
|
|
82
|
+
className={cn("font-mono text-xs", methodStyles[method], className)}
|
|
83
|
+
variant="secondary"
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{children ?? method}
|
|
87
|
+
</Badge>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type SchemaDisplayPathProps = HTMLAttributes<HTMLSpanElement>;
|
|
92
|
+
|
|
93
|
+
export const SchemaDisplayPath = ({
|
|
94
|
+
className,
|
|
95
|
+
children,
|
|
96
|
+
...props
|
|
97
|
+
}: SchemaDisplayPathProps) => {
|
|
98
|
+
const { path } = useContext(SchemaDisplayContext);
|
|
99
|
+
|
|
100
|
+
// Highlight path parameters
|
|
101
|
+
const highlightedPath = path.replaceAll(
|
|
102
|
+
/\{([^}]+)\}/g,
|
|
103
|
+
'<span class="text-blue-600 dark:text-blue-400">{$1}</span>'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<span
|
|
108
|
+
className={cn("font-mono text-sm", className)}
|
|
109
|
+
// oxlint-disable-next-line eslint-plugin-react(no-danger)
|
|
110
|
+
dangerouslySetInnerHTML={{ __html: children ?? highlightedPath }}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type SchemaDisplayDescriptionProps =
|
|
117
|
+
HTMLAttributes<HTMLParagraphElement>;
|
|
118
|
+
|
|
119
|
+
export const SchemaDisplayDescription = ({
|
|
120
|
+
className,
|
|
121
|
+
children,
|
|
122
|
+
...props
|
|
123
|
+
}: SchemaDisplayDescriptionProps) => {
|
|
124
|
+
const { description } = useContext(SchemaDisplayContext);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<p
|
|
128
|
+
className={cn(
|
|
129
|
+
"border-b px-4 py-3 text-muted-foreground text-sm",
|
|
130
|
+
className
|
|
131
|
+
)}
|
|
132
|
+
{...props}
|
|
133
|
+
>
|
|
134
|
+
{children ?? description}
|
|
135
|
+
</p>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export type SchemaDisplayContentProps = HTMLAttributes<HTMLDivElement>;
|
|
140
|
+
|
|
141
|
+
export const SchemaDisplayContent = ({
|
|
142
|
+
className,
|
|
143
|
+
children,
|
|
144
|
+
...props
|
|
145
|
+
}: SchemaDisplayContentProps) => (
|
|
146
|
+
<div className={cn("divide-y", className)} {...props}>
|
|
147
|
+
{children}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
export type SchemaDisplayParameterProps = HTMLAttributes<HTMLDivElement> &
|
|
152
|
+
SchemaParameter;
|
|
153
|
+
|
|
154
|
+
export const SchemaDisplayParameter = ({
|
|
155
|
+
name,
|
|
156
|
+
type,
|
|
157
|
+
required,
|
|
158
|
+
description,
|
|
159
|
+
location,
|
|
160
|
+
className,
|
|
161
|
+
...props
|
|
162
|
+
}: SchemaDisplayParameterProps) => (
|
|
163
|
+
<div className={cn("px-4 py-3 pl-10", className)} {...props}>
|
|
164
|
+
<div className="flex items-center gap-2">
|
|
165
|
+
<span className="font-mono text-sm">{name}</span>
|
|
166
|
+
<Badge className="text-xs" variant="outline">
|
|
167
|
+
{type}
|
|
168
|
+
</Badge>
|
|
169
|
+
{location && (
|
|
170
|
+
<Badge className="text-xs" variant="secondary">
|
|
171
|
+
{location}
|
|
172
|
+
</Badge>
|
|
173
|
+
)}
|
|
174
|
+
{required && (
|
|
175
|
+
<Badge
|
|
176
|
+
className="bg-red-100 text-red-700 text-xs dark:bg-red-900/30 dark:text-red-400"
|
|
177
|
+
variant="secondary"
|
|
178
|
+
>
|
|
179
|
+
required
|
|
180
|
+
</Badge>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
{description && (
|
|
184
|
+
<p className="mt-1 text-muted-foreground text-sm">{description}</p>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
export type SchemaDisplayParametersProps = ComponentProps<typeof Collapsible>;
|
|
190
|
+
|
|
191
|
+
export const SchemaDisplayParameters = ({
|
|
192
|
+
className,
|
|
193
|
+
children,
|
|
194
|
+
...props
|
|
195
|
+
}: SchemaDisplayParametersProps) => {
|
|
196
|
+
const { parameters } = useContext(SchemaDisplayContext);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Collapsible className={cn(className)} defaultOpen {...props}>
|
|
200
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-muted/50">
|
|
201
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
|
|
202
|
+
<span className="font-medium text-sm">Parameters</span>
|
|
203
|
+
<Badge className="ml-auto text-xs" variant="secondary">
|
|
204
|
+
{parameters?.length}
|
|
205
|
+
</Badge>
|
|
206
|
+
</CollapsibleTrigger>
|
|
207
|
+
<CollapsibleContent>
|
|
208
|
+
<div className="divide-y border-t">
|
|
209
|
+
{children ??
|
|
210
|
+
parameters?.map((param) => (
|
|
211
|
+
<SchemaDisplayParameter key={param.name} {...param} />
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
</CollapsibleContent>
|
|
215
|
+
</Collapsible>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export type SchemaDisplayPropertyProps = HTMLAttributes<HTMLDivElement> &
|
|
220
|
+
SchemaProperty & {
|
|
221
|
+
depth?: number;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const SchemaDisplayProperty = ({
|
|
225
|
+
name,
|
|
226
|
+
type,
|
|
227
|
+
required,
|
|
228
|
+
description,
|
|
229
|
+
properties,
|
|
230
|
+
items,
|
|
231
|
+
depth = 0,
|
|
232
|
+
className,
|
|
233
|
+
...props
|
|
234
|
+
}: SchemaDisplayPropertyProps) => {
|
|
235
|
+
const hasChildren = properties || items;
|
|
236
|
+
const paddingLeft = 40 + depth * 16;
|
|
237
|
+
|
|
238
|
+
if (hasChildren) {
|
|
239
|
+
return (
|
|
240
|
+
<Collapsible defaultOpen={depth < 2}>
|
|
241
|
+
<CollapsibleTrigger
|
|
242
|
+
className={cn(
|
|
243
|
+
"group flex w-full items-center gap-2 py-3 text-left transition-colors hover:bg-muted/50",
|
|
244
|
+
className
|
|
245
|
+
)}
|
|
246
|
+
style={{ paddingLeft }}
|
|
247
|
+
>
|
|
248
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
|
|
249
|
+
<span className="font-mono text-sm">{name}</span>
|
|
250
|
+
<Badge className="text-xs" variant="outline">
|
|
251
|
+
{type}
|
|
252
|
+
</Badge>
|
|
253
|
+
{required && (
|
|
254
|
+
<Badge
|
|
255
|
+
className="bg-red-100 text-red-700 text-xs dark:bg-red-900/30 dark:text-red-400"
|
|
256
|
+
variant="secondary"
|
|
257
|
+
>
|
|
258
|
+
required
|
|
259
|
+
</Badge>
|
|
260
|
+
)}
|
|
261
|
+
</CollapsibleTrigger>
|
|
262
|
+
{description && (
|
|
263
|
+
<p
|
|
264
|
+
className="pb-2 text-muted-foreground text-sm"
|
|
265
|
+
style={{ paddingLeft: paddingLeft + 24 }}
|
|
266
|
+
>
|
|
267
|
+
{description}
|
|
268
|
+
</p>
|
|
269
|
+
)}
|
|
270
|
+
<CollapsibleContent>
|
|
271
|
+
<div className="divide-y border-t">
|
|
272
|
+
{properties?.map((prop) => (
|
|
273
|
+
<SchemaDisplayProperty
|
|
274
|
+
key={prop.name}
|
|
275
|
+
{...prop}
|
|
276
|
+
depth={depth + 1}
|
|
277
|
+
/>
|
|
278
|
+
))}
|
|
279
|
+
{items && (
|
|
280
|
+
<SchemaDisplayProperty
|
|
281
|
+
{...items}
|
|
282
|
+
depth={depth + 1}
|
|
283
|
+
name={`${name}[]`}
|
|
284
|
+
/>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
</CollapsibleContent>
|
|
288
|
+
</Collapsible>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div
|
|
294
|
+
className={cn("py-3 pr-4", className)}
|
|
295
|
+
style={{ paddingLeft }}
|
|
296
|
+
{...props}
|
|
297
|
+
>
|
|
298
|
+
<div className="flex items-center gap-2">
|
|
299
|
+
{/* Spacer for alignment */}
|
|
300
|
+
<span className="size-4" />
|
|
301
|
+
<span className="font-mono text-sm">{name}</span>
|
|
302
|
+
<Badge className="text-xs" variant="outline">
|
|
303
|
+
{type}
|
|
304
|
+
</Badge>
|
|
305
|
+
{required && (
|
|
306
|
+
<Badge
|
|
307
|
+
className="bg-red-100 text-red-700 text-xs dark:bg-red-900/30 dark:text-red-400"
|
|
308
|
+
variant="secondary"
|
|
309
|
+
>
|
|
310
|
+
required
|
|
311
|
+
</Badge>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
{description && (
|
|
315
|
+
<p className="mt-1 pl-6 text-muted-foreground text-sm">{description}</p>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export type SchemaDisplayRequestProps = ComponentProps<typeof Collapsible>;
|
|
322
|
+
|
|
323
|
+
export const SchemaDisplayRequest = ({
|
|
324
|
+
className,
|
|
325
|
+
children,
|
|
326
|
+
...props
|
|
327
|
+
}: SchemaDisplayRequestProps) => {
|
|
328
|
+
const { requestBody } = useContext(SchemaDisplayContext);
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<Collapsible className={cn(className)} defaultOpen {...props}>
|
|
332
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-muted/50">
|
|
333
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
|
|
334
|
+
<span className="font-medium text-sm">Request Body</span>
|
|
335
|
+
</CollapsibleTrigger>
|
|
336
|
+
<CollapsibleContent>
|
|
337
|
+
<div className="border-t">
|
|
338
|
+
{children ??
|
|
339
|
+
requestBody?.map((prop) => (
|
|
340
|
+
<SchemaDisplayProperty key={prop.name} {...prop} depth={0} />
|
|
341
|
+
))}
|
|
342
|
+
</div>
|
|
343
|
+
</CollapsibleContent>
|
|
344
|
+
</Collapsible>
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export type SchemaDisplayResponseProps = ComponentProps<typeof Collapsible>;
|
|
349
|
+
|
|
350
|
+
export const SchemaDisplayResponse = ({
|
|
351
|
+
className,
|
|
352
|
+
children,
|
|
353
|
+
...props
|
|
354
|
+
}: SchemaDisplayResponseProps) => {
|
|
355
|
+
const { responseBody } = useContext(SchemaDisplayContext);
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<Collapsible className={cn(className)} defaultOpen {...props}>
|
|
359
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-muted/50">
|
|
360
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
|
|
361
|
+
<span className="font-medium text-sm">Response</span>
|
|
362
|
+
</CollapsibleTrigger>
|
|
363
|
+
<CollapsibleContent>
|
|
364
|
+
<div className="border-t">
|
|
365
|
+
{children ??
|
|
366
|
+
responseBody?.map((prop) => (
|
|
367
|
+
<SchemaDisplayProperty key={prop.name} {...prop} depth={0} />
|
|
368
|
+
))}
|
|
369
|
+
</div>
|
|
370
|
+
</CollapsibleContent>
|
|
371
|
+
</Collapsible>
|
|
372
|
+
);
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
export type SchemaDisplayProps = HTMLAttributes<HTMLDivElement> & {
|
|
376
|
+
method: HttpMethod;
|
|
377
|
+
path: string;
|
|
378
|
+
description?: string;
|
|
379
|
+
parameters?: SchemaParameter[];
|
|
380
|
+
requestBody?: SchemaProperty[];
|
|
381
|
+
responseBody?: SchemaProperty[];
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const SchemaDisplay = ({
|
|
385
|
+
method,
|
|
386
|
+
path,
|
|
387
|
+
description,
|
|
388
|
+
parameters,
|
|
389
|
+
requestBody,
|
|
390
|
+
responseBody,
|
|
391
|
+
className,
|
|
392
|
+
children,
|
|
393
|
+
...props
|
|
394
|
+
}: SchemaDisplayProps) => {
|
|
395
|
+
const contextValue = useMemo(
|
|
396
|
+
() => ({
|
|
397
|
+
description,
|
|
398
|
+
method,
|
|
399
|
+
parameters,
|
|
400
|
+
path,
|
|
401
|
+
requestBody,
|
|
402
|
+
responseBody,
|
|
403
|
+
}),
|
|
404
|
+
[description, method, parameters, path, requestBody, responseBody]
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<SchemaDisplayContext.Provider value={contextValue}>
|
|
409
|
+
<div
|
|
410
|
+
className={cn(
|
|
411
|
+
"overflow-hidden rounded-lg border bg-background",
|
|
412
|
+
className
|
|
413
|
+
)}
|
|
414
|
+
{...props}
|
|
415
|
+
>
|
|
416
|
+
{children ?? (
|
|
417
|
+
<>
|
|
418
|
+
<SchemaDisplayHeader>
|
|
419
|
+
<div className="flex items-center gap-3">
|
|
420
|
+
<SchemaDisplayMethod />
|
|
421
|
+
<SchemaDisplayPath />
|
|
422
|
+
</div>
|
|
423
|
+
</SchemaDisplayHeader>
|
|
424
|
+
{description && <SchemaDisplayDescription />}
|
|
425
|
+
<SchemaDisplayContent>
|
|
426
|
+
{parameters && parameters.length > 0 && (
|
|
427
|
+
<SchemaDisplayParameters />
|
|
428
|
+
)}
|
|
429
|
+
{requestBody && requestBody.length > 0 && (
|
|
430
|
+
<SchemaDisplayRequest />
|
|
431
|
+
)}
|
|
432
|
+
{responseBody && responseBody.length > 0 && (
|
|
433
|
+
<SchemaDisplayResponse />
|
|
434
|
+
)}
|
|
435
|
+
</SchemaDisplayContent>
|
|
436
|
+
</>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
439
|
+
</SchemaDisplayContext.Provider>
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
export type SchemaDisplayBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
444
|
+
|
|
445
|
+
export const SchemaDisplayBody = ({
|
|
446
|
+
className,
|
|
447
|
+
children,
|
|
448
|
+
...props
|
|
449
|
+
}: SchemaDisplayBodyProps) => (
|
|
450
|
+
<div className={cn("divide-y", className)} {...props}>
|
|
451
|
+
{children}
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
export type SchemaDisplayExampleProps = HTMLAttributes<HTMLPreElement>;
|
|
456
|
+
|
|
457
|
+
export const SchemaDisplayExample = ({
|
|
458
|
+
className,
|
|
459
|
+
children,
|
|
460
|
+
...props
|
|
461
|
+
}: SchemaDisplayExampleProps) => (
|
|
462
|
+
<pre
|
|
463
|
+
className={cn(
|
|
464
|
+
"mx-4 mb-4 overflow-auto rounded-md bg-muted p-4 font-mono text-sm",
|
|
465
|
+
className
|
|
466
|
+
)}
|
|
467
|
+
{...props}
|
|
468
|
+
>
|
|
469
|
+
{children}
|
|
470
|
+
</pre>
|
|
471
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import type { MotionProps } from "motion/react";
|
|
5
|
+
import { motion } from "motion/react";
|
|
6
|
+
import type { CSSProperties, ElementType, JSX } from "react";
|
|
7
|
+
import { memo, useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
type MotionHTMLProps = MotionProps & Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
// Cache motion components at module level to avoid creating during render
|
|
12
|
+
const motionComponentCache = new Map<
|
|
13
|
+
keyof JSX.IntrinsicElements,
|
|
14
|
+
React.ComponentType<MotionHTMLProps>
|
|
15
|
+
>();
|
|
16
|
+
|
|
17
|
+
const getMotionComponent = (element: keyof JSX.IntrinsicElements) => {
|
|
18
|
+
let component = motionComponentCache.get(element);
|
|
19
|
+
if (!component) {
|
|
20
|
+
component = motion.create(element);
|
|
21
|
+
motionComponentCache.set(element, component);
|
|
22
|
+
}
|
|
23
|
+
return component;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface TextShimmerProps {
|
|
27
|
+
children: string;
|
|
28
|
+
as?: ElementType;
|
|
29
|
+
className?: string;
|
|
30
|
+
duration?: number;
|
|
31
|
+
spread?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ShimmerComponent = ({
|
|
35
|
+
children,
|
|
36
|
+
as: Component = "p",
|
|
37
|
+
className,
|
|
38
|
+
duration = 2,
|
|
39
|
+
spread = 2,
|
|
40
|
+
}: TextShimmerProps) => {
|
|
41
|
+
const MotionComponent = getMotionComponent(
|
|
42
|
+
Component as keyof JSX.IntrinsicElements
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const dynamicSpread = useMemo(
|
|
46
|
+
() => (children?.length ?? 0) * spread,
|
|
47
|
+
[children, spread]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<MotionComponent
|
|
52
|
+
animate={{ backgroundPosition: "0% center" }}
|
|
53
|
+
className={cn(
|
|
54
|
+
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
|
55
|
+
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
initial={{ backgroundPosition: "100% center" }}
|
|
59
|
+
style={
|
|
60
|
+
{
|
|
61
|
+
"--spread": `${dynamicSpread}px`,
|
|
62
|
+
backgroundImage:
|
|
63
|
+
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
|
|
64
|
+
} as CSSProperties
|
|
65
|
+
}
|
|
66
|
+
transition={{
|
|
67
|
+
duration,
|
|
68
|
+
ease: "linear",
|
|
69
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</MotionComponent>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Shimmer = memo(ShimmerComponent);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
InputGroup,
|
|
5
|
+
InputGroupAddon,
|
|
6
|
+
InputGroupButton,
|
|
7
|
+
InputGroupInput,
|
|
8
|
+
InputGroupText,
|
|
9
|
+
} from "../ui/input-group";
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
12
|
+
import type { ComponentProps } from "react";
|
|
13
|
+
import {
|
|
14
|
+
createContext,
|
|
15
|
+
useCallback,
|
|
16
|
+
useContext,
|
|
17
|
+
useEffect,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
} from "react";
|
|
22
|
+
|
|
23
|
+
interface SnippetContextType {
|
|
24
|
+
code: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SnippetContext = createContext<SnippetContextType>({
|
|
28
|
+
code: "",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type SnippetProps = ComponentProps<typeof InputGroup> & {
|
|
32
|
+
code: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Snippet = ({
|
|
36
|
+
code,
|
|
37
|
+
className,
|
|
38
|
+
children,
|
|
39
|
+
...props
|
|
40
|
+
}: SnippetProps) => {
|
|
41
|
+
const contextValue = useMemo(() => ({ code }), [code]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<SnippetContext.Provider value={contextValue}>
|
|
45
|
+
<InputGroup className={cn("font-mono", className)} {...props}>
|
|
46
|
+
{children}
|
|
47
|
+
</InputGroup>
|
|
48
|
+
</SnippetContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type SnippetAddonProps = ComponentProps<typeof InputGroupAddon>;
|
|
53
|
+
|
|
54
|
+
export const SnippetAddon = (props: SnippetAddonProps) => (
|
|
55
|
+
<InputGroupAddon {...props} />
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export type SnippetTextProps = ComponentProps<typeof InputGroupText>;
|
|
59
|
+
|
|
60
|
+
export const SnippetText = ({ className, ...props }: SnippetTextProps) => (
|
|
61
|
+
<InputGroupText
|
|
62
|
+
className={cn("pl-2 font-normal text-muted-foreground", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export type SnippetInputProps = Omit<
|
|
68
|
+
ComponentProps<typeof InputGroupInput>,
|
|
69
|
+
"readOnly" | "value"
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
export const SnippetInput = ({ className, ...props }: SnippetInputProps) => {
|
|
73
|
+
const { code } = useContext(SnippetContext);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<InputGroupInput
|
|
77
|
+
className={cn("text-foreground", className)}
|
|
78
|
+
readOnly
|
|
79
|
+
value={code}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type SnippetCopyButtonProps = ComponentProps<typeof InputGroupButton> & {
|
|
86
|
+
onCopy?: () => void;
|
|
87
|
+
onError?: (error: Error) => void;
|
|
88
|
+
timeout?: number;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const SnippetCopyButton = ({
|
|
92
|
+
onCopy,
|
|
93
|
+
onError,
|
|
94
|
+
timeout = 2000,
|
|
95
|
+
children,
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: SnippetCopyButtonProps) => {
|
|
99
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
100
|
+
const timeoutRef = useRef<number>(0);
|
|
101
|
+
const { code } = useContext(SnippetContext);
|
|
102
|
+
|
|
103
|
+
const copyToClipboard = useCallback(async () => {
|
|
104
|
+
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
|
|
105
|
+
onError?.(new Error("Clipboard API not available"));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (!isCopied) {
|
|
111
|
+
await navigator.clipboard.writeText(code);
|
|
112
|
+
setIsCopied(true);
|
|
113
|
+
onCopy?.();
|
|
114
|
+
timeoutRef.current = window.setTimeout(
|
|
115
|
+
() => setIsCopied(false),
|
|
116
|
+
timeout
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
onError?.(error as Error);
|
|
121
|
+
}
|
|
122
|
+
}, [code, onCopy, onError, timeout, isCopied]);
|
|
123
|
+
|
|
124
|
+
useEffect(
|
|
125
|
+
() => () => {
|
|
126
|
+
window.clearTimeout(timeoutRef.current);
|
|
127
|
+
},
|
|
128
|
+
[]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const Icon = isCopied ? CheckIcon : CopyIcon;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<InputGroupButton
|
|
135
|
+
aria-label="Copy"
|
|
136
|
+
className={className}
|
|
137
|
+
onClick={copyToClipboard}
|
|
138
|
+
size="icon-sm"
|
|
139
|
+
title="Copy"
|
|
140
|
+
{...props}
|
|
141
|
+
>
|
|
142
|
+
{children ?? <Icon className="size-3.5" size={14} />}
|
|
143
|
+
</InputGroupButton>
|
|
144
|
+
);
|
|
145
|
+
};
|