UNPKG

aura-glass

Version:

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

369 lines (366 loc) 12.5 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { forwardRef, useState, useEffect, useMemo } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; 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 '../../primitives/motion/MotionFramer.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { createGlassStyle } from '../../utils/createGlassStyle.js'; import { useGlassSound } from '../../utils/soundDesign.js'; import { useA11yId } from '../../utils/a11y.js'; const cursorColors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD', '#00D2D3', '#FF9F43', '#EE5A6F', '#0FB9B1', '#3867D6', '#1DD1A1', '#FD79A8']; const GlassCollaborativeCursor = /*#__PURE__*/forwardRef(({ users = [], currentUserId, showCursorTails = true, showUserLabels = true, showAvatars = false, cursorSize = 'medium', fadeTimeout = 3000, maxTrailLength = 10, realTimeMode = false, soundEnabled = true, showSelections = true, showActions = true, onCursorMove, onUserAction, className = '', ...props }, ref) => { const prefersReducedMotion = useReducedMotion(); const initialUsers = Array.isArray(users) ? users : []; const [cursorTrails, setCursorTrails] = useState({}); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); const [simulatedUsers, setSimulatedUsers] = useState(initialUsers); const { play } = useGlassSound(); const id = useA11yId('glass-collaborative-cursor'); useEffect(() => { setSimulatedUsers(initialUsers); }, [initialUsers]); // Simulated cursor movement for demo useEffect(() => { if (!realTimeMode) return; const interval = setInterval(() => { setSimulatedUsers(prev => prev.map(user => { if (user.id === currentUserId) return user; const deltaX = (Math.random() - 0.5) * 20; const deltaY = (Math.random() - 0.5) * 20; const newX = Math.max(0, Math.min(containerSize.width, user.x + deltaX)); const newY = Math.max(0, Math.min(containerSize.height, user.y + deltaY)); const newUser = { ...user, x: newX, y: newY, lastActivity: Date.now(), isActive: Math.random() > 0.3, action: ['typing', 'selecting', 'drawing', 'idle'][Math.floor(Math.random() * 4)] }; onCursorMove?.(user.id, newX, newY); return newUser; })); }, 100); return () => clearInterval(interval); }, [realTimeMode, currentUserId, containerSize, onCursorMove]); // Update cursor trails useEffect(() => { simulatedUsers.forEach(user => { if (!user.isActive || user.id === currentUserId) return; setCursorTrails(prev => { const userTrails = prev[user.id] || []; const newTrail = { x: user.x, y: user.y, timestamp: Date.now() }; const updatedTrails = [...userTrails, newTrail].filter(trail => Date.now() - trail.timestamp < fadeTimeout).slice(-maxTrailLength); return { ...prev, [user.id]: updatedTrails }; }); }); }, [simulatedUsers, currentUserId, fadeTimeout, maxTrailLength]); // Container resize observer useEffect(() => { const container = document.getElementById(id); if (!container) return; const resizeObserver = new ResizeObserver(entries => { const entry = entries[0]; if (entry) { setContainerSize({ width: entry.contentRect.width, height: entry.contentRect.height }); } }); resizeObserver.observe(container); return () => resizeObserver.disconnect(); }, [id]); // Sound effects useEffect(() => { if (soundEnabled) { simulatedUsers.forEach(user => { if (user.action === 'typing') { play('type'); } else if (user.action === 'selecting') { play('select'); } }); } }, [simulatedUsers, soundEnabled, play]); const getCursorSize = () => { switch (cursorSize) { case 'small': return 16; case 'large': return 28; default: return 20; } }; const getUserColor = userId => { const user = simulatedUsers.find(u => u.id === userId); if (user?.color) return user.color; const index = parseInt(userId, 36) % cursorColors.length; return cursorColors[index]; }; const CursorIcon = ({ user, size }) => jsx(motion.div, { className: cn('glass-absolute glass-pointer-events-none glass-z-50'), style: { left: user.x, top: user.y, transform: 'translate(-2px, -2px)' }, animate: prefersReducedMotion ? {} : { x: 0, y: 0, scale: user.isActive ? 1 : 0.8, opacity: user.isActive ? 1 : 0.6 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3 }, children: jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", style: { filter: `drop-shadow(0 2px 4px rgba(var(--glass-color-black) / var(--glass-opacity-30)))` }, children: jsx("path", { d: "M5 3L19 12L12 14L9 21L5 3Z", fill: getUserColor(user.id), stroke: "white", strokeWidth: "1" }) }) }); const CursorLabel = ({ user }) => jsx(motion.div, { className: cn('glass-absolute glass-pointer-events-none glass-z-40'), style: { left: user.x + 15, top: user.y - 5 }, initial: { opacity: 0, scale: 0.8 }, animate: prefersReducedMotion ? {} : { opacity: user.isActive ? 1 : 0.7, scale: user.isActive ? 1 : 0.9 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2 }, children: jsx("div", { className: cn('glass-px-2 glass-py-1 glass-radius-md glass-text-xs glass-font-medium glass-text-primary glass-border glass-border-white/20'), style: createGlassStyle({ opacity: 0.8, blur: 'sm' }), children: jsxs("div", { className: cn('glass-flex glass-items-center glass-space-x-1'), children: [showAvatars && user.avatar && jsx("img", { src: user.avatar, alt: user.name, className: cn('glass-w-4 glass-h-4 glass-radius-full') }), jsx("span", { children: user.name }), showActions && user.action !== 'idle' && jsxs("span", { className: cn('glass-text-xs glass-opacity-75'), children: [user.action === 'typing' && '✏️', user.action === 'selecting' && '🔍', user.action === 'drawing' && '✨'] })] }) }) }); const CursorTrail = ({ userId }) => { const trails = cursorTrails[userId] || []; const color = getUserColor(userId); return jsx("g", { children: trails.map((trail, index) => { const age = Date.now() - trail.timestamp; const opacity = Math.max(0, 1 - age / fadeTimeout); const size = index / trails.length * 4 + 2; return jsx("circle", { cx: trail.x, cy: trail.y, r: size, fill: color, opacity: opacity * 0.6 }, `${userId}-${trail.timestamp}`); }) }); }; const SelectionOverlay = ({ user }) => { if (!user.selection || !showSelections) return null; const { startX, startY, endX, endY } = user.selection; const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); const x = Math.min(startX, endX); const y = Math.min(startY, endY); return jsx(motion.div, { className: cn('glass-absolute glass-pointer-events-none glass-border-2 glass-border-dashed'), style: { left: x, top: y, width, height, borderColor: getUserColor(user.id), backgroundColor: `${getUserColor(user.id)}20` }, initial: { opacity: 0, scale: 0.9 }, animate: prefersReducedMotion ? {} : { opacity: 0.5, scale: 1 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2 } }); }; const activeCursors = useMemo(() => simulatedUsers.filter(user => user.id !== currentUserId && user.isActive && Date.now() - user.lastActivity < fadeTimeout), [simulatedUsers, currentUserId, fadeTimeout]); return jsxs(OptimizedGlassCore, { ref: ref, id: id, intensity: "subtle", className: cn('glass-relative glass-overflow-hidden glass-min-h-96', className), ...props, children: [jsxs("div", { className: cn('glass-absolute glass-inset-0'), children: [showCursorTails && jsx("svg", { className: cn('glass-absolute glass-inset-0 glass-pointer-events-none'), style: { zIndex: 10 }, children: activeCursors.map(user => jsx(CursorTrail, { userId: user.id }, user.id)) }), jsx(AnimatePresence, { children: activeCursors.map(user => jsx(SelectionOverlay, { user: user }, `selection-${user.id}`)) }), jsx(AnimatePresence, { children: activeCursors.map(user => jsx(CursorIcon, { user: user, size: getCursorSize() }, `cursor-${user.id}`)) }), showUserLabels && jsx(AnimatePresence, { children: activeCursors.map(user => jsx(CursorLabel, { user: user }, `label-${user.id}`)) })] }), jsxs("div", { className: cn('glass-relative glass-z-0 glass-p-8 glass-space-y-4 glass-text-secondary'), children: [jsx("h2", { className: cn('glass-text-2xl glass-font-bold'), children: "Collaborative Workspace" }), jsx("p", { children: "This is a collaborative document where multiple users can work together." }), jsxs("div", { className: cn('glass-grid glass-grid-cols-2 glass-gap-4'), children: [jsxs("div", { className: cn('glass-p-4 glass-surface-secondary glass-radius-lg'), children: [jsx("h3", { className: cn('glass-font-semibold glass-mb-2'), children: "Section A" }), jsx("p", { children: "Content that users can interact with..." })] }), jsxs("div", { className: cn('glass-p-4 glass-surface-secondary glass-radius-lg'), children: [jsx("h3", { className: cn('glass-font-semibold glass-mb-2'), children: "Section B" }), jsx("p", { children: "More interactive content here..." })] })] })] }), jsx("div", { className: cn('glass-absolute glass-bottom-0 glass-left-0 glass-right-0 glass-p-3 glass-surface-overlay glass-blur-backdrop glass-border-t glass-border-white/10'), children: jsxs("div", { className: cn('glass-flex glass-items-center glass-justify-between glass-text-xs glass-text-muted'), children: [jsxs("div", { className: cn('glass-flex glass-items-center glass-space-x-4'), children: [jsxs("span", { children: [activeCursors.length, " active users"] }), realTimeMode && jsxs("span", { className: cn('glass-flex glass-items-center glass-space-x-1'), children: [jsx("div", { className: cn('glass-w-2 glass-h-2 glass-surface-success glass-radius-full glass-animate-pulse') }), jsx("span", { children: "Real-time" })] })] }), jsxs("div", { className: cn('glass-flex glass-items-center glass-space-x-2'), children: [activeCursors.slice(0, 5).map(user => jsx("div", { className: cn('glass-w-3 glass-h-3 glass-radius-full glass-border glass-border-white/30'), style: { backgroundColor: getUserColor(user.id) }, title: user.name }, user.id)), activeCursors.length > 5 && jsxs("span", { className: cn('glass-text-xs'), children: ["+", activeCursors.length - 5] })] })] }) })] }); }); export { GlassCollaborativeCursor }; //# sourceMappingURL=GlassCollaborativeCursor.js.map