mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d40bfd4f10
commit
c35bd84ff1
@ -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 =>
|
||||
|
@ -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} />
|
||||
)}
|
||||
|
17
packages/grafana-ui/src/components/Forms/InlineField.mdx
Normal file
17
packages/grafana-ui/src/components/Forms/InlineField.mdx
Normal 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} />
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
77
packages/grafana-ui/src/components/Forms/InlineField.tsx
Normal file
77
packages/grafana-ui/src/components/Forms/InlineField.tsx
Normal 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;
|
||||
`,
|
||||
};
|
||||
};
|
16
packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx
Normal file
16
packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx
Normal 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>
|
||||
```
|
@ -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>
|
||||
);
|
||||
};
|
27
packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx
Normal file
27
packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx
Normal 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;
|
||||
`,
|
||||
};
|
||||
};
|
17
packages/grafana-ui/src/components/Forms/InlineLabel.mdx
Normal file
17
packages/grafana-ui/src/components/Forms/InlineLabel.mdx
Normal 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}/>
|
@ -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>
|
||||
);
|
||||
};
|
68
packages/grafana-ui/src/components/Forms/InlineLabel.tsx
Normal file
68
packages/grafana-ui/src/components/Forms/InlineLabel.tsx
Normal 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};
|
||||
}
|
||||
`,
|
||||
};
|
||||
};
|
@ -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 = () => {
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user