8d86307ab71ac072ea676173b6098bcf5252c0d3
[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, {useState, useEffect} 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     selfClearProp: string;
39 }
40
41 interface SearchInputActionProps {
42     onSearch: (value: string) => any;
43     debounce?: number;
44 }
45
46 type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyles<CssRules>;
47
48 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
49
50 const SearchInputComponent = (props: SearchInputProps) => {
51     const [timeout, setTimeout] = useState(0);
52     const [value, setValue] = useState("");
53     const [label, setLabel] = useState("Search");
54     const [selfClearProp, setSelfClearProp] = useState("");
55
56     useEffect(() => {
57         if (props.value) {
58             setValue(props.value);
59         }
60         if (props.label) {
61             setLabel(props.label);
62         }
63
64         return () => {
65             setValue("");
66             clearTimeout(timeout);
67         };
68     }, [props.value, props.label]); // eslint-disable-line react-hooks/exhaustive-deps
69
70     useEffect(() => {
71         if (selfClearProp !== props.selfClearProp) {
72             setValue("");
73             setSelfClearProp(props.selfClearProp);
74             handleChange({ target: { value: "" } } as any);
75         }
76     }, [props.selfClearProp]); // eslint-disable-line react-hooks/exhaustive-deps
77
78     const handleSubmit = (event: React.FormEvent<HTMLElement>) => {
79         event.preventDefault();
80         clearTimeout(timeout);
81         props.onSearch(value);
82     };
83
84     const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
85         const { target: { value: eventValue } } = event;
86         clearTimeout(timeout);
87         setValue(eventValue);
88
89         setTimeout(window.setTimeout(
90             () => {
91                 props.onSearch(eventValue);
92             },
93              props.debounce || DEFAULT_SEARCH_DEBOUNCE
94         ));
95     };
96
97     return <form onSubmit={handleSubmit}>
98         <FormControl>
99             <InputLabel>{label}</InputLabel>
100             <Input
101                 type="text"
102                 data-cy="search-input"
103                 value={value}
104                 onChange={handleChange}
105                 endAdornment={
106                     <InputAdornment position="end">
107                         <Tooltip title='Search'>
108                             <IconButton
109                                 onClick={handleSubmit}>
110                                 <SearchIcon />
111                             </IconButton>
112                         </Tooltip>
113                     </InputAdornment>
114                 } />
115         </FormControl>
116     </form>;
117 }
118
119 export const SearchInput = withStyles(styles)(SearchInputComponent);