Refactor SpaceButton to be more reusable and add context menu to Home button

This commit is contained in:
Michael Telatynski 2021-07-28 17:40:33 +01:00
parent b3a28bde89
commit 67ef263940
6 changed files with 444 additions and 398 deletions

View file

@ -14,107 +14,35 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import React, { ComponentProps, Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu";
import { SpaceItem } from "./SpaceTreeLevel";
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import SpaceStore, {
HOME_SPACE,
UPDATE_HOME_BEHAVIOUR,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/SpaceStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import NotificationBadge from "../rooms/NotificationBadge";
import {
RovingAccessibleButton,
RovingAccessibleTooltipButton,
RovingTabIndexProvider,
} from "../../../accessibility/RovingTabIndex";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { Key } from "../../../Keyboard";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationState } from "../../../stores/notifications/NotificationState";
interface IButtonProps {
space?: Room;
className?: string;
selected?: boolean;
tooltip?: string;
notificationState?: NotificationState;
isNarrow?: boolean;
onClick(): void;
}
const SpaceButton: React.FC<IButtonProps> = ({
space,
className,
selected,
onClick,
tooltip,
notificationState,
isNarrow,
children,
}) => {
const classes = classNames("mx_SpaceButton", className, {
mx_SpaceButton_active: selected,
mx_SpaceButton_narrow: isNarrow,
});
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
if (space) {
avatar = <RoomAvatar width={32} height={32} room={space} />;
}
let notifBadge;
if (notificationState) {
notifBadge = <div className="mx_SpacePanel_badgeContainer">
<NotificationBadge
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space)}
forceCount={false}
notification={notificationState}
/>
</div>;
}
let button;
if (isNarrow) {
button = (
<RovingAccessibleTooltipButton className={classes} title={tooltip} onClick={onClick} role="treeitem">
<div className="mx_SpaceButton_selectionWrapper">
{ avatar }
{ notifBadge }
{ children }
</div>
</RovingAccessibleTooltipButton>
);
} else {
button = (
<RovingAccessibleButton className={classes} onClick={onClick} role="treeitem">
<div className="mx_SpaceButton_selectionWrapper">
{ avatar }
<span className="mx_SpaceButton_name">{ tooltip }</span>
{ notifBadge }
{ children }
</div>
</RovingAccessibleButton>
);
}
return <li className={classNames({
"mx_SpaceItem": true,
"collapsed": isNarrow,
})}>
{ button }
</li>;
};
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import IconizedContextMenu, {
IconizedContextMenuCheckbox,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
const useSpaces = (): [Room[], Room[], Room | null] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
@ -135,30 +63,108 @@ interface IInnerSpacePanelProps {
setPanelCollapsed: Dispatch<SetStateAction<boolean>>;
}
const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof SpaceContextMenu>) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
return <IconizedContextMenu
{...props}
onFinished={onFinished}
className="mx_SpacePanel_contextMenu"
compact
>
<div className="mx_SpacePanel_contextMenu_header">
{ _t("Home") }
</div>
<IconizedContextMenuOptionList first>
<IconizedContextMenuCheckbox
iconClassName="mx_SpacePanel_iconSettings"
label={_t("Show all rooms in home")}
active={allRoomsInHome}
onClick={() => {
SettingsStore.setValue("Spaces.all_rooms_in_home", null, SettingLevel.ACCOUNT, !allRoomsInHome);
}}
/>
</IconizedContextMenuOptionList>
</IconizedContextMenu>;
};
interface IHomeButtonProps {
selected: boolean;
isPanelCollapsed: boolean;
}
const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
return <li className={classNames("mx_SpaceItem", {
"collapsed": isPanelCollapsed,
})}>
<SpaceButton
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={selected}
label={allRoomsInHome ? _t("All rooms") : _t("Home")}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(HOME_SPACE)}
isNarrow={isPanelCollapsed}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>
</li>;
};
const CreateSpaceButton = ({
isPanelCollapsed,
setPanelCollapsed,
}: Pick<IInnerSpacePanelProps, "isPanelCollapsed" | "setPanelCollapsed">) => {
// We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<void>();
useEffect(() => {
if (!isPanelCollapsed && menuDisplayed) {
closeMenu();
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
let contextMenu = null;
if (menuDisplayed) {
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
}
const onNewClick = menuDisplayed ? closeMenu : () => {
if (!isPanelCollapsed) setPanelCollapsed(true);
openMenu();
};
return <li className={classNames("mx_SpaceItem", {
"collapsed": isPanelCollapsed,
})}>
<SpaceButton
className={classNames("mx_SpaceButton_new", {
mx_SpaceButton_newCancel: menuDisplayed,
})}
label={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={onNewClick}
isNarrow={isPanelCollapsed}
/>
{ contextMenu }
</li>;
};
// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCollapsed, setPanelCollapsed }) => {
const [invites, spaces, activeSpace] = useSpaces();
const activeSpaces = activeSpace ? [activeSpace] : [];
let homeTooltip: string;
let homeNotificationState: NotificationState;
if (SpaceStore.instance.allRoomsInHome) {
homeTooltip = _t("All rooms");
homeNotificationState = RoomNotificationStateStore.instance.globalState;
} else {
homeTooltip = _t("Home");
homeNotificationState = SpaceStore.instance.getNotificationState(HOME_SPACE);
}
return <div className="mx_SpaceTreeLevel">
<SpaceButton
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={!activeSpace}
tooltip={homeTooltip}
notificationState={homeNotificationState}
isNarrow={isPanelCollapsed}
/>
<HomeButton selected={!activeSpace} isPanelCollapsed={isPanelCollapsed} />
{ invites.map(s => (
<SpaceItem
key={s.roomId}
@ -188,26 +194,13 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
</Draggable>
)) }
{ children }
<CreateSpaceButton isPanelCollapsed={isPanelCollapsed} setPanelCollapsed={setPanelCollapsed} />
</div>;
});
const SpacePanel = () => {
// We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<void>();
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
useEffect(() => {
if (!isPanelCollapsed && menuDisplayed) {
closeMenu();
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
let contextMenu = null;
if (menuDisplayed) {
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
}
const onKeyDown = (ev: React.KeyboardEvent) => {
let handled = true;
@ -269,11 +262,6 @@ const SpacePanel = () => {
}
};
const onNewClick = menuDisplayed ? closeMenu : () => {
if (!isPanelCollapsed) setPanelCollapsed(true);
openMenu();
};
return (
<DragDropContext onDragEnd={result => {
if (!result.destination) return; // dropped outside the list
@ -301,15 +289,6 @@ const SpacePanel = () => {
>
{ provided.placeholder }
</InnerSpacePanel>
<SpaceButton
className={classNames("mx_SpaceButton_new", {
mx_SpaceButton_newCancel: menuDisplayed,
})}
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={onNewClick}
isNarrow={isPanelCollapsed}
/>
</AutoHideScrollbar>
) }
</Droppable>
@ -318,7 +297,6 @@ const SpacePanel = () => {
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
{ contextMenu }
</ul>
) }
</RovingTabIndexProvider>

View file

@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef, InputHTMLAttributes, LegacyRef } from "react";
import React, {
createRef,
MouseEvent,
InputHTMLAttributes,
LegacyRef,
ComponentProps,
ComponentType,
} from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
@ -23,31 +30,104 @@ import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
import { _t } from "../../../languageHandler";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { toRightOf } from "../../structures/ContextMenu";
import {
shouldShowSpaceSettings,
showAddExistingRooms,
showCreateNewRoom,
showSpaceInvite,
showSpaceSettings,
} from "../../../utils/space";
import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import RoomViewStore from "../../../stores/RoomViewStore";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { EventType } from "matrix-js-sdk/src/@types/event";
import AccessibleButton from "../elements/AccessibleButton";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
import { NotificationState } from "../../../stores/notifications/NotificationState";
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
interface IButtonProps extends Omit<ComponentProps<typeof RovingAccessibleTooltipButton>, "title"> {
space?: Room;
className?: string;
selected?: boolean;
label: string;
contextMenuTooltip?: string;
notificationState?: NotificationState;
isNarrow?: boolean;
avatarSize?: number;
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
onClick(ev: MouseEvent): void;
}
export const SpaceButton: React.FC<IButtonProps> = ({
space,
className,
selected,
onClick,
label,
contextMenuTooltip,
notificationState,
avatarSize,
isNarrow,
children,
ContextMenuComponent,
...props
}) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>();
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
if (space) {
avatar = <RoomAvatar width={avatarSize} height={avatarSize} room={space} />;
}
let notifBadge;
if (notificationState) {
notifBadge = <div className="mx_SpacePanel_badgeContainer">
<NotificationBadge
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space || null)}
forceCount={false}
notification={notificationState}
/>
</div>;
}
let contextMenu: JSX.Element;
if (menuDisplayed && ContextMenuComponent) {
contextMenu = <ContextMenuComponent
{...toRightOf(handle.current?.getBoundingClientRect(), 0)}
space={space}
onFinished={closeMenu}
/>;
}
return (
<RovingAccessibleTooltipButton
{...props}
className={classNames("mx_SpaceButton", className, {
mx_SpaceButton_active: selected,
mx_SpaceButton_hasMenuOpen: menuDisplayed,
mx_SpaceButton_narrow: isNarrow,
})}
title={label}
onClick={onClick}
onContextMenu={openMenu}
forceHide={!isNarrow || menuDisplayed}
role="treeitem"
inputRef={handle}
>
{ children }
<div className="mx_SpaceButton_selectionWrapper">
{ avatar }
{ !isNarrow && <span className="mx_SpaceButton_name">{ label }</span> }
{ notifBadge }
{ ContextMenuComponent && <ContextMenuTooltipButton
className="mx_SpaceButton_menuButton"
onClick={openMenu}
title={contextMenuTooltip}
isExpanded={menuDisplayed}
/> }
{ contextMenu }
</div>
</RovingAccessibleTooltipButton>
);
};
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
space?: Room;
@ -61,7 +141,6 @@ interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
interface IItemState {
collapsed: boolean;
contextMenuPosition: Pick<DOMRect, "right" | "top" | "height">;
childSpaces: Room[];
}
@ -81,7 +160,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
this.state = {
collapsed: collapsed,
contextMenuPosition: null,
childSpaces: this.childSpaces,
};
@ -124,19 +202,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
evt.stopPropagation();
};
private onContextMenu = (ev: React.MouseEvent) => {
if (this.props.space.getMyMembership() !== "join") return;
ev.preventDefault();
ev.stopPropagation();
this.setState({
contextMenuPosition: {
right: ev.clientX,
top: ev.clientY,
height: 0,
},
});
};
private onKeyDown = (ev: React.KeyboardEvent) => {
let handled = true;
const action = getKeyBindingsManager().getRoomListAction(ev);
@ -180,188 +245,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
SpaceStore.instance.setActiveSpace(this.props.space);
};
private onMenuOpenClick = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
const target = ev.target as HTMLButtonElement;
this.setState({ contextMenuPosition: target.getBoundingClientRect() });
};
private onMenuClose = () => {
this.setState({ contextMenuPosition: null });
};
private onInviteClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
showSpaceInvite(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onSettingsClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
showSpaceSettings(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onLeaveClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
defaultDispatcher.dispatch({
action: "leave_room",
room_id: this.props.space.roomId,
});
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onNewRoomClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
showCreateNewRoom(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onAddExistingRoomClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
showAddExistingRooms(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onMembersClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
if (!RoomViewStore.getRoomId()) {
defaultDispatcher.dispatch({
action: "view_room",
room_id: this.props.space.roomId,
}, true);
}
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.SpaceMemberList,
refireParams: { space: this.props.space },
});
this.setState({ contextMenuPosition: null }); // also close the menu
};
private onExploreRoomsClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
defaultDispatcher.dispatch({
action: "view_room",
room_id: this.props.space.roomId,
});
this.setState({ contextMenuPosition: null }); // also close the menu
};
private renderContextMenu(): React.ReactElement {
if (this.props.space.getMyMembership() !== "join") return null;
let contextMenu = null;
if (this.state.contextMenuPosition) {
const userId = this.context.getUserId();
let inviteOption;
if (this.props.space.getJoinRule() === "public" || this.props.space.canInvite(userId)) {
inviteOption = (
<IconizedContextMenuOption
className="mx_SpacePanel_contextMenu_inviteButton"
iconClassName="mx_SpacePanel_iconInvite"
label={_t("Invite people")}
onClick={this.onInviteClick}
/>
);
}
let settingsOption;
let leaveSection;
if (shouldShowSpaceSettings(this.props.space)) {
settingsOption = (
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconSettings"
label={_t("Settings")}
onClick={this.onSettingsClick}
/>
);
} else {
leaveSection = <IconizedContextMenuOptionList red first>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconLeave"
label={_t("Leave space")}
onClick={this.onLeaveClick}
/>
</IconizedContextMenuOptionList>;
}
const canAddRooms = this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
let newRoomSection;
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
newRoomSection = <IconizedContextMenuOptionList first>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Create new room")}
onClick={this.onNewRoomClick}
/>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconHash"
label={_t("Add existing room")}
onClick={this.onAddExistingRoomClick}
/>
</IconizedContextMenuOptionList>;
}
contextMenu = <IconizedContextMenu
{...toRightOf(this.state.contextMenuPosition, 0)}
onFinished={this.onMenuClose}
className="mx_SpacePanel_contextMenu"
compact
>
<div className="mx_SpacePanel_contextMenu_header">
{ this.props.space.name }
</div>
<IconizedContextMenuOptionList first>
{ inviteOption }
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconMembers"
label={_t("Members")}
onClick={this.onMembersClick}
/>
{ settingsOption }
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconExplore"
label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
onClick={this.onExploreRoomsClick}
/>
</IconizedContextMenuOptionList>
{ newRoomSection }
{ leaveSection }
</IconizedContextMenu>;
}
return (
<React.Fragment>
<ContextMenuTooltipButton
className="mx_SpaceButton_menuButton"
onClick={this.onMenuOpenClick}
title={_t("Space options")}
isExpanded={!!this.state.contextMenuPosition}
/>
{ contextMenu }
</React.Fragment>
);
}
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
@ -369,7 +252,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const collapsed = this.isCollapsed;
const isActive = activeSpaces.includes(space);
const itemClasses = classNames(this.props.className, {
"mx_SpaceItem": true,
"mx_SpaceItem_narrow": isPanelCollapsed,
@ -378,12 +260,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
});
const isInvite = space.getMyMembership() === "invite";
const classes = classNames("mx_SpaceButton", {
mx_SpaceButton_active: isActive,
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
mx_SpaceButton_narrow: isPanelCollapsed,
mx_SpaceButton_invite: isInvite,
});
const notificationState = isInvite
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
: SpaceStore.instance.getNotificationState(space.roomId);
@ -398,19 +275,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
/>;
}
let notifBadge;
if (notificationState) {
notifBadge = <div className="mx_SpacePanel_badgeContainer">
<NotificationBadge
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space)}
forceCount={false}
notification={notificationState}
/>
</div>;
}
const avatarSize = isNested ? 24 : 32;
const toggleCollapseButton = this.state.childSpaces?.length ?
<AccessibleButton
className="mx_SpaceButton_toggleCollapse"
@ -421,25 +285,23 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
return (
<li {...otherProps} className={itemClasses} ref={innerRef}>
<RovingAccessibleTooltipButton
className={classes}
title={space.name}
<SpaceButton
space={space}
className={isInvite ? "mx_SpaceButton_invite" : undefined}
selected={activeSpaces.includes(space)}
label={space.name}
contextMenuTooltip={_t("Space options")}
notificationState={notificationState}
isNarrow={isPanelCollapsed}
avatarSize={isNested ? 24 : 32}
onClick={this.onClick}
onContextMenu={this.onContextMenu}
forceHide={!isPanelCollapsed || !!this.state.contextMenuPosition}
role="treeitem"
aria-expanded={!collapsed}
inputRef={this.buttonRef}
onKeyDown={this.onKeyDown}
aria-expanded={!collapsed}
ContextMenuComponent={this.props.space.getMyMembership() === "join"
? SpaceContextMenu : undefined}
>
{ toggleCollapseButton }
<div className="mx_SpaceButton_selectionWrapper">
<RoomAvatar width={avatarSize} height={avatarSize} room={space} />
{ !isPanelCollapsed && <span className="mx_SpaceButton_name">{ space.name }</span> }
{ notifBadge }
{ this.renderContextMenu() }
</div>
</RovingAccessibleTooltipButton>
</SpaceButton>
{ childItems }
</li>