/* Sidebar: Spaces Rail + Rooms panel */
const SpacesRail = ({ spaces, activeSpace, onSelect, collapsed }) => {
if (collapsed) return null;
return (
);
};
const RailAddBtn = () => {
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
if (!open) return;
const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
setTimeout(() => document.addEventListener('mousedown', h), 50);
return () => document.removeEventListener('mousedown', h);
}, [open]);
return (
setOpen(v => !v)}>
Create · Join · Discover
{open && (
Create
setOpen(false)}> New space ⌘N
setOpen(false)}> Group DM
Join
setOpen(false)}> Paste invite link
setOpen(false)}> Join by space ID
Discover
setOpen(false)}> Browse public rooms
setOpen(false)}> Featured communities
)}
);
};
const roomIcon = (type) => {
if (type === 'voice') return ;
if (type === 'announce') return ;
if (type === 'lock') return ;
return ;
};
const RoomsPanel = ({ space, roomsData, activeRoom, onSelectRoom, user, onOpenProfile, onOpenSettings, call, onLeaveCall, onToggleStreamCollapse, onToggleFullscreen, onToggleMic, onToggleDeaf, onToggleScreen, onToggleCam, onJumpToCall }) => {
const [collapsed, setCollapsed] = React.useState({});
const [q, setQ] = React.useState('');
const [menuOpen, setMenuOpen] = React.useState(false);
const toggle = (id) => setCollapsed(c => ({ ...c, [id]: !c[id] }));
const matchesQ = (name) => !q || name.toLowerCase().includes(q.toLowerCase());
return (
);
};
const MiniCall = ({ call, onLeave, onToggleStreamCollapse, onToggleFullscreen, onToggleMic, onToggleDeaf, onToggleScreen, onToggleCam, onJumpToCall }) => {
const [elapsed, setElapsed] = React.useState(0);
React.useEffect(() => {
const i = setInterval(() => setElapsed(e => e + 1), 1000);
return () => clearInterval(i);
}, []);
const fmt = (s) => {
const m = Math.floor(s/60), sec = s%60;
return `${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`;
};
return (
{call.streaming && (
{call.streamer}'s stream
{!call.streamCollapsed && (
<>
>
)}
{!call.streamCollapsed && (
)}
)}
Voice connected
{call.roomName}
{fmt(elapsed)} · {call.participants.length} here
e.stopPropagation()}>
e.stopPropagation()}>
);
};
const RoomItem = ({ room, active, onClick }) => {
if (room.type === 'dm' || room.type === 'group') {
return (
{room.avatar}
{room.presence && }
{room.name}
{room.unread > 0 &&
{room.unread}
}
);
}
return (
{roomIcon(room.type)}
{room.name}
{room.type === 'voice' && room.users > 0 && (
{room.users}
)}
{room.unread > 0 &&
{room.unread}
}
);
};
const SpaceMenu = ({ onClose }) => {
React.useEffect(() => {
const h = (e) => onClose();
setTimeout(() => document.addEventListener('click', h, { once: true }), 0);
return () => document.removeEventListener('click', h);
}, []);
return (
Invite people ⌘I
Create room
Create category
Space settings
Notification prefs
Copy space link
Leave space
);
};
const UserPanel = ({ user, onOpenProfile, onOpenSettings }) => {
const [mic, setMic] = React.useState(true);
const [head, setHead] = React.useState(true);
return (
{user.avatar}
{user.name}
{user.statusText}
);
};
Object.assign(window, { SpacesRail, RoomsPanel, RoomItem, SpaceMenu, UserPanel, MiniCall });