aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
369 lines (366 loc) • 12.5 kB
JavaScript
'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