Files
zeekay b836586a64 fix: Include all component types in registry build
- Updated build-registry.mts to include all component types (ui, ai, 3d, animation, code, etc.)
- Fixed missing components issue by processing all components that start with 'components:'
- Now properly handles components that only exist in one theme variant
- Registry now contains 363 components (up from 117)
- AI, 3D, animation, and code components are now accessible via the registry
2025-10-16 22:33:38 -07:00

14 lines
10 KiB
JSON

{
"name": "animated-cursor",
"description": "Custom animated cursor",
"dependencies": [
"motion"
],
"files": [
{
"name": "animated-cursor.tsx",
"content": "\"use client\"\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedCursorProps {\n /**\n * Whether the cursor is visible\n */\n isVisible?: boolean\n /**\n * Cursor size in pixels\n */\n size?: number\n /**\n * Main cursor color\n */\n color?: string\n /**\n * Trail cursor color\n */\n trailColor?: string\n /**\n * Animation duration in milliseconds\n */\n animationDuration?: number\n /**\n * Whether to show trail effect\n */\n showTrail?: boolean\n /**\n * Number of trail elements\n */\n trailLength?: number\n /**\n * Blend mode for cursor\n */\n blendMode?:\n | \"normal\"\n | \"multiply\"\n | \"screen\"\n | \"overlay\"\n | \"darken\"\n | \"lighten\"\n | \"color-dodge\"\n | \"color-burn\"\n | \"hard-light\"\n | \"soft-light\"\n | \"difference\"\n | \"exclusion\"\n /**\n * Scale factor on hover\n */\n hoverScale?: number\n /**\n * Whether to hide on touch devices\n */\n hideOnTouch?: boolean\n /**\n * Custom CSS class\n */\n className?: string\n /**\n * Click animation duration\n */\n clickAnimationDuration?: number\n /**\n * Z-index for cursor\n */\n zIndex?: number\n}\n\nexport interface CursorPosition {\n x: number\n y: number\n}\n\nexport interface CursorState {\n position: CursorPosition\n isHovering: boolean\n isClicking: boolean\n cursorType: \"default\" | \"pointer\" | \"text\" | \"grab\" | \"grabbing\"\n}\n\nconst INTERACTIVE_SELECTORS = [\n \"a\",\n \"button\",\n \"input\",\n \"textarea\",\n \"select\",\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[tabindex]:not([tabindex=\"-1\"])',\n \".cursor-pointer\",\n '[data-cursor=\"pointer\"]',\n].join(\", \")\n\nconst TEXT_SELECTORS = [\n 'input[type=\"text\"]',\n 'input[type=\"email\"]',\n 'input[type=\"password\"]',\n 'input[type=\"search\"]',\n \"textarea\",\n '[contenteditable=\"true\"]',\n '[data-cursor=\"text\"]',\n].join(\", \")\n\nconst GRAB_SELECTORS = ['[data-cursor=\"grab\"]', \".cursor-grab\"].join(\", \")\n\nconst GRABBING_SELECTORS = [\n '[data-cursor=\"grabbing\"]',\n \".cursor-grabbing\",\n].join(\", \")\n\n/**\n * Detects if the device supports touch\n */\nfunction isTouchDevice(): boolean {\n if (typeof window === \"undefined\") return false\n\n return (\n \"ontouchstart\" in window ||\n navigator.maxTouchPoints > 0 ||\n // @ts-ignore\n navigator.msMaxTouchPoints > 0\n )\n}\n\n/**\n * Custom hook for managing cursor state\n */\nfunction useCursorState() {\n const [cursorState, setCursorState] = useState<CursorState>({\n position: { x: 0, y: 0 },\n isHovering: false,\n isClicking: false,\n cursorType: \"default\",\n })\n\n const updatePosition = useCallback((x: number, y: number) => {\n setCursorState((prev) => ({\n ...prev,\n position: { x, y },\n }))\n }, [])\n\n const updateHoverState = useCallback(\n (\n isHovering: boolean,\n cursorType: CursorState[\"cursorType\"] = \"default\"\n ) => {\n setCursorState((prev) => ({\n ...prev,\n isHovering,\n cursorType,\n }))\n },\n []\n )\n\n const updateClickState = useCallback((isClicking: boolean) => {\n setCursorState((prev) => ({\n ...prev,\n isClicking,\n }))\n }, [])\n\n return {\n cursorState,\n updatePosition,\n updateHoverState,\n updateClickState,\n }\n}\n\n/**\n * Custom hook for managing cursor trail\n */\nfunction useCursorTrail(trailLength: number, position: CursorPosition) {\n const [trail, setTrail] = useState<CursorPosition[]>([])\n const trailRef = useRef<CursorPosition[]>([])\n\n useEffect(() => {\n trailRef.current.push(position)\n\n if (trailRef.current.length > trailLength) {\n trailRef.current.shift()\n }\n\n setTrail([...trailRef.current])\n }, [position, trailLength])\n\n return trail\n}\n\n/**\n * AnimatedCursor component providing customizable animated cursor with trail effects\n */\nexport function AnimatedCursor({\n isVisible = true,\n size = 20,\n color = \"#000000\",\n trailColor = \"#00000050\",\n animationDuration = 200,\n showTrail = true,\n trailLength = 8,\n blendMode = \"normal\",\n hoverScale = 1.5,\n hideOnTouch = true,\n className,\n clickAnimationDuration = 100,\n zIndex = 9999,\n}: AnimatedCursorProps) {\n const { cursorState, updatePosition, updateHoverState, updateClickState } =\n useCursorState()\n\n const trail = useCursorTrail(trailLength, cursorState.position)\n const [isTouch, setIsTouch] = useState(false)\n const [mounted, setMounted] = useState(false)\n\n // Check for touch device on mount\n useEffect(() => {\n setIsTouch(isTouchDevice())\n setMounted(true)\n }, [])\n\n // Mouse move handler\n useEffect(() => {\n if (!mounted || (hideOnTouch && isTouch)) return\n\n const handleMouseMove = (e: MouseEvent) => {\n try {\n requestAnimationFrame(() => {\n updatePosition(e.clientX, e.clientY)\n })\n } catch (error) {\n console.warn(\"AnimatedCursor: Error updating position\", error)\n }\n }\n\n const handleMouseOver = (e: MouseEvent) => {\n try {\n const target = e.target as HTMLElement\n\n if (target?.closest?.(INTERACTIVE_SELECTORS)) {\n updateHoverState(true, \"pointer\")\n } else if (target?.closest?.(TEXT_SELECTORS)) {\n updateHoverState(true, \"text\")\n } else if (target?.closest?.(GRAB_SELECTORS)) {\n updateHoverState(true, \"grab\")\n } else if (target?.closest?.(GRABBING_SELECTORS)) {\n updateHoverState(true, \"grabbing\")\n } else {\n updateHoverState(false, \"default\")\n }\n } catch (error) {\n console.warn(\"AnimatedCursor: Error handling mouse over\", error)\n updateHoverState(false, \"default\")\n }\n }\n\n const handleMouseDown = () => {\n updateClickState(true)\n }\n\n const handleMouseUp = () => {\n updateClickState(false)\n }\n\n const handleMouseLeave = () => {\n updateHoverState(false, \"default\")\n }\n\n document.addEventListener(\"mousemove\", handleMouseMove)\n document.addEventListener(\"mouseover\", handleMouseOver)\n document.addEventListener(\"mousedown\", handleMouseDown)\n document.addEventListener(\"mouseup\", handleMouseUp)\n document.addEventListener(\"mouseleave\", handleMouseLeave)\n\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove)\n document.removeEventListener(\"mouseover\", handleMouseOver)\n document.removeEventListener(\"mousedown\", handleMouseDown)\n document.removeEventListener(\"mouseup\", handleMouseUp)\n document.removeEventListener(\"mouseleave\", handleMouseLeave)\n }\n }, [\n mounted,\n hideOnTouch,\n isTouch,\n updatePosition,\n updateHoverState,\n updateClickState,\n ])\n\n // Hide default cursor\n useEffect(() => {\n if (!mounted || (hideOnTouch && isTouch)) return\n\n try {\n const originalCursor = document.body.style.cursor\n document.body.style.cursor = \"none\"\n\n return () => {\n try {\n document.body.style.cursor = originalCursor\n } catch (error) {\n console.warn(\"AnimatedCursor: Error restoring cursor style\", error)\n }\n }\n } catch (error) {\n console.warn(\"AnimatedCursor: Error hiding cursor\", error)\n }\n }, [mounted, hideOnTouch, isTouch])\n\n // Don't render on server or touch devices (if hideOnTouch is true)\n if (!mounted || !isVisible || (hideOnTouch && isTouch)) {\n return null\n }\n\n const currentScale = cursorState.isHovering ? hoverScale : 1\n const clickScale = cursorState.isClicking ? 0.8 : 1\n const finalScale = currentScale * clickScale\n\n const getCursorShape = () => {\n switch (cursorState.cursorType) {\n case \"text\":\n return \"h-6 w-0.5\"\n case \"grab\":\n case \"grabbing\":\n return \"rounded-md\"\n default:\n return \"rounded-full\"\n }\n }\n\n return (\n <div className={cn(\"pointer-events-none fixed inset-0\", className)}>\n {/* Trail */}\n {showTrail &&\n trail.map((point, index) => {\n const opacity = ((index + 1) / trail.length) * 0.5\n const scale = ((index + 1) / trail.length) * 0.8\n\n return (\n <div\n key={index}\n className=\"absolute\"\n style={{\n left: point.x - size / 2,\n top: point.y - size / 2,\n width: size,\n height: size,\n backgroundColor: trailColor,\n borderRadius: \"50%\",\n opacity,\n transform: `scale(${scale})`,\n zIndex: zIndex - index - 1,\n mixBlendMode: blendMode,\n }}\n />\n )\n })}\n\n {/* Main cursor */}\n <div\n className={cn(\n \"absolute transition-transform duration-100 ease-out\",\n getCursorShape()\n )}\n style={{\n left: cursorState.position.x - size / 2,\n top: cursorState.position.y - size / 2,\n width: cursorState.cursorType === \"text\" ? 2 : size,\n height: cursorState.cursorType === \"text\" ? 24 : size,\n backgroundColor: color,\n transform: `scale(${finalScale})`,\n transitionDuration: `${animationDuration}ms`,\n zIndex,\n mixBlendMode: blendMode,\n }}\n />\n\n {/* Hover ring */}\n {cursorState.isHovering && cursorState.cursorType !== \"text\" && (\n <div\n className=\"absolute rounded-full border-2 transition-all duration-200 ease-out\"\n style={{\n left: cursorState.position.x - (size * hoverScale) / 2,\n top: cursorState.position.y - (size * hoverScale) / 2,\n width: size * hoverScale,\n height: size * hoverScale,\n borderColor: color,\n opacity: 0.3,\n zIndex: zIndex - 1,\n mixBlendMode: blendMode,\n }}\n />\n )}\n </div>\n )\n}\n\nexport default AnimatedCursor\n"
}
],
"type": "components:animation"
}