fix: VideoButton disabled state, PTT listener leak, TS prop errors

- VideoButton now accepts disabled prop with tooltip and visual feedback;
  PrescreenControls passes disabled=true when cameraOnJoin=false
- PTT key listener in settings tracked via ref, cleaned up on unmount,
  guarded against stacking on double-click; useCallback + useRef
- CallControls screenshare cancel button: variant Surface -> Secondary
- General.tsx Box align prop: align -> alignItems (TS2322 fix)
This commit is contained in:
root
2026-05-15 15:38:02 -04:00
parent a2331eab1f
commit 549634dca0
4 changed files with 29 additions and 7 deletions
+1 -1
View File
@@ -235,7 +235,7 @@ export function CallControls({ callEmbed }: CallControlsProps) {
<Text size="B300">Share</Text> <Text size="B300">Share</Text>
</Button> </Button>
<Button <Button
size="300" variant="Surface" fill="Soft" radii="300" outlined size="300" variant="Secondary" fill="Soft" radii="300" outlined
onClick={() => setShareConfirm(false)} onClick={() => setShareConfirm(false)}
> >
<Text size="B300">Cancel</Text> <Text size="B300">Cancel</Text>
+7 -2
View File
@@ -81,15 +81,18 @@ export function SoundButton({ enabled, onToggle }: SoundButtonProps) {
type VideoButtonProps = { type VideoButtonProps = {
enabled: boolean; enabled: boolean;
onToggle: () => void; onToggle: () => void;
disabled?: boolean;
}; };
export function VideoButton({ enabled, onToggle }: VideoButtonProps) { export function VideoButton({ enabled, onToggle, disabled }: VideoButtonProps) {
return ( return (
<TooltipProvider <TooltipProvider
position="Top" position="Top"
delay={500} delay={500}
tooltip={ tooltip={
<Tooltip> <Tooltip>
<Text size="T200">{enabled ? 'Stop Camera' : 'Start Camera'}</Text> <Text size="T200">
{disabled ? 'Camera disabled in settings' : enabled ? 'Stop Camera' : 'Start Camera'}
</Text>
</Tooltip> </Tooltip>
} }
> >
@@ -102,6 +105,8 @@ export function VideoButton({ enabled, onToggle }: VideoButtonProps) {
size="400" size="400"
onClick={() => onToggle()} onClick={() => onToggle()}
outlined outlined
disabled={disabled}
style={disabled ? { opacity: 0.4, cursor: 'not-allowed' } : undefined}
> >
<Icon <Icon
size="400" size="400"
+4 -1
View File
@@ -6,6 +6,8 @@ import { ChatButton, ControlDivider, MicrophoneButton, SoundButton, VideoButton
import { useIsDirectRoom, useRoom } from '../../hooks/useRoom'; import { useIsDirectRoom, useRoom } from '../../hooks/useRoom';
import { useCallEmbed, useCallJoined, useCallStart } from '../../hooks/useCallEmbed'; import { useCallEmbed, useCallJoined, useCallStart } from '../../hooks/useCallEmbed';
import { useCallPreferences } from '../../state/hooks/callPreferences'; import { useCallPreferences } from '../../state/hooks/callPreferences';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
type MediaPermState = 'granted' | 'denied' | 'prompt' | 'unknown'; type MediaPermState = 'granted' | 'denied' | 'prompt' | 'unknown';
@@ -50,6 +52,7 @@ export function PrescreenControls({ canJoin }: PrescreenControlsProps) {
const { microphone, video, sound, toggleMicrophone, toggleVideo, toggleSound } = const { microphone, video, sound, toggleMicrophone, toggleVideo, toggleSound } =
useCallPreferences(); useCallPreferences();
const [cameraOnJoin] = useSetting(settingsAtom, 'cameraOnJoin');
return ( return (
<SequenceCard <SequenceCard
@@ -67,7 +70,7 @@ export function PrescreenControls({ canJoin }: PrescreenControlsProps) {
</Box> </Box>
<ControlDivider /> <ControlDivider />
<Box shrink="No" alignItems="Inherit" justifyContent="SpaceBetween" gap="200"> <Box shrink="No" alignItems="Inherit" justifyContent="SpaceBetween" gap="200">
<VideoButton enabled={video} onToggle={toggleVideo} /> <VideoButton enabled={video} onToggle={toggleVideo} disabled={!cameraOnJoin} />
<ChatButton /> <ChatButton />
</Box> </Box>
<Box grow="Yes" direction="Column" gap="200"> <Box grow="Yes" direction="Column" gap="200">
+17 -3
View File
@@ -3,7 +3,9 @@ import React, {
FormEventHandler, FormEventHandler,
KeyboardEventHandler, KeyboardEventHandler,
MouseEventHandler, MouseEventHandler,
useCallback,
useEffect, useEffect,
useRef,
useState, useState,
} from 'react'; } from 'react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -378,7 +380,7 @@ function Appearance() {
title="Lotus Terminal Mode" title="Lotus Terminal Mode"
description="LotusGuild Terminal Design System: Anduril Orange + Ice Cyan + Matrix Green, dot-grid background, CRT scanlines, and boot sequence animation." description="LotusGuild Terminal Design System: Anduril Orange + Ice Cyan + Matrix Green, dot-grid background, CRT scanlines, and boot sequence animation."
after={ after={
<Box direction="Row" gap="200" align="Center"> <Box direction="Row" gap="200" alignItems="Center">
{lotusTerminal && ( {lotusTerminal && (
<button <button
type="button" type="button"
@@ -811,8 +813,18 @@ function Calls() {
const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode'); const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode');
const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey'); const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey');
const [listeningForKey, setListeningForKey] = useState(false); const [listeningForKey, setListeningForKey] = useState(false);
const keyListenerRef = useRef<((e: KeyboardEvent) => void) | null>(null);
const handleKeyBind = () => { useEffect(
() => () => {
if (keyListenerRef.current)
window.removeEventListener('keydown', keyListenerRef.current, true);
},
[]
);
const handleKeyBind = useCallback(() => {
if (listeningForKey) return;
setListeningForKey(true); setListeningForKey(true);
const onKey = (e: KeyboardEvent) => { const onKey = (e: KeyboardEvent) => {
e.preventDefault(); e.preventDefault();
@@ -823,9 +835,11 @@ function Calls() {
setListeningForKey(false); setListeningForKey(false);
} }
window.removeEventListener('keydown', onKey, true); window.removeEventListener('keydown', onKey, true);
keyListenerRef.current = null;
}; };
keyListenerRef.current = onKey;
window.addEventListener('keydown', onKey, true); window.addEventListener('keydown', onKey, true);
}; }, [listeningForKey, setPttKey]);
const keyLabel = (code: string) => code === 'Space' ? 'Space' : code.replace('Key', '').replace('Digit', ''); const keyLabel = (code: string) => code === 'Space' ? 'Space' : code.replace('Key', '').replace('Digit', '');