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