UNPKG

@sunui-design/floating

Version:

Animated floating action button component for SunUI Design

222 lines 10.3 kB
'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