{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "icon",
  "dependencies": [
    "class-variance-authority",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/watermelon-ui/icon.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { motion, AnimatePresence, type HTMLMotionProps, isMotionComponent, type UseInViewOptions, useInView } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  \"flex items-center justify-center rounded-md transition-[box-shadow,_color,_background-color,_border-color,_outline-color,_text-decoration-color,_fill,_stroke] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',\n        accent: 'bg-accent text-accent-foreground shadow-xs hover:bg-accent/90',\n        destructive:\n          'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default: 'size-9',\n        xs: \"size-7 [&_svg:not([class*='size-'])]:size-3.5 rounded-md\",\n        sm: 'size-8 rounded-md',\n        lg: 'size-10 rounded-md',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\ntype IconButtonProps = Omit<ButtonPrimitiveProps, 'asChild'> &\n  VariantProps<typeof buttonVariants> & {\n    children?: React.ReactNode;\n  };\n\nfunction IconButton({\n  className,\n  onClick,\n  variant,\n  size,\n  children,\n  ...props\n}: IconButtonProps) {\n  const [isActive, setIsActive] = React.useState(false);\n  const [key, setKey] = React.useState(0);\n\n  return (\n    <Particles asChild animate={isActive} key={key}>\n      <ButtonPrimitive\n        data-slot=\"icon-button\"\n        className={cn(buttonVariants({ variant, size, className }))}\n        onClick={(e) => {\n          setKey((prev) => prev + 1);\n          setIsActive(true);\n          onClick?.(e);\n        }}\n        {...props}\n      >\n        {children}\n        <ParticlesEffect\n          data-variant={variant}\n          className=\"bg-neutral-500 size-1 rounded-full\"\n        />\n      </ButtonPrimitive>\n    </Particles>\n  );\n}\n\nexport { IconButton, buttonVariants, type IconButtonProps };\n\n\n\ntype Side = 'top' | 'bottom' | 'left' | 'right';\ntype Align = 'start' | 'center' | 'end';\n\ntype ParticlesContextType = {\n  animate: boolean;\n  isInView: boolean;\n};\n\nconst [ParticlesProvider, useParticles] =\n  getStrictContext<ParticlesContextType>('ParticlesContext');\n\ntype ParticlesProps = WithAsChild<\n  Omit<HTMLMotionProps<'div'>, 'children'> & {\n    animate?: boolean;\n    children: React.ReactNode;\n  } & UseIsInViewOptions\n>;\n\nfunction Particles({\n  ref,\n  animate = true,\n  asChild = false,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  children,\n  style,\n  ...props\n}: ParticlesProps) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLDivElement>,\n    { inView, inViewOnce, inViewMargin },\n  );\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <ParticlesProvider value={{ animate, isInView }}>\n      <Component\n        ref={localRef}\n        style={{ position: 'relative', ...style }}\n        {...props}\n      >\n        {children}\n      </Component>\n    </ParticlesProvider>\n  );\n}\n\ntype ParticlesEffectProps = Omit<HTMLMotionProps<'div'>, 'children'> & {\n  side?: Side;\n  align?: Align;\n  count?: number;\n  radius?: number;\n  spread?: number;\n  duration?: number;\n  holdDelay?: number;\n  sideOffset?: number;\n  alignOffset?: number;\n  delay?: number;\n};\n\nfunction ParticlesEffect({\n  side = 'top',\n  align = 'center',\n  count = 6,\n  radius = 30,\n  spread = 360,\n  duration = 0.8,\n  holdDelay = 0.05,\n  sideOffset = 0,\n  alignOffset = 0,\n  delay = 0,\n  transition,\n  style,\n  ...props\n}: ParticlesEffectProps) {\n  const { animate, isInView } = useParticles();\n\n  const isVertical = side === 'top' || side === 'bottom';\n  const alignPct = align === 'start' ? '0%' : align === 'end' ? '100%' : '50%';\n\n  const top = isVertical\n    ? side === 'top'\n      ? `calc(0% - ${sideOffset}px)`\n      : `calc(100% + ${sideOffset}px)`\n    : `calc(${alignPct} + ${alignOffset}px)`;\n\n  const left = isVertical\n    ? `calc(${alignPct} + ${alignOffset}px)`\n    : side === 'left'\n      ? `calc(0% - ${sideOffset}px)`\n      : `calc(100% + ${sideOffset}px)`;\n\n  const containerStyle: React.CSSProperties = {\n    position: 'absolute',\n    top,\n    left,\n    transform: 'translate(-50%, -50%)',\n  };\n\n  const angleStep = (spread * (Math.PI / 180)) / Math.max(1, count - 1);\n\n  return (\n    <AnimatePresence>\n      {animate &&\n        isInView &&\n        [...Array(count)].map((_, i) => {\n          const angle = i * angleStep;\n          const x = Math.cos(angle) * radius;\n          const y = Math.sin(angle) * radius;\n\n          return (\n            <motion.div\n              key={i}\n              style={{ ...containerStyle, ...style }}\n              initial={{ scale: 0, opacity: 0 }}\n              animate={{\n                x: `${x}px`,\n                y: `${y}px`,\n                scale: [0, 1, 0],\n                opacity: [0, 1, 0],\n              }}\n              transition={{\n                duration,\n                delay: delay + i * holdDelay,\n                ease: 'easeOut',\n                ...transition,\n              }}\n              {...props}\n            />\n          );\n        })}\n    </AnimatePresence>\n  );\n}\n\ntype AnyProps = Record<string, unknown>;\n\ntype DOMMotionProps<T extends HTMLElement = HTMLElement> = Omit<\n  HTMLMotionProps<keyof HTMLElementTagNameMap>,\n  'ref'\n> & { ref?: React.Ref<T> };\n\ntype WithAsChild<Base extends object> =\n  | (Base & { asChild: true; children: React.ReactElement })\n  | (Base & { asChild?: false | undefined });\n\ntype SlotProps<T extends HTMLElement = HTMLElement> = {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  children?: any;\n} & DOMMotionProps<T>;\n\nfunction mergeRefs<T>(\n  ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n  return (node) => {\n    refs.forEach((ref) => {\n      if (!ref) return;\n      if (typeof ref === 'function') {\n        ref(node);\n      } else {\n        (ref as React.RefObject<T | null>).current = node;\n      }\n    });\n  };\n}\n\nfunction mergeProps<T extends HTMLElement>(\n  childProps: AnyProps,\n  slotProps: DOMMotionProps<T>,\n): AnyProps {\n  const merged: AnyProps = { ...childProps, ...slotProps };\n\n  if (childProps.className || slotProps.className) {\n    merged.className = cn(\n      childProps.className as string,\n      slotProps.className as string,\n    );\n  }\n\n  if (childProps.style || slotProps.style) {\n    merged.style = {\n      ...(childProps.style as React.CSSProperties),\n      ...(slotProps.style as React.CSSProperties),\n    };\n  }\n\n  return merged;\n}\n\nfunction Slot<T extends HTMLElement = HTMLElement>({\n  children,\n  ref,\n  ...props\n}: SlotProps<T>) {\n  const isAlreadyMotion =\n    typeof children.type === 'object' &&\n    children.type !== null &&\n    isMotionComponent(children.type);\n\n  const Base = React.useMemo(\n    () =>\n      isAlreadyMotion\n        ? (children.type as React.ElementType)\n        : motion.create(children.type as React.ElementType),\n    [isAlreadyMotion, children.type],\n  );\n\n  if (!React.isValidElement(children)) return null;\n\n  const { ref: childRef, ...childProps } = children.props as AnyProps;\n\n  const mergedProps = mergeProps(childProps, props);\n\n  return (\n    // eslint-disable-next-line react-hooks/static-components\n    <Base {...mergedProps} ref={mergeRefs(childRef as React.Ref<T>, ref)} />\n  );\n}\n\n\ninterface UseIsInViewOptions {\n  inView?: boolean;\n  inViewOnce?: boolean;\n  inViewMargin?: UseInViewOptions['margin'];\n}\n\nfunction useIsInView<T extends HTMLElement = HTMLElement>(\n  ref: React.Ref<T>,\n  options: UseIsInViewOptions = {},\n) {\n  const { inView, inViewOnce = false, inViewMargin = '0px' } = options;\n  const localRef = React.useRef<T>(null);\n  React.useImperativeHandle(ref, () => localRef.current as T);\n  const inViewResult = useInView(localRef, {\n    once: inViewOnce,\n    margin: inViewMargin,\n  });\n  const isInView = !inView || inViewResult;\n  return { ref: localRef, isInView };\n}\n\n\nfunction getStrictContext<T>(\n  name?: string,\n): readonly [\n  ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => React.JSX.Element,\n  () => T,\n] {\n  const Context = React.createContext<T | undefined>(undefined);\n\n  const Provider = ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => <Context.Provider value={value}>{children}</Context.Provider>;\n\n  const useSafeContext = () => {\n    const ctx = React.useContext(Context);\n    if (ctx === undefined) {\n      throw new Error(`useContext must be used within ${name ?? 'a Provider'}`);\n    }\n    return ctx;\n  };\n\n  return [Provider, useSafeContext] as const;\n}\n\n\ntype ButtonPrimitiveProps = WithAsChild<\n  HTMLMotionProps<'button'> & {\n    hoverScale?: number;\n    tapScale?: number;\n  }\n>;\n\nfunction ButtonPrimitive({\n  hoverScale = 1.05,\n  tapScale = 0.95,\n  asChild = false,\n  ...props\n}: ButtonPrimitiveProps) {\n  const Component = asChild ? Slot : motion.button;\n\n  return (\n    <Component\n      whileTap={{ scale: tapScale }}\n      whileHover={{ scale: hoverScale }}\n      {...props}\n    />\n  );\n}\n\n",
      "type": "registry:ui",
      "target": "components/ui/icon.tsx"
    }
  ],
  "type": "registry:ui"
}
