From 503e483b8211affac63e7ee890f499e654336be6 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:10:31 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Unified=20floating=20bottom=20panel=20?= =?UTF-8?q?=E2=80=94=20voice=20controls=20+=20user=20area=20in=20one=20car?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure VoiceControls and UserAreaPanel into a single fixed-positioned floating card that spans both server and channel sidebars. Panel expands when voice is connected and contracts to just user area when not. --- .../src/components/layout/ChannelSidebar.tsx | 61 +++++++++---------- .../src/components/voice/VoiceControls.tsx | 45 +++++++------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/packages/web/src/components/layout/ChannelSidebar.tsx b/packages/web/src/components/layout/ChannelSidebar.tsx index 1d8ba612..172bbc21 100644 --- a/packages/web/src/components/layout/ChannelSidebar.tsx +++ b/packages/web/src/components/layout/ChannelSidebar.tsx @@ -74,15 +74,37 @@ export function ChannelSidebar() { navigate(`/channels/${currentServerId}/${channelId}`); }; + // Floating bottom panel — shared between DM view and server view + const floatingPanel = user ? ( +
+
+ {/* Voice controls (expands when connected) */} + {currentVoiceChannelId && } + {/* Separator between voice and user area */} + {currentVoiceChannelId &&
} + {/* User area (always visible) */} + openModal('userSettings')} + /> +
+
+ ) : null; + if (!server) { return ( + <>
-
+
- {/* Voice controls — visible when in a call, even in DM view */} - {currentVoiceChannelId && } - - {/* User area at bottom */} - {user && ( - openModal('userSettings')} - /> - )}
+ {floatingPanel} + ); } return ( + <>
{/* Server header */}
@@ -230,7 +241,7 @@ export function ChannelSidebar() {
{/* Channels */} -
+
{/* Text Channels */}
@@ -321,21 +332,9 @@ export function ChannelSidebar() {
- {/* Voice controls — VoiceControls reads state and calls LiveKit SDK directly */} - {currentVoiceChannelId && } - - {/* User area */} - {user && ( - openModal('userSettings')} - /> - )}
+ {floatingPanel} + ); } @@ -650,7 +649,7 @@ function UserAreaPanel({ )} {/* User area bar */} -
+
{/* Avatar + name */}
diff --git a/packages/web/src/components/voice/VoiceControls.tsx b/packages/web/src/components/voice/VoiceControls.tsx index cfeb1f03..64052b7b 100644 --- a/packages/web/src/components/voice/VoiceControls.tsx +++ b/packages/web/src/components/voice/VoiceControls.tsx @@ -4,6 +4,10 @@ import { useServerStore } from '../../stores/serverStore'; import { getActiveRoom } from '../../hooks/useLiveKit'; import { wsSend } from '../../hooks/useWebSocket'; +/** + * VoiceControls renders the voice status + button rows. + * It has NO wrapper/card styling — the parent provides the container. + */ export function VoiceControls() { const currentVoiceChannelId = useVoiceStore((s) => s.currentVoiceChannelId); const isMuted = useVoiceStore((s) => s.isMuted); @@ -41,21 +45,19 @@ export function VoiceControls() { try { const willDeafen = !isDeafened; if (willDeafen) { - // Deafen: mute mic + mute all remote audio await room.localParticipant.setMicrophoneEnabled(false); room.remoteParticipants.forEach((p) => { p.setVolume(0); }); - if (!isMuted) toggleMic(); // Also mute mic when deafening + if (!isMuted) toggleMic(); } else { - // Undeafen: restore remote audio, unmute mic const outputVolume = useVoiceStore.getState().outputVolume; const scaled = outputVolume / 100; room.remoteParticipants.forEach((p) => { p.setVolume(scaled); }); await room.localParticipant.setMicrophoneEnabled(true); - if (isMuted) toggleMic(); // Unmute mic when undeafening + if (isMuted) toggleMic(); } } catch (err) { console.error('[VoiceControls] Failed to toggle deafen:', err); @@ -103,10 +105,13 @@ export function VoiceControls() { ? 'bg-discord-green/20' : 'bg-discord-yellow/20'; + const btnBase = 'flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors'; + const btnDefaultStyle = 'bg-[#111214] text-discord-text-muted hover:bg-[#1a1b1e] hover:text-discord-text-secondary'; + return ( -
+ <> {/* Row 1: Signal icon + status text + disconnect */} -
+
@@ -141,14 +146,13 @@ export function VoiceControls() {
{/* Row 2: Mute, Deafen, Camera, Screen Share */} -
- {/* Mute */} +
- {/* Deafen */} - {/* Camera */} - {/* Screen Share */}
-
+ ); }