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}); };