Todos os componentes

Gallery Modal Slider

Gallery Modal Slider é um componente de formulário para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

UI Layouts MIT forms

O que é

Gallery Modal Slider é um componente de formulário para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i motion lucide-react

Licença

Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Gallery Modal Slider

./registry/components/modal/gallery-modal/modal-slider.tsx
// @ts-nocheck
'use client';

import { items } from '@/components/website/constant';
import { X } from 'lucide-react';
import { AnimatePresence, LayoutGroup, motion, useMotionValue } from 'motion/react';
import type { StaticImport } from 'next/dist/shared/lib/get-img-props';
import Image from 'next/image';
import React, { useEffect, useRef, useState } from 'react';

type ImageModalProps = {
  item: any;
  uniqueId: string;
  itemArr: any;
};

const modal: React.FC = () => {
  return (
    <>
      <div className='columns-3 '>
        {items.slice(0, 8)?.map((item, index) => (
          <SliderModal
            key={item.id ?? item.url}
            item={item}
            itemArr={items}
            uniqueId={`id-${index}`}
          />
        ))}
      </div>
    </>
  );
};

export const SliderModal = ({ item, uniqueId, itemArr }: ImageModalProps) => {
  const [isOpen, setIsOpen] = useState(false);
  const [newItem, setNewItem] = useState(item);
  const [hight, setHight] = useState(0);
  const [constraints, setConstraints] = useState(0);
  const carousel = useRef(null);

  useEffect(() => {
    const element = carousel.current;
    const viewportHeight = element?.offsetHeight;
    const viewScrollHeight = element?.scrollHeight;
    // console.log(viewportHeight, viewScrollHeight);
    setConstraints(Number(viewportHeight) - Number(viewScrollHeight));
  }, [carousel, isOpen]);
  // console.log(constraints)
  useEffect(() => {
    if (isOpen) {
      document.body.classList.add('overflow-hidden');
    } else {
      document.body.classList.remove('overflow-hidden');
    }

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setIsOpen(false);
      }
    };

    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen]);

  return (
    <>
      <motion.div
        onClick={() => {
          setIsOpen(true);
          setNewItem(item);
        }}
        className='overflow-hidden mb-3'
      >
        <motion.div layoutId={uniqueId}>
          <Image
            width={400}
            height={400}
            src={item?.url}
            className='bg-white text-black rounded-md w-full cursor-zoom-in'
            alt='img'
          />
        </motion.div>
      </motion.div>

      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className='fixed inset-0 z-50 top-0 left-0  right-0 bottom-0  flex flex-col items-center w-full h-screen justify-center dark:bg-black/80 bg-neutral-300/80 backdrop-blur-lg cursor-zoom-out'
            onClick={() => {
              setNewItem(null);
              setIsOpen(false);
            }}
          >
            <button
              className='absolute top-2 right-2 p-4 border dark:bg-black/80 text-white  bg-neutral-400/40 backdrop-blur-lg '
              onClick={() => setIsOpen(false)}
            >
              <X />
            </button>
            <motion.div
              layoutId={uniqueId}
              className='rounded-md w-fit h-[80%] flex gap-2 items-center mx-auto cursor-auto '
              onClick={(e) => e.stopPropagation()}
            >
              {newItem && (
                <AnimatePresence>
                  {itemArr.map((tab: any, index: React.Key | null | undefined) => (
                    <>
                      <React.Fragment key={item.id ?? item.url}>
                        <AnimatePresence mode='popLayout'>
                          {tab.id === newItem.id && (
                            <motion.figure
                              key={tab?.id}
                              className='dark:bg-neutral-900/40 bg-neutral-100/40 border  rounded-md p-4'
                            >
                              <motion.div
                                initial={{ opacity: 0 }}
                                animate={{
                                  opacity: 1,
                                  transition: {
                                    ease: 'easeInOut',
                                    duration: 0.3,
                                    delay: 0.2,
                                  },
                                }}
                                exit={{
                                  opacity: 0,
                                  transition: {
                                    ease: 'easeInOut',
                                    duration: 0.2,
                                  },
                                }}
                                transition={{ duration: 0.2, delay: 0.2 }}
                              >
                                <Image
                                  src={newItem.url}
                                  width={1000}
                                  height={1000}
                                  alt='preview_img'
                                  className=' object-contain h-[70vh]  mx-auto rounded-md'
                                />
                              </motion.div>
                            </motion.figure>
                          )}
                        </AnimatePresence>
                      </React.Fragment>
                    </>
                  ))}
                </AnimatePresence>
              )}
              <motion.div
                className='h-[300px] overflow-hidden dark:bg-neutral-900/40 bg-white/40 border rounded-md'
                ref={carousel}
              >
                <motion.div
                  whileDrag={{ scale: 0.95 }}
                  dragElastic={0.2}
                  dragTransition={{ bounceDamping: 30 }}
                  transition={{ duration: 0.2, ease: 'easeInOut' }}
                  className='h-fit '
                  drag='y'
                  dragConstraints={{ top: constraints, bottom: 0 }}
                >
                  {itemArr?.map(
                    (
                      itemData: { url: string | StaticImport; id: any },
                      index: React.Key | null | undefined
                    ) => {
                      return (
                        <motion.div
                          key={item.id ?? item.url}
                          className={`relative p-2   cursor-grab active:cursor-grabbing`}
                          onClick={() => setNewItem(itemData)}
                        >
                          <Image
                            src={itemData?.url}
                            width={400}
                            height={400}
                            alt='img'
                            className='sm:w-28 w-52 h-16 object-cover cursor-pointer relative z-2 rounded-md pointer-events-none'
                          />
                          {itemData?.id === newItem?.id && (
                            <motion.div
                              layoutId='slider'
                              transition={{
                                layout: {
                                  duration: 0.2,
                                  ease: 'easeOut',
                                },
                              }}
                              className='absolute top-0 left-0 h-full w-full dark:bg-neutral-100 bg-neutral-800 rounded-md'
                            ></motion.div>
                          )}
                        </motion.div>
                      );
                    }
                  )}
                </motion.div>
              </motion.div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </>
  );
};

export default modal;

Componentes parecidos