Docs
Bar Poll
A bar poll is a visual representation of poll results using horizontal or vertical bars to indicate the percentage of votes for each option.
Installation
Install dependencies
npm install framer-motion lucide-react
Run the following command
It will create a new file bar-poll.tsx
inside the components/mage-ui/widget
directory.
mkdir -p components/mage-ui/widget && touch components/mage-ui/widget/bar-poll.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
interface VoteOption {
title: string;
votes: number;
color: string;
}
const BarPoll = () => {
const [votes, setVotes] = useState<VoteOption[]>([
{
title: "Tabs",
votes: 1,
color: "bg-indigo-500",
},
{
title: "Spaces",
votes: 2,
color: "bg-fuchsia-500",
},
{
title: "Who cares bro?",
votes: 3,
color: "bg-violet-500",
},
]);
return (
<section className="bg-slate-900 px-4 py-12">
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-2 md:grid-cols-[1fr_400px] md:gap-12">
<Options votes={votes} setVotes={setVotes} />
<Bars votes={votes} />
</div>
</section>
);
};
interface OptionsProps {
votes: VoteOption[];
setVotes: React.Dispatch<React.SetStateAction<VoteOption[]>>;
}
const Options: React.FC<OptionsProps> = ({ votes, setVotes }) => {
const totalVotes = votes.reduce((acc, cv) => acc + cv.votes, 0);
const handleIncrementVote = (vote: VoteOption) => {
setVotes((prevVotes) =>
prevVotes.map((v) =>
v.title === vote.title ? { ...v, votes: v.votes + 1 } : v
)
);
};
return (
<div className="col-span-1 py-12">
<h3 className="mb-6 text-3xl font-semibold text-slate-50">
What's your opinion?
</h3>
<div className="mb-6 space-y-2">
{votes.map((vote) => (
<motion.button
whileHover={{ scale: 1.015 }}
whileTap={{ scale: 0.985 }}
onClick={() => handleIncrementVote(vote)}
key={vote.title}
className={`w-full rounded-md ${vote.color} py-2 font-medium text-white`}
>
{vote.title}
</motion.button>
))}
</div>
<div className="flex items-center justify-between">
<span className="mb-2 italic text-slate-400">{totalVotes} votes</span>
<motion.button
whileHover={{ scale: 1.015 }}
whileTap={{ scale: 0.985 }}
onClick={() => {
setVotes((prevVotes) => prevVotes.map((v) => ({ ...v, votes: 0 })));
}}
className="rounded-sm bg-slate-700 px-2 py-1.5 text-sm font-medium text-slate-200"
>
Reset count
</motion.button>
</div>
</div>
);
};
interface BarsProps {
votes: VoteOption[];
}
const Bars: React.FC<BarsProps> = ({ votes }) => {
const totalVotes = votes.reduce((acc, cv) => acc + cv.votes, 0);
return (
<div
className="col-span-1 grid min-h-[200px] gap-2"
style={{
gridTemplateColumns: `repeat(${votes.length}, minmax(0, 1fr))`,
}}
>
{votes.map((vote) => {
const height = vote.votes ? ((vote.votes / totalVotes) * 100).toFixed(2) : "0";
return (
<div key={vote.title} className="col-span-1">
<div className="relative flex h-full w-full items-end overflow-hidden rounded-2xl bg-gradient-to-b from-slate-700 to-slate-800">
<motion.span
animate={{ height: `${height}%` }}
className={`relative z-0 w-full ${vote.color}`}
transition={{ type: "spring" }}
/>
<span className="absolute bottom-0 left-[50%] mt-2 inline-block w-full -translate-x-[50%] p-2 text-center text-sm text-slate-50">
<b>{vote.title}</b>
<br />
<span className="text-xs text-slate-200">{vote.votes} votes</span>
</span>
</div>
</div>
);
})}
</div>
);
};
export default BarPoll;