X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/1e2635886e1342a7b29914d3a437313807676466..cfd501bed1fb431e74816069f0ae8f83aacf29c7:/src/views-components/search-bar/search-bar-view.tsx diff --git a/src/views-components/search-bar/search-bar-view.tsx b/src/views-components/search-bar/search-bar-view.tsx index b4d98f88..ff4b6442 100644 --- a/src/views-components/search-bar/search-bar-view.tsx +++ b/src/views-components/search-bar/search-bar-view.tsx @@ -11,171 +11,234 @@ import { WithStyles, Tooltip, InputAdornment, Input, - ListItem, ListItemText, ListItemSecondaryAction, - ClickAwayListener + Popover, } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; -import { RemoveIcon } from '~/components/icon/icon'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import { ArvadosTheme } from '~/common/custom-theme'; import { SearchView } from '~/store/search-bar/search-bar-reducer'; -import { SearchBarBasicView } from '~/views-components/search-bar/search-bar-basic-view'; -import { SearchBarAdvancedView } from '~/views-components/search-bar/search-bar-advanced-view'; -import { SearchBarAutocompleteView } from '~/views-components/search-bar/search-bar-autocomplete-view'; +import { + SearchBarBasicView, + SearchBarBasicViewDataProps, + SearchBarBasicViewActionProps +} from '~/views-components/search-bar/search-bar-basic-view'; +import { + SearchBarAutocompleteView, + SearchBarAutocompleteViewDataProps, + SearchBarAutocompleteViewActionProps +} from '~/views-components/search-bar/search-bar-autocomplete-view'; +import { + SearchBarAdvancedView, + SearchBarAdvancedViewDataProps, + SearchBarAdvancedViewActionProps +} from '~/views-components/search-bar/search-bar-advanced-view'; +import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "~/common/codes"; -type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'searchBar'; +type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view'; -const styles: StyleRulesCallback = theme => { +const styles: StyleRulesCallback = (theme: ArvadosTheme) => { return { container: { position: 'relative', width: '100%', - borderRadius: theme.spacing.unit / 4 + borderRadius: theme.spacing.unit / 2 }, containerSearchViewOpened: { position: 'relative', width: '100%', - borderRadius: `${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px 0 0` + borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0` }, input: { border: 'none', - padding: `0px ${theme.spacing.unit}px` + padding: `0` }, - searchBar: { - height: '30px' + view: { + position: 'absolute', + width: '100%', + zIndex: 1 } }; }; -interface SearchBarDataProps { - value: string; - currentView: string; - open: boolean; -} +export type SearchBarDataProps = SearchBarViewDataProps + & SearchBarAutocompleteViewDataProps + & SearchBarAdvancedViewDataProps + & SearchBarBasicViewDataProps; -interface SearchBarActionProps { - onSearch: (value: string) => any; +interface SearchBarViewDataProps { + searchValue: string; + currentView: string; + isPopoverOpen: boolean; debounce?: number; - onSetView: (currentView: string) => void; - openView: () => void; - closeView: () => void; - saveQuery: (query: string) => void; - loadQueries: () => string[]; } -type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles; +export type SearchBarActionProps = SearchBarViewActionProps + & SearchBarAutocompleteViewActionProps + & SearchBarAdvancedViewActionProps + & SearchBarBasicViewActionProps; -interface SearchBarState { - value: string; +interface SearchBarViewActionProps { + onChange: (event: React.ChangeEvent) => void; + onSubmit: (event: React.FormEvent) => void; + onSetView: (currentView: string) => void; + closeView: () => void; + openSearchView: () => void; + loadRecentQueries: () => string[]; + moveUp: () => void; + moveDown: () => void; + setAdvancedDataFromSearchValue: (search: string) => void; } -interface RenderQueriesProps { - text: string; -} +type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles; -export const RenderRecentQueries = (props: RenderQueriesProps) => { - return - - ; +const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => { + if (e.keyCode === KEY_CODE_DOWN) { + e.preventDefault(); + if (!props.isPopoverOpen) { + props.onSetView(SearchView.AUTOCOMPLETE); + props.openSearchView(); + } else { + props.moveDown(); + } + } else if (e.keyCode === KEY_CODE_UP) { + e.preventDefault(); + props.moveUp(); + } else if (e.keyCode === KEY_CODE_ESC) { + e.preventDefault(); + props.closeView(); + } else if (e.keyCode === KEY_ENTER) { + if (props.currentView === SearchView.BASIC) { + e.preventDefault(); + props.onSearch(props.selectedItem.query); + } else if (props.currentView === SearchView.AUTOCOMPLETE) { + if (props.selectedItem.id !== props.searchValue) { + e.preventDefault(); + props.navigateTo(props.selectedItem.id); + } + } + } }; - -export const RenderSavedQueries = (props: RenderQueriesProps) => { - return - - - - - - - - - ; +const handleInputClick = (e: React.MouseEvent, props: SearchBarViewProps) => { + if (props.searchValue) { + props.onSetView(SearchView.AUTOCOMPLETE); + props.openSearchView(); + } else { + props.closeView(); + } }; -export const DEFAULT_SEARCH_DEBOUNCE = 1000; +const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => { + e.stopPropagation(); + if (props.isPopoverOpen) { + if (props.currentView === SearchView.ADVANCED) { + props.closeView(); + } else { + props.setAdvancedDataFromSearchValue(props.searchValue); + props.onSetView(SearchView.ADVANCED); + } + } else { + props.setAdvancedDataFromSearchValue(props.searchValue); + props.onSetView(SearchView.ADVANCED); + } +}; export const SearchBarView = withStyles(styles)( - class extends React.Component { - state: SearchBarState = { - value: "" - }; + class SearchBarView extends React.Component { - timeout: number; + viewAnchorRef = React.createRef(); render() { - const { classes, currentView, openView, closeView, open } = this.props; - return closeView()}> - -
- openView()} - endAdornment={ - - - - - - - - } /> - {open && this.getView(currentView)} -
+ const { children, ...props } = this.props; + const { classes, isPopoverOpen } = props; + return ( + +
+
+ handleInputClick(e, props)} + onKeyDown={e => handleKeyDown(e, props)} + startAdornment={ + + + + + + + + } + endAdornment={ + + + handleDropdownClick(e, props)}> + + + + + } /> +
+
+ + {getView({ ...props })} +
-
; - } - - componentDidMount() { - this.setState({ value: this.props.value }); - } - - componentWillReceiveProps(nextProps: SearchBarProps) { - if (nextProps.value !== this.props.value) { - this.setState({ value: nextProps.value }); - } + ); } - componentWillUnmount() { - clearTimeout(this.timeout); - } - - getView = (currentView: string) => { - switch (currentView) { - case SearchView.BASIC: - return ; - case SearchView.ADVANCED: - return ; - case SearchView.AUTOCOMPLETE: - return ; - default: - return ; - } + getViewWidth() { + const { current } = this.viewAnchorRef; + return current ? current.offsetWidth : 0; } + } - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - clearTimeout(this.timeout); - this.props.saveQuery(this.state.value); - this.props.onSearch(this.state.value); - this.props.loadQueries(); - } +); - handleChange = (event: React.ChangeEvent) => { - clearTimeout(this.timeout); - this.setState({ value: event.target.value }); - this.timeout = window.setTimeout( - () => this.props.onSearch(this.state.value), - this.props.debounce || DEFAULT_SEARCH_DEBOUNCE - ); - if (event.target.value.length > 0) { - this.props.onSetView(SearchView.AUTOCOMPLETE); - } else { - this.props.onSetView(SearchView.BASIC); - } - } +const getView = (props: SearchBarViewProps) => { + switch (props.currentView) { + case SearchView.AUTOCOMPLETE: + return ; + case SearchView.ADVANCED: + return ; + default: + return ; } -); +};