Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HeritageHero } from "./HeritageHero";
import { HeritageOverViewSection } from "./HeritageOverviewSection";
import { HeritageSidebar } from "./HeritageSidebar";
import { HeritageGallery } from "./HeritageGallery";
import { Lightbox } from "./Lightbox.tsx";
import { DetailHeritageMap } from "@features/top/components/heritage-detail/DetailHeritageMap.tsx";
import { textType } from "@shared/styles/typography";
import { useSetBreadcrumbLabel } from "@features/breadcrumbs/BreadCrumbHooks.ts";
Expand Down Expand Up @@ -97,6 +98,7 @@ function KeyExamInfo({ item }: { item: WorldHeritageDetailVm }) {
export function HeritageDetailLayout({ item }: { item: WorldHeritageDetailVm }) {
const [search, setSearch] = useState<SearchValues>(DEFAULT_SEARCH);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const setLabel = useSetBreadcrumbLabel();
const navigate = useNavigate();
const text = useText();
Expand Down Expand Up @@ -189,7 +191,7 @@ export function HeritageDetailLayout({ item }: { item: WorldHeritageDetailVm })
{/* Left: Overview → Gallery */}
<div className="space-y-8" id="content">
<HeritageOverViewSection item={item} />
<HeritageGallery images={item.images} />
<HeritageGallery images={item.images} onSelectImage={setLightboxIndex} />
</div>

{/* Right: Sidebar (PC only) */}
Expand All @@ -198,6 +200,13 @@ export function HeritageDetailLayout({ item }: { item: WorldHeritageDetailVm })
</div>
</div>
</main>

<Lightbox
images={item.images}
index={lightboxIndex}
onClose={() => setLightboxIndex(null)}
onNavigate={setLightboxIndex}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function HeritageGallery({
images: WorldHeritageImageVm[];
previewCount?: number;
onOpenGallery?: () => void;
onSelectImage?: (img: Pick<WorldHeritageImageVm, "id" | "url" | "alt" | "credit">) => void;
onSelectImage?: (index: number) => void;
}) {
if (!images?.length) return null;

Expand Down Expand Up @@ -44,13 +44,11 @@ export function HeritageGallery({
</div>

<div className="mt-5 grid grid-cols-2 gap-3 sm:grid-cols-3">
{preview.map((img) => (
{preview.map((img, index) => (
<button
key={img.id}
type="button"
onClick={() =>
onSelectImage?.({ id: img.id, url: img.url, alt: img.alt, credit: img.credit })
}
onClick={() => onSelectImage?.(index)}
className="group text-left"
aria-label={img.alt ? `Open photo: ${img.alt}` : "Open photo"}
title={img.alt ?? "Open photo"}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useEffect } from "react";
import CloseIcon from "@mui/icons-material/Close";
import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
import IconButton from "@shared/uis/Icon-Button.tsx";
import type { WorldHeritageImageVm } from "../../../../../domain/types.ts";

type LightboxImage = Pick<WorldHeritageImageVm, "id" | "url" | "alt" | "credit">;

export function Lightbox({
images,
index,
onClose,
onNavigate,
}: {
images: LightboxImage[];
index: number | null;
onClose: () => void;
onNavigate: (index: number) => void;
}) {
const image = index !== null ? images[index] : null;
const hasPrev = index !== null && index > 0;
const hasNext = index !== null && index < images.length - 1;

useEffect(() => {
if (!image) return;

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
if (e.key === "ArrowLeft" && hasPrev) onNavigate(index! - 1);
if (e.key === "ArrowRight" && hasNext) onNavigate(index! + 1);
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [image, index, hasPrev, hasNext, onClose, onNavigate]);

if (!image) return null;

return (
<div
role="dialog"
aria-modal="true"
onClick={onClose}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4"
>
<IconButton
onClick={onClose}
aria-label="Close"
className="!absolute !right-4 !top-4 !text-white"
>
<CloseIcon />
</IconButton>

{hasPrev && (
<IconButton
onClick={(e) => {
e.stopPropagation();
onNavigate(index! - 1);
}}
aria-label="Previous photo"
className="!absolute !left-4 !top-1/2 !-translate-y-1/2 !text-white"
>
<NavigateBeforeIcon />
</IconButton>
)}

{hasNext && (
<IconButton
onClick={(e) => {
e.stopPropagation();
onNavigate(index! + 1);
}}
aria-label="Next photo"
className="!absolute !right-4 !top-1/2 !-translate-y-1/2 !text-white"
>
<NavigateNextIcon />
</IconButton>
)}

<figure className="max-h-full max-w-full" onClick={(e) => e.stopPropagation()}>
<img
src={image.url}
alt={image.alt ?? ""}
referrerPolicy="no-referrer"
className="max-h-[85vh] max-w-full rounded-lg object-contain"
/>
{image.credit && (
<figcaption className="mt-2 text-center text-sm text-zinc-300">
© {image.credit}
</figcaption>
)}
</figure>
</div>
);
}
Loading