{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "text-focus", "title": "Text Focus", "description": "A text element that focuses on hover.", "dependencies": [ "motion" ], "files": [ { "path": "registry/new-york/components/text-focus/text-focus.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { motion } from \"motion/react\";\n\nexport interface TextFocusProps {\n sentence?: string;\n borderColor?: string;\n blurAmount?: number;\n}\n\ninterface FocusRectangle {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst CORNERS = [\n \"-top-2.5 -left-2.5 border-r-0 border-b-0\",\n \"-top-2.5 -right-2.5 border-l-0 border-b-0\",\n \"-bottom-2.5 -left-2.5 border-r-0 border-t-0\",\n \"-bottom-2.5 -right-2.5 border-l-0 border-t-0\",\n];\n\nconst TextFocus = ({\n sentence = \"All Eyes on Me\",\n borderColor = \"#22d3ee\",\n blurAmount = 3,\n}: TextFocusProps) => {\n const words = sentence.split(\" \");\n\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const containerRef = useRef(null);\n const wordRefs = useRef<(HTMLSpanElement | null)[]>([]);\n\n const [focusRect, setFocusRect] = useState({\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n });\n\n // Measure active word and position the focus frame around it\n const updateFocusRect = useCallback((index: number) => {\n const wordEl = wordRefs.current[index];\n const containerEl = containerRef.current;\n\n if (!wordEl || !containerEl) return;\n\n const wordRect = wordEl.getBoundingClientRect();\n const containerRect = containerEl.getBoundingClientRect();\n\n setFocusRect({\n x: wordRect.left - containerRect.left - 2,\n y: wordRect.top - containerRect.top - 2,\n width: wordRect.width + 4,\n height: wordRect.height + 4,\n });\n }, []);\n\n const activeIndex = Math.min(currentIndex, words.length - 1);\n\n useLayoutEffect(() => {\n updateFocusRect(activeIndex);\n }, [activeIndex, sentence, updateFocusRect]);\n\n // Keep measurements correct when layout changes\n useEffect(() => {\n const handleResize = () => updateFocusRect(activeIndex);\n const observer = typeof ResizeObserver !== \"undefined\" && containerRef.current\n ? new ResizeObserver(() => handleResize())\n : null;\n\n window.addEventListener(\"resize\", handleResize);\n if (observer && containerRef.current) {\n observer.observe(containerRef.current);\n }\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n observer?.disconnect();\n };\n }, [activeIndex, updateFocusRect]);\n\n const cornerStyle: React.CSSProperties = {\n borderColor: \"var(--border-color)\",\n filter: \"drop-shadow(0 0 4px var(--border-color))\",\n };\n\n return (\n \n \n {CORNERS.map((corner, index) => (\n \n ))}\n \n\n {words.map((word, index) => (\n {\n wordRefs.current[index] = el;\n }}\n onMouseEnter={() => setCurrentIndex(index)}\n className=\"transition-all duration-300\"\n style={{\n filter:\n index === activeIndex\n ? \"blur(0px)\"\n : `blur(${blurAmount}px)`,\n }}\n >\n {word}\n \n ))}\n \n );\n};\n\nexport default TextFocus;\n" } ], "type": "registry:component" }