UNPKG

aura-glass

Version:

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

733 lines (730 loc) 26 kB
'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