diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee4b4c7..f3d6816a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to - ⚡️(frontend) limit unnecessary re-renders #1510 - 📝(legal) update terms of service - 💄(frontend) render Avatar initials in uppercase +- 💄(frontend) improve participant name rendering in the list ## Fixed @@ -35,7 +36,6 @@ and this project adheres to - 🐛(backend) allow any string as sub in the API serializer - 🐛(frontend) fall back to user.full_name on request-entry - ## [1.24.0] - 2026-07-21 ### Added diff --git a/src/frontend/src/features/participants/components/ParticipantName.tsx b/src/frontend/src/features/participants/components/ParticipantName.tsx new file mode 100644 index 00000000..d8fec50c --- /dev/null +++ b/src/frontend/src/features/participants/components/ParticipantName.tsx @@ -0,0 +1,68 @@ +import React, { useLayoutEffect, useRef, useState } from 'react' +import { Text } from '@/primitives' +import { css } from '@/styled-system/css' +import { useTranslation } from 'react-i18next' +import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip' + +export const ParticipantName = React.memo( + ({ displayedName, isLocal }: { displayedName: string; isLocal: boolean }) => { + const { t } = useTranslation('rooms') + + const nameRef = useRef(null) + const [isTruncated, setIsTruncated] = useState(false) + + useLayoutEffect(() => { + const el = nameRef.current + if (!el) return + const truncated = el.scrollWidth > el.clientWidth + setIsTruncated((prev) => (prev === truncated ? prev : truncated)) + }, [displayedName]) + + return ( + + +

+ {displayedName} +

+
+ {isLocal && ( + + ({t('participants.you')}) + + )} +
+ ) + } +) + +ParticipantName.displayName = 'ParticipantName' diff --git a/src/frontend/src/features/participants/components/ParticipantRow.tsx b/src/frontend/src/features/participants/components/ParticipantRow.tsx index 1b9e0198..3e7efc11 100644 --- a/src/frontend/src/features/participants/components/ParticipantRow.tsx +++ b/src/frontend/src/features/participants/components/ParticipantRow.tsx @@ -26,6 +26,7 @@ import { ParticipantMenuButton } from './menu/ParticipantMenuButton' import { PinBadge } from './PinBadge' import { UnauthenticatedBadge } from './UnauthenticatedBadge' import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog' +import { ParticipantName } from './ParticipantName' type MicIndicatorProps = { participant: Participant @@ -111,59 +112,26 @@ export const ParticipantRow = ({ participant }: ParticipantListItemProps) => { width: 'full', })} > - -
+ +
- - - - {name} - - {isLocal(participant) && ( - - ({t('participants.you')}) - - )} + + + + {getParticipantIsRoomOwner(participant) && t('participants.host')} + {getParticipantIsRoomAdmin(participant) && t('participants.cohost')} + {getParticipantIsRoomMember(participant) && + t('participants.member')} - {getParticipantIsRoomOwner(participant) && ( - {t('participants.host')} - )} - {getParticipantIsRoomAdmin(participant) && ( - {t('participants.cohost')} - )} - {getParticipantIsRoomMember(participant) && ( - {t('participants.member')} - )}
- + diff --git a/src/frontend/src/features/participants/components/RaisedHandRow.tsx b/src/frontend/src/features/participants/components/RaisedHandRow.tsx index 65572220..aa052692 100644 --- a/src/frontend/src/features/participants/components/RaisedHandRow.tsx +++ b/src/frontend/src/features/participants/components/RaisedHandRow.tsx @@ -1,7 +1,6 @@ import { css } from '@/styled-system/css' import { HStack } from '@/styled-system/jsx' -import { Text } from '@/primitives/Text' import { useTranslation } from 'react-i18next' import { Avatar } from '@/components/Avatar' import { useLowerHandParticipant } from '../api/lowerHandParticipant' @@ -11,6 +10,7 @@ import { isLocal } from '@/utils/livekit' import { RiHand } from '@remixicon/react' import { Button } from '@/primitives' import { AdminOrOwnerOnly } from '@/features/rooms/components/AdminOrOwnerOnly' +import { ParticipantName } from './ParticipantName' const ActionButton = ({ participant, @@ -42,9 +42,7 @@ type HandRaisedListItemProps = { } export const RaisedHandRow = ({ participant }: HandRaisedListItemProps) => { - const { t } = useTranslation('rooms') const name = participant.name || participant.identity - return ( { width: 'full', })} > - + - - - {name} - - {isLocal(participant) && ( - - ({t('participants.you')}) - - )} - + + + + + + - - - ) } diff --git a/src/frontend/src/features/participants/components/WaitingParticipantRow.tsx b/src/frontend/src/features/participants/components/WaitingParticipantRow.tsx index 66cdeaeb..6babb8a8 100644 --- a/src/frontend/src/features/participants/components/WaitingParticipantRow.tsx +++ b/src/frontend/src/features/participants/components/WaitingParticipantRow.tsx @@ -1,10 +1,11 @@ -import { Button, Text } from '@/primitives' +import { Button } from '@/primitives' import { HStack } from '@/styled-system/jsx' import { css } from '@/styled-system/css' import { Avatar } from '@/components/Avatar' import { useTranslation } from 'react-i18next' import { WaitingParticipant } from '../api/listWaitingParticipants' import { RiCloseLine } from '@remixicon/react' +import { ParticipantName } from './ParticipantName' export const WaitingParticipantRow = ({ participant, @@ -26,42 +27,11 @@ export const WaitingParticipantRow = ({ width: 'full', })} > - + - - - {participant.username} - - + - +
@@ -183,3 +179,25 @@ export const VisualOnlyTooltip = ({ ) } + +/** + * Wrapper component that displays a tooltip visually only (not announced by screen readers). + * + * This is necessary because TooltipTrigger from react-aria-components automatically adds + * aria-describedby on the button, which links the tooltip for accessibility. + * Even with aria-hidden="true" on the tooltip, screen readers still announce its content → duplication. + * This CSS wrapper avoids TooltipTrigger → no automatic aria-describedby → no duplication. + * + * Uses a portal to avoid being clipped by parent containers with overflow: hidden. + */ +export const VisualOnlyTooltip = ({ + children, + disabled, + ...props +}: VisualOnlyTooltipProps) => { + if (disabled) { + return children + } + + return {children} +}