feat(calls): 'N Live' pill names who is in the call on hover/focus (#157)
CI / Build & Quality Checks (push) Successful in 1m51s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 6s
CI / Trigger Desktop Build (push) Successful in 5s
CI / Playwright smoke (e2e) (push) Successful in 1m37s

Tooltip (folds TooltipProvider, 300 ms) on the room-list Live badge listing the
call members' display names — deduped per user, capped at 6 + 'and N more' —
plus an aria-label with the same text so keyboard/screen-reader users get it
too (the badge is focusable). Names come from the same MatrixRTC memberships
that produce the count; no new data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-19 12:18:20 -04:00
co-authored by Claude Opus 5
parent 34574178a9
commit a475531b2b
+42 -6
View File
@@ -30,6 +30,8 @@ import {
RectCords,
Badge,
Spinner,
Tooltip,
TooltipProvider,
} from 'folds';
import { useFocusWithin, useHover } from 'react-aria';
import FocusTrap from 'focus-trap-react';
@@ -41,7 +43,12 @@ import isYesterday from 'dayjs/plugin/isYesterday';
import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav';
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
import { getDirectRoomAvatarUrl, getRoomAvatarUrl, getStateEvent } from '../../utils/room';
import {
getDirectRoomAvatarUrl,
getMemberName,
getRoomAvatarUrl,
getStateEvent,
} from '../../utils/room';
import { nameInitials } from '../../utils/common';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomUnread } from '../../state/hooks/unread';
@@ -690,6 +697,14 @@ function RoomNavItem_({
const optionsVisible = hover || !!menuAnchor;
const callSession = useCallSession(room);
const callMembers = useCallMembers(callSession);
const liveNames = useMemo(() => {
const names = Array.from(
new Set(callMembers.map((m) => m.sender).filter((u): u is string => !!u)),
).map((u) => getMemberName(room, u));
const shown = names.slice(0, 6);
const more = names.length - shown.length;
return more > 0 ? `${shown.join(', ')} and ${more} more` : shown.join(', ');
}, [callMembers, room]);
const startCall = useCallStart(direct);
const callEmbed = useCallEmbed();
// [Gitea #30] Same voice-limit check the call prescreen uses, so the sidebar
@@ -853,11 +868,32 @@ function RoomNavItem_({
/>
)}
{callMembers.length > 0 && (
<Badge variant="Critical" fill="Solid" size="400">
<Text as="span" size="L400" truncate>
{callMembers.length} Live
</Text>
</Badge>
// [Gitea #157] The count alone doesn't tell you whether it's your
// squad — name the people on hover/focus (long-press on touch).
<TooltipProvider
position="Right"
delay={300}
tooltip={
<Tooltip>
<Text size="T200">{liveNames}</Text>
</Tooltip>
}
>
{(tipRef) => (
<Badge
ref={tipRef}
variant="Critical"
fill="Solid"
size="400"
tabIndex={0}
aria-label={`${callMembers.length} in call: ${liveNames}`}
>
<Text as="span" size="L400" truncate>
{callMembers.length} Live
</Text>
</Badge>
)}
</TooltipProvider>
)}
</Box>
</NavItemContent>