refs #14186 Merge branch 'origin/14186-progress-indicator-store'
[arvados.git] / src / components / search-input / search-input.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, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment, Tooltip } 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 SearchInputDataProps {
36     value: string;
37 }
38
39 interface SearchInputActionProps {
40     onSearch: (value: string) => any;
41     debounce?: number;
42 }
43
44 type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyles<CssRules>;
45
46 interface SearchInputState {
47     value: string;
48 }
49
50 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
51
52 export const SearchInput = withStyles(styles)(
53     class extends React.Component<SearchInputProps> {
54         state: SearchInputState = {
55             value: ""
56         };
57
58         timeout: number;
59
60         render() {
61             const { classes } = this.props;
62             return <form onSubmit={this.handleSubmit}>
63                 <FormControl>
64                     <InputLabel>Search</InputLabel>
65                     <Input
66                         type="text"
67                         value={this.state.value}
68                         onChange={this.handleChange}
69                         endAdornment={
70                             <InputAdornment position="end">
71                                 <Tooltip title='Search'>
72                                     <IconButton
73                                         onClick={this.handleSubmit}>
74                                         <SearchIcon />
75                                     </IconButton>
76                                 </Tooltip>
77                             </InputAdornment>
78                         } />
79                 </FormControl>
80             </form>;
81         }
82
83         componentDidMount() {
84             this.setState({ value: this.props.value });
85         }
86
87         componentWillReceiveProps(nextProps: SearchInputProps) {
88             if (nextProps.value !== this.props.value) {
89                 this.setState({ value: nextProps.value });
90             }
91         }
92
93         componentWillUnmount() {
94             clearTimeout(this.timeout);
95         }
96
97         handleSubmit = (event: React.FormEvent<HTMLElement>) => {
98             event.preventDefault();
99             clearTimeout(this.timeout);
100             this.props.onSearch(this.state.value);
101         }
102
103         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
104             clearTimeout(this.timeout);
105             this.setState({ value: event.target.value });
106             this.timeout = window.setTimeout(
107                 () => this.props.onSearch(this.state.value),
108                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
109             );
110
111         }
112     }
113 );