From 4fef50272fdf31a6982041e5eaca8c452b4248f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 28 Apr 2022 12:25:51 +0200 Subject: [PATCH] Select: Improve usability slightly (#47796) * Select: Improve usability slightly * Latest change * Fixed test * Updated test --- .../components/Select/DropdownIndicator.tsx | 5 +- .../src/components/Select/InputControl.tsx | 60 +++++++++---------- .../src/components/Select/SelectBase.tsx | 14 ++++- .../src/components/Select/SingleValue.tsx | 15 +++-- ...CreatableSelectPersistedBehaviour.test.tsx | 2 +- .../components/MetricSelect.test.tsx | 6 +- .../querybuilder/components/MetricSelect.tsx | 6 ++ 7 files changed, 66 insertions(+), 42 deletions(-) diff --git a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx index 0a9fc619deb..60122f7bd8e 100644 --- a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx +++ b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx @@ -7,6 +7,7 @@ interface DropdownIndicatorProps { } export const DropdownIndicator: React.FC = ({ isOpen }) => { - const icon = isOpen ? 'angle-up' : 'angle-down'; - return ; + const icon = isOpen ? 'search' : 'angle-down'; + const size = isOpen ? 'sm' : 'md'; + return ; }; diff --git a/packages/grafana-ui/src/components/Select/InputControl.tsx b/packages/grafana-ui/src/components/Select/InputControl.tsx index c41f466163f..e107737495f 100644 --- a/packages/grafana-ui/src/components/Select/InputControl.tsx +++ b/packages/grafana-ui/src/components/Select/InputControl.tsx @@ -17,44 +17,42 @@ interface InputControlProps { innerProps: any; } -const getInputControlStyles = stylesFactory( - (theme: GrafanaTheme2, invalid: boolean, focused: boolean, disabled: boolean, withPrefix: boolean) => { - const styles = getInputStyles({ theme, invalid }); +const getInputControlStyles = stylesFactory((theme: GrafanaTheme2, invalid: boolean, withPrefix: boolean) => { + const styles = getInputStyles({ theme, invalid }); - return { - input: cx( - inputPadding(theme), + return { + input: cx( + inputPadding(theme), + css` + width: 100%; + max-width: 100%; + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + justify-content: space-between; + padding-right: 0; + position: relative; + box-sizing: border-box; + `, + withPrefix && css` - width: 100%; - max-width: 100%; - display: flex; - flex-direction: row; - align-items: center; - flex-wrap: wrap; - justify-content: space-between; - padding-right: 0; - position: relative; - box-sizing: border-box; - `, - withPrefix && - css` - padding-left: 0; - ` - ), - prefix: cx( - styles.prefix, - css` - position: relative; + padding-left: 0; ` - ), - }; - } -); + ), + prefix: cx( + styles.prefix, + css` + position: relative; + ` + ), + }; +}); export const InputControl = React.forwardRef>( function InputControl({ focused, invalid, disabled, children, innerProps, prefix, ...otherProps }, ref) { const theme = useTheme2(); - const styles = getInputControlStyles(theme, invalid, focused, disabled, !!prefix); + const styles = getInputControlStyles(theme, invalid, !!prefix); return (
{prefix &&
{prefix}
} diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index d6ab426a6cb..44b20ad31c3 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -252,7 +252,7 @@ export function SelectBase({ if (allowCustomValue) { ReactSelectComponent = Creatable as any; creatableProps.allowCreateWhileLoading = allowCreateWhileLoading; - creatableProps.formatCreateLabel = formatCreateLabel ?? ((input: string) => `Create: ${input}`); + creatableProps.formatCreateLabel = formatCreateLabel ?? defaultFormatCreateLabel; creatableProps.onCreateOption = onCreateOption; creatableProps.isValidNewOption = isValidNewOption; } @@ -351,3 +351,15 @@ export function SelectBase({ ); } + +function defaultFormatCreateLabel(input: string) { + return ( +
+
{input}
+
+
+ Hit enter to add +
+
+ ); +} diff --git a/packages/grafana-ui/src/components/Select/SingleValue.tsx b/packages/grafana-ui/src/components/Select/SingleValue.tsx index 785978ad195..84fdddf98b7 100644 --- a/packages/grafana-ui/src/components/Select/SingleValue.tsx +++ b/packages/grafana-ui/src/components/Select/SingleValue.tsx @@ -1,7 +1,6 @@ import { css, cx } from '@emotion/css'; import React from 'react'; import { components, GroupBase, SingleValueProps } from 'react-select'; -import tinycolor from 'tinycolor2'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; @@ -22,6 +21,7 @@ const getStyles = (theme: GrafanaTheme2) => { max-width: 100%; grid-area: 1 / 1 / 2 / 3; `; + const spinnerWrapper = css` width: 16px; height: 16px; @@ -39,10 +39,14 @@ const getStyles = (theme: GrafanaTheme2) => { `; const disabled = css` - color: ${tinycolor(theme.colors.text.disabled).setAlpha(0.64).toString()}; + color: ${theme.colors.text.disabled}; `; - return { singleValue, spinnerWrapper, spinnerIcon, disabled }; + const isOpen = css` + color: ${theme.colors.text.disabled}; + `; + + return { singleValue, spinnerWrapper, spinnerIcon, disabled, isOpen }; }; type StylesType = ReturnType; @@ -55,7 +59,10 @@ export const SingleValue = (props: Props) => { const loading = useDelayedSwitch(data.loading || false, { delay: 250, duration: 750 }); return ( - + {data.imgUrl ? ( { // we type in the input 'Option 2', which should prompt an option creation await userEvent.type(input, 'Option 2'); const creatableOption = screen.getByLabelText('Select option'); - expect(creatableOption).toHaveTextContent('Create: Option 2'); + expect(creatableOption).toHaveTextContent('Option 2'); // we click on the creatable option to trigger its creation await userEvent.click(creatableOption); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx index ce7c12f35fd..d527a581baa 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx @@ -26,12 +26,12 @@ describe('MetricSelect', () => { await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(3)); }); - it('shows option to create metric when typing', async () => { + it('shows option to set custom value when typing', async () => { render(); await openMetricSelect(); const input = screen.getByRole('combobox'); - await userEvent.type(input, 'new'); - await waitFor(() => expect(screen.getByText('Create: new')).toBeInTheDocument()); + await userEvent.type(input, 'custom value'); + await waitFor(() => expect(screen.getByText('custom value')).toBeInTheDocument()); }); it('shows searched options when typing', async () => { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx index 671a2c95d93..f5f899238f8 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx @@ -30,6 +30,12 @@ export function MetricSelect({ query, onChange, onGetMetrics }: Props) { if (!label) { return false; } + + // custom value is not a string label but a react node + if (!label.toLowerCase) { + return true; + } + const searchWords = searchQuery.split(splitSeparator); return searchWords.reduce((acc, cur) => acc && label.toLowerCase().includes(cur.toLowerCase()), true); }, []);