One

Smooth gallery effect when clicked and changes layout

Installation

Run the following command

It will create a new file one.tsx inside the components/gallery/one.tsx directory.

mkdir -p components/gallery && touch components/gallery/one.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import React, { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { cn } from "@/lib/utils";
 
interface Element {
  id: number;
  height: number;
  img: string;
}
 
interface Column {
  id: number;
  elements: Element[];
}
 
const items: Column[] = [
  {
    id: 1,
    elements: [
      { id: 1, height: 300, img: "/others/photo-1.jpg" },
      { id: 2, height: 250, img: "/others/photo-2.jpg" },
    ],
  },
  {
    id: 2,
    elements: [
      { id: 3, height: 150, img: "/others/photo-3.jpg" },
      { id: 4, height: 100, img: "/others/photo-4.jpg" },
      { id: 5, height: 150, img: "/others/photo-5.jpg" },
    ],
  },
  {
    id: 3,
    elements: [
      { id: 6, height: 300, img: "/others/photo-6.jpg" },
      { id: 7, height: 250, img: "/others/photo-7.jpg" },
    ],
  },
];
 
const One: React.FC = () => {
  const [activeItem, setActiveItem] = useState<Element | null>(null);
 
  const handleItemClick = (ele: Element) => {
    setActiveItem(ele);
  };
 
  const allElements = items.flatMap((column) => column.elements);
 
  return (
    <div className="w-full center relative h-full overflow-hidden">
      <motion.div
        layout
        transition={{ duration: 0.5, ease: "easeInOut" }}
        className="w-full flex-col  gap-10"
      >
        <motion.div
          className="w-full gap-2 flex items-start justify-center"
          layout
          transition={{ duration: 0.5, ease: "easeInOut" }}
        >
          {items.map((column) => (
            <motion.div
              className={cn(
                "w-48 flex flex-col items-center justify-center gap-2"
              )}
              key={column.id}
              layout
              animate={{
                opacity: activeItem !== null ? 0 : 1,
                willChange: "auto",
              }}
            >
              {column.elements.map((ele, index) => (
                <Gallery
                  item={ele}
                  key={index}
                  onClick={() => setActiveItem(ele)}
                />
              ))}
            </motion.div>
          ))}
        </motion.div>
      </motion.div>
      {activeItem && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1, willChange: "auto" }}
          transition={{ duration: 0.5, ease: "easeInOut" }}
          className="absolute inset-0 w-full h-full overflow-hidden"
        >
          <AnimatePresence mode="popLayout">
            <motion.div
              key={activeItem.id}
              className="w-full h-full flex items-center justify-center gap-10 flex-col overflow-hidden "
              transition={{ duration: 0.5, ease: "easeInOut" }}
              layout
            >
              <motion.div
                layoutId={`card-${activeItem.id}`}
                className="w-[600px] h-[400px]  rounded-3xl  cursor-pointer overflow-hidden"
                onClick={() => setActiveItem(null)}
              >
                <img
                  src={activeItem.img}
                  alt=""
                  className="w-full object-cover h-full"
                />
              </motion.div>
              <motion.div
                className="flex flex-row gap-4 justify-center items-center"
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: 0.3 }}
              >
                {allElements
                  .filter((ele) => ele.id !== activeItem.id)
                  .map((ele) => (
                    <Gallery
                      key={ele.id}
                      item={ele}
                      onClick={() => handleItemClick(ele)}
                      isSmall
                    />
                  ))}
              </motion.div>
            </motion.div>
          </AnimatePresence>
        </motion.div>
      )}
    </div>
  );
};
 
const Gallery = (props: {
  item: Element;
  onClick: () => void;
  isSmall?: boolean;
}) => {
  return (
    <motion.div
      style={{
        height: props.isSmall ? 100 : props.item.height,
        width: props.isSmall ? 100 : 192,
      }}
      className={cn(
        "rounded-2xl cursor-pointer center overflow-hidden",
        props.isSmall ? "w-[100px]" : "w-full"
      )}
      layoutId={`card-${props.item.id}`}
      onClick={props.onClick}
    >
      <motion.img
        src={props.item.img}
        alt=""
        className="w-full object-cover h-full"
        whileHover={{ scale: 1.05 }}
        transition={{
          duration: 0.3,
        }}
      />
    </motion.div>
  );
};
 
export default One;

Credits

Built by Bossadi Zenith