mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana-UI: Editor UI components (#41136)
* Grafana-UI: Update theme.spacing to support string value when called with just one arugment This allows theme.spacing("auto") to be valid * Grafana-UI: Support width="auto" for Select component This allows for inline Selects that are sized based on their content, rather than occupying block-width * Add toOption for creating Select options to @grafana/data * Add test util
This commit is contained in:
parent
7b15cd0ed2
commit
419c465edf
@ -17,7 +17,7 @@ export type ThemeSpacingArgument = number | string;
|
|||||||
* tslint:disable:unified-signatures */
|
* tslint:disable:unified-signatures */
|
||||||
export interface ThemeSpacing {
|
export interface ThemeSpacing {
|
||||||
(): string;
|
(): string;
|
||||||
(value: number): string;
|
(value: ThemeSpacingArgument): string;
|
||||||
(topBottom: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument): string;
|
(topBottom: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument): string;
|
||||||
(top: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument, bottom: ThemeSpacingArgument): string;
|
(top: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument, bottom: ThemeSpacingArgument): string;
|
||||||
(
|
(
|
||||||
|
@ -12,6 +12,7 @@ export * from './namedColorsPalette';
|
|||||||
export * from './series';
|
export * from './series';
|
||||||
export * from './binaryOperators';
|
export * from './binaryOperators';
|
||||||
export * from './nodeGraph';
|
export * from './nodeGraph';
|
||||||
|
export * from './selectUtils';
|
||||||
export { PanelOptionsEditorBuilder, FieldConfigEditorBuilder } from './OptionsUIBuilders';
|
export { PanelOptionsEditorBuilder, FieldConfigEditorBuilder } from './OptionsUIBuilders';
|
||||||
export { arrayUtils };
|
export { arrayUtils };
|
||||||
export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
|
export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
|
||||||
|
3
packages/grafana-data/src/utils/selectUtils.ts
Normal file
3
packages/grafana-data/src/utils/selectUtils.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SelectableValue } from '../types';
|
||||||
|
|
||||||
|
export const toOption = (value: string): SelectableValue<string> => ({ label: value, value });
|
@ -295,6 +295,29 @@ AutoMenuPlacement.args = {
|
|||||||
menuPlacement: auto,
|
menuPlacement: auto,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WidthAuto: Story = (args) => {
|
||||||
|
const [value, setValue] = useState<SelectableValue<string>>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<Select
|
||||||
|
menuShouldPortal
|
||||||
|
options={generateOptions()}
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => {
|
||||||
|
setValue(v);
|
||||||
|
action('onChange')(v);
|
||||||
|
}}
|
||||||
|
prefix={getPrefix(args.icon)}
|
||||||
|
{...args}
|
||||||
|
width="auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const CustomValueCreation: Story = (args) => {
|
export const CustomValueCreation: Story = (args) => {
|
||||||
const [value, setValue] = useState<SelectableValue<string>>();
|
const [value, setValue] = useState<SelectableValue<string>>();
|
||||||
const [customOptions, setCustomOptions] = useState<Array<SelectableValue<string>>>([]);
|
const [customOptions, setCustomOptions] = useState<Array<SelectableValue<string>>>([]);
|
||||||
|
@ -260,13 +260,18 @@ export function SelectBase<T>({
|
|||||||
css`
|
css`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: ${theme.colors.text.disabled};
|
color: ${theme.colors.text.disabled};
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`
|
`,
|
||||||
|
// When width: auto, the placeholder must take up space in the Select otherwise the width collapses down
|
||||||
|
width !== 'auto' &&
|
||||||
|
css`
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -355,8 +360,8 @@ export function SelectBase<T>({
|
|||||||
zIndex: theme.zIndex.dropdown,
|
zIndex: theme.zIndex.dropdown,
|
||||||
}),
|
}),
|
||||||
container: () => ({
|
container: () => ({
|
||||||
position: 'relative',
|
width: width ? theme.spacing(width) : '100%',
|
||||||
width: width ? `${8 * width}px` : '100%',
|
display: width === 'auto' ? 'inline-flex' : 'flex',
|
||||||
}),
|
}),
|
||||||
option: (provided: any, state: any) => ({
|
option: (provided: any, state: any) => ({
|
||||||
...provided,
|
...provided,
|
||||||
|
@ -6,16 +6,16 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { stylesFactory } from '../../themes';
|
import { stylesFactory } from '../../themes';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { focusCss } from '../../themes/mixins';
|
import { focusCss } from '../../themes/mixins';
|
||||||
import { components, ContainerProps, GroupTypeBase } from 'react-select';
|
import { components, ContainerProps as BaseContainerProps, GroupTypeBase } from 'react-select';
|
||||||
|
|
||||||
// isFocus prop is actually available, but its not in the types for the version we have.
|
// isFocus prop is actually available, but its not in the types for the version we have.
|
||||||
interface CorrectContainerProps<Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>
|
export interface ContainerProps<Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>
|
||||||
extends ContainerProps<Option, isMulti, Group> {
|
extends BaseContainerProps<Option, isMulti, Group> {
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>(
|
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>(
|
||||||
props: CorrectContainerProps<Option, isMulti, Group>
|
props: ContainerProps<Option, isMulti, Group>
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
isDisabled,
|
isDisabled,
|
||||||
@ -50,7 +50,7 @@ const getSelectContainerStyles = stylesFactory(
|
|||||||
css`
|
css`
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
/* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -5,3 +5,7 @@ export const selectOptionInTest = async (
|
|||||||
input: HTMLElement,
|
input: HTMLElement,
|
||||||
optionOrOptions: string | RegExp | Array<string | RegExp>
|
optionOrOptions: string | RegExp | Array<string | RegExp>
|
||||||
) => await select(input, optionOrOptions, { container: document.body });
|
) => await select(input, optionOrOptions, { container: document.body });
|
||||||
|
|
||||||
|
// Finds the parent of the Select so you can assert if it has a value
|
||||||
|
export const getSelectParent = (input: HTMLElement) =>
|
||||||
|
input.parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
|
||||||
|
@ -69,7 +69,7 @@ export interface SelectCommonProps<T> {
|
|||||||
tabSelectsValue?: boolean;
|
tabSelectsValue?: boolean;
|
||||||
value?: SelectValue<T> | null;
|
value?: SelectValue<T> | null;
|
||||||
/** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/
|
/** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/
|
||||||
width?: number;
|
width?: number | 'auto';
|
||||||
isOptionDisabled?: () => boolean;
|
isOptionDisabled?: () => boolean;
|
||||||
/** allowCustomValue must be enabled. Determines whether the "create new" option should be displayed based on the current input value, select value and options array. */
|
/** allowCustomValue must be enabled. Determines whether the "create new" option should be displayed based on the current input value, select value and options array. */
|
||||||
isValidNewOption?: (
|
isValidNewOption?: (
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LegacyForms } from '@grafana/ui';
|
import { LegacyForms } from '@grafana/ui';
|
||||||
import { TemplateSrv } from '@grafana/runtime';
|
import { TemplateSrv } from '@grafana/runtime';
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../datasource';
|
import CloudMonitoringDatasource from '../datasource';
|
||||||
import { AnnotationsHelp, LabelFilter, Metrics, Project, QueryEditorRow } from './';
|
import { AnnotationsHelp, LabelFilter, Metrics, Project, QueryEditorRow } from './';
|
||||||
import { toOption } from '../functions';
|
|
||||||
import { AnnotationTarget, EditorMode, MetricDescriptor, MetricKind } from '../types';
|
import { AnnotationTarget, EditorMode, MetricDescriptor, MetricKind } from '../types';
|
||||||
|
|
||||||
const { Input } = LegacyForms;
|
const { Input } = LegacyForms;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { FunctionComponent, useCallback, useMemo } from 'react';
|
import React, { FunctionComponent, useCallback, useMemo } from 'react';
|
||||||
import { flatten } from 'lodash';
|
import { flatten } from 'lodash';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { CustomControlProps } from '@grafana/ui/src/components/Select/types';
|
import { CustomControlProps } from '@grafana/ui/src/components/Select/types';
|
||||||
import { Button, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui';
|
import { Button, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui';
|
||||||
import { labelsToGroupedOptions, stringArrayToFilters, toOption } from '../functions';
|
import { labelsToGroupedOptions, stringArrayToFilters } from '../functions';
|
||||||
import { Filter } from '../types';
|
import { Filter } from '../types';
|
||||||
import { SELECT_WIDTH } from '../constants';
|
import { SELECT_WIDTH } from '../constants';
|
||||||
import { QueryEditorRow } from '.';
|
import { QueryEditorRow } from '.';
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { QueryEditorProps } from '@grafana/data';
|
import { QueryEditorProps, toOption } from '@grafana/data';
|
||||||
import { Button, Select } from '@grafana/ui';
|
import { Button, Select } from '@grafana/ui';
|
||||||
import { MetricQueryEditor, SLOQueryEditor, QueryEditorRow } from './';
|
import { MetricQueryEditor, SLOQueryEditor, QueryEditorRow } from './';
|
||||||
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery, EditorMode } from '../types';
|
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery, EditorMode } from '../types';
|
||||||
import { SELECT_WIDTH, QUERY_TYPES } from '../constants';
|
import { SELECT_WIDTH, QUERY_TYPES } from '../constants';
|
||||||
import { defaultQuery } from './MetricQueryEditor';
|
import { defaultQuery } from './MetricQueryEditor';
|
||||||
import { defaultQuery as defaultSLOQuery } from './SLO/SLOQueryEditor';
|
import { defaultQuery as defaultSLOQuery } from './SLO/SLOQueryEditor';
|
||||||
import { toOption } from '../functions';
|
|
||||||
import CloudMonitoringDatasource from '../datasource';
|
import CloudMonitoringDatasource from '../datasource';
|
||||||
|
|
||||||
export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery>;
|
export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery>;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { chunk, flatten, initial, startCase, uniqBy } from 'lodash';
|
import { chunk, flatten, initial, startCase, uniqBy } from 'lodash';
|
||||||
import { ALIGNMENTS, AGGREGATIONS, SYSTEM_LABELS } from './constants';
|
import { ALIGNMENTS, AGGREGATIONS, SYSTEM_LABELS } from './constants';
|
||||||
import { SelectableValue } from '@grafana/data';
|
|
||||||
import CloudMonitoringDatasource from './datasource';
|
import CloudMonitoringDatasource from './datasource';
|
||||||
import { TemplateSrv, getTemplateSrv } from '@grafana/runtime';
|
import { TemplateSrv, getTemplateSrv } from '@grafana/runtime';
|
||||||
import { MetricDescriptor, ValueTypes, MetricKind, AlignmentTypes, PreprocessorType, Filter } from './types';
|
import { MetricDescriptor, ValueTypes, MetricKind, AlignmentTypes, PreprocessorType, Filter } from './types';
|
||||||
@ -118,8 +117,6 @@ export const stringArrayToFilters = (filterArray: string[]) =>
|
|||||||
condition,
|
condition,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const toOption = (value: string) => ({ label: value, value } as SelectableValue<string>);
|
|
||||||
|
|
||||||
export const formatCloudMonitoringError = (error: any) => {
|
export const formatCloudMonitoringError = (error: any) => {
|
||||||
let message = error.statusText ?? '';
|
let message = error.statusText ?? '';
|
||||||
if (error.data && error.data.error) {
|
if (error.data && error.data.error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user