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