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
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:
@@ -30,6 +30,8 @@ import {
|
|||||||
RectCords,
|
RectCords,
|
||||||
Badge,
|
Badge,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
Tooltip,
|
||||||
|
TooltipProvider,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { useFocusWithin, useHover } from 'react-aria';
|
import { useFocusWithin, useHover } from 'react-aria';
|
||||||
import FocusTrap from 'focus-trap-react';
|
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 { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav';
|
||||||
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
|
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
|
||||||
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
|
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 { nameInitials } from '../../utils/common';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useRoomUnread } from '../../state/hooks/unread';
|
import { useRoomUnread } from '../../state/hooks/unread';
|
||||||
@@ -690,6 +697,14 @@ function RoomNavItem_({
|
|||||||
const optionsVisible = hover || !!menuAnchor;
|
const optionsVisible = hover || !!menuAnchor;
|
||||||
const callSession = useCallSession(room);
|
const callSession = useCallSession(room);
|
||||||
const callMembers = useCallMembers(callSession);
|
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 startCall = useCallStart(direct);
|
||||||
const callEmbed = useCallEmbed();
|
const callEmbed = useCallEmbed();
|
||||||
// [Gitea #30] Same voice-limit check the call prescreen uses, so the sidebar
|
// [Gitea #30] Same voice-limit check the call prescreen uses, so the sidebar
|
||||||
@@ -853,11 +868,32 @@ function RoomNavItem_({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{callMembers.length > 0 && (
|
{callMembers.length > 0 && (
|
||||||
<Badge variant="Critical" fill="Solid" size="400">
|
// [Gitea #157] The count alone doesn't tell you whether it's your
|
||||||
<Text as="span" size="L400" truncate>
|
// squad — name the people on hover/focus (long-press on touch).
|
||||||
{callMembers.length} Live
|
<TooltipProvider
|
||||||
</Text>
|
position="Right"
|
||||||
</Badge>
|
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>
|
</Box>
|
||||||
</NavItemContent>
|
</NavItemContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user