diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index b64674eb3e4..1db43f75f68 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -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 = props => { )} {dataSourceConfig.access === 'proxy' && (
- Whitelisted Cookies - + diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 5d7096d2a6b..24430a99980 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -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 { label: string; @@ -32,9 +32,9 @@ export const FormField: FunctionComponent = ({ }) => { return (
- + {label} - + {inputEl || ( )} diff --git a/packages/grafana-ui/src/components/Forms/InlineField.mdx b/packages/grafana-ui/src/components/Forms/InlineField.mdx new file mode 100644 index 00000000000..1ab000ddf7d --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineField.mdx @@ -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 + + + +``` + + diff --git a/packages/grafana-ui/src/components/Forms/InlineField.story.tsx b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx new file mode 100644 index 00000000000..a4f97954757 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx @@ -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 ( + + + + ); +}; + +export const withTooltip = () => { + return ( + + + + ); +}; + +export const grow = () => { + return ( + + + + ); +}; + +export const withSelect = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/packages/grafana-ui/src/components/Forms/InlineField.tsx b/packages/grafana-ui/src/components/Forms/InlineField.tsx new file mode 100644 index 00000000000..2c3829fe141 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineField.tsx @@ -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 { + /** 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 = ({ + 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' ? ( + + {label} + + ) : ( + label + ); + + return ( +
+ {labelElement} + {React.cloneElement(children, { invalid, disabled, loading })} +
+ ); +}; + +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; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx new file mode 100644 index 00000000000..b59cea2a753 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx @@ -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 + + + + + + + + +``` diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx new file mode 100644 index 00000000000..f4c43643a54 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx @@ -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 ( +
+ + + + + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx new file mode 100644 index 00000000000..4b400358a84 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx @@ -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, 'css'> { + children: ReactNode | ReactNode[]; +} + +export const InlineFieldRow: FC = ({ children, className, ...htmlProps }) => { + const styles = useStyles(getStyles); + return ( +
+ {children} +
+ ); +}; + +const getStyles = () => { + return { + container: css` + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-content: flex-start; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.mdx b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx new file mode 100644 index 00000000000..7f42c4ebb50 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx @@ -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 + + Simple label + +``` + + diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx new file mode 100644 index 00000000000..857bbd00fbd --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx @@ -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 Simple label; +}; + +export const withTooltip = () => { + return ( + + Simple label + + ); +}; diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx new file mode 100644 index 00000000000..348de296644 --- /dev/null +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx @@ -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 { + /** 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 = ({ children, className, htmlFor, tooltip, width, ...rest }) => { + const theme = useTheme(); + const styles = getInlineLabelStyles(theme, width); + + return ( + + ); +}; + +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}; + } + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Forms/Label.story.tsx b/packages/grafana-ui/src/components/Forms/Label.story.tsx index 052b8dce097..ac452922229 100644 --- a/packages/grafana-ui/src/components/Forms/Label.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Label.story.tsx @@ -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 = () => { diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 418ab7eb5ea..912e59974e7 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -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'; diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx index 2c8bf2b4687..bbc3098fc0f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx @@ -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={ - + - + } /> );