Compare commits

..

1 Commits

Author SHA1 Message Date
Ovgodd 3773e43d93 ♿️(frontend) show a visible focus outline on menu items
Keyboard focus was cleared with outline:none.
2026-10-09 18:13:14 +02:00
5 changed files with 16 additions and 25 deletions
+1 -2
View File
@@ -18,6 +18,7 @@ and this project adheres to
- ⚡️(backend) reduce domain queries on the application token endpoint
- ♻️️️(backend) use a dedicated auth scheme for LiveKit token auth
- ♻️(all) stop relying on cookies for the lobby flow
- ♿️(frontend) show a visible focus outline on menu items #1797
### Fixed
@@ -25,8 +26,6 @@ and this project adheres to
- 🔒️(frontend) upgrade tiff to fix CVE-2026-4775
- 🔒️(backend) bind accepted lobby entries to the current username
- 🔒️(backend) sign lobby participant id
- 🐛(frontend) omit the default speaker id sent to LiveKit
- 🐛(frontend) play the speaker test when no output is picked
## [1.34.0] - 2026-10-07
@@ -45,7 +45,6 @@ import { userStore } from '@/stores/user'
import { WatchMediaDeviceErrors } from './WatchMediaDeviceErrors'
import { MeetDevtools } from '@/features/devtools'
import { VOICE_AUDIO_CONSTRAINTS } from '@/features/rooms/livekit/utils/constants'
import { resolveAudioOutputDeviceId } from '@/features/rooms/utils/resolveAudioOutputDeviceId'
export const Conference = ({
roomId,
@@ -121,7 +120,7 @@ export const Conference = ({
...VOICE_AUDIO_CONSTRAINTS,
},
audioOutput: {
deviceId: resolveAudioOutputDeviceId(userConfig.audioOutputDeviceId),
deviceId: userConfig.audioOutputDeviceId ?? undefined,
},
}
// do not rely on the userConfig object directly as its reference may change on every render
@@ -4,7 +4,6 @@ import { RiVolumeUpLine } from '@remixicon/react'
import { styled } from '@/styled-system/jsx'
import { Button } from '@/primitives'
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
import { resolveAudioOutputDeviceId } from '@/features/rooms/utils/resolveAudioOutputDeviceId'
// Speaker test in the audiooutput menu footer (Meet-style UX). Outputs have
// no track: the test plays a bundled file through the selected sink, and
@@ -67,8 +66,6 @@ export const OutputSoundTester = ({
const audioRef = useRef<HTMLAudioElement>(null)
const [isPlaying, setIsPlaying] = useState(false)
const outputId = resolveAudioOutputDeviceId(sinkId)
const latestSinkIdRef = useRef(sinkId)
latestSinkIdRef.current = sinkId
@@ -82,15 +79,15 @@ export const OutputSoundTester = ({
}, [])
useEffect(() => {
if (!outputId || !canTestAudioOutput()) return
audioRef.current?.setSinkId(outputId).catch(() => {
if (!sinkId || !canTestAudioOutput()) return
audioRef.current?.setSinkId(sinkId).catch(() => {
// Re-routing failed (stale or unplugged device): stop the test rather
// than keep playing through the previous sink.
if (latestSinkIdRef.current === sinkId) {
stopPlayback()
}
})
}, [outputId, sinkId, stopPlayback])
}, [sinkId, stopPlayback])
useEffect(() => {
const audio = audioRef.current
@@ -111,8 +108,8 @@ export const OutputSoundTester = ({
// Confirm routing before starting: a no-op when already routed,
// but rejects on a stale device id, so the test never plays
// through the wrong sink.
if (outputId && canTestAudioOutput()) {
await audio.setSinkId(outputId)
if (sinkId && canTestAudioOutput()) {
await audio.setSinkId(sinkId)
}
await audio.play()
setIsPlaying(true)
@@ -1,11 +0,0 @@
/**
* `'default'` means "use the system output". Firefox has no device with that
* id, so setSinkId('default') rejects. Omit it and the browser keeps the OS
* default, which is the same result.
*/
export const resolveAudioOutputDeviceId = (
deviceId: string | undefined
): string | undefined => {
if (!deviceId || deviceId === 'default') return undefined
return deviceId
}
+9 -2
View File
@@ -25,12 +25,15 @@ export const menuRecipe = sva({
'&[data-focused]': {
color: 'primary.text',
backgroundColor: 'primaryDark.100',
outline: 'none!',
},
'&[data-hovered]': {
color: 'primary.text',
backgroundColor: 'primaryDark.100',
outline: 'none!',
},
'&[data-focus-visible]': {
outline: '2px solid',
outlineColor: 'focusRing',
outlineOffset: '2px',
},
},
},
@@ -49,6 +52,10 @@ export const menuRecipe = sva({
dark: {
item: {
color: 'white',
'&[data-focus-visible]': {
outlineColor: 'white!',
outlineOffset: '2px!',
},
},
},
},