1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import 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';
11 interface ChipsInputProps<Value> {
13 getLabel?: (value: Value) => string;
14 onChange: (value: Value[]) => void;
15 handleFocus?: (e: any) => void;
16 handleBlur?: (e: any) => void;
17 chipsClassName?: string;
18 createNewValue: (value: string) => Value;
19 inputComponent?: React.ComponentType<InputProps>;
20 inputProps?: InputProps;
26 type CssRules = 'chips' | 'input' | 'inputContainer';
28 const styles: StyleRulesCallback = ({ spacing }) => ({
30 minHeight: spacing.unit * 5,
44 export const ChipsInput = withStyles(styles)(
45 class ChipsInput<Value> extends React.Component<ChipsInputProps<Value> & WithStyles<CssRules>> {
51 filler = React.createRef<HTMLDivElement>();
54 setText = (event: React.ChangeEvent<HTMLInputElement>) => {
55 this.setState({ text: event.target.value });
58 handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
59 if (e.key === 'Enter') {
60 this.createNewValue();
62 } else if (e.key === 'Backspace') {
63 this.deleteLastValue();
67 createNewValue = () => {
68 if (this.state.text) {
69 const newValue = this.props.createNewValue(this.state.text);
70 this.setState({ text: '' });
71 this.props.onChange([...this.props.values, newValue]);
75 deleteLastValue = () => {
76 if (this.state.text.length === 0 && this.props.values.length > 0) {
77 this.props.onChange(this.props.values.slice(0, -1));
81 updateCursorPosition = () => {
83 clearTimeout(this.timeout);
85 this.timeout = window.setTimeout(() => this.setState({ ...this.state }));
88 getInputStyles = (): React.CSSProperties => ({
89 width: this.filler.current
90 ? this.filler.current.offsetWidth
92 right: this.filler.current
93 ? `calc(${this.filler.current.offsetWidth}px - 100%)`
99 this.updateCursorPosition();
110 const { classes, ...props } = this.props;
111 return <div className={[classes.chips, this.props.chipsClassName].join(' ')}>
114 clickable={!props.disabled}
115 filler={<div ref={this.filler} />}
121 const { inputProps: InputProps, inputComponent: Input = MuiInput, classes } = this.props;
124 value={this.state.text}
125 onChange={this.setText}
126 disabled={this.props.disabled}
127 onKeyDown={this.handleKeyPress}
128 onFocus={this.props.handleFocus}
129 onBlur={this.props.handleBlur}
131 ...(InputProps && InputProps.inputProps),
132 className: classes.input,
133 style: this.getInputStyles(),
136 className={classes.inputContainer} />;
139 componentDidUpdate(prevProps: ChipsInputProps<Value>) {
140 if (prevProps.values !== this.props.values) {
141 this.updateCursorPosition();
144 componentWillUnmount() {
145 clearTimeout(this.timeout);