{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar-circle",
  "title": "Avatar Circle",
  "author": "mrap10",
  "description": "Animated circular progress ring for avatars, profile pictures or any circular content.",
  "dependencies": ["motion"],
  "files": [
    {
      "path": "registry/default/overlays/avatar-circle.tsx",
      "content": "\"use client\";\n\nimport { motion, type Easing } from \"motion/react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype AvatarSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport interface AvatarCircleProps {\n  children?: React.ReactNode;\n  size?: AvatarSize;\n  strokeColor?: string;\n  strokeWidth?: number;\n  duration?: number;\n  ease?: Easing | Easing[];\n  showTrack?: boolean;\n  lineCap?: \"butt\" | \"round\" | \"square\";\n  className?: string;\n}\n\nconst sizeMap: Record<AvatarSize, { wrapper: string; px: number }> = {\n  xs: { wrapper: \"size-8\", px: 32 },\n  sm: { wrapper: \"size-12\", px: 48 },\n  md: { wrapper: \"size-16\", px: 64 },\n  lg: { wrapper: \"size-24\", px: 96 },\n  xl: { wrapper: \"size-32\", px: 128 },\n};\n\n// Ring sits slightly outside the avatar's edge for visual breathing room\nconst RING_OFFSET = 2;\n\nexport function AvatarCircle({\n  children,\n  size = \"md\",\n  strokeColor = \"currentColor\",\n  strokeWidth = 1.5,\n  duration = 0.6,\n  ease = \"easeInOut\",\n  showTrack = true,\n  lineCap = \"round\",\n  className,\n}: AvatarCircleProps) {\n  const { px } = sizeMap[size];\n\n  const half = px / 2;\n  const radius = half - strokeWidth / 2 + RING_OFFSET;\n  const circumference = 2 * Math.PI * radius;\n\n  return (\n    <motion.div\n      tabIndex={0}\n      className={cn(\n        \"relative inline-flex cursor-pointer items-center justify-center rounded-full focus:outline-none\",\n        sizeMap[size].wrapper,\n        className\n      )}\n      initial=\"rest\"\n      whileHover=\"active\"\n      whileFocus=\"active\"\n    >\n      <div className=\"relative z-10 size-full overflow-hidden rounded-full\">\n        {children}\n      </div>\n\n      <svg\n        className=\"pointer-events-none absolute inset-0 z-50 size-full\"\n        viewBox={`0 0 ${px} ${px}`}\n        aria-hidden=\"true\"\n        style={{ overflow: \"visible\" }}\n      >\n        {showTrack && (\n          <circle\n            cx={half}\n            cy={half}\n            r={radius}\n            fill=\"none\"\n            stroke={strokeColor}\n            strokeWidth={strokeWidth}\n            opacity={0.12}\n            transform={`rotate(-90 ${half} ${half})`}\n          />\n        )}\n\n        {/* Animated ring - draws clockwise on hover or keyboard focus */}\n        <motion.circle\n          cx={half}\n          cy={half}\n          r={radius}\n          fill=\"none\"\n          stroke={strokeColor}\n          strokeWidth={strokeWidth}\n          strokeLinecap={lineCap}\n          strokeDasharray={circumference}\n          variants={{\n            rest: { strokeDashoffset: circumference },\n            active: { strokeDashoffset: 0 },\n          }}\n          transition={{ duration, ease }}\n          transform={`rotate(-90 ${half} ${half})`}\n        />\n      </svg>\n    </motion.div>\n  );\n}\n\nexport interface AvatarCircleImageProps extends AvatarCircleProps {\n  src: string;\n  alt: string;\n  fallback?: React.ReactNode;\n}\n\nexport function AvatarCircleImage({\n  src,\n  alt,\n  fallback,\n  size = \"md\",\n  ...ringProps\n}: AvatarCircleImageProps) {\n  const [imgError, setImgError] = useState(false);\n  const { px } = sizeMap[size];\n\n  return (\n    <AvatarCircle size={size} {...ringProps}>\n      {imgError && fallback ? (\n        <div\n          className={cn(\n            \"flex size-full items-center justify-center rounded-full\",\n            \"bg-neutral-200 dark:bg-neutral-700\",\n            \"font-medium text-neutral-600 select-none dark:text-neutral-300\"\n          )}\n          style={{ fontSize: px * 0.28 }}\n          aria-label={alt}\n        >\n          {fallback}\n        </div>\n      ) : (\n        // Works with Next.js <Image> too — just swap the tag\n        <img\n          src={src}\n          alt={alt}\n          width={px}\n          height={px}\n          onError={() => setImgError(true)}\n          className=\"size-full rounded-full object-cover\"\n          draggable={false}\n        />\n      )}\n    </AvatarCircle>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": ["overlays"],
  "type": "registry:component"
}
