fix(a11y): correct call-control aria + picker/recorder/live-chip UX (DP7-DP12)

DP7: fix inverted Deafen/Undeafen aria-label on the sound button so it
  matches the action and tooltip.
DP8: add aria-pressed to Sound/Video/ScreenShare toggles and align the
  Microphone toggle to the same "pressed = feature on/active" semantic.
DP9: return focus to the trigger when the GifPicker closes and cap its
  fixed width to min(312px, calc(100vw - 16px)) so it can't overflow
  narrow viewports.
DP10: drop redundant mouse-only clear onClick nested inside the search
  filter Chip buttons (parent chip/menu already performs the clear);
  the cross icons are now purely decorative.
DP11: constrain the voice recorder widget and let the waveform shrink so
  it fits a narrow composer, and expose the live duration as a role="timer"
  snapshot instead of spamming a screen reader every 100ms.
DP12: announce the live-call participant count via a visually-hidden
  role="status" region.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 22:37:22 -04:00
co-authored by Claude Opus 4.8
parent db86432644
commit 6cf18c3bd9
6 changed files with 120 additions and 139 deletions
+5 -4
View File
@@ -7,6 +7,7 @@ import { useSetting } from '../state/hooks/settings';
import { settingsAtom } from '../state/settings';
const PICKER_WIDTH = 312;
const PICKER_WIDTH_CSS = `min(${PICKER_WIDTH}px, calc(100vw - 16px))`;
type GifPickerInnerProps = {
onSelect: (url: string, width: number, height: number) => void;
@@ -31,7 +32,7 @@ function GifPickerInner({ onSelect, requestClose, lotusTerminal }: GifPickerInne
);
return (
<Box direction="Column" style={{ width: `${PICKER_WIDTH}px` }}>
<Box direction="Column" style={{ width: PICKER_WIDTH_CSS }}>
{lotusTerminal && (
<div
style={{
@@ -88,7 +89,7 @@ export function GifPicker({ apiKey, onSelect, requestClose }: GifPickerProps) {
overflow: 'hidden',
boxShadow:
'0 4px 24px color-mix(in srgb, var(--lt-accent-orange) 10%, transparent), 0 0 0 1px color-mix(in srgb, var(--lt-accent-orange) 8%, transparent)',
width: `${PICKER_WIDTH}px`,
width: PICKER_WIDTH_CSS,
}
: {
background: color.Surface.Container,
@@ -96,14 +97,14 @@ export function GifPicker({ apiKey, onSelect, requestClose }: GifPickerProps) {
borderRadius: config.radii.R400,
overflow: 'hidden',
boxShadow: color.Other.Shadow,
width: `${PICKER_WIDTH}px`,
width: PICKER_WIDTH_CSS,
};
return (
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
returnFocusOnDeactivate: true,
onDeactivate: requestClose,
clickOutsideDeactivates: true,
allowOutsideClick: true,
+5 -1
View File
@@ -197,6 +197,8 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
background: color.SurfaceVariant.Container,
borderRadius: config.radii.R300,
padding: `${toRem(4)} ${toRem(8)}`,
maxWidth: '100%',
minWidth: 0,
}}
>
<Box
@@ -212,6 +214,8 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
/>
<Text
size="T200"
role="timer"
aria-label={`Recording duration ${formatDuration(durationMs)}`}
style={{
minWidth: toRem(32),
fontVariantNumeric: 'tabular-nums',
@@ -230,7 +234,7 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
data-voice-waveform
alignItems="Center"
gap="100"
style={{ height: toRem(20), overflow: 'hidden', flexShrink: 0 }}
style={{ height: toRem(20), overflow: 'hidden', flexShrink: 1, minWidth: 0 }}
>
{waveformBars.map((h, i) => (
<div