1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
11 interface ChipsInputProps<Value> {
13 getLabel?: (value: Value) => string;
14 onChange: (value: Value[]) => void;
15 createNewValue: (value: string) => Value;
16 inputComponent?: React.ComponentType<InputProps>;
17 inputProps?: InputProps;
23 type CssRules = 'chips' | 'input' | 'inputContainer';
25 const styles: StyleRulesCallback = ({ spacing }) => ({
27 minHeight: spacing.unit * 5,
41 export const ChipsInput = withStyles(styles)(
42 class ChipsInput<Value> extends React.Component<ChipsInputProps<Value> & WithStyles<CssRules>> {
48 filler = React.createRef<HTMLDivElement>();
51 setText = (event: React.ChangeEvent<HTMLInputElement>) => {
52 this.setState({ text: event.target.value });
55 handleKeyPress = ({ key }: React.KeyboardEvent<HTMLInputElement>) => {
56 if (key === 'Enter') {
57 this.createNewValue();
58 } else if (key === 'Backspace') {
59 this.deleteLastValue();
63 createNewValue = () => {
64 if (this.state.text) {
65 const newValue = this.props.createNewValue(this.state.text);
66 this.setState({ text: '' });
67 this.props.onChange([...this.props.value, newValue]);
71 deleteLastValue = () => {
72 if (this.state.text.length === 0 && this.props.value.length > 0) {
73 this.props.onChange(this.props.value.slice(0, -1));
77 updateCursorPosition = () => {
79 clearTimeout(this.timeout);
81 this.timeout = setTimeout(() => this.setState({ ...this.state }));
84 getInputStyles = (): React.CSSProperties => ({
85 width: this.filler.current
86 ? this.filler.current.offsetWidth
88 right: this.filler.current
89 ? `calc(${this.filler.current.offsetWidth}px - 100%)`
95 this.updateCursorPosition();
106 const { classes, value, ...props } = this.props;
107 return <div className={classes.chips}>
110 clickable={!props.disabled}
112 filler={<div ref={this.filler} />}
118 const { inputProps: InputProps, inputComponent: Input = MuiInput, classes } = this.props;
121 value={this.state.text}
122 onChange={this.setText}
123 disabled={this.props.disabled}
124 onKeyDown={this.handleKeyPress}
126 ...(InputProps && InputProps.inputProps),
127 className: classes.input,
128 style: this.getInputStyles(),
131 className={classes.inputContainer} />;
134 componentDidUpdate(prevProps: ChipsInputProps<Value>) {
135 if (prevProps.value !== this.props.value) {
136 this.updateCursorPosition();
139 componentWillUnmount() {
140 clearTimeout(this.timeout);