Update styles to match mui form
[arvados-workbench2.git] / src / components / chips-input / chips-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 { Chips } from '~/components/chips/chips';
7 import { Input as MuiInput, withStyles, WithStyles } from '@material-ui/core';
8 import { StyleRulesCallback } from '@material-ui/core/styles';
9 import { InputProps } from '@material-ui/core/Input';
10
11 interface ChipsInputProps<Value> {
12     value: Value[];
13     getLabel?: (value: Value) => string;
14     onChange: (value: Value[]) => void;
15     createNewValue: (value: string) => Value;
16     inputComponent?: React.ComponentType<InputProps>;
17     inputProps?: InputProps;
18 }
19
20 type CssRules = 'chips' | 'input' | 'inputContainer';
21
22 const styles: StyleRulesCallback = ({ spacing }) => ({
23     chips: {
24         minHeight: spacing.unit * 5,
25         zIndex: 1,
26         position: 'relative',
27     },
28     input: {
29         zIndex: 1,
30         marginBottom: 8,
31         position: 'relative',
32     },
33     inputContainer: {
34         marginTop: -34
35     },
36 });
37
38 export const ChipsInput = withStyles(styles)(
39     class ChipsInput<Value> extends React.Component<ChipsInputProps<Value> & WithStyles<CssRules>> {
40
41         state = {
42             text: '',
43         };
44
45         filler = React.createRef<HTMLDivElement>();
46         timeout = -1;
47
48         setText = (event: React.ChangeEvent<HTMLInputElement>) => {
49             this.setState({ text: event.target.value });
50         }
51
52         handleKeyPress = ({ key }: React.KeyboardEvent<HTMLInputElement>) => {
53             if (key === 'Enter') {
54                 this.createNewValue();
55             } else if (key === 'Backspace') {
56                 this.deleteLastValue();
57             }
58         }
59
60         createNewValue = () => {
61             if (this.state.text) {
62                 const newValue = this.props.createNewValue(this.state.text);
63                 this.setState({ text: '' });
64                 this.props.onChange([...this.props.value, newValue]);
65             }
66         }
67
68         deleteLastValue = () => {
69             if (this.state.text.length === 0 && this.props.value.length > 0) {
70                 this.props.onChange(this.props.value.slice(0, -1));
71             }
72         }
73
74         updateCursorPosition = () => {
75             if (this.timeout) {
76                 clearTimeout(this.timeout);
77             }
78             this.timeout = setTimeout(() => this.setState({ ...this.state }));
79         }
80
81         getInputStyles = (): React.CSSProperties => ({
82             width: this.filler.current
83                 ? this.filler.current.offsetWidth
84                 : '100%',
85             right: this.filler.current
86                 ? `calc(${this.filler.current.offsetWidth}px - 100%)`
87                 : 0,
88
89         })
90
91         componentDidMount() {
92             this.updateCursorPosition();
93         }
94
95         render() {
96             return <>
97                 {this.renderChips()}
98                 {this.renderInput()}
99             </>;
100         }
101
102         renderChips() {
103             const { classes, value, ...props } = this.props;
104             return <div className={classes.chips}>
105                 <Chips
106                     {...props}
107                     values={value}
108                     filler={<div ref={this.filler} />}
109                 />
110             </div>;
111         }
112
113         renderInput() {
114             const { inputProps: InputProps, inputComponent: Input = MuiInput, classes } = this.props;
115             return <Input
116                 {...InputProps}
117                 value={this.state.text}
118                 onChange={this.setText}
119                 onKeyDown={this.handleKeyPress}
120                 inputProps={{
121                     ...(InputProps && InputProps.inputProps),
122                     className: classes.input,
123                     style: this.getInputStyles(),
124                 }}
125                 fullWidth
126                 className={classes.inputContainer} />;
127         }
128
129         componentDidUpdate(prevProps: ChipsInputProps<Value>) {
130             if (prevProps.value !== this.props.value) {
131                 this.updateCursorPosition();
132             }
133         }
134         componentWillUnmount() {
135             clearTimeout(this.timeout);
136         }
137     });