5021c7c1defc856c24cfc1283c1432bf771483c9
[arvados-workbench2.git] / src / components / search-bar / search-bar.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     List, ListItem, ListItemText
15 } from '@material-ui/core';
16 import SearchIcon from '@material-ui/icons/Search';
17
18 type CssRules = 'container' | 'input' | 'advanced' | 'searchQueryList' | 'list' | 'searchView' | 'searchBar';
19
20 const styles: StyleRulesCallback<CssRules> = theme => {
21     return {
22         container: {
23             position: 'relative',
24             width: '100%',
25             borderRadius: '0px'
26         },
27         input: {
28             border: 'none',
29             padding: `0px ${theme.spacing.unit}px`
30         },
31         advanced: {
32             display: 'flex',
33             justifyContent: 'flex-end',
34             paddingRight: theme.spacing.unit * 2,
35             paddingBottom: theme.spacing.unit,
36             fontSize: '14px'
37         },
38         searchQueryList: {
39             padding: `${theme.spacing.unit / 2}px ${theme.spacing.unit}px `,
40             background: '#f2f2f2',
41             fontSize: '14px'
42         },
43         list: {
44             padding: '0px'
45         },
46         searchView: {
47             color: '#000',
48             zIndex: 1000
49         },
50         searchBar: {
51             height: '30px'
52         }
53     };
54 };
55
56 interface SearchBarDataProps {
57     value: string;
58 }
59
60 interface SearchBarActionProps {
61     onSearch: (value: string) => any;
62     debounce?: number;
63 }
64
65 type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
66
67 interface SearchBarState {
68     value: string;
69     isSearchViewOpen: boolean;
70 }
71
72 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
73
74 export const SearchBar = withStyles(styles)(
75     class extends React.Component<SearchBarProps> {
76         state: SearchBarState = {
77             value: "",
78             isSearchViewOpen: false
79         };
80
81         timeout: number;
82
83         render() {
84             const { classes } = this.props;
85             return <Paper className={classes.container} onBlur={this.closeSearchView}>
86                 <form onSubmit={this.handleSubmit} className={classes.searchBar}>
87                     <Input
88                         className={classes.input}
89                         onChange={this.handleChange}
90                         placeholder="Search"
91                         value={this.state.value}
92                         fullWidth={true}
93                         disableUnderline={true}
94                         onFocus={this.openSearchView}
95                         endAdornment={
96                             <InputAdornment position="end">
97                                 <Tooltip title='Search'>
98                                     <IconButton>
99                                         <SearchIcon />
100                                     </IconButton>
101                                 </Tooltip>
102                             </InputAdornment>
103                         } />
104                     {this.state.isSearchViewOpen
105                         ? <Paper className={classes.searchView}>
106                             <div className={classes.searchQueryList}>Saved search queries</div>
107                             <List component="nav" className={classes.list}>
108                                 {this.renderListItem('Trash')}
109                                 {this.renderListItem('Spam')}
110                             </List>
111                             <div className={classes.searchQueryList}>Recent search queries</div>
112                             <List component="nav" className={classes.list}>
113                                 {this.renderListItem('Trash')}
114                                 {this.renderListItem('Spam')}
115                             </List>
116                             <div className={classes.advanced}>Advanced</div>
117                         </Paper> : null}
118                 </form>
119             </Paper>;
120         }
121
122         componentDidMount() {
123             this.setState({ value: this.props.value });
124         }
125
126         componentWillReceiveProps(nextProps: SearchBarProps) {
127             if (nextProps.value !== this.props.value) {
128                 this.setState({ value: nextProps.value });
129             }
130         }
131
132         componentWillUnmount() {
133             clearTimeout(this.timeout);
134         }
135
136         closeSearchView = () =>
137             this.setState({ isSearchViewOpen: false })
138
139
140         openSearchView = () =>
141             this.setState({ isSearchViewOpen: true })
142
143
144         renderListItem = (text: string) =>
145             <ListItem button>
146                 <ListItemText secondary={text} />
147             </ListItem>
148
149         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
150             event.preventDefault();
151             clearTimeout(this.timeout);
152             this.props.onSearch(this.state.value);
153         }
154
155         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
156             clearTimeout(this.timeout);
157             this.setState({ value: event.target.value });
158             this.timeout = window.setTimeout(
159                 () => this.props.onSearch(this.state.value),
160                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
161             );
162
163         }
164     }
165 );