Docs

Video Chat

Video chat in real-time

requires interactionclick

Installation

Install dependencies

npm install framer-motion lucide-react

Run the following command

It will create a new file video-chat.tsx inside the components/animata/widget directory.

mkdir -p components/animata/widget && touch components/animata/widget/video-chat.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { useState } from "react";
import { StaticImageData } from "next/image";
import { motion } from "framer-motion";
import {
  Circle,
  LogOut,
  Maximize,
  Maximize2,
  Mic,
  Monitor,
  Slash,
  Square,
  VideoIcon,
} from "lucide-react";
 
import { cn } from "@/lib/utils";
import Hills from "@/public/bg-hills.jpg";
import Music from "@/public/widget/music.jpg";
 
interface SquareDivProps {
  bgColor?: string;
  children: React.ReactNode;
  onClick?: () => void;
}
 
interface ImageSlotProps {
  minimize: boolean;
  imageSource: StaticImageData;
  imageAlternate: string;
}
 
interface ControlIconStateProps {
  iconState: {
    Mic: boolean;
    Screen: boolean;
    Video: boolean;
  };
  toggleIconState: (icon: keyof ControlIconStateProps["iconState"]) => void;
}
 
function SquareDiv({ bgColor = "bg-green-600", children, onClick }: SquareDivProps) {
  return (
    <div
      className={cn("flex h-6 w-6 cursor-pointer items-center justify-center rounded-md", bgColor)}
      onClick={onClick}
    >
      {children}
    </div>
  );
}
 
function ImageSlot({ minimize, imageSource, imageAlternate }: ImageSlotProps) {
  return (
    <motion.div
      layout
      transition={{ duration: 0.5 }}
      className={cn(minimize ? "h-48 w-full" : "h-56 w-56")}
    >
      <img
        src={imageSource.src}
        alt={imageAlternate}
        className={cn("h-full w-full object-cover", minimize ? "rounded-xl" : "rounded-3xl")}
      />
    </motion.div>
  );
}
 
function YourImageSlot({ minimize, imageSource, imageAlternate }: ImageSlotProps) {
  return (
    <motion.div
      layout
      transition={{ duration: 0.5 }}
      className={cn(minimize ? "absolute bottom-2 right-0 h-20 w-20" : "h-56 w-56")}
    >
      <img
        src={imageSource.src}
        alt={imageAlternate}
        className={cn("h-full w-full object-cover", minimize ? "rounded-xl" : "rounded-3xl")}
      />
    </motion.div>
  );
}
function ControlIconState({ iconState, toggleIconState }: ControlIconStateProps) {
  return (
    <div className="flex items-center justify-center gap-1">
      <SquareDiv onClick={() => toggleIconState("Mic")}>
        <Mic size={18}>{iconState.Mic && <Slash />}</Mic>
      </SquareDiv>
      <SquareDiv bgColor="bg-slate-400" onClick={() => toggleIconState("Screen")}>
        <Monitor size={18}>{iconState.Screen && <Slash />}</Monitor>
      </SquareDiv>
      <SquareDiv onClick={() => toggleIconState("Video")}>
        <VideoIcon fill="white" size={18}>
          {iconState.Video && <Slash />}
        </VideoIcon>
      </SquareDiv>
      <SquareDiv bgColor="bg-red-600">
        <LogOut size={18} />
      </SquareDiv>
    </div>
  );
}
 
const persons = [
  {
    title: "Ram",
    image: Music,
  },
  {
    title: "Rick",
    image: Hills,
  },
  {
    title: "Alex",
    image: Hills,
  },
];
 
export default function VideoChat() {
  const [minimize, setMinimize] = useState(false);
  const [iconState, setIconState] = useState({
    Mic: false,
    Screen: true,
    Video: false,
  });
  const width = minimize ? "max-w-52" : "max-w-[480px]";
 
  const toggleIconState = (icon: keyof typeof iconState) => {
    setIconState((prevState) => ({
      ...prevState,
      [icon]: !prevState[icon],
    }));
  };
 
  return (
    <motion.div
      layout
      transition={{ duration: 0.5 }}
      className={cn(
        "mx-auto select-none bg-gray-700 text-white",
        width,
        minimize ? "rounded-xl" : "rounded-3xl",
      )}
    >
      <div className="flex items-center justify-between rounded-lg px-4 py-2">
        <div className="flex items-center gap-1">
          <Circle fill="red" stroke="none" size={14} />
          <Circle fill="yellow" stroke="none" size={14} />
          <Circle fill="green" stroke="none" size={14} />
        </div>
        {!minimize && <ControlIconState iconState={iconState} toggleIconState={toggleIconState} />}
        <div className="flex items-center gap-2">
          <button onClick={() => setMinimize(!minimize)}>
            {minimize ? (
              <div className="relative flex">
                <Maximize size={16} />
                <Maximize2 className="absolute" size={16} />
              </div>
            ) : (
              <div className="relative flex">
                <Square size={14} />
                <Square className="absolute -bottom-[1px] -left-[1px]" fill="white" size={10} />
              </div>
            )}
          </button>
          <div className="flex gap-1">
            <Circle fill="white" size={6} />
            <Circle fill="white" size={6} />
            <Circle fill="white" size={6} />
          </div>
        </div>
      </div>
      <motion.div
        layout
        transition={{ duration: 0.5 }}
        className={cn(
          "relative grid grid-cols-1 place-items-center gap-2",
          minimize ? "grid-rows-3" : "grid-cols-2",
        )}
      >
        {persons.map((person, index) => (
          <ImageSlot
            key={index}
            minimize={minimize}
            imageAlternate={person.title}
            imageSource={person.image}
          />
        ))}
        <YourImageSlot minimize={minimize} imageAlternate="Your Name" imageSource={Music} />
      </motion.div>
    </motion.div>
  );
}

Credits

Built by Aashish Katila

Inspired by Video

Photo by Hongru Wang on Unsplash Photo by Jr Korpa on Unsplash