{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "avatar-quick-actions", "type": "registry:ui", "title": "Avatar Quick Actions", "description": "An orbital action ring that blooms outward from the avatar with staggered spring emergence — hover any action and the rest dim, drawing the eye.", "dependencies": [ "motion" ], "registryDependencies": [ "avatar" ], "files": [ { "path": "registry/ruixenui/avatar-quick-actions.tsx", "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { type ReactNode, useState } from \"react\";\n\n/* ═══════════════════════════════════════════════════════════\n Orbital Bloom — actions live behind the avatar. Hover and\n they spring outward radially with staggered timing, settling\n into orbit positions. Focus any action and the rest dim,\n drawing the eye. The avatar contracts to signal the release.\n No tooltip, no portal — same spatial plane.\n ═══════════════════════════════════════════════════════════ */\n\ninterface AvatarQuickActionsItem {\n icon: ReactNode;\n label: string;\n onClick?: () => void;\n}\n\ninterface AvatarQuickActionsProps {\n items: AvatarQuickActionsItem[];\n avatarSrc?: string;\n avatarFallback?: string;\n size?: number;\n className?: string;\n}\n\nexport default function AvatarQuickActions({\n items,\n avatarSrc,\n avatarFallback,\n size = 44,\n className,\n}: AvatarQuickActionsProps) {\n const [open, setOpen] = useState(false);\n const [activeIdx, setActiveIdx] = useState(null);\n\n const actionSize = Math.round(size * 0.7);\n const orbit = Math.round(size * 0.95);\n const container = orbit * 2 + actionSize;\n\n const step = (2 * Math.PI) / items.length;\n const start = -Math.PI / 2;\n\n return (\n {\n setOpen(false);\n setActiveIdx(null);\n }}\n >\n {/* orbit ring */}\n \n\n {/* avatar */}\n setOpen(true)}\n >\n
\n \n {avatarSrc && (\n \n )}\n \n {avatarFallback?.[0]?.toUpperCase() || \"?\"}\n \n \n
\n \n\n {/* orbiting actions */}\n {items.map((item, idx) => {\n const angle = start + idx * step;\n const tx = Math.cos(angle) * orbit;\n const ty = Math.sin(angle) * orbit;\n const active = activeIdx === idx;\n const labelAbove = ty < 0;\n\n return (\n setActiveIdx(idx)}\n onMouseLeave={() => setActiveIdx(null)}\n >\n \n {item.icon}\n \n\n \n {active && (\n \n {item.label}\n \n )}\n \n \n );\n })}\n \n );\n}\n", "type": "registry:ui", "target": "components/ruixen/avatar-quick-actions.tsx" } ] }