Commit 1506ec7b by Torkel Ödegaard Committed by GitHub

InlineForms: Changes to make inline forms more flexible for query editors (#29782)

* Chore: Remove internal import for QueryEditorRow

* WIP inline forms fixes

* Added segment group component

* Added component status doc tag

* Chore: add as prop to InlineLabel to control rendered element (#29876)

* Use div for InlineLabel & fix iconbuttons positioning

Co-authored-by: Elfo404 <gio.ricci@grafana.com>
parent fac34f65
......@@ -59,19 +59,11 @@ const getStyles = (theme: GrafanaTheme, grow?: boolean) => {
container: css`
display: flex;
flex-direction: row;
align-items: center;
align-items: flex-start;
text-align: left;
position: relative;
flex: ${grow ? 1 : 0} 0 auto;
margin: 0 ${theme.spacing.xs} ${theme.spacing.xs} 0;
`,
wrapper: css`
display: flex;
width: 100%;
`,
fillContainer: css`
flex-grow: 1;
`,
};
};
import React, { FC, HTMLProps, ReactNode } from 'react';
import { css, cx } from 'emotion';
import { useStyles } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
export interface Props extends Omit<HTMLProps<HTMLDivElement>, 'css'> {
children: ReactNode | ReactNode[];
......@@ -15,7 +16,7 @@ export const InlineFieldRow: FC<Props> = ({ children, className, ...htmlProps })
);
};
const getStyles = () => {
const getStyles = (theme: GrafanaTheme) => {
return {
container: css`
label: InlineFieldRow;
......
......@@ -18,21 +18,31 @@ export interface Props extends Omit<LabelProps, 'css' | 'description' | 'categor
/** @deprecated */
/** This prop is deprecated and is not used anymore */
isInvalid?: boolean;
/** @beta */
/** Controls which element the InlineLabel should be rendered into */
as?: React.ElementType;
}
export const InlineLabel: FunctionComponent<Props> = ({ children, className, tooltip, width, ...rest }) => {
export const InlineLabel: FunctionComponent<Props> = ({
children,
className,
tooltip,
width,
as: Component = 'label',
...rest
}) => {
const theme = useTheme();
const styles = getInlineLabelStyles(theme, width);
return (
<label className={cx(styles.label, className)} {...rest}>
<Component className={cx(styles.label, className)} {...rest}>
{children}
{tooltip && (
<Tooltip placement="top" content={tooltip} theme="info">
<Icon name="info-circle" size="sm" className={styles.icon} />
</Tooltip>
)}
</label>
</Component>
);
};
......
import React, { FC } from 'react';
import { cx, css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useTheme } from '../../themes';
export interface Props {
grow?: boolean;
className?: string;
}
/** @beta */
export const InlineSegmentGroup: FC<Props> = ({ children, className, grow, ...htmlProps }) => {
const theme = useTheme();
const styles = getStyles(theme, grow);
return (
<div className={cx(styles.container, className)} {...htmlProps}>
{children}
</div>
);
};
InlineSegmentGroup.displayName = 'InlineSegmentGroup';
const getStyles = (theme: GrafanaTheme, grow?: boolean) => {
return {
container: css`
display: flex;
flex-direction: row;
align-items: flex-start;
text-align: left;
position: relative;
flex: ${grow ? 1 : 0} 0 auto;
margin-bottom: ${theme.spacing.xs};
`,
};
};
......@@ -152,6 +152,7 @@ export { Legend } from './Forms/Legend';
export { FieldSet } from './Forms/FieldSet';
export { FieldValidationMessage } from './Forms/FieldValidationMessage';
export { InlineField } from './Forms/InlineField';
export { InlineSegmentGroup } from './Forms/InlineSegmentGroup';
export { InlineLabel } from './Forms/InlineLabel';
export { InlineFieldRow } from './Forms/InlineFieldRow';
export { FieldArray } from './Forms/FieldArray';
......
import { MetricFindValue, SelectableValue } from '@grafana/data';
import { Segment, SegmentAsync } from '@grafana/ui';
import { InlineSegmentGroup, Segment, SegmentAsync } from '@grafana/ui';
import React, { FunctionComponent } from 'react';
import { useDispatch } from '../../../hooks/useStatelessReducer';
import { useDatasource } from '../ElasticsearchQueryContext';
......@@ -53,22 +53,24 @@ export const BucketAggregationEditor: FunctionComponent<QueryMetricEditorProps>
return (
<>
<Segment
className={segmentStyles}
options={bucketAggOptions}
onChange={e => dispatch(changeBucketAggregationType(value.id, e.value!))}
value={toOption(value)}
/>
{isBucketAggregationWithField(value) && (
<SegmentAsync
<InlineSegmentGroup>
<Segment
className={segmentStyles}
loadOptions={getFields}
onChange={e => dispatch(changeBucketAggregationField(value.id, e.value))}
placeholder="Select Field"
value={value.field}
options={bucketAggOptions}
onChange={e => dispatch(changeBucketAggregationType(value.id, e.value!))}
value={toOption(value)}
/>
)}
{isBucketAggregationWithField(value) && (
<SegmentAsync
className={segmentStyles}
loadOptions={getFields}
onChange={e => dispatch(changeBucketAggregationField(value.id, e.value))}
placeholder="Select Field"
value={value.field}
/>
)}
</InlineSegmentGroup>
<SettingsEditor bucketAgg={value} />
</>
......
import { MetricFindValue, SelectableValue } from '@grafana/data';
import { Segment, SegmentAsync, useTheme } from '@grafana/ui';
import { InlineSegmentGroup, Segment, SegmentAsync, useTheme } from '@grafana/ui';
import { cx } from 'emotion';
import React, { FunctionComponent } from 'react';
import { useDatasource, useQuery } from '../ElasticsearchQueryContext';
......@@ -88,31 +88,33 @@ export const MetricEditor: FunctionComponent<Props> = ({ value }) => {
return (
<>
<Segment
className={cx(styles.color, segmentStyles)}
options={getTypeOptions(previousMetrics, datasource.esVersion)}
onChange={e => dispatch(changeMetricType(value.id, e.value!))}
value={toOption(value)}
/>
{isMetricAggregationWithField(value) && !isPipelineAggregation(value) && (
<SegmentAsync
<InlineSegmentGroup>
<Segment
className={cx(styles.color, segmentStyles)}
loadOptions={getFields}
onChange={e => dispatch(changeMetricField(value.id, e.value!))}
placeholder="Select Field"
value={value.field}
options={getTypeOptions(previousMetrics, datasource.esVersion)}
onChange={e => dispatch(changeMetricType(value.id, e.value!))}
value={toOption(value)}
/>
)}
{isPipelineAggregation(value) && !isPipelineAggregationWithMultipleBucketPaths(value) && (
<MetricPicker
className={cx(styles.color, segmentStyles)}
onChange={e => dispatch(changeMetricField(value.id, e.value?.id!))}
options={previousMetrics}
value={value.field}
/>
)}
{isMetricAggregationWithField(value) && !isPipelineAggregation(value) && (
<SegmentAsync
className={cx(styles.color, segmentStyles)}
loadOptions={getFields}
onChange={e => dispatch(changeMetricField(value.id, e.value!))}
placeholder="Select Field"
value={value.field}
/>
)}
{isPipelineAggregation(value) && !isPipelineAggregationWithMultipleBucketPaths(value) && (
<MetricPicker
className={cx(styles.color, segmentStyles)}
onChange={e => dispatch(changeMetricField(value.id, e.value?.id!))}
options={previousMetrics}
value={value.field}
/>
)}
</InlineSegmentGroup>
{isMetricAggregationWithSettings(value) && <SettingsEditor metric={value} previousMetrics={previousMetrics} />}
</>
......
import { GrafanaTheme } from '@grafana/data';
import { IconButton, stylesFactory, useTheme } from '@grafana/ui';
import { getInlineLabelStyles } from '@grafana/ui/src/components/Forms/InlineLabel';
import { IconButton, InlineFieldRow, InlineLabel, InlineSegmentGroup, stylesFactory, useTheme } from '@grafana/ui';
import { css } from 'emotion';
import { noop } from 'lodash';
import React, { FunctionComponent } from 'react';
......@@ -23,44 +22,43 @@ export const QueryEditorRow: FunctionComponent<Props> = ({
const styles = getStyles(theme);
return (
<fieldset className={styles.root}>
<div className={getInlineLabelStyles(theme, 17).label}>
<legend className={styles.label}>{label}</legend>
{onHideClick && (
<IconButton
name={hidden ? 'eye-slash' : 'eye'}
onClick={onHideClick}
surface="header"
size="sm"
aria-pressed={hidden}
aria-label="hide metric"
className={styles.icon}
/>
)}
<IconButton
name="trash-alt"
surface="header"
size="sm"
className={styles.icon}
onClick={onRemoveClick || noop}
disabled={!onRemoveClick}
aria-label="remove metric"
/>
</div>
<InlineFieldRow>
<InlineSegmentGroup>
<InlineLabel width={17} as="div">
<span>{label}</span>
<span className={styles.iconWrapper}>
{onHideClick && (
<IconButton
name={hidden ? 'eye-slash' : 'eye'}
onClick={onHideClick}
surface="header"
size="sm"
aria-pressed={hidden}
aria-label="hide metric"
className={styles.icon}
/>
)}
<IconButton
name="trash-alt"
surface="header"
size="sm"
className={styles.icon}
onClick={onRemoveClick || noop}
disabled={!onRemoveClick}
aria-label="remove metric"
/>
</span>
</InlineLabel>
</InlineSegmentGroup>
{children}
</fieldset>
</InlineFieldRow>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
root: css`
iconWrapper: css`
display: flex;
margin-bottom: ${theme.spacing.xs};
`,
label: css`
font-size: ${theme.typography.size.sm};
margin: 0;
`,
icon: css`
color: ${theme.colors.textWeak};
......
import { GrafanaTheme } from '@grafana/data';
import { Icon, stylesFactory, useTheme } from '@grafana/ui';
import { Icon, InlineSegmentGroup, stylesFactory, useTheme } from '@grafana/ui';
import { css, cx } from 'emotion';
import React, { FunctionComponent, useState } from 'react';
import { segmentStyles } from './styles';
......@@ -32,21 +32,22 @@ interface Props {
export const SettingsEditorContainer: FunctionComponent<Props> = ({ label, children, hidden = false }) => {
const [open, setOpen] = useState(false);
const styles = getStyles(useTheme(), hidden);
return (
<div className={cx(styles.wrapper)}>
<button
className={cx('gf-form-label query-part', styles.button, segmentStyles)}
onClick={() => setOpen(!open)}
aria-expanded={open}
>
<Icon name={open ? 'angle-down' : 'angle-right'} aria-hidden="true" className={styles.icon} />
{label}
</button>
<InlineSegmentGroup>
<div className={cx(styles.wrapper)}>
<button
className={cx('gf-form-label query-part', styles.button, segmentStyles)}
onClick={() => setOpen(!open)}
aria-expanded={open}
>
<Icon name={open ? 'angle-down' : 'angle-right'} aria-hidden="true" className={styles.icon} />
{label}
</button>
{open && <div className={styles.settingsWrapper}>{children}</div>}
</div>
{open && <div className={styles.settingsWrapper}>{children}</div>}
</div>
</InlineSegmentGroup>
);
};
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