1import React from 'react';
2import { FieldConfigEditorProps, StringFieldConfigSettings, GrafanaTheme } from '@grafana/data';
3import { Input } from '../Input/Input';
4import { Icon } from '../Icon/Icon';
5import { stylesFactory, getTheme } from '../../themes';
6import { css } from '@emotion/css';
7import { Button } from '../Button';
8
9type Props = FieldConfigEditorProps<string[], StringFieldConfigSettings>;
10interface State {
11  showAdd: boolean;
12}
13
14export class StringArrayEditor extends React.PureComponent<Props, State> {
15  state = {
16    showAdd: false,
17  };
18
19  onRemoveString = (index: number) => {
20    const { value, onChange } = this.props;
21    const copy = [...value];
22    copy.splice(index, 1);
23    onChange(copy);
24  };
25
26  onValueChange = (e: React.SyntheticEvent, idx: number) => {
27    const evt = e as React.KeyboardEvent<HTMLInputElement>;
28    if (e.hasOwnProperty('key')) {
29      if (evt.key !== 'Enter') {
30        return;
31      }
32    }
33    const { value, onChange } = this.props;
34
35    // Form event, or Enter
36    const v = evt.currentTarget.value.trim();
37    if (idx < 0) {
38      if (v) {
39        evt.currentTarget.value = ''; // reset last value
40        onChange([...value, v]);
41      }
42      this.setState({ showAdd: false });
43      return;
44    }
45
46    if (!v) {
47      return this.onRemoveString(idx);
48    }
49
50    const copy = [...value];
51    copy[idx] = v;
52    onChange(copy);
53  };
54
55  render() {
56    const { value, item } = this.props;
57    const { showAdd } = this.state;
58    const styles = getStyles(getTheme());
59    const placeholder = item.settings?.placeholder || 'Add text';
60    return (
61      <div>
62        {value.map((v, index) => {
63          return (
64            <Input
65              className={styles.textInput}
66              key={`${index}/${v}`}
67              defaultValue={v || ''}
68              onBlur={(e) => this.onValueChange(e, index)}
69              onKeyDown={(e) => this.onValueChange(e, index)}
70              suffix={<Icon className={styles.trashIcon} name="trash-alt" onClick={() => this.onRemoveString(index)} />}
71            />
72          );
73        })}
74
75        {showAdd ? (
76          <Input
77            autoFocus
78            className={styles.textInput}
79            placeholder={placeholder}
80            defaultValue={''}
81            onBlur={(e) => this.onValueChange(e, -1)}
82            onKeyDown={(e) => this.onValueChange(e, -1)}
83            suffix={<Icon name="plus-circle" />}
84          />
85        ) : (
86          <Button icon="plus" size="sm" variant="secondary" onClick={() => this.setState({ showAdd: true })}>
87            {placeholder}
88          </Button>
89        )}
90      </div>
91    );
92  }
93}
94
95const getStyles = stylesFactory((theme: GrafanaTheme) => {
96  return {
97    textInput: css`
98      margin-bottom: 5px;
99      &:hover {
100        border: 1px solid ${theme.colors.formInputBorderHover};
101      }
102    `,
103    trashIcon: css`
104      color: ${theme.colors.textWeak};
105      cursor: pointer;
106
107      &:hover {
108        color: ${theme.colors.text};
109      }
110    `,
111  };
112});
113