From 3b89a386bb376faa183bb771aea3f3d094f680ff Mon Sep 17 00:00:00 2001 From: rxshellg Date: Fri, 17 Jul 2026 12:41:36 -0400 Subject: [PATCH] Added the resume modal --- src/App.test.tsx | 4 - src/components/layout/Navbar.css | 3 + src/components/layout/Navbar.test.tsx | 12 ++- src/components/layout/Navbar.tsx | 23 ++++- src/components/ui/ResumeModal.css | 127 ++++++++++++++++++++++++++ src/components/ui/ResumeModal.tsx | 95 +++++++++++++++++++ 6 files changed, 250 insertions(+), 14 deletions(-) create mode 100644 src/components/ui/ResumeModal.css create mode 100644 src/components/ui/ResumeModal.tsx diff --git a/src/App.test.tsx b/src/App.test.tsx index 1c2e440..2382012 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -51,10 +51,6 @@ describe("App", () => { "href", "#contact", ); - expect(screen.getByRole("link", { name: /resume\.pdf/i })).toHaveAttribute( - "href", - "/Rashell-Guerrero-Resume.pdf", - ); }); it("has accessible social links", () => { diff --git a/src/components/layout/Navbar.css b/src/components/layout/Navbar.css index 6503d29..e5d5bbd 100644 --- a/src/components/layout/Navbar.css +++ b/src/components/layout/Navbar.css @@ -65,6 +65,9 @@ text-transform: uppercase; font-size: 0.8rem; padding: 0.5rem 1.5rem; + border: 0; + font-family: inherit; + cursor: pointer; } .resume-section:hover { diff --git a/src/components/layout/Navbar.test.tsx b/src/components/layout/Navbar.test.tsx index a774ef7..96a1f71 100644 --- a/src/components/layout/Navbar.test.tsx +++ b/src/components/layout/Navbar.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from "vitest"; -import { render, screen, within } from "@testing-library/react"; +import { fireEvent, render, screen, within } from "@testing-library/react"; import Navbar from "./Navbar"; describe("Navbar", () => { @@ -31,9 +31,11 @@ describe("Navbar", () => { ); }); - it("renders the resume download link", () => { - const resumeLink = within(nav).getByRole("link", { name: /resume\.pdf/i }); - expect(resumeLink).toHaveAttribute("href", "/Rashell-Guerrero-Resume.pdf"); - expect(resumeLink).toHaveAttribute("download"); + it("opens the resume modal", () => { + fireEvent.click(within(nav).getByRole("button", { name: /resume\.pdf/i })); + + expect( + screen.getByRole("dialog", { name: /hey there/i }), + ).toBeInTheDocument(); }); }); diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index ef69542..48c4eea 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,11 +1,18 @@ import { useEffect, useState } from "react"; import { RiDownloadLine } from "react-icons/ri"; import { navigationLinks } from "../../data/navigation"; +import ResumeModal from "../ui/ResumeModal"; import "./Navbar.css"; function Navbar() { const [activeSection, setActiveSection] = useState("hero"); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [isResumeModalOpen, setIsResumeModalOpen] = useState(false); + + function handleGetInTouch() { + setIsResumeModalOpen(false); + document.getElementById("contact")?.scrollIntoView({ behavior: "smooth" }); + } useEffect(() => { const sections = navigationLinks @@ -50,14 +57,14 @@ function Navbar() {
{navLinks()}
- setIsResumeModalOpen(true)} > Resume.pdf - - +
)} + + setIsResumeModalOpen(false)} + onGetInTouch={handleGetInTouch} + /> ); } diff --git a/src/components/ui/ResumeModal.css b/src/components/ui/ResumeModal.css new file mode 100644 index 0000000..76bf268 --- /dev/null +++ b/src/components/ui/ResumeModal.css @@ -0,0 +1,127 @@ +.resume-modal { + position: fixed; + inset: 0; + z-index: 100; + padding: 1.5rem; + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(2px); + animation: modal-fade 0.15s ease-out; +} + +.resume-modal > div { + width: 100%; + max-width: 30rem; + border: 1px solid var(--primary); + background: #000; + box-shadow: 0 0 24px rgba(242, 119, 165, 0.25); + outline: none; + animation: modal-pop 0.18s ease-out; +} + +@keyframes modal-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes modal-pop { + from { + opacity: 0; + transform: scale(0.96) translateY(6px); + } + + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +.resume-modal .titlebar { + justify-content: space-between; + padding: 0.6rem 0.9rem; + border-bottom: 1px solid var(--primary); + color: var(--primary); +} + +.resume-modal .titlebar > span { + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.05em; +} + +.resume-modal .titlebar > div { + gap: 0.7rem; + font-size: 0.85rem; +} + +.resume-modal .titlebar button { + padding: 0; + border: 0; + background: none; + color: inherit; + font-size: 0.95rem; + cursor: pointer; +} + +.resume-modal .body { + padding: 1.75rem 1.5rem 1rem; + color: rgba(255, 255, 255, 0.85); + font-size: 0.85rem; + line-height: 1.55; +} + +.resume-modal h2 { + margin: 0; + color: #fff; + font-size: 1.25rem; + font-weight: 800; +} + +.resume-modal p { margin: 0.5rem 0; } + +.resume-modal .body::after { + content: ""; + display: block; + width: 1.6rem; + height: 2px; + margin: 0.7rem 0; + background: var(--primary); +} + +.resume-modal .actions { + gap: 0.75rem; + padding: 0.25rem 1.5rem 1.25rem; +} + +.resume-modal .actions button { + flex: 1; + gap: 0.5rem; + padding: 0.7rem 0.9rem; + border: 1px solid var(--primary); + background: transparent; + color: #fff; + font-family: inherit; + font-size: 0.78rem; + letter-spacing: 0.03em; + text-transform: uppercase; + cursor: pointer; + transition: box-shadow 0.15s; +} + +.resume-modal .actions .primary { + background: var(--primary); + color: #000; + font-weight: 700; +} + +.resume-modal .actions button:hover { + box-shadow: 0 0 10px var(--primary); +} + +@media (max-width: 480px) { + .resume-modal .body { + padding-top: 1.5rem; + text-align: center; + } + + .resume-modal .actions { flex-direction: column; } +} \ No newline at end of file diff --git a/src/components/ui/ResumeModal.tsx b/src/components/ui/ResumeModal.tsx new file mode 100644 index 0000000..c6a5a16 --- /dev/null +++ b/src/components/ui/ResumeModal.tsx @@ -0,0 +1,95 @@ +import { useEffect, useRef } from "react"; +import { FiArrowRight, FiMail, FiX } from "react-icons/fi"; +import "./ResumeModal.css"; + +type Props = { + isOpen: boolean; + onClose: () => void; + onGetInTouch: () => void; +}; + +export default function ResumeModal({ isOpen, onClose, onGetInTouch }: Props) { + const modalRef = useRef(null); + + useEffect(() => { + if (!isOpen) return; + + const closeOnEscape = ({ key }: KeyboardEvent) => + key === "Escape" && onClose(); + const { style } = document.body; + const overflow = style.overflow; + + document.addEventListener("keydown", closeOnEscape); + style.overflow = "hidden"; + modalRef.current?.focus(); + + return () => { + document.removeEventListener("keydown", closeOnEscape); + style.overflow = overflow; + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+ target === currentTarget && onClose() + } + > +
+
+ RESUME.EXE + +
+ + + + +
+
+ +
+

+ Hey there! +

+ +

+ Thanks for your interest in my work. I'd love to connect and share + my resume with you personally. +

+
+ +
+ + + +
+
+
+ ); +}