From bff5a38e821e3c9dcc70e8fa7e0bb7b005e58b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 25 Feb 2020 16:39:04 +0100 Subject: [PATCH 1/3] feat(react): introduce `inputRef` for focus management --- .../autocomplete-react/src/Autocomplete.tsx | 22 +- stories/display.stories.tsx | 201 ++++++++++++++++++ 2 files changed, 219 insertions(+), 4 deletions(-) diff --git a/packages/autocomplete-react/src/Autocomplete.tsx b/packages/autocomplete-react/src/Autocomplete.tsx index 96228ecbb..9adaa7495 100644 --- a/packages/autocomplete-react/src/Autocomplete.tsx +++ b/packages/autocomplete-react/src/Autocomplete.tsx @@ -1,7 +1,7 @@ /** @jsx h */ import { h } from 'preact'; -import { useRef, useEffect } from 'preact/hooks'; +import { useRef, useEffect, Ref } from 'preact/hooks'; import { createPortal } from 'preact/compat'; import { @@ -25,10 +25,18 @@ interface PublicRendererProps { * The dropdown placement related to the container. */ dropdownPlacement?: 'start' | 'end'; + /** + * The ref to the input element. + * + * Useful for managing focus. + */ + inputRef?: Ref; } -export interface RendererProps extends Required { +export interface RendererProps extends PublicRendererProps { dropdownContainer: HTMLElement; + dropdownPlacement: 'start' | 'end'; + inputRef?: Ref; } interface PublicProps @@ -47,6 +55,7 @@ export function getDefaultRendererProps( ) : autocompleteProps.environment.document.body, dropdownPlacement: rendererProps.dropdownPlacement ?? 'start', + inputRef: rendererProps.inputRef, }; } @@ -56,15 +65,20 @@ export function Autocomplete( const { dropdownContainer, dropdownPlacement, + inputRef: providedInputRef, ...autocompleteProps } = providedProps; const props = getDefaultProps(autocompleteProps); const rendererProps = getDefaultRendererProps( - { dropdownContainer, dropdownPlacement }, + { + dropdownContainer, + dropdownPlacement, + inputRef: providedInputRef, + }, props ); - const inputRef = useRef(null); + const inputRef = providedInputRef ?? useRef(null); const searchBoxRef = useRef(null); const dropdownRef = useRef(null); diff --git a/stories/display.stories.tsx b/stories/display.stories.tsx index 26d79919e..0b3c96be1 100644 --- a/stories/display.stories.tsx +++ b/stories/display.stories.tsx @@ -1,6 +1,8 @@ /** @jsx h */ import { h, render } from 'preact'; +import { useState, useEffect, useRef, useCallback } from 'preact/hooks'; +import { createPortal } from 'preact/compat'; import { storiesOf } from '@storybook/html'; import algoliasearch from 'algoliasearch/lite'; @@ -90,4 +92,203 @@ storiesOf('Display', module) searchBoxPosition: 'end', } ) + ) + .add( + 'Modal', + withPlayground( + ({ container, dropdownContainer }) => { + function App() { + const modalRef = useRef(null); + const inputRef = useRef(null); + const [isShowing, setIsShowing] = useState(false); + + const toggleModal = useCallback(() => { + if (isShowing) { + setIsShowing(false); + return; + } + + setIsShowing(true); + setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, 0); + }, [isShowing, setIsShowing]); + + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + const element = event.target as HTMLElement; + const tagName = element.tagName; + + if (event.key === 'Escape' && isShowing) { + toggleModal(); + } + + if ( + element.isContentEditable || + tagName === 'INPUT' || + tagName === 'SELECT' || + tagName === 'TEXTAREA' + ) { + return; + } + + if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { + toggleModal(); + } + } + + window.addEventListener('keydown', onKeyDown); + + return () => { + window.removeEventListener('keydown', onKeyDown); + }; + }, [toggleModal, isShowing]); + + return ( +
+ + + {isShowing && + createPortal( +
{ + if (event.target === modalRef.current) { + setIsShowing(false); + } + }} + style={{ + display: 'flex', + paddingTop: 120, + justifyContent: 'center', + backgroundColor: 'rgba(0, 0, 0, .24)', + bottom: 0, + left: 0, + overflowY: 'auto', + position: 'fixed', + top: 0, + right: 0, + }} + > +
+ { + if (!query) { + return [ + { + getInputValue({ suggestion }) { + return suggestion.query; + }, + getSuggestions() { + return [ + { + query: 'GitHub', + _highlightResult: { + query: { value: 'GitHub' }, + }, + }, + { + query: 'Twitter', + _highlightResult: { + query: { value: 'Twitter' }, + }, + }, + ]; + }, + }, + ]; + } + + return [ + { + getInputValue({ suggestion }) { + return suggestion.query; + }, + getSuggestions({ query }) { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: + 'instant_search_demo_query_suggestions', + query, + params: { + hitsPerPage: 4, + }, + }, + ], + }); + }, + }, + ]; + }} + /> +
+
, + dropdownContainer + )} +
+ ); + } + + render(, container); + + return container; + }, + { + searchBoxPosition: 'end', + } + ) ); From 03148f5c099ee8163f45168819b4eb3a2f758972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 25 Feb 2020 16:46:48 +0100 Subject: [PATCH 2/3] refactro(stories): simplify toggling condition --- stories/display.stories.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/stories/display.stories.tsx b/stories/display.stories.tsx index 0b3c96be1..e7422da1b 100644 --- a/stories/display.stories.tsx +++ b/stories/display.stories.tsx @@ -118,23 +118,10 @@ storiesOf('Display', module) useEffect(() => { function onKeyDown(event: KeyboardEvent) { - const element = event.target as HTMLElement; - const tagName = element.tagName; - - if (event.key === 'Escape' && isShowing) { - toggleModal(); - } - if ( - element.isContentEditable || - tagName === 'INPUT' || - tagName === 'SELECT' || - tagName === 'TEXTAREA' + (event.key === 'Escape' && isShowing) || + (event.key === 'k' && (event.metaKey || event.ctrlKey)) ) { - return; - } - - if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { toggleModal(); } } From b7753f3b141aae3a98380a845d3297b4a2a0a7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 25 Feb 2020 17:52:06 +0100 Subject: [PATCH 3/3] fix(stories): prevent default when Cmd+K --- stories/display.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/stories/display.stories.tsx b/stories/display.stories.tsx index e7422da1b..b662ddc6d 100644 --- a/stories/display.stories.tsx +++ b/stories/display.stories.tsx @@ -122,6 +122,7 @@ storiesOf('Display', module) (event.key === 'Escape' && isShowing) || (event.key === 'k' && (event.metaKey || event.ctrlKey)) ) { + event.preventDefault(); toggleModal(); } }