Grafana-UI: Refactor legacy inline form components (#27660)

* Move styles to emotion

* Move FormLabel to forms

* Add mdx file

* Setup InlineField

* Add InlineField docs

* Add grow prop

* Add isKeyword prop

* Add filled Field

* Keep legacy form label

* InlineFormLabel => InlineLabel

* Update references

* Add multiple elements example

* Revert label

* Add InlineFieldRow

* Adjust label width

* Export InlineFieldRow

* Expand props from base components

* Remove isKeyword prop

* Remove fill prop

* Update docs

* Update docs [2]
This commit is contained in:
Alex Khomenko 2020-09-28 14:24:25 +03:00 committed by GitHub
parent d40bfd4f10
commit c35bd84ff1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 378 additions and 10 deletions

View File

@ -10,7 +10,7 @@ import { Input } from '../Forms/Legacy/Input/Input';
import { Switch } from '../Forms/Legacy/Switch/Switch';
import { Icon } from '../Icon/Icon';
import { FormField } from '../FormField/FormField';
import { FormLabel } from '../FormLabel/FormLabel';
import { InlineFormLabel } from '../FormLabel/FormLabel';
import { TagsInput } from '../TagsInput/TagsInput';
import { useTheme } from '../../themes';
import { HttpSettingsProps } from './types';
@ -148,12 +148,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => {
)}
{dataSourceConfig.access === 'proxy' && (
<div className="gf-form">
<FormLabel
<InlineFormLabel
width={11}
tooltip="Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source."
>
Whitelisted Cookies
</FormLabel>
</InlineFormLabel>
<TagsInput
tags={dataSourceConfig.jsonData.keepCookies}
onChange={cookies =>

View File

@ -1,7 +1,7 @@
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
import { FormLabel } from '../FormLabel/FormLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
import { cx } from 'emotion';
import { InlineFormLabel } from '../FormLabel/FormLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
label: string;
@ -32,9 +32,9 @@ export const FormField: FunctionComponent<Props> = ({
}) => {
return (
<div className={cx('form-field', className)}>
<FormLabel width={labelWidth} tooltip={tooltip}>
<InlineFormLabel width={labelWidth} tooltip={tooltip}>
{label}
</FormLabel>
</InlineFormLabel>
{inputEl || (
<input type="text" className={`gf-form-input ${inputWidth ? `width-${inputWidth}` : ''}`} {...inputProps} />
)}

View File

@ -0,0 +1,17 @@
import { Props } from "@storybook/addon-docs/blocks";
import { InlineField } from "./InlineField";
# InlineField
A basic component for rendering form elements, like `Input`, `Select`, `Checkbox`, etc, inline together with `InlineLabel`. If the child element has `id` specified, the label's `htmlFor` attribute, pointing to the id, will be added.
The width of the `InlineLabel` can be modified via `labelWidth` prop. If `tooltip` prop is provided, an info icon with supplied tooltip content will be rendered inside the label.
# Usage
```jsx
<InlineField label="Inline field">
<Input placeholder="Inline input" />
</InlineField>
```
<Props of={InlineField} />

View File

@ -0,0 +1,71 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { Input } from '../Input/Input';
import { Select } from '../Select/Select';
import { InlineField } from './InlineField';
import mdx from './InlineField.mdx';
export default {
title: 'Forms/InlineField',
component: InlineField,
parameters: {
docs: {
page: mdx,
},
},
};
export const basic = () => {
return (
<InlineField label="Inline field">
<Input placeholder="Inline input" />
</InlineField>
);
};
export const withTooltip = () => {
return (
<InlineField label="Label" tooltip="Tooltip">
<Input placeholder="Inline input" />
</InlineField>
);
};
export const grow = () => {
return (
<InlineField label="Label" grow>
<Input placeholder="Inline input" />
</InlineField>
);
};
export const withSelect = () => {
return (
<InlineField label="Select option">
<Select
width={16}
onChange={action('item selected')}
options={[
{ value: 1, label: 'One' },
{ value: 2, label: 'Two' },
]}
/>
</InlineField>
);
};
export const multiple = () => {
return (
<>
<InlineField label="Field 1">
<Input placeholder="Inline input" />
</InlineField>
<InlineField label="Field 2">
<Input placeholder="Inline input" />
</InlineField>
<InlineField label="Field 3">
<Input placeholder="Inline input" />
</InlineField>
</>
);
};

View File

@ -0,0 +1,77 @@
import React, { FC } from 'react';
import { cx, css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useTheme } from '../../themes';
import { InlineLabel } from './InlineLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
import { FieldProps } from './Field';
export interface Props extends Omit<FieldProps, 'css' | 'horizontal' | 'description' | 'error'> {
/** Content for the label's tooltip */
tooltip?: PopoverContent;
/** Custom width for the label */
labelWidth?: number | 'auto';
/** Make the field's child to fill the width of the row. Equivalent to setting `flex-grow:1` on the field */
grow?: boolean;
}
export const InlineField: FC<Props> = ({
children,
label,
tooltip,
labelWidth = 'auto',
invalid,
loading,
disabled,
className,
grow,
...htmlProps
}) => {
const theme = useTheme();
const styles = getStyles(theme, grow);
const child = React.Children.only(children);
let inputId;
if (child) {
inputId = (child as React.ReactElement<{ id?: string }>).props.id;
}
const labelElement =
typeof label === 'string' ? (
<InlineLabel width={labelWidth} tooltip={tooltip} htmlFor={inputId}>
{label}
</InlineLabel>
) : (
label
);
return (
<div className={cx(styles.container, className)} {...htmlProps}>
{labelElement}
{React.cloneElement(children, { invalid, disabled, loading })}
</div>
);
};
InlineField.displayName = 'InlineField';
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: 0 ${theme.spacing.xs} ${theme.spacing.xs} 0;
`,
wrapper: css`
display: flex;
width: 100%;
`,
fillContainer: css`
flex-grow: 1;
`,
};
};

View File

@ -0,0 +1,16 @@
# InlineFieldRow
Used to align multiple `InlineField` components in one row. The row will wrap if the width of the children exceeds its own. Equivalent to the div with `gf-form-inline` class name.
Multiple `InlineFieldRow`s vertically stack on each other.
### Usage
```jsx
<InlineFieldRow>
<InlineField label="Label Row 1">
<Input placeholder="Label" />
</InlineField>
<InlineField label="Label Row 1">
<Input placeholder="Label" />
</InlineField>
</InlineFieldRow>
```

View File

@ -0,0 +1,41 @@
import React from 'react';
import { InlineFieldRow } from './InlineFieldRow';
import mdx from './InlineFieldRow.mdx';
import { InlineField } from './InlineField';
import { Input } from '../Input/Input';
export default {
title: 'Forms/InlineFieldRow',
component: InlineFieldRow,
parameters: {
docs: {
page: mdx,
},
},
};
export const single = () => {
return (
<div style={{ width: '100%' }}>
<InlineFieldRow>
<InlineField label="Label Row 1">
<Input placeholder="Label" />
</InlineField>
<InlineField label="Label Row 1">
<Input placeholder="Label" />
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField label="Label Row 2">
<Input placeholder="Label" />
</InlineField>
<InlineField label="Label Row 2">
<Input placeholder="Label" />
</InlineField>
<InlineField label="Label Row 2 Grow" grow>
<Input placeholder="Label" />
</InlineField>
</InlineFieldRow>
</div>
);
};

View File

@ -0,0 +1,27 @@
import React, { FC, ReactNode, HTMLProps } from 'react';
import { css, cx } from 'emotion';
import { useStyles } from '../../themes';
export interface Props extends Omit<HTMLProps<HTMLDivElement>, 'css'> {
children: ReactNode | ReactNode[];
}
export const InlineFieldRow: FC<Props> = ({ children, className, ...htmlProps }) => {
const styles = useStyles(getStyles);
return (
<div className={cx(styles.container, className)} {...htmlProps}>
{children}
</div>
);
};
const getStyles = () => {
return {
container: css`
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
`,
};
};

View File

@ -0,0 +1,17 @@
import { Props } from "@storybook/addon-docs/blocks";
import { InlineLabel } from "./InlineLabel";
# InlineLabel
A horizontal variant of `Label`, primarily used in query editors. Can be combined with form components that expect a label, eg. `Input`, `Select`, `Checkbox`.
If you need to add additional explanation, use the tooltip prop, which will render an info icon with tooltip inside the label.
For query editor readability, the label text should be as short as possible (4 words or fewer).
# Usage
```jsx
<InlineLabel width="auto" tooltip="Tooltip content">
Simple label
</InlineLabel>
```
<Props of={InlineLabel}/>

View File

@ -0,0 +1,25 @@
import React from 'react';
import { InlineLabel } from './InlineLabel';
import mdx from './InlineLabel.mdx';
export default {
title: 'Forms/InlineLabel',
component: InlineLabel,
parameters: {
docs: {
page: mdx,
},
},
};
export const basic = () => {
return <InlineLabel width="auto">Simple label</InlineLabel>;
};
export const withTooltip = () => {
return (
<InlineLabel width="auto" tooltip="Tooltip content">
Simple label
</InlineLabel>
);
};

View File

@ -0,0 +1,68 @@
import React, { FunctionComponent } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { css, cx } from 'emotion';
import { Tooltip, PopoverContent } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { useTheme } from '../../themes';
import { LabelProps } from './Label';
export interface Props extends Omit<LabelProps, 'css' | 'description' | 'category'> {
/** Content for the labels tooltip. If provided, an info icon with the tooltip content
* will be displayed */
tooltip?: PopoverContent;
/** Custom width for the label */
width?: number | 'auto';
/** @deprecated */
/** This prop is deprecated and is not used anymore */
isFocused?: boolean;
/** @deprecated */
/** This prop is deprecated and is not used anymore */
isInvalid?: boolean;
}
export const InlineLabel: FunctionComponent<Props> = ({ children, className, htmlFor, tooltip, width, ...rest }) => {
const theme = useTheme();
const styles = getInlineLabelStyles(theme, width);
return (
<label 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>
);
};
export const getInlineLabelStyles = (theme: GrafanaTheme, width?: number | 'auto') => {
return {
label: css`
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
padding: 0 ${theme.spacing.sm};
font-weight: ${theme.typography.weight.semibold};
font-size: ${theme.typography.size.sm};
background-color: ${theme.colors.bg2};
height: ${theme.height.md}px;
line-height: ${theme.height.md};
margin-right: ${theme.spacing.xs};
border-radius: ${theme.border.radius.md};
border: none;
width: ${width ? (width !== 'auto' ? `${8 * width}px` : width) : '100%'};
color: ${theme.colors.textHeading};
`,
icon: css`
flex-grow: 0;
color: ${theme.colors.textWeak};
margin-left: 10px;
:hover {
color: ${theme.colors.text};
}
`,
};
};

View File

@ -1,9 +1,15 @@
import React from 'react';
import { Label } from './Label';
import mdx from './Label.mdx';
export default {
title: 'Forms/Label',
component: Label,
parameters: {
docs: {
page: mdx,
},
},
};
export const simple = () => {

View File

@ -150,6 +150,9 @@ export { Field } from './Forms/Field';
export { Legend } from './Forms/Legend';
export { FieldSet } from './Forms/FieldSet';
export { FieldValidationMessage } from './Forms/FieldValidationMessage';
export { InlineField } from './Forms/InlineField';
export { InlineLabel } from './Forms/InlineLabel';
export { InlineFieldRow } from './Forms/InlineFieldRow';
export { default as resetSelectStyles } from './Select/resetSelectStyles';
export * from './Select/Select';

View File

@ -3,7 +3,7 @@ import React, { memo } from 'react';
// Types
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data';
import { FormLabel } from '@grafana/ui/src/components/FormLabel/FormLabel';
import { InlineFormLabel } from '@grafana/ui';
import { CloudWatchDatasource } from '../datasource';
import { CloudWatchLogsQuery, CloudWatchQuery } from '../types';
import { CloudWatchLogsQueryField } from './LogsQueryField';
@ -56,9 +56,9 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor
syntax={syntax}
allowCustomValue={allowCustomValue}
ExtraFieldElement={
<FormLabel className={`gf-form-label--btn ${labelClass}`} width="auto" tooltip="Link to Graph in AWS">
<InlineFormLabel className={`gf-form-label--btn ${labelClass}`} width="auto" tooltip="Link to Graph in AWS">
<CloudWatchLink query={query as CloudWatchLogsQuery} panelData={data} datasource={datasource} />
</FormLabel>
</InlineFormLabel>
}
/>
);