84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { t } from '@/lib/i18n'
|
|
|
|
interface SkipLink {
|
|
id: string
|
|
label: string
|
|
targetId: string
|
|
}
|
|
|
|
const SKIP_LINKS: SkipLink[] = [
|
|
{ id: 'skip-main', label: 'navigation.skipToMain', targetId: 'main-content' },
|
|
{ id: 'skip-filters', label: 'navigation.skipToFilters', targetId: 'filters-section' },
|
|
{ id: 'skip-articles', label: 'navigation.skipToArticles', targetId: 'articles-section' },
|
|
]
|
|
|
|
export function SkipLinks(): React.ReactElement | null {
|
|
const [isVisible, setIsVisible] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent): void => {
|
|
if (e.key === 'Tab' && !e.shiftKey) {
|
|
setIsVisible(true)
|
|
}
|
|
}
|
|
const handleKeyUp = (e: KeyboardEvent): void => {
|
|
if (e.key === 'Tab' && !e.shiftKey) {
|
|
setIsVisible(true)
|
|
}
|
|
}
|
|
const handleClick = (): void => {
|
|
setIsVisible(false)
|
|
}
|
|
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
document.addEventListener('keyup', handleKeyUp)
|
|
document.addEventListener('click', handleClick, true)
|
|
document.addEventListener('mousedown', handleClick, true)
|
|
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown)
|
|
document.removeEventListener('keyup', handleKeyUp)
|
|
document.removeEventListener('click', handleClick, true)
|
|
document.removeEventListener('mousedown', handleClick, true)
|
|
}
|
|
}, [])
|
|
|
|
if (!isVisible) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="sr-only focus-within:not-sr-only focus-within:absolute focus-within:z-50 focus-within:top-4 focus-within:left-4">
|
|
<nav aria-label={t('navigation.skipLinks')} className="flex flex-col gap-2">
|
|
{SKIP_LINKS.map((link) => (
|
|
<SkipLinkItem key={link.id} link={link} onFocus={() => setIsVisible(true)} />
|
|
))}
|
|
</nav>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SkipLinkItem({ link, onFocus }: { link: SkipLink; onFocus: () => void }): React.ReactElement {
|
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>): void => {
|
|
e.preventDefault()
|
|
const target = document.getElementById(link.targetId)
|
|
if (target) {
|
|
target.focus()
|
|
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<a
|
|
href={`#${link.targetId}`}
|
|
onClick={handleClick}
|
|
onFocus={onFocus}
|
|
className="bg-neon-cyan text-cyber-darker px-4 py-2 rounded-lg font-semibold focus:outline-none focus:ring-2 focus:ring-neon-green focus:ring-offset-2 focus:ring-offset-cyber-darker"
|
|
aria-label={t(link.label)}
|
|
>
|
|
{t(link.label)}
|
|
</a>
|
|
)
|
|
}
|