mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-18 14:44:58 +08:00
Iterate to get rid of the magic group and just provide a generic functional render wrapper
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
5252cf4c45
commit
dedf1eab31
@ -31,7 +31,7 @@ import PropTypes from 'prop-types';
|
|||||||
import RoomTile from "../views/rooms/RoomTile";
|
import RoomTile from "../views/rooms/RoomTile";
|
||||||
import LazyRenderList from "../views/elements/LazyRenderList";
|
import LazyRenderList from "../views/elements/LazyRenderList";
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import {RovingTabIndex, RovingTabIndexGroup} from "../../contexts/RovingTabIndexContext";
|
import {RovingTabIndexWrapper} from "../../contexts/RovingTabIndexContext";
|
||||||
|
|
||||||
// turn this on for drop & drag console debugging galore
|
// turn this on for drop & drag console debugging galore
|
||||||
const debug = false;
|
const debug = false;
|
||||||
@ -264,45 +264,6 @@ export default class RoomSubList extends React.PureComponent {
|
|||||||
const subListNotifCount = subListNotifications.count;
|
const subListNotifCount = subListNotifications.count;
|
||||||
const subListNotifHighlight = subListNotifications.highlight;
|
const subListNotifHighlight = subListNotifications.highlight;
|
||||||
|
|
||||||
let badge;
|
|
||||||
if (!this.props.collapsed) {
|
|
||||||
const badgeClasses = classNames({
|
|
||||||
'mx_RoomSubList_badge': true,
|
|
||||||
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
|
|
||||||
});
|
|
||||||
// Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
|
||||||
if (subListNotifCount > 0) {
|
|
||||||
badge = (
|
|
||||||
<RovingTabIndex
|
|
||||||
component={AccessibleButton}
|
|
||||||
useInputRef
|
|
||||||
className={badgeClasses}
|
|
||||||
onClick={this._onNotifBadgeClick}
|
|
||||||
aria-label={_t("Jump to first unread room.")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{ FormattingUtils.formatCount(subListNotifCount) }
|
|
||||||
</div>
|
|
||||||
</RovingTabIndex>
|
|
||||||
);
|
|
||||||
} else if (this.props.isInvite && this.props.list.length) {
|
|
||||||
// no notifications but highlight anyway because this is an invite badge
|
|
||||||
badge = (
|
|
||||||
<RovingTabIndex
|
|
||||||
component={AccessibleButton}
|
|
||||||
useInputRef
|
|
||||||
className={badgeClasses}
|
|
||||||
onClick={this._onInviteBadgeClick}
|
|
||||||
aria-label={_t("Jump to first invite.")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{ this.props.list.length }
|
|
||||||
</div>
|
|
||||||
</RovingTabIndex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When collapsed, allow a long hover on the header to show user
|
// When collapsed, allow a long hover on the header to show user
|
||||||
// the full tag name and room count
|
// the full tag name and room count
|
||||||
let title;
|
let title;
|
||||||
@ -318,19 +279,6 @@ export default class RoomSubList extends React.PureComponent {
|
|||||||
<IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={this.props.incomingCall} />;
|
<IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={this.props.incomingCall} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let addRoomButton;
|
|
||||||
if (this.props.onAddRoom) {
|
|
||||||
addRoomButton = (
|
|
||||||
<RovingTabIndex
|
|
||||||
component={AccessibleTooltipButton}
|
|
||||||
useInputRef
|
|
||||||
onClick={this.onAddRoom}
|
|
||||||
className="mx_RoomSubList_addRoom"
|
|
||||||
title={this.props.addRoomLabel || _t("Add room")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const len = this.props.list.length + this.props.extraTiles.length;
|
const len = this.props.list.length + this.props.extraTiles.length;
|
||||||
let chevron;
|
let chevron;
|
||||||
if (len) {
|
if (len) {
|
||||||
@ -342,26 +290,81 @@ export default class RoomSubList extends React.PureComponent {
|
|||||||
chevron = (<div className={chevronClasses} />);
|
chevron = (<div className={chevronClasses} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <RovingTabIndexGroup>
|
return <RovingTabIndexWrapper inputRef={this._headerButton}>
|
||||||
|
{({onFocus, isActive, ref}) => {
|
||||||
|
const tabIndex = isActive ? 0 : -1;
|
||||||
|
|
||||||
|
let badge;
|
||||||
|
if (!this.props.collapsed) {
|
||||||
|
const badgeClasses = classNames({
|
||||||
|
'mx_RoomSubList_badge': true,
|
||||||
|
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
|
||||||
|
});
|
||||||
|
// Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
||||||
|
if (subListNotifCount > 0) {
|
||||||
|
badge = (
|
||||||
|
<AccessibleButton
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
className={badgeClasses}
|
||||||
|
onClick={this._onNotifBadgeClick}
|
||||||
|
aria-label={_t("Jump to first unread room.")}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{ FormattingUtils.formatCount(subListNotifCount) }
|
||||||
|
</div>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
} else if (this.props.isInvite && this.props.list.length) {
|
||||||
|
// no notifications but highlight anyway because this is an invite badge
|
||||||
|
badge = (
|
||||||
|
<AccessibleButton
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
className={badgeClasses}
|
||||||
|
onClick={this._onInviteBadgeClick}
|
||||||
|
aria-label={_t("Jump to first invite.")}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{ this.props.list.length }
|
||||||
|
</div>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addRoomButton;
|
||||||
|
if (this.props.onAddRoom) {
|
||||||
|
addRoomButton = (
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
onClick={this.onAddRoom}
|
||||||
|
className="mx_RoomSubList_addRoom"
|
||||||
|
title={this.props.addRoomLabel || _t("Add room")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="mx_RoomSubList_labelContainer" title={title} ref={this._header} onKeyDown={this.onHeaderKeyDown}>
|
<div className="mx_RoomSubList_labelContainer" title={title} ref={this._header} onKeyDown={this.onHeaderKeyDown}>
|
||||||
<RovingTabIndex
|
<AccessibleButton
|
||||||
component={AccessibleButton}
|
onFocus={onFocus}
|
||||||
useInputRef
|
tabIndex={tabIndex}
|
||||||
|
inputRef={ref}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
className="mx_RoomSubList_label"
|
className="mx_RoomSubList_label"
|
||||||
aria-expanded={!isCollapsed}
|
aria-expanded={!isCollapsed}
|
||||||
inputRef={this._headerButton}
|
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
aria-level="1"
|
aria-level="1"
|
||||||
>
|
>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
<span>{this.props.label}</span>
|
<span>{this.props.label}</span>
|
||||||
{ incomingCall }
|
{ incomingCall }
|
||||||
</RovingTabIndex>
|
</AccessibleButton>
|
||||||
{ badge }
|
{ badge }
|
||||||
{ addRoomButton }
|
{ addRoomButton }
|
||||||
</div>
|
</div>
|
||||||
</RovingTabIndexGroup>;
|
);
|
||||||
|
} }
|
||||||
|
</RovingTabIndexWrapper>;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkOverflow = () => {
|
checkOverflow = () => {
|
||||||
|
@ -26,7 +26,7 @@ import classNames from 'classnames';
|
|||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {RovingTabIndex, RovingTabIndexGroup} from "../../../contexts/RovingTabIndexContext";
|
import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext";
|
||||||
|
|
||||||
// XXX this class copies a lot from RoomTile.js
|
// XXX this class copies a lot from RoomTile.js
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
@ -138,18 +138,6 @@ export default createReactClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
||||||
const badge = (
|
|
||||||
<RovingTabIndex
|
|
||||||
component={ContextMenuButton}
|
|
||||||
useInputRef
|
|
||||||
className={badgeClasses}
|
|
||||||
onClick={this.onContextMenuButtonClick}
|
|
||||||
label={_t("Options")}
|
|
||||||
isExpanded={isMenuDisplayed}
|
|
||||||
>
|
|
||||||
{ badgeContent }
|
|
||||||
</RovingTabIndex>
|
|
||||||
);
|
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (this.props.collapsed && this.state.hover) {
|
if (this.props.collapsed && this.state.hover) {
|
||||||
@ -173,10 +161,13 @@ export default createReactClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <RovingTabIndexGroup>
|
return <React.Fragment>
|
||||||
<RovingTabIndex
|
<RovingTabIndexWrapper>
|
||||||
component={AccessibleButton}
|
{({onFocus, isActive, ref}) =>
|
||||||
useInputRef
|
<AccessibleButton
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
inputRef={ref}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseEnter={this.onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
@ -188,12 +179,22 @@ export default createReactClass({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomTile_nameContainer">
|
<div className="mx_RoomTile_nameContainer">
|
||||||
{ label }
|
{ label }
|
||||||
{ badge }
|
<ContextMenuButton
|
||||||
|
className={badgeClasses}
|
||||||
|
onClick={this.onContextMenuButtonClick}
|
||||||
|
label={_t("Options")}
|
||||||
|
isExpanded={isMenuDisplayed}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
>
|
||||||
|
{ badgeContent }
|
||||||
|
</ContextMenuButton>
|
||||||
</div>
|
</div>
|
||||||
{ tooltip }
|
{ tooltip }
|
||||||
</RovingTabIndex>
|
</AccessibleButton>
|
||||||
|
}
|
||||||
|
</RovingTabIndexWrapper>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</RovingTabIndexGroup>;
|
</React.Fragment>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,7 @@ import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
|||||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import {RovingTabIndex} from "../../../contexts/RovingTabIndexContext";
|
import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext";
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
@ -433,9 +433,12 @@ module.exports = createReactClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<RovingTabIndex
|
<RovingTabIndexWrapper>
|
||||||
component={AccessibleButton}
|
{({onFocus, isActive, ref}) =>
|
||||||
useInputRef
|
<AccessibleButton
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
inputRef={ref}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseEnter={this.onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
@ -463,7 +466,9 @@ module.exports = createReactClass({
|
|||||||
</div>
|
</div>
|
||||||
{ /* { incomingCallBox } */ }
|
{ /* { incomingCallBox } */ }
|
||||||
{ tooltip }
|
{ tooltip }
|
||||||
</RovingTabIndex>
|
</AccessibleButton>
|
||||||
|
}
|
||||||
|
</RovingTabIndexWrapper>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -25,11 +25,9 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useReducer,
|
useReducer,
|
||||||
} from "react";
|
} from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {Key} from "../Keyboard";
|
import {Key} from "../Keyboard";
|
||||||
|
|
||||||
const DOCUMENT_POSITION_PRECEDING = 2;
|
const DOCUMENT_POSITION_PRECEDING = 2;
|
||||||
const ANY = Symbol();
|
|
||||||
|
|
||||||
const RovingTabIndexContext = createContext({
|
const RovingTabIndexContext = createContext({
|
||||||
state: {
|
state: {
|
||||||
@ -119,15 +117,42 @@ export const RovingTabIndexContextWrapper = ({children}) => {
|
|||||||
|
|
||||||
const context = useMemo(() => ({state, dispatch}), [state]);
|
const context = useMemo(() => ({state, dispatch}), [state]);
|
||||||
|
|
||||||
return <RovingTabIndexContext.Provider value={context}>
|
const onKeyDown = useCallback((ev) => {
|
||||||
|
if (state.refs.length <= 0) return;
|
||||||
|
|
||||||
|
let handled = true;
|
||||||
|
switch (ev.key) {
|
||||||
|
case Key.HOME:
|
||||||
|
setImmediate(() => state.refs[0].current.focus());
|
||||||
|
break;
|
||||||
|
case Key.END:
|
||||||
|
state.refs[state.refs.length - 1].current.focus();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
return <div onKeyDown={onKeyDown}>
|
||||||
|
<RovingTabIndexContext.Provider value={context}>
|
||||||
{children}
|
{children}
|
||||||
</RovingTabIndexContext.Provider>;
|
</RovingTabIndexContext.Provider>
|
||||||
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRovingTabIndex = () => {
|
export const useRovingTabIndex = (inputRef) => {
|
||||||
const ref = useRef(null);
|
let ref = useRef(null);
|
||||||
const context = useContext(RovingTabIndexContext);
|
const context = useContext(RovingTabIndexContext);
|
||||||
|
|
||||||
|
if (inputRef) {
|
||||||
|
ref = inputRef;
|
||||||
|
}
|
||||||
|
|
||||||
// setup/teardown
|
// setup/teardown
|
||||||
// add ref to the context
|
// add ref to the context
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -149,45 +174,13 @@ export const useRovingTabIndex = () => {
|
|||||||
payload: {ref},
|
payload: {ref},
|
||||||
});
|
});
|
||||||
}, [ref, context]);
|
}, [ref, context]);
|
||||||
const isActive = context.state.activeRef === ref || context.state.activeRef === ANY;
|
|
||||||
|
const isActive = context.state.activeRef === ref;
|
||||||
return [onFocus, isActive, ref];
|
return [onFocus, isActive, ref];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RovingTabIndexGroup = ({children}) => {
|
export const RovingTabIndexWrapper = ({children, inputRef}) => {
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex();
|
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
||||||
|
return children({onFocus, isActive, ref});
|
||||||
// fake reducer dispatch to catch SET_FOCUS calls and pass them to parent as a focus of the group
|
|
||||||
const dispatch = useCallback(({type}) => {
|
|
||||||
if (type === types.SET_FOCUS) {
|
|
||||||
onFocus();
|
|
||||||
}
|
|
||||||
}, [onFocus]);
|
|
||||||
|
|
||||||
const context = useMemo(() => ({
|
|
||||||
state: {activeRef: isActive ? ANY : undefined},
|
|
||||||
dispatch,
|
|
||||||
}), [isActive, dispatch]);
|
|
||||||
|
|
||||||
return <div ref={ref}>
|
|
||||||
<RovingTabIndexContext.Provider value={context}>
|
|
||||||
{children}
|
|
||||||
</RovingTabIndexContext.Provider>
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wraps a given element to attach it to the roving context, props onFocus and tabIndex overridden
|
|
||||||
export const RovingTabIndex = ({component: E, useInputRef, ...props}) => {
|
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex();
|
|
||||||
const refProps = {};
|
|
||||||
if (useInputRef) {
|
|
||||||
refProps.inputRef = ref;
|
|
||||||
} else {
|
|
||||||
refProps.ref = ref;
|
|
||||||
}
|
|
||||||
return <E {...props} {...refProps} onFocus={onFocus} tabIndex={isActive ? 0 : -1} />;
|
|
||||||
};
|
|
||||||
RovingTabIndex.propTypes = {
|
|
||||||
component: PropTypes.elementType.isRequired,
|
|
||||||
useInputRef: PropTypes.bool, // whether to pass inputRef instead of ref like for AccessibleButton
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user