From 2a6ba948050aafc41e061983a2b1c689c9c2787b Mon Sep 17 00:00:00 2001 From: jarydo Date: Sun, 15 Sep 2024 11:15:52 -0400 Subject: [PATCH] Update navbar based on page location --- src/App.tsx | 53 ++++++++++++++++++++++---- src/components/sections/Contact.tsx | 7 ++-- src/components/sections/Experience.tsx | 7 ++-- src/components/sections/Home.tsx | 53 ++++++++++++++------------ src/components/sections/Projects.tsx | 7 ++-- src/components/sections/types.ts | 1 + 6 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 src/components/sections/types.ts diff --git a/src/App.tsx b/src/App.tsx index 716fa33..565b28c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Button } from "@/components/ui/button"; // import { Label } from './components/ui/label'; // import { Switch } from './components/ui/switch'; @@ -13,16 +13,29 @@ function App() { const fullTitle = 'Jaryd.'; const [activeTab, setActiveTab] = useState('home'); + const [isScrolling, setIsScrolling] = useState(false); const navItems = [ { id: 'home', label: 'Home' }, { id: 'experience', label: 'Experience' }, { id: 'projects', label: 'Projects' }, { id: 'contact', label: 'Contact' }, ] + const sectionRefs = useRef<{ + home: HTMLElement | null; + experience: HTMLElement | null; + projects: HTMLElement | null; + contact: HTMLElement | null; + }>({ + home: null, + experience: null, + projects: null, + contact: null, + }); + // TODO: recruiter mode, handle mobile // const [recruiterMode, setRecruiterMode] = useState(true); - // TODO: Fix this reloading on navbar redirect + // TODO: change this to CSS animation useEffect(() => { if (index < fullTitle.length) { const timeout = setTimeout(() => { @@ -34,11 +47,35 @@ function App() { } }, [index]) +useEffect(() => { + const handleScroll = () => { + if (isScrolling) return; + + const scrollPosition = window.scrollY + window.innerHeight / 2; + + for (const [id, ref] of Object.entries(sectionRefs.current)) { + if (ref && ref.offsetTop <= scrollPosition && ref.offsetTop + ref.offsetHeight > scrollPosition) { + setActiveTab(id); + break; + } + } + }; + + window.addEventListener('scroll', handleScroll); + handleScroll(); // Call once to set initial active tab + + return () => window.removeEventListener('scroll', handleScroll); +}, [isScrolling]); + const handleNavClick = (id: string) => { + setIsScrolling(true); setActiveTab(id) - const element = document.getElementById(id) + const element = document.getElementById(id); if (element) { - element.scrollIntoView({ behavior: 'smooth' }) + element.scrollIntoView({ behavior: 'smooth' }); + setTimeout(() => { + setIsScrolling(false); + }, 1500); } } @@ -112,10 +149,10 @@ function App() { return ( - - - - + (sectionRefs.current.home = el)}/> + (sectionRefs.current.experience = el)}/> + (sectionRefs.current.projects = el)}/> + (sectionRefs.current.contact = el)}/> ) } diff --git a/src/components/sections/Contact.tsx b/src/components/sections/Contact.tsx index 3d5d659..8d20a77 100644 --- a/src/components/sections/Contact.tsx +++ b/src/components/sections/Contact.tsx @@ -1,8 +1,9 @@ import * as resume from '../../resume.json'; +import { forwardRef } from 'react'; -const Contact = () => { +const Contact = forwardRef((props, ref) => { return ( -
+

Get in Touch

@@ -20,6 +21,6 @@ const Contact = () => {
) -} +}); export default Contact; \ No newline at end of file diff --git a/src/components/sections/Experience.tsx b/src/components/sections/Experience.tsx index 6ebbd2d..6690360 100644 --- a/src/components/sections/Experience.tsx +++ b/src/components/sections/Experience.tsx @@ -1,11 +1,12 @@ import * as resume from '../../resume.json'; import { Card, CardContent } from '../ui/card'; import { Badge } from '../ui/badge'; +import { forwardRef } from 'react'; // TODO: add skills to each position, generate latex resume from json -const Experience = () => { +const Experience = forwardRef((props, ref) => { return ( -
+

Work Experience

{resume.experience.map((item) => { return ( @@ -37,6 +38,6 @@ const Experience = () => { })}
) -} +}); export default Experience; \ No newline at end of file diff --git a/src/components/sections/Home.tsx b/src/components/sections/Home.tsx index 1225e4a..6511a1e 100644 --- a/src/components/sections/Home.tsx +++ b/src/components/sections/Home.tsx @@ -1,38 +1,41 @@ -const Home = ({ title }: { title: string}) => { +import { forwardRef } from "react"; + +const Home = forwardRef((props, ref) => { const Title = () => { return ( - {title} + {props.title} | ) } return ( -
-
-

Introducing... </h1> - <h1 className="text-7xl font-bold">Your next hire.</h1> - <p className="text-2xl mt-10">Developer, community builder, filmmaker, amateur musician - you can guarantee his value is <b>not just code.</b></p> - </main> - - <footer> - <div className="max-w-4xl mx-auto"> - <div className="text-center mb-4"> - <span className="text-white text-lg">★★★★★</span> - <p className="text-xl text-gray-300 mt-2">Trusted by the following companies:</p> - </div> - <div className="flex justify-center space-x-10"> - <a href="https://arcticwolf.com/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/arctic_wolf.png" alt="Arctic Wolf" className="h-10" /></a> - <a href="https://99ravens.ai/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/99_ravens.png" alt="99 Ravens" className="h-10" /></a> - <a href="https://horizn.com/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/horizn.png" alt="Horizn" className="h-10" /></a> - <a href="https://www.arcticai.co/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/arctic_ai.png" alt="Arctic AI" className="h-10" /></a> - <a href="https://www.mikobyte.com/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/mikobyte.webp" alt="Mikobyte Solutions" className="h-10" /></a> + <section id="home" ref={ref} className="min-h-screen flex flex-col items-center justify-evenly mt-6"> + <main className="text-center flex flex-col gap-4 text-white"> + <h1 className="text-8xl font-bold">Introducing... <Title /></h1> + <h1 className="text-6xl font-bold">Your next hire.</h1> + {/*TODO: fix, A curious and passionate developer, with a variety of experience from startups to large organizations - you can guarantee his value is not just code.*/} + <p className="text-2xl mt-8">Developer, community builder, filmmaker, amateur musician - you can guarantee his value is <b>not just code.</b></p> + </main> + + <footer> + <div className="max-w-4xl mx-auto"> + <div className="text-center mb-4"> + <span className="text-white text-lg">★★★★★</span> + <p className="text-xl text-gray-300 mt-2">Trusted by the following companies:</p> + </div> + <div className="flex justify-center space-x-10"> + <a href="https://arcticwolf.com/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/arctic_wolf.png" alt="Arctic Wolf" className="h-10" /></a> + <a href="https://99ravens.ai/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/99_ravens.png" alt="99 Ravens" className="h-10" /></a> + <a href="https://horizn.com/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/horizn.png" alt="Horizn" className="h-10" /></a> + <a href="https://www.arcticai.co/" target="_blank" className="opacity-50 hover:opacity-100"><img src="/arctic_ai.png" alt="Arctic AI" className="h-10" /></a> + <a href="https://www.mikobyte.com/" target="_blank" className="opacity-50 hover:opacity-100 ml-2.5"><img src="/mikobyte.webp" alt="Mikobyte Solutions" className="h-10" /></a> + </div> </div> - </div> - </footer> - </section> + </footer> + </section> ) -} +}); export default Home; \ No newline at end of file diff --git a/src/components/sections/Projects.tsx b/src/components/sections/Projects.tsx index d71c9d3..65ffa86 100644 --- a/src/components/sections/Projects.tsx +++ b/src/components/sections/Projects.tsx @@ -2,11 +2,12 @@ import { Card, CardHeader, CardContent, CardTitle, CardDescription, CardFooter } import { Badge } from '../ui/badge'; import { Button } from '../ui/button'; import * as resume from '../../resume.json'; +import { forwardRef } from 'react'; // TODO: add proper images, external links -const Projects = () => { +const Projects = forwardRef<HTMLElement>((props, ref) => { return ( - <section id="projects" className="min-h-screen flex items-center justify-center pt-32"> + <section id="projects" ref={ref} className="min-h-screen flex items-center justify-center pt-32"> <div className="max-w-6xl mx-auto px-4"> <h2 className="text-6xl font-bold text-center text-white mb-8">Selected Projects</h2> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> @@ -46,6 +47,6 @@ const Projects = () => { </div> </section> ) -} +}); export default Projects; \ No newline at end of file diff --git a/src/components/sections/types.ts b/src/components/sections/types.ts new file mode 100644 index 0000000..93666cb --- /dev/null +++ b/src/components/sections/types.ts @@ -0,0 +1 @@ +export type Ref = ((el: HTMLDivElement | null) => void) | React.MutableRefObject<HTMLDivElement | null> | null; \ No newline at end of file