NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Survey Card

showing result of survey on hover over card

requires interactionhover
Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/feature-cards/survey-card.json

Manual

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]);
 
  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", {
                "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