feat: Discord Dark theme overhaul — darker palette, floating controls, UI polish
Shift entire color palette from Discord's Ash theme to the darker Dark theme. Redesign VoiceControlBar as floating auto-show pill, add gradient Join Voice screen, move invite icon to server header, add voice channel user status badges, update auth pages with subtle gradient backgrounds, and sweep all hardcoded hex colors across 14 files to match the new darker palette.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -123,9 +123,9 @@ export function DmCallView() {
|
||||
const remoteParticipant = participants.find(p => !p.isLocal);
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-[#111214] min-w-0">
|
||||
<div className="flex-1 flex flex-col bg-[#0b0c0e] min-w-0">
|
||||
{/* Header */}
|
||||
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-[#111214]">
|
||||
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-[#0b0c0e]">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-green">
|
||||
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" />
|
||||
@@ -140,7 +140,7 @@ export function DmCallView() {
|
||||
{/* Remote participant (or waiting) */}
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{remoteParticipant?.videoTrack ? (
|
||||
<div className="w-[360px] h-[270px] rounded-xl overflow-hidden bg-[#2b2d31] relative">
|
||||
<div className="w-[360px] h-[270px] rounded-xl overflow-hidden bg-[#111214] relative">
|
||||
<video
|
||||
id={`dm-video-${remoteParticipant.userId}`}
|
||||
autoPlay
|
||||
@@ -153,7 +153,7 @@ export function DmCallView() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-[200px] h-[200px] rounded-full bg-[#2b2d31] flex items-center justify-center relative">
|
||||
<div className="w-[200px] h-[200px] rounded-full bg-[#111214] flex items-center justify-center relative">
|
||||
<div className="w-24 h-24 rounded-full bg-discord-blurple flex items-center justify-center text-white text-4xl font-bold">
|
||||
{otherName.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
@@ -178,7 +178,7 @@ export function DmCallView() {
|
||||
{/* Local participant */}
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{localParticipant?.videoTrack ? (
|
||||
<div className="w-[360px] h-[270px] rounded-xl overflow-hidden bg-[#2b2d31] relative">
|
||||
<div className="w-[360px] h-[270px] rounded-xl overflow-hidden bg-[#111214] relative">
|
||||
<video
|
||||
id={`dm-video-${localParticipant.userId}`}
|
||||
autoPlay
|
||||
@@ -192,7 +192,7 @@ export function DmCallView() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-[200px] h-[200px] rounded-full bg-[#2b2d31] flex items-center justify-center relative">
|
||||
<div className="w-[200px] h-[200px] rounded-full bg-[#111214] flex items-center justify-center relative">
|
||||
<div className="w-24 h-24 rounded-full bg-discord-blurple flex items-center justify-center text-white text-4xl font-bold">
|
||||
{(authUser?.displayName ?? authUser?.username ?? 'Y').charAt(0).toUpperCase()}
|
||||
</div>
|
||||
@@ -208,12 +208,12 @@ export function DmCallView() {
|
||||
</div>
|
||||
|
||||
{/* Control bar */}
|
||||
<div className="h-[72px] bg-[#1e1f22] flex items-center justify-center gap-4 px-4 flex-shrink-0">
|
||||
<div className="h-[72px] bg-[#111214] flex items-center justify-center gap-4 px-4 flex-shrink-0">
|
||||
{/* Mute */}
|
||||
<button
|
||||
onClick={handleMute}
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-colors ${
|
||||
isMuted ? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30' : 'bg-[#2b2d31] text-discord-text-primary hover:bg-[#36373d]'
|
||||
isMuted ? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30' : 'bg-[#111214] text-discord-text-primary hover:bg-[#2b2d31]'
|
||||
}`}
|
||||
title={isMuted ? 'Unmute' : 'Mute'}
|
||||
>
|
||||
@@ -232,7 +232,7 @@ export function DmCallView() {
|
||||
<button
|
||||
onClick={handleDeafen}
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-colors ${
|
||||
isDeafened ? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30' : 'bg-[#2b2d31] text-discord-text-primary hover:bg-[#36373d]'
|
||||
isDeafened ? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30' : 'bg-[#111214] text-discord-text-primary hover:bg-[#2b2d31]'
|
||||
}`}
|
||||
title={isDeafened ? 'Undeafen' : 'Deafen'}
|
||||
>
|
||||
@@ -251,7 +251,7 @@ export function DmCallView() {
|
||||
<button
|
||||
onClick={handleCamera}
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-colors ${
|
||||
isCameraOn ? 'bg-discord-blurple/20 text-discord-blurple hover:bg-discord-blurple/30' : 'bg-[#2b2d31] text-discord-text-primary hover:bg-[#36373d]'
|
||||
isCameraOn ? 'bg-discord-blurple/20 text-discord-blurple hover:bg-discord-blurple/30' : 'bg-[#111214] text-discord-text-primary hover:bg-[#2b2d31]'
|
||||
}`}
|
||||
title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'}
|
||||
>
|
||||
@@ -270,7 +270,7 @@ export function DmCallView() {
|
||||
<button
|
||||
onClick={handleScreenShare}
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-colors ${
|
||||
isScreenSharing ? 'bg-discord-blurple/20 text-discord-blurple hover:bg-discord-blurple/30' : 'bg-[#2b2d31] text-discord-text-primary hover:bg-[#36373d]'
|
||||
isScreenSharing ? 'bg-discord-blurple/20 text-discord-blurple hover:bg-discord-blurple/30' : 'bg-[#111214] text-discord-text-primary hover:bg-[#2b2d31]'
|
||||
}`}
|
||||
title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'}
|
||||
>
|
||||
@@ -280,7 +280,7 @@ export function DmCallView() {
|
||||
</button>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="w-[1px] h-8 bg-[#3f4147] mx-2" />
|
||||
<div className="w-[1px] h-8 bg-[#2b2d31] mx-2" />
|
||||
|
||||
{/* End Call */}
|
||||
<button
|
||||
|
||||
@@ -193,12 +193,12 @@ export function PictureInPicture() {
|
||||
: channelName;
|
||||
const hasVideo = selectedStream !== null;
|
||||
const isScreen = selectedStream?.type === 'screen';
|
||||
return (_jsxs("div", { ref: containerRef, className: `fixed z-[40] overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/10 bg-[#111214] select-none ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`, style: {
|
||||
return (_jsxs("div", { ref: containerRef, className: `fixed z-[40] overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/10 bg-[#080a0b] select-none ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`, style: {
|
||||
width: PIP_WIDTH,
|
||||
height: PIP_HEIGHT,
|
||||
left: position.x,
|
||||
top: position.y,
|
||||
transition: isDragging ? 'none' : 'left 0.2s ease, top 0.2s ease',
|
||||
touchAction: 'none',
|
||||
}, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [hasVideo ? (_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "w-full h-full object-cover", style: { imageRendering: 'auto' } })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center bg-[#2b2d31]", children: displayParticipant ? (_jsxs("div", { className: "relative", children: [_jsx(Avatar, { name: displayParticipant.username, size: 64 }), displayParticipant.isSpeaking && (_jsx("div", { className: "absolute -inset-1 rounded-full ring-2 ring-discord-green animate-pulse" }))] })) : (_jsxs("div", { className: "flex items-center gap-2 text-discord-text-muted", children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46Z" }) }), _jsx("span", { className: "text-sm font-medium", children: channelName })] })) })), isScreen && (_jsx("div", { className: "absolute top-2 left-2 px-1.5 py-0.5 bg-discord-red rounded text-[11px] font-bold text-white uppercase tracking-wide", children: "LIVE" })), _jsx("button", { "data-pip-action": "close", onClick: handleClose, className: "absolute top-2 right-2 w-6 h-6 bg-black/60 hover:bg-black/80 rounded-full flex items-center justify-center text-white/80 hover:text-white transition-colors", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" }) }) }), _jsxs("div", { className: "absolute bottom-0 left-0 right-0 px-3 py-2 bg-gradient-to-t from-black/70 to-transparent", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-white text-xs font-semibold truncate", children: displayName }), displayParticipant?.isSpeaking && (_jsx("div", { className: "w-2 h-2 rounded-full bg-discord-green flex-shrink-0 animate-pulse" }))] }), _jsx("div", { className: "text-white/50 text-[10px] truncate", children: channelName })] }), _jsx("div", { className: "absolute bottom-2 right-2 w-5 h-5 flex items-center justify-center text-white/40", children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z" }) }) })] }));
|
||||
}, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [hasVideo ? (_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "w-full h-full object-cover", style: { imageRendering: 'auto' } })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center bg-[#1e1f22]", children: displayParticipant ? (_jsxs("div", { className: "relative", children: [_jsx(Avatar, { name: displayParticipant.username, size: 64 }), displayParticipant.isSpeaking && (_jsx("div", { className: "absolute -inset-1 rounded-full ring-2 ring-discord-green animate-pulse" }))] })) : (_jsxs("div", { className: "flex items-center gap-2 text-discord-text-muted", children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46Z" }) }), _jsx("span", { className: "text-sm font-medium", children: channelName })] })) })), isScreen && (_jsx("div", { className: "absolute top-2 left-2 px-1.5 py-0.5 bg-discord-red rounded text-[11px] font-bold text-white uppercase tracking-wide", children: "LIVE" })), _jsx("button", { "data-pip-action": "close", onClick: handleClose, className: "absolute top-2 right-2 w-6 h-6 bg-black/60 hover:bg-black/80 rounded-full flex items-center justify-center text-white/80 hover:text-white transition-colors", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" }) }) }), _jsxs("div", { className: "absolute bottom-0 left-0 right-0 px-3 py-2 bg-gradient-to-t from-black/70 to-transparent", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-white text-xs font-semibold truncate", children: displayName }), displayParticipant?.isSpeaking && (_jsx("div", { className: "w-2 h-2 rounded-full bg-discord-green flex-shrink-0 animate-pulse" }))] }), _jsx("div", { className: "text-white/50 text-[10px] truncate", children: channelName })] }), _jsx("div", { className: "absolute bottom-2 right-2 w-5 h-5 flex items-center justify-center text-white/40", children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z" }) }) })] }));
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ export function PictureInPicture() {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`fixed z-[40] overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/10 bg-[#111214] select-none ${
|
||||
className={`fixed z-[40] overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/10 bg-[#080a0b] select-none ${
|
||||
isDragging ? 'cursor-grabbing' : 'cursor-grab'
|
||||
}`}
|
||||
style={{
|
||||
@@ -249,7 +249,7 @@ export function PictureInPicture() {
|
||||
style={{ imageRendering: 'auto' }}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center bg-[#2b2d31]">
|
||||
<div className="w-full h-full flex items-center justify-center bg-[#1e1f22]">
|
||||
{displayParticipant ? (
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
|
||||
@@ -40,5 +40,5 @@ export function VideoQualityPopover({ open, onClose, anchorRect }) {
|
||||
setVideoQuality(quality);
|
||||
onClose();
|
||||
};
|
||||
return (_jsxs("div", { ref: popoverRef, className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-[240px] bg-[#2b2d31] rounded-lg shadow-lg border border-[#1e1f22] z-50 overflow-hidden", children: [_jsx("div", { className: "px-3 py-2 border-b border-[#1e1f22]", children: _jsx("span", { className: "text-[14px] font-bold text-discord-text-primary", children: "Video Quality" }) }), _jsx("div", { className: "py-1", children: PRESETS.map((preset) => (_jsxs("button", { onClick: () => handleSelect(preset.value), className: `w-full px-3 py-2 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors ${videoQuality === preset.value ? 'text-discord-text-primary' : 'text-discord-text-secondary'}`, children: [_jsxs("div", { className: "text-left", children: [_jsx("div", { className: "text-[14px] font-medium", children: preset.label }), _jsx("div", { className: "text-[12px] text-discord-text-muted", children: preset.desc })] }), videoQuality === preset.value && (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-blurple flex-shrink-0 ml-2", children: _jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) }))] }, preset.value))) })] }));
|
||||
return (_jsxs("div", { ref: popoverRef, className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-[240px] bg-[#1e1f22] rounded-lg shadow-lg border border-[#111214] z-50 overflow-hidden", children: [_jsx("div", { className: "px-3 py-2 border-b border-[#111214]", children: _jsx("span", { className: "text-[14px] font-bold text-discord-text-primary", children: "Video Quality" }) }), _jsx("div", { className: "py-1", children: PRESETS.map((preset) => (_jsxs("button", { onClick: () => handleSelect(preset.value), className: `w-full px-3 py-2 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors ${videoQuality === preset.value ? 'text-discord-text-primary' : 'text-discord-text-secondary'}`, children: [_jsxs("div", { className: "text-left", children: [_jsx("div", { className: "text-[14px] font-medium", children: preset.label }), _jsx("div", { className: "text-[12px] text-discord-text-muted", children: preset.desc })] }), videoQuality === preset.value && (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-blurple flex-shrink-0 ml-2", children: _jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) }))] }, preset.value))) })] }));
|
||||
}
|
||||
|
||||
@@ -54,9 +54,9 @@ export function VideoQualityPopover({ open, onClose, anchorRect }: VideoQualityP
|
||||
return (
|
||||
<div
|
||||
ref={popoverRef}
|
||||
className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-[240px] bg-[#2b2d31] rounded-lg shadow-lg border border-[#1e1f22] z-50 overflow-hidden"
|
||||
className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-[240px] bg-[#1e1f22] rounded-lg shadow-lg border border-[#111214] z-50 overflow-hidden"
|
||||
>
|
||||
<div className="px-3 py-2 border-b border-[#1e1f22]">
|
||||
<div className="px-3 py-2 border-b border-[#111214]">
|
||||
<span className="text-[14px] font-bold text-discord-text-primary">Video Quality</span>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
|
||||
@@ -6,15 +6,21 @@ import { Avatar } from '../ui/Avatar';
|
||||
export function VoiceChannel({ channelId, channelName, onClick }) {
|
||||
const voiceUsers = useVoiceStore((s) => s.voiceUsers.get(channelId)) ?? EMPTY_VOICE_USERS;
|
||||
const currentVoiceChannel = useVoiceStore((s) => s.currentVoiceChannelId);
|
||||
const participants = useVoiceStore((s) => s.participants);
|
||||
const members = useServerStore((s) => s.members);
|
||||
const isActive = currentVoiceChannel === channelId;
|
||||
return (_jsxs("div", { children: [_jsxs("button", { onClick: onClick, className: `w-full flex items-center gap-1.5 px-2 py-1.5 rounded text-sm group ${isActive
|
||||
? 'bg-discord-bg-active text-white'
|
||||
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'}`, children: [_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: "flex-shrink-0 opacity-60", children: _jsx("path", { d: "M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46ZM19.07 4.93C20.91 6.77 22 9.28 22 12C22 14.72 20.91 17.23 19.07 19.07L17.66 17.66C19.11 16.21 20 14.21 20 12C20 9.79 19.11 7.79 17.66 6.34L19.07 4.93Z" }) }), _jsx("span", { className: "truncate", children: channelName })] }), voiceUsers.length > 0 && (_jsx("div", { className: "ml-6 mt-0.5 space-y-0.5", children: voiceUsers.map((userId) => {
|
||||
return (_jsxs("div", { children: [_jsxs("button", { onClick: onClick, className: `w-full flex items-center gap-1.5 px-2 h-8 rounded-[4px] group transition-colors ${isActive
|
||||
? 'bg-discord-modifier-selected text-white'
|
||||
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-modifier-hover'}`, children: [_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: "flex-shrink-0 opacity-60", children: _jsx("path", { d: "M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46ZM19.07 4.93C20.91 6.77 22 9.28 22 12C22 14.72 20.91 17.23 19.07 19.07L17.66 17.66C19.11 16.21 20 14.21 20 12C20 9.79 19.11 7.79 17.66 6.34L19.07 4.93Z" }) }), _jsx("span", { className: "truncate text-[15px] font-medium", children: channelName })] }), voiceUsers.length > 0 && (_jsx("div", { className: "ml-6 mt-0.5 space-y-0.5", children: voiceUsers.map((userId) => {
|
||||
const member = members.find(m => m.userId === userId);
|
||||
if (!member)
|
||||
return null;
|
||||
const displayName = member.user.displayName ?? member.user.username;
|
||||
return (_jsxs("div", { className: "flex items-center gap-2 px-2 py-0.5 rounded hover:bg-discord-bg-hover transition-colors", children: [_jsx(Avatar, { src: member.user.avatar, name: displayName, size: 20, status: member.user.status }), _jsx("span", { className: "text-xs text-discord-text-secondary truncate", children: displayName })] }, userId));
|
||||
// Find participant for status badges
|
||||
const participant = participants.find(p => p.identity === userId || p.username === member.user.username);
|
||||
const isMuted = participant ? !participant.audioTrack : false;
|
||||
const hasCamera = participant ? participant.videoTrack !== null : false;
|
||||
const isScreenSharing = participant ? participant.screenTrack !== null : false;
|
||||
return (_jsxs("div", { className: "flex items-center gap-2 px-2 py-0.5 rounded hover:bg-discord-modifier-hover transition-colors", children: [_jsx(Avatar, { src: member.user.avatar, name: displayName, size: 20, status: member.user.status }), _jsx("span", { className: "text-[13px] text-discord-text-secondary truncate flex-1 min-w-0", children: displayName }), _jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [isMuted && (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-red", children: [_jsx("path", { d: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" }), _jsx("path", { d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" }), _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] })), hasCamera && (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-text-muted", children: _jsx("path", { d: "M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z" }) })), isScreenSharing && (_jsx("span", { className: "bg-discord-green text-white text-[9px] font-bold px-1 rounded leading-[14px]", children: "LIVE" }))] })] }, userId));
|
||||
}) }))] }));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ interface VoiceChannelProps {
|
||||
export function VoiceChannel({ channelId, channelName, onClick }: VoiceChannelProps) {
|
||||
const voiceUsers = useVoiceStore((s) => s.voiceUsers.get(channelId)) ?? EMPTY_VOICE_USERS;
|
||||
const currentVoiceChannel = useVoiceStore((s) => s.currentVoiceChannelId);
|
||||
const participants = useVoiceStore((s) => s.participants);
|
||||
const members = useServerStore((s) => s.members);
|
||||
const isActive = currentVoiceChannel === channelId;
|
||||
|
||||
@@ -21,16 +22,16 @@ export function VoiceChannel({ channelId, channelName, onClick }: VoiceChannelPr
|
||||
<div>
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`w-full flex items-center gap-1.5 px-2 py-1.5 rounded text-sm group ${
|
||||
className={`w-full flex items-center gap-1.5 px-2 h-8 rounded-[4px] group transition-colors ${
|
||||
isActive
|
||||
? 'bg-discord-bg-active text-white'
|
||||
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
|
||||
? 'bg-discord-modifier-selected text-white'
|
||||
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-modifier-hover'
|
||||
}`}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 opacity-60">
|
||||
<path d="M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46ZM19.07 4.93C20.91 6.77 22 9.28 22 12C22 14.72 20.91 17.23 19.07 19.07L17.66 17.66C19.11 16.21 20 14.21 20 12C20 9.79 19.11 7.79 17.66 6.34L19.07 4.93Z" />
|
||||
</svg>
|
||||
<span className="truncate">{channelName}</span>
|
||||
<span className="truncate text-[15px] font-medium">{channelName}</span>
|
||||
</button>
|
||||
|
||||
{/* Connected users */}
|
||||
@@ -40,15 +41,40 @@ export function VoiceChannel({ channelId, channelName, onClick }: VoiceChannelPr
|
||||
const member = members.find(m => m.userId === userId);
|
||||
if (!member) return null;
|
||||
const displayName = member.user.displayName ?? member.user.username;
|
||||
|
||||
// Find participant for status badges
|
||||
const participant = participants.find(p => p.identity === userId || p.username === member.user.username);
|
||||
const isMuted = participant ? !participant.audioTrack : false;
|
||||
const hasCamera = participant ? participant.videoTrack !== null : false;
|
||||
const isScreenSharing = participant ? participant.screenTrack !== null : false;
|
||||
|
||||
return (
|
||||
<div key={userId} className="flex items-center gap-2 px-2 py-0.5 rounded hover:bg-discord-bg-hover transition-colors">
|
||||
<div key={userId} className="flex items-center gap-2 px-2 py-0.5 rounded hover:bg-discord-modifier-hover transition-colors">
|
||||
<Avatar
|
||||
src={member.user.avatar}
|
||||
name={displayName}
|
||||
size={20}
|
||||
status={member.user.status}
|
||||
/>
|
||||
<span className="text-xs text-discord-text-secondary truncate">{displayName}</span>
|
||||
<span className="text-[13px] text-discord-text-secondary truncate flex-1 min-w-0">{displayName}</span>
|
||||
{/* Status badges */}
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
{isMuted && (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" className="text-discord-red">
|
||||
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" />
|
||||
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" />
|
||||
<line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
)}
|
||||
{hasCamera && (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
|
||||
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z" />
|
||||
</svg>
|
||||
)}
|
||||
{isScreenSharing && (
|
||||
<span className="bg-discord-green text-white text-[9px] font-bold px-1 rounded leading-[14px]">LIVE</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -14,6 +14,10 @@ const QUALITY_MAP = {
|
||||
'540p': new VideoPreset(960, 540, 2_000_000, 30),
|
||||
'360p': new VideoPreset(640, 360, 1_000_000, 30),
|
||||
};
|
||||
const btnBase = 'w-10 h-10 flex items-center justify-center rounded-full transition-colors';
|
||||
const btnDefault = `${btnBase} bg-[#1e1f22] text-discord-text-secondary hover:bg-[#2b2d31] hover:text-discord-text-primary`;
|
||||
const btnActive = (color) => `${btnBase} bg-${color}/20 text-${color} hover:bg-${color}/30`;
|
||||
const btnGreen = `${btnBase} bg-[#1e1f22] text-discord-green hover:bg-[#2b2d31]`;
|
||||
export function VoiceControlBar() {
|
||||
const isMuted = useVoiceStore((s) => s.isMuted);
|
||||
const isDeafened = useVoiceStore((s) => s.isDeafened);
|
||||
@@ -146,19 +150,15 @@ export function VoiceControlBar() {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleMute, handleDeafen, voiceFullscreen]);
|
||||
return (_jsxs("div", { className: "h-[72px] bg-[#1a1b1e] flex items-center justify-center gap-2 px-4 flex-shrink-0", children: [_jsx("button", { onClick: handleMute, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${isMuted || isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: isMuted ? 'Unmute (M)' : 'Mute (M)', children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" }), _jsx("path", { d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" }), (isMuted || isDeafened) && _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), _jsx("button", { onClick: handleDeafen, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: isDeafened ? 'Undeafen (D)' : 'Deafen (D)', children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" }), isDeafened && _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), _jsx("button", { onClick: handleCamera, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${isCameraOn
|
||||
? 'bg-[#2b2d31] text-discord-green hover:bg-[#36373d]'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: isCameraOn ? 'Turn Off Camera' : 'Turn On Camera', children: isCameraOn ? (_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" }) })) : (_jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" }), _jsx("line", { x1: "2", y1: "2", x2: "22", y2: "22", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] })) }), _jsx("button", { onClick: handleScreenShare, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${isScreenSharing
|
||||
? 'bg-[#2b2d31] text-discord-green hover:bg-[#36373d]'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: isScreenSharing ? 'Stop Sharing' : 'Share Screen', children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20ZM4 6H20V16H4V6Z" }), _jsx("path", { d: "M15 11L11 14V12H9V10H11V8L15 11Z" })] }) }), _jsxs("div", { className: "relative", children: [_jsx("button", { onClick: () => setQualityOpen(!qualityOpen), className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${qualityOpen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: "Video Quality", children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" }) }) }), _jsx(VideoQualityPopover, { open: qualityOpen, onClose: () => setQualityOpen(false) })] }), _jsx("div", { className: "w-[1px] h-8 bg-[#3f4147] mx-1" }), _jsx("button", { onClick: toggleVoiceChat, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${voiceChatOpen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: "Toggle Chat", children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" }) }) }), _jsx("button", { onClick: handleFullscreen, className: `w-12 h-12 flex items-center justify-center rounded-full transition-colors ${voiceFullscreen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'}`, title: voiceFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen', children: voiceFullscreen ? (_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) })) : (_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })) }), _jsx("div", { className: "w-[1px] h-8 bg-[#3f4147] mx-1" }), _jsx("button", { onClick: handleDisconnect, className: "w-12 h-12 flex items-center justify-center rounded-full bg-discord-red hover:bg-discord-red-hover transition-colors text-white", title: "Disconnect", children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" }) }) })] }));
|
||||
return (_jsx("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-20 opacity-0 translate-y-4 group-hover/voice:opacity-100 group-hover/voice:translate-y-0 transition-all duration-300 ease-out", children: _jsxs("div", { className: "flex items-center gap-1.5 rounded-full px-3 py-2 bg-[#111214]/90 backdrop-blur-md ring-1 ring-white/[0.06] shadow-[0_8px_32px_rgba(0,0,0,0.5)]", children: [_jsx("button", { onClick: handleMute, className: isMuted || isDeafened
|
||||
? `${btnBase} bg-discord-red/20 text-discord-red hover:bg-discord-red/30`
|
||||
: btnDefault, title: isMuted ? 'Unmute (M)' : 'Mute (M)', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" }), _jsx("path", { d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" }), (isMuted || isDeafened) && _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), _jsx("button", { onClick: handleDeafen, className: isDeafened
|
||||
? `${btnBase} bg-discord-red/20 text-discord-red hover:bg-discord-red/30`
|
||||
: btnDefault, title: isDeafened ? 'Undeafen (D)' : 'Deafen (D)', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" }), isDeafened && _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), _jsx("button", { onClick: handleCamera, className: isCameraOn ? btnGreen : btnDefault, title: isCameraOn ? 'Turn Off Camera' : 'Turn On Camera', children: isCameraOn ? (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" }) })) : (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" }), _jsx("line", { x1: "2", y1: "2", x2: "22", y2: "22", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] })) }), _jsx("button", { onClick: handleScreenShare, className: isScreenSharing ? btnGreen : btnDefault, title: isScreenSharing ? 'Stop Sharing' : 'Share Screen', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20ZM4 6H20V16H4V6Z" }), _jsx("path", { d: "M15 11L11 14V12H9V10H11V8L15 11Z" })] }) }), _jsxs("div", { className: "relative", children: [_jsx("button", { onClick: () => setQualityOpen(!qualityOpen), className: qualityOpen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault, title: "Video Quality", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" }) }) }), _jsx(VideoQualityPopover, { open: qualityOpen, onClose: () => setQualityOpen(false) })] }), _jsx("div", { className: "w-[1px] h-6 bg-white/10 mx-0.5" }), _jsx("button", { onClick: toggleVoiceChat, className: voiceChatOpen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault, title: "Toggle Chat", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" }) }) }), _jsx("button", { onClick: handleFullscreen, className: voiceFullscreen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault, title: voiceFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen', children: voiceFullscreen ? (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) })) : (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })) }), _jsx("div", { className: "w-[1px] h-6 bg-white/10 mx-0.5" }), _jsx("button", { onClick: handleDisconnect, className: `${btnBase} bg-discord-red hover:bg-discord-red-hover text-white`, title: "Disconnect", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" }) }) })] }) }));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ const QUALITY_MAP: Record<string, any> = {
|
||||
'360p': new VideoPreset(640, 360, 1_000_000, 30),
|
||||
};
|
||||
|
||||
const btnBase = 'w-10 h-10 flex items-center justify-center rounded-full transition-colors';
|
||||
const btnDefault = `${btnBase} bg-[#1e1f22] text-discord-text-secondary hover:bg-[#2b2d31] hover:text-discord-text-primary`;
|
||||
const btnActive = (color: string) => `${btnBase} bg-${color}/20 text-${color} hover:bg-${color}/30`;
|
||||
const btnGreen = `${btnBase} bg-[#1e1f22] text-discord-green hover:bg-[#2b2d31]`;
|
||||
|
||||
export function VoiceControlBar() {
|
||||
const isMuted = useVoiceStore((s) => s.isMuted);
|
||||
const isDeafened = useVoiceStore((s) => s.isDeafened);
|
||||
@@ -73,9 +78,9 @@ export function VoiceControlBar() {
|
||||
const videoQuality = useVoiceStore.getState().videoQuality;
|
||||
const preset = QUALITY_MAP[videoQuality];
|
||||
if (preset) {
|
||||
await room.localParticipant.setCameraEnabled(true,
|
||||
await room.localParticipant.setCameraEnabled(true,
|
||||
{ resolution: preset.resolution },
|
||||
{
|
||||
{
|
||||
videoEncoding: preset.encoding,
|
||||
simulcast: videoQuality === '1080p' || videoQuality === '720p'
|
||||
}
|
||||
@@ -144,148 +149,137 @@ export function VoiceControlBar() {
|
||||
}, [handleMute, handleDeafen, voiceFullscreen]);
|
||||
|
||||
return (
|
||||
<div className="h-[72px] bg-[#1a1b1e] flex items-center justify-center gap-2 px-4 flex-shrink-0">
|
||||
{/* Mute */}
|
||||
<button
|
||||
onClick={handleMute}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
isMuted || isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title={isMuted ? 'Unmute (M)' : 'Mute (M)'}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" />
|
||||
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" />
|
||||
{(isMuted || isDeafened) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Deafen */}
|
||||
<button
|
||||
onClick={handleDeafen}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title={isDeafened ? 'Undeafen (D)' : 'Deafen (D)'}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" />
|
||||
{isDeafened && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Camera */}
|
||||
<button
|
||||
onClick={handleCamera}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
isCameraOn
|
||||
? 'bg-[#2b2d31] text-discord-green hover:bg-[#36373d]'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'}
|
||||
>
|
||||
{isCameraOn ? (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" />
|
||||
<line x1="2" y1="2" x2="22" y2="22" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Screen Share */}
|
||||
<button
|
||||
onClick={handleScreenShare}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
isScreenSharing
|
||||
? 'bg-[#2b2d31] text-discord-green hover:bg-[#36373d]'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20ZM4 6H20V16H4V6Z" />
|
||||
<path d="M15 11L11 14V12H9V10H11V8L15 11Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Video Quality */}
|
||||
<div className="relative">
|
||||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 opacity-0 translate-y-4 group-hover/voice:opacity-100 group-hover/voice:translate-y-0 transition-all duration-300 ease-out">
|
||||
<div className="flex items-center gap-1.5 rounded-full px-3 py-2 bg-[#111214]/90 backdrop-blur-md ring-1 ring-white/[0.06] shadow-[0_8px_32px_rgba(0,0,0,0.5)]">
|
||||
{/* Mute */}
|
||||
<button
|
||||
onClick={() => setQualityOpen(!qualityOpen)}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
qualityOpen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title="Video Quality"
|
||||
onClick={handleMute}
|
||||
className={isMuted || isDeafened
|
||||
? `${btnBase} bg-discord-red/20 text-discord-red hover:bg-discord-red/30`
|
||||
: btnDefault
|
||||
}
|
||||
title={isMuted ? 'Unmute (M)' : 'Mute (M)'}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" />
|
||||
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" />
|
||||
{(isMuted || isDeafened) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Deafen */}
|
||||
<button
|
||||
onClick={handleDeafen}
|
||||
className={isDeafened
|
||||
? `${btnBase} bg-discord-red/20 text-discord-red hover:bg-discord-red/30`
|
||||
: btnDefault
|
||||
}
|
||||
title={isDeafened ? 'Undeafen (D)' : 'Deafen (D)'}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" />
|
||||
{isDeafened && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Camera */}
|
||||
<button
|
||||
onClick={handleCamera}
|
||||
className={isCameraOn ? btnGreen : btnDefault}
|
||||
title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'}
|
||||
>
|
||||
{isCameraOn ? (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 10.5V7C17 6.45 16.55 6 16 6H4C3.45 6 3 6.45 3 7V17C3 17.55 3.45 18 4 18H16C16.55 18 17 17.55 17 17V13.5L21 17.5V6.5L17 10.5Z" />
|
||||
<line x1="2" y1="2" x2="22" y2="22" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Screen Share */}
|
||||
<button
|
||||
onClick={handleScreenShare}
|
||||
className={isScreenSharing ? btnGreen : btnDefault}
|
||||
title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20ZM4 6H20V16H4V6Z" />
|
||||
<path d="M15 11L11 14V12H9V10H11V8L15 11Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Video Quality */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setQualityOpen(!qualityOpen)}
|
||||
className={qualityOpen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault
|
||||
}
|
||||
title="Video Quality"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
|
||||
</svg>
|
||||
</button>
|
||||
<VideoQualityPopover open={qualityOpen} onClose={() => setQualityOpen(false)} />
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-[1px] h-6 bg-white/10 mx-0.5" />
|
||||
|
||||
{/* Chat Toggle */}
|
||||
<button
|
||||
onClick={toggleVoiceChat}
|
||||
className={voiceChatOpen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault
|
||||
}
|
||||
title="Toggle Chat"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Fullscreen Toggle */}
|
||||
<button
|
||||
onClick={handleFullscreen}
|
||||
className={voiceFullscreen
|
||||
? `${btnBase} bg-[#1e1f22] text-discord-text-primary`
|
||||
: btnDefault
|
||||
}
|
||||
title={voiceFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen'}
|
||||
>
|
||||
{voiceFullscreen ? (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-[1px] h-6 bg-white/10 mx-0.5" />
|
||||
|
||||
{/* Disconnect */}
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
className={`${btnBase} bg-discord-red hover:bg-discord-red-hover text-white`}
|
||||
title="Disconnect"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<VideoQualityPopover open={qualityOpen} onClose={() => setQualityOpen(false)} />
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-[1px] h-8 bg-[#3f4147] mx-1" />
|
||||
|
||||
{/* Chat Toggle */}
|
||||
<button
|
||||
onClick={toggleVoiceChat}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
voiceChatOpen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title="Toggle Chat"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Fullscreen Toggle */}
|
||||
<button
|
||||
onClick={handleFullscreen}
|
||||
className={`w-12 h-12 flex items-center justify-center rounded-full transition-colors ${
|
||||
voiceFullscreen
|
||||
? 'bg-[#2b2d31] text-discord-text-primary'
|
||||
: 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#36373d] hover:text-discord-text-primary'
|
||||
}`}
|
||||
title={voiceFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen'}
|
||||
>
|
||||
{voiceFullscreen ? (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-[1px] h-8 bg-[#3f4147] mx-1" />
|
||||
|
||||
{/* Disconnect */}
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
className="w-12 h-12 flex items-center justify-center rounded-full bg-discord-red hover:bg-discord-red-hover transition-colors text-white"
|
||||
title="Disconnect"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export function VoiceControls() {
|
||||
: isLiveKitConnected
|
||||
? 'bg-discord-green/20'
|
||||
: 'bg-discord-yellow/20';
|
||||
return (_jsxs("div", { className: "bg-[#232428] border-t border-discord-bg-tertiary", children: [_jsxs("div", { className: "flex items-center gap-2 px-2 pt-[10px] pb-1", children: [_jsx("div", { className: `w-8 h-8 rounded-lg ${statusBgColor} flex items-center justify-center flex-shrink-0`, children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: statusColor, children: _jsx("path", { d: "M1.5 21.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM3.14 15.75a.75.75 0 01-.09-1.06A8.46 8.46 0 0112 11a8.46 8.46 0 018.95 3.69.75.75 0 01-1.15.97A6.96 6.96 0 0012 12.5a6.96 6.96 0 00-7.8 3.16.75.75 0 01-1.06.09zM6.37 18.3a.75.75 0 01-.08-1.06A5.46 5.46 0 0112 15a5.46 5.46 0 015.71 2.24.75.75 0 01-1.14.97A3.96 3.96 0 0012 16.5a3.96 3.96 0 00-4.57 1.71.75.75 0 01-1.06.09z" }) }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `text-[13px] font-semibold leading-[18px] ${statusColor}`, children: connectionError ? 'Connection Failed' : isLiveKitConnected ? 'Voice Connected' : 'Connecting...' }), _jsx("div", { className: "text-[12px] text-discord-channels-default truncate leading-[16px]", children: connectionError ? connectionError : channelName })] }), _jsxs("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [_jsx("button", { className: "w-7 h-7 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded", title: "Connection Info", children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M2 20h2V8H2v12zm5 0h2V4H7v16zm5 0h2v-8h-2v8zm5 0h2V12h-2v8z" }) }) }), _jsx("button", { onClick: handleDisconnect, className: "w-7 h-7 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded", title: "Disconnect", children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" }) }) })] })] }), _jsxs("div", { className: "flex items-center gap-1 px-2 pb-[10px] pt-1", children: [_jsx("button", { onClick: handleMute, className: `flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors ${isMuted || isDeafened
|
||||
return (_jsxs("div", { className: "bg-[#1a1b1e] border-t border-discord-bg-tertiary", children: [_jsxs("div", { className: "flex items-center gap-2 px-2 pt-[10px] pb-1", children: [_jsx("div", { className: `w-8 h-8 rounded-lg ${statusBgColor} flex items-center justify-center flex-shrink-0`, children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", className: statusColor, children: _jsx("path", { d: "M1.5 21.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM3.14 15.75a.75.75 0 01-.09-1.06A8.46 8.46 0 0112 11a8.46 8.46 0 018.95 3.69.75.75 0 01-1.15.97A6.96 6.96 0 0012 12.5a6.96 6.96 0 00-7.8 3.16.75.75 0 01-1.06.09zM6.37 18.3a.75.75 0 01-.08-1.06A5.46 5.46 0 0112 15a5.46 5.46 0 015.71 2.24.75.75 0 01-1.14.97A3.96 3.96 0 0012 16.5a3.96 3.96 0 00-4.57 1.71.75.75 0 01-1.06.09z" }) }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `text-[13px] font-semibold leading-[18px] ${statusColor}`, children: connectionError ? 'Connection Failed' : isLiveKitConnected ? 'Voice Connected' : 'Connecting...' }), _jsx("div", { className: "text-[12px] text-discord-channels-default truncate leading-[16px]", children: connectionError ? connectionError : channelName })] }), _jsxs("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [_jsx("button", { className: "w-7 h-7 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded", title: "Connection Info", children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M2 20h2V8H2v12zm5 0h2V4H7v16zm5 0h2v-8h-2v8zm5 0h2V12h-2v8z" }) }) }), _jsx("button", { onClick: handleDisconnect, className: "w-7 h-7 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded", title: "Disconnect", children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 9C10.4 9 8.85 9.25 7.4 9.72V12.82C7.4 13.22 7.17 13.56 6.84 13.72C5.86 14.21 4.97 14.84 4.18 15.57C4 15.75 3.75 15.85 3.48 15.85C3.2 15.85 2.95 15.74 2.77 15.56L0.29 13.08C0.11 12.9 0 12.65 0 12.38C0 12.1 0.11 11.85 0.29 11.67C3.34 8.78 7.46 7 12 7S20.66 8.78 23.71 11.67C23.89 11.85 24 12.1 24 12.38C24 12.65 23.89 12.9 23.71 13.08L21.23 15.56C21.05 15.74 20.8 15.85 20.52 15.85C20.25 15.85 20 15.75 19.82 15.57C19.03 14.84 18.14 14.21 17.16 13.72C16.83 13.56 16.6 13.22 16.6 12.82V9.72C15.15 9.25 13.6 9 12 9Z" }) }) })] })] }), _jsxs("div", { className: "flex items-center gap-1 px-2 pb-[10px] pt-1", children: [_jsx("button", { onClick: handleMute, className: `flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors ${isMuted || isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
: 'bg-discord-bg-tertiary text-discord-text-muted hover:bg-discord-bg-tertiary/80 hover:text-discord-text-secondary'}`, title: isMuted ? 'Unmute' : 'Mute', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" }), _jsx("path", { d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" }), (isMuted || isDeafened) && _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), _jsx("button", { onClick: handleDeafen, className: `flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors ${isDeafened
|
||||
? 'bg-discord-red/20 text-discord-red hover:bg-discord-red/30'
|
||||
|
||||
@@ -104,7 +104,7 @@ export function VoiceControls() {
|
||||
: 'bg-discord-yellow/20';
|
||||
|
||||
return (
|
||||
<div className="bg-[#232428] border-t border-discord-bg-tertiary">
|
||||
<div className="bg-[#1a1b1e] border-t border-discord-bg-tertiary">
|
||||
{/* Row 1: Signal icon + status text + disconnect */}
|
||||
<div className="flex items-center gap-2 px-2 pt-[10px] pb-1">
|
||||
<div className={`w-8 h-8 rounded-lg ${statusBgColor} flex items-center justify-center flex-shrink-0`}>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function VoiceUser({ participant, large }) {
|
||||
window.addEventListener('click', close);
|
||||
return () => window.removeEventListener('click', close);
|
||||
}, [volumeMenu]);
|
||||
return (_jsxs("div", { className: `relative bg-[#1e1f22] rounded-xl overflow-hidden flex items-center justify-center group transition-all duration-200 ${participant.isSpeaking
|
||||
return (_jsxs("div", { className: `relative bg-[#111214] rounded-xl overflow-hidden flex items-center justify-center group transition-all duration-200 ${participant.isSpeaking
|
||||
? 'ring-[3px] ring-discord-green shadow-[0_0_12px_rgba(35,165,90,0.25)]'
|
||||
: 'ring-1 ring-white/[0.06] hover:ring-white/10'} ${large ? 'h-full' : ''}`, style: large ? undefined : { aspectRatio: '16/9', minHeight: '140px' }, onContextMenu: handleContextMenu, children: [!isLocal && _jsx("audio", { ref: audioRef, autoPlay: true }), hasVideo ? (_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: isLocal, className: `w-full h-full ${large || isScreenShare ? 'object-contain bg-black' : 'object-cover'}` })) : (_jsx("div", { className: "w-full h-full flex flex-col items-center justify-center gap-3 bg-[#2b2d31]", children: _jsxs("div", { className: "relative", children: [_jsx(Avatar, { src: null, name: participant.username, size: large ? 100 : 64 }), participant.isSpeaking && (_jsx("div", { className: "absolute -inset-1.5 rounded-full ring-[3px] ring-discord-green animate-pulse" }))] }) })), isScreenShare && hasVideo && (_jsx("div", { className: "absolute top-2 left-2 px-1.5 py-0.5 bg-discord-red rounded text-[11px] font-bold text-white uppercase tracking-wide", children: "LIVE" })), _jsx("div", { className: "absolute bottom-0 left-0 right-0 px-3 py-2 bg-gradient-to-t from-black/70 via-black/30 to-transparent", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [_jsx("span", { className: `font-semibold text-white truncate ${large ? 'text-base' : 'text-[13px]'}`, children: participant.username }), isLocal && (_jsx("span", { className: "text-[10px] text-white/40 font-medium", children: "(you)" }))] }), _jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [participant.isMuted && (_jsx("div", { className: "w-5 h-5 bg-discord-red/90 rounded-full flex items-center justify-center", children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "white", children: [_jsx("path", { d: "M12 2C10.9 2 10 2.9 10 4V12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12V4C14 2.9 13.1 2 12 2Z" }), _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "white", strokeWidth: "2" })] }) })), participant.isScreenSharing && !isScreenShare && (_jsx("div", { className: "w-5 h-5 bg-discord-blurple/90 rounded-full flex items-center justify-center", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "white", children: _jsx("path", { d: "M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20Z" }) }) }))] })] }) }), volumeMenu && !isLocal && (_jsxs("div", { className: "fixed z-[60] bg-[#111214] rounded-lg shadow-2xl p-3 min-w-[200px] border border-white/[0.06]", style: { left: volumeMenu.x, top: volumeMenu.y }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { className: "text-xs text-discord-text-muted mb-2 font-medium uppercase tracking-wider", children: "User Volume" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-text-muted flex-shrink-0", children: _jsx("path", { d: "M3 9v6h4l5 5V4L7 9H3z" }) }), _jsx("input", { type: "range", min: "0", max: "200", value: perUserVolume, onChange: (e) => setParticipantVolume(participant.userId, parseInt(e.target.value)), className: "flex-1 accent-discord-blurple h-1" }), _jsxs("span", { className: "text-xs text-discord-text-secondary min-w-[32px] text-right", children: [perUserVolume, "%"] })] })] }))] }));
|
||||
: 'ring-1 ring-white/[0.06] hover:ring-white/10'} ${large ? 'h-full' : ''}`, style: large ? undefined : { aspectRatio: '16/9', minHeight: '140px' }, onContextMenu: handleContextMenu, children: [!isLocal && _jsx("audio", { ref: audioRef, autoPlay: true }), hasVideo ? (_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: isLocal, className: `w-full h-full ${large || isScreenShare ? 'object-contain bg-black' : 'object-cover'}` })) : (_jsx("div", { className: "w-full h-full flex flex-col items-center justify-center gap-3 bg-[#1e1f22]", children: _jsxs("div", { className: "relative", children: [_jsx(Avatar, { src: null, name: participant.username, size: large ? 100 : 64 }), participant.isSpeaking && (_jsx("div", { className: "absolute -inset-1.5 rounded-full ring-[3px] ring-discord-green animate-pulse" }))] }) })), isScreenShare && hasVideo && (_jsx("div", { className: "absolute top-2 left-2 px-1.5 py-0.5 bg-discord-red rounded text-[11px] font-bold text-white uppercase tracking-wide", children: "LIVE" })), _jsx("div", { className: "absolute bottom-0 left-0 right-0 px-3 py-2 bg-gradient-to-t from-black/70 via-black/30 to-transparent", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [_jsx("span", { className: `font-semibold text-white truncate ${large ? 'text-base' : 'text-[13px]'}`, children: participant.username }), isLocal && (_jsx("span", { className: "text-[10px] text-white/40 font-medium", children: "(you)" }))] }), _jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [participant.isMuted && (_jsx("div", { className: "w-5 h-5 bg-discord-red/90 rounded-full flex items-center justify-center", children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "white", children: [_jsx("path", { d: "M12 2C10.9 2 10 2.9 10 4V12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12V4C14 2.9 13.1 2 12 2Z" }), _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "white", strokeWidth: "2" })] }) })), participant.isScreenSharing && !isScreenShare && (_jsx("div", { className: "w-5 h-5 bg-discord-blurple/90 rounded-full flex items-center justify-center", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "white", children: _jsx("path", { d: "M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20Z" }) }) }))] })] }) }), volumeMenu && !isLocal && (_jsxs("div", { className: "fixed z-[60] bg-[#111214] rounded-lg shadow-2xl p-3 min-w-[200px] border border-white/[0.06]", style: { left: volumeMenu.x, top: volumeMenu.y }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { className: "text-xs text-discord-text-muted mb-2 font-medium uppercase tracking-wider", children: "User Volume" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", className: "text-discord-text-muted flex-shrink-0", children: _jsx("path", { d: "M3 9v6h4l5 5V4L7 9H3z" }) }), _jsx("input", { type: "range", min: "0", max: "200", value: perUserVolume, onChange: (e) => setParticipantVolume(participant.userId, parseInt(e.target.value)), className: "flex-1 accent-discord-blurple h-1" }), _jsxs("span", { className: "text-xs text-discord-text-secondary min-w-[32px] text-right", children: [perUserVolume, "%"] })] })] }))] }));
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export function VoiceUser({ participant, large }: VoiceUserProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative bg-[#1e1f22] rounded-xl overflow-hidden flex items-center justify-center group transition-all duration-200 ${
|
||||
className={`relative bg-[#111214] rounded-xl overflow-hidden flex items-center justify-center group transition-all duration-200 ${
|
||||
participant.isSpeaking
|
||||
? 'ring-[3px] ring-discord-green shadow-[0_0_12px_rgba(35,165,90,0.25)]'
|
||||
: 'ring-1 ring-white/[0.06] hover:ring-white/10'
|
||||
@@ -111,7 +111,7 @@ export function VoiceUser({ participant, large }: VoiceUserProps) {
|
||||
className={`w-full h-full ${large || isScreenShare ? 'object-contain bg-black' : 'object-cover'}`}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-3 bg-[#2b2d31]">
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-3 bg-[#1e1f22]">
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
src={null}
|
||||
|
||||
Reference in New Issue
Block a user