Merge pull request #4882 from matrix-org/t3chguy/room-list/6
First step towards a11y in the new room listpull/21833/head
						commit
						599cc9c9ed
					
				| 
						 | 
				
			
			@ -278,6 +278,7 @@ limitations under the License.
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    &.mx_RoomSublist2_hasMenuOpen,
 | 
			
		||||
    &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within,
 | 
			
		||||
    &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover {
 | 
			
		||||
        .mx_RoomSublist2_menuButton {
 | 
			
		||||
            visibility: visible;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,10 @@ limitations under the License.
 | 
			
		|||
    // The tile is also a flexbox row itself
 | 
			
		||||
    display: flex;
 | 
			
		||||
 | 
			
		||||
    &.mx_RoomTile2_selected, &:hover, &.mx_RoomTile2_hasMenuOpen {
 | 
			
		||||
    &.mx_RoomTile2_selected,
 | 
			
		||||
    &:hover,
 | 
			
		||||
    &:focus-within,
 | 
			
		||||
    &.mx_RoomTile2_hasMenuOpen {
 | 
			
		||||
        background-color: $roomtile2-selected-bg-color;
 | 
			
		||||
        border-radius: 32px;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +135,9 @@ limitations under the License.
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(.mx_RoomTile2_minimized) {
 | 
			
		||||
        &:hover, &.mx_RoomTile2_hasMenuOpen {
 | 
			
		||||
        &:hover,
 | 
			
		||||
        &:focus-within,
 | 
			
		||||
        &.mx_RoomTile2_hasMenuOpen {
 | 
			
		||||
            // Hide the badge container on hover because it'll be a menu button
 | 
			
		||||
            .mx_RoomTile2_badgeContainer {
 | 
			
		||||
                width: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,8 @@ import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
 | 
			
		|||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
 | 
			
		||||
import ResizeNotifier from "../../utils/ResizeNotifier";
 | 
			
		||||
import SettingsStore from "../../settings/SettingsStore";
 | 
			
		||||
import RoomListStore, { RoomListStore2, LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2";
 | 
			
		||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2";
 | 
			
		||||
import {Key} from "../../Keyboard";
 | 
			
		||||
 | 
			
		||||
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
 | 
			
		||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +58,7 @@ interface IState {
 | 
			
		|||
export default class LeftPanel2 extends React.Component<IProps, IState> {
 | 
			
		||||
    private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
 | 
			
		||||
    private tagPanelWatcherRef: string;
 | 
			
		||||
    private focusedElement = null;
 | 
			
		||||
 | 
			
		||||
    // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +152,69 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
 | 
			
		|||
        this.handleStickyHeaders(this.listContainerRef.current);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onFocus = (ev: React.FocusEvent) => {
 | 
			
		||||
        this.focusedElement = ev.target;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onBlur = () => {
 | 
			
		||||
        this.focusedElement = null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onKeyDown = (ev: React.KeyboardEvent) => {
 | 
			
		||||
        if (!this.focusedElement) return;
 | 
			
		||||
 | 
			
		||||
        switch (ev.key) {
 | 
			
		||||
            case Key.ARROW_UP:
 | 
			
		||||
            case Key.ARROW_DOWN:
 | 
			
		||||
                ev.stopPropagation();
 | 
			
		||||
                ev.preventDefault();
 | 
			
		||||
                this.onMoveFocus(ev.key === Key.ARROW_UP);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onMoveFocus = (up: boolean) => {
 | 
			
		||||
        let element = this.focusedElement;
 | 
			
		||||
 | 
			
		||||
        let descending = false; // are we currently descending or ascending through the DOM tree?
 | 
			
		||||
        let classes: DOMTokenList;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            const child = up ? element.lastElementChild : element.firstElementChild;
 | 
			
		||||
            const sibling = up ? element.previousElementSibling : element.nextElementSibling;
 | 
			
		||||
 | 
			
		||||
            if (descending) {
 | 
			
		||||
                if (child) {
 | 
			
		||||
                    element = child;
 | 
			
		||||
                } else if (sibling) {
 | 
			
		||||
                    element = sibling;
 | 
			
		||||
                } else {
 | 
			
		||||
                    descending = false;
 | 
			
		||||
                    element = element.parentElement;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (sibling) {
 | 
			
		||||
                    element = sibling;
 | 
			
		||||
                    descending = true;
 | 
			
		||||
                } else {
 | 
			
		||||
                    element = element.parentElement;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (element) {
 | 
			
		||||
                classes = element.classList;
 | 
			
		||||
            }
 | 
			
		||||
        } while (element && !(
 | 
			
		||||
            classes.contains("mx_RoomTile2") ||
 | 
			
		||||
            classes.contains("mx_RoomSublist2_headerText") ||
 | 
			
		||||
            classes.contains("mx_RoomSearch_input")));
 | 
			
		||||
 | 
			
		||||
        if (element) {
 | 
			
		||||
            element.focus();
 | 
			
		||||
            this.focusedElement = element;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private renderHeader(): React.ReactNode {
 | 
			
		||||
        let breadcrumbs;
 | 
			
		||||
        if (this.state.showBreadcrumbs) {
 | 
			
		||||
| 
						 | 
				
			
			@ -170,11 +235,15 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
 | 
			
		|||
 | 
			
		||||
    private renderSearchExplore(): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_LeftPanel2_filterContainer">
 | 
			
		||||
                <RoomSearch onQueryUpdate={this.onSearch} isMinimized={this.props.isMinimized} />
 | 
			
		||||
            <div className="mx_LeftPanel2_filterContainer" onFocus={this.onFocus} onBlur={this.onBlur}>
 | 
			
		||||
                <RoomSearch
 | 
			
		||||
                    onQueryUpdate={this.onSearch}
 | 
			
		||||
                    isMinimized={this.props.isMinimized}
 | 
			
		||||
                    onVerticalArrow={this.onKeyDown}
 | 
			
		||||
                />
 | 
			
		||||
                <AccessibleButton
 | 
			
		||||
                    tabIndex={-1}
 | 
			
		||||
                    className='mx_LeftPanel2_exploreButton'
 | 
			
		||||
                    // TODO fix the accessibility of this: https://github.com/vector-im/riot-web/issues/14180
 | 
			
		||||
                    className="mx_LeftPanel2_exploreButton"
 | 
			
		||||
                    onClick={this.onExplore}
 | 
			
		||||
                    alt={_t("Explore rooms")}
 | 
			
		||||
                />
 | 
			
		||||
| 
						 | 
				
			
			@ -189,14 +258,13 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
 | 
			
		|||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // TODO: Determine what these onWhatever handlers do: https://github.com/vector-im/riot-web/issues/14180
 | 
			
		||||
        const roomList = <RoomList2
 | 
			
		||||
            onKeyDown={() => {/*TODO*/}}
 | 
			
		||||
            onKeyDown={this.onKeyDown}
 | 
			
		||||
            resizeNotifier={null}
 | 
			
		||||
            collapsed={false}
 | 
			
		||||
            searchFilter={this.state.searchFilter}
 | 
			
		||||
            onFocus={() => {/*TODO*/}}
 | 
			
		||||
            onBlur={() => {/*TODO*/}}
 | 
			
		||||
            onFocus={this.onFocus}
 | 
			
		||||
            onBlur={this.onBlur}
 | 
			
		||||
            isMinimized={this.props.isMinimized}
 | 
			
		||||
        />;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +291,12 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
 | 
			
		|||
                        className={roomListClasses}
 | 
			
		||||
                        onScroll={this.onScroll}
 | 
			
		||||
                        ref={this.listContainerRef}
 | 
			
		||||
                    >{roomList}</div>
 | 
			
		||||
                        // Firefox sometimes makes this element focusable due to
 | 
			
		||||
                        // overflow:scroll;, so force it out of tab order.
 | 
			
		||||
                        tabIndex={-1}
 | 
			
		||||
                    >
 | 
			
		||||
                        {roomList}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </aside>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,7 @@ import { Action } from "../../dispatcher/actions";
 | 
			
		|||
interface IProps {
 | 
			
		||||
    onQueryUpdate: (newQuery: string) => void;
 | 
			
		||||
    isMinimized: boolean;
 | 
			
		||||
    onVerticalArrow(ev: React.KeyboardEvent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IState {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +112,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        if (ev.key === Key.ESCAPE) {
 | 
			
		||||
            this.clearInput();
 | 
			
		||||
            defaultDispatcher.fire(Action.FocusComposer);
 | 
			
		||||
        } else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) {
 | 
			
		||||
            this.props.onVerticalArrow(ev);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ import { ActionPayload } from "../../dispatcher/payloads";
 | 
			
		|||
import { Action } from "../../dispatcher/actions";
 | 
			
		||||
import { createRef } from "react";
 | 
			
		||||
import { _t } from "../../languageHandler";
 | 
			
		||||
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
 | 
			
		||||
import {ContextMenu, ContextMenuButton, MenuItem} from "./ContextMenu";
 | 
			
		||||
import {USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB} from "../views/dialogs/UserSettingsDialog";
 | 
			
		||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
 | 
			
		||||
import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog";
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ import LogoutDialog from "../views/dialogs/LogoutDialog";
 | 
			
		|||
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
 | 
			
		||||
import {getCustomTheme} from "../../theme";
 | 
			
		||||
import {getHostingLink} from "../../utils/HostingLink";
 | 
			
		||||
import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
 | 
			
		||||
import {ButtonEvent} from "../views/elements/AccessibleButton";
 | 
			
		||||
import SdkConfig from "../../SdkConfig";
 | 
			
		||||
import {getHomePageUrl} from "../../utils/pages";
 | 
			
		||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +50,19 @@ interface IState {
 | 
			
		|||
    isDarkTheme: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IMenuButtonProps {
 | 
			
		||||
    iconClassName: string;
 | 
			
		||||
    label: string;
 | 
			
		||||
    onClick(ev: ButtonEvent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MenuButton: React.FC<IMenuButtonProps> = ({iconClassName, label, onClick}) => {
 | 
			
		||||
    return <MenuItem label={label} onClick={onClick}>
 | 
			
		||||
        <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
 | 
			
		||||
        <span className="mx_IconizedContextMenu_label">{label}</span>
 | 
			
		||||
    </MenuItem>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default class UserMenu extends React.Component<IProps, IState> {
 | 
			
		||||
    private dispatcherRef: string;
 | 
			
		||||
    private themeWatcherRef: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +115,11 @@ export default class UserMenu extends React.Component<IProps, IState> {
 | 
			
		|||
    private onAction = (ev: ActionPayload) => {
 | 
			
		||||
        if (ev.action !== Action.ToggleUserMenu) return; // not interested
 | 
			
		||||
 | 
			
		||||
        // For accessibility
 | 
			
		||||
        if (this.state.contextMenuPosition) {
 | 
			
		||||
            this.setState({contextMenuPosition: null});
 | 
			
		||||
        } else {
 | 
			
		||||
            if (this.buttonRef.current) this.buttonRef.current.click();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onOpenMenuClick = (ev: InputEvent) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -209,10 +225,11 @@ export default class UserMenu extends React.Component<IProps, IState> {
 | 
			
		|||
        let homeButton = null;
 | 
			
		||||
        if (this.hasHomePage) {
 | 
			
		||||
            homeButton = (
 | 
			
		||||
                <AccessibleButton onClick={this.onHomeClick}>
 | 
			
		||||
                    <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconHome" />
 | 
			
		||||
                    <span>{_t("Home")}</span>
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
                <MenuButton
 | 
			
		||||
                    iconClassName="mx_UserMenu_iconHome"
 | 
			
		||||
                    label={_t("Home")}
 | 
			
		||||
                    onClick={this.onHomeClick}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -249,32 +266,38 @@ export default class UserMenu extends React.Component<IProps, IState> {
 | 
			
		|||
                    {hostingLink}
 | 
			
		||||
                    <div className="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst">
 | 
			
		||||
                        {homeButton}
 | 
			
		||||
                        <AccessibleButton onClick={(e) => this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconBell" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("Notification settings")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <AccessibleButton onClick={(e) => this.onSettingsOpen(e, USER_SECURITY_TAB)}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconLock" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("Security & privacy")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <AccessibleButton onClick={(e) => this.onSettingsOpen(e, null)}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconSettings" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("All settings")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <AccessibleButton onClick={this.onShowArchived}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconArchive" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("Archived rooms")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <AccessibleButton onClick={this.onProvideFeedback}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconMessage" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("Feedback")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconBell"
 | 
			
		||||
                            label={_t("Notification settings")}
 | 
			
		||||
                            onClick={(e) => this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}
 | 
			
		||||
                        />
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconLock"
 | 
			
		||||
                            label={_t("Security & privacy")}
 | 
			
		||||
                            onClick={(e) => this.onSettingsOpen(e, USER_SECURITY_TAB)}
 | 
			
		||||
                        />
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconSettings"
 | 
			
		||||
                            label={_t("All settings")}
 | 
			
		||||
                            onClick={(e) => this.onSettingsOpen(e, null)}
 | 
			
		||||
                        />
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconArchive"
 | 
			
		||||
                            label={_t("Archived rooms")}
 | 
			
		||||
                            onClick={this.onShowArchived}
 | 
			
		||||
                        />
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconMessage"
 | 
			
		||||
                            label={_t("Feedback")}
 | 
			
		||||
                            onClick={this.onProvideFeedback}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="mx_IconizedContextMenu_optionList mx_UserMenu_contextMenu_redRow">
 | 
			
		||||
                        <AccessibleButton onClick={this.onSignOutClick}>
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_icon mx_UserMenu_iconSignOut" />
 | 
			
		||||
                            <span className="mx_IconizedContextMenu_label">{_t("Sign out")}</span>
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                        <MenuButton
 | 
			
		||||
                            iconClassName="mx_UserMenu_iconSignOut"
 | 
			
		||||
                            label={_t("Sign out")}
 | 
			
		||||
                            onClick={this.onSignOutClick}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ContextMenu>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,7 @@ import NotificationBadge from "./NotificationBadge";
 | 
			
		|||
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
 | 
			
		||||
import Tooltip from "../elements/Tooltip";
 | 
			
		||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
 | 
			
		||||
import { Key } from "../../../Keyboard";
 | 
			
		||||
 | 
			
		||||
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
 | 
			
		||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +83,9 @@ interface IState {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export default class RoomSublist2 extends React.Component<IProps, IState> {
 | 
			
		||||
    private headerButton = createRef<HTMLDivElement>();
 | 
			
		||||
    private sublistRef = createRef<HTMLDivElement>();
 | 
			
		||||
 | 
			
		||||
    constructor(props: IProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -217,8 +221,52 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 | 
			
		|||
            sublist.scrollIntoView({behavior: 'smooth'});
 | 
			
		||||
        } else {
 | 
			
		||||
            // on screen - toggle collapse
 | 
			
		||||
            this.toggleCollapsed();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private toggleCollapsed = () => {
 | 
			
		||||
        this.props.layout.isCollapsed = !this.props.layout.isCollapsed;
 | 
			
		||||
        this.forceUpdate(); // because the layout doesn't trigger an update
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onHeaderKeyDown = (ev: React.KeyboardEvent) => {
 | 
			
		||||
        const isCollapsed = this.props.layout && this.props.layout.isCollapsed;
 | 
			
		||||
        switch (ev.key) {
 | 
			
		||||
            case Key.ARROW_LEFT:
 | 
			
		||||
                ev.stopPropagation();
 | 
			
		||||
                if (!isCollapsed) {
 | 
			
		||||
                    // On ARROW_LEFT collapse the room sublist if it isn't already
 | 
			
		||||
                    this.toggleCollapsed();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case Key.ARROW_RIGHT: {
 | 
			
		||||
                ev.stopPropagation();
 | 
			
		||||
                if (isCollapsed) {
 | 
			
		||||
                    // On ARROW_RIGHT expand the room sublist if it isn't already
 | 
			
		||||
                    this.toggleCollapsed();
 | 
			
		||||
                } else if (this.sublistRef.current) {
 | 
			
		||||
                    // otherwise focus the first room
 | 
			
		||||
                    const element = this.sublistRef.current.querySelector(".mx_RoomTile2") as HTMLDivElement;
 | 
			
		||||
                    if (element) {
 | 
			
		||||
                        element.focus();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private onKeyDown = (ev: React.KeyboardEvent) => {
 | 
			
		||||
        switch (ev.key) {
 | 
			
		||||
            // On ARROW_LEFT go to the sublist header
 | 
			
		||||
            case Key.ARROW_LEFT:
 | 
			
		||||
                ev.stopPropagation();
 | 
			
		||||
                this.headerButton.current.focus();
 | 
			
		||||
                break;
 | 
			
		||||
            // Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer
 | 
			
		||||
            case Key.ARROW_RIGHT:
 | 
			
		||||
                ev.stopPropagation();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -337,7 +385,6 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 | 
			
		|||
        return (
 | 
			
		||||
            <RovingTabIndexWrapper>
 | 
			
		||||
                {({onFocus, isActive, ref}) => {
 | 
			
		||||
                    // TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180
 | 
			
		||||
                    const tabIndex = isActive ? 0 : -1;
 | 
			
		||||
 | 
			
		||||
                    const badge = (
 | 
			
		||||
| 
						 | 
				
			
			@ -386,13 +433,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 | 
			
		|||
                    // doesn't become sticky.
 | 
			
		||||
                    // The same applies to the notification badge.
 | 
			
		||||
                    return (
 | 
			
		||||
                        <div className={classes}>
 | 
			
		||||
                            <div className='mx_RoomSublist2_stickable'>
 | 
			
		||||
                        <div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus}>
 | 
			
		||||
                            <div className="mx_RoomSublist2_stickable">
 | 
			
		||||
                                <AccessibleButton
 | 
			
		||||
                                    onFocus={onFocus}
 | 
			
		||||
                                    inputRef={ref}
 | 
			
		||||
                                    tabIndex={tabIndex}
 | 
			
		||||
                                    className={"mx_RoomSublist2_headerText"}
 | 
			
		||||
                                    className="mx_RoomSublist2_headerText"
 | 
			
		||||
                                    role="treeitem"
 | 
			
		||||
                                    aria-level={1}
 | 
			
		||||
                                    onClick={this.onHeaderClick}
 | 
			
		||||
| 
						 | 
				
			
			@ -520,12 +567,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 | 
			
		|||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: onKeyDown support: https://github.com/vector-im/riot-web/issues/14180
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                ref={this.sublistRef}
 | 
			
		||||
                className={classes}
 | 
			
		||||
                role="group"
 | 
			
		||||
                aria-label={this.props.label}
 | 
			
		||||
                onKeyDown={this.onKeyDown}
 | 
			
		||||
            >
 | 
			
		||||
                {this.renderHeader()}
 | 
			
		||||
                {content}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -241,7 +241,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
 | 
			
		|||
    private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY);
 | 
			
		||||
    private onClickMute = ev => this.saveNotifState(ev, MUTE);
 | 
			
		||||
 | 
			
		||||
    private renderNotificationsMenu(): React.ReactElement {
 | 
			
		||||
    private renderNotificationsMenu(isActive: boolean): React.ReactElement {
 | 
			
		||||
        if (MatrixClientPeg.get().isGuest() || !this.showContextMenu) {
 | 
			
		||||
            // the menu makes no sense in these cases so do not show one
 | 
			
		||||
            return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -304,6 +304,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
 | 
			
		|||
                    onClick={this.onNotificationsMenuOpenClick}
 | 
			
		||||
                    label={_t("Notification options")}
 | 
			
		||||
                    isExpanded={!!this.state.notificationsMenuPosition}
 | 
			
		||||
                    tabIndex={isActive ? 0 : -1}
 | 
			
		||||
                />
 | 
			
		||||
                {contextMenu}
 | 
			
		||||
            </React.Fragment>
 | 
			
		||||
| 
						 | 
				
			
			@ -439,7 +440,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
 | 
			
		|||
                            {roomAvatar}
 | 
			
		||||
                            {nameContainer}
 | 
			
		||||
                            {badge}
 | 
			
		||||
                            {this.renderNotificationsMenu()}
 | 
			
		||||
                            {this.renderNotificationsMenu(isActive)}
 | 
			
		||||
                            {this.renderGeneralMenu()}
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue