Browse Source

🌟 Added client import and CSS styles.

📸 Imported Next.js Image and navigation utilities.
🔄 Implemented photo mapping and URL parameter handling.
🔍 Added photo click handling and navigation.
🖼️ Created the ListingImageGallery component.
main
John Doe 1 year ago
parent
commit
f1298d6121
  1. 171
      src/components/listing-image-gallery/ListingImageGallery.tsx

171
src/components/listing-image-gallery/ListingImageGallery.tsx

@ -0,0 +1,171 @@
"use client";
import "./styles/index.css";
import Image from "next/image";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { FC, Fragment, useEffect, useRef } from "react";
import Modal from "./components/Modal";
import type { ListingGalleryImage } from "./utils/types";
import { useLastViewedPhoto } from "./utils/useLastViewedPhoto";
import { ArrowSmallLeftIcon } from "@heroicons/react/24/outline";
import { Dialog, Transition } from "@headlessui/react";
import LikeSaveBtns from "../LikeSaveBtns";
import { Route } from "next";
const PHOTOS: string[] = [
"https://images.pexels.com/photos/6129967/pexels-photo-6129967.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
"https://images.pexels.com/photos/7163619/pexels-photo-7163619.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6527036/pexels-photo-6527036.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6969831/pexels-photo-6969831.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6438752/pexels-photo-6438752.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/1320686/pexels-photo-1320686.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/261394/pexels-photo-261394.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/2861361/pexels-photo-2861361.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
];
export const DEMO_IMAGE: ListingGalleryImage[] = [...PHOTOS].map(
(item, index): ListingGalleryImage => {
return {
id: index,
url: item,
};
}
);
export const getNewParam = ({
paramName = "photoId",
value,
}: {
paramName?: string;
value: string | number;
}) => {
let params = new URLSearchParams(document.location.search);
params.set(paramName, String(value));
return params.toString();
};
interface Props {
images?: ListingGalleryImage[];
onClose?: () => void;
isShowModal: boolean;
}
const ListingImageGallery: FC<Props> = ({
images = DEMO_IMAGE,
onClose,
isShowModal,
}) => {
const searchParams = useSearchParams();
const photoId = searchParams?.get("photoId");
const router = useRouter();
const [lastViewedPhoto, setLastViewedPhoto] = useLastViewedPhoto();
const lastViewedPhotoRef = useRef<HTMLDivElement>(null);
const thisPathname = usePathname();
useEffect(() => {
// This effect keeps track of the last viewed photo in the modal to keep the index page in sync when the user navigates back
if (lastViewedPhoto && !photoId) {
lastViewedPhotoRef.current?.scrollIntoView({ block: "center" });
setLastViewedPhoto(null);
}
}, [photoId, lastViewedPhoto, setLastViewedPhoto]);
const handleClose = () => {
onClose && onClose();
};
const renderContent = () => {
return (
<div className=" ">
{photoId && (
<Modal
images={images}
onClose={() => {
// @ts-ignore
setLastViewedPhoto(photoId);
let params = new URLSearchParams(document.location.search);
params.delete("photoId");
router.push(`${thisPathname}/?${params.toString()}` as Route);
}}
/>
)}
<div className="columns-1 gap-4 sm:columns-2 xl:columns-3">
{images.map(({ id, url }) => (
<div
key={id}
onClick={() => {
const newPathname = getNewParam({ value: id });
router.push(`${thisPathname}/?${newPathname}` as Route);
}}
ref={id === Number(lastViewedPhoto) ? lastViewedPhotoRef : null}
className="after:content group relative mb-5 block w-full cursor-zoom-in after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:shadow-highlight focus:outline-none"
>
<Image
alt="chisfis listing gallery "
className="transform rounded-lg brightness-90 transition will-change-auto group-hover:brightness-110 focus:outline-none"
style={{
transform: "translate3d(0, 0, 0)",
}}
src={url}
width={720}
height={480}
sizes="(max-width: 640px) 100vw, (max-width: 1280px) 50vw, 350px"
/>
</div>
))}
</div>
</div>
);
};
return (
<>
<Transition appear show={isShowModal} as={Fragment}>
<Dialog as="div" className="relative z-40" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-white" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="sticky z-10 top-0 p-4 xl:px-10 flex items-center justify-between bg-white">
<button
className="focus:outline-none focus:ring-0 w-10 h-10 rounded-full flex items-center justify-center hover:bg-neutral-100"
onClick={handleClose}
>
<ArrowSmallLeftIcon className="w-6 h-6" />
</button>
<LikeSaveBtns />
</div>
<div className="flex min-h-full items-center justify-center sm:p-4 pt-0 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-5"
enterTo="opacity-100 translate-y-0"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-5"
>
<Dialog.Panel className="w-full max-w-screen-lg mx-auto transform p-4 pt-0 text-left transition-all ">
{renderContent()}
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
};
export default ListingImageGallery;
Loading…
Cancel
Save