From 17de7c45ac60d484e99686c11a852136418971ce Mon Sep 17 00:00:00 2001 From: KavanBhavsar35 Date: Sun, 9 Nov 2025 12:12:24 +0530 Subject: [PATCH] feat/true-focus: add support for custom words array prop for fine spacing control Added optional words prop to True Focus component Falls back to splitting sentence when words not provided Enables developers to control spacing precisely between words Maintains backward compatibility --- public/r/TrueFocus-JS-CSS.json | 2 +- public/r/TrueFocus-JS-TW.json | 2 +- public/r/TrueFocus-TS-CSS.json | 2 +- public/r/TrueFocus-TS-TW.json | 2 +- src/content/TextAnimations/TrueFocus/TrueFocus.jsx | 11 ++++++----- src/demo/TextAnimations/TrueFocusDemo.jsx | 7 +++++++ src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx | 11 ++++++----- .../TextAnimations/TrueFocus/TrueFocus.tsx | 12 +++++++----- .../TextAnimations/TrueFocus/TrueFocus.tsx | 12 +++++++----- 9 files changed, 37 insertions(+), 24 deletions(-) diff --git a/public/r/TrueFocus-JS-CSS.json b/public/r/TrueFocus-JS-CSS.json index 11873834..007f62a0 100644 --- a/public/r/TrueFocus-JS-CSS.json +++ b/public/r/TrueFocus-JS-CSS.json @@ -10,7 +10,7 @@ "files": [ { "path": "public/default/src/content/TextAnimations/TrueFocus/TrueFocus.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\nconst TrueFocus = ({\n sentence = 'True Focus',\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const words = sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs = useRef([]);\n const [focusRect, setFocusRect] = useState({ x: 0, y: 0, width: 0, height: 0 });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % words.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex].getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, words.length]);\n\n const handleMouseEnter = index => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex);\n }\n };\n\n return (\n
\n {words.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n (wordRefs.current[index] = el)}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={{\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n '--border-color': borderColor,\n '--glow-color': glowColor,\n transition: `filter ${animationDuration}s ease`\n }}\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={{\n '--border-color': borderColor,\n '--glow-color': glowColor\n }}\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", + "content": "import { useEffect, useRef, useState } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\nconst TrueFocus = ({\n sentence = 'True Focus',\n words,\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs = useRef([]);\n const [focusRect, setFocusRect] = useState({ x: 0, y: 0, width: 0, height: 0 });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % wordList.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex].getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, wordList.length]);\n\n const handleMouseEnter = index => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex);\n }\n };\n\n return (\n
\n {wordList.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n (wordRefs.current[index] = el)}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={{\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n '--border-color': borderColor,\n '--glow-color': glowColor,\n transition: `filter ${animationDuration}s ease`\n }}\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={{\n '--border-color': borderColor,\n '--glow-color': glowColor\n }}\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", "type": "registry:component" }, { diff --git a/public/r/TrueFocus-JS-TW.json b/public/r/TrueFocus-JS-TW.json index 77696c63..d781603c 100644 --- a/public/r/TrueFocus-JS-TW.json +++ b/public/r/TrueFocus-JS-TW.json @@ -10,7 +10,7 @@ "files": [ { "path": "public/tailwind/src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { motion } from 'motion/react';\n\nconst TrueFocus = ({\n sentence = 'True Focus',\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const words = sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs = useRef([]);\n const [focusRect, setFocusRect] = useState({ x: 0, y: 0, width: 0, height: 0 });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % words.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex].getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, words.length]);\n\n const handleMouseEnter = index => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex);\n }\n };\n\n return (\n
\n {words.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n (wordRefs.current[index] = el)}\n className=\"relative text-[3rem] font-black cursor-pointer\"\n style={{\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n '--border-color': borderColor,\n '--glow-color': glowColor,\n transition: `filter ${animationDuration}s ease`\n }}\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={{\n '--border-color': borderColor,\n '--glow-color': glowColor\n }}\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", + "content": "import { useEffect, useRef, useState } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\nconst TrueFocus = ({\n sentence = 'True Focus',\n words,\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs = useRef([]);\n const [focusRect, setFocusRect] = useState({ x: 0, y: 0, width: 0, height: 0 });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % wordList.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex].getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, wordList.length]);\n\n const handleMouseEnter = index => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex);\n }\n };\n\n return (\n
\n {wordList.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n (wordRefs.current[index] = el)}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={{\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n '--border-color': borderColor,\n '--glow-color': glowColor,\n transition: `filter ${animationDuration}s ease`\n }}\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={{\n '--border-color': borderColor,\n '--glow-color': glowColor\n }}\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", "type": "registry:component" } ] diff --git a/public/r/TrueFocus-TS-CSS.json b/public/r/TrueFocus-TS-CSS.json index 282cc1fe..62c02d4f 100644 --- a/public/r/TrueFocus-TS-CSS.json +++ b/public/r/TrueFocus-TS-CSS.json @@ -10,7 +10,7 @@ "files": [ { "path": "public/ts/default/src/ts-default/TextAnimations/TrueFocus/TrueFocus.tsx", - "content": "import { useEffect, useRef, useState, RefObject } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\ninterface TrueFocusProps {\n sentence?: string;\n manualMode?: boolean;\n blurAmount?: number;\n borderColor?: string;\n glowColor?: string;\n animationDuration?: number;\n pauseBetweenAnimations?: number;\n}\n\ninterface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst TrueFocus: React.FC = ({\n sentence = 'True Focus',\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const words = sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs: React.MutableRefObject<(HTMLSpanElement | null)[]> = useRef([]);\n const [focusRect, setFocusRect] = useState({\n x: 0,\n y: 0,\n width: 0,\n height: 0\n });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % words.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex]!.getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, words.length]);\n\n const handleMouseEnter = (index: number) => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex ?? 0);\n }\n };\n\n return (\n
\n {words.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n {\n if (el) {\n wordRefs.current[index] = el;\n }\n }}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={\n {\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n transition: `filter ${animationDuration}s ease`,\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={\n {\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", + "content": "import { useEffect, useRef, useState, RefObject } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\ninterface TrueFocusProps {\n sentence?: string;\n words?: string[];\n manualMode?: boolean;\n blurAmount?: number;\n borderColor?: string;\n glowColor?: string;\n animationDuration?: number;\n pauseBetweenAnimations?: number;\n}\n\ninterface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst TrueFocus: React.FC = ({\n sentence = 'True Focus',\n words,\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs: React.MutableRefObject<(HTMLSpanElement | null)[]> = useRef([]);\n const [focusRect, setFocusRect] = useState({\n x: 0,\n y: 0,\n width: 0,\n height: 0\n });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % wordList.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex]!.getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, wordList.length]);\n\n const handleMouseEnter = (index: number) => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex ?? 0);\n }\n };\n\n return (\n
\n {wordList.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n {\n if (el) {\n wordRefs.current[index] = el;\n }\n }}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={\n {\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n transition: `filter ${animationDuration}s ease`,\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={\n {\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", "type": "registry:component" }, { diff --git a/public/r/TrueFocus-TS-TW.json b/public/r/TrueFocus-TS-TW.json index 1367c5c5..146b5ec6 100644 --- a/public/r/TrueFocus-TS-TW.json +++ b/public/r/TrueFocus-TS-TW.json @@ -10,7 +10,7 @@ "files": [ { "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/TrueFocus/TrueFocus.tsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { motion } from 'motion/react';\n\ninterface TrueFocusProps {\n sentence?: string;\n manualMode?: boolean;\n blurAmount?: number;\n borderColor?: string;\n glowColor?: string;\n animationDuration?: number;\n pauseBetweenAnimations?: number;\n}\n\ninterface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst TrueFocus: React.FC = ({\n sentence = 'True Focus',\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const words = sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs = useRef<(HTMLSpanElement | null)[]>([]);\n const [focusRect, setFocusRect] = useState({ x: 0, y: 0, width: 0, height: 0 });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % words.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex]!.getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, words.length]);\n\n const handleMouseEnter = (index: number) => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex!);\n }\n };\n\n return (\n
\n {words.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n {\n wordRefs.current[index] = el;\n }}\n className=\"relative text-[3rem] font-black cursor-pointer\"\n style={\n {\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n transition: `filter ${animationDuration}s ease`\n } as React.CSSProperties\n }\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={\n {\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", + "content": "import { useEffect, useRef, useState, RefObject } from 'react';\nimport { motion } from 'motion/react';\nimport './TrueFocus.css';\n\ninterface TrueFocusProps {\n sentence?: string;\n words?: string[];\n manualMode?: boolean;\n blurAmount?: number;\n borderColor?: string;\n glowColor?: string;\n animationDuration?: number;\n pauseBetweenAnimations?: number;\n}\n\ninterface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst TrueFocus: React.FC = ({\n sentence = 'True Focus',\n words,\n manualMode = false,\n blurAmount = 5,\n borderColor = 'green',\n glowColor = 'rgba(0, 255, 0, 0.6)',\n animationDuration = 0.5,\n pauseBetweenAnimations = 1\n}) => {\n const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' ');\n const [currentIndex, setCurrentIndex] = useState(0);\n const [lastActiveIndex, setLastActiveIndex] = useState(null);\n const containerRef = useRef(null);\n const wordRefs: React.MutableRefObject<(HTMLSpanElement | null)[]> = useRef([]);\n const [focusRect, setFocusRect] = useState({\n x: 0,\n y: 0,\n width: 0,\n height: 0\n });\n\n useEffect(() => {\n if (!manualMode) {\n const interval = setInterval(\n () => {\n setCurrentIndex(prev => (prev + 1) % wordList.length);\n },\n (animationDuration + pauseBetweenAnimations) * 1000\n );\n\n return () => clearInterval(interval);\n }\n }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]);\n\n useEffect(() => {\n if (currentIndex === null || currentIndex === -1) return;\n\n if (!wordRefs.current[currentIndex] || !containerRef.current) return;\n\n const parentRect = containerRef.current.getBoundingClientRect();\n const activeRect = wordRefs.current[currentIndex]!.getBoundingClientRect();\n\n setFocusRect({\n x: activeRect.left - parentRect.left,\n y: activeRect.top - parentRect.top,\n width: activeRect.width,\n height: activeRect.height\n });\n }, [currentIndex, wordList.length]);\n\n const handleMouseEnter = (index: number) => {\n if (manualMode) {\n setLastActiveIndex(index);\n setCurrentIndex(index);\n }\n };\n\n const handleMouseLeave = () => {\n if (manualMode) {\n setCurrentIndex(lastActiveIndex ?? 0);\n }\n };\n\n return (\n
\n {wordList.map((word, index) => {\n const isActive = index === currentIndex;\n return (\n {\n if (el) {\n wordRefs.current[index] = el;\n }\n }}\n className={`focus-word ${manualMode ? 'manual' : ''} ${isActive && !manualMode ? 'active' : ''}`}\n style={\n {\n filter: manualMode\n ? isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`\n : isActive\n ? `blur(0px)`\n : `blur(${blurAmount}px)`,\n transition: `filter ${animationDuration}s ease`,\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n onMouseEnter={() => handleMouseEnter(index)}\n onMouseLeave={handleMouseLeave}\n >\n {word}\n \n );\n })}\n\n = 0 ? 1 : 0\n }}\n transition={{\n duration: animationDuration\n }}\n style={\n {\n '--border-color': borderColor,\n '--glow-color': glowColor\n } as React.CSSProperties\n }\n >\n \n \n \n \n \n
\n );\n};\n\nexport default TrueFocus;\n", "type": "registry:component" } ] diff --git a/src/content/TextAnimations/TrueFocus/TrueFocus.jsx b/src/content/TextAnimations/TrueFocus/TrueFocus.jsx index 578f5526..4e28bc6e 100644 --- a/src/content/TextAnimations/TrueFocus/TrueFocus.jsx +++ b/src/content/TextAnimations/TrueFocus/TrueFocus.jsx @@ -4,6 +4,7 @@ import './TrueFocus.css'; const TrueFocus = ({ sentence = 'True Focus', + words, manualMode = false, blurAmount = 5, borderColor = 'green', @@ -11,7 +12,7 @@ const TrueFocus = ({ animationDuration = 0.5, pauseBetweenAnimations = 1 }) => { - const words = sentence.split(' '); + const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' '); const [currentIndex, setCurrentIndex] = useState(0); const [lastActiveIndex, setLastActiveIndex] = useState(null); const containerRef = useRef(null); @@ -22,14 +23,14 @@ const TrueFocus = ({ if (!manualMode) { const interval = setInterval( () => { - setCurrentIndex(prev => (prev + 1) % words.length); + setCurrentIndex(prev => (prev + 1) % wordList.length); }, (animationDuration + pauseBetweenAnimations) * 1000 ); return () => clearInterval(interval); } - }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]); + }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]); useEffect(() => { if (currentIndex === null || currentIndex === -1) return; @@ -45,7 +46,7 @@ const TrueFocus = ({ width: activeRect.width, height: activeRect.height }); - }, [currentIndex, words.length]); + }, [currentIndex, wordList.length]); const handleMouseEnter = index => { if (manualMode) { @@ -62,7 +63,7 @@ const TrueFocus = ({ return (
- {words.map((word, index) => { + {wordList.map((word, index) => { const isActive = index === currentIndex; return ( { default: "'True Focus'", description: 'The text to display with the focus animation.' }, + { + name: 'words', + type: 'string[]', + default: 'undefined', + description: + 'Optional array of words or tokens to display. If provided, it will be used directly instead of splitting the `sentence` by spaces. Useful for precise spacing control.' + }, { name: 'manualMode', type: 'boolean', diff --git a/src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx b/src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx index f3dc7366..4e5557c3 100644 --- a/src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx +++ b/src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx @@ -3,6 +3,7 @@ import { motion } from 'motion/react'; const TrueFocus = ({ sentence = 'True Focus', + words, manualMode = false, blurAmount = 5, borderColor = 'green', @@ -10,7 +11,7 @@ const TrueFocus = ({ animationDuration = 0.5, pauseBetweenAnimations = 1 }) => { - const words = sentence.split(' '); + const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' '); const [currentIndex, setCurrentIndex] = useState(0); const [lastActiveIndex, setLastActiveIndex] = useState(null); const containerRef = useRef(null); @@ -21,14 +22,14 @@ const TrueFocus = ({ if (!manualMode) { const interval = setInterval( () => { - setCurrentIndex(prev => (prev + 1) % words.length); + setCurrentIndex(prev => (prev + 1) % wordList.length); }, (animationDuration + pauseBetweenAnimations) * 1000 ); return () => clearInterval(interval); } - }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]); + }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]); useEffect(() => { if (currentIndex === null || currentIndex === -1) return; @@ -43,7 +44,7 @@ const TrueFocus = ({ width: activeRect.width, height: activeRect.height }); - }, [currentIndex, words.length]); + }, [currentIndex, wordList.length]); const handleMouseEnter = index => { if (manualMode) { @@ -60,7 +61,7 @@ const TrueFocus = ({ return (
- {words.map((word, index) => { + {wordList.map((word, index) => { const isActive = index === currentIndex; return ( = ({ sentence = 'True Focus', + words, manualMode = false, blurAmount = 5, borderColor = 'green', @@ -28,7 +30,7 @@ const TrueFocus: React.FC = ({ animationDuration = 0.5, pauseBetweenAnimations = 1 }) => { - const words = sentence.split(' '); + const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' '); const [currentIndex, setCurrentIndex] = useState(0); const [lastActiveIndex, setLastActiveIndex] = useState(null); const containerRef = useRef(null); @@ -44,14 +46,14 @@ const TrueFocus: React.FC = ({ if (!manualMode) { const interval = setInterval( () => { - setCurrentIndex(prev => (prev + 1) % words.length); + setCurrentIndex(prev => (prev + 1) % wordList.length); }, (animationDuration + pauseBetweenAnimations) * 1000 ); return () => clearInterval(interval); } - }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]); + }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]); useEffect(() => { if (currentIndex === null || currentIndex === -1) return; @@ -67,7 +69,7 @@ const TrueFocus: React.FC = ({ width: activeRect.width, height: activeRect.height }); - }, [currentIndex, words.length]); + }, [currentIndex, wordList.length]); const handleMouseEnter = (index: number) => { if (manualMode) { @@ -84,7 +86,7 @@ const TrueFocus: React.FC = ({ return (
- {words.map((word, index) => { + {wordList.map((word, index) => { const isActive = index === currentIndex; return ( = ({ sentence = 'True Focus', + words, manualMode = false, blurAmount = 5, borderColor = 'green', @@ -27,7 +29,7 @@ const TrueFocus: React.FC = ({ animationDuration = 0.5, pauseBetweenAnimations = 1 }) => { - const words = sentence.split(' '); + const wordList = Array.isArray(words) && words.length > 0 ? words : sentence.split(' '); const [currentIndex, setCurrentIndex] = useState(0); const [lastActiveIndex, setLastActiveIndex] = useState(null); const containerRef = useRef(null); @@ -38,14 +40,14 @@ const TrueFocus: React.FC = ({ if (!manualMode) { const interval = setInterval( () => { - setCurrentIndex(prev => (prev + 1) % words.length); + setCurrentIndex(prev => (prev + 1) % wordList.length); }, (animationDuration + pauseBetweenAnimations) * 1000 ); return () => clearInterval(interval); } - }, [manualMode, animationDuration, pauseBetweenAnimations, words.length]); + }, [manualMode, animationDuration, pauseBetweenAnimations, wordList.length]); useEffect(() => { if (currentIndex === null || currentIndex === -1) return; @@ -60,7 +62,7 @@ const TrueFocus: React.FC = ({ width: activeRect.width, height: activeRect.height }); - }, [currentIndex, words.length]); + }, [currentIndex, wordList.length]); const handleMouseEnter = (index: number) => { if (manualMode) { @@ -77,7 +79,7 @@ const TrueFocus: React.FC = ({ return (
- {words.map((word, index) => { + {wordList.map((word, index) => { const isActive = index === currentIndex; return (