From dedf1eab315347cd85ea45ed3d6a85d60f542104 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 15 Jan 2020 11:37:14 +0000
Subject: [PATCH] Iterate to get rid of the magic group and just provide a
generic functional render wrapper
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/RoomSubList.js | 149 +++++++++---------
.../views/groups/GroupInviteTile.js | 67 ++++----
src/components/views/rooms/RoomTile.js | 69 ++++----
src/contexts/RovingTabIndexContext.js | 81 +++++-----
4 files changed, 184 insertions(+), 182 deletions(-)
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 915a952e79..98e69f6edb 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -31,7 +31,7 @@ import PropTypes from 'prop-types';
import RoomTile from "../views/rooms/RoomTile";
import LazyRenderList from "../views/elements/LazyRenderList";
import {_t} from "../../languageHandler";
-import {RovingTabIndex, RovingTabIndexGroup} from "../../contexts/RovingTabIndexContext";
+import {RovingTabIndexWrapper} from "../../contexts/RovingTabIndexContext";
// turn this on for drop & drag console debugging galore
const debug = false;
@@ -264,45 +264,6 @@ export default class RoomSubList extends React.PureComponent {
const subListNotifCount = subListNotifications.count;
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 = (
-
-
- { FormattingUtils.formatCount(subListNotifCount) }
-
-
- );
- } else if (this.props.isInvite && this.props.list.length) {
- // no notifications but highlight anyway because this is an invite badge
- badge = (
-
-
- { this.props.list.length }
-
-
- );
- }
- }
-
// When collapsed, allow a long hover on the header to show user
// the full tag name and room count
let title;
@@ -318,19 +279,6 @@ export default class RoomSubList extends React.PureComponent {
;
}
- let addRoomButton;
- if (this.props.onAddRoom) {
- addRoomButton = (
-
- );
- }
-
const len = this.props.list.length + this.props.extraTiles.length;
let chevron;
if (len) {
@@ -342,26 +290,81 @@ export default class RoomSubList extends React.PureComponent {
chevron = (
);
}
- return
-
-
- { chevron }
- {this.props.label}
- { incomingCall }
-
- { badge }
- { addRoomButton }
-
- ;
+ return
+ {({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 = (
+
+
+ { FormattingUtils.formatCount(subListNotifCount) }
+
+
+ );
+ } else if (this.props.isInvite && this.props.list.length) {
+ // no notifications but highlight anyway because this is an invite badge
+ badge = (
+
+
+ { this.props.list.length }
+
+
+ );
+ }
+ }
+
+ let addRoomButton;
+ if (this.props.onAddRoom) {
+ addRoomButton = (
+
+ );
+ }
+
+ return (
+
+
+ { chevron }
+ {this.props.label}
+ { incomingCall }
+
+ { badge }
+ { addRoomButton }
+
+ );
+ } }
+ ;
}
checkOverflow = () => {
diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js
index e7ccbdf40b..70baeb1e78 100644
--- a/src/components/views/groups/GroupInviteTile.js
+++ b/src/components/views/groups/GroupInviteTile.js
@@ -26,7 +26,7 @@ import classNames from 'classnames';
import MatrixClientPeg from "../../../MatrixClientPeg";
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
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
export default createReactClass({
@@ -138,18 +138,6 @@ export default createReactClass({
});
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
- const badge = (
-
- { badgeContent }
-
- );
let tooltip;
if (this.props.collapsed && this.state.hover) {
@@ -173,27 +161,40 @@ export default createReactClass({
);
}
- return
-
-
- { av }
-
-
- { label }
- { badge }
-
- { tooltip }
-
+ return
+
+ {({onFocus, isActive, ref}) =>
+
+
+ { av }
+
+
+ { label }
+
+ { badgeContent }
+
+
+ { tooltip }
+
+ }
+
{ contextMenu }
- ;
+ ;
},
});
diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js
index 6358564042..001baf0b96 100644
--- a/src/components/views/rooms/RoomTile.js
+++ b/src/components/views/rooms/RoomTile.js
@@ -32,7 +32,7 @@ import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import {_t} from "../../../languageHandler";
-import {RovingTabIndex} from "../../../contexts/RovingTabIndexContext";
+import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext";
module.exports = createReactClass({
displayName: 'RoomTile',
@@ -433,37 +433,42 @@ module.exports = createReactClass({
}
return
-
-
-
-
- { dmIndicator }
-
-
- { privateIcon }
-
-
- { label }
- { subtextLabel }
-
- { dmOnline }
- { contextMenuButton }
- { badge }
-
- { /* { incomingCallBox } */ }
- { tooltip }
-
+
+ {({onFocus, isActive, ref}) =>
+
+
+
+
+ { dmIndicator }
+
+
+ { privateIcon }
+
+
+ { label }
+ { subtextLabel }
+
+ { dmOnline }
+ { contextMenuButton }
+ { badge }
+
+ { /* { incomingCallBox } */ }
+ { tooltip }
+
+ }
+
{ contextMenu }
;
diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js
index a571bd2eae..f5001d28cc 100644
--- a/src/contexts/RovingTabIndexContext.js
+++ b/src/contexts/RovingTabIndexContext.js
@@ -25,11 +25,9 @@ import React, {
useRef,
useReducer,
} from "react";
-import PropTypes from "prop-types";
import {Key} from "../Keyboard";
const DOCUMENT_POSITION_PRECEDING = 2;
-const ANY = Symbol();
const RovingTabIndexContext = createContext({
state: {
@@ -119,15 +117,42 @@ export const RovingTabIndexContextWrapper = ({children}) => {
const context = useMemo(() => ({state, dispatch}), [state]);
- return
- {children}
- ;
+ 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
+
+ {children}
+
+
;
};
-export const useRovingTabIndex = () => {
- const ref = useRef(null);
+export const useRovingTabIndex = (inputRef) => {
+ let ref = useRef(null);
const context = useContext(RovingTabIndexContext);
+ if (inputRef) {
+ ref = inputRef;
+ }
+
// setup/teardown
// add ref to the context
useLayoutEffect(() => {
@@ -149,45 +174,13 @@ export const useRovingTabIndex = () => {
payload: {ref},
});
}, [ref, context]);
- const isActive = context.state.activeRef === ref || context.state.activeRef === ANY;
+
+ const isActive = context.state.activeRef === ref;
return [onFocus, isActive, ref];
};
-export const RovingTabIndexGroup = ({children}) => {
- const [onFocus, isActive, ref] = useRovingTabIndex();
-
- // 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
-
- {children}
-
-
;
-};
-
-// 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 ;
-};
-RovingTabIndex.propTypes = {
- component: PropTypes.elementType.isRequired,
- useInputRef: PropTypes.bool, // whether to pass inputRef instead of ref like for AccessibleButton
+export const RovingTabIndexWrapper = ({children, inputRef}) => {
+ const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
+ return children({onFocus, isActive, ref});
};