179 lines
6.9 KiB
TypeScript
179 lines
6.9 KiB
TypeScript
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
import { Mail, Send, Github, Linkedin } from 'lucide-react';
|
|
import { personalConfig } from '../../config/personal';
|
|
import { useLanguage } from '../../contexts/LanguageContext';
|
|
import { useScreenReaderAnnouncements } from '../../hooks/useScreenReaderAnnouncements';
|
|
|
|
interface ContactSectionProps {
|
|
title?: string;
|
|
subtitle?: string;
|
|
connectTitle?: string;
|
|
connectDescription?: string;
|
|
buttonText?: string;
|
|
socialTitle?: string;
|
|
availabilityText?: string;
|
|
responseTimeLabel?: string;
|
|
responseTimeValue?: string;
|
|
preferredContactLabel?: string;
|
|
preferredContactValue?: string;
|
|
locationLabel?: string;
|
|
locationValue?: string;
|
|
}
|
|
|
|
export default function ContactSection(props: ContactSectionProps = {}) {
|
|
const { texts: allTexts } = useLanguage();
|
|
const texts = allTexts.contact;
|
|
const { announce } = useScreenReaderAnnouncements();
|
|
|
|
const {
|
|
title = texts.title,
|
|
subtitle = texts.subtitle,
|
|
connectTitle = texts.connectTitle,
|
|
connectDescription = texts.connectDescription,
|
|
buttonText = texts.buttonText,
|
|
socialTitle = texts.socialTitle,
|
|
availabilityText = texts.availabilityText,
|
|
responseTimeLabel = texts.responseTimeLabel,
|
|
responseTimeValue = texts.responseTimeValue,
|
|
preferredContactLabel = texts.preferredContactLabel,
|
|
preferredContactValue = texts.preferredContactValue,
|
|
locationLabel = texts.locationLabel,
|
|
locationValue = texts.locationValue,
|
|
} = props;
|
|
|
|
// Enhanced email handler with better accessibility
|
|
const handleEmailClick = () => {
|
|
const email = getEmailAddress();
|
|
const subject = encodeURIComponent(texts.emailSubject);
|
|
const body = encodeURIComponent(texts.emailBody);
|
|
|
|
announce(texts.emailAnnouncement);
|
|
|
|
globalThis.location.href = `mailto:${email}?subject=${subject}&body=${body}`;
|
|
};
|
|
|
|
const getEmailAddress = () => {
|
|
const parts = ['freelancer', 'sascha-bach', 'de'];
|
|
return parts[0] + '@' + parts[1] + '.' + parts[2];
|
|
};
|
|
|
|
return (
|
|
<section id="contact" className="contact-section" aria-labelledby="contact-title">
|
|
<div className="contact-section__container">
|
|
{/* Section Header */}
|
|
<header className="contact-section__header">
|
|
<h2 id="contact-title" className="contact-section__title">
|
|
{title}
|
|
</h2>
|
|
<p className="contact-section__subtitle">
|
|
{subtitle}
|
|
</p>
|
|
</header>
|
|
|
|
{/* Contact Content Grid */}
|
|
<main id="contact-form" className="contact-section__grid">
|
|
{/* Let's Connect Card */}
|
|
<div className="contact-section__connect-card" role="region" aria-labelledby="connect-heading">
|
|
<div className="contact-section__connect-header">
|
|
<Mail
|
|
className="contact-section__email-icon"
|
|
aria-hidden="true"
|
|
/>
|
|
<div className="contact-section__connect-text">
|
|
<h3 id="connect-heading" className="contact-section__email-title">
|
|
{connectTitle}
|
|
</h3>
|
|
<p className="contact-section__email-description">
|
|
{connectDescription}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleEmailClick}
|
|
className="contact-section__email-button"
|
|
type="button"
|
|
aria-describedby="email-button-description"
|
|
>
|
|
<Send className="contact-section__button-icon" aria-hidden="true" />
|
|
<span className="contact-section__button-text">
|
|
{buttonText}
|
|
</span>
|
|
</button>
|
|
|
|
{/* Hidden description for screen readers */}
|
|
<div id="email-button-description" className="sr-only">
|
|
This button will open your default email client with a pre-filled message to {getEmailAddress()}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contact Info Sidebar */}
|
|
<aside className="contact-section__sidebar" aria-labelledby="info-heading">
|
|
<div className="contact-section__info-card">
|
|
<h3 id="info-heading" className="sr-only">Contact Information</h3>
|
|
|
|
<output className="contact-section__availability" aria-live="polite">
|
|
<div
|
|
className="contact-section__status-indicator"
|
|
aria-hidden="true"
|
|
></div>
|
|
<span className="contact-section__status-text">{availabilityText}</span>
|
|
</output>
|
|
|
|
<div className="contact-section__quick-facts">
|
|
<dl className="contact-section__fact-list">
|
|
<div className="contact-section__fact">
|
|
<dt className="contact-section__fact-label">{responseTimeLabel}</dt>
|
|
<dd className="contact-section__fact-value">{responseTimeValue}</dd>
|
|
</div>
|
|
<div className="contact-section__fact">
|
|
<dt className="contact-section__fact-label">{preferredContactLabel}</dt>
|
|
<dd className="contact-section__fact-value">{preferredContactValue}</dd>
|
|
</div>
|
|
<div className="contact-section__fact">
|
|
<dt className="contact-section__fact-label">{locationLabel}</dt>
|
|
<dd className="contact-section__fact-value">{locationValue}</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Social Contact Methods */}
|
|
<div className="contact-section__social-card" aria-labelledby="social-heading">
|
|
<h4 id="social-heading" className="contact-section__social-title">{socialTitle}</h4>
|
|
<ul className="contact-section__social-list">
|
|
|
|
<li className="contact-section__detail-item">
|
|
<Github className="contact-section__social-icon" aria-hidden="true" />
|
|
<a
|
|
href={personalConfig.social.github.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="contact-section__detail-link"
|
|
aria-label={`${texts.githubLinkText} (opens in new tab)`}
|
|
>
|
|
{texts.githubLinkText}
|
|
</a>
|
|
</li>
|
|
|
|
<li className="contact-section__detail-item">
|
|
<Linkedin className="contact-section__social-icon" aria-hidden="true" />
|
|
<a
|
|
href={personalConfig.social.linkedin.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="contact-section__detail-link"
|
|
aria-label={`${texts.linkedinLinkText} (opens in new tab)`}
|
|
>
|
|
{texts.linkedinLinkText}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|