Docs

Survey Card

showing result of survey on hover over card

requires interactionhover

Installation

Run the following command

It will create a new file survey-card.tsx inside the components/animata/card directory.

mkdir -p components/animata/card && touch components/animata/card/survey-card.tsx

Paste the code

Open the newly created file and paste the following code:

import { useEffect, useMemo, useRef, useState } from "react";
 
import { cn } from "@/lib/utils";
 
interface SurveyCardProps {
  /**
   * The items to display in the Survey.
   * Each item should have a vote and itemName.
   */
  items: {
    vote: number;
    itemName?: string;
  }[];
  /**
   * The width of the Survey. recommended to use with more than 250px
   */
  width?: number;
  /**
   * The title of the Survey.
   */
  surveyTitle?: string;
}
 
export default function SurveyCard({ items, width: providedWidth, surveyTitle }: SurveyCardProps) {
  const [{ width }, setSize] = useState({
    width: providedWidth ?? 250,
  });
  // Calculate total votes and max votes using useMemo
  const totalVotes = useMemo(() => items.reduce((acc, item) => acc + item.vote, 0), [items]);
  const maxVote = useMemo(() => Math.max(...items.map((item) => item.vote)), [items]);
 
  const containerRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    setSize({
      width: providedWidth ?? containerRef.current?.offsetWidth ?? 250,
    });
  }, [providedWidth, items]);
 
  const [isParentHovered, setIsParentHovered] = useState(false);
 
  const handleParentMouseOver = () => {
    setIsParentHovered(true);
  };
 
  const handleParentMouseOut = () => {
    setIsParentHovered(false);
  };
 
  return (
    <div
      ref={containerRef}
      className={cn(
        "relative box-border flex w-full flex-col items-start gap-4 overflow-hidden p-2",
      )}
      style={{ width }}
      onMouseOver={handleParentMouseOver}
      onMouseOut={handleParentMouseOut}
    >
      <div className="flex w-full justify-start">
        <h1 className="text-2xl font-bold">{surveyTitle}</h1>
      </div>
      {items.map((item, index) => {
        const clampedProgress = Math.max(0, item.vote);
        // saving overflow over itemName using 8/12
        const barWidth = isParentHovered ? (clampedProgress / totalVotes) * 100 * (8 / 12) : 30;
        return (
          <div
            key={`survey-item-${index}`}
            className={cn("flex w-full items-center justify-between")}
          >
            <div
              className={cn("duration-600 flex h-6 justify-start rounded-full transition-all", {
                "animate-pulse bg-slate-300": !isParentHovered,
                "bg-green-600": isParentHovered && maxVote === item.vote,
                "bg-slate-400": isParentHovered && maxVote !== item.vote,
              })}
              style={{ width: `${barWidth}%` }}
            />
            <div className="flex h-6 w-3/12 justify-end">
              <span className="text-xl text-black">{item.itemName}</span>
            </div>
          </div>
        );
      })}
    </div>
  );
}

Credits

Built by Mohit Ahlawat