Like Button

A toggle-able like button with a thumbs-up icon, animated counter, and smooth transition effects.

Preview

Installation

Install dependencies

# Install Shadcn Button component
npx shadcn@latest add button
 
# Install Framer Motion
npm install framer-motion
 
# Install Lucide React for icons
npm install lucide-react

Create the component file

mkdir -p components/shsfui/button && touch components/shsfui/button/like-button.tsx

Add the component code

Open the newly created file and paste the following code:

"use client";
 
import * as React from "react";
import { ThumbsUpIcon } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { Button, type ButtonProps } from "@/components/ui/button";
import { cn } from "@/utils/cn";
 
type LikeButtonProps = ButtonProps & {
  initialCount?: number;
  initialLiked?: boolean;
  label?: string;
  onLikeChange?: (isLiked: boolean, count: number) => void;
};
 
const thumbVariants = {
  initial: { scale: 1 },
  liked: { scale: [1, 1.4, 1], transition: { duration: 0.4 } },
  tap: { scale: 0.8 },
};
 
const countVariants = {
  initial: { y: -20, opacity: 0 },
  animate: { y: 0, opacity: 1 },
  exit: { y: 20, opacity: 0 },
};
 
const LikeButton = React.forwardRef<HTMLButtonElement, LikeButtonProps>(
  (props, ref) => {
    const {
      className,
      initialCount = 20,
      initialLiked = false,
      label = "Like",
      onLikeChange,
      ...restProps
    } = props;
 
    const [liked, setLiked] = React.useState(initialLiked);
    const [count, setCount] = React.useState(initialCount);
 
    const handleLike = () => {
      const newLiked = !liked;
      const newCount = count + (newLiked ? 1 : -1);
 
      setLiked(newLiked);
      setCount(newCount);
 
      if (onLikeChange) {
        onLikeChange(newLiked, newCount);
      }
    };
 
    return (
      <Button
        ref={ref}
        className={cn("py-0 pe-0 overflow-hidden", className)}
        variant="outline"
        onClick={handleLike}
        type="button"
        {...restProps}
      >
        <motion.div
          variants={thumbVariants}
          initial="initial"
          animate={liked ? "liked" : "initial"}
          whileTap="tap"
        >
          <ThumbsUpIcon
            className={cn(
              "transition-all duration-300",
              liked ? "text-blue-500 fill-blue-500" : "opacity-60"
            )}
            size={16}
            aria-hidden="true"
          />
        </motion.div>
        <span className="ml-1.5">{label}</span>
        <span className="relative inline-flex items-center justify-center h-full px-3 text-xs font-medium rounded-full text-muted-foreground before:absolute before:inset-0 before:w-px before:bg-border ms-1">
          <AnimatePresence mode="wait">
            <motion.span
              key={count}
              variants={countVariants}
              initial="initial"
              animate="animate"
              exit="exit"
              transition={{ duration: 0.2 }}
            >
              {count}
            </motion.span>
          </AnimatePresence>
        </span>
      </Button>
    );
  }
);
 
LikeButton.displayName = "LikeButton";
 
export LikeButton;