ðĨ Sylvain Hamann
ðĻâðŧ Web Developer
ðŦð· âïļ ð°ð· âïļ ðŊðĩ âïļ ðĻðĶ
ðĪ @sylvhama
Did you know? X and âŊ behaviors are reversed in Japan.
But not everyone has a dev kit ð
The user isn't using a keyboard or a mouse!
However the gamepad is firing keyboard events. ðĪŠ
onKeyDown = throttle(e => {const {focusedNode: { depth, rect },navigate,nodes,showExitModal} = this.props;switch (e.keyCode) {case 27: // PS: âŊ button ; PC: Esccase 196: // XBOX: B buttonthis.goBackOrShowExitModal();break;case 112: // PS: âģ buttoncase 198: // XBOX: Y buttonshowExitModal();break;case 37: // PS: DPAD and LEFT ANALOG left ; PC: âcase 214: // XBOX: LEFT STICK leftcase 205: // XBOX: DPAD leftnavigate(nodes, depth, rect, "left");break;case 38: // PS: DPAD and LEFT ANALOG up ; PC: âcase 211: // XBOX: LEFT STICK upcase 203: // XBOX: DPAD upnavigate(nodes, depth, rect, "up");break;case 39: // PS: DPAD and LEFT ANALOG right ; PC: âcase 213: // XBOX: LEFT STICK rightcase 206: // XBOX: DPAD rightnavigate(nodes, depth, rect, "right");break;case 40: // PS: DPAD and LEFT ANALOG down ; PC: âcase 212: // XBOX: LEFT STICK downcase 204: // XBOX: DPAD downnavigate(nodes, depth, rect, "down");break;default:break;}}, 250);
Focus Logic
export const FOCUS_ADD_NODE = "FOCUS_ADD_NODE";export const FOCUS_REMOVE_NODE = "FOCUS_REMOVE_NODE";export const FOCUS_NODE = "FOCUS_NODE";export const FOCUS_LOWER_DEPTH = "FOCUS_LOWER_DEPTH";export const addNode = node => ({type: FOCUS_ADD_NODE,node});export const removeNode = id => ({type: FOCUS_REMOVE_NODE,id});export const focusNode = id => ({type: FOCUS_NODE,id});export const focusLowerDepth = () => ({type: FOCUS_LOWER_DEPTH});
{nodes: {loginButton: {id: 'loginButton',depth: 0,rect: {bottom: 954.390625,height: 96,left: 141.84375,right: 345.8125,top: 858.390625,width: 203.96875,x: 141.84375,y: 858.390625}},createAccountButton: {...}},currentId: 'loginButton',previousId: 'createAccountButton',currentDepth: 0,depthsMainId: { 0: 'loginButton' }}
<FocusContainerid="loginButton"render={({ refCallback, isFocused }) => (<Buttonref={refCallback}isFocused={isFocused}onFocus={preloadLogin}onKeyDown={e => onKeyEnter(e, () => history.push(paths.AUTH_LOGIN))}hollow><FormattedMessage {...loginToUbi.msg} /></Button>)}/>;
import styled from 'styled-components';export default styled.button`border: 2px solid${props =>props.isFocused? props.theme.color.primary: props.theme.color.greyDark2};padding: ${props => props.theme.spacing.reg}px;width: ${props => (props.fullWidth ? '100%' : 'auto')};line-height: ${props => props.theme.lineHeight};font-size: ${props => props.theme.font.subtitle};font-weight: bold;text-align: center;text-transform: ${props => (props.uppercase ? 'uppercase' : 'none')};background-color: ${props =>props.hollow? 'transparent': props.isFocused? props.theme.color.primary: props.theme.color.greyDark2};color: ${props =>props.hollow? props.isFocused? props.theme.color.primary: props.theme.color.greyDark2: props.theme.color.white};border-radius: 32px;`;
import { Component } from "react";import { connect } from "react-redux";import { addNode, removeNode, focusNode } from "../actions/focus";const mapStateToProps = (state, ownProps) => ({nodes: state.focus.nodes,isFocused: state.focus.currentId === ownProps.id,isPreviousFocus: state.focus.previousId === ownProps.id});const mapDispatchToProps = dispatch => ({addNode: node => dispatch(addNode(node)),removeNode: id => dispatch(removeNode(id)),focusNode: id => dispatch(focusNode(id))});const FocusContainer = connect(mapStateToProps,mapDispatchToProps)(class extends Component {refCallback = ref => {const {nodes,id,depth,toFocus,addNode,focusNode,customRect} = this.props;if (id in nodes) return;this.ref = ref;addNode({id,depth,rect: customRect || ref.getBoundingClientRect()});if (toFocus) focusNode(id);};componentWillUnmount() {const { removeNode, id } = this.props;removeNode(id);}componentDidUpdate(prevProps) {const { isFocused, preventScroll } = this.props;if (this.ref && isFocused && !prevProps.isFocused)this.ref.focus({ preventScroll });}render() {const { isPreviousFocus, isFocused } = this.props;return this.props.render({refCallback: ref => ref && this.refCallback(ref),isFocused,isPreviousFocus,tabIndex: isFocused ? 0 : isPreviousFocus ? -1 : null});}});// PropTypesexport default FocusContainer;
const FocusProvider = props => {const [state, dispatch] = useReducer(reducer, initialState);const dispatchFocusNode = id => dispatch(focusNode(id));const throttledHandleKeyDown = throttle(event =>handleKeyDown(event,dispatchFocusNode,state.nodes,state.currentId,props.keyCodes,props.disablePreventDefaultStrokes),props.throttle);useEffect(() => {if (!props.disableOnKeyDown)window.addEventListener("keydown", throttledHandleKeyDown);else window.removeEventListener("keydown", throttledHandleKeyDown);return () => window.removeEventListener("keydown", throttledHandleKeyDown);});return (<FocusContext.Providervalue={{...state,addNode: node => dispatch(addNode(node)),removeNode: id => dispatch(removeNode(id)),focusNode: dispatchFocusNode,focusLowerDepth: () => dispatch(focusLowerDepth())}}>{props.children}</FocusContext.Provider>);};
import { useEffect } from "react";import useFocusContext from "./useFocusContext";const refCallback = ({ref,nodes,id,depth,toFocus,addNode,focusNode,customRect}) => {if (id in nodes) return;addNode({id,depth,rect: customRect || ref.getBoundingClientRect()});if (toFocus) focusNode(id);};export default ({id,customRect,depth = 0,toFocus = false,preventScroll = true}) => {if (!id) throw new Error("You must specify an unique id (string).");const focus = useFocusContext();const isFocused = id === focus.currentId;const isPreviousFocus = id === focus.previousId;let focusRef;useEffect(() => (isFocused && focusRef ? focusRef.focus({ preventScroll }) : undefined),[isFocused]);useEffect(() => () => focus.removeNode(id), []);return {refCallback: ref => {if (ref) {focusRef = ref;refCallback({ref,id,depth,toFocus,customRect,nodes: focus.nodes,addNode: focus.addNode,focusNode: focus.focusNode});}},isFocused,isPreviousFocus,tabIndex: isFocused ? 0 : isPreviousFocus ? -1 : null};};
import React from "react";import useFocus from "../hooks/useFocus";export default () => {const elt1Focus = useFocus({ id: "elt1", toFocus: true });const elt2Focus = useFocus({ id: "elt2" });return (<><divref={elt1Focus.refCallback}style={{ color: elt1Focus.isFocused ? "tomato" : "black" }}tabIndex={elt1Focus.tabIndex}role="button">Elt1</div><divref={elt2Focus.refCallback}style={{ color: elt1Focus.isFocused ? "tomato" : "black" }}tabIndex={elt2Focus.tabIndex}role="button">Elt2</div></>);};