Commit 53b9f325 by Ryan McKinley Committed by GitHub

OptionsUI: support string list (#25134)

parent d5266470
......@@ -75,6 +75,10 @@ export interface OptionsUIRegistryBuilderAPI<
config: OptionEditorConfig<TOptions, TSettings, string>
): this;
addStringArray?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
config: OptionEditorConfig<TOptions, TSettings, string[]>
): this;
addSelect?<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: OptionEditorConfig<TOptions, TSettings, TOption>
): this;
......
......@@ -143,6 +143,16 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde
});
}
addStringArray<TSettings>(
config: PanelOptionsEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string[]>
) {
return this.addCustomEditor({
...config,
id: config.path,
editor: standardEditorsRegistry.get('strings').editor as any,
});
}
addSelect<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: PanelOptionsEditorConfig<TOptions, TSettings, TOption>
) {
......
import React from 'react';
import { FieldConfigEditorProps, StringFieldConfigSettings, GrafanaTheme } from '@grafana/data';
import { Input } from '../Input/Input';
import { Icon } from '../Icon/Icon';
import { stylesFactory, getTheme } from '../../themes';
import { css } from 'emotion';
import { Button } from '../Button';
type Props = FieldConfigEditorProps<string[], StringFieldConfigSettings>;
interface State {
showAdd: boolean;
}
export class StringArrayEditor extends React.PureComponent<Props, State> {
state = {
showAdd: false,
};
onRemoveString = (index: number) => {
const { value, onChange } = this.props;
const copy = [...value];
copy.splice(index, 1);
onChange(copy);
};
onValueChange = (e: React.SyntheticEvent, idx: number) => {
const evt = e as React.KeyboardEvent<HTMLInputElement>;
if (e.hasOwnProperty('key')) {
if (evt.key !== 'Enter') {
return;
}
}
const { value, onChange } = this.props;
// Form event, or Enter
const v = evt.currentTarget.value.trim();
if (idx < 0) {
if (v) {
evt.currentTarget.value = ''; // reset last value
onChange([...value, v]);
}
this.setState({ showAdd: false });
return;
}
if (!v) {
return this.onRemoveString(idx);
}
const copy = [...value];
copy[idx] = v;
onChange(copy);
};
render() {
const { value, item } = this.props;
const { showAdd } = this.state;
const styles = getStyles(getTheme());
const placeholder = item.settings?.placeholder || 'Add text';
return (
<div>
{value.map((v, index) => {
return (
<Input
className={styles.textInput}
key={`${index}/${v}`}
defaultValue={v || ''}
onBlur={e => this.onValueChange(e, index)}
onKeyDown={e => this.onValueChange(e, index)}
suffix={<Icon className={styles.trashIcon} name="trash-alt" onClick={() => this.onRemoveString(index)} />}
/>
);
})}
{showAdd ? (
<Input
autoFocus
className={styles.textInput}
placeholder={placeholder}
defaultValue={''}
onBlur={e => this.onValueChange(e, -1)}
onKeyDown={e => this.onValueChange(e, -1)}
suffix={<Icon name="plus-circle" />}
/>
) : (
<Button icon="plus" size="sm" variant="secondary" onClick={() => this.setState({ showAdd: true })}>
{placeholder}
</Button>
)}
</div>
);
}
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
textInput: css`
margin-bottom: 5px;
&:hover {
border: 1px solid ${theme.colors.formInputBorderHover};
}
`,
trashIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
&:hover {
color: ${theme.colors.text};
}
`,
};
});
......@@ -118,6 +118,7 @@ export { Slider } from './Slider/Slider';
// TODO: namespace!!
export { StringValueEditor } from './OptionsUI/string';
export { StringArrayEditor } from './OptionsUI/strings';
export { NumberValueEditor } from './OptionsUI/number';
export { SelectValueEditor } from './OptionsUI/select';
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
......
......@@ -20,7 +20,13 @@ import {
} from '@grafana/data';
import { Switch } from '../components/Switch/Switch';
import { NumberValueEditor, RadioButtonGroup, StringValueEditor, SelectValueEditor } from '../components';
import {
NumberValueEditor,
RadioButtonGroup,
StringValueEditor,
StringArrayEditor,
SelectValueEditor,
} from '../components';
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
import { UnitValueEditor } from '../components/OptionsUI/units';
......@@ -227,6 +233,13 @@ export const getStandardOptionEditors = () => {
editor: StringValueEditor as any,
};
const strings: StandardEditorsRegistryItem<string[]> = {
id: 'strings',
name: 'String array',
description: 'An array of strings',
editor: StringArrayEditor as any,
};
const boolean: StandardEditorsRegistryItem<boolean> = {
id: 'boolean',
name: 'Boolean',
......@@ -290,5 +303,5 @@ export const getStandardOptionEditors = () => {
description: '',
};
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color, statsPicker];
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color, statsPicker, strings];
};
......@@ -19,6 +19,15 @@ export const plugin = new PanelPlugin<TextOptions>(TextPanel)
},
defaultValue: 'markdown',
})
.addStringArray({
path: 'strings',
name: 'String Array',
description: 'list of strings',
settings: {
placeholder: 'Add a string value (text2 demo)',
},
defaultValue: ['hello', 'world'],
})
.addTextInput({
path: 'content',
name: 'Content',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment