Fixed 3 accessability issues
This commit is contained in:
parent
fd02a6e486
commit
336f06f2dc
3 changed files with 89 additions and 7 deletions
|
|
@ -105,11 +105,12 @@ p { font-size: 1rem; line-height: 1.6; color: var(--foreground); }
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 150ms ease;
|
transition: background 150ms ease;
|
||||||
}
|
}
|
||||||
.lang-switcher-btn:hover {
|
.lang-switcher-btn:hover,
|
||||||
background: rgba(255, 255, 255, 0.1);
|
.lang-switcher-btn:focus-visible {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
.lang-switcher-btn.current {
|
.lang-switcher-btn.current {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Markdown content */
|
/* Markdown content */
|
||||||
|
|
@ -526,7 +527,7 @@ p { font-size: 1rem; line-height: 1.6; color: var(--foreground); }
|
||||||
.lang-switcher {
|
.lang-switcher {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
background: var(--color-red);
|
background: transparent;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
|
@ -536,9 +537,15 @@ p { font-size: 1rem; line-height: 1.6; color: var(--foreground); }
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.375rem 0.625rem;
|
padding: 0.375rem 0.625rem;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 150ms ease;
|
||||||
|
}
|
||||||
|
.lang-switcher a:hover,
|
||||||
|
.lang-switcher a:focus-visible {
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
}
|
}
|
||||||
.lang-switcher a.current {
|
.lang-switcher a.current {
|
||||||
background: rgba(255,255,255,0.2);
|
background: rgba(255,255,255,0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
type ConstructionModalProps = {
|
type ConstructionModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -8,6 +8,8 @@ type ConstructionModalProps = {
|
||||||
|
|
||||||
export function ConstructionModal({ isOpen, onClose }: ConstructionModalProps) {
|
export function ConstructionModal({ isOpen, onClose }: ConstructionModalProps) {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
const previousActiveElement = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
|
@ -16,20 +18,80 @@ export function ConstructionModal({ isOpen, onClose }: ConstructionModalProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
|
previousActiveElement.current = document.activeElement as HTMLElement;
|
||||||
|
|
||||||
|
// Focus the button inside the modal
|
||||||
|
setTimeout(() => {
|
||||||
|
const button = modalRef.current?.querySelector("button");
|
||||||
|
button?.focus();
|
||||||
|
}, 0);
|
||||||
} else {
|
} else {
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = "";
|
||||||
|
// Restore focus to the element that opened the modal
|
||||||
|
previousActiveElement.current?.focus();
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = "";
|
||||||
};
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
// Focus trap: prevent Tab from leaving the modal
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen || !modalRef.current) return;
|
||||||
|
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key !== "Tab") return;
|
||||||
|
|
||||||
|
const focusableElements = modalRef.current?.querySelectorAll(
|
||||||
|
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
||||||
|
);
|
||||||
|
if (!focusableElements || focusableElements.length === 0) return;
|
||||||
|
|
||||||
|
const firstElement = focusableElements[0] as HTMLElement;
|
||||||
|
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
// Shift+Tab on first element -> focus last element
|
||||||
|
if (activeElement === firstElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
lastElement.focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Tab on last element -> focus first element
|
||||||
|
if (activeElement === lastElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
firstElement.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
// Close on Escape key
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
if (!mounted || !isOpen) return null;
|
if (!mounted || !isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="modal-overlay" onClick={onClose} aria-hidden="true" />
|
<div className="modal-overlay" onClick={onClose} aria-hidden="true" />
|
||||||
<div
|
<div
|
||||||
|
ref={modalRef}
|
||||||
className="modal-dialog"
|
className="modal-dialog"
|
||||||
role="alertdialog"
|
role="alertdialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,21 @@ const slides = [
|
||||||
|
|
||||||
export function ImageCarousel() {
|
export function ImageCarousel() {
|
||||||
const [index, setIndex] = useState(0);
|
const [index, setIndex] = useState(0);
|
||||||
|
const [isPlaying, setIsPlaying] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isPlaying) return;
|
||||||
|
|
||||||
const id = setInterval(() => {
|
const id = setInterval(() => {
|
||||||
setIndex((prev) => (prev + 1) % slides.length);
|
setIndex((prev) => (prev + 1) % slides.length);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, []);
|
}, [isPlaying]);
|
||||||
|
|
||||||
const goTo = (i: number) => setIndex(i % slides.length);
|
const goTo = (i: number) => setIndex(i % slides.length);
|
||||||
const prev = () => setIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
const prev = () => setIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
||||||
const next = () => setIndex((prev) => (prev + 1) % slides.length);
|
const next = () => setIndex((prev) => (prev + 1) % slides.length);
|
||||||
|
const togglePlayPause = () => setIsPlaying((prev) => !prev);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section aria-label="Destaques" className="carousel" role="region">
|
<section aria-label="Destaques" className="carousel" role="region">
|
||||||
|
|
@ -57,6 +61,15 @@ export function ImageCarousel() {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={togglePlayPause}
|
||||||
|
aria-label={isPlaying ? "Pausar carrossel" : "Reproduzir carrossel"}
|
||||||
|
className="carousel-btn carousel-play-btn"
|
||||||
|
title={isPlaying ? "Pausar" : "Reproduzir"}
|
||||||
|
>
|
||||||
|
{isPlaying ? "⏸" : "▶"}
|
||||||
|
</button>
|
||||||
<button type="button" onClick={next} aria-label="Próxima imagem" className="carousel-btn">›</button>
|
<button type="button" onClick={next} aria-label="Próxima imagem" className="carousel-btn">›</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue