mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
GrafanaUI: Fix styles for invalid selects & DataSourcePicker (#53476)
* GrafanaUI: fix styles for invalid select & DataSourcePicker * Apply suggestions from code review Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> * fix focus issues & tests * remove unused import * TypeScript work in progress * Move react select props to types.ts Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: eledobleefe <laura.fernandez@grafana.com> Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:
parent
a58edc9f5e
commit
26524e3ff1
@ -1612,11 +1612,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "12"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "12"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "13"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "14"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"]
|
|
||||||
],
|
],
|
||||||
"packages/grafana-ui/src/components/Select/SelectMenu.tsx:5381": [
|
"packages/grafana-ui/src/components/Select/SelectMenu.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
|
@ -46,6 +46,7 @@ export interface DataSourcePickerProps {
|
|||||||
inputId?: string;
|
inputId?: string;
|
||||||
filter?: (dataSource: DataSourceInstanceSettings) => boolean;
|
filter?: (dataSource: DataSourceInstanceSettings) => boolean;
|
||||||
onClear?: () => void;
|
onClear?: () => void;
|
||||||
|
invalid?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +187,7 @@ export class DataSourcePicker extends PureComponent<DataSourcePickerProps, DataS
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
noOptionsMessage="No datasources found"
|
noOptionsMessage="No datasources found"
|
||||||
value={value ?? null}
|
value={value ?? null}
|
||||||
invalid={!!error}
|
invalid={Boolean(error) || Boolean(this.props.invalid)}
|
||||||
getOptionLabel={(o) => {
|
getOptionLabel={(o) => {
|
||||||
if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) {
|
if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
|
||||||
import React from 'react';
|
|
||||||
import { components, ContainerProps, GroupBase } from 'react-select';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
|
|
||||||
import { stylesFactory } from '../../themes';
|
|
||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
|
||||||
import { focusCss } from '../../themes/mixins';
|
|
||||||
import { sharedInputStyle } from '../Forms/commonStyles';
|
|
||||||
import { getInputStyles } from '../Input/Input';
|
|
||||||
|
|
||||||
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
|
|
||||||
props: ContainerProps<Option, isMulti, Group> & { isFocused: boolean }
|
|
||||||
) => {
|
|
||||||
const { isDisabled, isFocused, children } = props;
|
|
||||||
|
|
||||||
const theme = useTheme2();
|
|
||||||
const styles = getSelectContainerStyles(theme, isFocused, isDisabled);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}>
|
|
||||||
{children}
|
|
||||||
</components.SelectContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectContainerStyles = stylesFactory((theme: GrafanaTheme2, focused: boolean, disabled: boolean) => {
|
|
||||||
const styles = getInputStyles({ theme, invalid: false });
|
|
||||||
|
|
||||||
return {
|
|
||||||
wrapper: cx(
|
|
||||||
styles.wrapper,
|
|
||||||
sharedInputStyle(theme, false),
|
|
||||||
focused &&
|
|
||||||
css`
|
|
||||||
${focusCss(theme.v1)}
|
|
||||||
`,
|
|
||||||
disabled && styles.inputDisabled,
|
|
||||||
css`
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
min-height: 32px;
|
|
||||||
height: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
/* Input padding is applied to the InputControl so the menu is aligned correctly */
|
|
||||||
padding: 0;
|
|
||||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
|
||||||
`
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
@ -18,7 +18,6 @@ export function MultiSelect<T>(props: MultiSelectCommonProps<T>) {
|
|||||||
export interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
export interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
||||||
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
||||||
value?: SelectableValue<T> | null;
|
value?: SelectableValue<T> | null;
|
||||||
invalid?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AsyncSelect<T>(props: AsyncSelectProps<T>) {
|
export function AsyncSelect<T>(props: AsyncSelectProps<T>) {
|
||||||
|
@ -317,28 +317,28 @@ export function SelectBase<T>({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
LoadingIndicator(props: any) {
|
LoadingIndicator() {
|
||||||
return <Spinner inline={true} />;
|
return <Spinner inline />;
|
||||||
},
|
},
|
||||||
LoadingMessage(props: any) {
|
LoadingMessage() {
|
||||||
return <div className={styles.loadingMessage}>{loadingMessage}</div>;
|
return <div className={styles.loadingMessage}>{loadingMessage}</div>;
|
||||||
},
|
},
|
||||||
NoOptionsMessage(props: any) {
|
NoOptionsMessage() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.loadingMessage} aria-label="No options provided">
|
<div className={styles.loadingMessage} aria-label="No options provided">
|
||||||
{noOptionsMessage}
|
{noOptionsMessage}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
DropdownIndicator(props: any) {
|
DropdownIndicator(props) {
|
||||||
return <DropdownIndicator isOpen={props.selectProps.menuIsOpen} />;
|
return <DropdownIndicator isOpen={props.selectProps.menuIsOpen} />;
|
||||||
},
|
},
|
||||||
SingleValue(props: any) {
|
SingleValue(props: any) {
|
||||||
return <SingleValue {...props} disabled={disabled} />;
|
return <SingleValue {...props} disabled={disabled} />;
|
||||||
},
|
},
|
||||||
|
SelectContainer,
|
||||||
MultiValueContainer: MultiValueContainer,
|
MultiValueContainer: MultiValueContainer,
|
||||||
MultiValueRemove: MultiValueRemove,
|
MultiValueRemove: MultiValueRemove,
|
||||||
SelectContainer,
|
|
||||||
...components,
|
...components,
|
||||||
}}
|
}}
|
||||||
styles={selectStyles}
|
styles={selectStyles}
|
||||||
|
@ -10,19 +10,24 @@ import { focusCss } from '../../themes/mixins';
|
|||||||
import { sharedInputStyle } from '../Forms/commonStyles';
|
import { sharedInputStyle } from '../Forms/commonStyles';
|
||||||
import { getInputStyles } from '../Input/Input';
|
import { getInputStyles } from '../Input/Input';
|
||||||
|
|
||||||
// isFocus prop is actually available, but its not in the types for the version we have.
|
import { CustomComponentProps } from './types';
|
||||||
export interface SelectContainerProps<Option, isMulti extends boolean, Group extends GroupBase<Option>>
|
|
||||||
extends BaseContainerProps<Option, isMulti, Group> {
|
// prettier-ignore
|
||||||
isFocused: boolean;
|
export type SelectContainerProps<Option, isMulti extends boolean, Group extends GroupBase<Option>> =
|
||||||
}
|
BaseContainerProps<Option, isMulti, Group> & CustomComponentProps<Option, isMulti, Group>;
|
||||||
|
|
||||||
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
|
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
|
||||||
props: SelectContainerProps<Option, isMulti, Group>
|
props: SelectContainerProps<Option, isMulti, Group>
|
||||||
) => {
|
) => {
|
||||||
const { isDisabled, isFocused, children } = props;
|
const {
|
||||||
|
isDisabled,
|
||||||
|
isFocused,
|
||||||
|
children,
|
||||||
|
selectProps: { invalid = false },
|
||||||
|
} = props;
|
||||||
|
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = getSelectContainerStyles(theme, isFocused, isDisabled);
|
const styles = getSelectContainerStyles(theme, isFocused, isDisabled, invalid);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}>
|
<components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}>
|
||||||
@ -31,35 +36,37 @@ export const SelectContainer = <Option, isMulti extends boolean, Group extends G
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelectContainerStyles = stylesFactory((theme: GrafanaTheme2, focused: boolean, disabled: boolean) => {
|
const getSelectContainerStyles = stylesFactory(
|
||||||
const styles = getInputStyles({ theme, invalid: false });
|
(theme: GrafanaTheme2, focused: boolean, disabled: boolean, invalid: boolean) => {
|
||||||
|
const styles = getInputStyles({ theme, invalid });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wrapper: cx(
|
wrapper: cx(
|
||||||
styles.wrapper,
|
styles.wrapper,
|
||||||
sharedInputStyle(theme, false),
|
sharedInputStyle(theme, invalid),
|
||||||
focused &&
|
focused &&
|
||||||
|
css`
|
||||||
|
${focusCss(theme.v1)}
|
||||||
|
`,
|
||||||
|
disabled && styles.inputDisabled,
|
||||||
css`
|
css`
|
||||||
${focusCss(theme.v1)}
|
position: relative;
|
||||||
`,
|
box-sizing: border-box;
|
||||||
disabled && styles.inputDisabled,
|
/* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
|
||||||
css`
|
flex-direction: row;
|
||||||
position: relative;
|
flex-wrap: wrap;
|
||||||
box-sizing: border-box;
|
align-items: stretch;
|
||||||
/* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
|
justify-content: space-between;
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: stretch;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
/* Input padding is applied to the InputControl so the menu is aligned correctly */
|
/* Input padding is applied to the InputControl so the menu is aligned correctly */
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||||
`
|
`
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActionMeta as SelectActionMeta, GroupBase, OptionsOrGroups } from 'react-select';
|
import {
|
||||||
|
ActionMeta as SelectActionMeta,
|
||||||
|
CommonProps as ReactSelectCommonProps,
|
||||||
|
GroupBase,
|
||||||
|
OptionsOrGroups,
|
||||||
|
} from 'react-select';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
@ -103,10 +108,13 @@ export interface MultiSelectCommonProps<T> extends Omit<SelectCommonProps<T>, 'o
|
|||||||
onChange: (item: Array<SelectableValue<T>>) => {} | void;
|
onChange: (item: Array<SelectableValue<T>>) => {} | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the type of *our* SelectBase component, not ReactSelect's prop, although
|
||||||
|
// they should be mostly compatible.
|
||||||
export interface SelectBaseProps<T> extends SelectCommonProps<T>, SelectAsyncProps<T> {
|
export interface SelectBaseProps<T> extends SelectCommonProps<T>, SelectAsyncProps<T> {
|
||||||
invalid?: boolean;
|
invalid?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is used for the `renderControl` prop on *our* SelectBase component
|
||||||
export interface CustomControlProps<T> {
|
export interface CustomControlProps<T> {
|
||||||
ref: React.Ref<any>;
|
ref: React.Ref<any>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -133,3 +141,20 @@ export type SelectOptions<T = any> =
|
|||||||
| Array<SelectableValue<T> | SelectableOptGroup<T> | Array<SelectableOptGroup<T>>>;
|
| Array<SelectableValue<T> | SelectableOptGroup<T> | Array<SelectableOptGroup<T>>>;
|
||||||
|
|
||||||
export type FormatOptionLabelMeta<T> = { context: string; inputValue: string; selectValue: Array<SelectableValue<T>> };
|
export type FormatOptionLabelMeta<T> = { context: string; inputValue: string; selectValue: Array<SelectableValue<T>> };
|
||||||
|
|
||||||
|
// This is the type of `selectProps` our custom components (like SelectContainer, etc) recieve
|
||||||
|
// It's slightly different to the base react select props because we pass in additional props directly to
|
||||||
|
// react select
|
||||||
|
export type ReactSelectProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = ReactSelectCommonProps<
|
||||||
|
Option,
|
||||||
|
IsMulti,
|
||||||
|
Group
|
||||||
|
>['selectProps'] & {
|
||||||
|
invalid: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use this type when implementing custom components for react select.
|
||||||
|
// See SelectContainerProps in SelectContainer.tsx
|
||||||
|
export interface CustomComponentProps<Option, isMulti extends boolean, Group extends GroupBase<Option>> {
|
||||||
|
selectProps: ReactSelectProps<Option, isMulti, Group>;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user