UNPKG

aura-glass

Version:

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

395 lines (392 loc) 11.7 kB
'use client'; import { jsx } from 'react/jsx-runtime'; import React, { useContext, useState, useRef, useEffect, useCallback, createContext } from 'react'; import { cn } from '../../lib/utilsComprehensive.js'; const CollaborationContext = /*#__PURE__*/createContext(null); const defaultCollaborationContext = { currentUser: null, users: [], setCurrentUser: () => {}, updateCursor: () => {}, updateSelection: () => {}, comments: [], addComment: () => {}, resolveComment: () => {}, replyToComment: () => {}, edits: [], applyEdit: () => {}, activities: [], isConnected: false, connectionStatus: 'disconnected', showCursors: false, showComments: false, showActivity: false, toggleCursors: () => {}, toggleComments: () => {}, toggleActivity: () => {} }; let hasWarnedMissingCollaborationProvider = false; const useCollaboration = () => { const context = useContext(CollaborationContext); if (!context) { if (!hasWarnedMissingCollaborationProvider) { console.warn("useCollaboration must be used within a CollaborationProvider. Falling back to default no-op context."); hasWarnedMissingCollaborationProvider = true; } return defaultCollaborationContext; } return context; }; // Generate random colors for users const generateUserColor = () => { const colors = ['var(--glass-color-primary)', 'var(--glass-color-danger)', 'var(--glass-color-success)', 'var(--glass-color-warning)', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1']; return colors[Math.floor(Math.random() * colors.length)]; }; // Simulate WebSocket connection for demo class MockWebSocket { constructor() { this.listeners = {}; } addEventListener(event, callback) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); } removeEventListener(event, callback) { if (this.listeners[event]) { this.listeners[event] = this.listeners[event].filter(cb => cb !== callback); } } send(data) { // Simulate message echo for demo setTimeout(() => { this.emit('message', { data }); }, 50 + Math.random() * 100); } emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => callback(data)); } } // Simulate other users simulateOtherUsers(roomId) { const otherUsers = [{ id: 'user-2', name: 'Sarah Chen', email: 'sarah@company.com', color: 'var(--glass-color-danger)' }, { id: 'user-3', name: 'Mike Johnson', email: 'mike@company.com', color: 'var(--glass-color-success)' }, { id: 'user-4', name: 'Alex Rodriguez', email: 'alex@company.com', color: '#8B5CF6' }]; // Simulate users joining setTimeout(() => { this.emit('message', { data: JSON.stringify({ type: 'user_joined', user: otherUsers[0] }) }); }, 2000); setTimeout(() => { this.emit('message', { data: JSON.stringify({ type: 'user_joined', user: otherUsers[1] }) }); }, 4000); // Simulate cursor movements setInterval(() => { otherUsers.forEach(user => { if (Math.random() > 0.7) { this.emit('message', { data: JSON.stringify({ type: 'cursor_update', userId: user.id, cursor: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight } }) }); } }); }, 1000); } } const CollaborationProvider = ({ children, roomId, enableRealTime = true, maxUsers = 10 }) => { const [currentUser, setCurrentUser] = useState(null); const [users, setUsers] = useState([]); const [comments, setComments] = useState([]); const [edits, setEdits] = useState([]); const [activities, setActivities] = useState([]); const [isConnected, setIsConnected] = useState(false); const [connectionStatus, setConnectionStatus] = useState('disconnected'); const [showCursors, setShowCursors] = useState(true); const [showComments, setShowComments] = useState(true); const [showActivity, setShowActivity] = useState(true); const wsRef = useRef(null); const cursorTimeoutRef = useRef(); // Initialize connection useEffect(() => { if (!enableRealTime) return; setConnectionStatus('connecting'); // Simulate connection delay setTimeout(() => { wsRef.current = new MockWebSocket(); wsRef.current.addEventListener('message', handleWebSocketMessage); setIsConnected(true); setConnectionStatus('connected'); // Start simulating other users for demo wsRef.current.simulateOtherUsers(roomId); // Add activity addActivity({ userId: 'system', type: 'join', description: 'Connected to collaboration room' }); }, 1000); return () => { if (wsRef.current) { setIsConnected(false); setConnectionStatus('disconnected'); } }; }, [roomId, enableRealTime]); const handleWebSocketMessage = event => { try { const data = JSON.parse(event.data); switch (data.type) { case 'user_joined': setUsers(prev => { const existing = prev.find(u => u.id === data.user.id); if (existing) return prev; return [...prev, { ...data.user, lastActive: Date.now() }]; }); addActivity({ userId: data.user.id, type: 'join', description: `${data.user.name} joined the session` }); break; case 'user_left': setUsers(prev => prev.filter(u => u.id !== data.userId)); addActivity({ userId: data.userId, type: 'leave', description: `User left the session` }); break; case 'cursor_update': setUsers(prev => prev.map(user => user.id === data.userId ? { ...user, cursor: data.cursor, lastActive: Date.now() } : user)); break; case 'comment_added': setComments(prev => [...prev, data.comment]); addActivity({ userId: data.comment.userId, type: 'comment', description: 'Added a comment' }); break; case 'edit_applied': setEdits(prev => [...prev.slice(-99), data.edit]); // Keep last 100 edits addActivity({ userId: data.edit.userId, type: 'edit', description: `Made an edit` }); break; } } catch (error) { console.warn('Failed to parse collaboration message:', error); } }; const addActivity = useCallback(activity => { const newActivity = { ...activity, id: `activity-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now() }; setActivities(prev => [newActivity, ...prev.slice(0, 49)]); // Keep last 50 activities }, []); const updateCursor = useCallback(position => { if (!currentUser || !wsRef.current) return; setCurrentUser(prev => prev ? { ...prev, cursor: position, lastActive: Date.now() } : null); // Debounce cursor updates clearTimeout(cursorTimeoutRef.current); cursorTimeoutRef.current = setTimeout(() => { wsRef.current?.send(JSON.stringify({ type: 'cursor_update', userId: currentUser.id, cursor: position })); }, 50); }, [currentUser]); const updateSelection = useCallback(selection => { if (!currentUser || !wsRef.current) return; setCurrentUser(prev => prev ? { ...prev, selection, lastActive: Date.now() } : null); wsRef.current.send(JSON.stringify({ type: 'selection_update', userId: currentUser.id, selection })); }, [currentUser]); const addComment = useCallback(comment => { if (!currentUser || !wsRef.current) return; const newComment = { ...comment, id: `comment-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now() }; setComments(prev => [...prev, newComment]); wsRef.current.send(JSON.stringify({ type: 'comment_added', comment: newComment })); }, [currentUser]); const resolveComment = useCallback(commentId => { setComments(prev => prev.map(comment => comment.id === commentId ? { ...comment, resolved: true } : comment)); if (wsRef.current) { wsRef.current.send(JSON.stringify({ type: 'comment_resolved', commentId })); } }, []); const replyToComment = useCallback((commentId, reply) => { if (!currentUser) return; const newReply = { ...reply, id: `reply-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now(), position: { x: 0, y: 0 } // Replies don't need positions }; setComments(prev => prev.map(comment => comment.id === commentId ? { ...comment, replies: [...(comment.replies || []), newReply] } : comment)); }, [currentUser]); const applyEdit = useCallback(edit => { if (!currentUser || !wsRef.current) return; const newEdit = { ...edit, id: `edit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now() }; setEdits(prev => [...prev.slice(-99), newEdit]); wsRef.current.send(JSON.stringify({ type: 'edit_applied', edit: newEdit })); }, [currentUser]); const toggleCursors = useCallback(() => setShowCursors(prev => !prev), []); const toggleComments = useCallback(() => setShowComments(prev => !prev), []); const toggleActivity = useCallback(() => setShowActivity(prev => !prev), []); const handleSetCurrentUser = useCallback(user => { const userWithDefaults = { ...user, color: user.color || generateUserColor(), lastActive: Date.now() }; setCurrentUser(userWithDefaults); setUsers(prev => { const existing = prev.find(u => u.id === user.id); if (existing) { return prev.map(u => u.id === user.id ? userWithDefaults : u); } return [...prev, userWithDefaults]; }); addActivity({ userId: user.id, type: 'join', description: `${user.name} joined the session` }); }, [addActivity]); const value = { currentUser, users, setCurrentUser: handleSetCurrentUser, updateCursor, updateSelection, comments, addComment, resolveComment, replyToComment, edits, applyEdit, activities, isConnected, connectionStatus, showCursors, showComments, showActivity, toggleCursors, toggleComments, toggleActivity }; return jsx(CollaborationContext.Provider, { "data-glass-component": true, value: value, children: children }); }; const GlassCollaborationProvider = /*#__PURE__*/React.forwardRef(({ roomId, enableRealTime = true, maxUsers = 10, className, children, ...rest }, ref) => { return jsx("div", { ref: ref, className: cn("glass-collaboration-provider", className), ...rest, children: jsx(CollaborationProvider, { roomId: roomId, enableRealTime: enableRealTime, maxUsers: maxUsers, children: children }) }); }); GlassCollaborationProvider.displayName = "GlassCollaborationProvider"; export { CollaborationProvider, GlassCollaborationProvider, useCollaboration }; //# sourceMappingURL=GlassCollaborationProvider.js.map