{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radial-socials",
  "type": "registry:ui",
  "title": "Radial Socials",
  "description": "radial social icons arranged in circles, with smooth expansion and rotation",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/radial-socials.tsx",
      "content": "'use client';\n\nimport React, { useState, useEffect, createContext, useContext } from 'react';\nimport { cn } from '@/lib/utils';\n\ninterface RadialSocialsContextType {\n  animatedIcons: Set<string>;\n  rotationStarted: boolean;\n  animationDelay: number;\n  expandDuration: number;\n  calculatePosition: (\n    radius: number,\n    angle: number,\n  ) => { x: number; y: number };\n}\n\ninterface RadialIconData {\n  icon: React.ReactNode;\n  className?: string;\n}\n\ninterface RadialSocialsProps {\n  children: React.ReactNode;\n  className?: string;\n  animationDelay?: number;\n  expandDuration?: number;\n}\n\ninterface RadialSocialsContentProps {\n  children: React.ReactNode;\n  className?: string;\n  containerClassName?: string;\n}\n\ninterface RadialCircularProps {\n  children: React.ReactNode;\n  radius: number;\n  duration?: number;\n  className?: string;\n  circleLineClassName?: string;\n  startAngle?: number;\n}\n\ninterface RadialIconProps extends RadialIconData {\n  className?: string;\n  angle?: number;\n}\n\ninterface InternalRadialCircularProps extends RadialCircularProps {\n  circleIndex?: number;\n  globalIconStartIndex?: number;\n}\n\ninterface InternalRadialIconProps extends RadialIconProps {\n  radius?: number;\n  iconIndex?: number;\n  circleIndex?: number;\n  totalIcons?: number;\n  globalIconIndex?: number;\n  duration?: number;\n}\n\ninterface RadialSocialsContentInternalProps extends RadialSocialsContentProps {\n  setTotalIcons?: (count: number) => void;\n}\n\nconst RadialSocialsContext = createContext<RadialSocialsContextType | null>(\n  null,\n);\n\nconst useRadialSocials = () => {\n  const context = useContext(RadialSocialsContext);\n  if (!context) {\n    throw new Error(\n      'RadialSocials components must be used within RadialSocials',\n    );\n  }\n  return context;\n};\n\nconst RadialIcon = React.forwardRef<HTMLDivElement, InternalRadialIconProps>(\n  (\n    {\n      icon,\n      className,\n      radius = 80,\n      iconIndex = 0,\n      circleIndex = 0,\n      totalIcons = 1,\n      globalIconIndex = 0,\n      duration = 20,\n      angle,\n      ...props\n    },\n    ref,\n  ) => {\n    const {\n      animatedIcons,\n      expandDuration,\n      calculatePosition,\n      rotationStarted,\n    } = useRadialSocials();\n    const iconAngle =\n      angle !== undefined ? angle : (360 / totalIcons) * iconIndex;\n    const position = calculatePosition(radius, iconAngle);\n    const isAnimated = animatedIcons.has(globalIconIndex.toString());\n\n    return (\n      <div\n        ref={ref}\n        className='absolute'\n        style={{\n          left: '50%',\n          top: '50%',\n          marginLeft: `-20px`,\n          marginTop: `-20px`,\n          transform: isAnimated\n            ? `translate(${position.x}px, ${position.y}px) scale(1)`\n            : 'translate(0px, 0px) scale(0)',\n          transition: `transform ${expandDuration}ms cubic-bezier(0.34, 1.56, 0.64, 1)`,\n          opacity: isAnimated ? 1 : 0,\n        }}\n        {...props}\n      >\n        <div\n          className={cn(\n            'flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full bg-card/90 text-card-foreground hover:bg-card hover:text-card-foreground transition-all duration-300 hover:scale-110 backdrop-blur-xs border border-border/50 shadow-lg',\n            className,\n          )}\n          style={{\n            animation: rotationStarted\n              ? `counter-rotate-${circleIndex} ${duration}s linear infinite`\n              : 'none',\n          }}\n        >\n          <div className='w-4 h-4 sm:w-5 sm:h-5 flex items-center justify-center'>\n            {icon}\n          </div>\n        </div>\n      </div>\n    );\n  },\n);\n\nconst RadialCircular = React.forwardRef<\n  HTMLDivElement,\n  InternalRadialCircularProps\n>(\n  (\n    {\n      children,\n      radius,\n      duration = 20,\n      className,\n      circleLineClassName,\n      circleIndex = 0,\n      globalIconStartIndex = 0,\n      startAngle = 0,\n      ...props\n    },\n    ref,\n  ) => {\n    const { rotationStarted } = useRadialSocials();\n\n    const icons = React.Children.toArray(children).filter(\n      (child): child is React.ReactElement<InternalRadialIconProps> =>\n        React.isValidElement(child) && child.type === RadialIcon,\n    );\n\n    return (\n      <div ref={ref} className='absolute inset-0' {...props}>\n        <div\n          className={cn(\n            'absolute rounded-full border-2 border-black/30 dark:border-white/30',\n            circleLineClassName,\n            className,\n          )}\n          style={{\n            width: `${radius * 2}px`,\n            height: `${radius * 2}px`,\n            left: '50%',\n            top: '50%',\n            marginLeft: `-${radius}px`,\n            marginTop: `-${radius}px`,\n            boxShadow:\n              '0 0 10px rgba(0, 0, 0, 0.1), 0 0 10px rgba(255, 255, 255, 0.1)',\n          }}\n        />\n\n        <div\n          className='absolute'\n          style={{\n            width: `${radius * 2}px`,\n            height: `${radius * 2}px`,\n            left: '50%',\n            top: '50%',\n            marginLeft: `-${radius}px`,\n            marginTop: `-${radius}px`,\n            animation: rotationStarted\n              ? `rotate-${circleIndex} ${duration}s linear infinite`\n              : 'none',\n          }}\n        >\n          {React.Children.map(children, (child, iconIndex) => {\n            if (\n              React.isValidElement<InternalRadialIconProps>(child) &&\n              child.type === RadialIcon\n            ) {\n              return React.cloneElement(child, {\n                radius,\n                iconIndex,\n                circleIndex,\n                totalIcons: icons.length,\n                globalIconIndex: globalIconStartIndex + iconIndex,\n                duration,\n                key: `${circleIndex}-${iconIndex}`,\n              });\n            }\n            return child;\n          })}\n        </div>\n      </div>\n    );\n  },\n);\n\nconst RadialSocials = React.forwardRef<HTMLDivElement, RadialSocialsProps>(\n  (\n    {\n      children,\n      className,\n      animationDelay = 150,\n      expandDuration = 800,\n      ...props\n    },\n    ref,\n  ) => {\n    const [animatedIcons, setAnimatedIcons] = useState<Set<string>>(new Set());\n    const [rotationStarted, setRotationStarted] = useState(false);\n    const [totalIcons, setTotalIcons] = useState(0);\n\n    const calculatePosition = (radius: number, angle: number) => {\n      const radian = (angle * Math.PI) / 180;\n      return {\n        x: Math.cos(radian) * radius,\n        y: Math.sin(radian) * radius,\n      };\n    };\n\n    useEffect(() => {\n      if (totalIcons === 0) return;\n\n      // Schedule all state updates asynchronously to avoid cascading renders\n      const timeouts: NodeJS.Timeout[] = [];\n\n      // Reset icons asynchronously\n      const resetTimeout = setTimeout(() => {\n        setAnimatedIcons(new Set());\n      }, 0);\n      timeouts.push(resetTimeout);\n\n      // Animate icons one by one\n      Array.from({ length: totalIcons }, (_, index) => index).forEach(\n        (index) => {\n          const timeout = setTimeout(() => {\n            setAnimatedIcons((prev) => new Set([...prev, index.toString()]));\n          }, index * animationDelay);\n          timeouts.push(timeout);\n        },\n      );\n\n      // Start rotation after all animations complete\n      const totalAnimationTime = totalIcons * animationDelay + expandDuration;\n      const rotationTimeout = setTimeout(() => {\n        setRotationStarted(true);\n      }, totalAnimationTime);\n      timeouts.push(rotationTimeout);\n\n      return () => {\n        timeouts.forEach((timeout) => clearTimeout(timeout));\n      };\n    }, [totalIcons, animationDelay, expandDuration]);\n\n    const contextValue: RadialSocialsContextType = {\n      animatedIcons,\n      rotationStarted,\n      animationDelay,\n      expandDuration,\n      calculatePosition,\n    };\n\n    return (\n      <RadialSocialsContext.Provider value={contextValue}>\n        <div ref={ref} className={cn('w-full h-full', className)} {...props}>\n          {React.Children.map(children, (child) => {\n            if (\n              React.isValidElement<RadialSocialsContentInternalProps>(child)\n            ) {\n              return React.cloneElement(child, { setTotalIcons });\n            }\n            return child;\n          })}\n        </div>\n      </RadialSocialsContext.Provider>\n    );\n  },\n);\n\nconst RadialSocialsContent = React.forwardRef<\n  HTMLDivElement,\n  RadialSocialsContentInternalProps\n>(\n  (\n    { children, className, containerClassName, setTotalIcons, ...props },\n    ref,\n  ) => {\n    const circles = React.Children.toArray(children).filter(\n      (child): child is React.ReactElement<InternalRadialCircularProps> =>\n        React.isValidElement(child) && child.type === RadialCircular,\n    );\n\n    useEffect(() => {\n      let totalIconCount = 0;\n\n      circles.forEach((circle) => {\n        const icons = React.Children.toArray(circle.props.children).filter(\n          (child): child is React.ReactElement<InternalRadialIconProps> =>\n            React.isValidElement(child) && child.type === RadialIcon,\n        );\n        totalIconCount += icons.length;\n      });\n\n      if (setTotalIcons) {\n        setTotalIcons(totalIconCount);\n      }\n    }, [children, setTotalIcons, circles]);\n\n    useEffect(() => {\n      const styleId = 'radial-socials-keyframes';\n      let styleElement = document.getElementById(styleId) as HTMLStyleElement;\n\n      if (!styleElement) {\n        styleElement = document.createElement('style');\n        styleElement.id = styleId;\n        document.head.appendChild(styleElement);\n      }\n\n      const keyframes = circles\n        .map(\n          (_, index) => `\n          @keyframes rotate-${index} {\n            from { transform: rotate(0deg); }\n            to { transform: rotate(360deg); }\n          }\n          @keyframes counter-rotate-${index} {\n            from { transform: rotate(0deg); }\n            to { transform: rotate(-360deg); }\n          }\n        `,\n        )\n        .join('\\n');\n\n      styleElement.textContent = keyframes;\n\n      return () => {\n        if (styleElement && styleElement.parentNode) {\n          styleElement.parentNode.removeChild(styleElement);\n        }\n      };\n    }, [circles, circles.length]);\n\n    const circlesWithIconCount = circles.reduce<\n      Array<{\n        circle: React.ReactElement<InternalRadialCircularProps>;\n        startIndex: number;\n        iconCount: number;\n      }>\n    >((acc, circle) => {\n      const icons = React.Children.toArray(circle.props.children).filter(\n        (child): child is React.ReactElement<InternalRadialIconProps> =>\n          React.isValidElement(child) && child.type === RadialIcon,\n      );\n      const startIndex =\n        acc.length > 0\n          ? acc[acc.length - 1].startIndex + acc[acc.length - 1].iconCount\n          : 0;\n      acc.push({\n        circle: circle as React.ReactElement<InternalRadialCircularProps>,\n        startIndex,\n        iconCount: icons.length,\n      });\n      return acc;\n    }, []);\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          'w-full h-full flex items-center justify-center p-4',\n          containerClassName,\n        )}\n        {...props}\n      >\n        <div\n          className={cn(\n            'relative aspect-square w-full max-w-md flex items-center justify-center',\n            className,\n          )}\n        >\n          {circlesWithIconCount.map(({ circle, startIndex }, circleIndex) => {\n            return React.cloneElement(circle, {\n              circleIndex,\n              globalIconStartIndex: startIndex,\n              key: circleIndex,\n            });\n          })}\n        </div>\n      </div>\n    );\n  },\n);\n\nRadialSocials.displayName = 'RadialSocials';\nRadialSocialsContent.displayName = 'RadialSocialsContent';\nRadialCircular.displayName = 'RadialCircular';\nRadialIcon.displayName = 'RadialIcon';\n\nexport {\n  RadialSocials,\n  RadialSocialsContent,\n  RadialCircular,\n  RadialIcon,\n  type RadialSocialsProps,\n  type RadialSocialsContentProps,\n  type RadialCircularProps,\n  type RadialIconProps,\n  type RadialIconData,\n};\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
