kopia lustrzana https://github.com/cloudflare/wildebeest
improve images modal
improve the modal by: - allowing the navigation of images - making sure that the whole image is visible - adding controls - extracting the modal as a standalone componentpull/162/head
rodzic
d77df85b6d
commit
88f6dbf676
|
@ -1,8 +1,9 @@
|
|||
import { component$, useStore, $ } from '@builder.io/qwik'
|
||||
import { component$, useStore, PropFunction } from '@builder.io/qwik'
|
||||
import { MediaAttachment } from '~/types'
|
||||
|
||||
type Props = {
|
||||
mediaAttachment: MediaAttachment
|
||||
onOpenImagesModal$: PropFunction<(id: string) => void>
|
||||
}
|
||||
|
||||
export const focusToObjectFit = (focus: { x: number; y: number }) => {
|
||||
|
@ -15,7 +16,7 @@ export const focusToObjectFit = (focus: { x: number; y: number }) => {
|
|||
return { x: Math.floor(x2 * 100) / 100, y: Math.floor(y2 * 100) / 100 }
|
||||
}
|
||||
|
||||
export default component$<Props>(({ mediaAttachment }) => {
|
||||
export default component$<Props>(({ mediaAttachment, onOpenImagesModal$ }) => {
|
||||
const store = useStore({
|
||||
isModalOpen: false,
|
||||
})
|
||||
|
@ -25,35 +26,17 @@ export default component$<Props>(({ mediaAttachment }) => {
|
|||
objectFit = focusToObjectFit(mediaAttachment.meta.focus)
|
||||
}
|
||||
|
||||
const onPreviewClick = $(() => {
|
||||
document.body.style.overflowY = 'hidden'
|
||||
store.isModalOpen = true
|
||||
})
|
||||
|
||||
const onModalClose = $(() => {
|
||||
document.body.style.overflowY = 'scroll'
|
||||
store.isModalOpen = false
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class={`${store.isModalOpen ? '' : 'cursor-zoom-in'} w-full h-full`}>
|
||||
<img
|
||||
class="object-cover w-full h-full rounded"
|
||||
class="object-cover w-full h-full rounded cursor-pointer"
|
||||
style={{
|
||||
...(objectFit && { 'object-position': `${objectFit.x}% ${objectFit.y}%` }),
|
||||
}}
|
||||
src={mediaAttachment.preview_url || mediaAttachment.url}
|
||||
onClick$={onPreviewClick}
|
||||
onClick$={() => onOpenImagesModal$(mediaAttachment.id)}
|
||||
/>
|
||||
{store.isModalOpen && (
|
||||
<div class="relative pointer-events-auto z-50">
|
||||
<div class="overlay inset-0 fixed z-60 bg-black opacity-70"></div>
|
||||
<div class="fixed z-70 inset-0 grid place-items-center" onClick$={onModalClose}>
|
||||
<img src={mediaAttachment.url} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { component$, useSignal, PropFunction } from '@builder.io/qwik'
|
||||
import { MediaAttachment } from '~/types'
|
||||
|
||||
type Props = {
|
||||
images: MediaAttachment[]
|
||||
idxOfCurrentImage: number
|
||||
onCloseImagesModal$: PropFunction<() => void>
|
||||
}
|
||||
|
||||
export const ImagesModal = component$<Props>(({ images, idxOfCurrentImage: initialIdx, onCloseImagesModal$ }) => {
|
||||
const idxOfCurrentImage = useSignal(initialIdx)
|
||||
|
||||
return (
|
||||
<div class="pointer-events-auto cursor-default z-50 fixed inset-0 isolate flex items-center justify-between backdrop-blur-sm">
|
||||
<div class="inset-0 absolute z-[-1] bg-wildebeest-900 opacity-70" onClick$={() => onCloseImagesModal$()}></div>
|
||||
{images.length > 1 && (
|
||||
<button
|
||||
class="cursor-pointer text-4xl opacity-60 hover:opacity-90 focus-visible:opacity-90"
|
||||
onClick$={() => {
|
||||
const idx = idxOfCurrentImage.value - 1
|
||||
idxOfCurrentImage.value = idx < 0 ? images.length - 1 : idx
|
||||
}}
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left ml-5"></i>
|
||||
</button>
|
||||
)}
|
||||
<img class="ma max-w-[80vw] max-h-[90vh] m-auto" src={images[idxOfCurrentImage.value].url} />
|
||||
{images.length > 1 && (
|
||||
<button
|
||||
class="cursor-pointer text-4xl opacity-60 hover:opacity-90 focus-visible:opacity-90"
|
||||
onClick$={() => {
|
||||
idxOfCurrentImage.value = (idxOfCurrentImage.value + 1) % images.length
|
||||
}}
|
||||
>
|
||||
<i class="fa-solid fa-chevron-right mr-5"></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
class="cursor-pointer absolute top-7 right-7 text-4xl opacity-60 hover:opacity-90 focus-visible:opacity-90"
|
||||
onClick$={() => onCloseImagesModal$()}
|
||||
>
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})
|
|
@ -1,17 +0,0 @@
|
|||
import { component$ } from '@builder.io/qwik'
|
||||
import Image from './Image'
|
||||
import Video from './Video'
|
||||
import { MediaAttachment } from '~/types'
|
||||
|
||||
type Props = {
|
||||
mediaAttachment: MediaAttachment
|
||||
}
|
||||
|
||||
export default component$<Props>(({ mediaAttachment }) => {
|
||||
return (
|
||||
<>
|
||||
{mediaAttachment.type === 'image' ? <Image mediaAttachment={mediaAttachment} /> : ''}
|
||||
{mediaAttachment.type === 'video' ? <Video mediaAttachment={mediaAttachment} /> : ''}
|
||||
</>
|
||||
)
|
||||
})
|
|
@ -1,7 +1,9 @@
|
|||
import { component$, useStylesScoped$ } from '@builder.io/qwik'
|
||||
import { component$, useStylesScoped$, $, useStore } from '@builder.io/qwik'
|
||||
import { MediaAttachment } from '~/types'
|
||||
import Media from './Media'
|
||||
import Image from './Image'
|
||||
import Video from './Video'
|
||||
import styles from './index.scss?inline'
|
||||
import { ImagesModal } from './ImagesModal'
|
||||
|
||||
type Props = {
|
||||
medias: MediaAttachment[]
|
||||
|
@ -10,17 +12,44 @@ type Props = {
|
|||
export const MediaGallery = component$<Props>(({ medias }) => {
|
||||
useStylesScoped$(styles)
|
||||
|
||||
const images = medias.filter((media) => media.type === 'image')
|
||||
|
||||
const imagesModalState = useStore<{ isOpen: boolean; idxOfCurrentImage: number }>({
|
||||
isOpen: false,
|
||||
idxOfCurrentImage: 0,
|
||||
})
|
||||
|
||||
const onOpenImagesModal = $((imgId: string) => {
|
||||
document.body.style.overflowY = 'hidden'
|
||||
imagesModalState.isOpen = true
|
||||
const idx = images.findIndex(({ id }) => id === imgId)
|
||||
imagesModalState.idxOfCurrentImage = idx === -1 ? 0 : idx
|
||||
})
|
||||
|
||||
const onCloseImagesModal = $(() => {
|
||||
document.body.style.overflowY = 'scroll'
|
||||
imagesModalState.isOpen = false
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!medias.length && (
|
||||
<div class={`media-gallery overflow-hidden grid gap-1 h-52 md:h-60 lg:h-72 xl:h-80`}>
|
||||
{medias.map((media) => (
|
||||
<div class="w-full flex items-center justify-center overflow-hidden bg-black">
|
||||
<Media mediaAttachment={media} />
|
||||
{media.type === 'image' && <Image mediaAttachment={media} onOpenImagesModal$={onOpenImagesModal} />}
|
||||
{media.type === 'video' && <Video mediaAttachment={media} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{imagesModalState.isOpen && (
|
||||
<ImagesModal
|
||||
images={images}
|
||||
idxOfCurrentImage={imagesModalState.idxOfCurrentImage}
|
||||
onCloseImagesModal$={onCloseImagesModal}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
Ładowanie…
Reference in New Issue