aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
249 lines (246 loc) • 7.64 kB
JavaScript
'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