fix-tests + search-bar-border-radius
[arvados-workbench2.git] / src / views-components / search-bar / search-bar-view.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import {
7     IconButton,
8     Paper,
9     StyleRulesCallback,
10     withStyles,
11     WithStyles,
12     Tooltip,
13     InputAdornment, Input,
14     ListItem, ListItemText, ListItemSecondaryAction,
15     ClickAwayListener
16 } from '@material-ui/core';
17 import SearchIcon from '@material-ui/icons/Search';
18 import { RemoveIcon } from '~/components/icon/icon';
19 import { SearchView } from '~/store/search-bar/search-bar-reducer';
20 import { SearchBarBasicView } from '~/views-components/search-bar/search-bar-basic-view';
21 import { SearchBarAdvancedView } from '~/views-components/search-bar/search-bar-advanced-view';
22 import { SearchBarAutocompleteView } from '~/views-components/search-bar/search-bar-autocomplete-view';
23
24 type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'searchBar';
25
26 const styles: StyleRulesCallback<CssRules> = theme => {
27     return {
28         container: {
29             position: 'relative',
30             width: '100%',
31             borderRadius: theme.spacing.unit / 4
32         },
33         containerSearchViewOpened: {
34             position: 'relative',
35             width: '100%',
36             borderRadius: `${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px 0 0`
37         },
38         input: {
39             border: 'none',
40             padding: `0px ${theme.spacing.unit}px`
41         },
42         searchBar: {
43             height: '30px'
44         }
45     };
46 };
47
48 interface SearchBarDataProps {
49     value: string;
50     currentView: string;
51     open: boolean;
52 }
53
54 interface SearchBarActionProps {
55     onSearch: (value: string) => any;
56     debounce?: number;
57     onSetView: (currentView: string) => void;
58     openView: () => void;
59     closeView: () => void;
60     saveQuery: (query: string) => void;
61     loadQueries: () => string[];
62 }
63
64 type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
65
66 interface SearchBarState {
67     value: string;
68 }
69
70 interface RenderQueriesProps {
71     text: string;
72 }
73
74 export const RenderRecentQueries = (props: RenderQueriesProps) => {
75     return <ListItem button>
76         <ListItemText secondary={props.text} />
77     </ListItem>;
78 };
79
80
81 export const RenderSavedQueries = (props: RenderQueriesProps) => {
82     return <ListItem button>
83         <ListItemText secondary={props.text} />
84         <ListItemSecondaryAction>
85             <Tooltip title="Remove">
86                 <IconButton aria-label="Remove">
87                     <RemoveIcon />
88                 </IconButton>
89             </Tooltip>
90         </ListItemSecondaryAction>
91     </ListItem>;
92 };
93
94 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
95
96 export const SearchBarView = withStyles(styles)(
97     class extends React.Component<SearchBarProps> {
98         state: SearchBarState = {
99             value: ""
100         };
101
102         timeout: number;
103
104         render() {
105             const { classes, currentView, openView, closeView, open } = this.props;
106             return <ClickAwayListener onClickAway={() => closeView()}>
107                 <Paper className={open ? classes.containerSearchViewOpened : classes.container} >
108                     <form onSubmit={this.handleSubmit} className={classes.searchBar}>
109                         <Input
110                             className={classes.input}
111                             onChange={this.handleChange}
112                             placeholder="Search"
113                             value={this.state.value}
114                             fullWidth={true}
115                             disableUnderline={true}
116                             onClick={() => openView()}
117                             endAdornment={
118                                 <InputAdornment position="end">
119                                     <Tooltip title='Search'>
120                                         <IconButton>
121                                             <SearchIcon />
122                                         </IconButton>
123                                     </Tooltip>
124                                 </InputAdornment>
125                             } />
126                         {open && this.getView(currentView)}
127                     </form>
128                 </Paper >
129             </ClickAwayListener>;
130         }
131
132         componentDidMount() {
133             this.setState({ value: this.props.value });
134         }
135
136         componentWillReceiveProps(nextProps: SearchBarProps) {
137             if (nextProps.value !== this.props.value) {
138                 this.setState({ value: nextProps.value });
139             }
140         }
141
142         componentWillUnmount() {
143             clearTimeout(this.timeout);
144         }
145
146         getView = (currentView: string) => {
147             switch (currentView) {
148                 case SearchView.BASIC:
149                     return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
150                 case SearchView.ADVANCED:
151                     return <SearchBarAdvancedView setView={this.props.onSetView} />;
152                 case SearchView.AUTOCOMPLETE:
153                     return <SearchBarAutocompleteView />;
154                 default:
155                     return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
156             }
157         }
158
159         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
160             event.preventDefault();
161             clearTimeout(this.timeout);
162             this.props.saveQuery(this.state.value);
163             this.props.onSearch(this.state.value);
164             this.props.loadQueries();
165         }
166
167         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
168             clearTimeout(this.timeout);
169             this.setState({ value: event.target.value });
170             this.timeout = window.setTimeout(
171                 () => this.props.onSearch(this.state.value),
172                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
173             );
174             if (event.target.value.length > 0) {
175                 this.props.onSetView(SearchView.AUTOCOMPLETE);
176             } else {
177                 this.props.onSetView(SearchView.BASIC);
178             }
179         }
180     }
181 );