component
Agent Avatar
Circular avatar with colored status ring (online/busy/idle/offline), emoji or initials fallback.
Installation
npx shadcn@latest add https://optimotive-ui.dev.optimotive-tools.co.uk/registry.json agent-avatarPreview
Source
components/agent-avatar.tsx
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
export type AgentStatus = "online" | "busy" | "offline" | "idle"
export interface AgentAvatarProps extends React.HTMLAttributes<HTMLDivElement> {
name: string
emoji?: string
status?: AgentStatus
size?: "sm" | "md" | "lg"
}
const sizeMap = {
sm: { outer: "w-8 h-8", text: "text-xs", ring: "ring-2", dot: "w-2 h-2 border" },
md: { outer: "w-10 h-10", text: "text-sm", ring: "ring-2", dot: "w-2.5 h-2.5 border-2" },
lg: { outer: "w-14 h-14", text: "text-lg", ring: "ring-2", dot: "w-3.5 h-3.5 border-2" },
}
const statusRing: Record<AgentStatus, string> = {
online: "ring-emerald-500",
busy: "ring-amber-400",
idle: "ring-blue-400",
offline: "ring-slate-600",
}
const statusDot: Record<AgentStatus, string> = {
online: "bg-emerald-500",
busy: "bg-amber-400",
idle: "bg-blue-400",
offline: "bg-slate-600",
}
function getInitials(name: string) {
return name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)
}
export function AgentAvatar({ name, emoji, status = "offline", size = "md", className, ...props }: AgentAvatarProps) {
const s = sizeMap[size]
return (
<div className={cn("relative inline-flex shrink-0", className)} {...props}>
<div
className={cn(
"rounded-full flex items-center justify-center bg-slate-800 text-white font-semibold select-none",
s.outer,
s.text,
s.ring,
statusRing[status]
)}
>
{emoji ?? getInitials(name)}
</div>
<span
className={cn(
"absolute bottom-0 right-0 rounded-full border-slate-950",
s.dot,
statusDot[status],
status === "online" && "animate-pulse"
)}
/>
</div>
)
}