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