UI Elements

Download Button

A download button whose Apple icon slides out and an arrow slides in on hover for a playful call to action.

download-button.tsx
"use client";

import { buttonVariants } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import { motion, Transition } from "motion/react";
import { HTMLAttributes, useState } from "react";

type IconProps = HTMLAttributes<SVGElement>;

const AppleIcon = (props: IconProps) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="209"
    height="256"
    viewBox="0 0 814 1000"
    {...props}
  >
    <path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z" />
  </svg>
);

const TRANSITION: Transition = { duration: 0.5, ease: "easeInOut" };

const JoiDownloadButton = () => {
  const [hovered, setHovered] = useState(false);

  const handleToggleMouseState = () => setHovered((prev) => !prev);

  return (
    <div className="flex items-center justify-center size-full bg-background rounded-xl">
      <motion.button
        className={buttonVariants({
          className: "overflow-hidden flex items-center gap-2 relative",
        })}
        onMouseEnter={handleToggleMouseState}
        onMouseLeave={handleToggleMouseState}
      >
        <motion.span
          animate={{ x: hovered ? "-200%" : 0 }}
          transition={TRANSITION}
        >
          <AppleIcon className="size-5 fill-primary-foreground" />
        </motion.span>

        <motion.span animate={{ x: hovered ? -22 : 0 }} transition={TRANSITION}>
          Download for IOS
        </motion.span>
        <motion.span
          animate={{ x: hovered ? -12 : 20, opacity: hovered ? 1 : 0 }}
          transition={TRANSITION}
          className="absolute right-0 top-0 bottom-0 my-auto size-5 flex items-center justify-center"
        >
          <ArrowRight className="size-5" />
        </motion.span>
      </motion.button>
    </div>
  );
};

export default JoiDownloadButton;