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