{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "icons-icon",
  "title": "Icon",
  "description": "Base component to use animated icons.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@odysseyui/hooks-use-is-in-view",
    "@odysseyui/primitives-animate-slot"
  ],
  "files": [
    {
      "path": "registry/icons/icon/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  motion,\n  useAnimation,\n  type SVGMotionProps,\n  type UseInViewOptions,\n  type LegacyAnimationControls,\n  type Variants,\n  type HTMLMotionProps,\n} from 'motion/react';\n\nimport { cn } from '@/lib/utils';\nimport { useIsInView } from '@/hooks/use-is-in-view';\nimport { Slot, type WithAsChild } from '@/components/odyssey/primitives/animate/slot';\n\nconst staticAnimations = {\n  path: {\n    initial: { pathLength: 1 },\n    animate: {\n      pathLength: [0.05, 1],\n      transition: {\n        duration: 0.8,\n        ease: 'easeInOut',\n      },\n    },\n  } as Variants,\n  'path-loop': {\n    initial: { pathLength: 1 },\n    animate: {\n      pathLength: [1, 0.05, 1],\n      transition: {\n        duration: 1.6,\n        ease: 'easeInOut',\n      },\n    },\n  } as Variants,\n} as const;\n\ntype StaticAnimations = keyof typeof staticAnimations;\ntype TriggerProp<T = string> = boolean | StaticAnimations | T;\ntype Trigger = TriggerProp<string>;\n\ntype AnimateIconContextValue = {\n  controls: LegacyAnimationControls | undefined;\n  animation: StaticAnimations | string;\n  loop: boolean;\n  loopDelay: number;\n  active: boolean;\n  animate?: Trigger;\n  initialOnAnimateEnd?: boolean;\n  completeOnStop?: boolean;\n  persistOnAnimateEnd?: boolean;\n  delay?: number;\n};\n\ntype DefaultIconProps<T = string> = {\n  animate?: TriggerProp<T>;\n  animateOnHover?: TriggerProp<T>;\n  animateOnTap?: TriggerProp<T>;\n  animateOnView?: TriggerProp<T>;\n  animateOnViewMargin?: UseInViewOptions['margin'];\n  animateOnViewOnce?: boolean;\n  animation?: T | StaticAnimations;\n  loop?: boolean;\n  loopDelay?: number;\n  initialOnAnimateEnd?: boolean;\n  completeOnStop?: boolean;\n  persistOnAnimateEnd?: boolean;\n  delay?: number;\n};\n\ntype AnimateIconProps<T = string> = WithAsChild<\n  HTMLMotionProps<'span'> &\n    DefaultIconProps<T> & {\n      children: React.ReactNode;\n      asChild?: boolean;\n    }\n>;\n\ntype IconProps<T> = DefaultIconProps<T> &\n  Omit<SVGMotionProps<SVGSVGElement>, 'animate'> & {\n    size?: number;\n  };\n\ntype IconWrapperProps<T> = IconProps<T> & {\n  icon: React.ComponentType<IconProps<T>>;\n};\n\nconst AnimateIconContext = React.createContext<AnimateIconContextValue | null>(\n  null,\n);\n\nfunction useAnimateIconContext() {\n  const context = React.useContext(AnimateIconContext);\n  if (!context)\n    return {\n      controls: undefined,\n      animation: 'default',\n      loop: undefined,\n      loopDelay: undefined,\n      active: undefined,\n      animate: undefined,\n      initialOnAnimateEnd: undefined,\n      completeOnStop: undefined,\n      persistOnAnimateEnd: undefined,\n      delay: undefined,\n    };\n  return context;\n}\n\nfunction composeEventHandlers<E extends React.SyntheticEvent<unknown>>(\n  theirs?: (event: E) => void,\n  ours?: (event: E) => void,\n) {\n  return (event: E) => {\n    theirs?.(event);\n    ours?.(event);\n  };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyProps = Record<string, any>;\n\nfunction AnimateIcon({\n  asChild = false,\n  animate = false,\n  animateOnHover = false,\n  animateOnTap = false,\n  animateOnView = false,\n  animateOnViewMargin = '0px',\n  animateOnViewOnce = true,\n  animation = 'default',\n  loop = false,\n  loopDelay = 0,\n  initialOnAnimateEnd = false,\n  completeOnStop = false,\n  persistOnAnimateEnd = false,\n  delay = 0,\n  children,\n  ...props\n}: AnimateIconProps) {\n  const controls = useAnimation();\n\n  const [localAnimate, setLocalAnimate] = React.useState<boolean>(() => {\n    if (animate === undefined || animate === false) return false;\n    return delay <= 0;\n  });\n  const [currentAnimation, setCurrentAnimation] = React.useState<\n    string | StaticAnimations\n  >(typeof animate === 'string' ? animate : animation);\n  const [status, setStatus] = React.useState<'initial' | 'animate'>('initial');\n\n  const delayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n  const loopDelayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n  const isAnimateInProgressRef = React.useRef<boolean>(false);\n  const animateEndPromiseRef = React.useRef<Promise<void> | null>(null);\n  const resolveAnimateEndRef = React.useRef<(() => void) | null>(null);\n  const activeRef = React.useRef<boolean>(localAnimate);\n\n  const runGenRef = React.useRef(0);\n  const cancelledRef = React.useRef(false);\n\n  const bumpGeneration = React.useCallback(() => {\n    runGenRef.current++;\n  }, []);\n\n  const startAnimation = React.useCallback(\n    (trigger: TriggerProp) => {\n      const next = typeof trigger === 'string' ? trigger : animation;\n      bumpGeneration();\n      if (delayRef.current) {\n        clearTimeout(delayRef.current);\n        delayRef.current = null;\n      }\n      setCurrentAnimation(next);\n      if (delay > 0) {\n        setLocalAnimate(false);\n        delayRef.current = setTimeout(() => {\n          setLocalAnimate(true);\n        }, delay);\n      } else {\n        setLocalAnimate(true);\n      }\n    },\n    [animation, delay, bumpGeneration],\n  );\n\n  const stopAnimation = React.useCallback(() => {\n    bumpGeneration();\n    if (delayRef.current) {\n      clearTimeout(delayRef.current);\n      delayRef.current = null;\n    }\n    if (loopDelayRef.current) {\n      clearTimeout(loopDelayRef.current);\n      loopDelayRef.current = null;\n    }\n    setLocalAnimate(false);\n  }, [bumpGeneration]);\n\n  React.useEffect(() => {\n    activeRef.current = localAnimate;\n  }, [localAnimate]);\n\n  React.useEffect(() => {\n    if (animate === undefined) return;\n    setCurrentAnimation(typeof animate === 'string' ? animate : animation);\n    if (animate) startAnimation(animate as TriggerProp);\n    else stopAnimation();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [animate]);\n\n  React.useEffect(() => {\n    return () => {\n      if (delayRef.current) clearTimeout(delayRef.current);\n      if (loopDelayRef.current) clearTimeout(loopDelayRef.current);\n    };\n  }, []);\n\n  const viewOuterRef = React.useRef<HTMLElement>(null);\n  const { ref: inViewRef, isInView } = useIsInView(viewOuterRef, {\n    inView: !!animateOnView,\n    inViewOnce: animateOnViewOnce,\n    inViewMargin: animateOnViewMargin,\n  });\n\n  const startAnim = React.useCallback(\n    async (anim: 'initial' | 'animate', method: 'start' | 'set' = 'start') => {\n      try {\n        await controls[method](anim);\n        setStatus(anim);\n      } catch {\n        return;\n      }\n    },\n    [controls],\n  );\n\n  React.useEffect(() => {\n    if (!animateOnView) return;\n    if (isInView) startAnimation(animateOnView);\n    else stopAnimation();\n  }, [isInView, animateOnView, startAnimation, stopAnimation]);\n\n  React.useEffect(() => {\n    const gen = ++runGenRef.current;\n    cancelledRef.current = false;\n\n    async function run() {\n      if (cancelledRef.current || gen !== runGenRef.current) {\n        await startAnim('initial');\n        return;\n      }\n\n      if (!localAnimate) {\n        if (\n          completeOnStop &&\n          isAnimateInProgressRef.current &&\n          animateEndPromiseRef.current\n        ) {\n          try {\n            await animateEndPromiseRef.current;\n          } catch {\n            // noop\n          }\n        }\n        if (!persistOnAnimateEnd) {\n          if (cancelledRef.current || gen !== runGenRef.current) {\n            await startAnim('initial');\n            return;\n          }\n          await startAnim('initial');\n        }\n        return;\n      }\n\n      if (loop) {\n        if (cancelledRef.current || gen !== runGenRef.current) {\n          await startAnim('initial');\n          return;\n        }\n        await startAnim('initial', 'set');\n      }\n\n      isAnimateInProgressRef.current = true;\n      animateEndPromiseRef.current = new Promise<void>((resolve) => {\n        resolveAnimateEndRef.current = resolve;\n      });\n\n      if (cancelledRef.current || gen !== runGenRef.current) {\n        isAnimateInProgressRef.current = false;\n        resolveAnimateEndRef.current?.();\n        resolveAnimateEndRef.current = null;\n        animateEndPromiseRef.current = null;\n        await startAnim('initial');\n        return;\n      }\n\n      await startAnim('animate');\n\n      if (cancelledRef.current || gen !== runGenRef.current) {\n        isAnimateInProgressRef.current = false;\n        resolveAnimateEndRef.current?.();\n        resolveAnimateEndRef.current = null;\n        animateEndPromiseRef.current = null;\n        await startAnim('initial');\n        return;\n      }\n\n      isAnimateInProgressRef.current = false;\n      resolveAnimateEndRef.current?.();\n      resolveAnimateEndRef.current = null;\n      animateEndPromiseRef.current = null;\n\n      if (initialOnAnimateEnd) {\n        if (cancelledRef.current || gen !== runGenRef.current) {\n          await startAnim('initial');\n          return;\n        }\n        await startAnim('initial', 'set');\n      }\n\n      if (loop) {\n        if (loopDelay > 0) {\n          await new Promise<void>((resolve) => {\n            loopDelayRef.current = setTimeout(() => {\n              loopDelayRef.current = null;\n              resolve();\n            }, loopDelay);\n          });\n\n          if (cancelledRef.current || gen !== runGenRef.current) {\n            await startAnim('initial');\n            return;\n          }\n          if (!activeRef.current) {\n            if (status !== 'initial' && !persistOnAnimateEnd)\n              await startAnim('initial');\n            return;\n          }\n        } else {\n          if (!activeRef.current) {\n            if (status !== 'initial' && !persistOnAnimateEnd)\n              await startAnim('initial');\n            return;\n          }\n        }\n        if (cancelledRef.current || gen !== runGenRef.current) {\n          await startAnim('initial');\n          return;\n        }\n        await run();\n      }\n    }\n\n    void run();\n\n    return () => {\n      cancelledRef.current = true;\n      if (delayRef.current) {\n        clearTimeout(delayRef.current);\n        delayRef.current = null;\n      }\n      if (loopDelayRef.current) {\n        clearTimeout(loopDelayRef.current);\n        loopDelayRef.current = null;\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [localAnimate, controls]);\n\n  const childProps = (\n    React.isValidElement(children) ? (children as React.ReactElement).props : {}\n  ) as AnyProps;\n\n  const handleMouseEnter = composeEventHandlers<React.MouseEvent<HTMLElement>>(\n    childProps.onMouseEnter,\n    () => {\n      if (animateOnHover) startAnimation(animateOnHover);\n    },\n  );\n\n  const handleMouseLeave = composeEventHandlers<React.MouseEvent<HTMLElement>>(\n    childProps.onMouseLeave,\n    () => {\n      if (animateOnHover || animateOnTap) stopAnimation();\n    },\n  );\n\n  const handlePointerDown = composeEventHandlers<\n    React.PointerEvent<HTMLElement>\n  >(childProps.onPointerDown, () => {\n    if (animateOnTap) startAnimation(animateOnTap);\n  });\n\n  const handlePointerUp = composeEventHandlers<React.PointerEvent<HTMLElement>>(\n    childProps.onPointerUp,\n    () => {\n      if (animateOnTap) stopAnimation();\n    },\n  );\n\n  const content = asChild ? (\n    <Slot\n      ref={inViewRef}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      onPointerDown={handlePointerDown}\n      onPointerUp={handlePointerUp}\n      {...props}\n    >\n      {children}\n    </Slot>\n  ) : (\n    <motion.span\n      ref={inViewRef}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      onPointerDown={handlePointerDown}\n      onPointerUp={handlePointerUp}\n      {...props}\n    >\n      {children}\n    </motion.span>\n  );\n\n  return (\n    <AnimateIconContext.Provider\n      value={{\n        controls,\n        animation: currentAnimation,\n        loop,\n        loopDelay,\n        active: localAnimate,\n        animate,\n        initialOnAnimateEnd,\n        completeOnStop,\n        delay,\n      }}\n    >\n      {content}\n    </AnimateIconContext.Provider>\n  );\n}\n\nconst pathClassName =\n  \"[&_[stroke-dasharray='1px_1px']]:![stroke-dasharray:1px_0px]\";\n\nfunction IconWrapper<T extends string>({\n  size = 28,\n  animation: animationProp,\n  animate,\n  animateOnHover,\n  animateOnTap,\n  animateOnView,\n  animateOnViewMargin,\n  animateOnViewOnce,\n  icon: IconComponent,\n  loop,\n  loopDelay,\n  persistOnAnimateEnd,\n  initialOnAnimateEnd,\n  delay,\n  completeOnStop,\n  className,\n  ...props\n}: IconWrapperProps<T>) {\n  const context = React.useContext(AnimateIconContext);\n\n  if (context) {\n    const {\n      controls,\n      animation: parentAnimation,\n      loop: parentLoop,\n      loopDelay: parentLoopDelay,\n      active: parentActive,\n      animate: parentAnimate,\n      persistOnAnimateEnd: parentPersistOnAnimateEnd,\n      initialOnAnimateEnd: parentInitialOnAnimateEnd,\n      delay: parentDelay,\n      completeOnStop: parentCompleteOnStop,\n    } = context;\n\n    const hasOverrides =\n      animate !== undefined ||\n      animateOnHover !== undefined ||\n      animateOnTap !== undefined ||\n      animateOnView !== undefined ||\n      loop !== undefined ||\n      loopDelay !== undefined ||\n      initialOnAnimateEnd !== undefined ||\n      persistOnAnimateEnd !== undefined ||\n      delay !== undefined ||\n      completeOnStop !== undefined;\n\n    if (hasOverrides) {\n      const inheritedAnimate: Trigger = parentActive\n        ? (animationProp ?? parentAnimation ?? 'default')\n        : false;\n\n      const finalAnimate: Trigger = (animate ??\n        parentAnimate ??\n        inheritedAnimate) as Trigger;\n\n      return (\n        <AnimateIcon\n          animate={finalAnimate}\n          animateOnHover={animateOnHover}\n          animateOnTap={animateOnTap}\n          animateOnView={animateOnView}\n          animateOnViewMargin={animateOnViewMargin}\n          animateOnViewOnce={animateOnViewOnce}\n          animation={animationProp ?? parentAnimation}\n          loop={loop ?? parentLoop}\n          loopDelay={loopDelay ?? parentLoopDelay}\n          persistOnAnimateEnd={persistOnAnimateEnd ?? parentPersistOnAnimateEnd}\n          initialOnAnimateEnd={initialOnAnimateEnd ?? parentInitialOnAnimateEnd}\n          delay={delay ?? parentDelay}\n          completeOnStop={completeOnStop ?? parentCompleteOnStop}\n          asChild\n        >\n          <IconComponent\n            size={size}\n            className={cn(\n              className,\n              ((animationProp ?? parentAnimation) === 'path' ||\n                (animationProp ?? parentAnimation) === 'path-loop') &&\n                pathClassName,\n            )}\n            {...props}\n          />\n        </AnimateIcon>\n      );\n    }\n\n    const animationToUse = animationProp ?? parentAnimation;\n    const loopToUse = parentLoop;\n    const loopDelayToUse = parentLoopDelay;\n\n    return (\n      <AnimateIconContext.Provider\n        value={{\n          controls,\n          animation: animationToUse,\n          loop: loopToUse,\n          loopDelay: loopDelayToUse,\n          active: parentActive,\n          animate: parentAnimate,\n          initialOnAnimateEnd: parentInitialOnAnimateEnd,\n          delay: parentDelay,\n          completeOnStop: parentCompleteOnStop,\n        }}\n      >\n        <IconComponent\n          size={size}\n          className={cn(\n            className,\n            (animationToUse === 'path' || animationToUse === 'path-loop') &&\n              pathClassName,\n          )}\n          {...props}\n        />\n      </AnimateIconContext.Provider>\n    );\n  }\n\n  if (\n    animate !== undefined ||\n    animateOnHover !== undefined ||\n    animateOnTap !== undefined ||\n    animateOnView !== undefined ||\n    animationProp !== undefined\n  ) {\n    return (\n      <AnimateIcon\n        animate={animate}\n        animateOnHover={animateOnHover}\n        animateOnTap={animateOnTap}\n        animateOnView={animateOnView}\n        animateOnViewMargin={animateOnViewMargin}\n        animateOnViewOnce={animateOnViewOnce}\n        animation={animationProp}\n        loop={loop}\n        loopDelay={loopDelay}\n        delay={delay}\n        completeOnStop={completeOnStop}\n        asChild\n      >\n        <IconComponent\n          size={size}\n          className={cn(\n            className,\n            (animationProp === 'path' || animationProp === 'path-loop') &&\n              pathClassName,\n          )}\n          {...props}\n        />\n      </AnimateIcon>\n    );\n  }\n\n  return (\n    <IconComponent\n      size={size}\n      className={cn(\n        className,\n        (animationProp === 'path' || animationProp === 'path-loop') &&\n          pathClassName,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction getVariants<\n  V extends { default: T; [key: string]: T },\n  T extends Record<string, Variants>,\n>(animations: V): T {\n  // eslint-disable-next-line react-hooks/rules-of-hooks\n  const { animation: animationType } = useAnimateIconContext();\n\n  let result: T;\n\n  if (animationType in staticAnimations) {\n    const variant = staticAnimations[animationType as StaticAnimations];\n    result = {} as T;\n    for (const key in animations.default) {\n      if (\n        (animationType === 'path' || animationType === 'path-loop') &&\n        key.includes('group')\n      )\n        continue;\n      result[key] = variant as T[Extract<keyof T, string>];\n    }\n  } else {\n    result = (animations[animationType as keyof V] as T) ?? animations.default;\n  }\n\n  return result;\n}\n\nexport {\n  pathClassName,\n  staticAnimations,\n  AnimateIcon,\n  IconWrapper,\n  useAnimateIconContext,\n  getVariants,\n  type IconProps,\n  type IconWrapperProps,\n  type AnimateIconProps,\n  type AnimateIconContextValue,\n};\n",
      "type": "registry:ui",
      "target": "components/odysseyui/icons/icon.tsx"
    }
  ],
  "type": "registry:ui"
}