Hamburger
A toggle button with smooth, spring-like SVG arc transitions, stroke dash animations, and optional callbacks for state change inspired by Daniyal Asif
Loading demo...
"use client";
import { useState, startTransition } from "react";
import { Button } from "@/components/ui/button";
export interface HamburgerMenuProps {
strokeWidth?: number;
defaultChecked?: boolean;
onToggle?: (checked: boolean) => void;
}
const HamburgerMenu = (props: HamburgerMenuProps) => {
const { strokeWidth = 3, defaultChecked = false } = props;
const [isChecked, setIsChecked] = useState(defaultChecked);
const handleChange = () => {
const next = !isChecked;
startTransition(() => setIsChecked(next));
};
return (
<div className="flex items-center justify-center bg-background size-full">
<Button
className="size-12"
size="icon"
onClick={handleChange}
variant="ghost"
>
<svg
viewBox="0 0 32 32"
style={{
transform: isChecked ? "rotate(-45deg)" : "rotate(0deg)",
transition: "transform 600ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
className="size-10 stroke-primary"
>
<path
d="M27 10 13 10C10.8 10 9 8.2 9 6 9 3.5 10.8 2 13 2 15.2 2 17 3.8 17 6L17 26C17 28.2 18.8 30 21 30 23.2 30 25 28.2 25 26 25 23.8 23.2 22 21 22L7 22"
fill="none"
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
strokeDasharray={isChecked ? "20 300" : "12 63"}
strokeDashoffset={isChecked ? -32.42 : 0}
style={{
transition:
"stroke-dasharray 600ms cubic-bezier(0.4, 0, 0.2, 1), stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
<path
d="M7 16 27 16"
fill="none"
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
style={{
transition: "opacity 300ms ease",
}}
/>
</svg>
</Button>
</div>
);
};
export default HamburgerMenu;