import { motion, AnimatePresence } from 'framer-motion'
import Autoplay from 'embla-carousel-autoplay'
import useEmblaCarousel from 'embla-carousel-react'
import ClassNames from 'embla-carousel-class-names'
import { cn } from '@/lib/utils'
import preview from '@/assets/preview'
type UseDotButtonType = {
onDotButtonClick: (index: number) => void
interface CarouselProps {
children: React.ReactNode
options: EmblaOptionsType
interface CarouselContextType {
onPrevButtonClick: () => void
onNextButtonClick: () => void
const CarouselContext = createContext<CarouselContextType | undefined>(
const TWEEN_FACTOR_BASE = 0.52
const numberWithinRange = (number: number, min: number, max: number): number =>
Math.min(Math.max(number, min), max)
export const useCarouselContext = () => {
const context = useContext(CarouselContext)
throw new Error('useCarouselContext must be used within a CarouselProvider')
const Carousel: React.FC<CarouselProps> = ({
const carouselId = useId()
const [slidesrArr, setSlidesArr] = useState<Element[]>([])
plugins.push(ClassNames())
stopOnInteraction: false,
const [emblaRef, emblaApi] = useEmblaCarousel(options, plugins)
const [selectedThumbIndex, setSelectedThumbIndex] = useState(0)
const [emblaThumbsRef, emblaThumbsApi] = useEmblaCarousel({
containScroll: 'keepSnaps',
const onThumbClick = useCallback(
if (!emblaApi || !emblaThumbsApi) return
[emblaApi, emblaThumbsApi]
const onSelect = useCallback(() => {
if (!emblaApi || !emblaThumbsApi) return
setSelectedThumbIndex(emblaApi.selectedScrollSnap()) // Use setSelectedThumbIndex here
emblaThumbsApi.scrollTo(emblaApi.selectedScrollSnap())
}, [emblaApi, emblaThumbsApi, setSelectedThumbIndex])
emblaApi.on('select', onSelect)
emblaApi.on('reInit', onSelect)
const { selectedIndex, scrollSnaps, onDotButtonClick } =
const [scrollProgress, setScrollProgress] = useState(0)
} = usePrevNextButtons(emblaApi)
const onScroll = useCallback((emblaApi: EmblaCarouselType) => {
const progress = Math.max(0, Math.min(1, emblaApi.scrollProgress()))
setScrollProgress(progress * 100)
emblaApi.on('reInit', onScroll)
emblaApi.on('scroll', onScroll)
const { selectedSnap, snapCount } = useSelectedSnapDisplay(emblaApi)
const tweenFactor = useRef(0)
const tweenNodes = useRef<HTMLElement[]>([])
const setTweenNodes = useCallback(
(emblaApi: EmblaCarouselType): void => {
tweenNodes.current = emblaApi.slideNodes().map((slideNode, index) => {
const node = slideNode.querySelector('.slider_content') as HTMLElement
console.warn(`No .slider_content found for slide ${index}`)
const setTweenFactor = useCallback(
(emblaApi: EmblaCarouselType) => {
tweenFactor.current = TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length
const tweenScale = useCallback(
(emblaApi: EmblaCarouselType, eventName?: EmblaEventType) => {
const engine = emblaApi.internalEngine()
const scrollProgress = emblaApi.scrollProgress()
const slidesInView = emblaApi.slidesInView()
const isScrollEvent = eventName === 'scroll'
emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => {
let diffToTarget = scrollSnap - scrollProgress
const slidesInSnap = engine.slideRegistry[snapIndex]
slidesInSnap.forEach((slideIndex) => {
if (isScrollEvent && !slidesInView.includes(slideIndex)) return
if (engine.options.loop) {
engine.slideLooper.loopPoints.forEach((loopItem) => {
const target = loopItem.target()
if (slideIndex === loopItem.index && target !== 0) {
const sign = Math.sign(target)
diffToTarget = scrollSnap - (1 + scrollProgress)
diffToTarget = scrollSnap + (1 - scrollProgress)
const tweenValue = 1 - Math.abs(diffToTarget * tweenFactor.current)
const scale = numberWithinRange(tweenValue, 0, 1).toString()
const tweenNode = tweenNodes.current[slideIndex]
tweenNode.style.transform = `scale(${scale})`
.on('reInit', setTweenNodes)
.on('reInit', setTweenFactor)
.on('reInit', tweenScale)
.on('scroll', tweenScale)
}, [emblaApi, tweenScale, isScale])
<CarouselContext.Provider
className={cn(className, 'overflow-hidden rounded-md ')}
</CarouselContext.Provider>
children: React.ReactNode
export const SliderContainer = ({
className={cn('flex', className)}
style={{ touchAction: 'pan-y pinch-zoom' }}
export const Slider: React.FC<SliderProps> = ({
const { isScale, setSlidesArr } = useCarouselContext()
const addImgToSlider = useCallback(() => {
setSlidesArr((prev: any) => [...prev, thumnailSrc])
<div className={cn('min-w-0 flex-grow-0 flex-shrink-0', className)}>
<div className="slider_content">{children}</div>
export const SliderPrevButton = ({
const { onPrevButtonClick, prevBtnDisabled }: any = useCarouselContext()
className={cn('', className)}
onClick={onPrevButtonClick}
disabled={prevBtnDisabled}
export const SliderNextButton = ({
const { onNextButtonClick, nextBtnDisabled }: any = useCarouselContext()
className={cn('', className)}
onClick={onNextButtonClick}
disabled={nextBtnDisabled}
export const SliderProgress = ({ className }: { className?: string }) => {
const { scrollProgress }: any = useCarouselContext()
' bg-gray-500 relative rounded-md h-2 justify-end items-center w-96 max-w-[90%] overflow-hidden',
className="dark:bg-white bg-black absolute w-full top-0 -left-[100%] bottom-0"
style={{ transform: `translate3d(${scrollProgress}%,0px,0px)` }}
export const SliderSnapDisplay = ({ className }: { className?: string }) => {
const { selectedSnap, snapCount } = useCarouselContext()
const prevSnapRef = useRef(selectedSnap)
const [direction, setDirection] = useState<number>(0)
setDirection(selectedSnap > prevSnapRef.current ? 1 : -1)
prevSnapRef.current = selectedSnap
'mix-blend-difference overflow-hidden flex gap-1 items-center',
<AnimatePresence mode="wait" initial={false} custom={direction}>
initial={(d: number) => ({ y: d * 20, opacity: 0 })}
animate={{ y: 0, opacity: 1 }}
exit={(d: number) => ({ y: d * -20, opacity: 0 })}
<span>/ {snapCount}</span>
export const SliderDotButton = ({ className }: { className?: string }) => {
const { selectedIndex, scrollSnaps, onDotButtonClick, carouselId }: any =
<div className={cn('flex', className)}>
<div className="flex gap-2">
{scrollSnaps.map((_: any, index: React.Key | null | undefined) => (
onClick={() => onDotButtonClick(index)}
className={`relative inline-flex p-0 m-0 w-10 h-2 `}
<div className=" bg-gray-500/40 h-2 rounded-sm w-10 "></div>
{index === selectedIndex && (
<AnimatePresence mode="wait">
layoutId={`hover-${carouselId}`}
className="absolute z-[3] w-full h-full left-0 top-0 dark:bg-white bg-black rounded-full"
export const useDotButton = (
emblaApi: EmblaCarouselType | undefined
const [selectedIndex, setSelectedIndex] = useState(0)
const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
const onDotButtonClick = useCallback(
const onInit = useCallback((emblaApi: EmblaCarouselType) => {
setScrollSnaps(emblaApi.scrollSnapList())
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setSelectedIndex(emblaApi.selectedScrollSnap())
emblaApi.on('reInit', onInit)
emblaApi.on('reInit', onSelect)
emblaApi.on('select', onSelect)
}, [emblaApi, onInit, onSelect])
type UsePrevNextButtonsType = {
onPrevButtonClick: () => void
onNextButtonClick: () => void
export const usePrevNextButtons = (
emblaApi: EmblaCarouselType | undefined
): UsePrevNextButtonsType => {
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)
const onPrevButtonClick = useCallback(() => {
const onNextButtonClick = useCallback(() => {
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setPrevBtnDisabled(!emblaApi.canScrollPrev())
setNextBtnDisabled(!emblaApi.canScrollNext())
emblaApi.on('reInit', onSelect)
emblaApi.on('select', onSelect)
type UseSelectedSnapDisplayType = {
export const useSelectedSnapDisplay = (
emblaApi: EmblaCarouselType | undefined
): UseSelectedSnapDisplayType => {
const [selectedSnap, setSelectedSnap] = useState(0)
const [snapCount, setSnapCount] = useState(0)
const updateScrollSnapState = useCallback((emblaApi: EmblaCarouselType) => {
setSnapCount(emblaApi.scrollSnapList().length)
setSelectedSnap(emblaApi.selectedScrollSnap())
updateScrollSnapState(emblaApi)
emblaApi.on('select', updateScrollSnapState)
emblaApi.on('reInit', updateScrollSnapState)
}, [emblaApi, updateScrollSnapState])
export const ThumsSlider: React.FC<any> = () => {
const { emblaThumbsRef, slidesrArr, selectedIndex, onThumbClick }: any =
<div className=" overflow-hidden mt-2" ref={emblaThumbsRef}>
<div className="flex flex-row gap-2">
{slidesrArr?.map((slide: any, index: any) => (
className={`min-w-0 w-full xl:h-24 aspect-auto border-2 rounded-md ${
: 'border-transparent opacity-30 '
style={{ flex: `0 0 15%` }}
onClick={() => onThumbClick(index)}
className="w-full h-full object-cover rounded-sm"