diff --git a/docs/ai/architecture.md b/docs/ai/architecture.md index b1f6fe96..dec5f063 100644 --- a/docs/ai/architecture.md +++ b/docs/ai/architecture.md @@ -151,7 +151,7 @@ Use **`useIsOneOnOne()`** from `hooks/useRoom.ts` whenever you need the 1:1 vs g | Dir | Purpose | |-----|---------| -| `room/` | Core room view. **RoomTimeline.tsx** (~2516 LOC), **RoomInput.tsx** (~828 LOC), **RoomViewHeader.tsx** (11-line wrapper → **RoomViewHeaderDm.tsx**, ~791 LOC — the real Dawn header for *every* room class; identity area branches 3 ways: 1:1 → peer-profile sheet, group → members sheet, callView → static; subline shows `local:server` + presence for 1:1 or `N members` for groups; phone button via `useDmCallVisible`; search/pinned/invite/leave/settings/jump-to-time live in the `…` `RoomMenu`). Also **ThreadDrawer.tsx** (~1344 LOC, full thread surface with its own composer), `ThreadSummaryCard.tsx`, `RoomView.tsx` (composer-overlay pattern), `RoomViewMembersPanel`/`MembersSidePanel`, `RoomViewProfilePanel`/`ProfileSidePanel`, `RoomViewMediaSidePanel`/`MobileMediaViewerHorseshoe`, `RoomTimelineTyping.tsx`, `EmptyTimeline.tsx`, `RoomTombstone`, `CallChatView`, `CommandAutocomplete`, `room-pin-menu/`, `jump-to-time/`, `reaction-viewer/`. `MembersDrawer.tsx` still exists but is used **only** by lobby + `members-list/`, not Room.tsx. | +| `room/` | Core room view. **RoomTimeline.tsx** (~2516 LOC), **RoomInput.tsx** (~828 LOC), **RoomViewHeader.tsx** (11-line wrapper → **RoomViewHeaderDm.tsx**, ~791 LOC — the real Dawn header for *every* room class; identity area branches 3 ways: 1:1 → peer-profile sheet, group → members sheet, callView → static; subline shows `local:server` + presence for 1:1 or `N members` for groups; phone button via `useDmCallVisible`; the `…` overflow opens `room-actions/RoomActionsMenu` in an anchored folds PopOut — same chrome on desktop and mobile — restyled to a flat `ActionRow` vocabulary (`RoomActions.tsx`) on the dark-blue Vojo composer tone (folds Menu `variant="SurfaceVariant"` = #181a20); hosts mark-read/notifications/search/pinned/copy-link/settings/jump-to-time/invite/leave with the same nested popouts/overlays as upstream). Also **ThreadDrawer.tsx** (~1344 LOC, full thread surface with its own composer), `ThreadSummaryCard.tsx`, `RoomView.tsx` (composer-overlay pattern), `RoomViewMembersPanel`/`MembersSidePanel`, `RoomViewProfilePanel`/`ProfileSidePanel`, `RoomViewMediaSidePanel`/`MobileMediaViewerHorseshoe`, `RoomTimelineTyping.tsx`, `EmptyTimeline.tsx`, `RoomTombstone`, `CallChatView`, `CommandAutocomplete`, `room-pin-menu/`, `jump-to-time/`, `reaction-viewer/`. `MembersDrawer.tsx` still exists but is used **only** by lobby + `members-list/`, not Room.tsx. | | `room/message/` | `Message.tsx` (~1506 LOC). The Stream/Channel branch is `Message.tsx:1160` (`layout === 'channel' ? : `), driven by the `layout` prop from `RoomTimeline`. Hosts the edit/delete/react/report/pin/copy-link/source menu, `useDotColor` (Stream rail dot only), thread reply handler. Also `MessageEditor`, `CallMessage`, `SyslineMessage`, `Reactions`, `EncryptedContent`. | | `room-nav/` | **Three** list-row components now: `RoomNavItem.tsx` (~434 LOC, channels + spaces lists), `DmStreamRow.tsx` (~496 LOC, the Direct-list row), `DirectInviteRow.tsx` (~282 LOC, inline accept/decline invite row in the Direct list). | | `bots/` | **NEW.** Bridge-bot widget host (a bot's control room = the DM with its mxid). `catalog.ts` loads `BotPreset[]` from `config.json` `bots[]` (validates widget-origin allowlist + command prefix). `useBotRoom.ts` classifies control-room membership into a 6-state union. `BotShell` mounts a `matrix-widget-api` iframe (`BotWidgetEmbed`/`BotWidgetDriver`, tight `m.text`/`m.notice`-only capability allowlist). `botShowChatAtomFamily` toggles widget vs chat-fallback. `room.ts` = single source for portal-vs-control-room (`isBotControlRoom`). Pairs with `pages/client/bots/`. | diff --git a/src/app/features/room/RoomViewHeaderDm.tsx b/src/app/features/room/RoomViewHeaderDm.tsx index 0bc03600..6298140f 100644 --- a/src/app/features/room/RoomViewHeaderDm.tsx +++ b/src/app/features/room/RoomViewHeaderDm.tsx @@ -1,37 +1,25 @@ /* eslint-disable react/destructuring-assignment */ -import React, { forwardRef, MouseEventHandler, useState } from 'react'; +import React, { MouseEventHandler, useState } from 'react'; import { Avatar, - Badge, Box, Icon, IconButton, Icons, - Line, - Menu, - MenuItem, PopOut, RectCords, - Spinner, Text, Tooltip, TooltipProvider, - config, - toRem, } from 'folds'; import FocusTrap from 'focus-trap-react'; -import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useAtom, useAtomValue, useSetAtom } from 'jotai'; +import { useAtomValue } from 'jotai'; import { Room } from 'matrix-js-sdk'; import { PageHeader } from '../../components/page'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { UserAvatar } from '../../components/user-avatar'; -import { UseStateProvider } from '../../components/UseStateProvider'; import { BackRouteHandler } from '../../components/BackRouteHandler'; -import { LeaveRoomPrompt } from '../../components/leave-room-prompt'; -import { InviteUserPrompt } from '../../components/invite-user-prompt'; -import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { useStateEvent } from '../../hooks/useStateEvent'; @@ -42,20 +30,8 @@ import { useDmCallVisible } from '../../hooks/useDmCallVisible'; import { useRoomMemberCount } from '../../hooks/useRoomMemberCount'; import { Presence, useUserPresence } from '../../hooks/useUserPresence'; import { useRoomAvatar, useRoomName } from '../../hooks/useRoomMeta'; -import { useRoomNavigate } from '../../hooks/useRoomNavigate'; -import { useRoomUnread } from '../../state/hooks/unread'; -import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents'; -import { usePowerLevelsContext } from '../../hooks/usePowerLevels'; -import { useRoomCreators } from '../../hooks/useRoomCreators'; -import { useRoomPermissions } from '../../hooks/useRoomPermissions'; -import { useSetting } from '../../state/hooks/settings'; import { useSpaceOptionally } from '../../hooks/useSpace'; import { useOpenRoomSettings } from '../../state/hooks/roomSettings'; -import { - getRoomNotificationMode, - getRoomNotificationModeIcon, - useRoomsNotificationPreferencesContext, -} from '../../hooks/useRoomsNotificationPreferences'; import { useLivekitSupport } from '../../hooks/useLivekitSupport'; import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { useSwitchOrStartDmCall } from '../../hooks/useSwitchOrStartDmCall'; @@ -65,327 +41,22 @@ import { useOpenRoomMembersSheet, useRoomMembersSheetState, } from '../../state/hooks/roomMembersSheet'; -import { settingsAtom } from '../../state/settings'; import { callEmbedAtom } from '../../state/callEmbed'; -import { searchModalAtom } from '../../state/searchModal'; -import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import { RoomSettingsPage } from '../../state/roomSettings'; import { StateEvent } from '../../../types/matrix/room'; import { - getCanonicalAliasOrRoomId, getMxIdLocalPart, getMxIdServer, guessDmRoomUserId, - isRoomAlias, mxcUrlToHttp, } from '../../utils/matrix'; -import { copyToClipboard } from '../../utils/dom'; import { stopPropagation } from '../../utils/keyboard'; -import { markAsRead } from '../../utils/notifications'; -import { getMatrixToRoom } from '../../plugins/matrix-to'; -import { getViaServers } from '../../plugins/via-servers'; import { useBotPresets } from '../bots/catalog'; -import { findBotPresetForRoom, isCatalogBotControlRoom } from '../bots/room'; -import { botFailedAtomFamily, botShowChatAtomFamily } from '../bots/botExperienceState'; -import { getBotPath } from '../../pages/pathUtils'; -import { JumpToTime } from './jump-to-time'; +import { isCatalogBotControlRoom } from '../bots/room'; import { RoomPinMenu } from './room-pin-menu'; +import { RoomActionsMenu } from './room-actions'; import * as css from './RoomViewHeaderDm.css'; -// Single bot-aware menu item rendered at the top of RoomMenu when the -// current room is a Vojo bot control DM. Reads `botFailedAtomFamily` to -// label correctly («Retry widget» when a prior load failed, «Show widget» -// otherwise) and clears both atoms on click. -// -// IMPORTANT: this menu surfaces in BOTH `/bots/:botId` (chat-fallback) and -// `/direct/:roomId` (regular DM that happens to be a bot's control room). -// In the second case clearing the atoms alone is invisible — the user is -// not on the route that observes them. We navigate to `/bots/:botId` so -// the BotShell actually mounts. -function BotShowWidgetMenuItem({ - roomId, - botId, - requestClose, -}: { - roomId: string; - botId: string; - requestClose: () => void; -}) { - const { t } = useTranslation(); - const navigate = useNavigate(); - const setShowChat = useSetAtom(botShowChatAtomFamily(roomId)); - const [failed, setFailed] = useAtom(botFailedAtomFamily(roomId)); - const handleClick = () => { - setFailed(false); - setShowChat(false); - navigate(getBotPath(botId)); - requestClose(); - }; - return ( - } - radii="300" - > - - {t(failed ? 'Bots.retry_widget' : 'Bots.show_widget')} - - - ); -} - -type RoomMenuProps = { - room: Room; - callView?: boolean; - // When true the room is a Vojo bot control DM rendered in chat-fallback - // mode. The menu prepends a «Show widget / Retry widget» item so the - // user can return to BotShell without hunting for an overlay button. - // Other items stay standard — bots in chat-fallback should look like - // normal rooms beyond that one affordance. - botControlRoom?: boolean; - onSearch: () => void; - onPin: (cords: RectCords) => void; - requestClose: () => void; -}; -const RoomMenu = forwardRef( - ({ room, callView, botControlRoom, onSearch, onPin, requestClose }, ref) => { - const { t } = useTranslation(); - const mx = useMatrixClient(); - const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); - const unread = useRoomUnread(room.roomId, roomToUnreadAtom); - const powerLevels = usePowerLevelsContext(); - 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 { navigateRoom } = useRoomNavigate(); - const pinnedEvents = useRoomPinnedEvents(room); - const openSettings = useOpenRoomSettings(); - const parentSpace = useSpaceOptionally(); - // Look up the matching bot preset only when this menu IS the bot - // variant. The lookup walks the preset list once; cheap. Returns - // undefined for non-bot rooms — we won't render the menu item. - const bots = useBotPresets(); - const botPreset = botControlRoom ? findBotPresetForRoom(mx, room, bots) : undefined; - - const [invitePrompt, setInvitePrompt] = useState(false); - - const handleMarkAsRead = () => { - markAsRead(mx, room.roomId, hideActivity); - requestClose(); - }; - const handleSearch = () => { - onSearch(); - requestClose(); - }; - const handleOpenPinned: MouseEventHandler = (evt) => { - onPin(evt.currentTarget.getBoundingClientRect()); - requestClose(); - }; - const handleCopyLink = () => { - const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId); - const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room); - copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers)); - requestClose(); - }; - const handleOpenSettings = () => { - openSettings(room.roomId, parentSpace?.roomId); - requestClose(); - }; - const handleInvite = () => { - setInvitePrompt(true); - }; - - return ( - - {invitePrompt && ( - { - setInvitePrompt(false); - requestClose(); - }} - /> - )} - {botControlRoom && botPreset && ( - <> - - - - - - )} - - } - radii="300" - disabled={!unread} - > - - {t('Room.mark_as_read')} - - - - {(handleOpen, opened, changing) => ( - - ) : ( - - ) - } - radii="300" - aria-pressed={opened} - onClick={handleOpen} - > - - {t('Room.notifications')} - - - )} - - - - - } - radii="300" - > - - {t('Room.search')} - - - - {pinnedEvents.length > 0 && ( - - - {pinnedEvents.length} - - - )} - - - } - radii="300" - > - - {t('Room.pinned_messages')} - - - {canInvite && !botControlRoom && ( - } - radii="300" - aria-pressed={invitePrompt} - > - - {t('Room.invite')} - - - )} - } - radii="300" - > - - {t('Room.copy_link')} - - - } - radii="300" - > - - {t('Room.room_settings')} - - - - {(promptJump, setPromptJump) => ( - <> - setPromptJump(true)} - size="300" - after={} - radii="300" - aria-pressed={promptJump} - > - - {t('Room.jump_to_time')} - - - {promptJump && ( - { - setPromptJump(false); - navigateRoom(room.roomId, eventId); - requestClose(); - }} - onCancel={() => setPromptJump(false)} - /> - )} - - )} - - - - - - {(promptLeave, setPromptLeave) => ( - <> - setPromptLeave(true)} - variant="Critical" - fill="None" - size="300" - after={} - radii="300" - aria-pressed={promptLeave} - disabled={callView} - > - - {t('Room.leave_room')} - - - {promptLeave && ( - setPromptLeave(false)} - /> - )} - - )} - - - - ); - } -); - function DmCallButton({ room }: { room: Room }) { const { t } = useTranslation(); const mx = useMatrixClient(); @@ -502,7 +173,6 @@ export function RoomViewHeaderDm({ callView }: { callView?: boolean }) { const [menuAnchor, setMenuAnchor] = useState(); const [pinMenuAnchor, setPinMenuAnchor] = useState(); - const setSearchOpen = useSetAtom(searchModalAtom); const openUserRoomProfile = useOpenUserRoomProfile(); const openRoomMembersSheet = useOpenRoomMembersSheet(); const closeRoomMembersSheet = useCloseRoomMembersSheet(); @@ -511,15 +181,11 @@ export function RoomViewHeaderDm({ callView }: { callView?: boolean }) { const parentSpace = useSpaceOptionally(); const openSettings = useOpenRoomSettings(); + // The ⋮ overflow opens the RoomActionsMenu in an anchored PopOut — same + // chrome on desktop and mobile. const handleOpenMenu: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; - const handleSearch = () => { - // P4 routes in-room search through the global cmd+K modal — there is no - // /direct/{roomId}/search/ page yet (see desired_features.md §26). The - // modal lets the user pick the target room manually. - setSearchOpen(true); - }; const handlePeerProfile: MouseEventHandler = (evt) => { if (!peerUserId) return; openUserRoomProfile( @@ -752,11 +418,10 @@ export function RoomViewHeaderDm({ callView }: { callView?: boolean }) { escapeDeactivates: stopPropagation, }} > - setPinMenuAnchor(cords)} requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/features/room/room-actions/RoomActions.css.ts b/src/app/features/room/room-actions/RoomActions.css.ts new file mode 100644 index 00000000..00b22d65 --- /dev/null +++ b/src/app/features/room/room-actions/RoomActions.css.ts @@ -0,0 +1,99 @@ +import { style } from '@vanilla-extract/css'; +import { color, config, toRem } from 'folds'; + +// =========================================================================== +// Room overflow (⋮) menu — the anchored folds PopOut on both desktop and +// mobile. Flat transparent rows on the popout's deep Vojo input-field tone +// (Surface.Container #0d0e11 — the chat composer fill, set via the Menu's +// `variant="Surface"` in RoomActionsMenu), separated by single 1px hairlines. +// Single accent: violet on Invite, brick on Leave, green on the e2ee chip. +// =========================================================================== + +// Subtle Fleet hairline between row groups, inset from the edges. +export const SectionLine = style({ + height: toRem(1), + flexShrink: 0, + backgroundColor: color.Surface.ContainerLine, + margin: `${toRem(4)} ${config.space.S200}`, +}); + +// The flat action row. Reset + ); +} + +// Chevron for rows that open a sub-surface (notifications, pinned, jump, invite). +export function RowChevron() { + return ; +} + +// Muted trailing word (e.g. the current notification mode beside the row). +export function RowTrailingText({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +// Subtle Fleet hairline between row groups. +export function ActionSectionLine() { + return
; +} + +type BotWidgetActionRowProps = { + roomId: string; + botId: string; + requestClose: () => void; +}; +// Bot-control DM only: jump back to the BotShell widget. Navigates to +// /bots/:botId so the shell mounts even from a plain /direct route that +// happens to be the bot's control room. +export function BotWidgetActionRow({ roomId, botId, requestClose }: BotWidgetActionRowProps) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const setShowChat = useSetAtom(botShowChatAtomFamily(roomId)); + const [failed, setFailed] = useAtom(botFailedAtomFamily(roomId)); + + const handleClick = () => { + setFailed(false); + setShowChat(false); + navigate(getBotPath(botId)); + requestClose(); + }; + + return ( + } + /> + ); +} + +// Labels the Notifications row's current mode. +export function useNotificationModeLabel(mode: RoomNotificationMode): string { + const { t } = useTranslation(); + const labels: Record = { + [RoomNotificationMode.Unset]: t('Inbox.notif_default'), + [RoomNotificationMode.AllMessages]: t('Inbox.notif_all_messages'), + [RoomNotificationMode.SpecialMessages]: t('Inbox.notif_mentions_keywords'), + [RoomNotificationMode.Mute]: t('Inbox.notif_mute'), + }; + return labels[mode]; +} diff --git a/src/app/features/room/room-actions/RoomActionsMenu.tsx b/src/app/features/room/room-actions/RoomActionsMenu.tsx new file mode 100644 index 00000000..a78e1697 --- /dev/null +++ b/src/app/features/room/room-actions/RoomActionsMenu.tsx @@ -0,0 +1,227 @@ +import React, { MouseEventHandler, forwardRef, useState } from 'react'; +import { Badge, Icons, Menu, RectCords, Spinner, Text } from 'folds'; +import { useTranslation } from 'react-i18next'; +import { Room } from 'matrix-js-sdk'; +import { useMatrixClient } from '../../../hooks/useMatrixClient'; +import { useSetting } from '../../../state/hooks/settings'; +import { settingsAtom } from '../../../state/settings'; +import { useRoomUnread } from '../../../state/hooks/unread'; +import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; +import { usePowerLevelsContext } from '../../../hooks/usePowerLevels'; +import { useRoomCreators } from '../../../hooks/useRoomCreators'; +import { useRoomPermissions } from '../../../hooks/useRoomPermissions'; +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'; +import { InviteUserPrompt } from '../../../components/invite-user-prompt'; +import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix'; +import { getMatrixToRoom } from '../../../plugins/matrix-to'; +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, + RowTrailingText, + useNotificationModeLabel, +} from './RoomActions'; +import * as css from './RoomActions.css'; + +type RoomActionsMenuProps = { + room: Room; + callView?: boolean; + botControlRoom?: boolean; + onPin: (cords: RectCords) => void; + requestClose: () => void; +}; + +// 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. +export const RoomActionsMenu = forwardRef( + ({ room, callView, botControlRoom, onPin, requestClose }, ref) => { + const { t } = useTranslation(); + const mx = useMatrixClient(); + const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); + const unread = useRoomUnread(room.roomId, roomToUnreadAtom); + const powerLevels = usePowerLevelsContext(); + 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 notificationModeLabel = useNotificationModeLabel(notificationMode); + const pinnedEvents = useRoomPinnedEvents(room); + const { navigateRoom } = useRoomNavigate(); + const openSettings = useOpenRoomSettings(); + const parentSpace = useSpaceOptionally(); + + const bots = useBotPresets(); + const botPreset = botControlRoom ? findBotPresetForRoom(mx, room, bots) : undefined; + + const [invitePrompt, setInvitePrompt] = useState(false); + const [promptJump, setPromptJump] = useState(false); + const [promptLeave, setPromptLeave] = useState(false); + + const handleMarkAsRead = () => { + markAsRead(mx, room.roomId, hideActivity); + requestClose(); + }; + const handleOpenPinned: MouseEventHandler = (evt) => { + onPin(evt.currentTarget.getBoundingClientRect()); + requestClose(); + }; + const handleCopyLink = () => { + const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId); + const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room); + copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers)); + requestClose(); + }; + const handleOpenSettings = () => { + openSettings(room.roomId, parentSpace?.roomId); + requestClose(); + }; + + return ( + + {invitePrompt && ( + { + setInvitePrompt(false); + requestClose(); + }} + /> + )} + {promptJump && ( + { + setPromptJump(false); + navigateRoom(room.roomId, eventId); + requestClose(); + }} + onCancel={() => setPromptJump(false)} + /> + )} + {promptLeave && ( + setPromptLeave(false)} + /> + )} + +
+ {botControlRoom && botPreset && ( + + )} + + + {(handleOpen, opened, changing) => ( + + ) : ( + <> + {notificationModeLabel} + + + ) + } + /> + )} + + + {pinnedEvents.length > 0 && ( + + + {pinnedEvents.length} + + + )} + + + } + /> + + setPromptJump(true)} + ariaPressed={promptJump} + ariaHasPopup + trailing={} + /> + +
+ + {canInvite && !botControlRoom && ( + <> + +
+ setInvitePrompt(true)} + accent="primary" + ariaPressed={invitePrompt} + /> +
+ + )} + + +
+ setPromptLeave(true)} + accent="critical" + disabled={callView} + ariaPressed={promptLeave} + /> +
+
+ ); + } +); diff --git a/src/app/features/room/room-actions/index.ts b/src/app/features/room/room-actions/index.ts new file mode 100644 index 00000000..27451b53 --- /dev/null +++ b/src/app/features/room/room-actions/index.ts @@ -0,0 +1,2 @@ +export * from './RoomActions'; +export * from './RoomActionsMenu';