@sunui-design/floating
Version:
Animated floating action button component for SunUI Design
222 lines • 10.3 kB
JavaScript
'use client';
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { SocialButton } from '@sunui-design/social';
import { FacebookIcon, GithubIcon, InstagramIcon, LinkedinIcon, TwitterIcon } from './icons';
var socialIcons = {
facebook: FacebookIcon,
github: GithubIcon,
instagram: InstagramIcon,
linkedin: LinkedinIcon,
twitter: TwitterIcon
};
export var FloatingButton = function (_a) {
var _b = _a.show, show = _b === void 0 ? true : _b, controlledIsOpen = _a.isOpen, onOpenChange = _a.onOpenChange, _c = _a.defaultOpen, defaultOpen = _c === void 0 ? false : _c, children = _a.children, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.buttonClassName, buttonClassName = _e === void 0 ? '' : _e, _f = _a.position, position = _f === void 0 ? 'bottom-right' : _f, _g = _a.buttons, buttons = _g === void 0 ? [] : _g, _h = _a.variant, variant = _h === void 0 ? 'petal' : _h, _j = _a.showToggleButton, showToggleButton = _j === void 0 ? true : _j;
var _k = useState(defaultOpen), uncontrolledIsOpen = _k[0], setUncontrolledIsOpen = _k[1];
var isMenuOpen = controlledIsOpen !== undefined ? controlledIsOpen : uncontrolledIsOpen;
var positionClasses = {
'bottom-right': 'right-4 sm:right-8 bottom-8',
'bottom-left': 'left-4 sm:left-8 bottom-8',
'top-right': 'right-4 sm:right-8 top-8',
'top-left': 'left-4 sm:left-8 top-8'
};
var handleToggle = function () {
var newState = !isMenuOpen;
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(newState);
if (controlledIsOpen === undefined) {
setUncontrolledIsOpen(newState);
}
};
var calculatePosition = function (index, total) {
var baseRadius = 56;
var baseButtonCount = 3;
if (variant === 'grid') {
var spacing = 56;
var itemsPerRow = Math.ceil(Math.sqrt(total));
var now = index + 1;
var row = void 0, col = void 0;
if (position === 'bottom-right') {
var reversedIndex = total - index;
row = Math.floor(reversedIndex / itemsPerRow);
col = reversedIndex % itemsPerRow;
return {
x: -col * spacing,
y: -row * spacing
};
}
else if (position === 'bottom-left') {
var reversedIndex = total - index;
row = Math.floor(reversedIndex / itemsPerRow);
col = reversedIndex % itemsPerRow;
return {
x: col * spacing,
y: -row * spacing
};
}
else if (position === 'top-right') {
row = Math.floor(now / itemsPerRow);
col = now % itemsPerRow;
return {
x: -col * spacing,
y: row * spacing
};
}
else {
row = Math.floor(now / itemsPerRow);
col = now % itemsPerRow;
return {
x: col * spacing,
y: row * spacing
};
}
}
if (variant === 'vertical') {
var spacing = 56;
switch (position) {
case 'bottom-right':
case 'bottom-left':
return {
x: 0,
y: -spacing * (index + 1)
};
case 'top-right':
case 'top-left':
return {
x: 0,
y: spacing * (index + 1)
};
default:
return { x: 0, y: 0 };
}
}
// 花瓣模式的新計算方法
var currentIndex = index;
var layer = 0;
var buttonsInPreviousLayers = 0;
// 計算當前按鈕所在的層數和之前的按鈕數量
while (currentIndex >= 0) {
var buttonsInThisLayer_1 = Math.min(3 + (layer * 2), total - buttonsInPreviousLayers);
if (currentIndex < buttonsInThisLayer_1) {
break;
}
currentIndex -= buttonsInThisLayer_1;
buttonsInPreviousLayers += buttonsInThisLayer_1;
layer++;
}
// 計算當前層的按鈕數量和角度
var buttonsInThisLayer = Math.min(3 + (layer * 2), total - buttonsInPreviousLayers);
var indexInLayer = index - buttonsInPreviousLayers;
var radius = baseRadius * (layer + 1);
// 計算展開角度,根據按鈕數量調整
var maxAngle = Math.PI / 2; // 90度展開範圍
var angleStep = maxAngle / (buttonsInThisLayer - 1 || 1);
var angle = angleStep * indexInLayer;
switch (position) {
case 'bottom-right':
return {
x: -radius * Math.cos(angle),
y: -radius * Math.sin(angle)
};
case 'bottom-left':
return {
x: radius * Math.cos(angle),
y: -radius * Math.sin(angle)
};
case 'top-right':
return {
x: -radius * Math.cos(angle),
y: radius * Math.sin(angle)
};
case 'top-left':
return {
x: radius * Math.cos(angle),
y: radius * Math.sin(angle)
};
default:
return { x: 0, y: 0 };
}
};
var renderChildren = function () {
if (!children) {
return buttons.map(function (button, index) {
var IconComponent = button.type ? socialIcons[button.type] : undefined;
return (React.createElement(SocialButton, __assign({ key: button.href }, button, { icon: IconComponent ? React.createElement(IconComponent, null) : button.icon, className: button.className || "from-primary-600/90 to-primary-800/90 hover:shadow-primary-500/50", variant: variant })));
});
}
return React.Children.map(children, function (child, index) {
if (React.isValidElement(child)) {
return React.cloneElement(child, __assign(__assign({}, child.props), { key: index, variant: variant }));
}
return child;
});
};
return (React.createElement("div", { className: "fixed ".concat(positionClasses[position], " z-[140] transition-all duration-500\n ").concat(show ? 'translate-y-0 opacity-100' : 'translate-y-20 opacity-0', "\n ").concat(className) },
React.createElement(AnimatePresence, { mode: "wait" }, isMenuOpen && (React.createElement("div", { className: "absolute", style: { inset: 0 } }, renderChildren().map(function (child, index) {
if (!React.isValidElement(child))
return null;
var pos;
if (!showToggleButton) {
if (index === 0) {
// 第一個按鈕在展開位置
pos = { x: -24, y: -24 };
}
else {
// 其他按鈕從第一個位置開始依序排列
var basePos = calculatePosition(index - 1, renderChildren().length - 1);
pos = {
x: basePos.x - 24,
y: basePos.y - 24
};
}
}
else {
pos = calculatePosition(index, renderChildren().length);
}
return (React.createElement(motion.div, { className: 'absolute', style: {
width: '48px',
height: '48px',
pointerEvents: 'auto',
zIndex: variant === 'vertical' ? 1000 - index : 10,
transform: 'translate(-50%, -50%)'
}, key: child.key, initial: { opacity: 0, scale: 0, x: 0, y: 0 }, animate: {
opacity: 1,
scale: 1,
x: pos.x,
y: pos.y,
transition: {
type: "spring",
stiffness: 300,
damping: 24,
delay: index * 0.05
}
}, exit: {
opacity: 0,
scale: 0,
x: 0,
y: 0,
transition: {
duration: 0.2,
delay: (renderChildren().length - index - 1) * 0.05
}
} },
React.createElement("div", { className: "w-full h-full flex items-center justify-center" }, React.cloneElement(child, {
variant: child.props.variant,
className: "".concat(child.props.className || '', " cursor-pointer")
}))));
})))),
showToggleButton && (React.createElement("div", { className: "relative", style: { zIndex: variant === 'vertical' ? 2000 : 'auto' } },
React.createElement(SocialButton, { isMainButton: true, isOpen: isMenuOpen, icon: React.createElement(motion.svg, { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", animate: { rotate: isMenuOpen ? 180 : 0 }, transition: { duration: 0.3 } },
React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: isMenuOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16M4 18h16" })), className: "from-primary-600/90 to-primary-800/90 hover:shadow-primary-500/50", onClick: handleToggle })))));
};
//# sourceMappingURL=FloatingButton.js.map