Commit c35bd84f by Alex Khomenko Committed by GitHub

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]
parent d40bfd4f
...@@ -10,7 +10,7 @@ import { Input } from '../Forms/Legacy/Input/Input'; ...@@ -10,7 +10,7 @@ import { Input } from '../Forms/Legacy/Input/Input';
import { Switch } from '../Forms/Legacy/Switch/Switch'; import { Switch } from '../Forms/Legacy/Switch/Switch';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { FormField } from '../FormField/FormField'; import { FormField } from '../FormField/FormField';
import { FormLabel } from '../FormLabel/FormLabel'; import { InlineFormLabel } from '../FormLabel/FormLabel';
import { TagsInput } from '../TagsInput/TagsInput'; import { TagsInput } from '../TagsInput/TagsInput';
import { useTheme } from '../../themes'; import { useTheme } from '../../themes';
import { HttpSettingsProps } from './types'; import { HttpSettingsProps } from './types';
...@@ -148,12 +148,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => { ...@@ -148,12 +148,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => {
)} )}
{dataSourceConfig.access === 'proxy' && ( {dataSourceConfig.access === 'proxy' && (
<div className="gf-form"> <div className="gf-form">
<FormLabel <InlineFormLabel
width={11} width={11}
tooltip="Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source." tooltip="Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source."
> >
Whitelisted Cookies Whitelisted Cookies
</FormLabel> </InlineFormLabel>
<TagsInput <TagsInput
tags={dataSourceConfig.jsonData.keepCookies} tags={dataSourceConfig.jsonData.keepCookies}
onChange={cookies => onChange={cookies =>
......
import React, { InputHTMLAttributes, FunctionComponent } from 'react'; import React, { InputHTMLAttributes, FunctionComponent } from 'react';
import { FormLabel } from '../FormLabel/FormLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
import { cx } from 'emotion'; import { cx } from 'emotion';
import { InlineFormLabel } from '../FormLabel/FormLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
export interface Props extends InputHTMLAttributes<HTMLInputElement> { export interface Props extends InputHTMLAttributes<HTMLInputElement> {
label: string; label: string;
...@@ -32,9 +32,9 @@ export const FormField: FunctionComponent<Props> = ({ ...@@ -32,9 +32,9 @@ export const FormField: FunctionComponent<Props> = ({
}) => { }) => {
return ( return (
<div className={cx('form-field', className)}> <div className={cx('form-field', className)}>
<FormLabel width={labelWidth} tooltip={tooltip}> <InlineFormLabel width={labelWidth} tooltip={tooltip}>
{label} {label}
</FormLabel> </InlineFormLabel>
{inputEl || ( {inputEl || (
<input type="text" className={`gf-form-input ${inputWidth ? `width-${inputWidth}` : ''}`} {...inputProps} /> <input type="text" className={`gf-form-input ${inputWidth ? `width-${inputWidth}` : ''}`} {...inputProps} />
)} )}
......
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} />
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>
</>
);
};
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;
`,
};
};
# 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>
```
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>
);
};
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;
`,
};
};
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}/>
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>
);
};
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};
}
`,
};
};
import React from 'react'; import React from 'react';
import { Label } from './Label'; import { Label } from './Label';
import mdx from './Label.mdx';
export default { export default {
title: 'Forms/Label', title: 'Forms/Label',
component: Label, component: Label,
parameters: {
docs: {
page: mdx,
},
},
}; };
export const simple = () => { export const simple = () => {
......
...@@ -150,6 +150,9 @@ export { Field } from './Forms/Field'; ...@@ -150,6 +150,9 @@ export { Field } from './Forms/Field';
export { Legend } from './Forms/Legend'; export { Legend } from './Forms/Legend';
export { FieldSet } from './Forms/FieldSet'; export { FieldSet } from './Forms/FieldSet';
export { FieldValidationMessage } from './Forms/FieldValidationMessage'; 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 { default as resetSelectStyles } from './Select/resetSelectStyles';
export * from './Select/Select'; export * from './Select/Select';
......
...@@ -3,7 +3,7 @@ import React, { memo } from 'react'; ...@@ -3,7 +3,7 @@ import React, { memo } from 'react';
// Types // Types
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data'; 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 { CloudWatchDatasource } from '../datasource';
import { CloudWatchLogsQuery, CloudWatchQuery } from '../types'; import { CloudWatchLogsQuery, CloudWatchQuery } from '../types';
import { CloudWatchLogsQueryField } from './LogsQueryField'; import { CloudWatchLogsQueryField } from './LogsQueryField';
...@@ -56,9 +56,9 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor ...@@ -56,9 +56,9 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor
syntax={syntax} syntax={syntax}
allowCustomValue={allowCustomValue} allowCustomValue={allowCustomValue}
ExtraFieldElement={ 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} /> <CloudWatchLink query={query as CloudWatchLogsQuery} panelData={data} datasource={datasource} />
</FormLabel> </InlineFormLabel>
} }
/> />
); );
......
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