{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cursor-primitive",
  "title": "Cursor",
  "description": "An animated cursor component that allows you to customize both the cursor and cursor follow elements with smooth animations.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@unlumen-ui/slot",
    "@unlumen-ui/lib-get-strict-context"
  ],
  "files": [
    {
      "path": "registry/primitives/cursor-primitive/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  motion,\n  useMotionValue,\n  useSpring,\n  AnimatePresence,\n  type HTMLMotionProps,\n  type SpringOptions,\n} from \"motion/react\";\n\nimport { getStrictContext } from \"@/lib/get-strict-context\";\nimport { Slot, type WithAsChild } from \"@/components/unlumen-ui/primitives/slot\";\n\ntype CursorContextType = {\n  cursorPos: { x: number; y: number };\n  active: boolean;\n  global: boolean;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  cursorRef: React.RefObject<HTMLDivElement | null>;\n};\n\nconst [LocalCursorProvider, useCursor] =\n  getStrictContext<CursorContextType>(\"CursorContext\");\n\ntype CursorProviderProps = {\n  children: React.ReactNode;\n  global?: boolean;\n};\n\nfunction CursorProvider({ children, global = false }: CursorProviderProps) {\n  const [cursorPos, setCursorPos] = React.useState({ x: 0, y: 0 });\n  const [active, setActive] = React.useState(false);\n\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const cursorRef = React.useRef<HTMLDivElement>(null);\n\n  React.useEffect(() => {\n    const id = \"__cursor_none_style__\";\n    if (document.getElementById(id)) return;\n\n    const style = document.createElement(\"style\");\n    style.id = id;\n    style.textContent = `\n      .animate-ui-cursor-none, .animate-ui-cursor-none * { cursor: none !important; }\n    `;\n    document.head.appendChild(style);\n  }, []);\n\n  React.useEffect(() => {\n    let removeListeners: () => void;\n\n    if (global) {\n      const handlePointerMove = (e: PointerEvent) => {\n        setCursorPos({ x: e.clientX, y: e.clientY });\n        setActive(true);\n      };\n\n      const handlePointerOut = (e: PointerEvent | MouseEvent) => {\n        if (e instanceof PointerEvent && e.relatedTarget === null) {\n          setActive(false);\n        }\n      };\n\n      const handleVisibilityChange = () => {\n        if (document.visibilityState === \"hidden\") setActive(false);\n      };\n\n      window.addEventListener(\"pointermove\", handlePointerMove, {\n        passive: true,\n      });\n      window.addEventListener(\"pointerout\", handlePointerOut, {\n        passive: true,\n      });\n      window.addEventListener(\"mouseout\", handlePointerOut, { passive: true });\n      document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n      removeListeners = () => {\n        window.removeEventListener(\"pointermove\", handlePointerMove);\n        window.removeEventListener(\"pointerout\", handlePointerOut);\n        window.removeEventListener(\"mouseout\", handlePointerOut);\n        document.removeEventListener(\n          \"visibilitychange\",\n          handleVisibilityChange,\n        );\n      };\n    } else {\n      if (!containerRef.current) return;\n\n      const parent = containerRef.current.parentElement;\n      if (!parent) return;\n\n      if (getComputedStyle(parent).position === \"static\") {\n        parent.style.position = \"relative\";\n      }\n\n      const handlePointerMove = (e: PointerEvent) => {\n        const rect = parent.getBoundingClientRect();\n        setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n        setActive(true);\n      };\n\n      const handlePointerOut = (e: PointerEvent | MouseEvent) => {\n        if (\n          e.relatedTarget === null ||\n          !(parent as Node).contains(e.relatedTarget as Node)\n        ) {\n          setActive(false);\n        }\n      };\n\n      parent.addEventListener(\"pointermove\", handlePointerMove, {\n        passive: true,\n      });\n      parent.addEventListener(\"pointerout\", handlePointerOut, {\n        passive: true,\n      });\n      parent.addEventListener(\"mouseout\", handlePointerOut, { passive: true });\n\n      removeListeners = () => {\n        parent.removeEventListener(\"pointermove\", handlePointerMove);\n        parent.removeEventListener(\"pointerout\", handlePointerOut);\n        parent.removeEventListener(\"mouseout\", handlePointerOut);\n      };\n    }\n\n    return removeListeners;\n  }, [global]);\n\n  return (\n    <LocalCursorProvider\n      value={{ cursorPos, active, global, containerRef, cursorRef }}\n    >\n      {children}\n    </LocalCursorProvider>\n  );\n}\n\ntype CursorContainerProps = WithAsChild<HTMLMotionProps<\"div\">>;\n\nfunction CursorContainer({\n  ref,\n  asChild = false,\n  ...props\n}: CursorContainerProps) {\n  const { containerRef, global, active } = useCursor();\n  React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <Component\n      ref={containerRef}\n      data-slot=\"cursor-container\"\n      data-global={global}\n      data-active={active}\n      {...props}\n    />\n  );\n}\n\ntype CursorProps = WithAsChild<\n  HTMLMotionProps<\"div\"> & {\n    children: React.ReactNode;\n  }\n>;\n\nfunction Cursor({ ref, asChild = false, style, ...props }: CursorProps) {\n  const { cursorPos, active, containerRef, cursorRef, global } = useCursor();\n  React.useImperativeHandle(ref, () => cursorRef.current as HTMLDivElement);\n\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n\n  React.useEffect(() => {\n    const target = global\n      ? document.documentElement\n      : containerRef.current?.parentElement;\n\n    if (!target) return;\n\n    if (active) {\n      target.classList.add(\"animate-ui-cursor-none\");\n    } else {\n      target.classList.remove(\"animate-ui-cursor-none\");\n    }\n\n    return () => {\n      target.classList.remove(\"animate-ui-cursor-none\");\n    };\n  }, [active, global, containerRef]);\n\n  React.useEffect(() => {\n    x.set(cursorPos.x);\n    y.set(cursorPos.y);\n  }, [cursorPos, x, y]);\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <AnimatePresence>\n      {active && (\n        <Component\n          ref={cursorRef}\n          data-slot=\"cursor\"\n          data-global={global}\n          data-active={active}\n          style={{\n            transform: \"translate(-50%,-50%)\",\n            pointerEvents: \"none\",\n            zIndex: 9999,\n            position: global ? \"fixed\" : \"absolute\",\n            top: y,\n            left: x,\n            ...style,\n          }}\n          initial={{ scale: 0, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          exit={{ scale: 0, opacity: 0 }}\n          {...props}\n        />\n      )}\n    </AnimatePresence>\n  );\n}\n\ntype CursorFollowSide = \"top\" | \"right\" | \"bottom\" | \"left\";\ntype CursorFollowAlign = \"start\" | \"center\" | \"end\";\n\ntype CursorFollowProps = WithAsChild<\n  Omit<HTMLMotionProps<\"div\">, \"transition\"> & {\n    side?: CursorFollowSide;\n    sideOffset?: number;\n    align?: CursorFollowAlign;\n    alignOffset?: number;\n    transition?: SpringOptions;\n    children: React.ReactNode;\n  }\n>;\n\nfunction CursorFollow({\n  ref,\n  asChild = false,\n  side = \"bottom\",\n  sideOffset = 0,\n  align = \"end\",\n  alignOffset = 0,\n  style,\n  transition = { stiffness: 500, damping: 50, bounce: 0 },\n  ...props\n}: CursorFollowProps) {\n  const { cursorPos, active, cursorRef, global } = useCursor();\n  const cursorFollowRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(\n    ref,\n    () => cursorFollowRef.current as HTMLDivElement,\n  );\n\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n\n  const springX = useSpring(x, transition);\n  const springY = useSpring(y, transition);\n\n  const calculateOffset = React.useCallback(() => {\n    const rect = cursorFollowRef.current?.getBoundingClientRect();\n    const width = rect?.width ?? 0;\n    const height = rect?.height ?? 0;\n\n    let offsetX = 0;\n    let offsetY = 0;\n\n    switch (side) {\n      case \"top\":\n        offsetY = height + sideOffset;\n        switch (align) {\n          case \"start\":\n            offsetX = width + alignOffset;\n            break;\n          case \"center\":\n            offsetX = width / 2;\n            break;\n          case \"end\":\n            offsetX = -alignOffset;\n            break;\n        }\n        break;\n\n      case \"bottom\":\n        offsetY = -sideOffset;\n        switch (align) {\n          case \"start\":\n            offsetX = width + alignOffset;\n            break;\n          case \"center\":\n            offsetX = width / 2;\n            break;\n          case \"end\":\n            offsetX = -alignOffset;\n            break;\n        }\n        break;\n\n      case \"left\":\n        offsetX = width + sideOffset;\n        switch (align) {\n          case \"start\":\n            offsetY = height + alignOffset;\n            break;\n          case \"center\":\n            offsetY = height / 2;\n            break;\n          case \"end\":\n            offsetY = -alignOffset;\n            break;\n        }\n        break;\n\n      case \"right\":\n        offsetX = -sideOffset;\n        switch (align) {\n          case \"start\":\n            offsetY = height + alignOffset;\n            break;\n          case \"center\":\n            offsetY = height / 2;\n            break;\n          case \"end\":\n            offsetY = -alignOffset;\n            break;\n        }\n        break;\n    }\n\n    return { x: offsetX, y: offsetY };\n  }, [side, align, sideOffset, alignOffset]);\n\n  React.useEffect(() => {\n    const offset = calculateOffset();\n    const cursorRect = cursorRef.current?.getBoundingClientRect();\n    const cursorWidth = cursorRect?.width ?? 20;\n    const cursorHeight = cursorRect?.height ?? 20;\n\n    x.set(cursorPos.x - offset.x + cursorWidth / 2);\n    y.set(cursorPos.y - offset.y + cursorHeight / 2);\n  }, [calculateOffset, cursorPos, cursorRef, x, y]);\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <AnimatePresence>\n      {active && (\n        <Component\n          ref={cursorFollowRef}\n          data-slot=\"cursor-follow\"\n          data-global={global}\n          data-active={active}\n          style={{\n            transform: \"translate(-50%,-50%)\",\n            pointerEvents: \"none\",\n            zIndex: 9998,\n            position: global ? \"fixed\" : \"absolute\",\n            top: springY,\n            left: springX,\n            ...style,\n          }}\n          initial={{ scale: 0, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          exit={{ scale: 0, opacity: 0 }}\n          {...props}\n        />\n      )}\n    </AnimatePresence>\n  );\n}\n\nexport {\n  CursorProvider,\n  Cursor,\n  CursorContainer,\n  CursorFollow,\n  useCursor,\n  type CursorProviderProps,\n  type CursorProps,\n  type CursorContainerProps,\n  type CursorFollowProps,\n  type CursorFollowAlign,\n  type CursorFollowSide,\n  type CursorContextType,\n};\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/cursor.tsx"
    }
  ],
  "type": "registry:ui"
}