fix(a11y): add ARIA attributes to interactive elements
- ImageBlock: wrap clickable image in <button> with aria-label, add role=dialog and aria-modal to lightbox overlay - KeyboardShortcuts: add role=dialog and aria-modal to modal overlay - CodeBlock: add aria-label to copy button - LanguageSelector: add aria-label with current language
This commit is contained in:
@@ -26,6 +26,7 @@ export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
|||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
className="absolute top-2 right-2 p-1.5 rounded-lg bg-zinc-700/60 hover:bg-zinc-600/80 border border-white/10 text-zinc-400 hover:text-zinc-200 opacity-0 group-hover/code:opacity-100 transition-opacity duration-150"
|
className="absolute top-2 right-2 p-1.5 rounded-lg bg-zinc-700/60 hover:bg-zinc-600/80 border border-white/10 text-zinc-400 hover:text-zinc-200 opacity-0 group-hover/code:opacity-100 transition-opacity duration-150"
|
||||||
title="Copy code"
|
title="Copy code"
|
||||||
|
aria-label="Copy code to clipboard"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{copied
|
{copied
|
||||||
|
|||||||
@@ -18,11 +18,15 @@ function Lightbox({ src, alt, onClose }: ImageBlockProps & { onClose: () => void
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label={alt || 'Image preview'}
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm animate-fade-in"
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm animate-fade-in"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
aria-label="Close preview"
|
||||||
className="absolute top-4 right-4 p-2 rounded-full bg-zinc-800/80 border border-white/10 text-zinc-300 hover:text-white hover:bg-zinc-700/80 transition-colors"
|
className="absolute top-4 right-4 p-2 rounded-full bg-zinc-800/80 border border-white/10 text-zinc-300 hover:text-white hover:bg-zinc-700/80 transition-colors"
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
@@ -43,13 +47,19 @@ export function ImageBlock({ src, alt }: ImageBlockProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<img
|
<button
|
||||||
src={src}
|
type="button"
|
||||||
alt={alt || 'Image'}
|
|
||||||
className="max-w-full max-h-80 rounded-xl border border-white/8 cursor-pointer hover:brightness-110 transition-all"
|
|
||||||
onClick={() => setLightbox(true)}
|
onClick={() => setLightbox(true)}
|
||||||
loading="lazy"
|
aria-label={`View ${alt || 'image'} full size`}
|
||||||
/>
|
className="block rounded-xl border border-white/8 cursor-pointer hover:brightness-110 transition-all focus:outline-none focus:ring-2 focus:ring-cyan-400/40"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={alt || 'Image'}
|
||||||
|
className="max-w-full max-h-80 rounded-xl"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{lightbox && <Lightbox src={src} alt={alt} onClose={() => setLightbox(false)} />}
|
{lightbox && <Lightbox src={src} alt={alt} onClose={() => setLightbox(false)} />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function KeyboardShortcuts({ open, onClose }: Props) {
|
|||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose}>
|
<div role="dialog" aria-modal="true" aria-label={t('shortcuts.title')} className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose}>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function LanguageSelector() {
|
|||||||
onClick={cycle}
|
onClick={cycle}
|
||||||
className="flex items-center gap-1.5 rounded-2xl border border-white/8 bg-zinc-800/30 px-2.5 py-1.5 text-xs text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors"
|
className="flex items-center gap-1.5 rounded-2xl border border-white/8 bg-zinc-800/30 px-2.5 py-1.5 text-xs text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors"
|
||||||
title="Change language"
|
title="Change language"
|
||||||
|
aria-label={`Language: ${localeLabels[current] || current}. Click to change.`}
|
||||||
>
|
>
|
||||||
<Globe size={14} />
|
<Globe size={14} />
|
||||||
<span className="font-medium">{localeLabels[current] || current.toUpperCase()}</span>
|
<span className="font-medium">{localeLabels[current] || current.toUpperCase()}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user