web accessibility
This commit is contained in:
parent
ca488cfce9
commit
2404b8760c
|
|
@ -52,8 +52,9 @@ function AppRouter() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className="app">
|
<div className="app">
|
||||||
|
<a href="#main-content" className="skip-link">Skip to main content</a>
|
||||||
<Navbar theme={theme} setTheme={setTheme} />
|
<Navbar theme={theme} setTheme={setTheme} />
|
||||||
<main className="app__main">
|
<main id="main-content" className="app__main">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/imprint" element={<ImprintPage />} />
|
<Route path="/imprint" element={<ImprintPage />} />
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
menuItems: string[];
|
menuItems: string[];
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -5,19 +7,29 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MobileMenu({ menuItems, isOpen, onItemClick }: Props) {
|
export default function MobileMenu({ menuItems, isOpen, onItemClick }: Props) {
|
||||||
|
const firstButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && firstButtonRef.current) {
|
||||||
|
firstButtonRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="navbar__mobile-menu">
|
<nav id="mobile-menu" className="navbar__mobile-menu" role="navigation" aria-label="Mobile navigation">
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item, index) => (
|
||||||
<button
|
<button
|
||||||
key={item}
|
key={item}
|
||||||
|
ref={index === 0 ? firstButtonRef : undefined}
|
||||||
onClick={() => onItemClick(item)}
|
onClick={() => onItemClick(item)}
|
||||||
className="navbar__mobile-menu__item"
|
className="navbar__mobile-menu__item"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,16 @@ export default function Navbar({
|
||||||
))}
|
))}
|
||||||
<ThemeToggle theme={theme} setTheme={setTheme} />
|
<ThemeToggle theme={theme} setTheme={setTheme} />
|
||||||
{/* Burger Button für Mobile */}
|
{/* Burger Button für Mobile */}
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
className={`navbar__burger-button ${menuOpen ? 'active' : ''}`}
|
className={`navbar__burger-button ${menuOpen ? 'active' : ''}`}
|
||||||
aria-label={texts.navigation.mobileMenuAriaLabel}
|
aria-label={texts.navigation.mobileMenuAriaLabel}
|
||||||
|
aria-expanded={menuOpen}
|
||||||
|
aria-controls="mobile-menu"
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
>
|
>
|
||||||
{[0, 1, 2].map(i => <span key={i} className="navbar__burger-button__item"></span>)}
|
{[0, 1, 2].map(i => <span key={i} className="navbar__burger-button__item"></span>)}
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
menuItems={menuItems}
|
menuItems={menuItems}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="projects-section__action-button projects-section__action-button--secondary"
|
className="projects-section__action-button projects-section__action-button--secondary"
|
||||||
|
aria-label={`View ${project.title} source code on GitHub`}
|
||||||
>
|
>
|
||||||
<Github className="projects-section__action-icon" />
|
<Github className="projects-section__action-icon" />
|
||||||
{texts.codeButtonText}
|
{texts.codeButtonText}
|
||||||
|
|
@ -90,6 +91,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="projects-section__action-button projects-section__action-button--primary"
|
className="projects-section__action-button projects-section__action-button--primary"
|
||||||
|
aria-label={`View ${project.title} live demo`}
|
||||||
>
|
>
|
||||||
<ExternalLink className="projects-section__action-icon" />
|
<ExternalLink className="projects-section__action-icon" />
|
||||||
{texts.liveButtonText}
|
{texts.liveButtonText}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default function ServicesSection(props: ServicesSectionProps = {}) {
|
||||||
services = [
|
services = [
|
||||||
{
|
{
|
||||||
icon: Eye,
|
icon: Eye,
|
||||||
title: "Web Accessiblity",
|
title: "Web Accessibility",
|
||||||
description: "Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.",
|
description: "Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.",
|
||||||
colorClass: "primary"
|
colorClass: "primary"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,27 @@
|
||||||
@forward 'variables';
|
@forward 'variables';
|
||||||
@forward 'mixins';
|
@forward 'mixins';
|
||||||
|
|
||||||
|
// Skip link for accessibility
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
|
left: 6px;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
padding: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
z-index: 9999;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: top 0.3s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
top: 6px;
|
||||||
|
outline: 2px solid var(--color-secondary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Global button placeholder
|
// Global button placeholder
|
||||||
%button-base {
|
%button-base {
|
||||||
@include button-base;
|
@include button-base;
|
||||||
|
|
@ -39,15 +60,15 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
@ -72,4 +93,3 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
&__burger-button {
|
&__burger-button {
|
||||||
@include globals.flex-center();
|
@include globals.flex-center();
|
||||||
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
|
|
@ -42,31 +43,28 @@
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
display: block;
|
display: block;
|
||||||
height: 4px;
|
height: 3px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--color-text);
|
background-color: var(--color-text);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
margin-top: 2px;
|
margin: 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .navbar__container__burger-button__item {
|
&:hover &__item {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
.navbar__container__burger-button__item:nth-child(1) {
|
&__item:nth-child(1) {
|
||||||
transform: rotate(45deg) translate(5px, 5px);
|
transform: rotate(45deg) translate(5px, 5px);
|
||||||
}
|
}
|
||||||
.navbar__container__burger-button__item:nth-child(2) {
|
&__item:nth-child(2) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.navbar__container__burger-button__item:nth-child(3) {
|
&__item:nth-child(3) {
|
||||||
transform: rotate(-45deg) translate(5px, -5px);
|
transform: rotate(-45deg) translate(5px, -5px);
|
||||||
}
|
}
|
||||||
.navbar__container__burger-button__item:nth-child(n + 4) {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue