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