Browse Source
🌟 Added client import and CSS styles.
🌟 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
1 changed files with 171 additions and 0 deletions
@ -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; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue