@cloudbase/agent-react-ui 0.0.23
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 +135 -0
- package/components.json +21 -0
- package/dist/index.css +4241 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +2169 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2182 -0
- package/dist/index.mjs.map +1 -0
- package/example/.env.sample +2 -0
- package/example/App.tsx +368 -0
- package/example/app.css +1 -0
- package/example/index.html +12 -0
- package/example/main.tsx +9 -0
- package/example/vite.config.ts +34 -0
- package/package.json +75 -0
- package/postcss.config.cjs +3 -0
- package/src/components/ai-elements/agent.tsx +140 -0
- package/src/components/ai-elements/artifact.tsx +147 -0
- package/src/components/ai-elements/attachments.tsx +421 -0
- package/src/components/ai-elements/audio-player.tsx +228 -0
- package/src/components/ai-elements/canvas.tsx +22 -0
- package/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/src/components/ai-elements/checkpoint.tsx +71 -0
- package/src/components/ai-elements/code-block.tsx +532 -0
- package/src/components/ai-elements/commit.tsx +448 -0
- package/src/components/ai-elements/confirmation.tsx +176 -0
- package/src/components/ai-elements/connection.tsx +28 -0
- package/src/components/ai-elements/context.tsx +408 -0
- package/src/components/ai-elements/controls.tsx +18 -0
- package/src/components/ai-elements/conversation.tsx +100 -0
- package/src/components/ai-elements/edge.tsx +140 -0
- package/src/components/ai-elements/environment-variables.tsx +295 -0
- package/src/components/ai-elements/file-tree.tsx +258 -0
- package/src/components/ai-elements/image.tsx +24 -0
- package/src/components/ai-elements/inline-citation.tsx +287 -0
- package/src/components/ai-elements/message.tsx +336 -0
- package/src/components/ai-elements/mic-selector.tsx +370 -0
- package/src/components/ai-elements/model-selector.tsx +211 -0
- package/src/components/ai-elements/node.tsx +71 -0
- package/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/src/components/ai-elements/package-info.tsx +233 -0
- package/src/components/ai-elements/panel.tsx +15 -0
- package/src/components/ai-elements/persona.tsx +270 -0
- package/src/components/ai-elements/plan.tsx +142 -0
- package/src/components/ai-elements/prompt-input.tsx +1263 -0
- package/src/components/ai-elements/queue.tsx +274 -0
- package/src/components/ai-elements/reasoning.tsx +193 -0
- package/src/components/ai-elements/sandbox.tsx +126 -0
- package/src/components/ai-elements/schema-display.tsx +458 -0
- package/src/components/ai-elements/shimmer.tsx +64 -0
- package/src/components/ai-elements/snippet.tsx +139 -0
- package/src/components/ai-elements/sources.tsx +77 -0
- package/src/components/ai-elements/speech-input.tsx +301 -0
- package/src/components/ai-elements/stack-trace.tsx +482 -0
- package/src/components/ai-elements/suggestion.tsx +53 -0
- package/src/components/ai-elements/task.tsx +87 -0
- package/src/components/ai-elements/terminal.tsx +261 -0
- package/src/components/ai-elements/test-results.tsx +485 -0
- package/src/components/ai-elements/tool.tsx +174 -0
- package/src/components/ai-elements/toolbar.tsx +16 -0
- package/src/components/ai-elements/transcription.tsx +124 -0
- package/src/components/ai-elements/voice-selector.tsx +479 -0
- package/src/components/ai-elements/web-preview.tsx +263 -0
- package/src/components/chat/Chat.tsx +178 -0
- package/src/components/chat/Input.tsx +98 -0
- package/src/components/chat/Message.tsx +276 -0
- package/src/components/chat/index.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/accordion.tsx +64 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +239 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/hover-card.tsx +42 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/css/global.css +123 -0
- package/src/css/index.css +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/context.ts +8 -0
- package/src/locales/hooks.ts +20 -0
- package/src/locales/index.ts +3 -0
- package/src/locales/langs/en.ts +17 -0
- package/src/locales/langs/index.ts +12 -0
- package/src/locales/langs/zh-cn.ts +18 -0
- package/tsconfig.json +21 -0
- 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 @@
|
|
|
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
|
+
};
|