feat: Initialize portfolio project with core pages, UI components, and configuration files.

This commit is contained in:
Evandro Fonseca Junior committed 2026-03-07 10:27:04 -03:00
1 parent aa07d6faeb
commit 0523052864
38 files changed
+1165 -460

No files matched your search

+43 -27
View File
@@ -1,7 +1,15 @@
'use client';
"use client";
import { ElementType, useEffect, useRef, useState, createElement, useMemo, useCallback } from 'react';
import { gsap } from 'gsap';
import {
ElementType,
useEffect,
useRef,
useState,
createElement,
useMemo,
useCallback,
} from "react";
import { gsap } from "gsap";
interface TextTypeProps {
className?: string;
@@ -26,17 +34,17 @@ interface TextTypeProps {
const TextType = ({
text,
as: Component = 'div',
as: Component = "div",
typingSpeed = 50,
initialDelay = 0,
pauseDuration = 2000,
deletingSpeed = 30,
loop = true,
className = '',
className = "",
showCursor = true,
hideCursorWhileTyping = false,
cursorCharacter = '|',
cursorClassName = '',
cursorCharacter = "|",
cursorClassName = "",
cursorBlinkDuration = 0.5,
textColors = [],
variableSpeed,
@@ -45,7 +53,7 @@ const TextType = ({
reverseMode = false,
...props
}: TextTypeProps & React.HTMLAttributes<HTMLElement>) => {
const [displayedText, setDisplayedText] = useState('');
const [displayedText, setDisplayedText] = useState("");
const [currentCharIndex, setCurrentCharIndex] = useState(0);
const [isDeleting, setIsDeleting] = useState(false);
const [currentTextIndex, setCurrentTextIndex] = useState(0);
@@ -53,7 +61,10 @@ const TextType = ({
const cursorRef = useRef<HTMLSpanElement>(null);
const containerRef = useRef<HTMLElement>(null);
const textArray = useMemo(() => (Array.isArray(text) ? text : [text]), [text]);
const textArray = useMemo(
() => (Array.isArray(text) ? text : [text]),
[text],
);
const getRandomSpeed = useCallback(() => {
if (!variableSpeed) return typingSpeed;
@@ -62,7 +73,7 @@ const TextType = ({
}, [variableSpeed, typingSpeed]);
const getCurrentTextColor = () => {
if (textColors.length === 0) return '#ffffff';
if (textColors.length === 0) return "#ffffff";
return textColors[currentTextIndex % textColors.length];
};
@@ -70,14 +81,14 @@ const TextType = ({
if (!startOnVisible || !containerRef.current) return;
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
});
},
{ threshold: 0.1 }
{ threshold: 0.1 },
);
observer.observe(containerRef.current);
@@ -92,7 +103,7 @@ const TextType = ({
duration: cursorBlinkDuration,
repeat: -1,
yoyo: true,
ease: 'power2.inOut'
ease: "power2.inOut",
});
}
}, [showCursor, cursorBlinkDuration]);
@@ -103,11 +114,13 @@ const TextType = ({
let timeout: NodeJS.Timeout;
const currentText = textArray[currentTextIndex];
const processedText = reverseMode ? currentText.split('').reverse().join('') : currentText;
const processedText = reverseMode
? currentText.split("").reverse().join("")
: currentText;
const executeTypingAnimation = () => {
if (isDeleting) {
if (displayedText === '') {
if (displayedText === "") {
setIsDeleting(false);
if (currentTextIndex === textArray.length - 1 && !loop) {
return;
@@ -117,22 +130,24 @@ const TextType = ({
onSentenceComplete(textArray[currentTextIndex], currentTextIndex);
}
setCurrentTextIndex(prev => (prev + 1) % textArray.length);
setCurrentTextIndex((prev) => (prev + 1) % textArray.length);
setCurrentCharIndex(0);
timeout = setTimeout(() => {}, pauseDuration);
} else {
timeout = setTimeout(() => {
setDisplayedText(prev => prev.slice(0, -1));
setDisplayedText((prev) => prev.slice(0, -1));
}, deletingSpeed);
}
} else {
if (currentCharIndex < processedText.length) {
timeout = setTimeout(
() => {
setDisplayedText(prev => prev + processedText[currentCharIndex]);
setCurrentCharIndex(prev => prev + 1);
setDisplayedText(
(prev) => prev + processedText[currentCharIndex],
);
setCurrentCharIndex((prev) => prev + 1);
},
variableSpeed ? getRandomSpeed() : typingSpeed
variableSpeed ? getRandomSpeed() : typingSpeed,
);
} else if (textArray.length > 1) {
timeout = setTimeout(() => {
@@ -142,7 +157,7 @@ const TextType = ({
}
};
if (currentCharIndex === 0 && !isDeleting && displayedText === '') {
if (currentCharIndex === 0 && !isDeleting && displayedText === "") {
timeout = setTimeout(executeTypingAnimation, initialDelay);
} else {
executeTypingAnimation();
@@ -163,18 +178,19 @@ const TextType = ({
isVisible,
reverseMode,
variableSpeed,
onSentenceComplete
onSentenceComplete,
]);
const shouldHideCursor =
hideCursorWhileTyping && (currentCharIndex < textArray[currentTextIndex].length || isDeleting);
hideCursorWhileTyping &&
(currentCharIndex < textArray[currentTextIndex].length || isDeleting);
return createElement(
Component,
{
ref: containerRef,
className: `inline-block whitespace-pre-wrap tracking-tight ${className}`,
...props
...props,
},
<span className="inline" style={{ color: getCurrentTextColor() }}>
{displayedText}
@@ -182,11 +198,11 @@ const TextType = ({
showCursor && (
<span
ref={cursorRef}
className={`ml-1 inline-block opacity-100 ${shouldHideCursor ? 'hidden' : ''} ${cursorClassName}`}
className={`ml-1 inline-block opacity-100 ${shouldHideCursor ? "hidden" : ""} ${cursorClassName}`}
>
{cursorCharacter}
</span>
)
),
);
};