@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,276 @@
1
+ import {
2
+ Message,
3
+ MessageAction,
4
+ MessageActions,
5
+ MessageContent,
6
+ MessageResponse,
7
+ } from "@/components/ai-elements/message";
8
+ import {
9
+ Tool,
10
+ ToolContent,
11
+ ToolHeader,
12
+ ToolInput,
13
+ ToolOutput,
14
+ } from "@/components/ai-elements/tool";
15
+ import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
16
+ import { CheckIcon, CopyIcon } from "lucide-react";
17
+ import { memo, type ReactElement, type SVGProps } from "react";
18
+ import { type UIMessage, type ToolPart, type Action } from "@cloudbase/agent-react-core";
19
+ import { useLocale } from "@/locales";
20
+
21
+ interface CopyActionProps {
22
+ content: string;
23
+ }
24
+
25
+ const CopyAction = memo(({ content }: CopyActionProps) => {
26
+ const { locale } = useLocale("message");
27
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
28
+
29
+ return (
30
+ <MessageAction
31
+ label={locale.copy}
32
+ onClick={() => copyToClipboard(content)}
33
+ tooltip={isCopied ? locale.copied : locale.copyToClipboard}
34
+ >
35
+ {isCopied ? (
36
+ <CheckIcon className="size-4" />
37
+ ) : (
38
+ <CopyIcon className="size-4" />
39
+ )}
40
+ </MessageAction>
41
+ );
42
+ });
43
+
44
+ CopyAction.displayName = "CopyAction";
45
+
46
+ // Map ToolCallStatus to Tool component state
47
+ const mapToolStatus = (status: ToolPart["status"]) => {
48
+ const statusMap: Record<
49
+ ToolPart["status"],
50
+ "input-streaming" | "input-available" | "output-available" | "output-error"
51
+ > = {
52
+ pending: "input-streaming",
53
+ ready: "input-available",
54
+ executing: "input-available",
55
+ completed: "output-available",
56
+ failed: "output-error",
57
+ };
58
+ return statusMap[status];
59
+ };
60
+
61
+ interface ToolCallRendererProps {
62
+ toolPart: ToolPart;
63
+ actions?: Record<string, Action>;
64
+ onRespond?: (toolCallId: string, result: string) => void;
65
+ }
66
+
67
+ const ToolCallRenderer = memo(
68
+ ({ toolPart, actions, onRespond }: ToolCallRendererProps) => {
69
+ // Check if there's a custom render function for this tool
70
+ const action = actions?.[toolPart.name];
71
+
72
+ if (
73
+ action &&
74
+ ((action.type === "client-tool" && action.render) ||
75
+ action.type === "tool-call")
76
+ ) {
77
+ const respond = (result: string) => {
78
+ onRespond?.(toolPart.toolCallId, result);
79
+ };
80
+ const render = action.render;
81
+ const rendered = render?.({
82
+ toolCall: toolPart,
83
+ respond,
84
+ });
85
+ if (typeof rendered === "string") {
86
+ return <div>{rendered}</div>;
87
+ }
88
+ return rendered as ReactElement;
89
+ }
90
+
91
+ // Default Tool component rendering
92
+ const state = mapToolStatus(toolPart.status);
93
+
94
+ return (
95
+ <Tool>
96
+ <ToolHeader
97
+ type="dynamic-tool"
98
+ state={state}
99
+ toolName={toolPart.name}
100
+ />
101
+ <ToolContent>
102
+ <ToolInput input={toolPart.args} />
103
+ {(toolPart.status === "completed" ||
104
+ toolPart.status === "failed") && (
105
+ <ToolOutput
106
+ output={toolPart.result}
107
+ errorText={toolPart.error?.message}
108
+ />
109
+ )}
110
+ </ToolContent>
111
+ </Tool>
112
+ );
113
+ }
114
+ );
115
+
116
+ ToolCallRenderer.displayName = "ToolCallRenderer";
117
+
118
+ export interface ChatMessageProps {
119
+ messages: UIMessage[];
120
+ isLoading?: boolean;
121
+ actions?: Record<string, Action>;
122
+ onRespond?: (toolCallId: string, result: string) => void;
123
+ }
124
+
125
+ export function ChatMessage({
126
+ messages,
127
+ isLoading = false,
128
+ actions,
129
+ onRespond,
130
+ }: ChatMessageProps) {
131
+ const { locale } = useLocale("message");
132
+ const visibleMessages = messages.filter(
133
+ (message) => message.role === "user" || message.role === "assistant"
134
+ );
135
+ const lastVisibleMessage = visibleMessages[visibleMessages.length - 1];
136
+ const shouldRenderAssistantPlaceholder =
137
+ isLoading && (!lastVisibleMessage || lastVisibleMessage.role === "user");
138
+
139
+ return (
140
+ <div className="flex flex-col gap-4">
141
+ {visibleMessages.map((message) => {
142
+ const role = message.role as "user" | "assistant";
143
+ const isLoadingAssistantMessage =
144
+ isLoading &&
145
+ role === "assistant" &&
146
+ message.id === lastVisibleMessage?.id;
147
+
148
+ // Get all text content for copy action
149
+ const allTextContent = message.parts
150
+ .filter((part) => part.type === "text")
151
+ .map((part) => (part as { type: "text"; text: string }).text)
152
+ .join("");
153
+ const hasAssistantText =
154
+ role === "assistant" && allTextContent.trim().length > 0;
155
+ const isPausedAssistantNoText =
156
+ role === "assistant" && Boolean(message.paused) && !hasAssistantText;
157
+ const copyContent =
158
+ allTextContent || (isPausedAssistantNoText ? locale.paused : "");
159
+
160
+ return (
161
+ <Message from={role} key={message.id}>
162
+ {/* Render parts in order */}
163
+ {message.parts.map((part) => {
164
+ if (part.type === "text") {
165
+ const textPart = part as {
166
+ type: "text";
167
+ text: string;
168
+ id: string;
169
+ };
170
+ return (
171
+ <MessageContent key={textPart.id}>
172
+ {role === "assistant" ? (
173
+ <MessageResponse>{textPart.text}</MessageResponse>
174
+ ) : (
175
+ textPart.text
176
+ )}
177
+ </MessageContent>
178
+ );
179
+ }
180
+
181
+ if (part.type === "tool") {
182
+ const toolPart = part as ToolPart;
183
+ return (
184
+ <ToolCallRenderer
185
+ key={toolPart.id}
186
+ toolPart={toolPart}
187
+ actions={actions}
188
+ onRespond={onRespond}
189
+ />
190
+ );
191
+ }
192
+
193
+ return null;
194
+ })}
195
+
196
+ {/* Loading state for the active assistant message */}
197
+ {isLoadingAssistantMessage && (
198
+ <MessageContent>
199
+ <div className="inline-flex items-center text-muted-foreground">
200
+ <SvgSpinners3DotsFade className="text-xl" />
201
+ </div>
202
+ </MessageContent>
203
+ )}
204
+
205
+ {isPausedAssistantNoText && !isLoadingAssistantMessage && (
206
+ <MessageContent>
207
+ <div className="text-muted-foreground">{locale.paused}</div>
208
+ </MessageContent>
209
+ )}
210
+
211
+ {/* Copy action for assistant messages with text */}
212
+ {role === "assistant" &&
213
+ copyContent &&
214
+ !isLoadingAssistantMessage && (
215
+ <MessageActions>
216
+ <CopyAction content={copyContent} />
217
+ </MessageActions>
218
+ )}
219
+ </Message>
220
+ );
221
+ })}
222
+
223
+ {shouldRenderAssistantPlaceholder && (
224
+ <Message from="assistant">
225
+ <MessageContent>
226
+ <div className="inline-flex items-center text-muted-foreground">
227
+ <SvgSpinners3DotsFade className="text-xl" />
228
+ </div>
229
+ </MessageContent>
230
+ </Message>
231
+ )}
232
+ </div>
233
+ );
234
+ }
235
+
236
+ export function SvgSpinners3DotsFade(props: SVGProps<SVGSVGElement>) {
237
+ return (
238
+ <svg
239
+ xmlns="http://www.w3.org/2000/svg"
240
+ width="1em"
241
+ height="1em"
242
+ viewBox="0 0 24 24"
243
+ {...props}
244
+ >
245
+ <circle cx="4" cy="12" r="3" fill="currentColor">
246
+ <animate
247
+ id="dotFadeFirst"
248
+ fill="freeze"
249
+ attributeName="opacity"
250
+ begin="0;dotFadeThird.end-0.25s"
251
+ dur="0.75s"
252
+ values="1;.2"
253
+ />
254
+ </circle>
255
+ <circle cx="12" cy="12" r="3" fill="currentColor" opacity=".4">
256
+ <animate
257
+ fill="freeze"
258
+ attributeName="opacity"
259
+ begin="dotFadeFirst.begin+0.15s"
260
+ dur="0.75s"
261
+ values="1;.2"
262
+ />
263
+ </circle>
264
+ <circle cx="20" cy="12" r="3" fill="currentColor" opacity=".3">
265
+ <animate
266
+ id="dotFadeThird"
267
+ fill="freeze"
268
+ attributeName="opacity"
269
+ begin="dotFadeFirst.begin+0.3s"
270
+ dur="0.75s"
271
+ values="1;.2"
272
+ />
273
+ </circle>
274
+ </svg>
275
+ );
276
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Chat";
2
+ export * from "./Input";
@@ -0,0 +1 @@
1
+ export * from "./chat";
@@ -0,0 +1,64 @@
1
+ import * as React from "react";
2
+ import { ChevronDownIcon } from "lucide-react";
3
+ import { Accordion as AccordionPrimitive } from "radix-ui";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function Accordion({
8
+ ...props
9
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
10
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
11
+ }
12
+
13
+ function AccordionItem({
14
+ className,
15
+ ...props
16
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
17
+ return (
18
+ <AccordionPrimitive.Item
19
+ data-slot="accordion-item"
20
+ className={cn("border-b last:border-b-0", className)}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function AccordionTrigger({
27
+ className,
28
+ children,
29
+ ...props
30
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
31
+ return (
32
+ <AccordionPrimitive.Header className="flex">
33
+ <AccordionPrimitive.Trigger
34
+ data-slot="accordion-trigger"
35
+ className={cn(
36
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
37
+ className
38
+ )}
39
+ {...props}
40
+ >
41
+ {children}
42
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
43
+ </AccordionPrimitive.Trigger>
44
+ </AccordionPrimitive.Header>
45
+ );
46
+ }
47
+
48
+ function AccordionContent({
49
+ className,
50
+ children,
51
+ ...props
52
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
53
+ return (
54
+ <AccordionPrimitive.Content
55
+ data-slot="accordion-content"
56
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
57
+ {...props}
58
+ >
59
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
60
+ </AccordionPrimitive.Content>
61
+ );
62
+ }
63
+
64
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,66 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ );
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ export { Alert, AlertTitle, AlertDescription };
@@ -0,0 +1,107 @@
1
+ import * as React from "react";
2
+ import { Avatar as AvatarPrimitive } from "radix-ui";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function Avatar({
7
+ className,
8
+ size = "default",
9
+ ...props
10
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
11
+ size?: "default" | "sm" | "lg";
12
+ }) {
13
+ return (
14
+ <AvatarPrimitive.Root
15
+ data-slot="avatar"
16
+ data-size={size}
17
+ className={cn(
18
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function AvatarImage({
27
+ className,
28
+ ...props
29
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
30
+ return (
31
+ <AvatarPrimitive.Image
32
+ data-slot="avatar-image"
33
+ className={cn("aspect-square size-full", className)}
34
+ {...props}
35
+ />
36
+ );
37
+ }
38
+
39
+ function AvatarFallback({
40
+ className,
41
+ ...props
42
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
43
+ return (
44
+ <AvatarPrimitive.Fallback
45
+ data-slot="avatar-fallback"
46
+ className={cn(
47
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
56
+ return (
57
+ <span
58
+ data-slot="avatar-badge"
59
+ className={cn(
60
+ "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
61
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
62
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
63
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ );
69
+ }
70
+
71
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
72
+ return (
73
+ <div
74
+ data-slot="avatar-group"
75
+ className={cn(
76
+ "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ function AvatarGroupCount({
85
+ className,
86
+ ...props
87
+ }: React.ComponentProps<"div">) {
88
+ return (
89
+ <div
90
+ data-slot="avatar-group-count"
91
+ className={cn(
92
+ "bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
93
+ className
94
+ )}
95
+ {...props}
96
+ />
97
+ );
98
+ }
99
+
100
+ export {
101
+ Avatar,
102
+ AvatarImage,
103
+ AvatarFallback,
104
+ AvatarBadge,
105
+ AvatarGroup,
106
+ AvatarGroupCount,
107
+ };
@@ -0,0 +1,48 @@
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
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>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 transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
+ destructive:
16
+ "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
+ outline:
18
+ "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ },
26
+ }
27
+ );
28
+
29
+ function Badge({
30
+ className,
31
+ variant = "default",
32
+ asChild = false,
33
+ ...props
34
+ }: React.ComponentProps<"span"> &
35
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
36
+ const Comp = asChild ? Slot.Root : "span";
37
+
38
+ return (
39
+ <Comp
40
+ data-slot="badge"
41
+ data-variant={variant}
42
+ className={cn(badgeVariants({ variant }), className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ export { Badge, badgeVariants };
@@ -0,0 +1,83 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { Slot } from "radix-ui";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { Separator } from "@/components/ui/separator";
6
+
7
+ const buttonGroupVariants = cva(
8
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
9
+ {
10
+ variants: {
11
+ orientation: {
12
+ horizontal:
13
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
14
+ vertical:
15
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ orientation: "horizontal",
20
+ },
21
+ }
22
+ );
23
+
24
+ function ButtonGroup({
25
+ className,
26
+ orientation,
27
+ ...props
28
+ }: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
29
+ return (
30
+ <div
31
+ role="group"
32
+ data-slot="button-group"
33
+ data-orientation={orientation}
34
+ className={cn(buttonGroupVariants({ orientation }), className)}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ function ButtonGroupText({
41
+ className,
42
+ asChild = false,
43
+ ...props
44
+ }: React.ComponentProps<"div"> & {
45
+ asChild?: boolean;
46
+ }) {
47
+ const Comp = asChild ? Slot.Root : "div";
48
+
49
+ return (
50
+ <Comp
51
+ className={cn(
52
+ "bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ function ButtonGroupSeparator({
61
+ className,
62
+ orientation = "vertical",
63
+ ...props
64
+ }: React.ComponentProps<typeof Separator>) {
65
+ return (
66
+ <Separator
67
+ data-slot="button-group-separator"
68
+ orientation={orientation}
69
+ className={cn(
70
+ "bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ );
76
+ }
77
+
78
+ export {
79
+ ButtonGroup,
80
+ ButtonGroupSeparator,
81
+ ButtonGroupText,
82
+ buttonGroupVariants,
83
+ };