Docs
Two

Two

Smooth gallery effect that changes layout when clicked.

Installation

Run the following command

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

mkdir -p components/gallery && touch components/gallery/two.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;
  width: number;
  img: string;
}
 
interface Column {
  id: number;
  elements: Element[];
}
 
const items: Column[] = [
  {
    id: 1,
    elements: [
      { id: 1, width: 250, img: "/others/photo-1.jpg" },
      { id: 2, width: 100, img: "/others/photo-2.jpg" },
    ],
  },
  {
    id: 2,
    elements: [
      { id: 3, width: 100, img: "/others/photo-6.jpg" },
      { id: 4, width: 250, img: "/others/photo-7.jpg" },
    ],
  },
];
 
const Two = () => {
  const [activeItem, setActiveItem] = useState<Element | null>(null);
 
  const allElements = items.flatMap((column) => column.elements);
 
  const handleItemClick = (ele: Element) => {
    setActiveItem(ele);
  };
 
  return (
    <div className="h-full center w-full flex flex-col gap-5 relative">
      <motion.div
        className={cn("flex flex-col gap-5")}
        layout
        transition={{ duration: 0.5, ease: "easeInOut" }}
      >
        {items.map((column) => (
          <motion.div
            className={cn("flex items-center justify-center gap-5")}
            key={column.id}
            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>
 
      {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 overflow-hidden "
              transition={{ duration: 0.5, ease: "easeInOut" }}
              layout
            >
              <motion.div
                layoutId={`card-${activeItem.id}`}
                className="w-[400px] h-[400px] rounded-3xl center font-bold text-5xl cursor-pointer overflow-hidden z-10"
                onClick={() => setActiveItem(null)}
              >
                <img
                  src={activeItem.img}
                  alt=""
                  className="w-full object-cover h-full"
                />
              </motion.div>
              <motion.div
                className="flex flex-col gap-4 justify-center items-center"
                animate={{ opacity: 1, x: 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={{
        width: props.isSmall ? 80 : props.item.width,
        height: props.isSmall ? 80 : 150,
      }}
      className={cn(
        "rounded-2xl cursor-pointer text-3xl center overflow-hidden relative"
      )}
      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 Two;

Credits

Built by Bossadi Zenith