clip-corners-button

PreviousNext

A ClipCornersButton component for SmoothUI.

Docs
smoothuiui

Preview

Loading preview…
index.tsx
"use client";

import { Button } from "@repo/shadcn-ui/components/ui/button";
import { motion } from "motion/react";
import { useState } from "react";

export type ClipCornersButtonProps = {
  children: React.ReactNode;
  className?: string;
  onClick?: () => void;
};

export function ClipCornersButton({
  children,
  className = "",
  onClick,
}: ClipCornersButtonProps) {
  const [isHovered, setIsHovered] = useState(false);

  // Distance triangles move on hover
  const move = -4;

  return (
    <Button
      className={`relative overflow-hidden rounded-none border-none bg-foreground px-8 py-4 font-mono text-2xl text-background hover:bg-foreground/90 ${className}`}
      onClick={onClick}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      style={{ borderRadius: 8 }}
      type="button"
    >
      {/* Top-left triangle */}
      <motion.div
        animate={{
          x: isHovered ? -move : 0,
          y: isHovered ? -move : 0,
        }}
        className="absolute top-1.5 left-1.5"
        initial={false}
        style={{ width: 8, height: 8 }}
        transition={{ type: "spring", stiffness: 400, damping: 24 }}
      >
        <svg
          aria-label="Top-left triangle"
          className="fill-background"
          height="8"
          width="8"
        >
          <title>Top-left triangle</title>
          <polygon fill="currentColor" points="0,0 8,0 0,8" />
        </svg>
      </motion.div>
      {/* Top-right triangle */}
      <motion.div
        animate={{
          x: isHovered ? move : 0,
          y: isHovered ? -move : 0,
        }}
        className="absolute top-1.5 right-1.5"
        initial={false}
        style={{ width: 8, height: 8 }}
        transition={{ type: "spring", stiffness: 400, damping: 24 }}
      >
        <svg
          aria-label="Top-right triangle"
          className="fill-background"
          height="8"
          width="8"
        >
          <title>Top-right triangle</title>
          <polygon fill="currentColor" points="8,0 8,8 0,0" />
        </svg>
      </motion.div>
      {/* Bottom-left triangle */}
      <motion.div
        animate={{
          x: isHovered ? -move : 0,
          y: isHovered ? move : 0,
        }}
        className="absolute bottom-1.5 left-1.5"
        initial={false}
        style={{ width: 8, height: 8 }}
        transition={{ type: "spring", stiffness: 400, damping: 24 }}
      >
        <svg
          aria-label="Bottom-left triangle"
          className="fill-background"
          height="8"
          width="8"
        >
          <title>Bottom-left triangle</title>
          <polygon fill="currentColor" points="0,8 8,8 0,0" />
        </svg>
      </motion.div>
      {/* Bottom-right triangle */}
      <motion.div
        animate={{
          x: isHovered ? move : 0,
          y: isHovered ? move : 0,
        }}
        className="absolute right-1.5 bottom-1.5"
        initial={false}
        style={{ width: 8, height: 8 }}
        transition={{ type: "spring", stiffness: 400, damping: 24 }}
      >
        <svg
          aria-label="Bottom-right triangle"
          className="fill-background"
          height="8"
          width="8"
        >
          <title>Bottom-right triangle</title>
          <polygon fill="currentColor" points="8,8 0,8 8,0" />
        </svg>
      </motion.div>
      <span className="relative z-10 flex w-full select-none items-center justify-center">
        {children}
      </span>
    </Button>
  );
}

export default ClipCornersButton;

Installation

npx shadcn@latest add @smoothui/clip-corners-button

Usage

import { ClipCornersButton } from "@/components/ui/clip-corners-button"
<ClipCornersButton />