refactor(room): rebuild the room and bot action menus onto a shared inline notifications mode-picker tray

This commit is contained in:
heaven 2026-06-15 02:57:32 +03:00
parent 0956e58cfd
commit f2ce9d4cd9
6 changed files with 273 additions and 162 deletions

View file

@ -1,5 +1,5 @@
import { Box, config, Icon, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
import React, { MouseEventHandler, ReactNode, useMemo, useState } from 'react';
import React, { MouseEventHandler, ReactNode, useMemo, useRef, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { stopPropagation } from '../utils/keyboard';
@ -55,22 +55,37 @@ export function RoomNotificationModeSwitcher({
const changing = modeState.status === AsyncStatus.Loading;
const [menuCords, setMenuCords] = useState<RectCords>();
// The trigger element of the currently-open popout. Lets the FocusTrap
// treat clicks on the trigger as "ours" instead of outside-clicks.
const anchorElRef = useRef<HTMLElement | null>(null);
const open = !!menuCords;
const handleToggleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
// Second click on the trigger CLOSES the popout instead of re-anchoring (reopening) it. `open`
// is the pre-click render value: even though focus-trap's `clickOutsideDeactivates` also fires
// `onDeactivate` for this same click, both paths resolve to "close" — so there's no reopen race
// regardless of listener order.
// Second click on the trigger CLOSES the popout instead of re-anchoring
// (reopening) it. The naive version was broken: focus-trap's
// `clickOutsideDeactivates` fires on MOUSEDOWN, flushing `open=false`
// and re-rendering before the trigger's CLICK handler ran — so the
// handler's closure always saw the closed state and re-opened. The
// `isAnchorTarget` guards below exclude the trigger from the trap's
// outside-click handling entirely, so this handler is the single owner
// of the trigger interaction and `open` really is the pre-click value.
if (open) {
setMenuCords(undefined);
anchorElRef.current = null;
} else {
anchorElRef.current = evt.currentTarget;
setMenuCords(evt.currentTarget.getBoundingClientRect());
}
};
const handleClose = () => {
setMenuCords(undefined);
anchorElRef.current = null;
};
const isAnchorTarget = (evt: MouseEvent | TouchEvent): boolean => {
const anchor = anchorElRef.current;
return !!anchor && evt.target instanceof Node && anchor.contains(evt.target);
};
const handleSelect = (mode: RoomNotificationMode) => {
@ -90,7 +105,12 @@ export function RoomNotificationModeSwitcher({
focusTrapOptions={{
initialFocus: false,
onDeactivate: handleClose,
clickOutsideDeactivates: true,
// Trigger clicks are NOT outside-clicks: deactivating on the
// trigger's mousedown closed the menu before the click could
// toggle, and the toggle re-opened it (the reopen bug). The
// trigger's own click handler owns open/close instead.
clickOutsideDeactivates: (evt) => !isAnchorTarget(evt),
allowOutsideClick: (evt) => isAnchorTarget(evt),
isKeyForward: (evt: KeyboardEvent) =>
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',

View file

@ -1,16 +1,16 @@
import React, { forwardRef, useState } from 'react';
import { Icons, Menu, Spinner } from 'folds';
import { Icons, Menu } from 'folds';
import type { Room } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import {
getRoomNotificationMode,
useRoomsNotificationPreferencesContext,
} from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
// Reuse the EXACT row vocabulary + popout chrome of the 1:1 chat's ⋮ menu (RoomActionsMenu) so the
// AI surface's overflow menu is the same popup, styled identically — only the action set differs.
import { ActionRow, ActionSectionLine, RowChevron } from '../room/room-actions/RoomActions';
import {
ActionRow,
ActionSectionLine,
NotificationsActionRow,
RowChevron,
} from '../room/room-actions/RoomActions';
import * as css from '../room/room-actions/RoomActions.css';
type AiChatMenuProps = {
@ -28,9 +28,6 @@ type AiChatMenuProps = {
export const AiChatMenu = forwardRef<HTMLDivElement, AiChatMenuProps>(
({ room, requestClose, onNewChat, onOpenHistory, onOpenPrivacy }, ref) => {
const { t } = useTranslation();
const notificationPreferences = useRoomsNotificationPreferencesContext();
const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId);
const [promptLeave, setPromptLeave] = useState(false);
return (
@ -62,18 +59,7 @@ export const AiChatMenu = forwardRef<HTMLDivElement, AiChatMenuProps>(
ariaHasPopup
trailing={<RowChevron />}
/>
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
{(handleOpen, opened, changing) => (
<ActionRow
icon={Icons.Bell}
label={t('Room.notifications')}
onClick={handleOpen}
ariaPressed={opened}
ariaHasPopup
trailing={changing ? <Spinner size="100" variant="Secondary" /> : <RowChevron />}
/>
)}
</RoomNotificationModeSwitcher>
<NotificationsActionRow roomId={room.roomId} />
<ActionRow
icon={Icons.ShieldLock}
label={t('Bots.privacy.menu')}
@ -93,7 +79,7 @@ export const AiChatMenu = forwardRef<HTMLDivElement, AiChatMenuProps>(
label={t('Room.leave_room')}
onClick={() => setPromptLeave(true)}
accent="critical"
ariaPressed={promptLeave}
ariaExpanded={promptLeave}
/>
</div>
</Menu>

View file

@ -1,5 +1,5 @@
import React, { forwardRef } from 'react';
import { Box, Icon, Icons, Line, Menu, MenuItem, Spinner, Text, config, toRem } from 'folds';
import React, { forwardRef, useState } from 'react';
import { Icons, Menu } from 'folds';
import { useSetAtom } from 'jotai';
import type { Room } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
@ -8,27 +8,30 @@ import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { useRoomUnread } from '../../state/hooks/unread';
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import {
getRoomNotificationMode,
getRoomNotificationModeIcon,
useRoomsNotificationPreferencesContext,
} from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
import { UseStateProvider } from '../../components/UseStateProvider';
import { markAsRead } from '../../utils/notifications';
import { botShowChatAtomFamily } from './botExperienceState';
// Same row vocabulary + popout chrome as the 1:1 chat's ⋮ menu
// (RoomActionsMenu) and the AI chat's AiChatMenu — one popup style across
// every surface, only the action set differs.
import {
ActionRow,
ActionSectionLine,
NotificationsActionRow,
RowChevron,
} from '../room/room-actions/RoomActions';
import * as css from '../room/room-actions/RoomActions.css';
type BotShellMenuProps = {
room: Room;
requestClose: () => void;
};
// Slim dropdown for the bot hero's «Настроить» button. Only items that make
// sense in a 1:1 with a bridge bot:
// The bridge-bot widget hero's ⋮ menu. Only items that make sense in a 1:1
// with a bridge bot:
// - Show chat (chat-fallback toggle, switches BotExperienceHost branch)
// - Mark as read
// - Notifications (shared switcher)
// - Notifications (inline mode picker)
// - Leave room
// Search / Pinned / Invite / Copy-link / Room-settings / Jump-to-date are
// dropped — none apply to a bridge bot's control DM.
@ -38,12 +41,12 @@ export const BotShellMenu = forwardRef<HTMLDivElement, BotShellMenuProps>(
const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const notificationPreferences = useRoomsNotificationPreferencesContext();
const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId);
// Setter-only — the menu's only job around showChat is to flip it.
// useSetAtom skips the value-subscription that useAtom registers.
const setShowChat = useSetAtom(botShowChatAtomFamily(room.roomId));
const [promptLeave, setPromptLeave] = useState(false);
const handleShowChat = () => {
setShowChat(true);
requestClose();
@ -54,80 +57,41 @@ export const BotShellMenu = forwardRef<HTMLDivElement, BotShellMenuProps>(
};
return (
<Menu ref={ref} style={{ maxWidth: toRem(240), width: '100vw' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
<Menu ref={ref} variant="Surface" className={css.PopoutMenu}>
{promptLeave && (
<LeaveRoomPrompt
roomId={room.roomId}
onDone={requestClose}
onCancel={() => setPromptLeave(false)}
/>
)}
<div className={css.PopoutGroup}>
<ActionRow
icon={Icons.Message}
label={t('Bots.show_chat')}
onClick={handleShowChat}
size="300"
after={<Icon size="100" src={Icons.Message} />}
radii="300"
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
{t('Bots.show_chat')}
</Text>
</MenuItem>
<MenuItem
trailing={<RowChevron />}
/>
<ActionRow
icon={Icons.CheckTwice}
label={t('Room.mark_as_read')}
onClick={handleMarkAsRead}
size="300"
after={<Icon size="100" src={Icons.CheckTwice} />}
radii="300"
disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
{t('Room.mark_as_read')}
</Text>
</MenuItem>
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
{(handleOpen, opened, changing) => (
<MenuItem
size="300"
after={
changing ? (
<Spinner size="100" variant="Secondary" />
) : (
<Icon size="100" src={getRoomNotificationModeIcon(notificationMode)} />
)
}
radii="300"
aria-pressed={opened}
onClick={handleOpen}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
{t('Room.notifications')}
</Text>
</MenuItem>
)}
</RoomNotificationModeSwitcher>
</Box>
<Line variant="Surface" size="300" />
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<UseStateProvider initial={false}>
{(promptLeave, setPromptLeave) => (
<>
<MenuItem
onClick={() => setPromptLeave(true)}
variant="Critical"
fill="None"
size="300"
after={<Icon size="100" src={Icons.ArrowGoLeft} />}
radii="300"
aria-pressed={promptLeave}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
{t('Room.leave_room')}
</Text>
</MenuItem>
{promptLeave && (
<LeaveRoomPrompt
roomId={room.roomId}
onDone={requestClose}
onCancel={() => setPromptLeave(false)}
/>
)}
</>
)}
</UseStateProvider>
</Box>
/>
<NotificationsActionRow roomId={room.roomId} />
</div>
<ActionSectionLine />
<div className={css.PopoutGroup}>
<ActionRow
icon={Icons.ArrowGoLeft}
label={t('Room.leave_room')}
onClick={() => setPromptLeave(true)}
accent="critical"
ariaExpanded={promptLeave}
/>
</div>
</Menu>
);
}

View file

@ -73,16 +73,57 @@ export const ActionRowTrailing = style({
gap: config.space.S100,
});
export const ActionRowTrailingText = style({
color: color.Surface.OnContainer,
opacity: config.opacity.P400,
});
export const ActionRowChevron = style({
color: color.Surface.OnContainer,
opacity: config.opacity.P300,
});
// Inline expansion group under a parent ActionRow (the Notifications mode
// picker). Indented past the parent's fixed icon slot so the sub-labels
// align with the parent label — reads as a nested tray INSIDE the popout
// instead of a second anchored PopOut (which rendered half-hidden over the
// menu on mobile and re-opened on the second tap — see NotificationsActionRow).
export const SubRowGroup = style({
display: 'flex',
flexDirection: 'column',
gap: toRem(1),
paddingLeft: `calc(${toRem(20)} + ${config.space.S300})`,
});
// A mode option row — same flat vocabulary as ActionRow, just shorter.
export const SubRow = style({
width: '100%',
minHeight: toRem(36),
display: 'flex',
alignItems: 'center',
gap: config.space.S300,
padding: `0 ${config.space.S300}`,
borderRadius: toRem(8),
background: 'transparent',
border: 'none',
textAlign: 'left',
font: 'inherit',
color: color.Surface.OnContainer,
cursor: 'pointer',
selectors: {
'&:hover:not(:disabled)': { backgroundColor: color.Surface.ContainerHover },
'&:active:not(:disabled)': { backgroundColor: color.Surface.ContainerActive },
'&:disabled': {
cursor: 'default',
opacity: config.opacity.P300,
},
'&:focus-visible': {
outline: `${toRem(2)} solid ${color.Primary.Main}`,
outlineOffset: toRem(-2),
},
},
});
export const SubRowSelectedIcon = style({
flexShrink: 0,
color: color.Primary.Main,
});
// Popout container: width bump from the legacy 220px so the Notifications
// mode word + Pinned badge breathe; softer mobile corner radius overriding
// the folds Menu default. Background/border/shadow come from the folds Menu
@ -91,7 +132,14 @@ export const PopoutMenu = style({
width: toRem(280),
maxWidth: '100vw',
borderRadius: toRem(16),
overflow: 'hidden',
// Scroll, don't clip: the inline Notifications tray grows the menu AFTER
// the folds PopOut has fixed its position (PopOut never repositions on
// content growth), so on short viewports the tail rows would otherwise
// extend past the viewport bottom unreachable. The max-height keeps the
// frozen `top` valid; overflowY:auto still clips to the rounded corners.
maxHeight: 'calc(100dvh - 5rem)',
overflowY: 'auto',
overflowX: 'hidden',
});
export const PopoutGroup = style({

View file

@ -1,9 +1,16 @@
import React, { MouseEventHandler, ReactNode } from 'react';
import { Icon, Icons, IconSrc, Text, color } from 'folds';
import React, { MouseEventHandler, ReactNode, useState } from 'react';
import { Icon, Icons, IconSrc, Spinner, Text, color } from 'folds';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAtom, useSetAtom } from 'jotai';
import { RoomNotificationMode } from '../../../hooks/useRoomsNotificationPreferences';
import {
getRoomNotificationMode,
getRoomNotificationModeIcon,
RoomNotificationMode,
useRoomsNotificationPreferencesContext,
useSetRoomNotificationPreference,
} from '../../../hooks/useRoomsNotificationPreferences';
import { AsyncStatus } from '../../../hooks/useAsyncCallback';
import { botFailedAtomFamily, botShowChatAtomFamily } from '../../bots/botExperienceState';
import { getBotPath } from '../../../pages/pathUtils';
import * as css from './RoomActions.css';
@ -22,7 +29,7 @@ type ActionRowProps = {
trailing?: ReactNode;
accent?: 'primary' | 'critical';
disabled?: boolean;
ariaPressed?: boolean;
ariaExpanded?: boolean;
ariaHasPopup?: boolean;
};
@ -37,7 +44,7 @@ export function ActionRow({
trailing,
accent,
disabled,
ariaPressed,
ariaExpanded,
ariaHasPopup,
}: ActionRowProps) {
let accentColor: string | undefined;
@ -50,7 +57,7 @@ export function ActionRow({
className={css.ActionRow}
onClick={onClick}
disabled={disabled}
aria-pressed={ariaPressed}
aria-expanded={ariaExpanded}
aria-haspopup={ariaHasPopup}
style={accentColor ? { color: accentColor } : undefined}
>
@ -70,15 +77,6 @@ export function RowChevron() {
return <Icon className={css.ActionRowChevron} size="100" src={Icons.ChevronRight} />;
}
// Muted trailing word (e.g. the current notification mode beside the row).
export function RowTrailingText({ children }: { children: ReactNode }) {
return (
<Text as="span" size="T200" className={css.ActionRowTrailingText}>
{children}
</Text>
);
}
// Subtle Fleet hairline between row groups.
export function ActionSectionLine() {
return <div aria-hidden className={css.SectionLine} />;
@ -126,3 +124,109 @@ export function useNotificationModeLabel(mode: RoomNotificationMode): string {
};
return labels[mode];
}
const NOTIFICATION_MODES: RoomNotificationMode[] = [
RoomNotificationMode.Unset,
RoomNotificationMode.AllMessages,
RoomNotificationMode.SpecialMessages,
RoomNotificationMode.Mute,
];
function NotificationModeSubRow({
mode,
selected,
disabled,
onSelect,
}: {
mode: RoomNotificationMode;
selected: boolean;
disabled: boolean;
onSelect: (mode: RoomNotificationMode) => void;
}) {
const label = useNotificationModeLabel(mode);
return (
<button
type="button"
className={css.SubRow}
onClick={() => onSelect(mode)}
disabled={disabled}
aria-pressed={selected}
>
<span className={css.ActionRowIcon}>
<Icon size="100" src={getRoomNotificationModeIcon(mode)} filled={selected} />
</span>
<Text
as="span"
size="T300"
className={css.ActionRowLabel}
style={selected ? { fontWeight: 600 } : undefined}
>
{label}
</Text>
{selected && <Icon className={css.SubRowSelectedIcon} size="100" src={Icons.Check} />}
</button>
);
}
type NotificationsActionRowProps = {
roomId: string;
};
// Notifications row for the ⋮ popout menus. The mode picker expands INLINE
// (an indented tray of the four modes under the row) instead of the old
// nested anchored PopOut, which rendered half-hidden on top of the menu on
// mobile. A plain boolean expansion has no anchor and no second FocusTrap,
// so the whole class of nested-trap positioning/toggle foot-guns is out.
export function NotificationsActionRow({ roomId }: NotificationsActionRowProps) {
const { t } = useTranslation();
const preferences = useRoomsNotificationPreferencesContext();
const mode = getRoomNotificationMode(preferences, roomId);
const [expanded, setExpanded] = useState(false);
const { modeState, setMode } = useSetRoomNotificationPreference(roomId);
const changing = modeState.status === AsyncStatus.Loading;
const handleSelect = (nextMode: RoomNotificationMode) => {
if (changing) return;
setMode(nextMode, mode);
setExpanded(false);
};
return (
<>
{/* No current-mode word in the trailing slot it crowded the row
label on narrow popouts; the expanded tray's check mark is the
mode indicator. */}
<ActionRow
icon={Icons.Bell}
label={t('Room.notifications')}
onClick={() => setExpanded(!expanded)}
ariaExpanded={expanded}
trailing={
changing ? (
<Spinner size="100" variant="Secondary" />
) : (
<Icon
className={css.ActionRowChevron}
size="100"
src={expanded ? Icons.ChevronTop : Icons.ChevronBottom}
/>
)
}
/>
{expanded && (
<div className={css.SubRowGroup}>
{NOTIFICATION_MODES.map((m) => (
<NotificationModeSubRow
key={m}
mode={m}
selected={m === mode}
disabled={changing}
onSelect={handleSelect}
/>
))}
</div>
)}
</>
);
}

View file

@ -1,5 +1,5 @@
import React, { MouseEventHandler, forwardRef, useState } from 'react';
import { Badge, Icons, Menu, RectCords, Spinner, Text } from 'folds';
import { Badge, Icons, Menu, RectCords, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { Room } from 'matrix-js-sdk';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
@ -14,11 +14,6 @@ import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useSpaceOptionally } from '../../../hooks/useSpace';
import { useOpenRoomSettings } from '../../../state/hooks/roomSettings';
import {
getRoomNotificationMode,
useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../../components/RoomNotificationSwitcher';
import { useBotPresets } from '../../bots/catalog';
import { findBotPresetForRoom } from '../../bots/room';
import { LeaveRoomPrompt } from '../../../components/leave-room-prompt';
@ -29,7 +24,13 @@ import { getViaServers } from '../../../plugins/via-servers';
import { copyToClipboard } from '../../../utils/dom';
import { markAsRead } from '../../../utils/notifications';
import { JumpToTime } from '../jump-to-time';
import { ActionRow, ActionSectionLine, BotWidgetActionRow, RowChevron } from './RoomActions';
import {
ActionRow,
ActionSectionLine,
BotWidgetActionRow,
NotificationsActionRow,
RowChevron,
} from './RoomActions';
import * as css from './RoomActions.css';
type RoomActionsMenuProps = {
@ -43,9 +44,10 @@ type RoomActionsMenuProps = {
// Content of the room overflow (⋮) popout, on both desktop and mobile. The
// folds Menu `variant="Surface"` paints the deep Vojo input-field tone
// (#0d0e11 = Surface.Container — the exact fill of the chat message composer,
// see RoomView.css.ts). Nested sub-flows are the same as upstream:
// Notifications → the anchored RoomNotificationModeSwitcher PopOut; Pinned →
// the RoomPinMenu PopOut via onPin(cords); Invite/Jump/Leave → centered overlays.
// see RoomView.css.ts). Nested sub-flows: Notifications expands INLINE
// (NotificationsActionRow — the old nested PopOut was half-hidden on mobile
// and re-opened on second tap); Pinned → the RoomPinMenu PopOut via
// onPin(cords); Invite/Jump/Leave → centered overlays.
export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
({ room, callView, botControlRoom, onPin, requestClose }, ref) => {
const { t } = useTranslation();
@ -56,8 +58,6 @@ export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
const creators = useRoomCreators(room);
const permissions = useRoomPermissions(creators, powerLevels);
const canInvite = permissions.action('invite', mx.getSafeUserId());
const notificationPreferences = useRoomsNotificationPreferencesContext();
const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId);
const pinnedEvents = useRoomPinnedEvents(room);
const { navigateRoom } = useRoomNavigate();
const openSettings = useOpenRoomSettings();
@ -132,18 +132,7 @@ export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
onClick={handleMarkAsRead}
disabled={!unread}
/>
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
{(handleOpen, opened, changing) => (
<ActionRow
icon={Icons.Bell}
label={t('Room.notifications')}
onClick={handleOpen}
ariaPressed={opened}
ariaHasPopup
trailing={changing ? <Spinner size="100" variant="Secondary" /> : <RowChevron />}
/>
)}
</RoomNotificationModeSwitcher>
<NotificationsActionRow roomId={room.roomId} />
<ActionRow
icon={Icons.Pin}
label={t('Room.pinned_messages')}
@ -167,7 +156,7 @@ export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
icon={Icons.RecentClock}
label={t('Room.jump_to_time')}
onClick={() => setPromptJump(true)}
ariaPressed={promptJump}
ariaExpanded={promptJump}
ariaHasPopup
trailing={<RowChevron />}
/>
@ -187,7 +176,7 @@ export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
label={t('Room.invite')}
onClick={() => setInvitePrompt(true)}
accent="primary"
ariaPressed={invitePrompt}
ariaExpanded={invitePrompt}
/>
</div>
</>
@ -201,7 +190,7 @@ export const RoomActionsMenu = forwardRef<HTMLDivElement, RoomActionsMenuProps>(
onClick={() => setPromptLeave(true)}
accent="critical"
disabled={callView}
ariaPressed={promptLeave}
ariaExpanded={promptLeave}
/>
</div>
</Menu>