lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
85 lines (84 loc) • 4.51 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Plus } from "lucide-react";
import { cn } from "../../lib/utils";
export function ExpandableSpeedDial({ actions, direction = "up", className, size = "md", }) {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => setIsOpen(!isOpen);
const sizes = {
sm: "h-10 w-10",
md: "h-12 w-12",
lg: "h-14 w-14",
};
const actionSizes = {
sm: "h-8 w-8",
md: "h-10 w-10",
lg: "h-12 w-12",
};
const getDirectionClasses = () => {
switch (direction) {
case "up":
return "bottom-full mb-3 flex-col-reverse left-1/2 -translate-x-1/2";
case "down":
return "top-full mt-3 flex-col left-1/2 -translate-x-1/2";
case "left":
return "right-full mr-3 flex-row-reverse top-1/2 -translate-y-1/2";
case "right":
return "left-full ml-3 flex-row top-1/2 -translate-y-1/2";
default:
return "bottom-full mb-3 flex-col-reverse left-1/2 -translate-x-1/2";
}
};
const getMotionVariants = (index) => {
const delay = index * 0.05;
const distance = 15;
let x = 0;
let y = 0;
switch (direction) {
case "up":
y = distance;
break;
case "down":
y = -distance;
break;
case "left":
x = distance;
break;
case "right":
x = -distance;
break;
}
return {
hidden: { opacity: 0, scale: 0.5, x, y },
visible: {
opacity: 1,
scale: 1,
x: 0,
y: 0,
transition: {
type: "spring",
stiffness: 300,
damping: 20,
delay
}
},
exit: {
opacity: 0,
scale: 0.5,
x,
y,
transition: {
duration: 0.2,
delay: (actions.length - 1 - index) * 0.05
}
}
};
};
return (_jsxs("div", { className: cn("relative z-50", className), children: [_jsx(AnimatePresence, { children: isOpen && (_jsx("div", { className: cn("absolute flex gap-3", getDirectionClasses()), children: actions.map((action, index) => (_jsxs(motion.button, { variants: getMotionVariants(index), initial: "hidden", animate: "visible", exit: "exit", onClick: () => {
action.onClick();
setIsOpen(false);
}, className: cn("relative flex items-center justify-center rounded-full bg-background shadow-md border hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", actionSizes[size]), title: action.label, "aria-label": action.label, whileHover: { scale: 1.1 }, whileTap: { scale: 0.95 }, children: [action.icon, (direction === "up" || direction === "down") && (_jsx("span", { className: "absolute right-full mr-3 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-xs text-background opacity-0 transition-opacity group-hover:opacity-100 hidden sm:block", children: action.label })), (direction === "left" || direction === "right") && (_jsx("span", { className: "absolute bottom-full mb-3 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-xs text-background opacity-0 transition-opacity group-hover:opacity-100 hidden sm:block", children: action.label }))] }, action.label))) })) }), _jsx(motion.button, { onClick: toggleOpen, className: cn("flex items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg hover:shadow-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", sizes[size]), "aria-label": "Toggle Speed Dial", whileTap: { scale: 0.9 }, children: _jsx(motion.div, { animate: { rotate: isOpen ? 135 : 0 }, transition: { type: "spring", stiffness: 300, damping: 20 }, className: "flex items-center justify-center", children: _jsx(Plus, { className: size === "sm" ? "h-5 w-5" : size === "lg" ? "h-8 w-8" : "h-6 w-6" }) }) })] }));
}