Started on adding the context menu for rooms in the nav on mobile

This commit is contained in:
Gigiaj 2025-06-17 03:33:15 -05:00
parent c9fd33b2dc
commit fb73ebbb3b

View file

@ -19,6 +19,8 @@ import {
} 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';
import { useParams } from 'react-router-dom';
import { useLongPress } from 'use-long-press';
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';
@ -49,6 +51,10 @@ import {
RoomNotificationMode, RoomNotificationMode,
} from '../../hooks/useRoomsNotificationPreferences'; } from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
import { useCallState } from '../../pages/client/call/CallProvider';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BottomSheetMenu } from '../room/MessageOptionsMenu';
type RoomNavItemMenuProps = { type RoomNavItemMenuProps = {
room: Room; room: Room;
@ -65,6 +71,8 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const openRoomSettings = useOpenRoomSettings(); const openRoomSettings = useOpenRoomSettings();
const space = useSpaceOptionally(); const space = useSpaceOptionally();
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(mx, room.roomId, hideActivity); markAsRead(mx, room.roomId, hideActivity);
@ -89,7 +97,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
}; };
return ( return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}> <Menu ref={ref} style={!isMobile ? { maxWidth: toRem(160), width: '100vw' } : {}}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}> <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem <MenuItem
onClick={handleMarkAsRead} onClick={handleMarkAsRead}
@ -220,9 +228,30 @@ export function RoomNavItem({
const typingMember = useRoomTypingMember(room.roomId).filter( const typingMember = useRoomTypingMember(room.roomId).filter(
(receipt) => receipt.userId !== mx.getUserId() (receipt) => receipt.userId !== mx.getUserId()
); );
const { navigateRoom } = useRoomNavigate();
const { roomIdOrAlias: viewedRoomId } = useParams();
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
const longPressBinder = useLongPress(
() => {
if (isMobile) {
setMobileSheetOpen(true);
}
},
{
threshold: 400,
cancelOnMovement: true,
}
);
const handleContextMenu: MouseEventHandler<HTMLElement> = (evt) => { const handleContextMenu: MouseEventHandler<HTMLElement> = (evt) => {
evt.preventDefault(); evt.preventDefault();
if (isMobile) {
// return;
}
setMenuAnchor({ setMenuAnchor({
x: evt.clientX, x: evt.clientX,
y: evt.clientY, y: evt.clientY,
@ -235,21 +264,58 @@ export function RoomNavItem({
setMenuAnchor(evt.currentTarget.getBoundingClientRect()); setMenuAnchor(evt.currentTarget.getBoundingClientRect());
}; };
const optionsVisible = hover || !!menuAnchor; const handleNavItemClick: MouseEventHandler<HTMLElement> = (evt) => {
const target = evt.target as HTMLElement;
const chatButton = (evt.currentTarget as HTMLElement).querySelector(
'[data-testid="chat-button"]'
);
if (chatButton && chatButton.contains(target)) {
return;
}
if (room.isCallRoom()) {
if (!isMobile) {
if (activeCallRoomId !== room.roomId) {
if (mx.getRoom(viewedRoomId)?.isCallRoom()) {
navigateRoom(room.roomId);
}
hangUp(room.roomId);
setActiveCallRoomId(room.roomId);
} else {
navigateRoom(room.roomId);
}
} else {
evt.stopPropagation();
if (isChatOpen) toggleChat();
setViewedCallRoomId(room.roomId);
navigateRoom(room.roomId);
}
} else {
navigateRoom(room.roomId);
}
};
const handleChatButtonClick = (evt: MouseEvent<HTMLButtonElement>) => {
evt.stopPropagation();
if (!isChatOpen) toggleChat();
setViewedCallRoomId(room.roomId);
};
const optionsVisible = !isMobile && (hover || !!menuAnchor);
return ( return (
<NavItem <>
variant="Background" <NavItem
radii="400" variant="Background"
highlight={unread !== undefined} radii="400"
aria-selected={selected} highlight={unread !== undefined}
data-hover={!!menuAnchor} aria-selected={selected}
onContextMenu={handleContextMenu} data-hover={!!menuAnchor}
{...hoverProps} onContextMenu={handleContextMenu}
{...focusWithinProps} {...hoverProps}
> {...focusWithinProps}
<NavLink to={linkPath}> {...longPressBinder()}
<NavItemContent> >
<NavItemContent onClick={handleNavItemClick}>
<Box as="span" grow="Yes" alignItems="Center" gap="200"> <Box as="span" grow="Yes" alignItems="Center" gap="200">
<Avatar size="200" radii="400"> <Avatar size="200" radii="400">
{showAvatar ? ( {showAvatar ? (
@ -273,6 +339,7 @@ export function RoomNavItem({
filled={selected} filled={selected}
size="100" size="100"
joinRule={room.getJoinRule()} joinRule={room.getJoinRule()}
call={room.isCallRoom()}
/> />
)} )}
</Avatar> </Avatar>
@ -296,48 +363,85 @@ export function RoomNavItem({
)} )}
</Box> </Box>
</NavItemContent> </NavItemContent>
</NavLink> {optionsVisible && (
{optionsVisible && ( <NavItemOptions>
<NavItemOptions> <PopOut
<PopOut anchor={menuAnchor}
anchor={menuAnchor} offset={menuAnchor?.width === 0 ? 0 : undefined}
offset={menuAnchor?.width === 0 ? 0 : undefined} alignOffset={menuAnchor?.width === 0 ? 0 : -5}
alignOffset={menuAnchor?.width === 0 ? 0 : -5} position="Bottom"
position="Bottom" align={menuAnchor?.width === 0 ? 'Start' : 'End'}
align={menuAnchor?.width === 0 ? 'Start' : 'End'} content={
content={ <FocusTrap
<FocusTrap focusTrapOptions={{
focusTrapOptions={{ initialFocus: false,
initialFocus: false, returnFocusOnDeactivate: false,
returnFocusOnDeactivate: false, onDeactivate: () => setMenuAnchor(undefined),
onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true,
clickOutsideDeactivates: true, isKeyForward: (evt) => evt.key === 'ArrowDown',
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt) => evt.key === 'ArrowUp',
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation,
escapeDeactivates: stopPropagation, }}
}} >
> <RoomNavItemMenu
<RoomNavItemMenu room={room}
room={room} requestClose={() => setMenuAnchor(undefined)}
requestClose={() => setMenuAnchor(undefined)} notificationMode={notificationMode}
notificationMode={notificationMode} />
/> </FocusTrap>
</FocusTrap> }
}
>
<IconButton
onClick={handleOpenMenu}
aria-pressed={!!menuAnchor}
variant="Background"
fill="None"
size="300"
radii="300"
> >
<Icon size="50" src={Icons.VerticalDots} /> {room.isCallRoom() && (
</IconButton> <TooltipProvider
</PopOut> position="Bottom"
</NavItemOptions> offset={4}
tooltip={
<Tooltip>
<Text>Open chat</Text>
</Tooltip>
}
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
data-testid="chat-button"
onClick={handleChatButtonClick}
aria-pressed={isChatOpen}
variant="Background"
fill="None"
size="300"
radii="300"
>
<NavLink to={linkPath}>
<Icon size="50" src={Icons.Message} />
</NavLink>
</IconButton>
)}
</TooltipProvider>
)}
<IconButton
onClick={handleOpenMenu}
aria-pressed={!!menuAnchor}
variant="Background"
fill="None"
size="300"
radii="300"
>
<Icon size="50" src={Icons.VerticalDots} />
</IconButton>
</PopOut>
</NavItemOptions>
)}
</NavItem>
{isMobile && (
<BottomSheetMenu isOpen={isMobileSheetOpen} onClose={() => setMobileSheetOpen(false)}>
<RoomNavItemMenu
room={room}
requestClose={() => setMobileSheetOpen(false)}
notificationMode={notificationMode}
/>
</BottomSheetMenu>
)} )}
</NavItem> </>
); );
} }