@vercel/agent-eval-playground 0.0.1

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 (49) hide show
  1. package/README.md +50 -0
  2. package/app/compare/page.tsx +40 -0
  3. package/app/evals/[name]/page.tsx +22 -0
  4. package/app/evals/page.tsx +18 -0
  5. package/app/experiments/[name]/[timestamp]/page.tsx +23 -0
  6. package/app/experiments/page.tsx +28 -0
  7. package/app/globals.css +126 -0
  8. package/app/layout.tsx +102 -0
  9. package/app/page.tsx +179 -0
  10. package/app/transcript/[experiment]/[timestamp]/[evalName]/[run]/page.tsx +43 -0
  11. package/bin.mjs +86 -0
  12. package/components/ComparePage.tsx +312 -0
  13. package/components/EvalDetail.tsx +114 -0
  14. package/components/EvalsPage.tsx +80 -0
  15. package/components/ExperimentDetail.tsx +162 -0
  16. package/components/ExperimentList.tsx +103 -0
  17. package/components/O11ySummary.tsx +114 -0
  18. package/components/RunResultCard.tsx +72 -0
  19. package/components/ShowMore.tsx +60 -0
  20. package/components/TranscriptPage.tsx +46 -0
  21. package/components/TranscriptViewer.tsx +201 -0
  22. package/components/ui/alert-dialog.tsx +184 -0
  23. package/components/ui/badge.tsx +45 -0
  24. package/components/ui/button.tsx +60 -0
  25. package/components/ui/card.tsx +94 -0
  26. package/components/ui/collapsible.tsx +34 -0
  27. package/components/ui/combobox.tsx +297 -0
  28. package/components/ui/dropdown-menu.tsx +269 -0
  29. package/components/ui/field.tsx +227 -0
  30. package/components/ui/input-group.tsx +147 -0
  31. package/components/ui/input.tsx +19 -0
  32. package/components/ui/label.tsx +24 -0
  33. package/components/ui/progress.tsx +31 -0
  34. package/components/ui/scroll-area.tsx +58 -0
  35. package/components/ui/select.tsx +191 -0
  36. package/components/ui/separator.tsx +28 -0
  37. package/components/ui/table.tsx +116 -0
  38. package/components/ui/tabs.tsx +91 -0
  39. package/components/ui/textarea.tsx +18 -0
  40. package/components/ui/tooltip.tsx +57 -0
  41. package/components.json +25 -0
  42. package/lib/data.ts +297 -0
  43. package/lib/types.ts +113 -0
  44. package/lib/utils.ts +6 -0
  45. package/next.config.ts +5 -0
  46. package/package.json +51 -0
  47. package/postcss.config.mjs +7 -0
  48. package/public/vercel.svg +1 -0
  49. package/tsconfig.json +42 -0
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Card, CardContent } from "@/components/ui/card";
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger,
10
+ } from "@/components/ui/collapsible";
11
+ import { ScrollArea } from "@/components/ui/scroll-area";
12
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
13
+ import { Separator } from "@/components/ui/separator";
14
+ import { ChevronRight, Terminal, FileText, Brain, AlertCircle, MessageSquare, Wrench } from "lucide-react";
15
+ import type { TranscriptEvent, Transcript } from "@/lib/types";
16
+ import { O11ySummary } from "./O11ySummary";
17
+ import { ShowMore } from "./ShowMore";
18
+
19
+ interface TranscriptViewerProps {
20
+ transcript: Transcript;
21
+ }
22
+
23
+ const EVENT_CONFIG: Record<
24
+ TranscriptEvent["type"],
25
+ { label: string; variant: "default" | "secondary" | "destructive" | "outline"; icon: React.ElementType }
26
+ > = {
27
+ message: { label: "Message", variant: "default", icon: MessageSquare },
28
+ tool_call: { label: "Tool Call", variant: "secondary", icon: Wrench },
29
+ tool_result: { label: "Tool Result", variant: "outline", icon: Terminal },
30
+ thinking: { label: "Thinking", variant: "outline", icon: Brain },
31
+ error: { label: "Error", variant: "destructive", icon: AlertCircle },
32
+ };
33
+
34
+ function TranscriptEventCard({ event, index }: { event: TranscriptEvent; index: number }) {
35
+ const [isOpen, setIsOpen] = useState(false);
36
+ const config = EVENT_CONFIG[event.type];
37
+ const Icon = config.icon;
38
+
39
+ const hasExpandableContent =
40
+ event.tool?.args ||
41
+ event.tool?.result ||
42
+ (event.content && event.content.length > 200);
43
+
44
+ const preview = event.content
45
+ ? event.content.slice(0, 200) + (event.content.length > 200 ? "..." : "")
46
+ : event.tool
47
+ ? `${event.tool.originalName}${event.tool.success === false ? " (failed)" : ""}`
48
+ : null;
49
+
50
+ return (
51
+ <Collapsible open={isOpen} onOpenChange={setIsOpen}>
52
+ <Card size="sm" className="border-l-4 overflow-hidden py-0! gap-0!" style={{ borderLeftColor: getEventColor(event.type) }}>
53
+ <CollapsibleTrigger className="w-full text-left cursor-pointer transition-colors hover:bg-muted rounded-t-lg" disabled={!hasExpandableContent}>
54
+ <div className="flex items-center gap-2 min-w-0 px-4 py-3">
55
+ <span className="text-xs text-muted-foreground w-5 text-right font-mono shrink-0">
56
+ {index + 1}
57
+ </span>
58
+ <Icon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
59
+ <Badge variant={config.variant} className="text-xs shrink-0 max-w-32 truncate">
60
+ {event.type === "tool_call" && event.tool
61
+ ? event.tool.originalName
62
+ : config.label}
63
+ </Badge>
64
+ {event.role && (
65
+ <Badge variant="outline" className="text-xs shrink-0">
66
+ {event.role}
67
+ </Badge>
68
+ )}
69
+ <p className="flex-1 min-w-0 text-xs text-muted-foreground truncate">
70
+ {preview}
71
+ </p>
72
+ {event.tool?.durationMs !== undefined && (
73
+ <span className="text-xs text-muted-foreground shrink-0">
74
+ {event.tool.durationMs}ms
75
+ </span>
76
+ )}
77
+ {hasExpandableContent && (
78
+ <ChevronRight
79
+ className={`h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform ${
80
+ isOpen ? "rotate-90" : ""
81
+ }`}
82
+ />
83
+ )}
84
+ </div>
85
+ </CollapsibleTrigger>
86
+ <CollapsibleContent>
87
+ <div className="px-4 pb-3 space-y-2">
88
+ <Separator />
89
+ {event.content && (
90
+ <pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-96 overflow-y-auto">
91
+ {event.content}
92
+ </pre>
93
+ )}
94
+ {event.tool?.args && (
95
+ <div>
96
+ <div className="text-xs text-muted-foreground mb-1">Arguments:</div>
97
+ <pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">
98
+ {JSON.stringify(event.tool.args, null, 2)}
99
+ </pre>
100
+ </div>
101
+ )}
102
+ {event.tool?.result !== undefined && (
103
+ <div>
104
+ <div className="text-xs text-muted-foreground mb-1">Result:</div>
105
+ <pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">
106
+ {typeof event.tool.result === "string"
107
+ ? event.tool.result
108
+ : JSON.stringify(event.tool.result, null, 2)}
109
+ </pre>
110
+ </div>
111
+ )}
112
+ </div>
113
+ </CollapsibleContent>
114
+ </Card>
115
+ </Collapsible>
116
+ );
117
+ }
118
+
119
+ function getEventColor(type: TranscriptEvent["type"]): string {
120
+ switch (type) {
121
+ case "message":
122
+ return "hsl(217, 91%, 60%)"; // blue
123
+ case "tool_call":
124
+ return "hsl(271, 91%, 65%)"; // purple
125
+ case "tool_result":
126
+ return "hsl(215, 14%, 50%)"; // gray
127
+ case "thinking":
128
+ return "hsl(48, 96%, 53%)"; // yellow
129
+ case "error":
130
+ return "hsl(0, 84%, 60%)"; // red
131
+ default:
132
+ return "hsl(215, 14%, 50%)";
133
+ }
134
+ }
135
+
136
+ export function TranscriptViewer({ transcript }: TranscriptViewerProps) {
137
+ return (
138
+ <Tabs defaultValue="timeline" className="w-full">
139
+ <TabsList>
140
+ <TabsTrigger value="timeline">Timeline</TabsTrigger>
141
+ <TabsTrigger value="raw">Raw JSON</TabsTrigger>
142
+ </TabsList>
143
+
144
+ <TabsContent value="timeline" className="mt-4">
145
+ <div className="space-y-6">
146
+ {/* Summary */}
147
+ <div className="flex flex-wrap items-center gap-x-5 gap-y-1 text-sm">
148
+ <div>
149
+ <span className="text-muted-foreground">Agent </span>
150
+ <span className="font-medium">{transcript.agent}</span>
151
+ </div>
152
+ {transcript.model && (
153
+ <div>
154
+ <span className="text-muted-foreground">Model </span>
155
+ <span className="font-medium">{transcript.model}</span>
156
+ </div>
157
+ )}
158
+ <div>
159
+ <span className="text-muted-foreground">Events </span>
160
+ <span className="font-medium">{transcript.events.length}</span>
161
+ </div>
162
+ <div>
163
+ <span className="text-muted-foreground">Status </span>
164
+ <Badge
165
+ variant={transcript.parseSuccess ? "default" : "destructive"}
166
+ className="text-xs"
167
+ >
168
+ {transcript.parseSuccess ? "Success" : "Partial"}
169
+ </Badge>
170
+ </div>
171
+ </div>
172
+
173
+ <O11ySummary summary={transcript.summary} />
174
+
175
+ {/* Timeline */}
176
+ {transcript.events.length === 0 ? (
177
+ <Card>
178
+ <CardContent className="py-8 text-center text-muted-foreground">
179
+ No transcript events found.
180
+ </CardContent>
181
+ </Card>
182
+ ) : (
183
+ <ShowMore limit={50} className="space-y-2">
184
+ {transcript.events.map((event, i) => (
185
+ <TranscriptEventCard key={i} event={event} index={i} />
186
+ ))}
187
+ </ShowMore>
188
+ )}
189
+ </div>
190
+ </TabsContent>
191
+
192
+ <TabsContent value="raw" className="mt-4">
193
+ <ScrollArea className="h-[calc(100vh-280px)]">
194
+ <pre className="text-xs font-mono bg-muted rounded p-4 whitespace-pre-wrap break-all">
195
+ {JSON.stringify(transcript, null, 2)}
196
+ </pre>
197
+ </ScrollArea>
198
+ </TabsContent>
199
+ </Tabs>
200
+ );
201
+ }
@@ -0,0 +1,184 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Button } from "@/components/ui/button"
8
+
9
+ function AlertDialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
12
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
13
+ }
14
+
15
+ function AlertDialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
18
+ return (
19
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
20
+ )
21
+ }
22
+
23
+ function AlertDialogPortal({
24
+ ...props
25
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
26
+ return (
27
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
28
+ )
29
+ }
30
+
31
+ function AlertDialogOverlay({
32
+ className,
33
+ ...props
34
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
35
+ return (
36
+ <AlertDialogPrimitive.Overlay
37
+ data-slot="alert-dialog-overlay"
38
+ className={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
39
+ {...props}
40
+ />
41
+ )
42
+ }
43
+
44
+ function AlertDialogContent({
45
+ className,
46
+ size = "default",
47
+ ...props
48
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
49
+ size?: "default" | "sm"
50
+ }) {
51
+ return (
52
+ <AlertDialogPortal>
53
+ <AlertDialogOverlay />
54
+ <AlertDialogPrimitive.Content
55
+ data-slot="alert-dialog-content"
56
+ data-size={size}
57
+ className={cn(
58
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-3 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ </AlertDialogPortal>
64
+ )
65
+ }
66
+
67
+ function AlertDialogHeader({
68
+ className,
69
+ ...props
70
+ }: React.ComponentProps<"div">) {
71
+ return (
72
+ <div
73
+ data-slot="alert-dialog-header"
74
+ className={cn("grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ function AlertDialogFooter({
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="alert-dialog-footer"
87
+ className={cn(
88
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function AlertDialogMedia({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<"div">) {
100
+ return (
101
+ <div
102
+ data-slot="alert-dialog-media"
103
+ className={cn("bg-muted mb-2 inline-flex size-8 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4", className)}
104
+ {...props}
105
+ />
106
+ )
107
+ }
108
+
109
+ function AlertDialogTitle({
110
+ className,
111
+ ...props
112
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
113
+ return (
114
+ <AlertDialogPrimitive.Title
115
+ data-slot="alert-dialog-title"
116
+ className={cn("text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
117
+ {...props}
118
+ />
119
+ )
120
+ }
121
+
122
+ function AlertDialogDescription({
123
+ className,
124
+ ...props
125
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
126
+ return (
127
+ <AlertDialogPrimitive.Description
128
+ data-slot="alert-dialog-description"
129
+ className={cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
130
+ {...props}
131
+ />
132
+ )
133
+ }
134
+
135
+ function AlertDialogAction({
136
+ className,
137
+ variant = "default",
138
+ size = "default",
139
+ ...props
140
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
141
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
142
+ return (
143
+ <Button variant={variant} size={size} asChild>
144
+ <AlertDialogPrimitive.Action
145
+ data-slot="alert-dialog-action"
146
+ className={cn(className)}
147
+ {...props}
148
+ />
149
+ </Button>
150
+ )
151
+ }
152
+
153
+ function AlertDialogCancel({
154
+ className,
155
+ variant = "outline",
156
+ size = "default",
157
+ ...props
158
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
159
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
160
+ return (
161
+ <Button variant={variant} size={size} asChild>
162
+ <AlertDialogPrimitive.Cancel
163
+ data-slot="alert-dialog-cancel"
164
+ className={cn(className)}
165
+ {...props}
166
+ />
167
+ </Button>
168
+ )
169
+ }
170
+
171
+ export {
172
+ AlertDialog,
173
+ AlertDialogAction,
174
+ AlertDialogCancel,
175
+ AlertDialogContent,
176
+ AlertDialogDescription,
177
+ AlertDialogFooter,
178
+ AlertDialogHeader,
179
+ AlertDialogMedia,
180
+ AlertDialogOverlay,
181
+ AlertDialogPortal,
182
+ AlertDialogTitle,
183
+ AlertDialogTrigger,
184
+ }
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
14
+ destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
15
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
16
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
17
+ link: "text-primary underline-offset-4 hover:underline",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ function Badge({
27
+ className,
28
+ variant = "default",
29
+ asChild = false,
30
+ ...props
31
+ }: React.ComponentProps<"span"> &
32
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
33
+ const Comp = asChild ? Slot.Root : "span"
34
+
35
+ return (
36
+ <Comp
37
+ data-slot="badge"
38
+ data-variant={variant}
39
+ className={cn(badgeVariants({ variant }), className)}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ export { Badge, badgeVariants }
@@ -0,0 +1,60 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "cursor-pointer focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium focus-visible:ring-2 aria-invalid:ring-2 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
13
+ outline: "border-border dark:bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
14
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
15
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
16
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
17
+ link: "text-primary underline-offset-4 hover:underline",
18
+ },
19
+ size: {
20
+ default: "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
21
+ xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
22
+ sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
23
+ lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
24
+ icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
25
+ "icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
26
+ "icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
27
+ "icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ function Button({
38
+ className,
39
+ variant = "default",
40
+ size = "default",
41
+ asChild = false,
42
+ ...props
43
+ }: React.ComponentProps<"button"> &
44
+ VariantProps<typeof buttonVariants> & {
45
+ asChild?: boolean
46
+ }) {
47
+ const Comp = asChild ? Slot.Root : "button"
48
+
49
+ return (
50
+ <Comp
51
+ data-slot="button"
52
+ data-variant={variant}
53
+ data-size={size}
54
+ className={cn(buttonVariants({ variant, size, className }))}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ export { Button, buttonVariants }
@@ -0,0 +1,94 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-xs/relaxed ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col", className)}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
21
+ return (
22
+ <div
23
+ data-slot="card-header"
24
+ className={cn(
25
+ "gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
34
+ return (
35
+ <div
36
+ data-slot="card-title"
37
+ className={cn("text-sm font-medium", className)}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
44
+ return (
45
+ <div
46
+ data-slot="card-description"
47
+ className={cn("text-muted-foreground text-xs/relaxed", className)}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="card-action"
57
+ className={cn(
58
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
67
+ return (
68
+ <div
69
+ data-slot="card-content"
70
+ className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+
76
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
77
+ return (
78
+ <div
79
+ data-slot="card-footer"
80
+ className={cn("rounded-b-lg px-4 group-data-[size=sm]/card:px-3 [.border-t]:pt-4 group-data-[size=sm]/card:[.border-t]:pt-3 flex items-center", className)}
81
+ {...props}
82
+ />
83
+ )
84
+ }
85
+
86
+ export {
87
+ Card,
88
+ CardHeader,
89
+ CardFooter,
90
+ CardTitle,
91
+ CardAction,
92
+ CardDescription,
93
+ CardContent,
94
+ }
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui"
4
+
5
+ function Collapsible({
6
+ ...props
7
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
8
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
9
+ }
10
+
11
+ function CollapsibleTrigger({
12
+ ...props
13
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
14
+ return (
15
+ <CollapsiblePrimitive.CollapsibleTrigger
16
+ data-slot="collapsible-trigger"
17
+ className="cursor-pointer"
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function CollapsibleContent({
24
+ ...props
25
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
26
+ return (
27
+ <CollapsiblePrimitive.CollapsibleContent
28
+ data-slot="collapsible-content"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }