aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
199 lines (196 loc) • 5.97 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { forwardRef, useState, useRef, useCallback, useMemo } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { GlassButton } from '../button/GlassButton.js';
import '../button/GlassFab.js';
import '../button/GlassMagneticButton.js';
import { createGlassStyle } from '../../core/mixins/glassMixins.js';
import styles from './RippleButton.module.css.js';
// Convert color string to RGB values
const colorToRgb = color => {
// Convert named colors to their RGB values
switch (color) {
case 'primary':
return '99, 102, 241';
// Indigo
case 'secondary':
return '156, 39, 176';
// Purple
case 'error':
return '240, 82, 82';
// Red
case 'info':
return '3, 169, 244';
// Light Blue
case 'success':
return '76, 175, 80';
// Green
case 'warning':
return '255, 152, 0';
// Orange
case 'white':
return '255, 255, 255';
default:
// If it's already an RGB value in format 'r, g, b'
if (/^\d+,\s*\d+,\s*\d+$/.test(color)) {
return color;
}
// Default color (white)
return '255, 255, 255';
}
};
// Get ripple size based on button size
const getRippleSize = size => {
switch (size) {
case 'small':
return 100;
case 'large':
return 200;
case 'medium':
default:
return 150;
}
};
// Get ripple duration based on speed - Adjusted default
const getRippleDuration = speed => {
switch (speed) {
case 'slow':
return 900;
case 'fast':
return 400;
case 'medium':
default:
return 300;
// Shortened default duration further for better sync with physics
}
};
/**
* RippleButton Component Implementation
* Wraps the base Button component to add a ripple effect.
*/
function RippleButtonComponent(
// Inherit ButtonProps and add Ripple-specific props
props, ref) {
const {
children,
disabled = false,
rippleColor = 'white',
rippleSize: rippleSizeProp,
// Rename prop to avoid conflict
rippleSpeed = 'medium',
centerRipple = false,
onClick,
onMouseDown,
// Capture original onMouseDown
// Extract Button props
variant = 'primary',
size = 'md',
fullWidth = false,
className,
style,
...rest // Rest are Button props
} = props;
const prefersReducedMotion = useReducedMotion();
const [ripples, setRipples] = useState([]);
const rippleCount = useRef(0);
const wrapperRef = useRef(null); // Ref for the wrapper
// Handle ripple creation on mouse down
const handleMouseDown = useCallback(event => {
if (disabled || prefersReducedMotion) {
if (onMouseDown) onMouseDown(event); // Call original handler
return;
}
const wrapper = wrapperRef.current;
if (!wrapper) return;
const rect = wrapper.getBoundingClientRect();
let x, y;
if (centerRipple) {
x = rect.width / 2;
y = rect.height / 2;
} else {
x = event.clientX - rect.left;
y = event.clientY - rect.top;
}
const id = rippleCount.current++;
const duration = getRippleDuration(rippleSpeed);
// Start ripple animation
setRipples(prev => [...prev, {
id,
x,
y
}]);
// Schedule ripple removal after animation duration
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== id));
}, duration);
// Call the original onMouseDown handler if provided
if (onMouseDown) {
onMouseDown(event);
}
}, [disabled, prefersReducedMotion, centerRipple, rippleSpeed, onMouseDown]); // Added dependencies
const rippleSizeValue = rippleSizeProp ? getRippleSize(rippleSizeProp) : getRippleSize(size); // Use prop or button size
const rippleDuration = getRippleDuration(rippleSpeed);
const rippleColorRgb = colorToRgb(rippleColor);
const wrapperStyle = useMemo(() => {
const base = createGlassStyle({
intent: 'neutral',
elevation: 'level2',
tier: 'high'
});
const {
position,
...rest
} = base;
return {
...rest,
'--ripple-color-rgb': rippleColorRgb
};
}, [rippleColorRgb]);
return (
// Apply ripple color CSS variable to the wrapper
jsxs("div", {
ref: wrapperRef,
style: wrapperStyle,
className: cn(styles.wrapper, 'glass-ripple-button glass-contrast-guard', className),
children: [jsx(GlassButton, {
ref: ref,
variant: variant,
size: size,
disabled: disabled,
fullWidth: fullWidth,
className: "glass-focus glass-touch-target",
style: style,
onClick: onClick,
onMouseDown: handleMouseDown,
...rest,
'aria-pressed': rest['aria-pressed'] === 'true' ? true : rest['aria-pressed'] === 'false' ? false : rest['aria-pressed'] === 'mixed' ? undefined : rest['aria-pressed'],
'aria-expanded': rest['aria-expanded'] === 'true' ? true : rest['aria-expanded'] === 'false' ? false : rest['aria-expanded'],
children: children
}), !prefersReducedMotion && jsx("div", {
className: styles.ripples,
"aria-hidden": "true",
children: ripples.map(ripple => jsx("span", {
className: styles.ripple,
style: {
left: ripple.x - rippleSizeValue / 2,
top: ripple.y - rippleSizeValue / 2,
width: rippleSizeValue,
height: rippleSizeValue,
animationDuration: `${rippleDuration}ms`
}
}, ripple.id))
})]
})
);
}
/**
* RippleButton Component
*
* A button component that wraps the base Button to add a ripple effect feedback.
* It inherits physics interactions from the base Button.
*/
const RippleButton = /*#__PURE__*/forwardRef(RippleButtonComponent);
export { RippleButton as default };
//# sourceMappingURL=RippleButton.js.map