Commit d0d59b80 by Torkel Ödegaard Committed by GitHub

Select: Allow custom value for selects (#19775)

* WIP: simple poc of allow custom value for selects

* Add support for custom value in segment

* Update snapshots
parent 7963422d
......@@ -80,6 +80,38 @@ SegmentStories.add('Grouped Array Options', () => {
);
});
SegmentStories.add('With custom options allowed', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
return (
<UseState initialState={options[0].value}>
{(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={(value: SelectableValue<string>) => {
updateValue(value);
action('Segment value changed')(value);
}}
/>
<Segment
allowCustomValue
Component={AddButton}
onChange={(value: SelectableValue<string>) => action('New value added')(value)}
options={options}
/>
</div>
</>
)}
</UseState>
);
});
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>;
SegmentStories.add('Custom Label Field', () => {
......
......@@ -13,6 +13,7 @@ export function Segment<T>({
onChange,
Component,
className,
allowCustomValue,
}: React.PropsWithChildren<SegmentSyncProps<T>>) {
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
......@@ -25,6 +26,7 @@ export function Segment<T>({
width={width}
options={options}
onClickOutside={() => setExpanded(false)}
allowCustomValue={allowCustomValue}
onChange={value => {
setExpanded(false);
onChange(value);
......
......@@ -81,8 +81,39 @@ SegmentStories.add('Grouped Array Options', () => {
);
});
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>;
SegmentStories.add('With custom options allowed', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
return (
<UseState initialState={options[0].value}>
{(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={value => {
updateValue(value);
action('Segment value changed')(value);
}}
/>
<SegmentAsync
allowCustomValue
Component={AddButton}
onChange={value => action('New value added')(value)}
loadOptions={() => loadOptions(options)}
/>
</div>
</>
)}
</UseState>
);
});
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>;
SegmentStories.add('Custom Label Field', () => {
return (
<UseState initialState={groupedOptions[0].options[0].value}>
......
......@@ -14,6 +14,7 @@ export function SegmentAsync<T>({
loadOptions,
Component,
className,
allowCustomValue,
}: React.PropsWithChildren<SegmentAsyncProps<T>>) {
const [selectPlaceholder, setSelectPlaceholder] = useState<string>('');
const [loadedOptions, setLoadedOptions] = useState<Array<SelectableValue<T>>>([]);
......@@ -38,6 +39,7 @@ export function SegmentAsync<T>({
width={width}
options={loadedOptions}
noOptionsMessage={selectPlaceholder}
allowCustomValue={allowCustomValue}
onClickOutside={() => {
setSelectPlaceholder('');
setLoadedOptions([]);
......
......@@ -10,6 +10,7 @@ export interface Props<T> {
onClickOutside: () => void;
width: number;
noOptionsMessage?: string;
allowCustomValue?: boolean;
}
export function SegmentSelect<T>({
......@@ -18,6 +19,7 @@ export function SegmentSelect<T>({
onClickOutside,
width,
noOptionsMessage = '',
allowCustomValue = false,
}: React.PropsWithChildren<Props<T>>) {
const ref = useRef(null);
......@@ -39,6 +41,7 @@ export function SegmentSelect<T>({
isOpen={true}
onChange={({ value }) => onChange(value!)}
options={options}
allowCustomValue={allowCustomValue}
/>
</div>
);
......
......@@ -5,4 +5,5 @@ export interface SegmentProps<T> {
value?: T;
Component?: ReactElement;
className?: string;
allowCustomValue?: boolean;
}
......@@ -36,3 +36,30 @@ SelectStories.add('default', () => {
</UseState>
);
});
SelectStories.add('With allowCustomValue', () => {
const intialState: SelectableValue<string> = { label: 'A label', value: 'A value' };
const value = object<SelectableValue<string>>('Selected Value:', intialState);
const options = object<Array<SelectableValue<string>>>('Options:', [
intialState,
{ label: 'Another label', value: 'Another value' },
]);
return (
<UseState initialState={value}>
{(value, updateValue) => {
return (
<Select
value={value}
options={options}
allowCustomValue={true}
onChange={value => {
action('onChanged fired')(value);
updateValue(value);
}}
/>
);
}}
</UseState>
);
});
......@@ -4,7 +4,9 @@ import React, { PureComponent } from 'react';
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
// @ts-ignore
import { default as ReactSelect } from '@torkelo/react-select';
import { default as ReactSelect, Creatable } from '@torkelo/react-select';
// @ts-ignore
import { Creatable } from '@torkelo/react-select/lib/creatable';
// @ts-ignore
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
// @ts-ignore
......@@ -48,6 +50,7 @@ export interface CommonProps<T> {
onOpenMenu?: () => void;
onCloseMenu?: () => void;
tabSelectsValue?: boolean;
allowCustomValue: boolean;
}
export interface SelectProps<T> extends CommonProps<T> {
......@@ -83,6 +86,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
backspaceRemovesValue: true,
maxMenuHeight: 300,
tabSelectsValue: true,
allowCustomValue: false,
components: {
Option: SelectOption,
SingleValue,
......@@ -120,6 +124,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
tabSelectsValue,
onCloseMenu,
onOpenMenu,
allowCustomValue,
} = this.props;
let widthClass = '';
......@@ -127,6 +132,14 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
widthClass = 'width-' + width;
}
let SelectComponent: ReactSelect | Creatable = ReactSelect;
const creatableOptions: any = {};
if (allowCustomValue) {
SelectComponent = Creatable;
creatableOptions.formatCreateLabel = (input: string) => input;
}
const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
const selectComponents = { ...Select.defaultProps.components, ...components };
......@@ -134,7 +147,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
<WrapInTooltip onCloseMenu={onCloseMenu} onOpenMenu={onOpenMenu} tooltipContent={tooltipContent} isOpen={isOpen}>
{(onOpenMenuInternal, onCloseMenuInternal) => {
return (
<ReactSelect
<SelectComponent
classNamePrefix="gf-form-select-box"
className={selectClassNames}
components={selectComponents}
......@@ -162,6 +175,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
onMenuOpen={onOpenMenuInternal}
onMenuClose={onCloseMenuInternal}
tabSelectsValue={tabSelectsValue}
{...creatableOptions}
/>
);
}}
......
......@@ -81,6 +81,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned off should not ren
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="gf-form-select-box__control--menu-right"
......@@ -166,6 +167,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render p
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="gf-form-select-box__control--menu-right"
......
......@@ -43,6 +43,7 @@ exports[`Render should disable log analytics credentials form 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......@@ -138,6 +139,7 @@ exports[`Render should enable azure log analytics load workspaces button 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......@@ -233,6 +235,7 @@ exports[`Render should render component 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......
......@@ -18,6 +18,7 @@ exports[`Render should disable azure monitor secret input 1`] = `
Azure Cloud
</Component>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
......@@ -163,6 +164,7 @@ exports[`Render should disable azure monitor secret input 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......@@ -233,6 +235,7 @@ exports[`Render should enable azure monitor load subscriptions button 1`] = `
Azure Cloud
</Component>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
......@@ -368,6 +371,7 @@ exports[`Render should enable azure monitor load subscriptions button 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......@@ -438,6 +442,7 @@ exports[`Render should render component 1`] = `
Azure Cloud
</Component>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
......@@ -573,6 +578,7 @@ exports[`Render should render component 1`] = `
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......
......@@ -68,6 +68,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
Resolution
</div>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......@@ -134,6 +135,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
Format
</div>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
......
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