snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
"use client"
import * as React from "react"
import { type FileUIPart } from "ai"
import { cn } from "cn"
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentTitle,
AttachmentTrigger,
} from "@/registry/bases/radix/ui/attachment"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { IconPlaceholder } from "@/app/(app)/create/components/icon-placeholder"
function getFilename(part: FileUIPart) {
if (part.filename) {
return part.filename
}
try {
const pathname = new URL(part.url).pathname
const filename = pathname.split("/").filter(Boolean).pop()
if (filename?.includes(".")) {
return decodeURIComponent(filename)
}
} catch {
const filename = part.url.split("?")[0]?.split("/").pop()
if (filename?.includes(".")) {
return filename
}
}
return part.mediaType.startsWith("image/") ? "Image file" : "File"
}
export function PartFile({
part,
className,
}: { part: FileUIPart } & Omit<React.ComponentProps<"div">, "part">) {
if (part.type !== "file") {
return null
}
const isImage = part.mediaType.startsWith("image/")
const filename = getFilename(part)
if (isImage) {
return (
<Attachment
orientation="vertical"
className={cn("group/file w-full max-w-72", className)}
>
<AttachmentMedia variant="image" className="aspect-[4/3] w-full">
<img src={part.url} alt={filename} />
</AttachmentMedia>
<AttachmentContent className="flex items-center gap-2">
<AttachmentMedia className="size-7">
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="ImageIcon"
phosphor="ImageIcon"
remixicon="RiImageLine"
/>
</AttachmentMedia>
<span className="min-w-0 flex-1">
<AttachmentTitle>{filename}</AttachmentTitle>
</span>
<Badge variant="outline" className="shrink-0">
{part.mediaType.replace("image/", "").toUpperCase()}
</Badge>
</AttachmentContent>
<AttachmentTrigger asChild>
<a
href={part.url}
target="_blank"
rel="noreferrer"
aria-label={`Open ${filename}`}
/>
</AttachmentTrigger>
</Attachment>
)
}
return (
<Attachment className={cn("w-full max-w-80", className)}>
<AttachmentMedia>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="FileIcon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>{filename}</AttachmentTitle>
<AttachmentDescription>{part.mediaType}</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction asChild aria-hidden="true">
<span>
<IconPlaceholder
lucide="ExternalLinkIcon"
tabler="IconExternalLink"
hugeicons="LinkSquare02Icon"
phosphor="ArrowSquareOutIcon"
remixicon="RiExternalLinkLine"
/>
</span>
</AttachmentAction>
</AttachmentActions>
<AttachmentTrigger asChild>
<a
href={part.url}
target="_blank"
rel="noreferrer"
aria-label={`Open ${filename}`}
/>
</AttachmentTrigger>
</Attachment>
)
}

View File

@@ -0,0 +1,67 @@
"use client"
import * as React from "react"
import { type ReasoningUIPart } from "ai"
import { cn } from "cn"
import { Markdown } from "@/components/markdown"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const REASONING_LEVEL_LABELS = [
"Understanding",
"Analyzing",
"Prioritizing",
"Refining",
] as const
export function getReasoningLevelLabel(index: number) {
return REASONING_LEVEL_LABELS[index] ?? `Reasoning ${index + 1}`
}
function PartReasoningTrigger({ label }: { label?: string }) {
return (
<div>
<IconPlaceholder
lucide="BrainIcon"
tabler="IconBrain"
hugeicons="AiBrainIcon"
phosphor="BrainIcon"
remixicon="RiBrainLine"
data-slot="reasoning-trigger-icon"
className="cn-reasoning-trigger-icon size-4 shrink-0"
/>
<span
data-slot="reasoning-trigger-label"
className="cn-reasoning-trigger-label min-w-0 flex-1"
>
{label}
</span>
</div>
)
}
export function PartReasoning({
part,
label,
className,
}: { part: ReasoningUIPart; label?: string } & Omit<
React.ComponentProps<"div">,
"part"
>) {
if (part.type !== "reasoning") {
return null
}
const isStreaming = part.state === "streaming"
return (
<div
data-slot="part-reasoning"
className={cn("w-full max-w-full min-w-0", className)}
>
<div className="w-full shimmer">
<span className="whitespace-pre-wrap">{part.text}</span>
</div>
</div>
)
}

View File

