From 0609811224b306629207906a8ead88da65067dbf Mon Sep 17 00:00:00 2001 From: Benjamin Elo Date: Mon, 29 Jul 2019 10:10:04 -0700 Subject: [PATCH] Added dropdown sheet for nav bar Summary: Here I've added a drop down sheet for the nav bar. Currently it only supports showing the first five bookmarks with no filtering. I use a custom hook to handle navigation with the keyboard in the nav bar, so that works well. So you can use the arrow keys to select a uri from the dropdown, or by using the mouse. Reviewed By: danielbuechele Differential Revision: D16542218 fbshipit-source-id: 4c242fd3097297fc599b36523bb4821bbc172f88 --- .../components/AutoCompleteSheet.js | 101 +++++++++++++++++ .../navigation/components/SearchBar.js | 107 ++++++++++++------ src/plugins/navigation/components/index.js | 1 + 3 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 src/plugins/navigation/components/AutoCompleteSheet.js diff --git a/src/plugins/navigation/components/AutoCompleteSheet.js b/src/plugins/navigation/components/AutoCompleteSheet.js new file mode 100644 index 000000000..5625b6241 --- /dev/null +++ b/src/plugins/navigation/components/AutoCompleteSheet.js @@ -0,0 +1,101 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + * @flow strict-local + */ + +import {styled} from 'flipper'; +import {useEffect, useState} from 'react'; + +import type {Bookmark} from '../flow-types'; + +type Props = {| + bookmarks: Map, + onHighlighted: string => void, + onNavigate: string => void, +|}; + +const MAX_ITEMS = 5; + +const AutoCompleteSheetContainer = styled('div')({ + width: '100%', + overflowY: 'scroll', + position: 'absolute', + top: '100%', + backgroundColor: 'white', + zIndex: 1, + borderBottomRightRadius: 10, + borderBottomLeftRadius: 10, + boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', +}); + +const SheetItem = styled('div')({ + padding: 5, + textOverflow: 'ellipsis', + overflowX: 'hidden', + whiteSpace: 'nowrap', + '&.selected': { + backgroundColor: 'rgba(155, 155, 155, 0.2)', + }, + '&:hover': { + backgroundColor: 'rgba(155, 155, 155, 0.2)', + }, +}); + +// Menu Item Navigation Hook +const useItemNavigation = ( + bookmarks: Array, + onHighlighted: string => void, +) => { + const [selectedItem, setSelectedItem] = useState(-1); + + const handleKeyPress = ({key}) => { + switch (key) { + case 'ArrowDown': { + const newSelectedItem = + selectedItem < MAX_ITEMS - 1 ? selectedItem + 1 : selectedItem; + setSelectedItem(newSelectedItem); + onHighlighted(bookmarks[newSelectedItem].uri); + break; + } + case 'ArrowUp': { + const newSelectedItem = + selectedItem > 0 ? selectedItem - 1 : selectedItem; + setSelectedItem(newSelectedItem); + onHighlighted(bookmarks[newSelectedItem].uri); + break; + } + default: + break; + } + }; + + useEffect(() => { + window.addEventListener('keydown', handleKeyPress); + return () => { + window.removeEventListener('keydown', handleKeyPress); + }; + }); + + return selectedItem; +}; + +export default (props: Props) => { + const {bookmarks, onHighlighted, onNavigate} = props; + const filteredBookmarks = [...bookmarks.values()].slice(0, MAX_ITEMS); + const selectedItem = useItemNavigation(filteredBookmarks, onHighlighted); + return ( + + {filteredBookmarks.map((bookmark, idx) => ( + onNavigate(bookmark.uri)}> + {bookmark.uri} + + ))} + + ); +}; diff --git a/src/plugins/navigation/components/SearchBar.js b/src/plugins/navigation/components/SearchBar.js index 5e8fabd62..b36247e7a 100644 --- a/src/plugins/navigation/components/SearchBar.js +++ b/src/plugins/navigation/components/SearchBar.js @@ -14,7 +14,7 @@ import { Toolbar, Glyph, } from 'flipper'; -import {IconButton, FavoriteButton} from './'; +import {AutoCompleteSheet, IconButton, FavoriteButton} from './'; import type {Bookmark} from '../flow-types'; @@ -26,6 +26,8 @@ type Props = {| type State = {| query: string, + inputFocused: boolean, + autoCompleteSheetOpen: boolean, |}; const IconContainer = styled('div')({ @@ -44,12 +46,24 @@ const IconContainer = styled('div')({ }, }); -const SearchChevronContainer = styled('div')({ - marginRight: 12, +const ToolbarContainer = styled('div')({ + '.drop-shadow': { + boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', + }, +}); + +const SearchInputContainer = styled('div')({ + height: '100%', + width: '100%', + marginLeft: 5, + marginRight: 9, + position: 'relative', }); class SearchBar extends Component { state = { + inputFocused: false, + autoCompleteSheetOpen: false, query: '', }; @@ -58,6 +72,7 @@ class SearchBar extends Component { }; navigateTo = (query: string) => { + this.setState({query}); this.props.onNavigate(query); }; @@ -67,39 +82,61 @@ class SearchBar extends Component { render = () => { const {bookmarks} = this.props; - const {query} = this.state; + const {autoCompleteSheetOpen, inputFocused, query} = this.state; return ( - - - { - if (e.key === 'Enter') { - this.navigateTo(this.state.query); - } - }} - placeholder="Navigate To..." - /> - - - - - {query.length > 0 ? ( - - this.navigateTo(this.state.query)} - /> - this.favorite(this.state.query)} - /> - - ) : null} - + + + + + + this.setState({ + autoCompleteSheetOpen: false, + inputFocused: false, + }) + } + onFocus={() => + this.setState({ + autoCompleteSheetOpen: true, + inputFocused: true, + }) + } + onChange={this.queryInputChanged} + onKeyPress={e => { + if (e.key === 'Enter') { + this.navigateTo(this.state.query); + e.target.blur(); + } + }} + placeholder="Navigate To..." + /> + {autoCompleteSheetOpen ? ( + this.setState({query: newQuery})} + /> + ) : null} + + + {query.length > 0 ? ( + + this.navigateTo(this.state.query)} + /> + this.favorite(this.state.query)} + /> + + ) : null} + + ); }; } diff --git a/src/plugins/navigation/components/index.js b/src/plugins/navigation/components/index.js index 831644d28..f5d977f58 100644 --- a/src/plugins/navigation/components/index.js +++ b/src/plugins/navigation/components/index.js @@ -6,6 +6,7 @@ * @flow strict-local */ +export {default as AutoCompleteSheet} from './AutoCompleteSheet'; export {default as BookmarksSidebar} from './BookmarksSidebar'; export {default as FavoriteButton} from './FavoriteButton'; export {default as IconButton} from './IconButton';