Commit 64e609e1 by Dominik Prokop Committed by GitHub

Forms: Introduce typographic form elements (#19879)

* Implement Label component

* Expose next-gen form components from grafana-ui under Forms namespace

* Minor Label update

* Add Legend component

* Test form story

* Expose Legend class name via getFormStyles

* Test

* FieldValidationMessage spacing

* Expose FieldValidationMessage styles via getFormStyles

* Update snapshot
parent 82326590
import React from 'react';
import { FieldValidationMessage } from './FieldValidationMessage';
import { text } from '@storybook/addon-knobs';
const getKnobs = () => {
return {
message: text('message', 'Invalid input message'),
};
};
export default {
title: 'UI/Forms/FieldValidationMessage',
component: FieldValidationMessage,
};
export const simple = () => {
const { message } = getKnobs();
return <FieldValidationMessage>{message}</FieldValidationMessage>;
};
import React from 'react';
import { useTheme, stylesFactory } from '../../themes';
import { GrafanaTheme } from '../../types';
import { css, cx } from 'emotion';
export interface FieldValidationMessageProps {
children: string;
className?: string;
}
export const getFieldValidationMessageStyles = stylesFactory((theme: GrafanaTheme) => {
return {
fieldValidationMessage: css`
font-size: ${theme.typography.size.sm};
font-weight: ${theme.typography.weight.semibold};
margin: ${theme.spacing.formLabelMargin};
padding: ${theme.spacing.formValidationMessagePadding};
color: ${theme.colors.formValidationMessageText};
background: ${theme.colors.formValidationMessageBg};
border-radius: ${theme.border.radius.sm};
position: relative;
&:before {
content: '';
position: absolute;
left: 9px;
top: -5px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid ${theme.colors.formValidationMessageBg};
}
`,
fieldValidationMessageIcon: css`
margin-right: ${theme.spacing.formSpacingBase}px;
`,
};
});
export const FieldValidationMessage: React.FC<FieldValidationMessageProps> = ({ children, className }) => {
const theme = useTheme();
const styles = getFieldValidationMessageStyles(theme);
return (
<div className={cx(styles.fieldValidationMessage, className)}>
<i className={cx(styles.fieldValidationMessageIcon, 'fa', 'fa-warning')} />
{children}
</div>
);
};
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Legend } from './Legend';
import { Label } from './Label';
const story = storiesOf('UI/Forms/Test', module);
story.add('Configuration/Preferences', () => {
return (
<div>
<fieldset>
<Legend>Organization profile</Legend>
<Label description="Provide a name of your organisation that will be used across Grafana installation">
Organization name
</Label>
</fieldset>
</div>
);
});
import React from 'react';
import { text } from '@storybook/addon-knobs';
import { Label } from './Label';
const getKnobs = () => {
return {
label: text('text', 'Form element label'),
description: text('description', 'Description of the form field'),
};
};
export default {
title: 'UI|Forms',
component: Label,
};
export const simple = () => {
const { label, description } = getKnobs();
return <Label description={description}>{label}</Label>;
};
import React from 'react';
import { useTheme, stylesFactory } from '../../themes';
import { GrafanaTheme } from '../../types';
import { css, cx } from 'emotion';
export interface LabelProps extends React.HTMLAttributes<HTMLLabelElement> {
children: string;
description?: string;
}
export const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
return {
label: css`
font-size: ${theme.typography.size.sm};
font-weight: ${theme.typography.weight.semibold};
margin: ${theme.spacing.formLabelMargin};
padding: ${theme.spacing.formLabelPadding};
color: ${theme.colors.formLabel};
`,
description: css`
font-weight: ${theme.typography.weight.regular};
`,
};
});
export const Label: React.FC<LabelProps> = ({ children, description, className, ...labelProps }) => {
const theme = useTheme();
const styles = getLabelStyles(theme);
return (
<div className={cx(styles.label, className)}>
<label {...labelProps}>{children}</label>
{description && <div className={styles.description}>{description}</div>}
</div>
);
};
import React from 'react';
import { storiesOf } from '@storybook/react';
import { text } from '@storybook/addon-knobs';
import { Legend } from './Legend';
const getKnobs = () => {
return {
label: text('text', 'Form section'),
};
};
const story = storiesOf('UI/Forms', module);
story.add('Legend', () => {
const { label } = getKnobs();
return <Legend>{label}</Legend>;
});
import React from 'react';
import { useTheme, stylesFactory } from '../../themes';
import { GrafanaTheme } from '../../types';
import { css, cx } from 'emotion';
export interface LabelProps extends React.HTMLAttributes<HTMLLegendElement> {
children: string;
description?: string;
}
export const getLegendStyles = stylesFactory((theme: GrafanaTheme) => {
return {
legend: css`
font-size: ${theme.typography.heading.h3};
font-weight: ${theme.typography.weight.regular};
margin: ${theme.spacing.formLegendMargin};
color: ${theme.colors.formLegend};
`,
};
});
export const Legend: React.FC<LabelProps> = ({ children, className, ...legendProps }) => {
const theme = useTheme();
const styles = getLegendStyles(theme);
return (
<legend className={cx(styles.legend, className)} {...legendProps}>
{children}
</legend>
);
};
import { stylesFactory } from '../../themes';
import { GrafanaTheme } from '../../types';
import { getLabelStyles } from './Label';
import { getLegendStyles } from './Legend';
import { getFieldValidationMessageStyles } from './FieldValidationMessage';
export const getFormStyles = stylesFactory((theme: GrafanaTheme) => {
return {
...getLabelStyles(theme),
...getLegendStyles(theme),
...getFieldValidationMessageStyles(theme),
};
});
import { getFormStyles } from './getFormStyles';
import { Label } from './Label';
const Forms = {
getFormStyles,
Label: Label,
};
export default Forms;
...@@ -209,8 +209,10 @@ exports[`Render should render with base threshold 1`] = ` ...@@ -209,8 +209,10 @@ exports[`Render should render with base threshold 1`] = `
"formInputPaddingHorizontal": "8px", "formInputPaddingHorizontal": "8px",
"formLabelMargin": "0 0 4px 0", "formLabelMargin": "0 0 4px 0",
"formLabelPadding": "0 0 0 2px", "formLabelPadding": "0 0 0 2px",
"formLegendMargin": "16px", "formLegendMargin": "0 0 16px 0",
"formMargin": "32px", "formMargin": "32px",
"formSpacingBase": 8,
"formValidationMessagePadding": "4px 8px",
"gutter": "30px", "gutter": "30px",
"insetSquishMd": "4px 8px", "insetSquishMd": "4px 8px",
"lg": "24px", "lg": "24px",
...@@ -415,8 +417,10 @@ exports[`Render should render with base threshold 1`] = ` ...@@ -415,8 +417,10 @@ exports[`Render should render with base threshold 1`] = `
"formInputPaddingHorizontal": "8px", "formInputPaddingHorizontal": "8px",
"formLabelMargin": "0 0 4px 0", "formLabelMargin": "0 0 4px 0",
"formLabelPadding": "0 0 0 2px", "formLabelPadding": "0 0 0 2px",
"formLegendMargin": "16px", "formLegendMargin": "0 0 16px 0",
"formMargin": "32px", "formMargin": "32px",
"formSpacingBase": 8,
"formValidationMessagePadding": "4px 8px",
"gutter": "30px", "gutter": "30px",
"insetSquishMd": "4px 8px", "insetSquishMd": "4px 8px",
"lg": "24px", "lg": "24px",
......
...@@ -93,3 +93,6 @@ export { Spinner } from './Spinner/Spinner'; ...@@ -93,3 +93,6 @@ export { Spinner } from './Spinner/Spinner';
export { FadeTransition } from './transitions/FadeTransition'; export { FadeTransition } from './transitions/FadeTransition';
export { SlideOutTransition } from './transitions/SlideOutTransition'; export { SlideOutTransition } from './transitions/SlideOutTransition';
export { Segment, SegmentAsync, SegmentSelect } from './Segment/'; export { Segment, SegmentAsync, SegmentSelect } from './Segment/';
// Next-gen forms
export { default as Forms } from './Forms';
...@@ -82,9 +82,10 @@ const theme: GrafanaThemeCommons = { ...@@ -82,9 +82,10 @@ const theme: GrafanaThemeCommons = {
// Next-gen forms spacing variables // Next-gen forms spacing variables
// TODO: Move variables definition to respective components when implementing // TODO: Move variables definition to respective components when implementing
formSpacingBase: SPACING_BASE,
formMargin: `${SPACING_BASE * 4}px`, formMargin: `${SPACING_BASE * 4}px`,
formFieldsetMargin: `${SPACING_BASE * 2}px`, formFieldsetMargin: `${SPACING_BASE * 2}px`,
formLegendMargin: `${SPACING_BASE * 2}px`, formLegendMargin: `0 0 ${SPACING_BASE * 2}px 0`,
formInputHeight: `${SPACING_BASE * 4}px`, formInputHeight: `${SPACING_BASE * 4}px`,
formInputPaddingHorizontal: `${SPACING_BASE}px`, formInputPaddingHorizontal: `${SPACING_BASE}px`,
...@@ -95,6 +96,7 @@ const theme: GrafanaThemeCommons = { ...@@ -95,6 +96,7 @@ const theme: GrafanaThemeCommons = {
formInputMargin: `${SPACING_BASE * 2}px`, formInputMargin: `${SPACING_BASE * 2}px`,
formLabelPadding: '0 0 0 2px', formLabelPadding: '0 0 0 2px',
formLabelMargin: '0 0 4px 0', formLabelMargin: '0 0 4px 0',
formValidationMessagePadding: '4px 8px',
}, },
border: { border: {
radius: { radius: {
......
...@@ -64,6 +64,7 @@ export interface GrafanaThemeCommons { ...@@ -64,6 +64,7 @@ export interface GrafanaThemeCommons {
// Next-gen forms spacing variables // Next-gen forms spacing variables
// TODO: Move variables definition to respective components when implementing // TODO: Move variables definition to respective components when implementing
formSpacingBase: number;
formMargin: string; formMargin: string;
formFieldsetMargin: string; formFieldsetMargin: string;
formLegendMargin: string; formLegendMargin: string;
...@@ -75,6 +76,7 @@ export interface GrafanaThemeCommons { ...@@ -75,6 +76,7 @@ export interface GrafanaThemeCommons {
formInputMargin: string; formInputMargin: string;
formLabelPadding: string; formLabelPadding: string;
formLabelMargin: string; formLabelMargin: string;
formValidationMessagePadding: string;
}; };
border: { border: {
radius: { radius: {
......
...@@ -5,33 +5,14 @@ ...@@ -5,33 +5,14 @@
// GENERAL STYLES // GENERAL STYLES
// -------------- // --------------
// Groups of fields with labels on top (legends)
legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: $line-height-base;
font-size: $font-size-base * 1.5;
line-height: $line-height-base * 2;
color: $gray-3;
border: 0;
border-bottom: 1px solid #e5e5e5;
// Small
small {
font-size: $line-height-base * 0.75;
color: $gray-2;
}
}
// Reset height since textareas have rows // Reset height since textareas have rows
// Set font for forms // Set font for forms
label,
input, input,
button, button,
select, select,
textarea { textarea {
@include font-shorthand($font-size-base, normal, $line-height-base); // Set size, weight, line-height here @include font-shorthand($font-size-base, normal, $line-height-base);
} }
input, input,
button, button,
...@@ -40,11 +21,6 @@ textarea { ...@@ -40,11 +21,6 @@ textarea {
font-family: $font-family-sans-serif; // And only set font-family here for those that need it (note the missing label element) font-family: $font-family-sans-serif; // And only set font-family here for those that need it (note the missing label element)
} }
// Identify controls by their labels
label {
display: block;
}
input, input,
select { select {
background-color: $input-bg; background-color: $input-bg;
......
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