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;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
.lang-switcher-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
.lang-switcher-btn:hover,
|
||||
.lang-switcher-btn:focus-visible {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.lang-switcher-btn.current {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
/* Markdown content */
|
||||
|
|
@ -526,7 +527,7 @@ p { font-size: 1rem; line-height: 1.6; color: var(--foreground); }
|
|||
.lang-switcher {
|
||||
display: inline-flex;
|
||||
gap: 0.25rem;
|
||||
background: var(--color-red);
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
border-radius: 999px;
|
||||
padding: 0.25rem;
|
||||
|
|
@ -536,9 +537,15 @@ p { font-size: 1rem; line-height: 1.6; color: var(--foreground); }
|
|||
color: #fff;
|
||||
padding: 0.375rem 0.625rem;
|
||||
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 {
|
||||
background: rgba(255,255,255,0.2);
|
||||
background: rgba(255,255,255,0.25);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type ConstructionModalProps = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -8,6 +8,8 @@ type ConstructionModalProps = {
|
|||
|
||||
export function ConstructionModal({ isOpen, onClose }: ConstructionModalProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const previousActiveElement = useRef<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
|
@ -16,20 +18,80 @@ export function ConstructionModal({ isOpen, onClose }: ConstructionModalProps) {
|
|||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
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 {
|
||||
document.body.style.overflow = "";
|
||||
// Restore focus to the element that opened the modal
|
||||
previousActiveElement.current?.focus();
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [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;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="modal-overlay" onClick={onClose} aria-hidden="true" />
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="modal-dialog"
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
|
|
|
|||
|
|
@ -10,17 +10,21 @@ const slides = [
|
|||
|
||||
export function ImageCarousel() {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
const id = setInterval(() => {
|
||||
setIndex((prev) => (prev + 1) % slides.length);
|
||||
}, 5000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
}, [isPlaying]);
|
||||
|
||||
const goTo = (i: number) => setIndex(i % slides.length);
|
||||
const prev = () => setIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
||||
const next = () => setIndex((prev) => (prev + 1) % slides.length);
|
||||
const togglePlayPause = () => setIsPlaying((prev) => !prev);
|
||||
|
||||
return (
|
||||
<section aria-label="Destaques" className="carousel" role="region">
|
||||
|
|
@ -57,6 +61,15 @@ export function ImageCarousel() {
|
|||
/>
|
||||
))}
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Reference in a new issue