UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

249 lines (246 loc) 7.64 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import React, { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react'; import { cn } from '../../lib/utilsComprehensive.js'; import SpeedDialAction from './SpeedDialAction.js'; import { SpeedDialIcon } from './SpeedDialIcon.js'; import styles from './SpeedDial.module.css.js'; const COLOR_VARIANT_CLASS = { primary: 'fabSolidPrimary', secondary: 'fabSolidSecondary', error: 'fabSolidError', info: 'fabSolidInfo', success: 'fabSolidSuccess', warning: 'fabSolidWarning', default: 'fabSolidDefault' }; const resolvePositionStyle = position => { if (!position) return undefined; const entries = Object.entries(position); if (entries.length === 0) return undefined; return entries.reduce((acc, [key, value]) => { if (value === undefined) return acc; acc[key] = typeof value === 'number' ? `${value}px` : value; return acc; }, {}); }; const mergeRefs = (...refs) => value => { refs.forEach(ref => { if (!ref) return; if (typeof ref === 'function') { ref(value); } else { ref.current = value; } }); }; /** * SpeedDial Component Implementation */ function SpeedDialComponent(props, ref) { const { className, style, icon, children, defaultOpen = false, open: controlledOpen, direction = 'up', disabled = false, onOpen, onClose, onActionClick, hideOnScroll = false, position = { bottom: 16, right: 16 }, size = 'medium', color = 'default', glass = false, glassActions = false, showTooltips = true, ariaLabel, transition = true, ...rest } = props; // Refs const rootRef = useRef(null); // State for uncontrolled component const [internalOpen, setInternalOpen] = useState(defaultOpen); const [visible, setVisible] = useState(true); // Determine if component is controlled const isControlled = controlledOpen !== undefined; const open = isControlled ? controlledOpen : internalOpen; const colorClass = useMemo(() => { const key = COLOR_VARIANT_CLASS[color] ?? COLOR_VARIANT_CLASS.default; return styles[key] ?? styles.fabSolidDefault; }, [color]); // Toggle open state const toggle = useCallback(() => { if (disabled) return; const newOpen = !open; if (!isControlled) { setInternalOpen(newOpen); } if (newOpen && onOpen) { onOpen(); } else if (!newOpen && onClose) { onClose(); } }, [disabled, open, isControlled, onOpen, onClose]); // Handle action click const handleActionClick = useCallback((event, actionIndex) => { if (onActionClick) { onActionClick(event, actionIndex); } // Close the speed dial after an action is clicked if (!isControlled) { setInternalOpen(false); } if (onClose) { onClose(); } }, [isControlled, onClose, onActionClick]); // Handle backdrop click const handleBackdropClick = useCallback(() => { if (!isControlled) { setInternalOpen(false); } if (onClose) { onClose(); } }, [isControlled, onClose]); // Hide on scroll useEffect(() => { if (!hideOnScroll) return; let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; let scrollTimer; const handleScroll = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; if (scrollTop > lastScrollTop + 10) { // Scrolling down setVisible(false); // Close the speed dial if (open && !isControlled) { setInternalOpen(false); } } else if (scrollTop < lastScrollTop - 10) { // Scrolling up setVisible(true); } lastScrollTop = scrollTop; // Clear the previous timer if (scrollTimer) { window.clearTimeout(scrollTimer); } // Set a new timer to show the speed dial after scrolling stops scrollTimer = window.setTimeout(() => { setVisible(true); }, 1000); }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => { window.removeEventListener('scroll', handleScroll); if (scrollTimer) { window.clearTimeout(scrollTimer); } }; }, [hideOnScroll, open, isControlled]); // Handle keyboard events useEffect(() => { const handleKeyDown = event => { if (event.key === 'Escape' && open) { if (!isControlled) { setInternalOpen(false); } if (onClose) { onClose(); } } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [open, isControlled, onClose]); return jsxs(Fragment, { children: [jsx("div", { className: cn(styles.backdrop, open && styles.backdropVisible), onClick: handleBackdropClick, "aria-hidden": "true" }), jsx("div", { ref: mergeRefs(ref, rootRef), className: cn(styles.root, 'glass-speed-dial', !visible && styles.hidden, className), style: { ...resolvePositionStyle(position), ...(style ?? {}) }, ...rest, children: jsxs("div", { className: styles.container, children: [jsx("div", { className: cn(styles.actions, open && styles.actionsOpen, direction === 'up' || direction === 'down' ? styles.actionsVertical : styles.actionsHorizontal), "aria-hidden": !open, children: React.Children.toArray(children).map((child, index) => { // Check if the action is a valid React element before rendering if (! /*#__PURE__*/React.isValidElement(child)) { if (process.env.NODE_ENV === 'development') { console.warn('Invalid element passed as child to SpeedDial:', child); } return null; } // Extract props from the valid element const actionProps = child.props; return jsx(SpeedDialAction, { ...actionProps, onClick: event => handleActionClick(event, index), glass: glassActions, index: index, totalActions: React.Children.count(children), direction: direction, transition: transition, open: open, showTooltip: showTooltips, size: size }, child.key || index); }) }), jsx("button", { type: "button", onClick: toggle, "aria-label": ariaLabel, "aria-expanded": open, "aria-haspopup": "true", disabled: disabled, className: cn(styles.fab, glass ? styles.fabGlass : colorClass, size === 'small' && styles.fabSmall, size === 'medium' && styles.fabMedium, size === 'large' && styles.fabLarge, disabled && styles.fabDisabled), children: jsx(SpeedDialIcon, { icon: icon, open: open }) })] }) })] }); } /** * SpeedDial Component * * A floating action button that expands to show multiple actions. */ const SpeedDial = /*#__PURE__*/forwardRef(SpeedDialComponent); /** * GlassSpeedDial Component * * Glass variant of the SpeedDial component. */ const GlassSpeedDial = /*#__PURE__*/forwardRef((props, ref) => jsx(SpeedDial, { ...props, glass: true, glassActions: true, ref: ref })); GlassSpeedDial.displayName = 'GlassSpeedDial'; export { GlassSpeedDial, SpeedDial, SpeedDial as default }; //# sourceMappingURL=SpeedDial.js.map