mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
UI: Segment fixes (#20947)
* Add support for primitive values/onchange * Fix segment clickaway bug * Fix onchange * Use primitive in cloudwatch * Add placeholder * Use placeholder in cloudwatch editor * Fix lint error * Fix lodash import * Use new component story format * Add support for autofocus * Use selectable value for onchange event * Fix lint error
This commit is contained in:
parent
26789d1eb6
commit
0e4850f203
@ -1,12 +1,6 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from './';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
|
||||
const SegmentStories = storiesOf('UI/Segment/SegmentSync', module);
|
||||
|
||||
const AddButton = (
|
||||
<a className="gf-form-label query-part">
|
||||
@ -15,131 +9,125 @@ const AddButton = (
|
||||
);
|
||||
|
||||
const toOption = (value: any) => ({ label: value, value: value });
|
||||
|
||||
SegmentStories.add('Array Options', () => {
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
options[0].label = 'Option1 Label';
|
||||
return (
|
||||
<UseState initialState={options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<Segment
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<Segment
|
||||
Component={AddButton}
|
||||
onChange={(value: SelectableValue<string>) => action('New value added')(value)}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
);
|
||||
});
|
||||
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
const groupedOptions = [
|
||||
{ label: 'Names', options: ['Jane', 'Tom', 'Lisa'].map(toOption) },
|
||||
{ label: 'Prime', options: [2, 3, 5, 7, 11, 13].map(toOption) },
|
||||
];
|
||||
|
||||
SegmentStories.add('Grouped Array Options', () => {
|
||||
return (
|
||||
<UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<Segment
|
||||
value={value}
|
||||
options={groupedOptions}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<Segment
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
options={groupedOptions}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
);
|
||||
});
|
||||
const SegmentFrame = ({ options, children }: any) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
{children}
|
||||
<Segment Component={AddButton} onChange={({ value }) => action('New value added')(value)} options={options} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
SegmentStories.add('With custom options allowed', () => {
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
export const ArrayOptions = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<UseState initialState={options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<Segment
|
||||
allowCustomValue
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<Segment
|
||||
allowCustomValue
|
||||
Component={AddButton}
|
||||
onChange={(value: SelectableValue<string>) => action('New value added')(value)}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const CustomLabelComponent = ({ value: { value } }: any) => <div className="gf-form-label">custom({value})</div>;
|
||||
export default {
|
||||
title: 'UI/Segment/SegmentSync',
|
||||
component: ArrayOptions,
|
||||
};
|
||||
|
||||
SegmentStories.add('Custom Label Field', () => {
|
||||
export const ArrayOptionsWithPrimitiveValue = () => {
|
||||
const [value, setValue] = useState('Option1');
|
||||
return (
|
||||
<UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
|
||||
{(value, setValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<Segment
|
||||
Component={<CustomLabelComponent value={value} />}
|
||||
options={groupedOptions}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<Segment
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
options={groupedOptions}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const ArrayOptionsWithPlaceholder = () => {
|
||||
const [value, setValue] = useState<any>(undefined);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
value={value}
|
||||
options={options}
|
||||
placeholder="Enter a value"
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const GroupedArrayOptions = () => {
|
||||
const [value, setValue] = useState<any>(groupedOptions[0].options[0]);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
value={value}
|
||||
options={groupedOptions}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomOptionsAllowed = () => {
|
||||
const [value, setValue] = useState(options[0]);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
allowCustomValue
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>;
|
||||
|
||||
export const CustomLabelField = () => {
|
||||
const [value, setValue] = useState<any>(groupedOptions[0].options[0].value);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
Component={<CustomLabelComponent value={value} />}
|
||||
options={groupedOptions}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SegmentSelect, useExpandableLabel, SegmentProps } from './';
|
||||
|
||||
export interface SegmentSyncProps<T> extends SegmentProps<T> {
|
||||
value?: SelectableValue<T>;
|
||||
value?: T | SelectableValue<T>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
options: Array<SelectableValue<T>>;
|
||||
}
|
||||
@ -16,20 +17,28 @@ export function Segment<T>({
|
||||
Component,
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
}: React.PropsWithChildren<SegmentSyncProps<T>>) {
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
|
||||
if (!expanded) {
|
||||
const label = _.isObject(value) ? value.label : value;
|
||||
return (
|
||||
<Label
|
||||
Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value && value.label}</a>}
|
||||
Component={
|
||||
Component || (
|
||||
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}>
|
||||
{label || placeholder}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
value={value}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={options}
|
||||
width={width}
|
||||
onClickOutside={() => setExpanded(false)}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
const SegmentStories = storiesOf('UI/Segment/SegmentAsync', module);
|
||||
import { SegmentAsync } from './';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
|
||||
const AddButton = (
|
||||
<a className="gf-form-label query-part">
|
||||
@ -13,132 +10,116 @@ const AddButton = (
|
||||
);
|
||||
|
||||
const toOption = (value: any) => ({ label: value, value: value });
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
|
||||
const loadOptions = (options: any): Promise<Array<SelectableValue<string>>> =>
|
||||
new Promise(res => setTimeout(() => res(options), 2000));
|
||||
|
||||
SegmentStories.add('Array Options', () => {
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
return (
|
||||
<UseState initialState={options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentAsync
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
);
|
||||
});
|
||||
const SegmentFrame = ({ loadOptions, children }: any) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
{children}
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const groupedOptions = [
|
||||
export const ArrayOptions = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<SegmentFrame loadOptions={() => loadOptions(options)}>
|
||||
<SegmentAsync
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'UI/Segment/SegmentAsync',
|
||||
component: ArrayOptions,
|
||||
};
|
||||
|
||||
export const ArrayOptionsWithPrimitiveValue = () => {
|
||||
const [value, setValue] = useState(options[0].value);
|
||||
return (
|
||||
<SegmentFrame loadOptions={() => loadOptions(options)}>
|
||||
<SegmentAsync
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
const groupedOptions: any = [
|
||||
{ label: 'Names', options: ['Jane', 'Tom', 'Lisa'].map(toOption) },
|
||||
{ label: 'Prime', options: [2, 3, 5, 7, 11, 13].map(toOption) },
|
||||
];
|
||||
|
||||
SegmentStories.add('Grouped Array Options', () => {
|
||||
export const GroupedArrayOptions = () => {
|
||||
const [value, setValue] = useState(groupedOptions[0].options[0]);
|
||||
return (
|
||||
<UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentAsync
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame loadOptions={() => loadOptions(groupedOptions)}>
|
||||
<SegmentAsync
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
SegmentStories.add('With custom options allowed', () => {
|
||||
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
|
||||
export const CustomOptionsAllowed = () => {
|
||||
const [value, setValue] = useState(groupedOptions[0].options[0]);
|
||||
return (
|
||||
<UseState initialState={options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame loadOptions={() => loadOptions(groupedOptions)}>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const CustomLabelComponent = ({ value: { value } }: any) => <div className="gf-form-label">custom({value})</div>;
|
||||
SegmentStories.add('Custom Label Field', () => {
|
||||
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>;
|
||||
|
||||
export const CustomLabel = () => {
|
||||
const [value, setValue] = useState(groupedOptions[0].options[0].value);
|
||||
return (
|
||||
<UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentAsync
|
||||
Component={<CustomLabelComponent value={value} />}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
onChange={item => {
|
||||
updateValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={value => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame loadOptions={() => loadOptions(groupedOptions)}>
|
||||
<SegmentAsync
|
||||
Component={<CustomLabelComponent value={value} />}
|
||||
loadOptions={() => loadOptions(groupedOptions)}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SegmentSelect } from './SegmentSelect';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { useExpandableLabel, SegmentProps } from '.';
|
||||
|
||||
export interface SegmentAsyncProps<T> extends SegmentProps<T> {
|
||||
value?: SelectableValue<T>;
|
||||
value?: T | SelectableValue<T>;
|
||||
loadOptions: (query?: string) => Promise<Array<SelectableValue<T>>>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
}
|
||||
@ -17,12 +18,14 @@ export function SegmentAsync<T>({
|
||||
Component,
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
}: React.PropsWithChildren<SegmentAsyncProps<T>>) {
|
||||
const [selectPlaceholder, setSelectPlaceholder] = useState<string>('');
|
||||
const [loadedOptions, setLoadedOptions] = useState<Array<SelectableValue<T>>>([]);
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
|
||||
if (!expanded) {
|
||||
const label = _.isObject(value) ? value.label : value;
|
||||
return (
|
||||
<Label
|
||||
onClick={async () => {
|
||||
@ -31,14 +34,20 @@ export function SegmentAsync<T>({
|
||||
setLoadedOptions(opts);
|
||||
setSelectPlaceholder(opts.length ? '' : 'No options found');
|
||||
}}
|
||||
Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value && value.label}</a>}
|
||||
Component={
|
||||
Component || (
|
||||
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}>
|
||||
{label || placeholder}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
value={value}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={loadedOptions}
|
||||
width={width}
|
||||
noOptionsMessage={selectPlaceholder}
|
||||
|
@ -1,29 +1,84 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
const SegmentStories = storiesOf('UI/Segment/SegmentInput', module);
|
||||
import { SegmentInput } from '.';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
|
||||
SegmentStories.add('Segment Input', () => {
|
||||
const SegmentFrame = ({ children }: any) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export const BasicInput = () => {
|
||||
const [value, setValue] = useState('some text');
|
||||
return (
|
||||
<UseState initialState={'some text'}>
|
||||
{(value, updateValue) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentInput
|
||||
value={value}
|
||||
onChange={text => {
|
||||
updateValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</UseState>
|
||||
<SegmentFrame>
|
||||
<SegmentInput
|
||||
value={value}
|
||||
onChange={text => {
|
||||
setValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'UI/Segment/SegmentInput',
|
||||
component: BasicInput,
|
||||
};
|
||||
|
||||
export const BasicInputWithPlaceholder = () => {
|
||||
const [value, setValue] = useState('');
|
||||
return (
|
||||
<SegmentFrame>
|
||||
<SegmentInput
|
||||
placeholder="add text"
|
||||
value={value}
|
||||
onChange={text => {
|
||||
setValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
const InputComponent = ({ initialValue }: any) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
return (
|
||||
<SegmentInput
|
||||
placeholder="add text"
|
||||
autofocus
|
||||
value={value}
|
||||
onChange={text => {
|
||||
setValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const InputWithAutoFocus = () => {
|
||||
const [inputComponents, setInputComponents] = useState<any>([]);
|
||||
return (
|
||||
<SegmentFrame>
|
||||
{inputComponents.map((InputComponent: any) => (
|
||||
<InputComponent intitialValue="test"></InputComponent>
|
||||
))}
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
onClick={() => {
|
||||
setInputComponents([...inputComponents, InputComponent]);
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-plus" />
|
||||
</a>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { useExpandableLabel, SegmentProps } from '.';
|
||||
export interface SegmentInputProps<T> extends SegmentProps<T> {
|
||||
value: string | number;
|
||||
onChange: (text: string | number) => void;
|
||||
autofocus?: boolean;
|
||||
}
|
||||
|
||||
const FONT_SIZE = 14;
|
||||
@ -16,16 +17,30 @@ export function SegmentInput<T>({
|
||||
onChange,
|
||||
Component,
|
||||
className,
|
||||
placeholder,
|
||||
autofocus = false,
|
||||
}: React.PropsWithChildren<SegmentInputProps<T>>) {
|
||||
const ref = useRef(null);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [value, setValue] = useState<number | string>(initialValue);
|
||||
const [inputWidth, setInputWidth] = useState<number>(measureText(initialValue.toString(), FONT_SIZE).width);
|
||||
const [Label, , expanded, setExpanded] = useExpandableLabel(false);
|
||||
useClickAway(ref, () => setExpanded(false));
|
||||
const [inputWidth, setInputWidth] = useState<number>(measureText((initialValue || '').toString(), FONT_SIZE).width);
|
||||
const [Label, , expanded, setExpanded] = useExpandableLabel(autofocus);
|
||||
|
||||
useClickAway(ref, () => {
|
||||
setExpanded(false);
|
||||
onChange(value);
|
||||
});
|
||||
|
||||
if (!expanded) {
|
||||
return (
|
||||
<Label Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{initialValue}</a>} />
|
||||
<Label
|
||||
Component={
|
||||
Component || (
|
||||
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}>
|
||||
{initialValue || placeholder}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,4 +4,5 @@ export interface SegmentProps<T> {
|
||||
Component?: ReactElement;
|
||||
className?: string;
|
||||
allowCustomValue?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
@ -34,15 +34,13 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
return options.filter(({ value }) => !Object.keys(data).includes(value));
|
||||
};
|
||||
|
||||
const toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(data).map(([key, value], index) => (
|
||||
<Fragment key={index}>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={toOption(key)}
|
||||
value={key}
|
||||
loadOptions={() => loadKeys().then(keys => [removeOption, ...excludeUsedKeys(keys)])}
|
||||
onChange={({ value: newKey }) => {
|
||||
const { [key]: value, ...newDimensions } = data;
|
||||
@ -56,7 +54,8 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
<label className="gf-form-label query-segment-operator">=</label>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={toOption(value || 'select dimension value')}
|
||||
value={value}
|
||||
placeholder="select dimension value"
|
||||
loadOptions={() => loadValues(key)}
|
||||
onChange={({ value: newValue }) => setData({ ...data, [key]: newValue })}
|
||||
/>
|
||||
|
@ -112,7 +112,8 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
<>
|
||||
<QueryInlineField label="Region">
|
||||
<Segment
|
||||
value={this.toOption(query.region || 'Select region')}
|
||||
value={query.region}
|
||||
placeholder="Select region"
|
||||
options={regions}
|
||||
allowCustomValue
|
||||
onChange={({ value: region }) => this.onChange({ ...query, region })}
|
||||
@ -123,7 +124,8 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
<>
|
||||
<QueryInlineField label="Namespace">
|
||||
<Segment
|
||||
value={this.toOption(query.namespace || 'Select namespace')}
|
||||
value={query.namespace}
|
||||
placeholder="Select namespace"
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={({ value: namespace }) => this.onChange({ ...query, namespace })}
|
||||
@ -132,7 +134,8 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
<QueryInlineField label="Metric Name">
|
||||
<SegmentAsync
|
||||
value={this.toOption(query.metricName || 'Select metric name')}
|
||||
value={query.metricName}
|
||||
placeholder="Select metric name"
|
||||
allowCustomValue
|
||||
loadOptions={this.loadMetricNames}
|
||||
onChange={({ value: metricName }) => this.onChange({ ...query, metricName })}
|
||||
|
@ -12,7 +12,6 @@ export interface Props {
|
||||
|
||||
const removeText = '-- remove stat --';
|
||||
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
|
||||
const toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
|
||||
<>
|
||||
@ -21,7 +20,7 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
|
||||
<Segment
|
||||
allowCustomValue
|
||||
key={value + index}
|
||||
value={toOption(value)}
|
||||
value={value}
|
||||
options={[removeOption, ...stats, variableOptionGroup]}
|
||||
onChange={({ value }) =>
|
||||
onChange(
|
||||
|
@ -7,6 +7,10 @@
|
||||
color: $orange;
|
||||
}
|
||||
|
||||
.query-placeholder {
|
||||
color: $gray-2;
|
||||
}
|
||||
|
||||
.query-editor-rows {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user