feat: carousel dot
This commit is contained in:
12
package-lock.json
generated
12
package-lock.json
generated
@@ -5200,11 +5200,6 @@
|
|||||||
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/embla-carousel": {
|
|
||||||
"version": "8.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz",
|
|
||||||
"integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A=="
|
|
||||||
},
|
|
||||||
"node_modules/embla-carousel-react": {
|
"node_modules/embla-carousel-react": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.5.1.tgz",
|
||||||
@@ -5217,7 +5212,12 @@
|
|||||||
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/embla-carousel-reactive-utils": {
|
"node_modules/embla-carousel-react/node_modules/embla-carousel": {
|
||||||
|
"version": "8.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz",
|
||||||
|
"integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A=="
|
||||||
|
},
|
||||||
|
"node_modules/embla-carousel-react/node_modules/embla-carousel-reactive-utils": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.5.1.tgz",
|
||||||
"integrity": "sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A==",
|
"integrity": "sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A==",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Carousel, CarouselContent, CarouselItem } from '@/components/ui/carousel'
|
import { Carousel, CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel'
|
||||||
import { TImageInfo } from '@/types'
|
import { TImageInfo } from '@/types'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Lightbox from 'yet-another-react-lightbox'
|
import Lightbox from 'yet-another-react-lightbox'
|
||||||
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
|
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
|
||||||
import Image from '../Image'
|
import Image from '../Image'
|
||||||
@@ -13,16 +13,35 @@ export function ImageCarousel({
|
|||||||
images: TImageInfo[]
|
images: TImageInfo[]
|
||||||
isNsfw?: boolean
|
isNsfw?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [index, setIndex] = useState(-1)
|
const [api, setApi] = useState<CarouselApi>()
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
const [lightboxIndex, setLightboxIndex] = useState(-1)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentIndex(api.selectedScrollSnap())
|
||||||
|
|
||||||
|
api.on('select', () => {
|
||||||
|
setCurrentIndex(api.selectedScrollSnap())
|
||||||
|
})
|
||||||
|
}, [api])
|
||||||
|
|
||||||
const handlePhotoClick = (event: React.MouseEvent, current: number) => {
|
const handlePhotoClick = (event: React.MouseEvent, current: number) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setIndex(current)
|
setLightboxIndex(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDotClick = (index: number) => {
|
||||||
|
api?.scrollTo(index)
|
||||||
|
setCurrentIndex(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Carousel className="w-full">
|
<Carousel className="w-full" setApi={setApi}>
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{images.map((image, index) => (
|
{images.map((image, index) => (
|
||||||
<CarouselItem key={index}>
|
<CarouselItem key={index}>
|
||||||
@@ -31,12 +50,15 @@ export function ImageCarousel({
|
|||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
{images.length > 1 && (
|
||||||
|
<CarouselDot total={images.length} currentIndex={currentIndex} onClick={onDotClick} />
|
||||||
|
)}
|
||||||
<Lightbox
|
<Lightbox
|
||||||
index={index}
|
index={lightboxIndex}
|
||||||
slides={images.map(({ url }) => ({ src: url }))}
|
slides={images.map(({ url }) => ({ src: url }))}
|
||||||
plugins={[Zoom]}
|
plugins={[Zoom]}
|
||||||
open={index >= 0}
|
open={lightboxIndex >= 0}
|
||||||
close={() => setIndex(-1)}
|
close={() => setLightboxIndex(-1)}
|
||||||
controller={{
|
controller={{
|
||||||
closeOnBackdropClick: true,
|
closeOnBackdropClick: true,
|
||||||
closeOnPullUp: true,
|
closeOnPullUp: true,
|
||||||
@@ -48,3 +70,25 @@ export function ImageCarousel({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CarouselDot({
|
||||||
|
total,
|
||||||
|
currentIndex,
|
||||||
|
onClick
|
||||||
|
}: {
|
||||||
|
total: number
|
||||||
|
currentIndex: number
|
||||||
|
onClick: (index: number) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex gap-1 justify-center">
|
||||||
|
{Array.from({ length: total }).map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`w-2 h-2 rounded-full ${index === currentIndex ? 'bg-foreground/40' : 'bg-muted'}`}
|
||||||
|
onClick={() => onClick(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user