Last active
August 2, 2019 09:21
-
-
Save shadyvb/c6c2b892377c87dde74816fa48e4dbd0 to your computer and use it in GitHub Desktop.
Revisions
-
shadyvb revised this gist
Aug 2, 2019 . 1 changed file with 37 additions and 28 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,33 +1,36 @@ diff --git a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js index fcbe02b7..bf12822a 100644 --- a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js +++ b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js @@ -1,6 +1,7 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { __ } from '@wordpress/i18n'; +import PropTypes from 'prop-types'; +import React, { useEffect, useRef, useState } from 'react'; +import { __ } from '@wordpress/i18n'; +import { onClickedOutside, onKeyboardActions } from '../effects'; /** * Component to render the search form and related UI in the site header. @@ -11,6 +12,16 @@ import { __ } from '@wordpress/i18n'; * @constructor */ const HeaderSearch = ( { keyword, onChange } ) => { + const nodeRef = useRef(); + const actions = [ + [ 'ArrowUp', () => console.log( 'test up' ) ], + [ 'ArrowDown', () => console.log( 'test down' ) ], + ]; + useEffect( onKeyboardActions( nodeRef, actions ), [] ); + + const [ isOptionsVisible, setIsOptionsVisible ] = useState( false ); + useEffect( onClickedOutside( [ nodeRef ], setIsOptionsVisible ), [] ); + return ( <form id="form-search-input-inline" @@ -30,6 +41,7 @@ const HeaderSearch = ( { keyword, onChange } ) => { placeholder={ __( 'Search', 'siemens' ) } required type="search" @@ -36,31 +39,31 @@ index fcbe02b7..48310c2c 100644 /> <button className="header__searchSubmit" aria-label={ __( 'Search', 'siemens' ) } type="submit" /> diff --git a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js index e69de29b..9b5877cb 100644 --- a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js +++ b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js @@ -0,0 +1,66 @@ +import { useEffect } from 'react'; + +/** + * React effect: Handle clicking outside of an element + * + * Usage: + * `useEffect( onClickedOutside( nodeRefs, setIsVisible ), [] );` + * Where + * - `nodeRefs` are refs for elements that do not steal focus off the popover + * - `setIsVisible` is the callback to set the state, coming from `useState` + * + * @param {Array<React.Ref>} nodeRefs Array of nodes to exclude + * @param {Function} setState Callback to execute + * + * @returns {function(): function(): *} + */ +export const onClickedOutside = ( nodeRefs, setState ) => { + // Document click handler + const onClick = e => { + // See if the click target is within one of the passed refs + if ( nodeRefs.filter( ref => ref.current && ref.current.contains( e.target ) ).length === 0 ) { + // If not, set the state to false + setState( false ); + } @@ -77,26 +80,32 @@ index e69de29b..bdf824ba 100644 +/** + * React effect: Execute callbacks on keyboard keys pressed + * + * Usage: + * `useEffect( onKeyboardActions( nodeRef, actionMap ), [] );` + * Where + * - `nodeRef` is a ref for the tracked element + * - `actionMap` is an array of objects, each with a `key` and a `callback` + * + * @param {Object<{current:String}>} nodeRef + * @param {Array<Array<String|Function>>} actionMap + * + * @returns {function(): function(): *} + */ +export const onKeyboardActions = ( nodeRef, actionMap ) => { + /** + * Keyboard press event handler + * @param {KeyboardEvent} e + */ + const onPress = e => actionMap + .filter( ( [ key ] ) => ( + ( typeof key === 'string' && e.key === key ) + || + ( typeof key === 'function' && key( e ) ) + ) ) + .map( ( [ , callback ] ) => callback() ); + + return () => { + nodeRef.current && nodeRef.current.addEventListener( 'keydown', onPress ); + return () => nodeRef.current && nodeRef.current.removeEventListener( 'keydown', onPress ); + } +}; -
shadyvb created this gist
Aug 2, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,102 @@ diff --git a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js index fcbe02b7..48310c2c 100644 --- a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js +++ b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js @@ -1,6 +1,7 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { __ } from '@wordpress/i18n'; +import PropTypes from 'prop-types'; +import React, { useEffect, useRef } from 'react'; +import { __ } from '@wordpress/i18n'; +import { OnKeyboardActions } from '../effects'; /** * Component to render the search form and related UI in the site header. @@ -11,6 +12,13 @@ import { __ } from '@wordpress/i18n'; * @constructor */ const HeaderSearch = ( { keyword, onChange } ) => { + const nodeRef = useRef(); + const actions = { + 'ArrowUp': () => console.log( 'test up' ), + 'ArrowDown': () => console.log( 'test down' ), + }; + useEffect( OnKeyboardActions( nodeRef, actions ), [] ); + return ( <form id="form-search-input-inline" @@ -30,6 +38,7 @@ const HeaderSearch = ( { keyword, onChange } ) => { placeholder={ __( 'Search', 'siemens' ) } required type="search" + ref={ nodeRef } value={ keyword } /> <button className="header__searchSubmit" aria-label={ __( 'Search', 'siemens' ) } type="submit" /> diff --git a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js index e69de29b..bdf824ba 100644 --- a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js +++ b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js @@ -0,0 +1,60 @@ +import { useEffect } from 'react'; + +/** + * React effect: Handle clicking outside of an element + * + * Usage: + * `useEffect( effectHideOnBlur( [ ...nodeRefs ], setIsVisible ), [] );` + * Where + * - `nodeRefs` are refs for elements that do not steal focus off the popover + * - `setIsVisible` is the callback to set the state, coming from `useState` + * + * @param {Array<Node>} nodes Array of nodes to exclude + * @param {Function} setState Callback to execute + * + * @returns {function(): function(): *} + */ +export const OnClickedOutside = ( nodes, setState ) => { + // Document click handler + const onClick = e => { + // See if the click target is within one of the passed refs + if ( nodes.filter( node => node && node.contains( e.target ) ).length === 0 ) { + // If not, set the state to false + setState( false ); + } + }; + + // Return a function ( to execute on componentDidMount ) + // .. that returns a cleaning function ( to execute on componentWillUnmount ) + return () => { + document.addEventListener( 'mousedown', onClick ); + return () => document.removeEventListener( 'mousedown', onClick ); + } +}; + +/** + * React effect: Execute callbacks on keyboard keys pressed + * + * @param {Object<{current:String}>} nodeRef + * @param {Array<Object{key:<String|Function>,callback:<Function>}>} actionMap + * + * @returns {function(): function(): *} + */ +export const OnKeyboardActions = ( nodeRef, actionMap ) => { + /** + * Keyboard press event handler + * @param {KeyboardEvent} e + */ + const onPress = e => actionMap + .filter( action => ( + ( typeof action.key === 'string' && e.key === action.key ) + || + ( typeof action.key === 'function' && action.key( e ) ) + ) ) + .map( action => action.callback() ); + + return () => { + nodeRef && nodeRef.current.addEventListener( 'keypress', onPress ); + return () => nodeRef.current && nodeRef.current.removeEventListener( 'keypress', onPress ); + } +};