UNPKG

aura-glass

Version:

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

598 lines (595 loc) 18.5 kB
'use client'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { motion, AnimatePresence } from 'framer-motion'; import { useState, useEffect } from 'react'; import { easings } from './AdvancedAnimations.js'; // Glass transition variants const glassTransitionVariants = { // Shatter transition shatter: { initial: { scale: 1, opacity: 1, filter: 'blur(0px)' }, exit: { scale: [1, 1.02, 0.98, 1.1, 0], opacity: [1, 0.8, 0.6, 0.3, 0], filter: ['blur(0px)', 'blur(1px)', 'blur(3px)', 'blur(var(--glass-glass-blur-md))', 'blur(15px)'], rotate: [0, -2, 4, -8, 15], transition: { duration: 0.8, times: [0, 0.2, 0.4, 0.7, 1], ease: easings.easeOutExpo } }, enter: { scale: [0, 0.8, 1.05, 1], opacity: [0, 0.3, 0.8, 1], filter: ['blur(15px)', 'blur(var(--glass-glass-blur-md))', 'blur(2px)', 'blur(0px)'], rotate: [15, -5, 2, 0], transition: { duration: 0.8, times: [0, 0.3, 0.7, 1], ease: easings.easeOutBack } } }, // Liquid transition liquid: { initial: { clipPath: 'inset(0% 0% 0% 0% round 16px)', scale: 1, opacity: 1 }, exit: { clipPath: ['inset(0% 0% 0% 0% round 16px)', 'inset(10% 5% 10% 5% round 32px)', 'inset(30% 20% 30% 20% round 50%)', 'inset(50% 50% 50% 50% round 50%)'], scale: [1, 0.95, 0.8, 0.5], opacity: [1, 0.8, 0.4, 0], transition: { duration: 0.6, ease: easings.easeInOutCubic } }, enter: { clipPath: ['inset(50% 50% 50% 50% round 50%)', 'inset(30% 20% 30% 20% round 50%)', 'inset(10% 5% 10% 5% round 32px)', 'inset(0% 0% 0% 0% round 16px)'], scale: [0.5, 0.8, 0.95, 1], opacity: [0, 0.4, 0.8, 1], transition: { duration: 0.6, ease: easings.easeOutExpo } } }, // Ripple transition ripple: { initial: { scale: 1, opacity: 1, borderRadius: '16px' }, exit: { scale: [1, 1.5, 0], opacity: [1, 0.3, 0], borderRadius: ['16px', '50%', '50%'], transition: { duration: 0.5, ease: easings.easeInOutCubic } }, enter: { scale: [0, 1.2, 1], opacity: [0, 0.6, 1], borderRadius: ['50%', '50%', '16px'], transition: { duration: 0.5, ease: easings.easeOutBack } } }, // Morph transition morph: { initial: { borderRadius: '16px', scaleX: 1, scaleY: 1, opacity: 1 }, exit: { borderRadius: ['16px', '50px 16px 30px 40px', '20px 60px 40px 20px', '50%'], scaleX: [1, 0.8, 1.2, 0], scaleY: [1, 1.2, 0.8, 0], opacity: [1, 0.7, 0.3, 0], transition: { duration: 0.7, ease: easings.easeInOutCubic } }, enter: { borderRadius: ['50%', '20px 60px 40px 20px', '50px 16px 30px 40px', '16px'], scaleX: [0, 1.2, 0.8, 1], scaleY: [0, 0.8, 1.2, 1], opacity: [0, 0.3, 0.7, 1], transition: { duration: 0.7, ease: easings.easeOutExpo } } }, // Frost transition frost: { initial: { scale: 1, opacity: 1, filter: 'blur(0px) brightness(1)' }, exit: { scale: [1, 1.1, 0.9, 0], opacity: [1, 0.8, 0.4, 0], filter: ['blur(0px) brightness(1)', 'blur(2px) brightness(1.2)', 'blur(var(--glass-glass-blur-md)) brightness(0.8)', 'blur(var(--glass-glass-blur-lg)) brightness(0.5)'], transition: { duration: 0.8, ease: easings.easeInOutCubic } }, enter: { scale: [0, 0.9, 1.1, 1], opacity: [0, 0.4, 0.8, 1], filter: ['blur(var(--glass-glass-blur-lg)) brightness(0.5)', 'blur(var(--glass-glass-blur-md)) brightness(0.8)', 'blur(2px) brightness(1.2)', 'blur(0px) brightness(1)'], transition: { duration: 0.8, ease: easings.easeOutExpo } } } }; function GlassTransition({ children, variant = 'liquid', duration, className, style }) { useReducedMotion(); const transitionVariant = glassTransitionVariants[variant]; // Override duration if provided const customVariant = duration ? { ...transitionVariant, exit: { ...transitionVariant.exit, transition: { ...transitionVariant.exit.transition, duration } }, enter: { ...transitionVariant.enter, transition: { ...transitionVariant.enter.transition, duration } } } : transitionVariant; return jsx(motion.div, { className: cn('glass-transition-container', className), style: style, variants: customVariant, initial: "initial", animate: "initial", exit: "exit", children: children }); } function GlassPageTransition({ children, variant = 'morph', duration = 0.6, className }) { return jsx(AnimatePresence, { mode: "wait", children: jsx(GlassTransition, { variant: variant, duration: duration, className: className, children: children }) }); } function SwipeableGlassCards({ cards, onSwipe, className }) { const [currentIndex, setCurrentIndex] = useState(0); const [direction, setDirection] = useState('left'); const handleDragEnd = info => { const threshold = 100; const velocity = info.velocity.x; const offset = info.offset.x; if (Math.abs(velocity) > 500 || Math.abs(offset) > threshold) { const swipeDirection = velocity > 0 || offset > 0 ? 'right' : 'left'; setDirection(swipeDirection); const newIndex = swipeDirection === 'left' ? Math.min(currentIndex + 1, cards.length - 1) : Math.max(currentIndex - 1, 0); if (newIndex !== currentIndex) { setCurrentIndex(newIndex); onSwipe?.(swipeDirection, cards[currentIndex].id); } } }; const slideVariants = { enter: direction => ({ x: direction === 'left' ? 300 : -300, opacity: 0, scale: 0.8, rotateY: direction === 'left' ? 45 : -45 }), center: { x: 0, opacity: 1, scale: 1, rotateY: 0, transition: { duration: 0.5, ease: easings.easeOutExpo } }, exit: direction => ({ x: direction === 'left' ? -300 : 300, opacity: 0, scale: 0.8, rotateY: direction === 'left' ? -45 : 45, transition: { duration: 0.3, ease: easings.easeInOutCubic } }) }; return jsxs("div", { className: cn('glass-relative glass-overflow-hidden', className), children: [jsx(AnimatePresence, { mode: "wait", custom: direction, children: jsx(motion.div, { custom: direction, variants: slideVariants, initial: "enter", animate: "center", exit: "exit", drag: "x", dragConstraints: { left: 0, right: 0 }, dragElastic: 0.3, onDragEnd: (_, info) => handleDragEnd(info), className: cn("glass-foundation-complete glass-surface-primary glass-border glass-border-primary glass-radius-xl glass-p-6 glass-cursor-grab active:glass-cursor-grabbing"), style: { background: cards[currentIndex].background || 'var(--glass-bg-default)' }, children: cards[currentIndex].content }, currentIndex) }), jsx("div", { className: cn("glass-flex glass-justify-center glass-mt-4 glass-gap-2"), children: cards.map((_, index) => jsx("button", { onClick: () => { setDirection(index > currentIndex ? 'left' : 'right'); setCurrentIndex(index); }, className: cn('glass-w-2 glass-h-2 glass-radius-full glass-transition-all glass-duration-300 glass-focus glass-touch-target glass-contrast-guard', index === currentIndex ? 'glass-surface-white glass-scale-125' : 'glass-surface-muted hover:glass-surface-secondary') }, index)) })] }); } function GlassAccordion({ items, allowMultiple = false, className }) { const prefersReducedMotion = useReducedMotion(); const [openItems, setOpenItems] = useState([]); const toggleItem = id => { setOpenItems(prev => { if (prev.includes(id)) { return prev.filter(item => item !== id); } else { return allowMultiple ? [...prev, id] : [id]; } }); }; return jsx("div", { className: cn('glass-space-y-2', className), children: items.map(item => { const isOpen = openItems.includes(item.id); return jsxs(motion.div, { className: cn("glass-foundation-complete glass-surface-subtle glass-border glass-border-subtle glass-radius-lg glass-overflow-hidden"), layout: true, children: [jsxs(motion.button, { onClick: () => toggleItem(item.id), className: cn("glass-w-full glass-p-4 glass-text-left glass-flex glass-items-center glass-justify-between hover:glass-surface-hover glass-transition-colors glass-focus glass-touch-target glass-contrast-guard"), whileHover: { scale: 1.01 }, whileTap: { scale: 0.99 }, children: [jsxs("div", { className: cn("glass-flex glass-items-center glass-gap-3"), children: [item.icon, jsx("span", { className: cn("glass-font-medium glass-text-white"), children: item.title })] }), jsx(motion.div, { animate: prefersReducedMotion ? {} : { rotate: isOpen ? 180 : 0 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3 }, children: jsx("svg", { className: cn("glass-w-5 glass-h-5 glass-text-secondary"), fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) })] }), jsx(AnimatePresence, { children: isOpen && jsx(motion.div, { initial: { height: 0, opacity: 0 }, animate: prefersReducedMotion ? {} : { height: 'auto', opacity: 1 }, exit: { height: 0, opacity: 0 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3, ease: easings.easeInOutCubic }, className: cn("glass-border-t glass-border-subtle"), children: jsx(motion.div, { initial: { y: -10 }, animate: prefersReducedMotion ? {} : { y: 0 }, exit: { y: -10 }, className: cn("glass-p-4 glass-text-primary"), children: item.content }) }) })] }, item.id); }) }); } function GlassModal({ isOpen, onClose, children, variant = 'ripple', className }) { const prefersReducedMotion = useReducedMotion(); useEffect(() => { const handleEscape = e => { if (e.key === 'Escape') onClose(); }; if (isOpen) { document.addEventListener('keydown', handleEscape); document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleEscape); document.body.style.overflow = 'unset'; }; }, [isOpen, onClose]); return jsx(AnimatePresence, { children: isOpen && jsxs(motion.div, { initial: { opacity: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1 }, exit: { opacity: 0 }, className: cn("glass-fixed glass-inset-0 glass-z-50 glass-flex glass-items-center glass-justify-center glass-p-4"), onClick: onClose, children: [jsx(motion.div, { initial: { opacity: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1 }, exit: { opacity: 0 }, className: cn("glass-absolute glass-inset-0 glass-surface-overlay") }), jsx(GlassTransition, { variant: variant, className: cn('glass-relative glass-max-w-lg glass-w-full glass-max-h-90vh glass-overflow-auto', 'glass-foundation-complete glass-surface-primary glass-border glass-border-primary glass-radius-xl', className), children: jsx("div", { onClick: e => e.stopPropagation(), children: children }) })] }) }); } function GlassTabs({ tabs, defaultTab, orientation = 'horizontal', className, onTabChange }) { const prefersReducedMotion = useReducedMotion(); const [activeTab, setActiveTab] = useState(defaultTab || tabs?.[0]?.id); const handleTabChange = tabId => { setActiveTab(tabId); onTabChange?.(tabId); }; return jsxs("div", { className: cn('glass-tabs', orientation === 'vertical' ? 'glass-flex glass-gap-6' : 'glass-space-y-4', className), children: [jsx("div", { className: cn('glass-foundation-complete glass-surface-subtle glass-border glass-border-subtle glass-radius-lg glass-p-1', orientation === 'vertical' ? 'glass-flex-col glass-w-48' : 'glass-flex', 'glass-relative'), children: tabs?.map(tab => jsxs("button", { onClick: () => handleTabChange(tab.id), className: cn('glass-relative glass-px-4 glass-py-2 glass-radius-md glass-transition-all glass-duration-300 glass-flex glass-items-center glass-gap-2 glass-focus glass-touch-target glass-contrast-guard', 'glass-text-sm glass-font-medium glass-whitespace-nowrap', activeTab === tab.id ? 'glass-text-white glass-surface-subtle' : 'glass-text-secondary hover:glass-text-white hover:glass-surface-hover'), children: [tab.icon, jsx("span", { children: tab.label })] }, tab.id)) }), jsx("div", { className: cn('glass-flex-1 glass-foundation-complete glass-surface-subtle glass-border glass-border-subtle glass-radius-lg', 'glass-min-h-200'), children: jsx(AnimatePresence, { mode: "wait", children: jsx(motion.div, { initial: { opacity: 0, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, exit: { opacity: 0, y: -20 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3, ease: easings.easeOutExpo }, className: cn("glass-p-6"), children: tabs?.find(tab => tab.id === activeTab)?.content }, activeTab) }) })] }); } const demoCards = [{ id: 'insight', content: jsxs("div", { children: [jsx("p", { className: "glass-text-xs glass-text-tertiary uppercase tracking-wide", children: "Insight" }), jsx("h3", { className: "glass-text-xl glass-text-primary font-semibold", children: "Fluid page transitions" }), jsx("p", { className: "glass-text-sm glass-text-secondary", children: "Use material-aware easing to keep movement intuitive." })] }) }, { id: 'motion', content: jsxs("div", { children: [jsx("p", { className: "glass-text-xs glass-text-tertiary uppercase tracking-wide", children: "Motion" }), jsx("h3", { className: "glass-text-xl glass-text-primary font-semibold", children: "Directional swipes" }), jsx("p", { className: "glass-text-sm glass-text-secondary", children: "Cards respond to drag gestures with depth and blur." })] }) }, { id: 'layers', content: jsxs("div", { children: [jsx("p", { className: "glass-text-xs glass-text-tertiary uppercase tracking-wide", children: "Layers" }), jsx("h3", { className: "glass-text-xl glass-text-primary font-semibold", children: "Frosted overlays" }), jsx("p", { className: "glass-text-sm glass-text-secondary", children: "Blend regions using the glass transition variants." })] }) }]; const GlassTransitionsComponent = ({ variant = 'liquid', className, children, ...rest }) => { const [panelIndex, setPanelIndex] = useState(0); const panels = [{ id: 'panel-0', title: 'Glass dashboards', description: 'Morph between layouts while preserving context.' }, { id: 'panel-1', title: 'Immersive nav', description: 'Page transitions ripple out from focal points.' }]; const nextPanel = () => { setPanelIndex(prev => (prev + 1) % panels.length); }; return jsx("div", { className: cn('glass-transitions-demo glass-space-y-6', className), ...rest, children: children ?? jsxs(Fragment, { children: [jsx(GlassPageTransition, { variant: variant, children: jsxs(motion.div, { className: "glass-surface-primary glass-radius-3xl glass-p-8 glass-border glass-border-white/10", children: [jsx("p", { className: "glass-text-xs glass-text-tertiary uppercase tracking-wide mb-2", children: panels[panelIndex].title }), jsx("h2", { className: "glass-text-2xl glass-text-primary font-semibold", children: panels[panelIndex].description }), jsx("button", { type: "button", onClick: nextPanel, className: "glass-mt-6 glass-text-sm glass-text-secondary glass-focus glass-touch-target glass-contrast-guard", children: "Cycle transition" })] }, panels[panelIndex].id) }), jsx(SwipeableGlassCards, { cards: demoCards })] }) }); }; const GlassTransitions = Object.assign(GlassTransitionsComponent, { GlassTransition, GlassPageTransition, SwipeableGlassCards, GlassAccordion, GlassModal, GlassTabs, glassTransitionVariants }); export { GlassAccordion, GlassModal, GlassPageTransition, GlassTabs, GlassTransition, GlassTransitions, SwipeableGlassCards, GlassTransitions as default }; //# sourceMappingURL=GlassTransitions.js.map