mirror of https://github.com/grafana/grafana
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]pull/27968/head
parent
d40bfd4f10
commit
c35bd84ff1
@ -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> |
||||
</> |
||||
); |
||||
}; |
@ -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; |
||||
`,
|
||||
}; |
||||
}; |
@ -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> |
||||
); |
||||
}; |
@ -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; |
||||
`,
|
||||
}; |
||||
}; |
@ -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> |
||||
); |
||||
}; |
@ -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}; |
||||
} |
||||
`,
|
||||
}; |
||||
}; |
Loading…
Reference in new issue