Merge branch 'master'
[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 { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core';
7 import SearchIcon from '@material-ui/icons/Search';
8
9 type CssRules = 'container' | 'input' | 'button';
10
11 const styles: StyleRulesCallback<CssRules> = theme => {
12     return {
13         container: {
14             position: 'relative',
15             width: '100%'
16         },
17         input: {
18             border: 'none',
19             borderRadius: theme.spacing.unit / 4,
20             boxSizing: 'border-box',
21             padding: theme.spacing.unit,
22             paddingRight: theme.spacing.unit * 4,
23             width: '100%',
24         },
25         button: {
26             position: 'absolute',
27             top: theme.spacing.unit / 2,
28             right: theme.spacing.unit / 2,
29             width: theme.spacing.unit * 3,
30             height: theme.spacing.unit * 3
31         }
32     };
33 };
34
35 interface SearchBarDataProps {
36     value: string;
37 }
38
39 interface SearchBarActionProps {
40     onSearch: (value: string) => any;
41     debounce?: number;
42 }
43
44 type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
45
46 interface SearchBarState {
47     value: string;
48 }
49
50 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
51
52 export const SearchBar = withStyles(styles)(
53     class extends React.Component<SearchBarProps> {
54         state: SearchBarState = {
55             value: ""
56         };
57
58         timeout: number;
59
60         render() {
61             const {classes} = this.props;
62             return <Paper className={classes.container}>
63                 <form onSubmit={this.handleSubmit}>
64                     <input
65                         className={classes.input}
66                         onChange={this.handleChange}
67                         placeholder="Search"
68                         value={this.state.value}
69                     />
70                     <IconButton className={classes.button}>
71                         <SearchIcon/>
72                     </IconButton>
73                 </form>
74             </Paper>;
75         }
76
77         componentDidMount() {
78             this.setState({value: this.props.value});
79         }
80
81         componentWillReceiveProps(nextProps: SearchBarProps) {
82             if (nextProps.value !== this.props.value) {
83                 this.setState({value: nextProps.value});
84             }
85         }
86
87         componentWillUnmount() {
88             clearTimeout(this.timeout);
89         }
90
91         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
92             event.preventDefault();
93             clearTimeout(this.timeout);
94             this.props.onSearch(this.state.value);
95         }
96
97         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
98             clearTimeout(this.timeout);
99             this.setState({value: event.target.value});
100             this.timeout = window.setTimeout(
101                 () => this.props.onSearch(this.state.value),
102                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
103             );
104
105         }
106     }
107 );