diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4f944195b..6232e6ba685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Converted `EuiSuggest` to Typescript ([#2692](https://github.com/elastic/eui/pull/2692)) - Converted `EuiErrorBoundary` to Typescript ([#2690](https://github.com/elastic/eui/pull/2690)) - Updated `EuiNavDrawer` to accept React fragments ([#2710](https://github.com/elastic/eui/pull/2710)) +- Added `EuiFormFieldset` and `EuiFormLegend` components ([#2706](https://github.com/elastic/eui/pull/2706)) **Bug fixes** diff --git a/src-docs/src/views/form_controls/fieldset.tsx b/src-docs/src/views/form_controls/fieldset.tsx new file mode 100644 index 00000000000..9a7ddb18fb5 --- /dev/null +++ b/src-docs/src/views/form_controls/fieldset.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import { EuiFormFieldset } from '../../../../src/components/form/form_fieldset'; +import { EuiSwitch } from '../../../../src/components/form/switch'; +import { EuiSpacer } from '../../../../src/components/spacer'; + +export default () => ( + + {}} checked={false} /> + + {}} checked={true} /> + +); diff --git a/src-docs/src/views/form_controls/form_controls_example.js b/src-docs/src/views/form_controls/form_controls_example.js index 4c61e9ea9f4..685883913e9 100644 --- a/src-docs/src/views/form_controls/form_controls_example.js +++ b/src-docs/src/views/form_controls/form_controls_example.js @@ -17,6 +17,8 @@ import { EuiFieldSearch, EuiFieldText, EuiFilePicker, + EuiFormFieldset, + EuiFormLegend, EuiFormControlLayout, EuiFormControlLayoutDelimited, EuiLink, @@ -25,6 +27,7 @@ import { EuiSelect, EuiSwitch, EuiTextArea, + EuiSpacer, } from '../../../../src/components'; import FieldSearch from './field_search'; @@ -79,6 +82,10 @@ import PrependAppend from './prepend_append'; const PrependAppendSource = require('!!raw-loader!./prepend_append'); const PrependAppendHtml = renderToHtml(PrependAppend); +import Fieldset from './fieldset'; +const fieldsetSource = require('!!raw-loader!./fieldset'); +const fieldsetHtml = renderToHtml(Fieldset); + import FormControlLayout from './form_control_layout'; const formControlLayoutSource = require('!!raw-loader!./form_control_layout'); const formControlLayoutHtml = renderToHtml(FormControlLayout); @@ -319,6 +326,61 @@ export const FormControlsExample = { }, demo: , }, + { + title: 'Fieldset and legend', + source: [ + { + type: GuideSectionTypes.JS, + code: fieldsetSource, + }, + { + type: GuideSectionTypes.HTML, + code: fieldsetHtml, + }, + ], + text: ( + + + "[Use a fieldset and legend] for groups of related controls + where the individual labels for each control do not provide a + sufficient description, and an additional group level + description is needed."{' '} + + WCAG Spec + + + } + /> + +

+ EuiFieldset simply wraps its children in a{' '} + <fieldset> with the option to add a{' '} + <legend> via the legend{' '} + object prop. +

+
+ ), + props: { + EuiFormFieldset, + EuiFormLegend, + }, + demo:
, + snippet: [ + ` + /* Controls */ +`, + ` + /* Controls */ +`, + ], + }, { title: 'Prepend and Append', text: ( diff --git a/src/components/form/_index.scss b/src/components/form/_index.scss index ae9fcb9d333..0cd3dd54e79 100644 --- a/src/components/form/_index.scss +++ b/src/components/form/_index.scss @@ -8,6 +8,7 @@ @import 'form'; @import 'form_control_layout/index'; @import 'form_error_text/index'; +@import 'form_fieldset/index'; @import 'form_help_text/index'; @import 'form_label/index'; @import 'form_row/index'; diff --git a/src/components/form/form_fieldset/__snapshots__/form_fieldset.test.tsx.snap b/src/components/form/form_fieldset/__snapshots__/form_fieldset.test.tsx.snap new file mode 100644 index 00000000000..5317bcbf64e --- /dev/null +++ b/src/components/form/form_fieldset/__snapshots__/form_fieldset.test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiFormFieldset is rendered 1`] = ` +
+ +
+`; + +exports[`EuiFormFieldset props legend is rendered 1`] = ` +
+ + Legend + + +
+`; diff --git a/src/components/form/form_fieldset/__snapshots__/form_legend.test.tsx.snap b/src/components/form/form_fieldset/__snapshots__/form_legend.test.tsx.snap new file mode 100644 index 00000000000..2f0257d2b10 --- /dev/null +++ b/src/components/form/form_fieldset/__snapshots__/form_legend.test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiFormLegend is rendered 1`] = ` + + Legend + +`; + +exports[`EuiFormLegend props compressed is rendered 1`] = ` + + Legend + +`; + +exports[`EuiFormLegend props hidden is rendered 1`] = ` + + + Legend + + +`; diff --git a/src/components/form/form_fieldset/_form_legend.scss b/src/components/form/form_fieldset/_form_legend.scss new file mode 100644 index 00000000000..0b79ffc6f06 --- /dev/null +++ b/src/components/form/form_fieldset/_form_legend.scss @@ -0,0 +1,11 @@ +.euiFormLegend { + @include euiFormLabel; + + &:not(.euiFormLegend-isHidden) { + margin-bottom: $euiSizeS; + + &.euiFormLegend--compressed { + margin-bottom: $euiSizeXS; + } + } +} diff --git a/src/components/form/form_fieldset/_index.scss b/src/components/form/form_fieldset/_index.scss new file mode 100644 index 00000000000..2751e94c43c --- /dev/null +++ b/src/components/form/form_fieldset/_index.scss @@ -0,0 +1 @@ +@import 'form_legend'; diff --git a/src/components/form/form_fieldset/form_fieldset.test.tsx b/src/components/form/form_fieldset/form_fieldset.test.tsx new file mode 100644 index 00000000000..b236b048dcc --- /dev/null +++ b/src/components/form/form_fieldset/form_fieldset.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { EuiFormFieldset } from './form_fieldset'; + +describe('EuiFormFieldset', () => { + test('is rendered', () => { + const component = render( + + + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + test('legend is rendered', () => { + const component = render( + + + + ); + + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/form/form_fieldset/form_fieldset.tsx b/src/components/form/form_fieldset/form_fieldset.tsx new file mode 100644 index 00000000000..72be13d2758 --- /dev/null +++ b/src/components/form/form_fieldset/form_fieldset.tsx @@ -0,0 +1,28 @@ +import React, { HTMLAttributes, FunctionComponent } from 'react'; +import { CommonProps } from '../../common'; +import { EuiFormLegendProps, EuiFormLegend } from './form_legend'; + +export interface EuiFormFieldsetProps + extends CommonProps, + HTMLAttributes { + /** + * Adds an EuiFormLegend element as the first child + */ + legend?: EuiFormLegendProps; +} + +export const EuiFormFieldset: FunctionComponent = ({ + children, + className, + legend, + ...rest +}) => { + const legendDisplay = !!legend && ; + + return ( +
+ {legendDisplay} + {children} +
+ ); +}; diff --git a/src/components/form/form_fieldset/form_legend.test.tsx b/src/components/form/form_fieldset/form_legend.test.tsx new file mode 100644 index 00000000000..2a3676563bb --- /dev/null +++ b/src/components/form/form_fieldset/form_legend.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { EuiFormLegend } from './form_legend'; + +describe('EuiFormLegend', () => { + test('is rendered', () => { + const component = render( + Legend + ); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + test('hidden is rendered', () => { + const component = render( + Legend + ); + + expect(component).toMatchSnapshot(); + }); + + test('compressed is rendered', () => { + const component = render( + Legend + ); + + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/form/form_fieldset/form_legend.tsx b/src/components/form/form_fieldset/form_legend.tsx new file mode 100644 index 00000000000..d5d6344aee3 --- /dev/null +++ b/src/components/form/form_fieldset/form_legend.tsx @@ -0,0 +1,44 @@ +import React, { HTMLAttributes, FunctionComponent, ReactNode } from 'react'; +import { CommonProps } from '../../common'; +import classNames from 'classnames'; +import { EuiScreenReaderOnly } from '../../accessibility'; + +export type EuiFormLegendProps = HTMLAttributes & + CommonProps & { + children: ReactNode; + /** + * For a hidden legend that is still visible to the screen reader, set to 'hidden' + */ + display?: 'hidden' | 'visible'; + compressed?: boolean; + }; + +export const EuiFormLegend: FunctionComponent = ({ + children, + className, + display = 'visible', + compressed, + ...rest +}) => { + const isLegendHidden = display === 'hidden'; + const classes = classNames( + 'euiFormLegend', + { + 'euiFormLegend-isHidden': isLegendHidden, + 'euiFormLegend--compressed': compressed, + }, + className + ); + + return ( + + {isLegendHidden ? ( + + {children} + + ) : ( + children + )} + + ); +}; diff --git a/src/components/form/form_fieldset/index.ts b/src/components/form/form_fieldset/index.ts new file mode 100644 index 00000000000..e1eb28b81d0 --- /dev/null +++ b/src/components/form/form_fieldset/index.ts @@ -0,0 +1,2 @@ +export { EuiFormFieldset, EuiFormFieldsetProps } from './form_fieldset'; +export { EuiFormLegend, EuiFormLegendProps } from './form_legend'; diff --git a/src/components/form/form_label/_form_label.scss b/src/components/form/form_label/_form_label.scss index 887d7de476c..14055e077af 100644 --- a/src/components/form/form_label/_form_label.scss +++ b/src/components/form/form_label/_form_label.scss @@ -2,11 +2,9 @@ * 1. Focused state overrides invalid state. */ .euiFormLabel { - @include euiFontSizeXS; + @include euiFormLabel; display: inline-block; transition: all $euiAnimSpeedFast $euiAnimSlightResistance; - color: $euiTitleColor; - font-weight: $euiFontWeightSemiBold; &.euiFormLabel-isInvalid { color: $euiColorDanger; /* 1 */ diff --git a/src/components/form/index.js b/src/components/form/index.js index 0c0d51a554b..b07c9f0c34f 100644 --- a/src/components/form/index.js +++ b/src/components/form/index.js @@ -11,6 +11,7 @@ export { EuiFormControlLayoutDelimited, } from './form_control_layout'; export { EuiFormErrorText } from './form_error_text'; +export { EuiFormFieldset, EuiFormLegend } from './form_fieldset'; export { EuiFormHelpText } from './form_help_text'; export { EuiFormLabel } from './form_label'; export { EuiFormRow } from './form_row'; diff --git a/src/components/index.js b/src/components/index.js index febbba31948..f4cb7cf6652 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -118,8 +118,10 @@ export { EuiFormControlLayout, EuiFormControlLayoutDelimited, EuiFormErrorText, + EuiFormFieldset, EuiFormHelpText, EuiFormLabel, + EuiFormLegend, EuiFormRow, EuiRadio, EuiRadioGroup, diff --git a/src/global_styling/mixins/_form.scss b/src/global_styling/mixins/_form.scss index 1aa4e831e1e..56b0e87bab1 100644 --- a/src/global_styling/mixins/_form.scss +++ b/src/global_styling/mixins/_form.scss @@ -1,3 +1,10 @@ +// Labels +@mixin euiFormLabel { + @include euiFontSizeXS; + color: $euiTitleColor; + font-weight: $euiFontWeightSemiBold; +} + @mixin euiFormControlLayoutPadding($numOfIcons, $side: 'right', $compressed: false) { $firstIconSize: $euiFormControlPadding + $euiSize + $euiFormControlPadding; $secondIconSize: $euiFormControlPadding + $euiSize;