diff --git a/keith-portfolio/src/App.tsx b/keith-portfolio/src/App.tsx index ae1398c..c738c2d 100644 --- a/keith-portfolio/src/App.tsx +++ b/keith-portfolio/src/App.tsx @@ -6,9 +6,11 @@ import ExperienceSection from './sections/ExperienceSection'; import ProjectsSection from './sections/ProjectsSection'; import CredentialsSection from './sections/CredentialsSection'; import ContactSection from './sections/ContactSection'; +import { ContactProvider } from './components/ContactDialog'; export default function App() { return ( + @@ -19,5 +21,6 @@ export default function App() { + ); } diff --git a/keith-portfolio/src/components/ContactButton.tsx b/keith-portfolio/src/components/ContactButton.tsx index b36d598..59b4988 100644 --- a/keith-portfolio/src/components/ContactButton.tsx +++ b/keith-portfolio/src/components/ContactButton.tsx @@ -1,18 +1,21 @@ -import { profile } from "../data/content"; +import { useOpenContact } from './ContactDialog'; export default function ContactButton() { + const openContact = useOpenContact(); return ( - Contact Me - + ); } diff --git a/keith-portfolio/src/components/ContactDialog.tsx b/keith-portfolio/src/components/ContactDialog.tsx new file mode 100644 index 0000000..1079475 --- /dev/null +++ b/keith-portfolio/src/components/ContactDialog.tsx @@ -0,0 +1,101 @@ +import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import type { ReactNode } from 'react'; +import { Check, Copy, Mail, X } from 'lucide-react'; +import { profile } from '../data/content'; + +const OpenContext = createContext<() => void>(() => {}); +export const useOpenContact = () => useContext(OpenContext); + +const subject = encodeURIComponent('Portfolio enquiry'); +const to = encodeURIComponent(profile.email); +const options = [ + { label: 'Open my email app', href: `mailto:${profile.email}?subject=${subject}`, newTab: false }, + { label: 'Write in Gmail', href: `https://mail.google.com/mail/?view=cm&fs=1&to=${to}&su=${subject}`, newTab: true }, + { label: 'Write in Yahoo Mail', href: `https://compose.mail.yahoo.com/?to=${to}&subject=${subject}`, newTab: true }, +]; + +const optionClass = + 'flex items-center justify-between gap-3 rounded-full border-2 border-[#D7E2EA] px-6 py-3 text-sm font-medium uppercase tracking-widest text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10 sm:text-base'; + +export function ContactProvider({ children }: { children: ReactNode }) { + const [open, setOpen] = useState(false); + const [copied, setCopied] = useState(false); + const inputRef = useRef(null); + const closeRef = useRef(null); + const opener = useRef(null); + + const show = useCallback(() => { + opener.current = document.activeElement as HTMLElement | null; + setCopied(false); + setOpen(true); + }, []); + const hide = useCallback(() => { + setOpen(false); + opener.current?.focus?.(); + }, []); + + useEffect(() => { + if (!open) return; + closeRef.current?.focus(); + const onKey = (e: KeyboardEvent) => e.key === 'Escape' && hide(); + document.addEventListener('keydown', onKey); + const prev = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.removeEventListener('keydown', onKey); + document.body.style.overflow = prev; + }; + }, [open, hide]); + + const copy = async () => { + try { + await navigator.clipboard.writeText(profile.email); + setCopied(true); + } catch { + inputRef.current?.select(); + } + }; + + return ( + + {children} + {open && ( + e.target === e.currentTarget && hide()} + > + + + + + Email Keith + Pick how you would like to write. Your message goes to the address below. + + e.currentTarget.select()} + className="min-w-0 flex-1 rounded-full border border-[#D7E2EA]/40 bg-transparent px-5 py-3 text-[#D7E2EA]" + /> + + {copied ? : } + {copied ? 'Copied' : 'Copy'} + + + + {options.map((o) => ( + + {o.label} + + + ))} + + + + )} + + ); +}
Pick how you would like to write. Your message goes to the address below.