From f5c3e2595df6e2fc4fd14710b64d845023a7cc96 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 14 Aug 2017 19:35:17 -0700 Subject: [PATCH] [UI Framework] [K7] Refactor form component interfaces (#13493) * Add htmlIdGeneratorFactory service. * Extract KuiFormHelpText and KuiFormErrorText components. * Add KuiFormLabel. Update form examples to emphasize the basic form components. * Refine KuiFormRow, KuiSelect, and KuiFieldX component interfaces. - Add KuiFormControlLayout component for adding icons to controls. - Make icon a concern of the field itself, not the KuiFormRow. - Allow developer to specify icon for KuiFieldText and KuiFieldNumber. - Hardcode icons for KuiSelect, KuiFieldPassword, and KuiFieldSearch components. * Move validation from KuiFormRow to each individual component. - Create :invalid pseudo element selector. - Create KuiValidatableControl component. * Refine isInvalid and error interface for FormRow and Form. * Return child element without wrapper from KuiFormControlLayout if no icon is specified. * Override form control invalid state with focus state. * Update form examples to demonstrate all controls. - Fix help text and error text spacing regression. - Refine KuiCheckbox interface. * Rename KuiCheckbox to KuiCheckboxGroup and remove top padding on first item. * Refine KuiSwitch interface. --- ui_framework/dist/ui_framework_theme_dark.css | 247 +++++++++++------- .../dist/ui_framework_theme_light.css | 247 +++++++++++------- .../doc_site/src/views/form/field_text.js | 65 ----- .../doc_site/src/views/form/form_controls.js | 111 ++++++++ .../doc_site/src/views/form/form_example.js | 59 ++--- .../doc_site/src/views/form/form_popover.js | 38 ++- .../form/{form_everything.js => form_rows.js} | 134 +++++----- .../src/views/form/form_validation.js | 84 ------ .../doc_site/src/views/form/validation.js | 110 ++++++++ .../form/__snapshots__/form.test.js.snap | 9 + ui_framework/src/components/form/_index.scss | 58 +++- .../__snapshots__/checkbox_group.test.js.snap | 3 + .../components/form/checkbox/_checkbox.scss | 1 - .../form/checkbox/_checkbox_group.scss | 3 + .../src/components/form/checkbox/_index.scss | 1 + .../src/components/form/checkbox/checkbox.js | 29 -- .../form/checkbox/checkbox_group.js | 56 ++++ .../form/checkbox/checkbox_group.test.js | 16 ++ .../src/components/form/checkbox/index.js | 2 +- .../__snapshots__/field_number.test.js.snap | 10 + .../form/field_number/_field_number.scss | 3 +- .../form/field_number/field_number.js | 57 +++- .../__snapshots__/field_password.test.js.snap | 24 ++ .../form/field_password/_field_password.scss | 3 +- .../form/field_password/field_password.js | 45 +++- .../__snapshots__/field_search.test.js.snap | 24 ++ .../form/field_search/_field_search.scss | 4 +- .../form/field_search/field_search.js | 44 +++- .../__snapshots__/field_text.test.js.snap | 10 + .../form/field_text/_field_text.scss | 3 +- .../components/form/field_text/field_text.js | 53 +++- ui_framework/src/components/form/form.js | 37 ++- .../form_control_layout.test.js.snap | 3 + .../_form_control_layout.scss | 17 ++ .../form/form_control_layout/_index.scss | 1 + .../form_control_layout.js | 45 ++++ .../form_control_layout.test.js | 17 ++ .../form/form_control_layout/index.js | 3 + .../form_error_text.test.js.snap | 9 + .../form_error_text/_form_error_text.scss | 5 + .../form/form_error_text/_index.scss | 1 + .../form/form_error_text/form_error_text.js | 21 ++ .../form_error_text/form_error_text.test.js | 16 ++ .../components/form/form_error_text/index.js | 3 + .../__snapshots__/form_help_text.test.js.snap | 9 + .../form/form_help_text/_form_help_text.scss | 5 + .../form/form_help_text/_index.scss | 1 + .../form/form_help_text/form_help_text.js | 21 ++ .../form_help_text/form_help_text.test.js | 16 ++ .../components/form/form_help_text/index.js | 3 + .../__snapshots__/form_label.test.js.snap | 9 + .../form/form_label/_form_label.scss | 18 ++ .../components/form/form_label/_index.scss | 1 + .../components/form/form_label/form_label.js | 26 ++ .../form_label.test.js} | 6 +- .../src/components/form/form_label/index.js | 3 + .../__snapshots__/form_row.test.js.snap | 11 + .../components/form/form_row/_form_row.scss | 76 +----- .../src/components/form/form_row/form_row.js | 149 ++++++----- .../components/form/form_row/form_row.test.js | 4 +- ui_framework/src/components/form/index.js | 15 +- .../range/__snapshots__/range.test.js.snap | 12 + .../src/components/form/range/range.js | 4 +- .../select/__snapshots__/select.test.js.snap | 23 ++ .../src/components/form/select/_select.scss | 9 +- .../src/components/form/select/select.js | 49 +++- .../switch/__snapshots__/switch.test.js.snap | 34 +++ .../src/components/form/switch/_switch.scss | 17 +- .../src/components/form/switch/switch.js | 35 ++- .../__snapshots__/text_area.test.js.snap | 10 + .../components/form/text_area/_text_area.scss | 2 +- .../components/form/text_area/text_area.js | 40 ++- .../validatable_control.test.js.snap | 3 + .../form/validatable_control/index.js | 3 + .../validatable_control.js | 34 +++ .../validatable_control.test.js | 17 ++ .../icon/__snapshots__/icon.test.js.snap | 26 ++ ui_framework/src/components/index.js | 16 +- .../html_id_generator_factory.js | 28 ++ .../src/services/accessibility/index.js | 4 + 80 files changed, 1714 insertions(+), 756 deletions(-) delete mode 100644 ui_framework/doc_site/src/views/form/field_text.js create mode 100644 ui_framework/doc_site/src/views/form/form_controls.js rename ui_framework/doc_site/src/views/form/{form_everything.js => form_rows.js} (58%) delete mode 100644 ui_framework/doc_site/src/views/form/form_validation.js create mode 100644 ui_framework/doc_site/src/views/form/validation.js create mode 100644 ui_framework/src/components/form/__snapshots__/form.test.js.snap create mode 100644 ui_framework/src/components/form/checkbox/__snapshots__/checkbox_group.test.js.snap create mode 100644 ui_framework/src/components/form/checkbox/_checkbox_group.scss delete mode 100644 ui_framework/src/components/form/checkbox/checkbox.js create mode 100644 ui_framework/src/components/form/checkbox/checkbox_group.js create mode 100644 ui_framework/src/components/form/checkbox/checkbox_group.test.js create mode 100644 ui_framework/src/components/form/field_number/__snapshots__/field_number.test.js.snap create mode 100644 ui_framework/src/components/form/field_password/__snapshots__/field_password.test.js.snap create mode 100644 ui_framework/src/components/form/field_search/__snapshots__/field_search.test.js.snap create mode 100644 ui_framework/src/components/form/field_text/__snapshots__/field_text.test.js.snap create mode 100644 ui_framework/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.js.snap create mode 100644 ui_framework/src/components/form/form_control_layout/_form_control_layout.scss create mode 100644 ui_framework/src/components/form/form_control_layout/_index.scss create mode 100644 ui_framework/src/components/form/form_control_layout/form_control_layout.js create mode 100644 ui_framework/src/components/form/form_control_layout/form_control_layout.test.js create mode 100644 ui_framework/src/components/form/form_control_layout/index.js create mode 100644 ui_framework/src/components/form/form_error_text/__snapshots__/form_error_text.test.js.snap create mode 100644 ui_framework/src/components/form/form_error_text/_form_error_text.scss create mode 100644 ui_framework/src/components/form/form_error_text/_index.scss create mode 100644 ui_framework/src/components/form/form_error_text/form_error_text.js create mode 100644 ui_framework/src/components/form/form_error_text/form_error_text.test.js create mode 100644 ui_framework/src/components/form/form_error_text/index.js create mode 100644 ui_framework/src/components/form/form_help_text/__snapshots__/form_help_text.test.js.snap create mode 100644 ui_framework/src/components/form/form_help_text/_form_help_text.scss create mode 100644 ui_framework/src/components/form/form_help_text/_index.scss create mode 100644 ui_framework/src/components/form/form_help_text/form_help_text.js create mode 100644 ui_framework/src/components/form/form_help_text/form_help_text.test.js create mode 100644 ui_framework/src/components/form/form_help_text/index.js create mode 100644 ui_framework/src/components/form/form_label/__snapshots__/form_label.test.js.snap create mode 100644 ui_framework/src/components/form/form_label/_form_label.scss create mode 100644 ui_framework/src/components/form/form_label/_index.scss create mode 100644 ui_framework/src/components/form/form_label/form_label.js rename ui_framework/src/components/form/{checkbox/checkbox.test.js => form_label/form_label.test.js} (67%) create mode 100644 ui_framework/src/components/form/form_label/index.js create mode 100644 ui_framework/src/components/form/form_row/__snapshots__/form_row.test.js.snap create mode 100644 ui_framework/src/components/form/range/__snapshots__/range.test.js.snap create mode 100644 ui_framework/src/components/form/select/__snapshots__/select.test.js.snap create mode 100644 ui_framework/src/components/form/switch/__snapshots__/switch.test.js.snap create mode 100644 ui_framework/src/components/form/text_area/__snapshots__/text_area.test.js.snap create mode 100644 ui_framework/src/components/form/validatable_control/__snapshots__/validatable_control.test.js.snap create mode 100644 ui_framework/src/components/form/validatable_control/index.js create mode 100644 ui_framework/src/components/form/validatable_control/validatable_control.js create mode 100644 ui_framework/src/components/form/validatable_control/validatable_control.test.js create mode 100644 ui_framework/src/services/accessibility/html_id_generator_factory.js diff --git a/ui_framework/dist/ui_framework_theme_dark.css b/ui_framework/dist/ui_framework_theme_dark.css index a8e3d93d7e77b1..3c6c3df11a2438 100644 --- a/ui_framework/dist/ui_framework_theme_dark.css +++ b/ui_framework/dist/ui_framework_theme_dark.css @@ -563,80 +563,12 @@ table { transform: translateY(2px); /* 1 */ } -.kuiForm__error { - font-size: 14px; - font-size: 0.875rem; - line-height: 18px; - list-style: disc; - margin-left: 32px; } - -.kuiForm__errors { - margin-bottom: 16px; } - -.kuiFormRow { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - position: relative; - max-width: 400px; } - .kuiFormRow + * { - margin-top: 24px; } - .kuiFormRow.kuiFormRow--withIcon input { - padding-left: 40px; } - .kuiFormRow.kuiFormRow--dropdown input { - padding-left: 12px; - padding-right: 40px; } - .kuiFormRow.kuiFormRow--select .kuiFormRow__icon { - left: auto; - right: 12px; } - .kuiFormRow .kuiFormRow__label { - font-size: 12px; - padding-bottom: 8px; - cursor: pointer; - transition: all 150ms cubic-bezier(0.694, 0.0482, 0.335, 1); - font-weight: 500; - -webkit-box-ordinal-group: 0; - -webkit-order: -1; - -ms-flex-order: -1; - order: -1; } - .kuiFormRow .kuiFormRow__helpText { - font-size: 12px; - padding: 8px 0; - color: #D9D9D9; } - .kuiFormRow .kuiFormRow__error { - font-size: 12px; - padding: 8px 0; - color: #bf4d4d; } - .kuiFormRow .kuiFormRow__error + * { - padding-top: 0; } - .kuiFormRow .kuiFormRow__icon { - position: absolute; - top: 32px; - left: 12px; } - .kuiFormRow input:focus + label, - .kuiFormRow select:focus + label, - .kuiFormRow textarea:focus + label { - color: #4da1c0; } - .kuiFormRow.kuiFormRow--invalid .kuiFormRow__label { - color: #bf4d4d !important; } - .kuiFormRow.kuiFormRow--invalid input[type="text"], - .kuiFormRow.kuiFormRow--invalid input[type="password"], - .kuiFormRow.kuiFormRow--invalid input[type="number"], - .kuiFormRow.kuiFormRow--invalid input[type="search"], - .kuiFormRow.kuiFormRow--invalid select, - .kuiFormRow.kuiFormRow--invalid textarea { - box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d !important; } - +/** + * 1. Override invalid state with focus state. + */ .kuiCheckbox { position: relative; - height: 24px; - margin-top: 8px; } + height: 24px; } .kuiCheckbox .kuiCheckbox__label { position: absolute; padding-left: 32px; @@ -672,69 +604,171 @@ table { -webkit-mask: url("../src/components/icon/assets/check.svg") center center no-repeat; mask: url("../src/components/icon/assets/check.svg") center center no-repeat; } +.kuiCheckboxGroup__item + .kuiCheckboxGroup__item { + margin-top: 8px; } + .kuiFieldNumber { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiFieldNumber:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiFieldNumber:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } + .kuiFieldNumber--withIcon { + padding-left: 40px; } .kuiFieldPassword { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-left: 40px; } + .kuiFieldPassword:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiFieldPassword:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } .kuiFieldSearch { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-left: 40px; } + .kuiFieldSearch:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiFieldSearch:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } .kuiFieldText { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiFieldText:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiFieldText:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } + .kuiFieldText--withIcon { + padding-left: 40px; } + +.kuiForm__error { + font-size: 14px; + font-size: 0.875rem; + line-height: 18px; + list-style: disc; + margin-left: 32px; } + +.kuiForm__errors { + margin-bottom: 16px; } + +.kuiFormControlLayout { + min-width: 256px; + max-width: 400px; + width: 100%; + display: inline-block; + position: relative; } + +.kuiFormControlLayout__icon { + position: absolute; + top: 12px; + left: 12px; } + +.kuiFormControlLayout__icon--right { + left: auto; + right: 12px; } + +.kuiFormErrorText { + font-size: 12px; + padding: 8px 0; + color: #bf4d4d; } + +.kuiFormHelpText { + font-size: 12px; + padding: 8px 0; + color: #D9D9D9; } + +/** + * 1. Focused state overrides invalid state. + */ +.kuiFormLabel { + font-size: 12px; + margin-bottom: 8px; + cursor: pointer; + transition: all 150ms cubic-bezier(0.694, 0.0482, 0.335, 1); + font-weight: 500; } + .kuiFormLabel.kuiFormLabel-isInvalid { + color: #bf4d4d; + /* 1 */ } + .kuiFormLabel.kuiFormLabel-isFocused { + color: #4da1c0; + /* 1 */ } + +/** + * 1. Coerce inline form elements to behave as block-level elements. + */ +.kuiFormRow { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + /* 1 */ + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + /* 1 */ + max-width: 400px; } + .kuiFormRow + * { + margin-top: 24px; } + .kuiFormRow .kuiFormRow__text + .kuiFormRow__text { + padding-top: 0; } .kuiRange { -webkit-appearance: none; @@ -808,22 +842,32 @@ table { width: 16px; margin-top: 0; } +/** + * 1. Leave room for caret. + */ .kuiSelect { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-right: 40px; + /* 1 */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + .kuiSelect:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiSelect:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } .kuiSelect::-ms-expand { @@ -833,10 +877,6 @@ table { position: relative; display: inline-block; height: 24px; - cursor: pointer; - /** - * 1. The label is our main clickable area. It sits above the actual switch. - */ /** * 1. The input is "hidden" but still focusable. */ @@ -853,23 +893,23 @@ table { * When input is not checked, we shift around the positioning of sibling/child selectors. */ } .kuiSwitch .kuiSwitch__label { - position: absolute; - left: 0; - padding-left: 60px; - z-index: 2; + padding-left: 8px; line-height: 24px; - font-size: 14px; - cursor: pointer; } + font-size: 14px; } .kuiSwitch .kuiSwitch__input { position: absolute; opacity: 0; - z-index: -1; } + /* 1 */ + width: 100%; + height: 100%; + cursor: pointer; } .kuiSwitch .kuiSwitch__input:focus + .kuiSwitch__body { background: #222; } .kuiSwitch .kuiSwitch__input:focus + .kuiSwitch__body .kuiSwitch__thumb { border-color: #4da1c0; background-color: #4da1c0; } .kuiSwitch .kuiSwitch__body { + pointer-events: none; width: 52px; height: 24px; background: #262626; @@ -931,18 +971,23 @@ table { left: -40px; } .kuiTextArea { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #F5F5F5; - min-width: 256px; background: #262626; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #262626; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiTextArea:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #bf4d4d; } .kuiTextArea:focus { + /* 1 */ background: #222; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #222, inset 0 -2px 0 0 #4da1c0; } diff --git a/ui_framework/dist/ui_framework_theme_light.css b/ui_framework/dist/ui_framework_theme_light.css index 1237453bc1f39a..422c3e41afd9d9 100644 --- a/ui_framework/dist/ui_framework_theme_light.css +++ b/ui_framework/dist/ui_framework_theme_light.css @@ -563,80 +563,12 @@ table { transform: translateY(2px); /* 1 */ } -.kuiForm__error { - font-size: 14px; - font-size: 0.875rem; - line-height: 18px; - list-style: disc; - margin-left: 32px; } - -.kuiForm__errors { - margin-bottom: 16px; } - -.kuiFormRow { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - position: relative; - max-width: 400px; } - .kuiFormRow + * { - margin-top: 24px; } - .kuiFormRow.kuiFormRow--withIcon input { - padding-left: 40px; } - .kuiFormRow.kuiFormRow--dropdown input { - padding-left: 12px; - padding-right: 40px; } - .kuiFormRow.kuiFormRow--select .kuiFormRow__icon { - left: auto; - right: 12px; } - .kuiFormRow .kuiFormRow__label { - font-size: 12px; - padding-bottom: 8px; - cursor: pointer; - transition: all 150ms cubic-bezier(0.694, 0.0482, 0.335, 1); - font-weight: 500; - -webkit-box-ordinal-group: 0; - -webkit-order: -1; - -ms-flex-order: -1; - order: -1; } - .kuiFormRow .kuiFormRow__helpText { - font-size: 12px; - padding: 8px 0; - color: #666; } - .kuiFormRow .kuiFormRow__error { - font-size: 12px; - padding: 8px 0; - color: #A30000; } - .kuiFormRow .kuiFormRow__error + * { - padding-top: 0; } - .kuiFormRow .kuiFormRow__icon { - position: absolute; - top: 32px; - left: 12px; } - .kuiFormRow input:focus + label, - .kuiFormRow select:focus + label, - .kuiFormRow textarea:focus + label { - color: #0079a5; } - .kuiFormRow.kuiFormRow--invalid .kuiFormRow__label { - color: #A30000 !important; } - .kuiFormRow.kuiFormRow--invalid input[type="text"], - .kuiFormRow.kuiFormRow--invalid input[type="password"], - .kuiFormRow.kuiFormRow--invalid input[type="number"], - .kuiFormRow.kuiFormRow--invalid input[type="search"], - .kuiFormRow.kuiFormRow--invalid select, - .kuiFormRow.kuiFormRow--invalid textarea { - box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000 !important; } - +/** + * 1. Override invalid state with focus state. + */ .kuiCheckbox { position: relative; - height: 24px; - margin-top: 8px; } + height: 24px; } .kuiCheckbox .kuiCheckbox__label { position: absolute; padding-left: 32px; @@ -672,69 +604,171 @@ table { -webkit-mask: url("../src/components/icon/assets/check.svg") center center no-repeat; mask: url("../src/components/icon/assets/check.svg") center center no-repeat; } +.kuiCheckboxGroup__item + .kuiCheckboxGroup__item { + margin-top: 8px; } + .kuiFieldNumber { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiFieldNumber:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiFieldNumber:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } + .kuiFieldNumber--withIcon { + padding-left: 40px; } .kuiFieldPassword { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-left: 40px; } + .kuiFieldPassword:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiFieldPassword:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } .kuiFieldSearch { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-left: 40px; } + .kuiFieldSearch:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiFieldSearch:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } .kuiFieldText { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiFieldText:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiFieldText:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } + .kuiFieldText--withIcon { + padding-left: 40px; } + +.kuiForm__error { + font-size: 14px; + font-size: 0.875rem; + line-height: 18px; + list-style: disc; + margin-left: 32px; } + +.kuiForm__errors { + margin-bottom: 16px; } + +.kuiFormControlLayout { + min-width: 256px; + max-width: 400px; + width: 100%; + display: inline-block; + position: relative; } + +.kuiFormControlLayout__icon { + position: absolute; + top: 12px; + left: 12px; } + +.kuiFormControlLayout__icon--right { + left: auto; + right: 12px; } + +.kuiFormErrorText { + font-size: 12px; + padding: 8px 0; + color: #A30000; } + +.kuiFormHelpText { + font-size: 12px; + padding: 8px 0; + color: #666; } + +/** + * 1. Focused state overrides invalid state. + */ +.kuiFormLabel { + font-size: 12px; + margin-bottom: 8px; + cursor: pointer; + transition: all 150ms cubic-bezier(0.694, 0.0482, 0.335, 1); + font-weight: 500; } + .kuiFormLabel.kuiFormLabel-isInvalid { + color: #A30000; + /* 1 */ } + .kuiFormLabel.kuiFormLabel-isFocused { + color: #0079a5; + /* 1 */ } + +/** + * 1. Coerce inline form elements to behave as block-level elements. + */ +.kuiFormRow { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + /* 1 */ + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + /* 1 */ + max-width: 400px; } + .kuiFormRow + * { + margin-top: 24px; } + .kuiFormRow .kuiFormRow__text + .kuiFormRow__text { + padding-top: 0; } .kuiRange { -webkit-appearance: none; @@ -808,22 +842,32 @@ table { width: 16px; margin-top: 0; } +/** + * 1. Leave room for caret. + */ .kuiSelect { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; - border-radius: 0; } + border-radius: 0; + padding-right: 40px; + /* 1 */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + .kuiSelect:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiSelect:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } .kuiSelect::-ms-expand { @@ -833,10 +877,6 @@ table { position: relative; display: inline-block; height: 24px; - cursor: pointer; - /** - * 1. The label is our main clickable area. It sits above the actual switch. - */ /** * 1. The input is "hidden" but still focusable. */ @@ -853,23 +893,23 @@ table { * When input is not checked, we shift around the positioning of sibling/child selectors. */ } .kuiSwitch .kuiSwitch__label { - position: absolute; - left: 0; - padding-left: 60px; - z-index: 2; + padding-left: 8px; line-height: 24px; - font-size: 14px; - cursor: pointer; } + font-size: 14px; } .kuiSwitch .kuiSwitch__input { position: absolute; opacity: 0; - z-index: -1; } + /* 1 */ + width: 100%; + height: 100%; + cursor: pointer; } .kuiSwitch .kuiSwitch__input:focus + .kuiSwitch__body { background: #FFF; } .kuiSwitch .kuiSwitch__input:focus + .kuiSwitch__body .kuiSwitch__thumb { border-color: #0079a5; background-color: #0079a5; } .kuiSwitch .kuiSwitch__body { + pointer-events: none; width: 52px; height: 24px; background: #fbfbfb; @@ -931,18 +971,23 @@ table { left: -40px; } .kuiTextArea { + min-width: 256px; + max-width: 400px; + width: 100%; border: none; font-size: 14px; font-family: "Roboto", Helvetica, Arial, sans-serif; padding: 12px; color: #3F3F3F; - min-width: 256px; background: #fbfbfb; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.08), inset -400px 0 0 0 #fbfbfb; transition: box-shadow 250ms ease-in, background 250ms ease-in; - max-width: 400px; border-radius: 0; } + .kuiTextArea:invalid { + /* 1 */ + box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #A30000; } .kuiTextArea:focus { + /* 1 */ background: #FFF; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.16), inset 0 0 0 0 #FFF, inset 0 -2px 0 0 #0079a5; } diff --git a/ui_framework/doc_site/src/views/form/field_text.js b/ui_framework/doc_site/src/views/form/field_text.js deleted file mode 100644 index 19b8bc4c1f5ce9..00000000000000 --- a/ui_framework/doc_site/src/views/form/field_text.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { - Component, -} from 'react'; - -import { - KuiFormRow, - KuiFieldText, -} from '../../../../components'; - -// Don't use this, make proper ids instead. This is just for the example. -function makeId() { - return Math.random().toString(36).substr(2, 5); -} - -export default class extends Component { - - render() { - return ( -
- - -

- - - - - - - - - - - - - - - - - - -
- ); - } -} - diff --git a/ui_framework/doc_site/src/views/form/form_controls.js b/ui_framework/doc_site/src/views/form/form_controls.js new file mode 100644 index 00000000000000..c066c3c576fde5 --- /dev/null +++ b/ui_framework/doc_site/src/views/form/form_controls.js @@ -0,0 +1,111 @@ +import React, { + Component, +} from 'react'; + +import { + KuiCheckboxGroup, + KuiFieldNumber, + KuiFieldPassword, + KuiFieldSearch, + KuiFieldText, + KuiRange, + KuiSelect, + KuiSwitch, + KuiTextArea, +} from '../../../../components'; + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isSwitchChecked: false, + }; + } + + onSwitchChange = () => { + this.setState({ + isSwitchChecked: !this.state.isSwitchChecked, + }); + } + + render() { + return ( +
+ + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + {} }, + { id: '1', label: 'Option two is checked by default', checked: true, onChange: () => {} }, + { id: '2', label: 'Option three', onChange: () => {} }, + ]} + /> +
+ ); + } +} diff --git a/ui_framework/doc_site/src/views/form/form_example.js b/ui_framework/doc_site/src/views/form/form_example.js index 7cec41f68425ef..4c0df93af4de54 100644 --- a/ui_framework/doc_site/src/views/form/form_example.js +++ b/ui_framework/doc_site/src/views/form/form_example.js @@ -11,17 +11,17 @@ import { GuideText, } from '../../components'; -import FieldText from './field_text'; -const fieldTextSource = require('!!raw!./field_text'); -const fieldTextHtml = renderToHtml(FieldText); +import FormControls from './form_controls'; +const formControlsSource = require('!!raw!./form_controls'); +const formControlsHtml = renderToHtml(FormControls); -import FormEverything from './form_everything'; -const formEverythingSource = require('!!raw!./form_everything'); -const formEverythingHtml = renderToHtml(FormEverything); +import FormRows from './form_rows'; +const formRowsSource = require('!!raw!./form_rows'); +const formRowsHtml = renderToHtml(FormRows); -import FormValidation from './form_validation'; -const formValidationSource = require('!!raw!./form_validation'); -const formValidationHtml = renderToHtml(FormValidation); +import Validation from './validation'; +const validationSource = require('!!raw!./validation'); +const validationHtml = renderToHtml(Validation); import FormPopover from './form_popover'; const formPopoverSource = require('!!raw!./form_popover'); @@ -30,46 +30,40 @@ const formPopoverHtml = renderToHtml(FormPopover); export default props => ( - - Each form input has a base component to cover generic use cases. These are raw HTML elements with only basic styling. - Additionally, you can wrap any of these elements with a FormRow which gives you optional - prebuilt labels, help text and validation. Below is an example showing the FieldText component - in a bunch of configurations, but they all act roughly the same. Farther down in the docs you can see an example - showing every form element and their individual prop settings (which mirror their HTML counterparts). - - - + + - This example shows every form element in use and showcases a variety of styles. Note that each one of these elements is wrapped - by FormRow. + Use the FormRow component to easily associate form components with + labels, help text, and error text. - + + ( + - Validation is achieved by applying invalid and optionally error props + Validation is achieved by applying isInvalid and optionally error props onto the KuiForm or KuiFormRow components. Errors are optional and are passed as an array in case you need to list many errors. - + diff --git a/ui_framework/doc_site/src/views/form/form_popover.js b/ui_framework/doc_site/src/views/form/form_popover.js index 0002fd22d0044c..8a607bde5c217f 100644 --- a/ui_framework/doc_site/src/views/form/form_popover.js +++ b/ui_framework/doc_site/src/views/form/form_popover.js @@ -22,16 +22,23 @@ export default class extends Component { this.state = { isPopoverOpen: false, + isSwitchChecked: true, }; } - onButtonClick() { + onSwitchChange = () => { + this.setState({ + isSwitchChecked: !this.state.isSwitchChecked, + }); + } + + onButtonClick = () => { this.setState({ isPopoverOpen: !this.state.isPopoverOpen, }); } - closePopover() { + closePopover = () => { this.setState({ isPopoverOpen: false, }); @@ -39,7 +46,12 @@ export default class extends Component { render() { const button = ( - + Form in a popover ); @@ -47,20 +59,32 @@ export default class extends Component { const formSample = ( - + + + - + ); @@ -72,7 +96,9 @@ export default class extends Component { isOpen={this.state.isPopoverOpen} closePopover={this.closePopover.bind(this)} > -
{formSample}
+
+ {formSample} +
); diff --git a/ui_framework/doc_site/src/views/form/form_everything.js b/ui_framework/doc_site/src/views/form/form_rows.js similarity index 58% rename from ui_framework/doc_site/src/views/form/form_everything.js rename to ui_framework/doc_site/src/views/form/form_rows.js index 7170b429cce32c..59f1572d345470 100644 --- a/ui_framework/doc_site/src/views/form/form_everything.js +++ b/ui_framework/doc_site/src/views/form/form_rows.js @@ -3,152 +3,142 @@ import React, { } from 'react'; import { - KuiForm, - KuiCheckbox, + KuiCheckboxGroup, KuiFieldNumber, KuiFieldPassword, - KuiRange, - KuiFormRow, KuiFieldSearch, + KuiFieldText, + KuiForm, + KuiFormRow, + KuiRange, KuiSelect, KuiSwitch, - KuiFieldText, KuiTextArea, } from '../../../../components'; - // Don't use this, make proper ids instead. This is just for the example. function makeId() { return Math.random().toString(36).substr(2, 5); } - export default class extends Component { + constructor(props) { + super(props); - render() { - - // Checkboxes are passed as an array of objects. They can be optionally checked to start. - const checkboxOptions = [ - { id: makeId(), label: 'Option one' }, - { id: makeId(), label: 'Option two is checked by default', checked: true }, - { id: makeId(), label: 'Option three' }, - ]; - - // Select options are passed as an array of objects. - const selectOptions = [ - { value: 'option_one', text: 'Option one' }, - { value: 'option_two', text: 'Option two' }, - { value: 'option_three', text: 'Option three' }, - ]; + this.state = { + isSwitchChecked: false, + }; + } + onSwitchChange = () => { + this.setState({ + isSwitchChecked: !this.state.isSwitchChecked, + }); + } - const formSample = ( + render() { + return ( - - + - + - - + - + - + + - + - + {} }, + { id: makeId(), label: 'Option two is checked by default', checked: true, onChange: () => {} }, + { id: makeId(), label: 'Option three', onChange: () => {} }, + ]} + /> - ); - - return ( -
- {formSample} -
- ); } } diff --git a/ui_framework/doc_site/src/views/form/form_validation.js b/ui_framework/doc_site/src/views/form/form_validation.js deleted file mode 100644 index b29e3d75bdece3..00000000000000 --- a/ui_framework/doc_site/src/views/form/form_validation.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { - Component, -} from 'react'; - -import { - KuiButton, - KuiForm, - KuiCheckbox, - KuiFormRow, - KuiFieldText, -} from '../../../../components'; - -function makeId() { - return Math.random().toString(36).substr(2, 5); -} - -export default class extends Component { - constructor(props) { - super(props); - - this.state = { - showErrors: false, - }; - } - - onButtonClick() { - this.setState({ - showErrors: !this.state.showErrors, - }); - } - - render() { - const button = ( - - Toggle errors - - ); - - const checkboxOptions = [ - { id: makeId(), label: 'Option one' }, - { id: makeId(), label: 'Option two' }, - { id: makeId(), label: 'Option three' }, - ]; - - let errors = null; - if (this.state.showErrors) { - errors = ['Here\'s an example of an error', 'You might have more than one error, so pass an array.']; - } else { - errors = null; - } - - - return ( -
- - - - - - - - - - - {button} - -
- ); - } -} - diff --git a/ui_framework/doc_site/src/views/form/validation.js b/ui_framework/doc_site/src/views/form/validation.js new file mode 100644 index 00000000000000..184a50e91dd8c7 --- /dev/null +++ b/ui_framework/doc_site/src/views/form/validation.js @@ -0,0 +1,110 @@ +import React, { + Component, +} from 'react'; + +import { + KuiButton, + KuiForm, + KuiSelect, + KuiFormRow, + KuiTextArea, + KuiFieldText, +} from '../../../../components'; + +function makeId() { + return Math.random().toString(36).substr(2, 5); +} + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + showErrors: true, + }; + } + + onButtonClick() { + this.setState({ + showErrors: !this.state.showErrors, + }); + } + + render() { + const button = ( + + Toggle errors + + ); + + let errors; + + if (this.state.showErrors) { + errors = [ + 'Here\'s an example of an error', + 'You might have more than one error, so pass an array.', + ]; + } + + return ( +
+ + + + + + + + + + + + + + + + + + {button} + +
+ ); + } +} + diff --git a/ui_framework/src/components/form/__snapshots__/form.test.js.snap b/ui_framework/src/components/form/__snapshots__/form.test.js.snap new file mode 100644 index 00000000000000..098aa40be977b7 --- /dev/null +++ b/ui_framework/src/components/form/__snapshots__/form.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiForm is rendered 1`] = ` +
+`; diff --git a/ui_framework/src/components/form/_index.scss b/ui_framework/src/components/form/_index.scss index 0e5c3264c6a27b..bdc512a907fc41 100644 --- a/ui_framework/src/components/form/_index.scss +++ b/ui_framework/src/components/form/_index.scss @@ -1,32 +1,74 @@ $kuiFormMaxWidth: 400px; $kuiFormBackgroundColor: tintOrShade($kuiColorLightestShade, 60%, 25%); -@mixin kuiFieldStyle { +@mixin kuiFormControlSize { + min-width: $kuiSize * 16; + max-width: $kuiFormMaxWidth; + width: 100%; +} + +@mixin kuiFormControlWithIcon($isIconOptional: false) { + $iconPadding: $kuiSizeXXL; + + @if ($isIconOptional) { + @at-root { + #{&}--withIcon { + padding-left: $iconPadding; + } + } + } @else { + padding-left: $iconPadding; + } +} + +/** + * 1. Override invalid state with focus state. + */ +@mixin kuiFormControlStyle { + @include kuiFormControlSize; + border: none; font-size: $kuiFontSizeS; font-family: $kuiFontFamily; padding: $kuiSizeM; color: $kuiTextColor; - min-width: $kuiSize * 16; background: $kuiFormBackgroundColor; - box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0,0,0,0.08), inset -400px 0 0 0 $kuiFormBackgroundColor; + box-shadow: + 0 2px 2px -1px rgba(0, 0, 0, 0.1), + 0 0 0 1px rgba(0,0,0,0.08), + inset #{-$kuiFormMaxWidth} 0 0 0 $kuiFormBackgroundColor; transition: box-shadow $kuiAnimSpeedNormal ease-in, background $kuiAnimSpeedNormal ease-in; - max-width: $kuiFormMaxWidth; border-radius: 0; - &:focus { + &:invalid { /* 1 */ + box-shadow: + 0 $kuiSizeXS $kuiSizeXS (-$kuiSizeXS / 2) rgba(0, 0, 0, 0.1), + 0 0 0 1px rgba(0,0,0,0.16), + inset 0 0 0 0 $kuiColorEmptyShade, + inset 0 (-$kuiSizeXS / 2) 0 0 $kuiColorDanger; + } + + &:focus { /* 1 */ background: $kuiColorEmptyShade; - box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0,0,0,0.16), inset 0 0 0 0 $kuiColorEmptyShade, inset 0 -2px 0 0 $kuiColorPrimary; + box-shadow: + 0 4px 4px -2px rgba(0, 0, 0, 0.1), + 0 0 0 1px rgba(0,0,0,0.16), + inset 0 0 0 0 $kuiColorEmptyShade, + inset 0 -2px 0 0 $kuiColorPrimary; } } -@import 'form'; -@import 'form_row/index'; @import 'checkbox/index'; @import 'field_number/index'; @import 'field_password/index'; @import 'field_search/index'; @import 'field_text/index'; +@import 'form'; +@import 'form_control_layout/index'; +@import 'form_error_text/index'; +@import 'form_help_text/index'; +@import 'form_label/index'; +@import 'form_row/index'; @import 'range/index'; @import 'select/index'; @import 'switch/index'; diff --git a/ui_framework/src/components/form/checkbox/__snapshots__/checkbox_group.test.js.snap b/ui_framework/src/components/form/checkbox/__snapshots__/checkbox_group.test.js.snap new file mode 100644 index 00000000000000..f0c5e9213f6780 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/__snapshots__/checkbox_group.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiCheckboxGroup is rendered 1`] = `
`; diff --git a/ui_framework/src/components/form/checkbox/_checkbox.scss b/ui_framework/src/components/form/checkbox/_checkbox.scss index 171c9a1e011a8c..dc7b8e576e8050 100644 --- a/ui_framework/src/components/form/checkbox/_checkbox.scss +++ b/ui_framework/src/components/form/checkbox/_checkbox.scss @@ -1,7 +1,6 @@ .kuiCheckbox { position: relative; height: $kuiSizeL; - margin-top: $kuiSizeS; .kuiCheckbox__label { position: absolute; diff --git a/ui_framework/src/components/form/checkbox/_checkbox_group.scss b/ui_framework/src/components/form/checkbox/_checkbox_group.scss new file mode 100644 index 00000000000000..f23a128cc69722 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/_checkbox_group.scss @@ -0,0 +1,3 @@ +.kuiCheckboxGroup__item + .kuiCheckboxGroup__item { + margin-top: $kuiSizeS; +} diff --git a/ui_framework/src/components/form/checkbox/_index.scss b/ui_framework/src/components/form/checkbox/_index.scss index e6e53eb712869c..295196b245958a 100644 --- a/ui_framework/src/components/form/checkbox/_index.scss +++ b/ui_framework/src/components/form/checkbox/_index.scss @@ -1 +1,2 @@ @import 'checkbox'; +@import 'checkbox_group'; diff --git a/ui_framework/src/components/form/checkbox/checkbox.js b/ui_framework/src/components/form/checkbox/checkbox.js deleted file mode 100644 index 55490f31611491..00000000000000 --- a/ui_framework/src/components/form/checkbox/checkbox.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { - PropTypes, -} from 'react'; -import classNames from 'classnames'; - -export const KuiCheckbox = ({ options, className, ...rest }) => { - const classes = classNames('kuiCheckbox', className); - - return ( -
- {options.map((option, index) => { - - return ( -
- -
-
-
- -
- ); - })} -
- ); -}; - -KuiCheckbox.propTypes = { - options: PropTypes.arrayOf(React.PropTypes.object).isRequired, -}; diff --git a/ui_framework/src/components/form/checkbox/checkbox_group.js b/ui_framework/src/components/form/checkbox/checkbox_group.js new file mode 100644 index 00000000000000..e85925df8626bb --- /dev/null +++ b/ui_framework/src/components/form/checkbox/checkbox_group.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const KuiCheckboxGroup = ({ options, className, ...rest }) => { + const classes = classNames('kuiCheckbox kuiCheckboxGroup__item', className); + + return ( +
+ {options.map((option, index) => { + + return ( +
+ + +
+
+
+ + +
+ ); + })} +
+ ); +}; + +KuiCheckboxGroup.propTypes = { + options: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + checked: PropTypes.bool, + onChange: PropTypes.func.isRequired, + label: PropTypes.string, + }), + ).isRequired, +}; + +KuiCheckboxGroup.defaultProps = { + options: [], +}; diff --git a/ui_framework/src/components/form/checkbox/checkbox_group.test.js b/ui_framework/src/components/form/checkbox/checkbox_group.test.js new file mode 100644 index 00000000000000..3080858ada5c39 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/checkbox_group.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiCheckboxGroup } from './checkbox_group'; + +describe('KuiCheckboxGroup', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/checkbox/index.js b/ui_framework/src/components/form/checkbox/index.js index 4551ccb9b41558..078f315be912a8 100644 --- a/ui_framework/src/components/form/checkbox/index.js +++ b/ui_framework/src/components/form/checkbox/index.js @@ -1 +1 @@ -export { KuiCheckbox } from './checkbox'; +export { KuiCheckboxGroup } from './checkbox_group'; diff --git a/ui_framework/src/components/form/field_number/__snapshots__/field_number.test.js.snap b/ui_framework/src/components/form/field_number/__snapshots__/field_number.test.js.snap new file mode 100644 index 00000000000000..d00ca973aa1f4b --- /dev/null +++ b/ui_framework/src/components/form/field_number/__snapshots__/field_number.test.js.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFieldNumber is rendered 1`] = ` + +`; diff --git a/ui_framework/src/components/form/field_number/_field_number.scss b/ui_framework/src/components/form/field_number/_field_number.scss index 4e7c4bcc0fd652..82e0170e498f75 100644 --- a/ui_framework/src/components/form/field_number/_field_number.scss +++ b/ui_framework/src/components/form/field_number/_field_number.scss @@ -1,3 +1,4 @@ .kuiFieldNumber { - @include kuiFieldStyle; + @include kuiFormControlStyle; + @include kuiFormControlWithIcon($isIconOptional: true); } diff --git a/ui_framework/src/components/form/field_number/field_number.js b/ui_framework/src/components/form/field_number/field_number.js index e9affb862ebd10..9cdf1a1763b1ab 100644 --- a/ui_framework/src/components/form/field_number/field_number.js +++ b/ui_framework/src/components/form/field_number/field_number.js @@ -3,31 +3,60 @@ import React, { } from 'react'; import classNames from 'classnames'; -export const KuiFieldNumber = ({ className, id, placeholder, name, min, max, value, ...rest }) => { - const classes = classNames('kuiFieldNumber', className); +import { + KuiFormControlLayout, +} from '../form_control_layout'; + +import { + KuiValidatableControl, +} from '../validatable_control'; + +export const KuiFieldNumber = ({ + className, + icon, + id, + placeholder, + name, + min, + max, + value, + isInvalid, + ...rest, +}) => { + const classes = classNames('kuiFieldNumber', className, { + 'kuiFieldNumber--withIcon': icon, + }); return ( - + + + + + ); }; KuiFieldNumber.propTypes = { id: PropTypes.string, - name: PropTypes.string.isRequired, + name: PropTypes.string, min: PropTypes.number, max: PropTypes.number, step: PropTypes.number, value: PropTypes.number, + icon: PropTypes.string, + isInvalid: PropTypes.bool, }; KuiFieldNumber.defaultProps = { diff --git a/ui_framework/src/components/form/field_password/__snapshots__/field_password.test.js.snap b/ui_framework/src/components/form/field_password/__snapshots__/field_password.test.js.snap new file mode 100644 index 00000000000000..24fc5068be5b52 --- /dev/null +++ b/ui_framework/src/components/form/field_password/__snapshots__/field_password.test.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFieldPassword is rendered 1`] = ` +
+ + + + lock icon + + + +
+`; diff --git a/ui_framework/src/components/form/field_password/_field_password.scss b/ui_framework/src/components/form/field_password/_field_password.scss index 6b511ea2298fb8..db451488b4b8df 100644 --- a/ui_framework/src/components/form/field_password/_field_password.scss +++ b/ui_framework/src/components/form/field_password/_field_password.scss @@ -1,3 +1,4 @@ .kuiFieldPassword { - @include kuiFieldStyle; + @include kuiFormControlStyle; + @include kuiFormControlWithIcon($isIconOptional: false); } diff --git a/ui_framework/src/components/form/field_password/field_password.js b/ui_framework/src/components/form/field_password/field_password.js index a6bcadd17894ac..a001f1e192b8ee 100644 --- a/ui_framework/src/components/form/field_password/field_password.js +++ b/ui_framework/src/components/form/field_password/field_password.js @@ -3,27 +3,50 @@ import React, { } from 'react'; import classNames from 'classnames'; -export const KuiFieldPassword = ({ className, id, name, placeholder, value, ...rest }) => { +import { + KuiFormControlLayout, +} from '../form_control_layout'; + +import { + KuiValidatableControl, +} from '../validatable_control'; + +export const KuiFieldPassword = ({ + className, + id, + name, + placeholder, + value, + isInvalid, + ...rest, +}) => { const classes = classNames('kuiFieldPassword', className); return ( - + + + + + ); }; KuiFieldPassword.propTypes = { - name: PropTypes.string.isRequired, + name: PropTypes.string, id: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string, + isInvalid: PropTypes.bool, }; KuiFieldPassword.defaultProps = { diff --git a/ui_framework/src/components/form/field_search/__snapshots__/field_search.test.js.snap b/ui_framework/src/components/form/field_search/__snapshots__/field_search.test.js.snap new file mode 100644 index 00000000000000..eb21864577de93 --- /dev/null +++ b/ui_framework/src/components/form/field_search/__snapshots__/field_search.test.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFieldSearch is rendered 1`] = ` +
+ + + + search icon + + + +
+`; diff --git a/ui_framework/src/components/form/field_search/_field_search.scss b/ui_framework/src/components/form/field_search/_field_search.scss index 15530ebedc5c60..9be288e4472cda 100644 --- a/ui_framework/src/components/form/field_search/_field_search.scss +++ b/ui_framework/src/components/form/field_search/_field_search.scss @@ -1,4 +1,4 @@ .kuiFieldSearch { - @include kuiFieldStyle; + @include kuiFormControlStyle; + @include kuiFormControlWithIcon($isIconOptional: false); } - diff --git a/ui_framework/src/components/form/field_search/field_search.js b/ui_framework/src/components/form/field_search/field_search.js index 0253d6c5fd6302..1333484c82e29c 100644 --- a/ui_framework/src/components/form/field_search/field_search.js +++ b/ui_framework/src/components/form/field_search/field_search.js @@ -3,28 +3,50 @@ import React, { } from 'react'; import classNames from 'classnames'; +import { + KuiFormControlLayout, +} from '../form_control_layout'; -export const KuiFieldSearch = ({ className, id, name, placeholder, value, ...rest }) => { +import { + KuiValidatableControl, +} from '../validatable_control'; + +export const KuiFieldSearch = ({ + className, + id, + name, + placeholder, + value, + isInvalid, + ...rest, +}) => { const classes = classNames('kuiFieldSearch', className); return ( - + + + + + ); }; KuiFieldSearch.propTypes = { - name: PropTypes.string.isRequired, + name: PropTypes.string, id: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string, + isInvalid: PropTypes.bool, }; KuiFieldSearch.defaultProps = { diff --git a/ui_framework/src/components/form/field_text/__snapshots__/field_text.test.js.snap b/ui_framework/src/components/form/field_text/__snapshots__/field_text.test.js.snap new file mode 100644 index 00000000000000..18bf0ca7301a52 --- /dev/null +++ b/ui_framework/src/components/form/field_text/__snapshots__/field_text.test.js.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFieldText is rendered 1`] = ` + +`; diff --git a/ui_framework/src/components/form/field_text/_field_text.scss b/ui_framework/src/components/form/field_text/_field_text.scss index 4b8afbedfb8493..f458d7c07a11f3 100644 --- a/ui_framework/src/components/form/field_text/_field_text.scss +++ b/ui_framework/src/components/form/field_text/_field_text.scss @@ -1,3 +1,4 @@ .kuiFieldText { - @include kuiFieldStyle; + @include kuiFormControlStyle; + @include kuiFormControlWithIcon($isIconOptional: true); } diff --git a/ui_framework/src/components/form/field_text/field_text.js b/ui_framework/src/components/form/field_text/field_text.js index f04889921bb789..2ac8cd8370ac9b 100644 --- a/ui_framework/src/components/form/field_text/field_text.js +++ b/ui_framework/src/components/form/field_text/field_text.js @@ -3,27 +3,56 @@ import React, { } from 'react'; import classNames from 'classnames'; -export const KuiFieldText = ({ id, name, placeholder, value, className, ...rest }) => { - const classes = classNames('kuiFieldText', className); +import { + KuiFormControlLayout, +} from '../form_control_layout'; + +import { + KuiValidatableControl, +} from '../validatable_control'; + +export const KuiFieldText = ({ + id, + name, + placeholder, + value, + className, + icon, + isInvalid, + ...rest, +}) => { + const classes = classNames('kuiFieldText', className, { + 'kuiFieldText--withIcon': icon, + }); return ( - + + + + + ); }; KuiFieldText.propTypes = { - name: PropTypes.string.isRequired, + name: PropTypes.string, id: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string, + icon: PropTypes.string, + isInvalid: PropTypes.bool, }; KuiFieldText.defaultProps = { diff --git a/ui_framework/src/components/form/form.js b/ui_framework/src/components/form/form.js index a9b6907e1c2c53..f2d0b34e906e0a 100644 --- a/ui_framework/src/components/form/form.js +++ b/ui_framework/src/components/form/form.js @@ -4,26 +4,39 @@ import React, { import classNames from 'classnames'; import { KuiCallOut } from '../../../components'; -export const KuiForm = ({ children, className, invalid, errors, ...rest }) => { +export const KuiForm = ({ + children, + className, + isInvalid, + error, + ...rest, +}) => { const classes = classNames('kuiForm', className); + let optionalErrors; - - let optionalErrors = null; - if (errors) { + if (error) { + const errorTexts = Array.isArray(error) ? error : [error]; optionalErrors = (
    - {errors.map(function (error, index) { - return
  • {error}
  • ; - })} + {errorTexts.map(error => ( +
  • + {error} +
  • + ))}
); } - let optionalErrorAlert = null; - if (invalid) { + let optionalErrorAlert; + + if (isInvalid) { optionalErrorAlert = ( - + {optionalErrors} ); @@ -41,6 +54,6 @@ export const KuiForm = ({ children, className, invalid, errors, ...rest }) => { }; KuiForm.propTypes = { - invalid: PropTypes.bool, - errors: PropTypes.array, + isInvalid: PropTypes.bool, + error: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), }; diff --git a/ui_framework/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.js.snap b/ui_framework/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.js.snap new file mode 100644 index 00000000000000..f35543f9b69c28 --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFormControlLayout is rendered 1`] = ``; diff --git a/ui_framework/src/components/form/form_control_layout/_form_control_layout.scss b/ui_framework/src/components/form/form_control_layout/_form_control_layout.scss new file mode 100644 index 00000000000000..8698862574554a --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/_form_control_layout.scss @@ -0,0 +1,17 @@ +.kuiFormControlLayout { + @include kuiFormControlSize; + + display: inline-block; + position: relative; +} + + .kuiFormControlLayout__icon { + position: absolute; + top: $kuiSizeM; + left: $kuiSizeM; + } + + .kuiFormControlLayout__icon--right { + left: auto; + right: $kuiSizeM; + } diff --git a/ui_framework/src/components/form/form_control_layout/_index.scss b/ui_framework/src/components/form/form_control_layout/_index.scss new file mode 100644 index 00000000000000..563ea1b4af1900 --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/_index.scss @@ -0,0 +1 @@ +@import 'form_control_layout'; diff --git a/ui_framework/src/components/form/form_control_layout/form_control_layout.js b/ui_framework/src/components/form/form_control_layout/form_control_layout.js new file mode 100644 index 00000000000000..193e8e81b8f7b6 --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/form_control_layout.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { KuiIcon } from '../../../components'; + +const iconSideToClassNameMap = { + left: '', + right: 'kuiFormControlLayout__icon--right', +}; + +export const ICON_SIDES = Object.keys(iconSideToClassNameMap); + +export const KuiFormControlLayout = ({ children, icon, iconSide }) => { + if (icon) { + const iconClasses = classNames('kuiFormControlLayout__icon', iconSideToClassNameMap[iconSide]); + + const optionalIcon = ( + + ); + + return ( +
+ {children} + {optionalIcon} +
+ ); + } + + return children; +}; + +KuiFormControlLayout.propTypes = { + children: PropTypes.node, + icon: PropTypes.string, + iconSide: PropTypes.oneOf(ICON_SIDES), +}; + +KuiFormControlLayout.defaultProps = { + iconSide: 'left', +}; diff --git a/ui_framework/src/components/form/form_control_layout/form_control_layout.test.js b/ui_framework/src/components/form/form_control_layout/form_control_layout.test.js new file mode 100644 index 00000000000000..1c1e2cac43a5b1 --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/form_control_layout.test.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { render } from 'enzyme'; + +import { KuiFormControlLayout } from './form_control_layout'; + +describe('KuiFormControlLayout', () => { + test('is rendered', () => { + const component = render( + + + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/form_control_layout/index.js b/ui_framework/src/components/form/form_control_layout/index.js new file mode 100644 index 00000000000000..69f74cd3ea4d3b --- /dev/null +++ b/ui_framework/src/components/form/form_control_layout/index.js @@ -0,0 +1,3 @@ +export { + KuiFormControlLayout, +} from './form_control_layout'; diff --git a/ui_framework/src/components/form/form_error_text/__snapshots__/form_error_text.test.js.snap b/ui_framework/src/components/form/form_error_text/__snapshots__/form_error_text.test.js.snap new file mode 100644 index 00000000000000..4232fbeeccbba7 --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/__snapshots__/form_error_text.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFormErrorText is rendered 1`] = ` +
+`; diff --git a/ui_framework/src/components/form/form_error_text/_form_error_text.scss b/ui_framework/src/components/form/form_error_text/_form_error_text.scss new file mode 100644 index 00000000000000..de7e2053553a59 --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/_form_error_text.scss @@ -0,0 +1,5 @@ +.kuiFormErrorText { + font-size: $kuiFontSizeXS; + padding: $kuiSizeS 0; + color: $kuiColorDanger; +} diff --git a/ui_framework/src/components/form/form_error_text/_index.scss b/ui_framework/src/components/form/form_error_text/_index.scss new file mode 100644 index 00000000000000..eb02ecea23eef6 --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/_index.scss @@ -0,0 +1 @@ +@import 'form_error_text'; diff --git a/ui_framework/src/components/form/form_error_text/form_error_text.js b/ui_framework/src/components/form/form_error_text/form_error_text.js new file mode 100644 index 00000000000000..398871a39a18db --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/form_error_text.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const KuiFormErrorText = ({ children, className, ...rest }) => { + const classes = classNames('kuiFormErrorText', className); + + return ( +
+ {children} +
+ ); +}; + +KuiFormErrorText.propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; diff --git a/ui_framework/src/components/form/form_error_text/form_error_text.test.js b/ui_framework/src/components/form/form_error_text/form_error_text.test.js new file mode 100644 index 00000000000000..815dacbf03d4be --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/form_error_text.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFormErrorText } from './form_error_text'; + +describe('KuiFormErrorText', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/form_error_text/index.js b/ui_framework/src/components/form/form_error_text/index.js new file mode 100644 index 00000000000000..24ad9d6bf7ac72 --- /dev/null +++ b/ui_framework/src/components/form/form_error_text/index.js @@ -0,0 +1,3 @@ +export { + KuiFormErrorText, +} from './form_error_text'; diff --git a/ui_framework/src/components/form/form_help_text/__snapshots__/form_help_text.test.js.snap b/ui_framework/src/components/form/form_help_text/__snapshots__/form_help_text.test.js.snap new file mode 100644 index 00000000000000..908244c5db00a8 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/__snapshots__/form_help_text.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFormHelpText is rendered 1`] = ` +
+`; diff --git a/ui_framework/src/components/form/form_help_text/_form_help_text.scss b/ui_framework/src/components/form/form_help_text/_form_help_text.scss new file mode 100644 index 00000000000000..ceb1b99a49dac9 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/_form_help_text.scss @@ -0,0 +1,5 @@ +.kuiFormHelpText { + font-size: $kuiFontSizeXS; + padding: $kuiSizeS 0; + color: $kuiColorDarkShade; +} diff --git a/ui_framework/src/components/form/form_help_text/_index.scss b/ui_framework/src/components/form/form_help_text/_index.scss new file mode 100644 index 00000000000000..ceb9997afcf386 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/_index.scss @@ -0,0 +1 @@ +@import 'form_help_text'; diff --git a/ui_framework/src/components/form/form_help_text/form_help_text.js b/ui_framework/src/components/form/form_help_text/form_help_text.js new file mode 100644 index 00000000000000..a2c153ea3a5921 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/form_help_text.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const KuiFormHelpText = ({ children, className, ...rest }) => { + const classes = classNames('kuiFormHelpText', className); + + return ( +
+ {children} +
+ ); +}; + +KuiFormHelpText.propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; diff --git a/ui_framework/src/components/form/form_help_text/form_help_text.test.js b/ui_framework/src/components/form/form_help_text/form_help_text.test.js new file mode 100644 index 00000000000000..7150ccfbdc8342 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/form_help_text.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFormHelpText } from './form_help_text'; + +describe('KuiFormHelpText', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/form_help_text/index.js b/ui_framework/src/components/form/form_help_text/index.js new file mode 100644 index 00000000000000..0d9afdf5a83d78 --- /dev/null +++ b/ui_framework/src/components/form/form_help_text/index.js @@ -0,0 +1,3 @@ +export { + KuiFormHelpText, +} from './form_help_text'; diff --git a/ui_framework/src/components/form/form_label/__snapshots__/form_label.test.js.snap b/ui_framework/src/components/form/form_label/__snapshots__/form_label.test.js.snap new file mode 100644 index 00000000000000..5bb84cc6407efe --- /dev/null +++ b/ui_framework/src/components/form/form_label/__snapshots__/form_label.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiFormLabel is rendered 1`] = ` +