67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { useId, useState } from "react";
|
||
import { ArchitectureFigure } from "./ArchitectureFigure";
|
||
|
||
type Slide = { source: string; alt: string };
|
||
|
||
export function FigureGallery({ slides }: { slides: Slide[] }) {
|
||
const [active, setActive] = useState(0);
|
||
const viewport = useId();
|
||
const slide = slides[active];
|
||
if (!slide) return null;
|
||
|
||
function move(offset: number) {
|
||
setActive((current) => (current + offset + slides.length) % slides.length);
|
||
}
|
||
|
||
return (
|
||
<section
|
||
className="figure-gallery"
|
||
role="region"
|
||
aria-roledescription="轮播"
|
||
aria-label="文档配图"
|
||
onKeyDown={(event) => {
|
||
if ((event.target as HTMLElement).closest("dialog")) return;
|
||
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
||
event.preventDefault();
|
||
move(event.key === "ArrowLeft" ? -1 : 1);
|
||
}
|
||
}}
|
||
>
|
||
<div
|
||
id={viewport}
|
||
role="group"
|
||
aria-roledescription="幻灯片"
|
||
aria-label={`第 ${active + 1} 张,共 ${slides.length} 张`}
|
||
>
|
||
<ArchitectureFigure key={slide.source} source={slide.source} alt={slide.alt} />
|
||
</div>
|
||
<div className="figure-gallery-controls">
|
||
<button type="button" className="figure-gallery-arrow" aria-label="上一张图片" aria-controls={viewport} onClick={() => move(-1)}>
|
||
<span aria-hidden="true">‹</span>
|
||
</button>
|
||
<div className="figure-gallery-dots" role="group" aria-label="选择图片">
|
||
{slides.map((item, index) => (
|
||
<button
|
||
key={item.source}
|
||
type="button"
|
||
className="figure-gallery-dot"
|
||
aria-label={`显示第 ${index + 1} 张图片`}
|
||
aria-current={index === active ? "true" : undefined}
|
||
aria-controls={viewport}
|
||
onClick={() => setActive(index)}
|
||
>
|
||
<span aria-hidden="true" />
|
||
</button>
|
||
))}
|
||
</div>
|
||
<button type="button" className="figure-gallery-arrow" aria-label="下一张图片" aria-controls={viewport} onClick={() => move(1)}>
|
||
<span aria-hidden="true">›</span>
|
||
</button>
|
||
</div>
|
||
<p className="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
||
第 {active + 1} 张,共 {slides.length} 张:{slide.alt}
|
||
</p>
|
||
</section>
|
||
);
|
||
}
|