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`] = `
+
+`;
+
+exports[`EuiFormControlLayoutDelimited props delimiter is rendered as a node 1`] = `
+
+`;
+
+exports[`EuiFormControlLayoutDelimited props delimiter is rendered as a string 1`] = `
+
+`;
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,