Extract styles
[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             if (this.timeout) {
73                 clearTimeout(this.timeout);
74             }
75             this.timeout = setTimeout(() => this.forceUpdate());
76         }
77
78         render() {
79             this.updateCursorPosition();
80             return <>
81                 <div className={this.props.classes.chips}>
82                     <Chips {...this.props} filler={<div ref={this.filler} />} />
83                 </div>
84                 <Input
85                     value={this.state.text}
86                     onChange={this.setText}
87                     onKeyDown={this.handleKeyPress}
88                     inputProps={{
89                         className: this.props.classes.input,
90                         style: this.getInputStyles(),
91                     }}
92                     fullWidth
93                     className={this.props.classes.inputContainer} />
94             </>;
95         }
96
97         getInputStyles = (): React.CSSProperties => ({
98             width: this.filler.current
99                 ? this.filler.current.offsetWidth + 8
100                 : '100%',
101             right: this.filler.current
102                 ? `calc(${this.filler.current.offsetWidth}px - 100%)`
103                 : 0,
104
105         })
106     });