fix(mobile): member list no longer opens on top of every room on phones (#175)

Room.tsx showed the MembersDrawer on phone widths whenever the persisted
desktop preference isPeopleDrawer was true — which is the default — so a
new mobile session opened every room behind a full-screen member list.
Mobile now uses a transient mobileMembersPanelAtom (header ⋮ → Members
toggles it, the drawer's X closes it, leaving the room resets it); the
desktop drawer keeps using the setting. Verified with Playwright at 400px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-18 17:45:43 -04:00
co-authored by Claude Opus 5
parent 9d84f9153a
commit 4c671fbf3a
4 changed files with 32 additions and 8 deletions
+8 -1
View File
@@ -31,6 +31,8 @@ import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';
import { useVirtualizer } from '@tanstack/react-virtual';
import classNames from 'classnames';
import { useSetAtom } from 'jotai';
import { mobileMembersPanelAtom } from '../../state/mobileMembersPanel';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { MobileTouchTarget } from '../../styles/mobile.css';
import { Membership } from '../../../types/matrix/room';
@@ -79,6 +81,8 @@ type MemberDrawerHeaderProps = {
};
function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
const setMobileMembers = useSetAtom(mobileMembersPanelAtom);
const screenSize = useScreenSizeContext();
return (
<Header className={css.MembersDrawerHeader} variant="Background" size="600">
@@ -104,7 +108,10 @@ function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
ref={triggerRef}
variant="Background"
aria-label="Close member list"
onClick={() => setPeopleDrawer(false)}
onClick={() => {
if (screenSize === ScreenSize.Mobile) setMobileMembers(false);
else setPeopleDrawer(false);
}}
>
<Icon src={Icons.Cross} />
</IconButton>
+11 -3
View File
@@ -2,13 +2,14 @@ import React, { useCallback, useEffect, useRef } from 'react';
import { Box, Line } from 'folds';
import { useParams } from 'react-router-dom';
import { isKeyHotkey } from 'is-hotkey';
import { useAtomValue, useSetAtom } from 'jotai';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { RoomView } from './RoomView';
import { MembersDrawer } from './MembersDrawer';
import { MediaGallery } from './MediaGallery';
import { mediaGalleryAtom } from '../../state/mediaGallery';
import { WidgetsPanel } from './widgets/WidgetsPanel';
import { widgetsPanelAtom } from '../../state/widgetsPanel';
import { mobileMembersPanelAtom } from '../../state/mobileMembersPanel';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
@@ -46,6 +47,7 @@ export function Room() {
const galleryOpen = useAtomValue(mediaGalleryAtom);
const setGalleryOpen = useSetAtom(mediaGalleryAtom);
const widgetsOpen = useAtomValue(widgetsPanelAtom);
const [mobileMembersOpen, setMobileMembersOpen] = useAtom(mobileMembersPanelAtom);
const setWidgetsOpen = useSetAtom(widgetsPanelAtom);
const threadsListOpen = useAtomValue(threadsListAtom);
const setThreadsListOpen = useSetAtom(threadsListAtom);
@@ -146,10 +148,16 @@ export function Room() {
threadsListOpen &&
!activeThreadId &&
(isDesktop || (!galleryOpen && !widgetsOpen));
// Desktop: the persisted drawer preference. Mobile: a transient panel the
// user opened from the header menu (never the desktop default).
const showMembers =
!callView &&
isDrawer &&
(isDesktop || (!activeThreadId && !galleryOpen && !widgetsOpen && !threadsListOpen));
(isDesktop
? isDrawer
: mobileMembersOpen && !activeThreadId && !galleryOpen && !widgetsOpen && !threadsListOpen);
// Leaving a room on a phone closes its member panel.
useEffect(() => () => setMobileMembersOpen(false), [room.roomId, setMobileMembersOpen]);
return (
<PowerLevelsContextProvider value={powerLevels}>
+5 -4
View File
@@ -75,6 +75,7 @@ import { useLivekitSupport } from '../../hooks/useLivekitSupport';
import { webRTCSupported } from '../../utils/rtc';
import { mediaGalleryAtom } from '../../state/mediaGallery';
import { widgetsPanelAtom } from '../../state/widgetsPanel';
import { mobileMembersPanelAtom } from '../../state/mobileMembersPanel';
import { threadsListAtom } from '../../state/threadsList';
import { usePendingKnocks } from '../../hooks/usePendingKnocks';
import { bookmarksPanelAtom } from '../../state/bookmarksPanel';
@@ -105,7 +106,7 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(
const [invitePrompt, setInvitePrompt] = useState(false);
const [reportRoomOpen, setReportRoomOpen] = useState(false);
const [bookmarksOpen, setBookmarksOpen] = useAtom(bookmarksPanelAtom);
const [peopleDrawer, setPeopleDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const [mobileMembers, setMobileMembers] = useAtom(mobileMembersPanelAtom);
const handleMarkAsRead = () => {
markAsRead(mx, room.roomId, hideActivity);
@@ -196,13 +197,13 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(
{screenSize === ScreenSize.Mobile && (
<MenuItem
onClick={() => {
setPeopleDrawer(!peopleDrawer);
setMobileMembers(!mobileMembers);
requestClose();
}}
size="300"
after={<Icon size="100" src={Icons.User} filled={peopleDrawer} />}
after={<Icon size="100" src={Icons.User} filled={mobileMembers} />}
radii="300"
aria-pressed={peopleDrawer}
aria-pressed={mobileMembers}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Members
+8
View File
@@ -0,0 +1,8 @@
import { atom } from 'jotai';
// Whether the member list is open on a phone-width layout, where it is a
// full-screen panel rather than a side drawer. Deliberately NOT the persisted
// `isPeopleDrawer` setting: that one remembers the desktop drawer (default
// on), and reusing it on mobile made every room open on top of the member
// list until the user found the X (Gitea #175).
export const mobileMembersPanelAtom = atom<boolean>(false);