= ({ children, className }) => {
+ const theme = useTheme();
+ const styles = getFieldValidationMessageStyles(theme);
+
+ return (
+
+
+ {children}
+
+ );
+};
diff --git a/packages/grafana-ui/src/components/Forms/Form.story.tsx b/packages/grafana-ui/src/components/Forms/Form.story.tsx
new file mode 100644
index 00000000000..a9e374055cd
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/Form.story.tsx
@@ -0,0 +1,20 @@
+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 (
+
+
+
+ );
+});
diff --git a/packages/grafana-ui/src/components/Forms/Label.story.tsx b/packages/grafana-ui/src/components/Forms/Label.story.tsx
new file mode 100644
index 00000000000..1386c24cd55
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/Label.story.tsx
@@ -0,0 +1,22 @@
+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 ;
+};
diff --git a/packages/grafana-ui/src/components/Forms/Label.tsx b/packages/grafana-ui/src/components/Forms/Label.tsx
new file mode 100644
index 00000000000..1b648f0ad49
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/Label.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { useTheme, stylesFactory } from '../../themes';
+import { GrafanaTheme } from '../../types';
+import { css, cx } from 'emotion';
+
+export interface LabelProps extends React.HTMLAttributes {
+ 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 = ({ children, description, className, ...labelProps }) => {
+ const theme = useTheme();
+ const styles = getLabelStyles(theme);
+
+ return (
+
+
+ {description &&
{description}
}
+
+ );
+};
diff --git a/packages/grafana-ui/src/components/Forms/Legend.story.tsx b/packages/grafana-ui/src/components/Forms/Legend.story.tsx
new file mode 100644
index 00000000000..6820c6ff363
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/Legend.story.tsx
@@ -0,0 +1,19 @@
+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 ;
+});
diff --git a/packages/grafana-ui/src/components/Forms/Legend.tsx b/packages/grafana-ui/src/components/Forms/Legend.tsx
new file mode 100644
index 00000000000..17260714320
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/Legend.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { useTheme, stylesFactory } from '../../themes';
+import { GrafanaTheme } from '../../types';
+import { css, cx } from 'emotion';
+
+export interface LabelProps extends React.HTMLAttributes {
+ 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 = ({ children, className, ...legendProps }) => {
+ const theme = useTheme();
+ const styles = getLegendStyles(theme);
+
+ return (
+
+ );
+};
diff --git a/packages/grafana-ui/src/components/Forms/getFormStyles.ts b/packages/grafana-ui/src/components/Forms/getFormStyles.ts
new file mode 100644
index 00000000000..41e9baa9da5
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/getFormStyles.ts
@@ -0,0 +1,13 @@
+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),
+ };
+});
diff --git a/packages/grafana-ui/src/components/Forms/index.ts b/packages/grafana-ui/src/components/Forms/index.ts
new file mode 100644
index 00000000000..446a2a1d026
--- /dev/null
+++ b/packages/grafana-ui/src/components/Forms/index.ts
@@ -0,0 +1,9 @@
+import { getFormStyles } from './getFormStyles';
+import { Label } from './Label';
+
+const Forms = {
+ getFormStyles,
+ Label: Label,
+};
+
+export default Forms;
diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap
index cf2726ebd46..284e5e33910 100644
--- a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap
+++ b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap
@@ -209,8 +209,10 @@ exports[`Render should render with base threshold 1`] = `
"formInputPaddingHorizontal": "8px",
"formLabelMargin": "0 0 4px 0",
"formLabelPadding": "0 0 0 2px",
- "formLegendMargin": "16px",
+ "formLegendMargin": "0 0 16px 0",
"formMargin": "32px",
+ "formSpacingBase": 8,
+ "formValidationMessagePadding": "4px 8px",
"gutter": "30px",
"insetSquishMd": "4px 8px",
"lg": "24px",
@@ -415,8 +417,10 @@ exports[`Render should render with base threshold 1`] = `
"formInputPaddingHorizontal": "8px",
"formLabelMargin": "0 0 4px 0",
"formLabelPadding": "0 0 0 2px",
- "formLegendMargin": "16px",
+ "formLegendMargin": "0 0 16px 0",
"formMargin": "32px",
+ "formSpacingBase": 8,
+ "formValidationMessagePadding": "4px 8px",
"gutter": "30px",
"insetSquishMd": "4px 8px",
"lg": "24px",
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index 42c528210f0..97d3f80668a 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -93,3 +93,6 @@ export { Spinner } from './Spinner/Spinner';
export { FadeTransition } from './transitions/FadeTransition';
export { SlideOutTransition } from './transitions/SlideOutTransition';
export { Segment, SegmentAsync, SegmentSelect } from './Segment/';
+
+// Next-gen forms
+export { default as Forms } from './Forms';
diff --git a/packages/grafana-ui/src/themes/default.ts b/packages/grafana-ui/src/themes/default.ts
index a2ef04da58f..22578ff0ac9 100644
--- a/packages/grafana-ui/src/themes/default.ts
+++ b/packages/grafana-ui/src/themes/default.ts
@@ -82,9 +82,10 @@ const theme: GrafanaThemeCommons = {
// Next-gen forms spacing variables
// TODO: Move variables definition to respective components when implementing
+ formSpacingBase: SPACING_BASE,
formMargin: `${SPACING_BASE * 4}px`,
formFieldsetMargin: `${SPACING_BASE * 2}px`,
- formLegendMargin: `${SPACING_BASE * 2}px`,
+ formLegendMargin: `0 0 ${SPACING_BASE * 2}px 0`,
formInputHeight: `${SPACING_BASE * 4}px`,
formInputPaddingHorizontal: `${SPACING_BASE}px`,
@@ -95,6 +96,7 @@ const theme: GrafanaThemeCommons = {
formInputMargin: `${SPACING_BASE * 2}px`,
formLabelPadding: '0 0 0 2px',
formLabelMargin: '0 0 4px 0',
+ formValidationMessagePadding: '4px 8px',
},
border: {
radius: {
diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts
index d7bd051987d..03c0c24ac3e 100644
--- a/packages/grafana-ui/src/types/theme.ts
+++ b/packages/grafana-ui/src/types/theme.ts
@@ -64,6 +64,7 @@ export interface GrafanaThemeCommons {
// Next-gen forms spacing variables
// TODO: Move variables definition to respective components when implementing
+ formSpacingBase: number;
formMargin: string;
formFieldsetMargin: string;
formLegendMargin: string;
@@ -75,6 +76,7 @@ export interface GrafanaThemeCommons {
formInputMargin: string;
formLabelPadding: string;
formLabelMargin: string;
+ formValidationMessagePadding: string;
};
border: {
radius: {
diff --git a/public/sass/base/_forms.scss b/public/sass/base/_forms.scss
index bfd59b113b9..7cf9f7eb1a0 100644
--- a/public/sass/base/_forms.scss
+++ b/public/sass/base/_forms.scss
@@ -5,33 +5,14 @@
// 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
// Set font for forms
-label,
+
input,
button,
select,
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,
button,
@@ -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)
}
-// Identify controls by their labels
-label {
- display: block;
-}
-
input,
select {
background-color: $input-bg;