diff --git a/ui_framework/dist/ui_framework.css b/ui_framework/dist/ui_framework.css index aae083006bf4b..3b1d18e63275b 100644 --- a/ui_framework/dist/ui_framework.css +++ b/ui_framework/dist/ui_framework.css @@ -491,8 +491,8 @@ table { transform: translateY(2px); } } .kuiCallOut { - padding: 12px 24px; - border-left: 4px solid transparent; } + padding: 16px; + border-left: 2px solid transparent; } .kuiCallOut--info { border-color: #0079a5; @@ -528,13 +528,13 @@ table { /** * 1. Align icon with first line of title text if it wraps. - * 2. Apply margin to all but last item in the flex. + * 2. If content exists under the header, space it appropriately. + * 3. Apply margin to all but last item in the flex. */ .kuiCallOutHeader { font-size: 16px; font-size: 1rem; line-height: 24px; - margin-bottom: 9px; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -544,9 +544,12 @@ table { -ms-flex-align: baseline; align-items: baseline; /* 1 */ } + .kuiCallOutHeader + * { + margin-top: 8px; + /* 1 */ } .kuiCallOutHeader > * + * { margin-left: 8px; - /* 2 */ } + /* 3 */ } /** * 1. Vertically center icon with first line of title. @@ -560,6 +563,389 @@ 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; } + +.kuiCheckbox { + position: relative; + height: 24px; + margin-top: 8px; } + .kuiCheckbox .kuiCheckbox__label { + position: absolute; + padding-left: 32px; + line-height: 24px; + display: block; + font-weight: 400; + z-index: 2; + font-size: 14px; + cursor: pointer; } + .kuiCheckbox .kuiCheckbox__square { + position: absolute; + height: 24px; + width: 24px; + border-radius: 4px; + border: 1px solid #D9D9D9; + background: #fbfbfb; + z-index: 0; } + .kuiCheckbox .kuiCheckbox__check { + height: 100%; + width: 100%; } + .kuiCheckbox .kuiCheckbox__input { + position: absolute; + opacity: 0; } + .kuiCheckbox .kuiCheckbox__input:checked ~ .kuiCheckbox__square .kuiCheckbox__check { + background-color: #3F3F3F; + -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; } + .kuiCheckbox .kuiCheckbox__input:focus ~ .kuiCheckbox__square { + background-color: #e6f2f6; + border-color: #0079a5; } + .kuiCheckbox .kuiCheckbox__input:checked:focus ~ .kuiCheckbox__square .kuiCheckbox__check { + background-color: #3F3F3F; + -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; } + +.kuiFieldNumber { + 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:focus { + 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; } + +.kuiFieldPassword { + 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; } + .kuiFieldPassword:focus { + 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 { + 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; } + .kuiFieldSearch:focus { + 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 { + 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:focus { + 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; } + +.kuiRange { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + margin: 8px 0; + width: 100%; } + .kuiRange:focus::-webkit-slider-thumb { + border: 2px solid #0079a5; } + .kuiRange:focus::-moz-range-thumb { + border: 2px solid #0079a5; } + .kuiRange:focus::-ms-thumb { + border: 2px solid #0079a5; } + .kuiRange:focus::-webkit-slider-runnable-track { + background-color: #0079a5; } + .kuiRange::-webkit-slider-runnable-track { + cursor: pointer; + height: 2px; + transition: all 250ms ease-in; + width: 100%; + background: #D9D9D9; + border: 0 solid #D9D9D9; + border-radius: 4px; } + .kuiRange::-webkit-slider-thumb { + background: #FFF; + border: 2px solid #3F3F3F; + border-radius: 50%; + cursor: pointer; + height: 16px; + width: 16px; + -webkit-appearance: none; + margin-top: -7px; } + .kuiRange::-moz-range-track { + cursor: pointer; + height: 2px; + transition: all 250ms ease-in; + width: 100%; + background: #D9D9D9; + border: 0 solid #D9D9D9; + border-radius: 4px; } + .kuiRange::-moz-range-thumb { + background: #FFF; + border: 2px solid #3F3F3F; + border-radius: 50%; + cursor: pointer; + height: 16px; + width: 16px; } + .kuiRange::-ms-track { + cursor: pointer; + height: 2px; + transition: all 250ms ease-in; + width: 100%; + background: transparent; + border-color: transparent; + border-width: 8px 0; + color: transparent; } + .kuiRange::-ms-fill-lower { + background: #D9D9D9; + border: 0 solid #D9D9D9; + border-radius: 8px; } + .kuiRange::-ms-fill-upper { + background: #D9D9D9; + border: 0 solid #D9D9D9; + border-radius: 8px; } + .kuiRange::-ms-thumb { + background: #FFF; + border: 2px solid #3F3F3F; + border-radius: 50%; + cursor: pointer; + height: 16px; + width: 16px; + margin-top: 0; } + +.kuiSelect { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + 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; } + .kuiSelect:focus { + 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 { + display: none; } + +.kuiSwitch { + 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. + */ + /** + * 1. Accounts for the border on the body. + */ + /** + * 1. Mask is used to color the svg. Text color is used so works regardless of theme. + */ + /** + * The thumb is slightly scaled when in use. + */ + /** + * 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; + line-height: 24px; + font-size: 14px; + cursor: pointer; } + .kuiSwitch .kuiSwitch__input { + position: absolute; + opacity: 0; + z-index: -1; } + .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 { + width: 52px; + height: 24px; + background: #fbfbfb; + border: 1px solid #D9D9D9; + display: inline-block; + position: relative; + border-radius: 24px; + vertical-align: middle; } + .kuiSwitch .kuiSwitch__thumb { + position: absolute; + width: 24px; + height: 24px; + display: inline-block; + background-color: #FFF; + left: 28px; + top: -1px; + border-radius: 50%; + border: 1px solid #D9D9D9; + transition: border-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), background-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), left 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); + transition: border-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), background-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), left 250ms cubic-bezier(0.34, 1.61, 0.7, 1), transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); + transition: border-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), background-color 250ms cubic-bezier(0.34, 1.61, 0.7, 1), left 250ms cubic-bezier(0.34, 1.61, 0.7, 1), transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); + z-index: 1; } + .kuiSwitch .kuiSwitch__track { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + border-radius: 24px; } + .kuiSwitch .kuiSwitch__icon { + position: absolute; + right: -40px; + top: 0; + bottom: 0; + width: 48px; + transition: left 250ms cubic-bezier(0.34, 1.61, 0.7, 1), right 250ms cubic-bezier(0.34, 1.61, 0.7, 1); + -webkit-mask: url("../src/components/icon/assets/cross.svg") center center no-repeat; + mask: url("../src/components/icon/assets/cross.svg") center center no-repeat; + background-color: #3F3F3F; } + .kuiSwitch .kuiSwitch__icon--checked { + right: auto; + left: -8px; + background-color: #3F3F3F; + -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; } + .kuiSwitch:hover .kuiSwitch__thumb { + -webkit-transform: scale(1.05); + transform: scale(1.05); } + .kuiSwitch:active .kuiSwitch__thumb { + -webkit-transform: scale(0.95); + transform: scale(0.95); } + .kuiSwitch .kuiSwitch__input:not(:checked) ~ .kuiSwitch__body .kuiSwitch__thumb { + left: -1px; } + .kuiSwitch .kuiSwitch__input:not(:checked) ~ .kuiSwitch__body .kuiSwitch__icon { + right: -8px; } + .kuiSwitch .kuiSwitch__input:not(:checked) ~ .kuiSwitch__body .kuiSwitch__icon.kuiSwitch__icon--checked { + right: auto; + left: -40px; } + +.kuiTextArea { + 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:focus { + 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; } + .kuiHeader { box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1); display: -webkit-box; @@ -754,7 +1140,8 @@ table { .kuiIcon { display: inline-block; - vertical-align: middle; } + vertical-align: middle; + fill: #3F3F3F; } .kuiIcon:focus { opacity: 1; background: #e6f2f6; } @@ -1363,7 +1750,7 @@ table { .kuiPopover.kuiPopover-isOpen .kuiPopover__body { opacity: 1; visibility: visible; - z-index: 0; + z-index: 2000; margin-top: 8px; } .kuiPopover__body { diff --git a/ui_framework/doc_site/src/components/guide_components.scss b/ui_framework/doc_site/src/components/guide_components.scss index 450e357978fca..c2e0d78e6109f 100644 --- a/ui_framework/doc_site/src/components/guide_components.scss +++ b/ui_framework/doc_site/src/components/guide_components.scss @@ -10,7 +10,7 @@ $guideChromeTransition: 0.3s ease; // Colors $guideBaseBackgroundColor: #f7f7f7; -$guidePanelBackgroundColor: #ffffff; +$guidePanelBackgroundColor: $kuiColorEmptyShade; $guideTextColor: #444; $guideLinkColor: #00a9e5; $guideLinkHoverColor: #00a9e5; diff --git a/ui_framework/doc_site/src/components/guide_page/_guide_page.scss b/ui_framework/doc_site/src/components/guide_page/_guide_page.scss index 3c3249a940109..e18ce8f986031 100644 --- a/ui_framework/doc_site/src/components/guide_page/_guide_page.scss +++ b/ui_framework/doc_site/src/components/guide_page/_guide_page.scss @@ -11,7 +11,7 @@ $scrollBarWidth: 20px; background-color: $guidePanelBackgroundColor; - border: 1px solid #CCCCCC; + border: 1px solid $kuiColorLightShade; border-radius: 4px; flex: 1 1 auto; padding: 40px 60px; diff --git a/ui_framework/doc_site/src/main.scss b/ui_framework/doc_site/src/main.scss index f1707487acfbf..7472366bdd57d 100644 --- a/ui_framework/doc_site/src/main.scss +++ b/ui_framework/doc_site/src/main.scss @@ -1,2 +1,3 @@ @import "../../dist/ui_framework.css"; +@import "../../src/global_styling/variables/_index.scss"; @import "./components/guide_components"; diff --git a/ui_framework/doc_site/src/services/routes/routes.js b/ui_framework/doc_site/src/services/routes/routes.js index 2e701b73103c8..d257c1f77ddd9 100644 --- a/ui_framework/doc_site/src/services/routes/routes.js +++ b/ui_framework/doc_site/src/services/routes/routes.js @@ -9,6 +9,9 @@ import ButtonExample import CallOutExample from '../../views/call_out/call_out_example'; +import FormExample + from '../../views/form/form_example'; + import IconExample from '../../views/icon/icon_example'; @@ -64,6 +67,10 @@ const components = [{ name: 'CallOut', component: CallOutExample, hasReact: true, +}, { + name: 'Form', + component: FormExample, + hasReact: true, }, { name: 'Header', component: HeaderExample, diff --git a/ui_framework/doc_site/src/views/form/field_text.js b/ui_framework/doc_site/src/views/form/field_text.js new file mode 100644 index 0000000000000..19b8bc4c1f5ce --- /dev/null +++ b/ui_framework/doc_site/src/views/form/field_text.js @@ -0,0 +1,65 @@ +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_everything.js b/ui_framework/doc_site/src/views/form/form_everything.js new file mode 100644 index 0000000000000..7170b429cce32 --- /dev/null +++ b/ui_framework/doc_site/src/views/form/form_everything.js @@ -0,0 +1,154 @@ +import React, { + Component, +} from 'react'; + +import { + KuiForm, + KuiCheckbox, + KuiFieldNumber, + KuiFieldPassword, + KuiRange, + KuiFormRow, + KuiFieldSearch, + 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 { + + 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' }, + ]; + + + const formSample = ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + return ( +
+ {formSample} +
+ ); + } +} diff --git a/ui_framework/doc_site/src/views/form/form_example.js b/ui_framework/doc_site/src/views/form/form_example.js new file mode 100644 index 0000000000000..7cec41f68425e --- /dev/null +++ b/ui_framework/doc_site/src/views/form/form_example.js @@ -0,0 +1,112 @@ +import React from 'react'; + +import { renderToHtml } from '../../services'; + +import { + GuideDemo, + GuideCode, + GuidePage, + GuideSection, + GuideSectionTypes, + GuideText, +} from '../../components'; + +import FieldText from './field_text'; +const fieldTextSource = require('!!raw!./field_text'); +const fieldTextHtml = renderToHtml(FieldText); + +import FormEverything from './form_everything'; +const formEverythingSource = require('!!raw!./form_everything'); +const formEverythingHtml = renderToHtml(FormEverything); + +import FormValidation from './form_validation'; +const formValidationSource = require('!!raw!./form_validation'); +const formValidationHtml = renderToHtml(FormValidation); + +import FormPopover from './form_popover'; +const formPopoverSource = require('!!raw!./form_popover'); +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. + + + + + + + + + Forms can be placed within popovers and should scale accordingly. + + + + + + + + + Validation is achieved by applying invalid 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 new file mode 100644 index 0000000000000..0002fd22d0044 --- /dev/null +++ b/ui_framework/doc_site/src/views/form/form_popover.js @@ -0,0 +1,80 @@ +import React, { + Component, +} from 'react'; + +import { + KuiButton, + KuiPopover, + KuiForm, + KuiRange, + KuiFormRow, + KuiSwitch, + KuiFieldText, +} from '../../../../components'; + +function makeId() { + return Math.random().toString(36).substr(2, 5); +} + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isPopoverOpen: false, + }; + } + + onButtonClick() { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen, + }); + } + + closePopover() { + this.setState({ + isPopoverOpen: false, + }); + } + + render() { + const button = ( + + Form in a popover + + ); + + const formSample = ( + + + + + + + + + + + + ); + + 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 new file mode 100644 index 0000000000000..b29e3d75bdece --- /dev/null +++ b/ui_framework/doc_site/src/views/form/form_validation.js @@ -0,0 +1,84 @@ +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/kibana/kibana.js b/ui_framework/doc_site/src/views/kibana/kibana.js index 7b7c30b1a79ba..ac372ae5b6c8c 100644 --- a/ui_framework/doc_site/src/views/kibana/kibana.js +++ b/ui_framework/doc_site/src/views/kibana/kibana.js @@ -408,9 +408,7 @@ export default class extends Component { {/* Kibana section */} - + Kibana @@ -434,9 +432,7 @@ export default class extends Component { {/* Logstash section */} - + Logstash diff --git a/ui_framework/src/components/call_out/_call_out.scss b/ui_framework/src/components/call_out/_call_out.scss index 5ac6f6740ad80..8ce78f71b0db0 100644 --- a/ui_framework/src/components/call_out/_call_out.scss +++ b/ui_framework/src/components/call_out/_call_out.scss @@ -1,6 +1,6 @@ .kuiCallOut { - padding: $kuiSizeM $kuiSizeL; - border-left: 4px solid transparent; + padding: $kuiSize; + border-left: $kuiSizeXS / 2 solid transparent; } // Modifier naming and colors. @@ -31,17 +31,20 @@ $callOutTypes: ( /** * 1. Align icon with first line of title text if it wraps. - * 2. Apply margin to all but last item in the flex. + * 2. If content exists under the header, space it appropriately. + * 3. Apply margin to all but last item in the flex. */ .kuiCallOutHeader { @include kuiFontSizeM; - - margin-bottom: $kuiVerticalRhythmS; display: flex; align-items: baseline; /* 1 */ + + * { + margin-top: $kuiSizeS; /* 1 */ + } + > * + * { - margin-left: $kuiSizeS; /* 2 */ + margin-left: $kuiSizeS; /* 3 */ } } diff --git a/ui_framework/src/components/form/_form.scss b/ui_framework/src/components/form/_form.scss new file mode 100644 index 0000000000000..e9b56f5e0a4dc --- /dev/null +++ b/ui_framework/src/components/form/_form.scss @@ -0,0 +1,9 @@ +.kuiForm__error { + @include kuiFontSizeS; + list-style: disc; + margin-left: $kuiSizeXL; +} + +.kuiForm__errors { + margin-bottom: $kuiSize; +} diff --git a/ui_framework/src/components/form/_index.scss b/ui_framework/src/components/form/_index.scss new file mode 100644 index 0000000000000..0e5c3264c6a27 --- /dev/null +++ b/ui_framework/src/components/form/_index.scss @@ -0,0 +1,33 @@ +$kuiFormMaxWidth: 400px; +$kuiFormBackgroundColor: tintOrShade($kuiColorLightestShade, 60%, 25%); + +@mixin kuiFieldStyle { + 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; + transition: box-shadow $kuiAnimSpeedNormal ease-in, background $kuiAnimSpeedNormal ease-in; + max-width: $kuiFormMaxWidth; + border-radius: 0; + + &:focus { + 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; + } +} + +@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 'range/index'; +@import 'select/index'; +@import 'switch/index'; +@import 'text_area/index'; diff --git a/ui_framework/src/components/form/checkbox/_checkbox.scss b/ui_framework/src/components/form/checkbox/_checkbox.scss new file mode 100644 index 0000000000000..171c9a1e011a8 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/_checkbox.scss @@ -0,0 +1,51 @@ +.kuiCheckbox { + position: relative; + height: $kuiSizeL; + margin-top: $kuiSizeS; + + .kuiCheckbox__label { + position: absolute; + padding-left: $kuiSizeXL; + line-height: $kuiSizeL; + display: block; + font-weight: $kuiFontWeightRegular; + z-index: 2; + font-size: $kuiFontSizeS; + cursor: pointer; + } + + .kuiCheckbox__square { + position: absolute; + height: $kuiSizeL; + width: $kuiSizeL; + border-radius: $kuiBorderRadius; + border: $kuiBorderThin; + background: $kuiFormBackgroundColor; + z-index: 0; + } + + .kuiCheckbox__check { + height: 100%; + width: 100%; + } + + .kuiCheckbox__input { + position: absolute; + opacity: 0; + + &:checked ~ .kuiCheckbox__square .kuiCheckbox__check { + background-color: $kuiTextColor; + mask: url('../src/components/icon/assets/check.svg') center center no-repeat; + } + + &:focus ~ .kuiCheckbox__square { + background-color: $kuiFocusBackgroundColor; + border-color: $kuiColorPrimary; + } + + &:checked:focus ~ .kuiCheckbox__square .kuiCheckbox__check { + background-color: $kuiTextColor; + mask: url('../src/components/icon/assets/check.svg') center center no-repeat; + } + } +} diff --git a/ui_framework/src/components/form/checkbox/_index.scss b/ui_framework/src/components/form/checkbox/_index.scss new file mode 100644 index 0000000000000..e6e53eb712869 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/_index.scss @@ -0,0 +1 @@ +@import 'checkbox'; diff --git a/ui_framework/src/components/form/checkbox/checkbox.js b/ui_framework/src/components/form/checkbox/checkbox.js new file mode 100644 index 0000000000000..55490f3161149 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/checkbox.js @@ -0,0 +1,29 @@ +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.test.js b/ui_framework/src/components/form/checkbox/checkbox.test.js new file mode 100644 index 0000000000000..74479a5a2c408 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/checkbox.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiCheckbox } from './checkbox'; + +describe('KuiCheckbox', () => { + 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 new file mode 100644 index 0000000000000..4551ccb9b4155 --- /dev/null +++ b/ui_framework/src/components/form/checkbox/index.js @@ -0,0 +1 @@ +export { KuiCheckbox } from './checkbox'; diff --git a/ui_framework/src/components/form/field_number/_field_number.scss b/ui_framework/src/components/form/field_number/_field_number.scss new file mode 100644 index 0000000000000..4e7c4bcc0fd65 --- /dev/null +++ b/ui_framework/src/components/form/field_number/_field_number.scss @@ -0,0 +1,3 @@ +.kuiFieldNumber { + @include kuiFieldStyle; +} diff --git a/ui_framework/src/components/form/field_number/_index.scss b/ui_framework/src/components/form/field_number/_index.scss new file mode 100644 index 0000000000000..497eee52289d4 --- /dev/null +++ b/ui_framework/src/components/form/field_number/_index.scss @@ -0,0 +1 @@ +@import 'field_number'; diff --git a/ui_framework/src/components/form/field_number/field_number.js b/ui_framework/src/components/form/field_number/field_number.js new file mode 100644 index 0000000000000..e9affb862ebd1 --- /dev/null +++ b/ui_framework/src/components/form/field_number/field_number.js @@ -0,0 +1,36 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiFieldNumber = ({ className, id, placeholder, name, min, max, value, ...rest }) => { + const classes = classNames('kuiFieldNumber', className); + + return ( + + ); +}; + +KuiFieldNumber.propTypes = { + id: PropTypes.string, + name: PropTypes.string.isRequired, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + value: PropTypes.number, +}; + +KuiFieldNumber.defaultProps = { + value: undefined, +}; + diff --git a/ui_framework/src/components/form/field_number/field_number.test.js b/ui_framework/src/components/form/field_number/field_number.test.js new file mode 100644 index 0000000000000..2fb206e545c59 --- /dev/null +++ b/ui_framework/src/components/form/field_number/field_number.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFieldNumber } from './field_number'; + +describe('KuiFieldNumber', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/field_number/index.js b/ui_framework/src/components/form/field_number/index.js new file mode 100644 index 0000000000000..5ae02c1c69034 --- /dev/null +++ b/ui_framework/src/components/form/field_number/index.js @@ -0,0 +1 @@ +export { KuiFieldNumber } from './field_number'; diff --git a/ui_framework/src/components/form/field_password/_field_password.scss b/ui_framework/src/components/form/field_password/_field_password.scss new file mode 100644 index 0000000000000..6b511ea2298fb --- /dev/null +++ b/ui_framework/src/components/form/field_password/_field_password.scss @@ -0,0 +1,3 @@ +.kuiFieldPassword { + @include kuiFieldStyle; +} diff --git a/ui_framework/src/components/form/field_password/_index.scss b/ui_framework/src/components/form/field_password/_index.scss new file mode 100644 index 0000000000000..aab5cd9d3b3a2 --- /dev/null +++ b/ui_framework/src/components/form/field_password/_index.scss @@ -0,0 +1 @@ +@import 'field_password'; diff --git a/ui_framework/src/components/form/field_password/field_password.js b/ui_framework/src/components/form/field_password/field_password.js new file mode 100644 index 0000000000000..a6bcadd17894a --- /dev/null +++ b/ui_framework/src/components/form/field_password/field_password.js @@ -0,0 +1,31 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiFieldPassword = ({ className, id, name, placeholder, value, ...rest }) => { + const classes = classNames('kuiFieldPassword', className); + + return ( + + ); +}; + +KuiFieldPassword.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + placeholder: PropTypes.string, + value: PropTypes.string, +}; + +KuiFieldPassword.defaultProps = { + value: undefined, +}; diff --git a/ui_framework/src/components/form/field_password/field_password.test.js b/ui_framework/src/components/form/field_password/field_password.test.js new file mode 100644 index 0000000000000..a70c135388164 --- /dev/null +++ b/ui_framework/src/components/form/field_password/field_password.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFieldPassword } from './field_password'; + +describe('KuiFieldPassword', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/field_password/index.js b/ui_framework/src/components/form/field_password/index.js new file mode 100644 index 0000000000000..1596934c1917a --- /dev/null +++ b/ui_framework/src/components/form/field_password/index.js @@ -0,0 +1 @@ +export { KuiFieldPassword } from './field_password'; diff --git a/ui_framework/src/components/form/field_search/_field_search.scss b/ui_framework/src/components/form/field_search/_field_search.scss new file mode 100644 index 0000000000000..15530ebedc5c6 --- /dev/null +++ b/ui_framework/src/components/form/field_search/_field_search.scss @@ -0,0 +1,4 @@ +.kuiFieldSearch { + @include kuiFieldStyle; +} + diff --git a/ui_framework/src/components/form/field_search/_index.scss b/ui_framework/src/components/form/field_search/_index.scss new file mode 100644 index 0000000000000..b9652e5dde154 --- /dev/null +++ b/ui_framework/src/components/form/field_search/_index.scss @@ -0,0 +1 @@ +@import 'field_search'; diff --git a/ui_framework/src/components/form/field_search/field_search.js b/ui_framework/src/components/form/field_search/field_search.js new file mode 100644 index 0000000000000..0253d6c5fd630 --- /dev/null +++ b/ui_framework/src/components/form/field_search/field_search.js @@ -0,0 +1,32 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + + +export const KuiFieldSearch = ({ className, id, name, placeholder, value, ...rest }) => { + const classes = classNames('kuiFieldSearch', className); + + return ( + + ); +}; + +KuiFieldSearch.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + placeholder: PropTypes.string, + value: PropTypes.string, +}; + +KuiFieldSearch.defaultProps = { + value: undefined, +}; diff --git a/ui_framework/src/components/form/field_search/field_search.test.js b/ui_framework/src/components/form/field_search/field_search.test.js new file mode 100644 index 0000000000000..bf703e790687d --- /dev/null +++ b/ui_framework/src/components/form/field_search/field_search.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFieldSearch } from './field_search'; + +describe('KuiFieldSearch', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/field_search/index.js b/ui_framework/src/components/form/field_search/index.js new file mode 100644 index 0000000000000..d91a57353e651 --- /dev/null +++ b/ui_framework/src/components/form/field_search/index.js @@ -0,0 +1 @@ +export { KuiFieldSearch } from './field_search'; diff --git a/ui_framework/src/components/form/field_text/_field_text.scss b/ui_framework/src/components/form/field_text/_field_text.scss new file mode 100644 index 0000000000000..4b8afbedfb849 --- /dev/null +++ b/ui_framework/src/components/form/field_text/_field_text.scss @@ -0,0 +1,3 @@ +.kuiFieldText { + @include kuiFieldStyle; +} diff --git a/ui_framework/src/components/form/field_text/_index.scss b/ui_framework/src/components/form/field_text/_index.scss new file mode 100644 index 0000000000000..937e44ac8d6e6 --- /dev/null +++ b/ui_framework/src/components/form/field_text/_index.scss @@ -0,0 +1 @@ +@import 'field_text'; diff --git a/ui_framework/src/components/form/field_text/field_text.js b/ui_framework/src/components/form/field_text/field_text.js new file mode 100644 index 0000000000000..f04889921bb78 --- /dev/null +++ b/ui_framework/src/components/form/field_text/field_text.js @@ -0,0 +1,31 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiFieldText = ({ id, name, placeholder, value, className, ...rest }) => { + const classes = classNames('kuiFieldText', className); + + return ( + + ); +}; + +KuiFieldText.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + placeholder: PropTypes.string, + value: PropTypes.string, +}; + +KuiFieldText.defaultProps = { + value: undefined, +}; diff --git a/ui_framework/src/components/form/field_text/field_text.test.js b/ui_framework/src/components/form/field_text/field_text.test.js new file mode 100644 index 0000000000000..f6061c3ec1c3c --- /dev/null +++ b/ui_framework/src/components/form/field_text/field_text.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFieldText } from './field_text'; + +describe('KuiFieldText', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/field_text/index.js b/ui_framework/src/components/form/field_text/index.js new file mode 100644 index 0000000000000..ff6960d5f2af6 --- /dev/null +++ b/ui_framework/src/components/form/field_text/index.js @@ -0,0 +1 @@ +export { KuiFieldText } from './field_text'; diff --git a/ui_framework/src/components/form/form.js b/ui_framework/src/components/form/form.js new file mode 100644 index 0000000000000..a9b6907e1c2c5 --- /dev/null +++ b/ui_framework/src/components/form/form.js @@ -0,0 +1,46 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; +import { KuiCallOut } from '../../../components'; + +export const KuiForm = ({ children, className, invalid, errors, ...rest }) => { + const classes = classNames('kuiForm', className); + + + + let optionalErrors = null; + if (errors) { + optionalErrors = ( +
    + {errors.map(function (error, index) { + return
  • {error}
  • ; + })} +
+ ); + } + + let optionalErrorAlert = null; + if (invalid) { + optionalErrorAlert = ( + + {optionalErrors} + + ); + } + + return ( +
+ {optionalErrorAlert} + {children} +
+ ); +}; + +KuiForm.propTypes = { + invalid: PropTypes.bool, + errors: PropTypes.array, +}; diff --git a/ui_framework/src/components/form/form.test.js b/ui_framework/src/components/form/form.test.js new file mode 100644 index 0000000000000..8db361d646f93 --- /dev/null +++ b/ui_framework/src/components/form/form.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { KuiForm } from './form'; + +describe('KuiForm', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/form_row/_form_row.scss b/ui_framework/src/components/form/form_row/_form_row.scss new file mode 100644 index 0000000000000..35834f9952e79 --- /dev/null +++ b/ui_framework/src/components/form/form_row/_form_row.scss @@ -0,0 +1,80 @@ +.kuiFormRow { + display: flex; + flex-direction: column; + position: relative; + max-width: $kuiFormMaxWidth; + + + * { + margin-top: $kuiSizeL; + } + + &.kuiFormRow--withIcon input { + padding-left: $kuiSizeXXL; + } + + &.kuiFormRow--dropdown input { + padding-left: $kuiSizeM; + padding-right: $kuiSizeXXL; + } + + &.kuiFormRow--select .kuiFormRow__icon { + left: auto; + right: $kuiSizeM; + } + + .kuiFormRow__label { + font-size: $kuiFontSizeXS; + padding-bottom: $kuiSizeS; + cursor: pointer; + transition: all $kuiAnimSpeedFast $kuiAnimSlightResistance; + font-weight: $kuiFontWeightMedium; + order: -1; // Force the label to be first, so we can use sibling:focus selectors. + } + + .kuiFormRow__helpText { + font-size: $kuiFontSizeXS; + padding: $kuiSizeS 0; + color: $kuiColorDarkShade; + } + + .kuiFormRow__error { + font-size: $kuiFontSizeXS; + padding: $kuiSizeS 0; + color: $kuiColorDanger; + } + + .kuiFormRow__error + * { + padding-top: 0; + } + + .kuiFormRow__icon { + position: absolute; + top: $kuiSizeXL; + left: $kuiSizeM; + } + + // Naked selectors on purpose to touch inner components. + input:focus + label, + select:focus + label, + textarea:focus + label, { + color: $kuiColorPrimary; + } + + &.kuiFormRow--invalid { + + .kuiFormRow__label { + color: $kuiColorDanger !important; + } + + // Naked selectors on purpose to touch inner components. + input[type="text"], + input[type="password"], + input[type="number"], + input[type="search"], + select, + textarea, + { + 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 !important; + } + } +} diff --git a/ui_framework/src/components/form/form_row/_index.scss b/ui_framework/src/components/form/form_row/_index.scss new file mode 100644 index 0000000000000..b95cb0dc91828 --- /dev/null +++ b/ui_framework/src/components/form/form_row/_index.scss @@ -0,0 +1 @@ +@import 'form_row'; diff --git a/ui_framework/src/components/form/form_row/form_row.js b/ui_framework/src/components/form/form_row/form_row.js new file mode 100644 index 0000000000000..ca73ed5e93cb3 --- /dev/null +++ b/ui_framework/src/components/form/form_row/form_row.js @@ -0,0 +1,82 @@ +import React, { + cloneElement, + PropTypes, +} from 'react'; +import classNames from 'classnames'; +import { KuiIcon } from '../../../components'; + +export const KuiFormRow = ({ children, icon, containsSelect, helpText, invalid, errors, label, id, className, ...rest }) => { + const classes = classNames( + 'kuiFormRow', + className, + { + 'kuiFormRow--withIcon' : icon, + 'kuiFormRow--invalid' : invalid, + 'kuiFormRow--select' : containsSelect, + } + ); + + let field; + let optionalIcon = null; + let optionalHelpText = null; + let optionalErrors = null; + let optionalLabel = null; + + if (icon) { + optionalIcon = ; + } + + if (helpText) { + optionalHelpText =
{helpText}
; + } + + if (errors) { + optionalErrors = ( + errors.map(function (error, index) { + return
{error}
; + }) + ); + } + + if (label) { + optionalLabel = ; + } + + if (id) { + field = cloneElement(children, { + id, + }); + } else { + field = children; + } + + return ( +
+ {/* + Order is important here. The label needs to be UNDER the field. + We rearrange the flex order in the CSS so the label ends up + displaying above the children / input. This allows us to still + use sibling selectors against the label that are tiggered by the + focus state of the input. + */} + {field} + {optionalLabel} + {optionalErrors} + {optionalHelpText} + {optionalIcon} +
+ ); +}; + +KuiFormRow.propTypes = { + label: PropTypes.string, + id: PropTypes.string, + icon: PropTypes.string, + invalid: PropTypes.bool, + containsSelect: PropTypes.bool, + errors: PropTypes.array, + helpText: PropTypes.string, +}; diff --git a/ui_framework/src/components/form/form_row/form_row.test.js b/ui_framework/src/components/form/form_row/form_row.test.js new file mode 100644 index 0000000000000..9f1d85f2586a6 --- /dev/null +++ b/ui_framework/src/components/form/form_row/form_row.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiFormRow } from './form_row'; + +describe('KuiFormRow', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/form_row/index.js b/ui_framework/src/components/form/form_row/index.js new file mode 100644 index 0000000000000..29d7fb780579d --- /dev/null +++ b/ui_framework/src/components/form/form_row/index.js @@ -0,0 +1 @@ +export { KuiFormRow } from './form_row'; diff --git a/ui_framework/src/components/form/index.js b/ui_framework/src/components/form/index.js new file mode 100644 index 0000000000000..5ea96bd92a7a0 --- /dev/null +++ b/ui_framework/src/components/form/index.js @@ -0,0 +1,11 @@ +export { KuiForm } from './form'; +export { KuiCheckbox } from './checkbox'; +export { KuiFieldNumber } from './field_number'; +export { KuiFieldPassword } from './field_password'; +export { KuiRange } from './range'; +export { KuiFormRow } from './form_row'; +export { KuiFieldSearch } from './field_search'; +export { KuiSelect } from './select'; +export { KuiSwitch } from './switch'; +export { KuiFieldText } from './field_text'; +export { KuiTextArea } from './text_area'; diff --git a/ui_framework/src/components/form/range/_index.scss b/ui_framework/src/components/form/range/_index.scss new file mode 100644 index 0000000000000..3dabcb56381af --- /dev/null +++ b/ui_framework/src/components/form/range/_index.scss @@ -0,0 +1,32 @@ +$kuiRangeTrackColor: $kuiColorLightShade; +$kuiRangeThumbColor: $kuiColorEmptyShade; + +$kuiRangeThumbRadius: 50%; +$kuiRangeThumbHeight: $kuiSize; +$kuiRangeThumbWidth: $kuiSize; +$kuiRangeBorderWidth: 2px; +$kuiRangeBorderColor: $kuiTextColor; + +$kuiRangeTrackWidth: 100%; +$kuiRangeTrackHeight: 2px; +$kuiRangeTrackBorderWidth: 0; +$kuiRangeTrackBorderColor: $kuiColorLightShade; +$kuiRangeTrackRadius: $kuiBorderRadius; + +@mixin track { + cursor: pointer; + height: $kuiRangeTrackHeight; + transition: all $kuiAnimSpeedNormal ease-in; + width: $kuiRangeTrackWidth; +} + +@mixin thumb { + background: $kuiRangeThumbColor; + border: $kuiRangeBorderWidth solid $kuiRangeBorderColor; + border-radius: $kuiRangeThumbRadius; + cursor: pointer; + height: $kuiRangeThumbHeight; + width: $kuiRangeThumbWidth; +} + +@import 'range'; diff --git a/ui_framework/src/components/form/range/_range.scss b/ui_framework/src/components/form/range/_range.scss new file mode 100644 index 0000000000000..1aca75b7eac3f --- /dev/null +++ b/ui_framework/src/components/form/range/_range.scss @@ -0,0 +1,83 @@ +// The following code is inspired by... + +// Github: https://github.com/darlanrod/input-range-sass +// Author: Darlan Rod https://github.com/darlanrod +// Version 1.4.1 +// MIT License + +// It has been modified to fit the styling patterns of Kibana and +// to be more easily maintained / themeable going forward. + +.kuiRange { + appearance: none; + margin: $kuiRangeThumbHeight / 2 0; + width: $kuiRangeTrackWidth; + + &:focus { + + &::-webkit-slider-thumb { + border: $kuiRangeBorderWidth solid $kuiColorPrimary; + } + + &::-moz-range-thumb { + border: $kuiRangeBorderWidth solid $kuiColorPrimary; + } + + &::-ms-thumb { + border: $kuiRangeBorderWidth solid $kuiColorPrimary; + } + &::-webkit-slider-runnable-track { + background-color: $kuiColorPrimary; + } + + } + + &::-webkit-slider-runnable-track { + @include track; + background: $kuiRangeTrackColor; + border: $kuiRangeTrackBorderWidth solid $kuiRangeTrackBorderColor; + border-radius: $kuiRangeTrackRadius; + } + + &::-webkit-slider-thumb { + @include thumb; + -webkit-appearance: none; + margin-top: ((-$kuiRangeTrackBorderWidth * 2 + $kuiRangeTrackHeight) / 2) - ($kuiRangeThumbHeight / 2); + } + + &::-moz-range-track { + @include track; + background: $kuiRangeTrackColor; + border: $kuiRangeTrackBorderWidth solid $kuiRangeTrackBorderColor; + border-radius: $kuiRangeTrackRadius; + } + + &::-moz-range-thumb { + @include thumb; + } + + &::-ms-track { + @include track; + background: transparent; + border-color: transparent; + border-width: ($kuiRangeThumbHeight / 2) 0; + color: transparent; + } + + &::-ms-fill-lower { + background: $kuiRangeTrackColor; + border: $kuiRangeTrackBorderWidth solid $kuiRangeTrackBorderColor; + border-radius: $kuiRangeTrackRadius * 2; + } + + &::-ms-fill-upper { + background: $kuiRangeTrackColor; + border: $kuiRangeTrackBorderWidth solid $kuiRangeTrackBorderColor; + border-radius: $kuiRangeTrackRadius * 2; + } + + &::-ms-thumb { + @include thumb; + margin-top: 0; + } +} diff --git a/ui_framework/src/components/form/range/index.js b/ui_framework/src/components/form/range/index.js new file mode 100644 index 0000000000000..ffb6d6bf2097e --- /dev/null +++ b/ui_framework/src/components/form/range/index.js @@ -0,0 +1 @@ +export { KuiRange } from './range'; diff --git a/ui_framework/src/components/form/range/range.js b/ui_framework/src/components/form/range/range.js new file mode 100644 index 0000000000000..36b3b514f81f2 --- /dev/null +++ b/ui_framework/src/components/form/range/range.js @@ -0,0 +1,36 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiRange = ({ className, id, name, min, max, value, ...rest }) => { + const classes = classNames('kuiRange', className); + + return ( + + ); +}; + +KuiRange.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + min: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + value: PropTypes.string, +}; + +KuiRange.defaultProps = { + value: undefined, + min: 1, + max: 100, +}; + diff --git a/ui_framework/src/components/form/range/range.test.js b/ui_framework/src/components/form/range/range.test.js new file mode 100644 index 0000000000000..5641e202ad207 --- /dev/null +++ b/ui_framework/src/components/form/range/range.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiRange } from './range'; + +describe('KuiRange', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/select/_index.scss b/ui_framework/src/components/form/select/_index.scss new file mode 100644 index 0000000000000..e16bb999b26fd --- /dev/null +++ b/ui_framework/src/components/form/select/_index.scss @@ -0,0 +1 @@ +@import 'select'; diff --git a/ui_framework/src/components/form/select/_select.scss b/ui_framework/src/components/form/select/_select.scss new file mode 100644 index 0000000000000..50c8ca17fb418 --- /dev/null +++ b/ui_framework/src/components/form/select/_select.scss @@ -0,0 +1,10 @@ +.kuiSelect { + appearance: none; + @include kuiFieldStyle; + + &::-ms-expand { + display: none; + } +} + + diff --git a/ui_framework/src/components/form/select/index.js b/ui_framework/src/components/form/select/index.js new file mode 100644 index 0000000000000..b006942416d67 --- /dev/null +++ b/ui_framework/src/components/form/select/index.js @@ -0,0 +1 @@ +export { KuiSelect } from './select'; diff --git a/ui_framework/src/components/form/select/select.js b/ui_framework/src/components/form/select/select.js new file mode 100644 index 0000000000000..b4ba9e0a87183 --- /dev/null +++ b/ui_framework/src/components/form/select/select.js @@ -0,0 +1,27 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiSelect = ({ className, options, id, name, ...rest }) => { + const classes = classNames('kuiSelect', className); + + return ( + + ); +}; + +KuiSelect.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + options: PropTypes.arrayOf(React.PropTypes.object).isRequired, +}; diff --git a/ui_framework/src/components/form/select/select.test.js b/ui_framework/src/components/form/select/select.test.js new file mode 100644 index 0000000000000..755d95488561c --- /dev/null +++ b/ui_framework/src/components/form/select/select.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiSelect } from './select'; + +describe('KuiSelect', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/switch/_index.scss b/ui_framework/src/components/form/switch/_index.scss new file mode 100644 index 0000000000000..d77dd0ef13e79 --- /dev/null +++ b/ui_framework/src/components/form/switch/_index.scss @@ -0,0 +1,5 @@ +$kuiSwitchHeight: $kuiSizeL; +$kuiSwitchWidth: ($kuiSizeL * 2) + $kuiSizeXS; +$kuiSwitchThumbSize: $kuiSizeL; + +@import 'switch'; diff --git a/ui_framework/src/components/form/switch/_switch.scss b/ui_framework/src/components/form/switch/_switch.scss new file mode 100644 index 0000000000000..c98ad07f972cc --- /dev/null +++ b/ui_framework/src/components/form/switch/_switch.scss @@ -0,0 +1,130 @@ +.kuiSwitch { + position: relative; + display: inline-block; + height: $kuiSwitchHeight; + cursor: pointer; + + /** + * 1. The label is our main clickable area. It sits above the actual switch. + */ + .kuiSwitch__label { + position: absolute; + left: 0; + padding-left: $kuiSwitchWidth + $kuiSizeS; // 1 + z-index: 2; // 1 + line-height: $kuiSwitchHeight; + font-size: $kuiFontSizeS; + cursor: pointer; + } + + /** + * 1. The input is "hidden" but still focusable. + */ + .kuiSwitch__input { + position: absolute; + opacity: 0; // 1 + z-index: -1; // 1 + } + + .kuiSwitch__input:focus + .kuiSwitch__body { + background: $kuiColorEmptyShade; + + .kuiSwitch__thumb { + border-color: $kuiColorPrimary; + background-color: $kuiColorPrimary; + } + } + + .kuiSwitch__body { + width: $kuiSwitchWidth; + height: $kuiSwitchHeight; + background: $kuiFormBackgroundColor; + border: $kuiBorderThin; + display: inline-block; + position: relative; + border-radius: $kuiSwitchHeight; + vertical-align: middle; + } + + /** + * 1. Accounts for the border on the body. + */ + .kuiSwitch__thumb { + position: absolute; + width: $kuiSwitchHeight; + height: $kuiSwitchHeight; + display: inline-block; + background-color: $kuiColorEmptyShade; + left: $kuiSwitchWidth - $kuiSwitchThumbSize; + top: -1px; // 1 + border-radius: 50%; + border: $kuiBorderThin; + transition: border-color $kuiAnimSpeedNormal $kuiAnimSlightBounce, background-color $kuiAnimSpeedNormal $kuiAnimSlightBounce, left $kuiAnimSpeedNormal $kuiAnimSlightBounce, transform $kuiAnimSpeedNormal $kuiAnimSlightBounce; + z-index: 1; + } + + .kuiSwitch__track { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + border-radius: $kuiSwitchHeight; + } + + /** + * 1. Mask is used to color the svg. Text color is used so works regardless of theme. + */ + .kuiSwitch__icon { + position: absolute; + right: -($kuiSwitchWidth - ($kuiSwitchThumbSize / 2)); + top: 0; + bottom: 0; + width: $kuiSwitchWidth - ($kuiSwitchThumbSize / 2) + $kuiSizeS; + transition: left $kuiAnimSpeedNormal $kuiAnimSlightBounce, right $kuiAnimSpeedNormal $kuiAnimSlightBounce; + mask: url('../src/components/icon/assets/cross.svg') center center no-repeat; // 1 + background-color: $kuiTextColor; // 1 + } + + .kuiSwitch__icon--checked { + right: auto; + left: -$kuiSizeS; + background-color: $kuiTextColor; + mask: url('../src/components/icon/assets/check.svg') center center no-repeat; + } + + /** + * The thumb is slightly scaled when in use. + */ + &:hover { + .kuiSwitch__thumb { + transform: scale(1.05); + } + } + + &:active { + .kuiSwitch__thumb { + transform: scale(.95); + } + } + + /** + * When input is not checked, we shift around the positioning of sibling/child selectors. + */ + .kuiSwitch__input:not(:checked) ~ .kuiSwitch__body { + + .kuiSwitch__thumb { + left: -1px; + } + + .kuiSwitch__icon { + right: -$kuiSizeS; + + &.kuiSwitch__icon--checked { + right: auto; + left: -($kuiSwitchWidth - ($kuiSwitchThumbSize / 2)); + } + } + } +} diff --git a/ui_framework/src/components/form/switch/index.js b/ui_framework/src/components/form/switch/index.js new file mode 100644 index 0000000000000..5d52adb1d6c7c --- /dev/null +++ b/ui_framework/src/components/form/switch/index.js @@ -0,0 +1 @@ +export { KuiSwitch } from './switch'; diff --git a/ui_framework/src/components/form/switch/switch.js b/ui_framework/src/components/form/switch/switch.js new file mode 100644 index 0000000000000..255e498764b55 --- /dev/null +++ b/ui_framework/src/components/form/switch/switch.js @@ -0,0 +1,33 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiSwitch = ({ label, id, name, defaultChecked, className, ...rest }) => { + const classes = classNames('kuiSwitch', className); + + return ( +
+ + + + + + + + + +
+ ); +}; + +KuiSwitch.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + label: PropTypes.string.isRequired, + defaultChecked: PropTypes.bool, +}; + +KuiSwitch.defaultProps = { + defaultChecked: false, +}; diff --git a/ui_framework/src/components/form/switch/switch.test.js b/ui_framework/src/components/form/switch/switch.test.js new file mode 100644 index 0000000000000..79bb53b29ac98 --- /dev/null +++ b/ui_framework/src/components/form/switch/switch.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiSwitch } from './switch'; + +describe('KuiSwitch', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/form/text_area/_index.scss b/ui_framework/src/components/form/text_area/_index.scss new file mode 100644 index 0000000000000..f14b87660661e --- /dev/null +++ b/ui_framework/src/components/form/text_area/_index.scss @@ -0,0 +1 @@ +@import 'text_area'; diff --git a/ui_framework/src/components/form/text_area/_text_area.scss b/ui_framework/src/components/form/text_area/_text_area.scss new file mode 100644 index 0000000000000..97320031ead3f --- /dev/null +++ b/ui_framework/src/components/form/text_area/_text_area.scss @@ -0,0 +1,3 @@ +.kuiTextArea { + @include kuiFieldStyle; +} diff --git a/ui_framework/src/components/form/text_area/index.js b/ui_framework/src/components/form/text_area/index.js new file mode 100644 index 0000000000000..6d9dfe10b23dc --- /dev/null +++ b/ui_framework/src/components/form/text_area/index.js @@ -0,0 +1 @@ +export { KuiTextArea } from './text_area'; diff --git a/ui_framework/src/components/form/text_area/text_area.js b/ui_framework/src/components/form/text_area/text_area.js new file mode 100644 index 0000000000000..6c9e13b7f231b --- /dev/null +++ b/ui_framework/src/components/form/text_area/text_area.js @@ -0,0 +1,33 @@ +import React, { + PropTypes, +} from 'react'; +import classNames from 'classnames'; + +export const KuiTextArea = ({ children, rows, name, id, placeholder, className, ...rest }) => { + const classes = classNames('kuiTextArea', className); + + return ( + + ); +}; + +KuiTextArea.propTypes = { + name: PropTypes.string.isRequired, + id: PropTypes.string, + placeholder: PropTypes.string, + rows: PropTypes.number, +}; + +KuiTextArea.defaultProps = { + rows: 6, +}; + diff --git a/ui_framework/src/components/form/text_area/text_area.test.js b/ui_framework/src/components/form/text_area/text_area.test.js new file mode 100644 index 0000000000000..4014c2c8d412e --- /dev/null +++ b/ui_framework/src/components/form/text_area/text_area.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../../test/required_props'; + +import { KuiTextArea } from './text_area'; + +describe('KuiTextArea', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/icon/_icon.scss b/ui_framework/src/components/icon/_icon.scss index 87749f040d62a..994677dbed148 100644 --- a/ui_framework/src/components/icon/_icon.scss +++ b/ui_framework/src/components/icon/_icon.scss @@ -1,6 +1,7 @@ .kuiIcon { display: inline-block; vertical-align: middle; + fill: $kuiTextColor; &:focus { opacity: 1; // We often hide icons on hover. Make sure they appear on focus. diff --git a/ui_framework/src/components/icon/assets/check.svg b/ui_framework/src/components/icon/assets/check.svg new file mode 100644 index 0000000000000..71bda73605bc6 --- /dev/null +++ b/ui_framework/src/components/icon/assets/check.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui_framework/src/components/icon/assets/lock.svg b/ui_framework/src/components/icon/assets/lock.svg new file mode 100644 index 0000000000000..4dda0d8f86d1b --- /dev/null +++ b/ui_framework/src/components/icon/assets/lock.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui_framework/src/components/icon/icon.js b/ui_framework/src/components/icon/icon.js index 2039fd39ca610..ddd719c1098c1 100644 --- a/ui_framework/src/components/icon/icon.js +++ b/ui_framework/src/components/icon/icon.js @@ -16,6 +16,8 @@ import '!!svg-sprite!./assets/search.svg'; import '!!svg-sprite!./assets/user.svg'; import '!!svg-sprite!./assets/help.svg'; import '!!svg-sprite!./assets/cross.svg'; +import '!!svg-sprite!./assets/check.svg'; +import '!!svg-sprite!./assets/lock.svg'; import '!!svg-sprite!./assets/arrow_up.svg'; import '!!svg-sprite!./assets/arrow_down.svg'; import '!!svg-sprite!./assets/arrow_left.svg'; @@ -40,6 +42,8 @@ const typeToIconMap = { user: 'user', help: 'help', cross: 'cross', + check: 'check', + lock: 'lock', arrowUp: 'arrow_up', arrowDown: 'arrow_down', arrowLeft: 'arrow_left', diff --git a/ui_framework/src/components/index.js b/ui_framework/src/components/index.js index c3f9ad5b40954..23cb80f7708fc 100644 --- a/ui_framework/src/components/index.js +++ b/ui_framework/src/components/index.js @@ -10,6 +10,21 @@ export { KuiCallOut, } from './call_out'; +export { + KuiForm, + KuiCheckbox, + KuiFieldNumber, + KuiFieldPassword, + KuiRadio, + KuiRange, + KuiFormRow, + KuiFieldSearch, + KuiSelect, + KuiSwitch, + KuiFieldText, + KuiTextArea, +} from './form'; + export { KuiHeader, KuiHeaderBreadcrumb, diff --git a/ui_framework/src/components/index.scss b/ui_framework/src/components/index.scss index 20d002454b7c8..017c8029b6277 100644 --- a/ui_framework/src/components/index.scss +++ b/ui_framework/src/components/index.scss @@ -1,6 +1,7 @@ @import 'avatar/index'; @import 'button/index'; @import 'call_out/index'; +@import 'form/index'; @import 'header/index'; @import 'icon/index'; @import 'key_pad_menu/index'; diff --git a/ui_framework/src/components/popover/_popover.scss b/ui_framework/src/components/popover/_popover.scss index 2b6fb8efd2a28..61dc0dca34c25 100644 --- a/ui_framework/src/components/popover/_popover.scss +++ b/ui_framework/src/components/popover/_popover.scss @@ -10,7 +10,7 @@ .kuiPopover__body { opacity: 1; visibility: visible; - z-index: $kuiZContent; + z-index: $kuiZContentMenu; margin-top: $kuiSizeS; } } diff --git a/ui_framework/src/global_styling/variables/_colors.scss b/ui_framework/src/global_styling/variables/_colors.scss index cc78279cb3963..841ca00b0bff1 100644 --- a/ui_framework/src/global_styling/variables/_colors.scss +++ b/ui_framework/src/global_styling/variables/_colors.scss @@ -56,6 +56,7 @@ $kuiColorFullShade: #000 !default; // $kuiColorDarkestShade: #F5F5F5; // $kuiColorFullShade: #FFF; // $kuiColorPrimary: tint($kuiColorPrimary, 30%); +// $kuiColorDanger: tint($kuiColorDanger, 30%); // Every color below must be based mathmatically on the set above.