aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
733 lines (730 loc) • 26 kB
JavaScript
'use client';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Slot } from '@radix-ui/react-slot';
import React, { forwardRef, useRef, useState, useImperativeHandle, useCallback, useEffect } from 'react';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
import { LiquidGlassMaterial } from '../../primitives/LiquidGlassMaterial.js';
import { useA11yId, announceToScreenReader, createButtonA11y } from '../../utils/a11y.js';
import { usePredictiveEngine, useInteractionRecorder } from '../advanced/GlassPredictiveEngine.js';
import { useAchievements } from '../advanced/GlassAchievementSystem.js';
import { useBiometricAdaptation } from '../advanced/GlassBiometricAdaptation.js';
import { useEyeTracking } from '../advanced/GlassEyeTracking.js';
import { useSpatialAudio } from '../advanced/GlassSpatialAudio.js';
/**
* GlassButton component
* A glassmorphism button with multiple variants and animations
*/
const GlassButton = /*#__PURE__*/forwardRef(function GlassButton({
variant = "default",
size = "md",
elevation = "level2",
glassVariant = "frosted",
intensity = "medium",
depth = 2,
tint = "neutral",
border = "subtle",
loading = false,
iconOnly = false,
fullWidth = false,
animation = "none",
leftIcon,
rightIcon,
loadingSpinner,
loadingText = "Loading...",
className,
children,
disabled,
asChild = false,
flat = false,
material = "glass",
materialProps,
description,
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy,
"aria-describedby": ariaDescribedBy,
"aria-pressed": ariaPressed,
"aria-expanded": ariaExpanded,
"aria-controls": ariaControls,
"aria-haspopup": ariaHaspopup,
// Consciousness features
predictive = false,
preloadContent = false,
eyeTracking = false,
gazeResponsive = false,
adaptive = false,
biometricResponsive = false,
spatialAudio = false,
audioFeedback = false,
trackAchievements = false,
achievementId,
usageContext = "main",
...props
}, ref) {
useReducedMotion();
const buttonRef = useRef(null);
const [clickCount, setClickCount] = useState(0);
const [isHovered, setIsHovered] = useState(false);
const [adaptiveSize, setAdaptiveSize] = useState(size);
const [predictedAction, setPredictedAction] = useState(null);
const externalOnMouseEnter = props.onMouseEnter;
const externalOnMouseLeave = props.onMouseLeave;
const externalOnFocus = props.onFocus;
const externalOnBlur = props.onBlur;
// Consciousness feature hooks - only initialize if features are enabled
const predictiveEngine = predictive ? usePredictiveEngine() : null;
const eyeTracker = eyeTracking ? useEyeTracking() : null;
const biometricAdapter = adaptive ? useBiometricAdaptation() : null;
const spatialAudioEngine = spatialAudio ? useSpatialAudio() : null;
const achievementTracker = trackAchievements ? useAchievements() : null;
const interactionRecorder = predictive || trackAchievements ? useInteractionRecorder(`glass-button-${variant}-${usageContext}`) : null;
// Generate unique ID for accessibility
const componentId = useA11yId("glass-button");
const descriptionId = description ? useA11yId("glass-button-desc") : undefined;
// Handle ref forwarding
useImperativeHandle(ref, () => buttonRef.current);
// Utility: filter out non-DOM props before spreading on elements
const filterDomProps = useCallback(p => {
const {
loadingText: _lt,
asChild: _ac,
leftIcon: _li,
rightIcon: _ri,
loadingSpinner: _ls,
animation: _an,
flat: _fl,
description: _desc,
// Master flag
consciousness: _cons,
// Consciousness-related props
predictive: _pred,
preloadContent: _prel,
eyeTracking: _eye,
gazeResponsive: _gaze,
adaptive: _ad,
biometricResponsive: _bio,
spatialAudio: _sa,
audioFeedback: _af,
trackAchievements: _ta,
achievementId: _aid,
usageContext: _uctx,
onClick: _oc,
onMouseEnter: _ome,
onMouseLeave: _oml,
onFocus: _ofc,
onBlur: _obl,
...valid
} = p || {};
return valid;
}, []);
const handleMouseEnter = useCallback(event => {
if (!disabled && !loading) {
setIsHovered(true);
}
externalOnMouseEnter?.(event);
}, [disabled, loading, externalOnMouseEnter]);
const handleMouseLeave = useCallback(event => {
setIsHovered(false);
externalOnMouseLeave?.(event);
}, [externalOnMouseLeave]);
const handleFocus = useCallback(event => {
if (!disabled && !loading) {
setIsHovered(true);
}
externalOnFocus?.(event);
}, [disabled, loading, externalOnFocus]);
const handleBlur = useCallback(event => {
setIsHovered(false);
externalOnBlur?.(event);
}, [externalOnBlur]);
// Biometric adaptation effects
useEffect(() => {
if (!biometricResponsive || !biometricAdapter) return;
const adaptButton = () => {
const stressLevel = biometricAdapter.currentStressLevel;
// For now, assume desktop capabilities
const deviceCapabilities = {
isMobile: false,
isDesktop: true
};
// Adapt button size based on device and stress
if (stressLevel < 0.3 && deviceCapabilities.isDesktop) {
setAdaptiveSize("sm"); // Smaller size when relaxed on desktop
} else {
setAdaptiveSize(size); // Use original size
}
};
// Initial adaptation
adaptButton();
// Listen for biometric changes
const interval = setInterval(adaptButton, 3000);
return () => clearInterval(interval);
}, [biometricResponsive, biometricAdapter, size]);
// Eye tracking effects
useEffect(() => {
if (!gazeResponsive || !eyeTracker || !buttonRef.current) return;
// Eye tracking methods not available in current implementation
// eyeTracker.onGazeEnter(buttonRef.current, handleGazeEnter);
// eyeTracker.onGazeExit(buttonRef.current, handleGazeExit);
return () => {
if (buttonRef.current) ;
};
}, [gazeResponsive, eyeTracker, disabled, loading, spatialAudioEngine, audioFeedback, achievementTracker, trackAchievements, variant, usageContext, children]);
// Predictive action detection
useEffect(() => {
if (!predictive || !predictiveEngine) return;
// Check for predicted actions related to this button
const predictions = predictiveEngine.predictions;
const buttonPrediction = predictions.find(p => p.metadata?.buttonVariant === variant || p.metadata?.buttonContext === usageContext);
if (buttonPrediction && buttonPrediction.confidence > 0.7) {
setPredictedAction(buttonPrediction.type);
// Preload action if specified
if (preloadContent && buttonPrediction.type === "preload") {
console.log(`Preloading content for button: ${variant}-${usageContext}`);
}
} else {
setPredictedAction(null);
}
}, [predictive, predictiveEngine, variant, usageContext, preloadContent]);
// Enhanced interaction tracking
const handleInteraction = useCallback(event => {
if (disabled || loading) return;
setClickCount(prev => prev + 1);
// Record interaction for predictive learning
if (interactionRecorder) {
interactionRecorder.recordClick(event);
}
// Play spatial audio feedback
if (spatialAudioEngine && audioFeedback) {
spatialAudioEngine.playGlassSound("button_click_success", {
x: buttonRef.current?.offsetLeft || 0,
y: buttonRef.current?.offsetTop || 0,
z: 0 // Default to center depth
});
}
// Track achievements
if (achievementTracker && trackAchievements) {
achievementTracker.recordAction(achievementId || "button_interaction", {
variant,
context: usageContext,
clickCount,
buttonText: typeof children === "string" ? children : "button",
timestamp: Date.now()
});
// Special achievements for frequent use
if (clickCount > 0 && clickCount % 10 === 0) {
achievementTracker.recordAction("button_power_user", {
variant,
totalClicks: clickCount
});
}
}
// Call original onClick handler
props.onClick?.(event);
}, [disabled, loading, interactionRecorder, spatialAudioEngine, audioFeedback, achievementTracker, trackAchievements, achievementId, variant, usageContext, clickCount, children, props.onClick]);
// Ensure icon-only controls default to a non-frosted appearance
const resolvedVariant = iconOnly && (variant === "default" || !variant) ? "ghost" : variant;
// Validate accessibility requirements
React.useEffect(() => {
if (iconOnly && !ariaLabel && !ariaLabelledBy && !children) {
console.warn("GlassButton: Icon-only buttons must have an aria-label or aria-labelledby prop for accessibility");
}
}, [iconOnly, ariaLabel, ariaLabelledBy, children]);
// Create accessibility attributes
const a11yProps = createButtonA11y({
id: componentId,
label: ariaLabel,
description,
pressed: ariaPressed,
expanded: ariaExpanded,
controls: ariaControls,
haspopup: ariaHaspopup === "true" ? true : ariaHaspopup === "false" ? false : ariaHaspopup,
disabled: disabled || loading,
descriptionId
});
// Announce loading state changes to screen readers
React.useEffect(() => {
if (loading) {
announceToScreenReader(loadingText, "polite");
}
}, [loading, loadingText]);
// Use adaptive size if biometric adaptation is enabled, otherwise use original size
const effectiveSize = biometricResponsive ? adaptiveSize : size;
const sizeClasses = {
xs: iconOnly ? "h-6 w-6 p-0" : "h-6 px-2 text-xs",
sm: iconOnly ? "h-8 w-8 p-0" : "h-8 px-3 text-sm",
md: iconOnly ? "h-10 w-10 p-0" : "h-10 px-4 text-sm",
lg: iconOnly ? "h-12 w-12 p-0" : "h-12 px-6 text-base",
xl: iconOnly ? "h-14 w-14 p-0" : "h-14 px-8 text-lg"
};
const baseClasses = cn(
// Base styles
"inline-flex items-center justify-center", "font-medium", "transition-all duration-300 ease-out",
// Unified focus ring utility
"glass-focus", "disabled:pointer-events-none disabled:opacity-50", "relative overflow-hidden",
// Specular overlay + parallax support
// Avoid specular overlay on icon-only buttons to prevent hard frost look
!iconOnly && "glass-overlay-specular glass-parallax",
// Size - use effective size with biometric adaptation
sizeClasses?.[effectiveSize],
// Full width
{
"w-full": fullWidth
},
// Consciousness feature styles
{
"glass-focus-strong glass-surface-primary": gazeResponsive && isHovered /* || (eyeTracker?.isGazing && !disabled) */,
"animate-pulse": predictedAction === "suggest" && !disabled,
"glass-focus glass-surface-success": predictedAction === "preload" && !disabled,
"transform-gpu": biometricResponsive || gazeResponsive,
"scale-105": gazeResponsive && isHovered && !disabled
});
const variantStyles = {
default: {
glassVariant: "frosted",
tint: "neutral",
intensity: "medium",
border: "subtle",
className: "glass-text-primary hover:glass-text-secondary"
},
primary: {
glassVariant: "liquid",
tint: "blue",
intensity: "strong",
border: "glow",
lighting: "volumetric",
caustics: true,
className: "glass-text-primary hover:glass-text-secondary"
},
secondary: {
glassVariant: "crystal",
tint: "purple",
intensity: "strong",
border: "glow",
lighting: "directional",
refraction: true,
className: "glass-text-primary hover:glass-text-secondary"
},
tertiary: {
glassVariant: "frosted",
tint: "gray",
intensity: "medium",
border: "subtle",
lighting: "ambient",
className: "glass-text-primary hover:glass-text-secondary"
},
destructive: {
glassVariant: "frosted",
tint: "red",
intensity: "extreme",
border: "neon",
lighting: "directional",
chromatic: true,
className: "glass-text-primary hover:glass-text-secondary"
},
error: {
glassVariant: "frosted",
tint: "red",
intensity: "extreme",
border: "neon",
lighting: "directional",
chromatic: true,
className: "glass-text-primary hover:glass-text-secondary"
},
success: {
glassVariant: "frosted",
tint: "green",
intensity: "strong",
border: "glow",
lighting: "directional",
className: "glass-text-primary hover:glass-text-secondary"
},
warning: {
glassVariant: "frosted",
tint: "orange",
intensity: "strong",
border: "glow",
lighting: "directional",
className: "glass-text-primary hover:glass-text-secondary"
},
outline: {
glassVariant: "ethereal",
tint: "neutral",
intensity: "medium",
border: "glow",
lighting: "ambient",
adaptive: true,
className: "glass-text-primary hover:glass-text-secondary"
},
ghost: {
glassVariant: "ethereal",
tint: "neutral",
intensity: "subtle",
border: "none",
lighting: "ambient",
adaptive: true,
className: "glass-text-secondary hover:glass-text-primary glass-elev-0"
},
link: {
glassVariant: "ethereal",
tint: "blue",
intensity: "subtle",
border: "none",
className: "glass-text-link hover:glass-text-link-hover underline-offset-4 hover:underline"
},
gradient: {
glassVariant: "holographic",
tint: "rainbow",
intensity: "ultra",
border: "dynamic",
lighting: "iridescent",
chromatic: true,
parallax: true,
className: "glass-text-primary hover:glass-text-secondary"
}
};
const variantConfig = variantStyles?.[resolvedVariant];
// Map variant to accent focus ring utility
const variantAccent = resolvedVariant === "destructive" || resolvedVariant === "error" ? "glass-accent-danger" : resolvedVariant === "success" ? "glass-accent-success" : resolvedVariant === "warning" ? "glass-accent-warning" : resolvedVariant === "secondary" ? "glass-accent-info" : "glass-accent-primary";
const getAnimationPreset = () => {
switch (animation) {
case "scale":
return "scaleIn";
case "bounce":
return "bounceIn";
case "pulse":
return "pulseIn";
default:
return "none";
}
};
const renderContent = () => {
if (loading) {
return jsxs(Fragment, {
children: [loadingSpinner || jsx("div", {
className: 'w-4 h-4 glass-border-2 glass-border-current glass-border-t-transparent glass-radius-full animate-spin'
}), !iconOnly && jsx("span", {
className: 'ml-2',
children: loadingText
})]
});
}
if (iconOnly) {
return leftIcon || rightIcon || children;
}
return jsxs(Fragment, {
children: [leftIcon && jsx("span", {
className: 'mr-2',
"data-icon": true,
children: leftIcon
}), children, rightIcon && jsx("span", {
className: 'ml-2',
"data-icon": true,
children: rightIcon
})]
});
};
if (resolvedVariant === "ghost" || resolvedVariant === "link") {
const Comp = asChild ? Slot : "button";
return jsx(MotionFramer, {
"data-glass-component": true,
preset: getAnimationPreset(),
animateOnHover: animation !== "none",
className: 'inline-block',
children: jsxs(Comp, {
className: cn(baseClasses, variantAccent, variantConfig.className, "glass-radius-md overflow-visible glass-magnet glass-ripple glass-press", flat && "bg-transparent border-0 shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0", className),
disabled: disabled || loading,
onClick: handleInteraction,
ref: buttonRef,
...a11yProps,
...filterDomProps(props),
children: [renderContent(), description && jsx("span", {
id: descriptionId,
className: 'sr-only glass-focus glass-touch-target glass-contrast-guard',
children: description
})]
})
});
}
return jsx(MotionFramer, {
preset: getAnimationPreset(),
animateOnHover: animation !== "none",
className: 'inline-block',
children: material === "liquid" ? jsx(LiquidGlassMaterial, {
as: asChild ? Slot : "button",
ior: materialProps?.ior || (variant === "primary" ? 1.48 : 1.44),
thickness: materialProps?.thickness || (size === "xs" ? 4 : size === "sm" ? 6 : size === "md" ? 8 : 10),
tint: materialProps?.tint || (variant === "primary" ? {
r: 59,
g: 130,
b: 246,
a: 0.1
} : {
r: 0,
g: 0,
b: 0,
a: 0.05
}),
variant: materialProps?.variant || "regular",
quality: materialProps?.quality || "high",
environmentAdaptation: true,
motionResponsive: true,
interactive: true,
className: cn(baseClasses, variantAccent, variantConfig.className, "liquid-glass-button-surface glass-ripple glass-magnet", className),
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onFocus: handleFocus,
onBlur: handleBlur,
style: {
"--liquid-glass-button-pressure": isHovered ? "0.02" : "0.0",
"--liquid-glass-hover-refraction": "1.2",
"--liquid-glass-press-density": "0.95"
},
"data-liquid-glass-button": "true",
"data-button-variant": variant,
"data-button-size": size,
disabled: disabled || loading,
onClick: handleInteraction,
ref: buttonRef,
...a11yProps,
...(() => {
const {
loadingText: _,
asChild: __,
leftIcon: ___,
rightIcon: ____,
loadingSpinner: _____,
animation: ______,
description: _______,
// Filter out the master flag too so it never hits the DOM
consciousness: __ignoreConsciousness,
material: ________,
materialProps: _________,
predictive: __________,
preloadContent: ___________,
eyeTracking: ____________,
gazeResponsive: _____________,
adaptive: ______________,
biometricResponsive: _______________,
spatialAudio: ________________,
audioFeedback: _________________,
trackAchievements: __________________,
achievementId: ___________________,
usageContext: ____________________,
onClick: _____________________,
onMouseEnter: ______________________,
onMouseLeave: _______________________,
onFocus: _________________________,
onBlur: __________________________,
...validProps
} = props;
return validProps;
})(),
children: asChild ?
// When rendering asChild (Slot), Slot expects exactly one child element.
// Defer content to the passed child to avoid React.Children.only errors.
children : jsxs(Fragment, {
children: [resolvedVariant === "gradient" && jsx("div", {
className: 'absolute inset-0 glass-gradient-primary glass-gradient-primary via-secondary/20 glass-gradient-primary glass-radius-md glass-focus glass-touch-target glass-contrast-guard'
}), jsx("span", {
className: 'relative z-10',
children: renderContent()
}), description && jsx("span", {
id: descriptionId,
className: 'sr-only',
children: description
})]
})
}) : jsx(OptimizedGlassCore, {
as: asChild ? Slot : "button",
variant: glassVariant || variantConfig.glassVariant,
elevation: elevation,
intensity: intensity || variantConfig.intensity,
depth: depth,
tint: tint || variantConfig.tint,
border: border || variantConfig.border || "subtle",
animation: animation === "none" ? "none" : animation === "scale" ? "float" : animation === "bounce" ? "pulse" : "shimmer",
lighting: variantConfig.lighting || "ambient",
caustics: variantConfig.caustics || false,
chromatic: variantConfig.chromatic || false,
parallax: variantConfig.parallax || false,
refraction: variantConfig.refraction || false,
adaptive: variantConfig.adaptive || false,
interactive: true,
hoverSheen: true,
liftOnHover: true,
press: true,
magnet: true,
cursorHighlight: false,
performanceMode: "ultra",
className: cn(baseClasses, variantAccent, variantConfig.className, "glass-radius-md glass-ripple glass-magnet", className),
disabled: disabled || loading,
onClick: handleInteraction,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onFocus: handleFocus,
onBlur: handleBlur,
ref: buttonRef,
...a11yProps,
...filterDomProps(props),
children: asChild ?
// When rendering asChild (Slot), Slot expects exactly one child element.
// Defer content to the passed child to avoid React.Children.only errors.
children : jsxs(Fragment, {
children: [resolvedVariant === "gradient" && jsx("div", {
className: 'absolute inset-0 glass-gradient-primary glass-gradient-primary via-secondary/20 glass-gradient-primary glass-radius-md glass-focus glass-touch-target glass-contrast-guard'
}), jsx("span", {
className: 'relative z-10',
children: renderContent()
}), description && jsx("span", {
id: descriptionId,
className: 'sr-only',
children: description
})]
})
})
});
});
GlassButton.displayName = "GlassButton";
const IconButton = /*#__PURE__*/forwardRef(function IconButton({
icon,
variant = "ghost",
flat = true,
size = "sm",
...props
}, ref) {
return jsx(GlassButton, {
ref: ref,
iconOnly: true,
variant: variant,
flat: flat,
size: size,
...props,
children: icon
});
});
IconButton.displayName = "IconButton";
const ToggleButton = /*#__PURE__*/forwardRef(function ToggleButton({
pressed = false,
onPressedChange,
pressedVariant = "primary",
unpressedVariant = "outline",
onClick,
...props
}, ref) {
const handleClick = event => {
onPressedChange?.(!pressed);
onClick?.(event);
};
return jsx(GlassButton, {
ref: ref,
variant: pressed ? pressedVariant : unpressedVariant,
onClick: handleClick,
"aria-pressed": pressed,
...props
});
});
ToggleButton.displayName = "ToggleButton";
const FloatingActionButton = /*#__PURE__*/forwardRef(function FloatingActionButton({
size = "md",
position = "bottom-right",
icon,
extended = false,
children,
className,
...props
}, ref) {
const positionClasses = {
"bottom-right": "fixed bottom-6 right-6",
"bottom-left": "fixed bottom-6 left-6",
"top-right": "fixed top-6 right-6",
"top-left": "fixed top-6 left-6"
};
const sizeClasses = {
sm: extended ? "h-8 px-3" : "h-8 w-8",
md: extended ? "h-10 px-4" : "h-10 w-10",
lg: extended ? "h-12 px-6" : "h-12 w-12"
};
return jsx(GlassButton, {
ref: ref,
variant: "default",
elevation: "level3",
animation: "bounce",
iconOnly: !extended,
leftIcon: extended ? icon : undefined,
className: cn("shadow-lg z-50", "glass-radius-full", positionClasses?.[position], sizeClasses?.[size], className),
...props,
children: extended ? children : icon
});
});
FloatingActionButton.displayName = "FloatingActionButton";
/**
* Enhanced GlassButton with consciousness features enabled by default
* Use this for buttons that should be intelligent and adaptive
*/
const ConsciousGlassButton = /*#__PURE__*/forwardRef(function ConsciousGlassButton(props, ref) {
return jsx(GlassButton, {
ref: ref,
predictive: true,
adaptive: true,
biometricResponsive: true,
trackAchievements: true,
achievementId: "conscious_button_usage",
...props
});
});
ConsciousGlassButton.displayName = "ConsciousGlassButton";
/**
* Eye-tracking enabled button for gaze-responsive interactions
*/
const GazeResponsiveButton = /*#__PURE__*/forwardRef(function GazeResponsiveButton(props, ref) {
return jsx(GlassButton, {
ref: ref,
eyeTracking: true,
gazeResponsive: true,
spatialAudio: true,
audioFeedback: true,
trackAchievements: true,
achievementId: "gaze_button_interaction",
...props
});
});
GazeResponsiveButton.displayName = "GazeResponsiveButton";
/**
* Predictive button that learns user interaction patterns
*/
const PredictiveButton = /*#__PURE__*/forwardRef(function PredictiveButton(props, ref) {
return jsx(GlassButton, {
ref: ref,
predictive: true,
preloadContent: true,
trackAchievements: true,
achievementId: "predictive_button_usage",
...props
});
});
PredictiveButton.displayName = "PredictiveButton";
/**
* Accessibility-focused button with biometric adaptation and spatial audio
*/
const AccessibleButton = /*#__PURE__*/forwardRef(function AccessibleButton(props, ref) {
return jsx(GlassButton, {
ref: ref,
adaptive: true,
biometricResponsive: true,
spatialAudio: true,
audioFeedback: true,
trackAchievements: true,
achievementId: "accessible_button_usage",
...props
});
});
AccessibleButton.displayName = "AccessibleButton";
export { AccessibleButton, ConsciousGlassButton, FloatingActionButton, GazeResponsiveButton, GlassButton, IconButton, PredictiveButton, ToggleButton };
//# sourceMappingURL=GlassButton.js.map