@@ -0,0 +1,101 @@
"use client"
import * as React from "react"
import { type SourceDocumentUIPart, type SourceUrlUIPart } from "ai"
import { cn } from "cn"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { IconPlaceholder } from "@/app/(app)/create/components/icon-placeholder"
type SourcePart = SourceDocumentUIPart | SourceUrlUIPart
function getHostname(url: string) {
try {
return new URL(url).hostname.replace(/^www\./, "")
} catch {
return url
}
}
export function PartSource({
part,
className,
}: { part: SourcePart } & Omit<React.ComponentProps<"div">, "part">) {
if (part.type === "source-url") {
return (
<a
data-slot="part-source"
data-type={part.type}
href={part.url}
target="_blank"
rel="noreferrer"
className={cn(
"flex w-full max-w-full min-w-0 items-center gap-2 rounded-lg border bg-muted/40 p-3 text-sm transition-colors hover:bg-muted",
className
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground">
<IconPlaceholder
lucide="ExternalLinkIcon"
tabler="IconExternalLink"
hugeicons="LinkSquare02Icon"
phosphor="ArrowSquareOutIcon"
remixicon="RiExternalLinkLine"
className="size-4"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate font-medium">
{part.title ?? part.url}
</span>
<span className="block truncate text-xs text-muted-foreground">
{getHostname(part.url)}
</span>
</span>
<Badge variant="outline" className="shrink-0">
URL
</Badge>
</a>
)
}
if (part.type === "source-document") {
return (
<div
data-slot="part-source"
data-type={part.type}
className={cn(
"flex w-full max-w-full min-w-0 items-start gap-2 rounded-lg border bg-muted/40 p-3 text-sm",
className
)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground">
<IconPlaceholder
lucide="FileSearchIcon"
tabler="IconFileSearch"
hugeicons="FileSearchIcon"
phosphor="FileSearchIcon"
remixicon="RiFileSearchLine"
className="size-4"
/>
</span>
<span className="min-w-0 flex-1">
<span className="block truncate font-medium">
{part.title ?? part.filename ?? "Document source"}
</span>
<span className="mt-0.5 block truncate text-xs text-muted-foreground">
{[part.filename, part.mediaType].filter(Boolean).join(" / ")}
</span>
<span className="mt-2 block text-xs text-muted-foreground">
Source ID: {part.sourceId}
</span>
</span>
<Badge variant="outline" className="shrink-0">
Document
</Badge>
</div>
)
}
return null
}

View File

@@ -0,0 +1,49 @@
import * as React from "react"
import { type TextUIPart } from "ai"
import { Markdown } from "@/components/markdown"
import { Bubble, BubbleContent } from "@/registry/bases/radix/ui/bubble"
type PartTextVariant = "default" | "bubble"
export function PartText({
part,
role,
variant,
tinted = false,
className,
...props
}: {
part: TextUIPart
role?: "user" | "system" | "assistant"
tinted?: boolean
variant?: PartTextVariant
} & Omit<
React.ComponentProps<typeof Bubble>,
"children" | "part" | "variant"
>) {
if (part.type !== "text") {
return null
}
const content =
role === "user" || variant === "bubble" ? (
part.text
) : (
<Markdown animated>{part.text}</Markdown>
)
const bubbleVariant =
variant === "bubble"
? tinted
? "tinted"
: "default"
: role === "user"
? "default"
: "ghost"
return (
<Bubble variant={bubbleVariant} className={className} {...props}>
<BubbleContent>{content}</BubbleContent>
</Bubble>
)
}

View File

@@ -0,0 +1,123 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Separator } from "@/registry/bases/radix/ui/separator"
import { IconPlaceholder } from "@/app/(app)/create/components/icon-placeholder"
type ToolPart = {
type: `tool-${string}` | "dynamic-tool"
toolName?: string
toolCallId: string
title?: string
state: string
input?: unknown
output?: unknown
errorText?: string
}
function getToolName(part: ToolPart) {
return part.type === "dynamic-tool"
? (part.toolName ?? "dynamicTool")
: part.type.replace("tool-", "")
}
function getToolTitle(part: ToolPart) {
if ("title" in part && typeof part.title === "string") {
return part.title
}
return getToolName(part)
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.replace(/[-_]/g, " ")
.replace(/\b\w/g, (letter) => letter.toUpperCase())
}
function formatValue(value: unknown) {
if (typeof value === "string") {
return value
}
return JSON.stringify(value, null, 2)
}
export function PartTool({
part,
className,
}: { part: ToolPart } & Omit<React.ComponentProps<"div">, "part">) {
if (part.type !== "dynamic-tool" && !part.type.startsWith("tool-")) {
return null
}
const input = "input" in part ? part.input : undefined
const output = "output" in part ? part.output : undefined
const errorText = "errorText" in part ? part.errorText : undefined
const stateLabel = part.state.replace(/-/g, " ")
return (
<div
data-slot="part-tool"
data-state={part.state}
className={cn(
"w-full max-w-full min-w-0 rounded-lg border bg-muted/40 text-sm",
className
)}
>
<div className="flex min-w-0 items-center gap-2 px-3 py-2">
<span className="flex size-7 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground">
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="TerminalIcon"
remixicon="RiSettingsLine"
className="size-4"
/>
</span>
<div className="min-w-0 flex-1">
<div className="truncate font-medium">{getToolTitle(part)}</div>
<div className="truncate text-xs text-muted-foreground">
{getToolName(part)}
</div>
</div>
<Badge variant="outline" className="capitalize">
{stateLabel}
</Badge>
</div>
{input !== undefined || output !== undefined || errorText ? (
<>
<Separator />
<div className="grid gap-3 p-3">
{input !== undefined ? (
<div className="grid gap-1">
<div className="text-xs font-medium text-muted-foreground">
Input
</div>
<pre className="max-h-32 overflow-auto rounded-md bg-background p-2 text-xs leading-relaxed">
{formatValue(input)}
</pre>
</div>
) : null}
{output !== undefined ? (
<div className="grid gap-1">
<div className="text-xs font-medium text-muted-foreground">
Output
</div>
<pre className="max-h-40 overflow-auto rounded-md bg-background p-2 text-xs leading-relaxed">
{formatValue(output)}
</pre>
</div>
) : null}
{errorText ? (
<div className="rounded-md border border-destructive/30 bg-destructive/10 p-2 text-xs text-destructive">
{errorText}
</div>
) : null}
</div>
</>
) : null}
</div>
)
}

View File

@@ -0,0 +1,227 @@
"use client"
import * as React from "react"
import { type ChatStatus } from "ai"
import { cn } from "cn"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/bases/radix/ui/dropdown-menu"
import { FieldGroup } from "@/registry/bases/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/registry/bases/radix/ui/input-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function PromptInput({
onSubmit,
status,
className,
defaultValue,
}: {
onSubmit: (input: string) => void
status: ChatStatus
className?: string
defaultValue?: string
}) {
const [value, setValue] = React.useState(defaultValue ?? "")
React.useEffect(() => {
setValue(defaultValue ?? "")
}, [defaultValue])
function handleFormSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
if (value?.trim()) {
onSubmit(value)
}
}
const isBusy = status === "submitted" || status === "streaming"
const showStopButton = status === "streaming"
const showSendButton = status !== "streaming"
return (
<form
onSubmit={handleFormSubmit}
className={cn("w-full", className)}
id="chat-form"
>
<FieldGroup className="gap-1">
<InputGroup>
<InputGroupTextarea
placeholder="Ask me anything..."
className="h-10 min-h-10 overflow-y-auto transition-all"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<InputGroupAddon align="block-end" className="p-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
<span className="sr-only">Add</span>
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-fit">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="PaperclipIcon"
tabler="IconPaperclip"
hugeicons="AttachmentIcon"
phosphor="PaperclipIcon"
remixicon="RiAttachmentLine"
/>
Add Photos and Files
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="MonitorIcon"
tabler="IconDeviceDesktop"
hugeicons="ComputerIcon"
phosphor="MonitorIcon"
remixicon="RiComputerLine"
/>
Take a Screenshot
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="ImageIcon"
phosphor="ImageIcon"
remixicon="RiImageLine"
/>
Create Image
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BrainIcon"
tabler="IconBrain"
hugeicons="AiBrainIcon"
phosphor="BrainIcon"
remixicon="RiBrainLine"
/>
Deep Research
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="GlobeIcon"
tabler="IconGlobe"
hugeicons="Globe02Icon"
phosphor="GlobeIcon"
remixicon="RiGlobeLine"
/>
Web Search
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
More
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>
<IconPlaceholder
lucide="BotIcon"
tabler="IconRobot"
hugeicons="RoboticIcon"
phosphor="RobotIcon"
remixicon="RiRobotLine"
/>
Agent Mode
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BookOpenIcon"
tabler="IconBook"
hugeicons="BookOpen02Icon"
phosphor="BookOpenIcon"
remixicon="RiBookOpenLine"
/>
Add Sources
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
Create Tasks
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupButton
variant="default"
size="icon-sm"
type="submit"
disabled={isBusy || !value?.trim()}
className="ml-auto data-[visible=false]:hidden"
data-visible={showSendButton}
>
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUp02Icon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
<span className="sr-only">Send</span>
</InputGroupButton>
<InputGroupButton
size="icon-sm"
variant="default"
type="button"
data-visible={showStopButton}
className="ml-auto data-[visible=false]:hidden"
>
<IconPlaceholder
lucide="SquareIcon"
tabler="IconPlayerStop"
hugeicons="StopCircleIcon"
phosphor="StopCircleIcon"
remixicon="RiStopCircleLine"
className="fill-foreground"
/>
<span className="sr-only">Stop</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</FieldGroup>
</form>
)
}