From f74766aee049fdf24b0978efbf1cead4f0fe749e Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Tue, 23 Jul 2019 10:19:49 -0400 Subject: [PATCH] [Feature branch] Added EuiFormControlLayoutDelimited component (#2117) As a layout helper component to create date and number ranges * Added Sass var for `$euiFormControlLayoutGroupInputHeight` and compressed version --- .../form_control_layout_range.js | 137 ++++++++++++++++++ .../form_controls/form_controls_example.js | 45 ++++++ .../date_picker/_date_picker_range.scss | 3 +- .../super_date_picker/_mixins.scss | 6 +- src/components/form/_index.scss | 2 - src/components/form/_mixins.scss | 7 +- src/components/form/_variables.scss | 2 +- ...orm_control_layout_delimited.test.tsx.snap | 100 +++++++++++++ .../_form_control_layout.scss | 13 +- .../_form_control_layout_range.scss | 56 +++++++ .../form/form_control_layout/_index.scss | 2 + .../form/form_control_layout/_variables.scss | 8 + .../form_control_layout.tsx | 54 +------ .../form_control_layout_delimited.test.tsx | 52 +++++++ .../form_control_layout_delimited.tsx | 56 +++++++ .../form/form_control_layout/index.ts | 2 + src/components/form/index.js | 5 +- src/components/form/select/_select.scss | 4 +- src/components/index.js | 1 + 19 files changed, 494 insertions(+), 61 deletions(-) create mode 100644 src-docs/src/views/form_controls/form_control_layout_range.js create mode 100644 src/components/form/form_control_layout/__snapshots__/form_control_layout_delimited.test.tsx.snap create mode 100644 src/components/form/form_control_layout/_form_control_layout_range.scss create mode 100644 src/components/form/form_control_layout/_variables.scss create mode 100644 src/components/form/form_control_layout/form_control_layout_delimited.test.tsx create mode 100644 src/components/form/form_control_layout/form_control_layout_delimited.tsx diff --git a/src-docs/src/views/form_controls/form_control_layout_range.js b/src-docs/src/views/form_controls/form_control_layout_range.js new file mode 100644 index 00000000000..a11d465fc05 --- /dev/null +++ b/src-docs/src/views/form_controls/form_control_layout_range.js @@ -0,0 +1,137 @@ +import React, { Fragment } from 'react'; + +import { + EuiFormControlLayoutDelimited, + EuiSpacer, + EuiFormLabel, + EuiIcon, +} from '../../../../src/components'; + +export default () => ( + + + } + endControl={ + + } + /> + + + px} + startControl={ + + } + endControl={ + + } + /> + + + + } + endControl={ + + } + /> + + + {} }} + isLoading + startControl={ + + } + endControl={ + + } + /> + + + + } + endControl={ + + } + /> + + + + } + endControl={ + + } + /> + + + + } + endControl={ + + } + /> + + + + Add} + startControl={ + + } + delimiter="+" + endControl={ + + } + /> + + + + Merge} + startControl={ + + } + delimiter={} + endControl={ + + } + /> + + + + Read only} + startControl={ + + } + endControl={ + + } + /> + +); 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 7fcdb3d04f0..9d186929cd1 100644 --- a/src-docs/src/views/form_controls/form_controls_example.js +++ b/src-docs/src/views/form_controls/form_controls_example.js @@ -18,6 +18,7 @@ import { EuiFieldText, EuiFilePicker, EuiFormControlLayout, + EuiFormControlLayoutDelimited, EuiLink, EuiRadio, EuiRadioGroup, @@ -78,6 +79,10 @@ import FormControlLayout from './form_control_layout'; const formControlLayoutSource = require('!!raw-loader!./form_control_layout'); const formControlLayoutHtml = renderToHtml(FormControlLayout); +import FormControlLayoutRange from './form_control_layout_range'; +const formControlLayoutRangeSource = require('!!raw-loader!./form_control_layout_range'); +const formControlLayoutRangeHtml = renderToHtml(FormControlLayoutRange); + export const FormControlsExample = { title: 'Form controls', sections: [ @@ -351,5 +356,45 @@ export const FormControlsExample = { }, demo: , }, + { + title: 'Form control layout delimited', + source: [ + { + type: GuideSectionTypes.JS, + code: formControlLayoutRangeSource, + }, + { + type: GuideSectionTypes.HTML, + code: formControlLayoutRangeHtml, + }, + ], + text: ( + +

+ Building block only +

+ +

+ Like EuiFormControlLayout,{' '} + EuiFormControlLayoutDelimited is generally used + internally to consistently style form controls. This component + specifically lays out two form controls with center text or icon. +

+

+ It takes all of the same props as{' '} + EuiFormControlLayout except for{' '} + children. Instead it requires both a{' '} + single startControl and a{' '} + single endControl. You can + optionally change the center content to a different string or node + (like an EuiIcon). +

+
+ ), + props: { + EuiFormControlLayoutDelimited, + }, + demo: , + }, ], }; diff --git a/src/components/date_picker/_date_picker_range.scss b/src/components/date_picker/_date_picker_range.scss index 583562cd5a3..cedeb5772b4 100644 --- a/src/components/date_picker/_date_picker_range.scss +++ b/src/components/date_picker/_date_picker_range.scss @@ -1,5 +1,6 @@ @import '../form/variables'; @import '../form/mixins'; +@import '../form/form_control_layout/variables'; /** * 1. Account for inner box-shadow style border @@ -34,7 +35,7 @@ padding: 0; .euiDatePicker { - height: $euiFormControlHeight - 2px; + height: $euiFormControlLayoutGroupInputHeight; } } diff --git a/src/components/date_picker/super_date_picker/_mixins.scss b/src/components/date_picker/super_date_picker/_mixins.scss index 02db2c4fd55..4f5a7a5e23a 100644 --- a/src/components/date_picker/super_date_picker/_mixins.scss +++ b/src/components/date_picker/super_date_picker/_mixins.scss @@ -1,10 +1,12 @@ +@import '../../form/form_control_layout/variables'; + @mixin euiSuperDatePickerText { @include euiFormControlText; display: block; width: 100%; padding: 0 $euiSizeS; - line-height: $euiFormControlHeight - 2px; - height: $euiFormControlHeight - 2px; + line-height: $euiFormControlLayoutGroupInputHeight; + height: $euiFormControlLayoutGroupInputHeight; word-break: break-all; transition: background $euiAnimSpeedFast ease-in; } diff --git a/src/components/form/_index.scss b/src/components/form/_index.scss index 689fdbb54a3..e8f451f6b3f 100644 --- a/src/components/form/_index.scss +++ b/src/components/form/_index.scss @@ -1,5 +1,3 @@ -@import 'form_control_layout/mixins'; - @import 'variables'; @import 'mixins'; diff --git a/src/components/form/_mixins.scss b/src/components/form/_mixins.scss index 9672e9a34b5..e97115c2884 100644 --- a/src/components/form/_mixins.scss +++ b/src/components/form/_mixins.scss @@ -1,3 +1,6 @@ +@import 'variables'; +@import 'form_control_layout/variables'; + @mixin euiPlaceholderPerBrowser { // sass-lint:disable-block no-vendor-prefixes // Each prefix must be it's own content block @@ -54,11 +57,11 @@ } &--inGroup:not(:read-only) { - height: $euiFormControlHeight - 2px; /* 2 */ + height: $euiFormControlLayoutGroupInputHeight; /* 2 */ } &--inGroup#{&}--compressed:not(:read-only) { - height: $euiFormControlCompressedHeight - 2px; /* 2 */ + height: $euiFormControlLayoutGroupInputCompressedHeight; /* 2 */ } } } diff --git a/src/components/form/_variables.scss b/src/components/form/_variables.scss index e42d4f02023..f192c28b9e6 100644 --- a/src/components/form/_variables.scss +++ b/src/components/form/_variables.scss @@ -16,7 +16,7 @@ $euiSwitchIconHeight: $euiSize !default; // Coloring $euiFormBackgroundColor: tintOrShade($euiColorLightestShade, 60%, 40%) !default; $euiFormBackgroundDisabledColor: darken($euiColorLightestShade, 2%) !default; -$euiFormBorderOpaqueColor: shade(desaturate(adjust-hue($euiColorPrimary, 22), 22.95), 26%) !default; +$euiFormBorderOpaqueColor: shadeOrTint(desaturate(adjust-hue($euiColorPrimary, 22), 22.95), 26%, 60%) !default; $euiFormBorderColor: transparentize($euiFormBorderOpaqueColor, .9) !default; $euiFormBorderDisabledColor: transparentize($euiFormBorderOpaqueColor, .9) !default; $euiFormCustomControlDisabledIconColor: shadeOrTint($euiColorMediumShade, 38%, 48.5%) !default; // exact 508c foreground for $euiColorLightShade diff --git a/src/components/form/form_control_layout/__snapshots__/form_control_layout_delimited.test.tsx.snap b/src/components/form/form_control_layout/__snapshots__/form_control_layout_delimited.test.tsx.snap new file mode 100644 index 00000000000..e42a39191cb --- /dev/null +++ b/src/components/form/form_control_layout/__snapshots__/form_control_layout_delimited.test.tsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiFormControlLayoutDelimited is rendered 1`] = ` +
+
+ + start + +
+
+ → +
+
+ + end + +
+
+`; + +exports[`EuiFormControlLayoutDelimited props delimiter is rendered as a node 1`] = ` +
+
+ + start + +
+
+ +
+
+ + end + +
+
+`; + +exports[`EuiFormControlLayoutDelimited props delimiter is rendered as a string 1`] = ` +
+
+ + start + +
+
+ + +
+
+ + end + +
+
+`; diff --git a/src/components/form/form_control_layout/_form_control_layout.scss b/src/components/form/form_control_layout/_form_control_layout.scss index 6910a59c6c8..12cfbafe776 100644 --- a/src/components/form/form_control_layout/_form_control_layout.scss +++ b/src/components/form/form_control_layout/_form_control_layout.scss @@ -28,7 +28,7 @@ .euiFormControlLayout__prepend, .euiFormControlLayout__append { flex-shrink: 0; - height: $euiFormControlHeight - 2px; /* 1 */ + height: $euiFormControlLayoutGroupInputHeight; line-height: $euiFontSize; border: none; // remove any border in case it exists @@ -66,7 +66,7 @@ &.euiFormControlLayout--compressed { .euiFormControlLayout__prepend, .euiFormControlLayout__append { - height: $euiFormControlCompressedHeight - 2px; /* 1 */ + height: $euiFormControlLayoutGroupInputCompressedHeight; &.euiFormLabel, &.euiText { @@ -76,12 +76,19 @@ } } + > .euiFormControlLayout--compressed { + height: $euiFormControlLayoutGroupInputCompressedHeight; + } + // // ReadOnly alterations &.euiFormControlLayout--readOnly { @include euiFormControlReadOnlyStyle; padding: 0; /* 1 */ - background-color: transparent; // Ensures the input and layout don't double up on background color + + input { + background-color: transparent; // Ensures the input and layout don't double up on background color + } .euiFormControlLayout__prepend, .euiFormControlLayout__append { diff --git a/src/components/form/form_control_layout/_form_control_layout_range.scss b/src/components/form/form_control_layout/_form_control_layout_range.scss new file mode 100644 index 00000000000..81c7af082c3 --- /dev/null +++ b/src/components/form/form_control_layout/_form_control_layout_range.scss @@ -0,0 +1,56 @@ +.euiFormControlLayoutDelimited { + // Match just the regular drop shadow of inputs + @include euiFormControlDefaultShadow; + padding: 1px; /* 1 */ + + > .euiFormControlLayout__childrenWrapper { + display: flex; + align-items: center; + } + + input { + height: $euiFormControlLayoutGroupInputHeight; + } + + &[class*='--compressed'] input { + height: $euiFormControlLayoutGroupInputCompressedHeight; + padding-top: 0; // Fixes IE + padding-bottom: 0; // Fixes IE + } + + &[class*='--fullWidth'] input { + max-width: none; + } + + .euiFormControlLayoutIcons { + // Absolutely positioning the icons doesn't work because they + // overlay only one of controls making the layout unbalanced + position: static; // Overrider absolute + padding-left: $euiFormControlPadding; + padding-right: $euiFormControlPadding; + flex-shrink: 0; // Fixes IE + + &:not(.euiFormControlLayoutIcons--right) { + order: -1; + } + } +} + +.euiFormControlLayoutDelimited__child--noStyle { + // sass-lint:disable-block no-important + box-shadow: none !important; + border-radius: 0 !important; +} + +.euiFormControlLayoutDelimited__child--centered { + text-align: center; +} + +.euiFormControlLayoutDelimited__delimeter { + // sass-lint:disable-block no-important + // Override EuiText line-height + line-height: 1 !important; + flex: 0 0 auto; + padding-left: $euiFormControlPadding / 2; + padding-right: $euiFormControlPadding / 2; +} diff --git a/src/components/form/form_control_layout/_index.scss b/src/components/form/form_control_layout/_index.scss index f5a9b5d33bc..d14b350f0ef 100644 --- a/src/components/form/form_control_layout/_index.scss +++ b/src/components/form/form_control_layout/_index.scss @@ -1,5 +1,7 @@ +@import 'variables'; @import 'mixins'; @import 'form_control_layout'; +@import 'form_control_layout_range'; @import 'form_control_layout_icons'; @import 'form_control_layout_clear_button'; @import 'form_control_layout_custom_icon'; diff --git a/src/components/form/form_control_layout/_variables.scss b/src/components/form/form_control_layout/_variables.scss new file mode 100644 index 00000000000..8e04c7aeba1 --- /dev/null +++ b/src/components/form/form_control_layout/_variables.scss @@ -0,0 +1,8 @@ +@import '../variables'; + +/** + * 1. Account for inner box-shadow style border + */ + +$euiFormControlLayoutGroupInputHeight: $euiFormControlHeight - 2px; /* 1 */ +$euiFormControlLayoutGroupInputCompressedHeight: $euiFormControlCompressedHeight - 2px; /* 1 */ diff --git a/src/components/form/form_control_layout/form_control_layout.tsx b/src/components/form/form_control_layout/form_control_layout.tsx index aaf51f8fa70..b0198868c9a 100644 --- a/src/components/form/form_control_layout/form_control_layout.tsx +++ b/src/components/form/form_control_layout/form_control_layout.tsx @@ -17,28 +17,8 @@ export { ICON_SIDES } from './form_control_layout_icons'; type ReactElements = ReactElement | ReactElement[]; -// if `prepend` and/or `append` is specified then `children` must be undefined or a single ReactElement -interface AppendWithChildren { - append: ReactElements; - children?: ReactElement; -} -interface PrependWithChildren { - prepend: ReactElements; - children?: ReactElement; -} -type SiblingsWithChildren = AppendWithChildren | PrependWithChildren; - -type ChildrenOptions = - | SiblingsWithChildren - | { - append?: undefined | null; - prepend?: undefined | null; - children?: ReactNode; - }; - type EuiFormControlLayoutProps = CommonProps & - HTMLAttributes & - ChildrenOptions & { + HTMLAttributes & { /** * Creates an input group with element(s) coming before children */ @@ -47,6 +27,7 @@ type EuiFormControlLayoutProps = CommonProps & * Creates an input group with element(s) coming after children */ append?: ReactElements; + children?: ReactNode; icon?: EuiFormControlLayoutIconsProps['icon']; clear?: EuiFormControlLayoutIconsProps['clear']; fullWidth?: boolean; @@ -57,14 +38,6 @@ type EuiFormControlLayoutProps = CommonProps & readOnly?: boolean; }; -function isChildrenIsReactElement( - append: EuiFormControlLayoutProps['append'], - prepend: EuiFormControlLayoutProps['prepend'], - children: EuiFormControlLayoutProps['children'] -): children is ReactElement { - return (!!append || !!prepend) && children != null; -} - export class EuiFormControlLayout extends Component { render() { const { @@ -94,23 +67,14 @@ export class EuiFormControlLayout extends Component { className ); - const prependNodes = this.renderPrepends(); - const appendNodes = this.renderAppends(); - - let clonedChildren; - if (isChildrenIsReactElement(append, prepend, children)) { - clonedChildren = cloneElement(children, { - className: `${ - children.props.className - } euiFormControlLayout__child--noStyle`, - }); - } + const prependNodes = this.renderPrepends(prepend); + const appendNodes = this.renderAppends(append); return (
{prependNodes}
- {clonedChildren || children} + {children} { ); } - renderPrepends() { - const { prepend } = this.props; - + renderPrepends(prepend: ReactElements | undefined | null) { if (!prepend) { return; } @@ -137,9 +99,7 @@ export class EuiFormControlLayout extends Component { return prependNodes; } - renderAppends() { - const { append } = this.props; - + renderAppends(append: ReactElements | undefined | null) { if (!append) { return; } diff --git a/src/components/form/form_control_layout/form_control_layout_delimited.test.tsx b/src/components/form/form_control_layout/form_control_layout_delimited.test.tsx new file mode 100644 index 00000000000..22dce0c5498 --- /dev/null +++ b/src/components/form/form_control_layout/form_control_layout_delimited.test.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { EuiFormControlLayoutDelimited } from './form_control_layout_delimited'; +import { EuiIcon } from '../../icon'; + +describe('EuiFormControlLayoutDelimited', () => { + test('is rendered', () => { + const component = render( + start} + endControl={end} + {...requiredProps} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + describe('delimiter', () => { + describe('is rendered', () => { + test('as a string', () => { + const component = render( + start} + endControl={end} + delimiter="+" + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('as a node', () => { + const icon = ; + + const component = render( + start} + endControl={end} + delimiter={icon} + /> + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); + }); +}); diff --git a/src/components/form/form_control_layout/form_control_layout_delimited.tsx b/src/components/form/form_control_layout/form_control_layout_delimited.tsx new file mode 100644 index 00000000000..56f3b34481a --- /dev/null +++ b/src/components/form/form_control_layout/form_control_layout_delimited.tsx @@ -0,0 +1,56 @@ +import React, { + FunctionComponent, + ReactElement, + cloneElement, + ReactNode, +} from 'react'; +import classNames from 'classnames'; + +import { EuiText } from '../../text'; +import { EuiFormControlLayout } from './form_control_layout'; + +type EuiFormControlLayoutDelimitedProps = Partial & { + /** + * Left side control + */ + startControl: ReactElement; + /** + * Right side control + */ + endControl: ReactElement; + /** + * The center content. Accepts a string to be wrapped in a subdued EuiText + * or a single ReactElement + */ + delimiter?: ReactNode; + className?: string; +}; + +export const EuiFormControlLayoutDelimited: FunctionComponent< + EuiFormControlLayoutDelimitedProps +> = ({ startControl, endControl, delimiter = '→', className, ...rest }) => { + const classes = classNames('euiFormControlLayoutDelimited', className); + + return ( + + {addClassesToControl(startControl)} + + {delimiter} + + {addClassesToControl(endControl)} + + ); +}; + +function addClassesToControl(control: ReactElement) { + return cloneElement(control, { + className: classNames( + control.props.className, + 'euiFormControlLayoutDelimited__child--noStyle', + 'euiFormControlLayoutDelimited__child--centered' + ), + }); +} diff --git a/src/components/form/form_control_layout/index.ts b/src/components/form/form_control_layout/index.ts index 79e0774a1f0..0f16df04588 100644 --- a/src/components/form/form_control_layout/index.ts +++ b/src/components/form/form_control_layout/index.ts @@ -5,3 +5,5 @@ export { export { EuiFormControlLayoutCustomIcon, } from './form_control_layout_custom_icon'; + +export { EuiFormControlLayoutDelimited } from './form_control_layout_delimited'; diff --git a/src/components/form/index.js b/src/components/form/index.js index 75310f40940..0c0d51a554b 100644 --- a/src/components/form/index.js +++ b/src/components/form/index.js @@ -6,7 +6,10 @@ export { EuiFieldSearch } from './field_search'; export { EuiFieldText } from './field_text'; export { EuiFilePicker } from './file_picker'; export { EuiForm } from './form'; -export { EuiFormControlLayout } from './form_control_layout'; +export { + EuiFormControlLayout, + EuiFormControlLayoutDelimited, +} from './form_control_layout'; export { EuiFormErrorText } from './form_error_text'; export { EuiFormHelpText } from './form_help_text'; export { EuiFormLabel } from './form_label'; diff --git a/src/components/form/select/_select.scss b/src/components/form/select/_select.scss index 9dc5e60b1a8..93e2f795623 100644 --- a/src/components/form/select/_select.scss +++ b/src/components/form/select/_select.scss @@ -20,11 +20,11 @@ } &--inGroup { - line-height: $euiFormControlHeight - 2px; /* 2 */ + line-height: $euiFormControlLayoutGroupInputHeight; /* 2 */ } &--inGroup#{&}--compressed { - line-height: $euiFormControlCompressedHeight - 2px; /* 2 */ + line-height: $euiFormControlLayoutGroupInputCompressedHeight; /* 2 */ } // Turn off linter for some MS specific bits. diff --git a/src/components/index.js b/src/components/index.js index 32923f19c17..4a8d83c490d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -106,6 +106,7 @@ export { EuiFilePicker, EuiForm, EuiFormControlLayout, + EuiFormControlLayoutDelimited, EuiFormErrorText, EuiFormHelpText, EuiFormLabel,