From 8ad731e8fb626eafc7963ad669ef4dcd7501573d Mon Sep 17 00:00:00 2001 From: Kyle Knight Date: Fri, 16 Dec 2016 09:54:49 -0600 Subject: [PATCH] Refactor form fields (#636) --- .../components/buttons/Button/_styles.scss | 4 +- frontend/components/buttons/Rocker/Rocker.jsx | 43 ++++++++----- .../components/buttons/Rocker/_styles.scss | 18 +++--- frontend/components/buttons/Slider/Slider.jsx | 29 --------- .../components/buttons/Slider/_styles.scss | 29 --------- .../components/forms/FormField/FormField.jsx | 63 +++++++++++++++++++ .../components/forms/FormField/_styles.scss | 33 ++++++++++ frontend/components/forms/FormField/index.js | 1 + .../forms/InviteUserForm/InviteUserForm.jsx | 2 + .../forms/fields/Checkbox/Checkbox.jsx | 35 ++++++++--- .../forms/fields/Checkbox/_styles.scss | 15 +++++ .../forms/fields/Dropdown/Dropdown.jsx | 43 +++++++++---- .../forms/fields/Dropdown/_styles.scss | 21 +++++++ .../forms/fields/InputField/InputField.jsx | 41 ++++-------- .../forms/fields/InputField/_styles.scss | 18 +++++- .../components/forms/fields/Slider/Slider.jsx | 50 +++++++++++++++ .../forms/fields/Slider/_styles.scss | 60 ++++++++++++++++++ .../{buttons => forms/fields}/Slider/index.js | 0 .../forms/queries/QueryForm/QueryForm.jsx | 2 +- .../queries/QueryComposer/_styles.scss | 15 +++-- .../QuerySidePanel/QuerySidePanel.jsx | 2 +- .../UserBlock/UserBlock.jsx | 2 +- .../UserManagementPage/UserManagementPage.jsx | 18 +++--- frontend/pages/HomePage/HomePage.jsx | 2 +- .../hosts/ManageHostsPage/ManageHostsPage.jsx | 18 +++--- .../ManageHostsPage/ManageHostsPage.tests.jsx | 4 +- frontend/styles/global/_global.scss | 2 +- 27 files changed, 400 insertions(+), 170 deletions(-) delete mode 100644 frontend/components/buttons/Slider/Slider.jsx delete mode 100644 frontend/components/buttons/Slider/_styles.scss create mode 100644 frontend/components/forms/FormField/FormField.jsx create mode 100644 frontend/components/forms/FormField/_styles.scss create mode 100644 frontend/components/forms/FormField/index.js create mode 100644 frontend/components/forms/fields/Slider/Slider.jsx create mode 100644 frontend/components/forms/fields/Slider/_styles.scss rename frontend/components/{buttons => forms/fields}/Slider/index.js (100%) diff --git a/frontend/components/buttons/Button/_styles.scss b/frontend/components/buttons/Button/_styles.scss index 321c882597..75cba95c84 100644 --- a/frontend/components/buttons/Button/_styles.scss +++ b/frontend/components/buttons/Button/_styles.scss @@ -110,10 +110,10 @@ $base-class: 'button'; padding: $medium; width: 100%; height: auto; - box-shadow: inset 0 -3px 0px 0 rgba(0, 0, 0, 0.25), 0 -2px 3px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, 0.25), 0 -2px 3px 0 rgba(0, 0, 0, 0.15); &:active { - box-shadow: 0 1px 0 #734893, 0 -2px 0 #D1D9E9; + box-shadow: 0 1px 0 #734893, 0 -2px 0 #d1d9e9; } } diff --git a/frontend/components/buttons/Rocker/Rocker.jsx b/frontend/components/buttons/Rocker/Rocker.jsx index 23bea52e28..3230c9ade8 100644 --- a/frontend/components/buttons/Rocker/Rocker.jsx +++ b/frontend/components/buttons/Rocker/Rocker.jsx @@ -8,43 +8,54 @@ class Rocker extends Component { static propTypes = { className: PropTypes.string, - handleChange: PropTypes.func, - name: PropTypes.string, + onChange: PropTypes.func, options: PropTypes.shape({ - aText: PropTypes.string, - aIcon: PropTypes.string, - bText: PropTypes.string, - bIcon: PropTypes.string, + rightText: PropTypes.string, + rightIcon: PropTypes.string, + leftText: PropTypes.string, + leftIcon: PropTypes.string, }), value: PropTypes.string, }; static defaultProps = { - handleChange: noop, + onChange: noop, + }; + + handleChange = (evt) => { + const { onChange, options: { rightText, leftText }, value } = this.props; + evt.preventDefault(); + + const newOption = value === leftText ? rightText : leftText; + + onChange(newOption); }; render () { - const { className, handleChange, name, options, value } = this.props; - const { aText, aIcon, bText, bIcon } = options; + const { handleChange } = this; + const { className, options, value } = this.props; + const { rightText, rightIcon, leftText, leftIcon } = options; const baseClass = 'kolide-rocker'; const rockerClasses = classnames(baseClass, className); + const buttonClasses = classnames(`${baseClass}__button`, 'button', 'button--unstyled', { + [`${baseClass}__button--checked`]: value === leftText, + }); return (
-
); } diff --git a/frontend/components/buttons/Rocker/_styles.scss b/frontend/components/buttons/Rocker/_styles.scss index 10e38e2874..5378687dcc 100644 --- a/frontend/components/buttons/Rocker/_styles.scss +++ b/frontend/components/buttons/Rocker/_styles.scss @@ -1,7 +1,8 @@ .kolide-rocker { box-shadow: 0 0 9px 0 rgba(72, 81, 109, 0.1); - &__label { + &__button { + @include transform(translateY(-19px)); position: relative; cursor: pointer; width: 180px; @@ -10,13 +11,10 @@ background-color: #9fa5ab; display: block; padding: 0 2px; - box-shadow: inset 0 -3px 3px #aab3bd; - } + background: transparent; + border: 0; - &__checkbox { - display: none; - - &:checked ~ .kolide-rocker__switch--opt-b { + &--checked > .kolide-rocker__switch--left { span { @include transform(skewX(0) rotateZ(0)); top: 3px; @@ -31,7 +29,7 @@ } } - &:checked ~ .kolide-rocker__switch--opt-a { + &--checked > .kolide-rocker__switch--right { span { @include transform(skewX(-6deg) rotateZ(-6deg)); top: -6px; @@ -92,7 +90,7 @@ } } - &--opt-b { + &--left { @include linear-gradient(-180deg, #eaedfb 81%, #aab3bd 100%); left: 2px; @@ -113,7 +111,7 @@ } } - &--opt-a { + &--right { @include linear-gradient(-180deg, #9651ca 81%, #6e3c93 100%); right: 2px; diff --git a/frontend/components/buttons/Slider/Slider.jsx b/frontend/components/buttons/Slider/Slider.jsx deleted file mode 100644 index b4bb387596..0000000000 --- a/frontend/components/buttons/Slider/Slider.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { PropTypes } from 'react'; -import classnames from 'classnames'; - -const Slider = ({ onClick, engaged }) => { - const baseClass = 'slider-wrap'; - - const sliderBtnClass = classnames( - baseClass, - { [`${baseClass}--active`]: engaged } - ); - - const sliderDotClass = classnames( - `${baseClass}__dot`, - { [`${baseClass}__dot--active`]: engaged } - ); - - return ( - - ); -}; - -Slider.propTypes = { - engaged: PropTypes.bool, - onClick: PropTypes.func, -}; - -export default Slider; diff --git a/frontend/components/buttons/Slider/_styles.scss b/frontend/components/buttons/Slider/_styles.scss deleted file mode 100644 index 772dab8d9f..0000000000 --- a/frontend/components/buttons/Slider/_styles.scss +++ /dev/null @@ -1,29 +0,0 @@ -.slider-wrap { - @include transition(background-color 400ms ease-in-out); - background-color: $text-medium; - border-radius: 12px; - border: 1px solid #eaeaea; - cursor: pointer; - display: inline-block; - height: 22px; - min-width: 40px; - position: relative; - width: 40px; - - &--active { - background-color: $brand; - } - - &__dot { - @include transition(lest 300ms ease-in-out); - @include size(14px); - @include position(absolute, 0 null null 5px); - margin-top: 3px; - border-radius: 50%; - background-color: $white; - - &--active { - left: 21px; - } - } -} diff --git a/frontend/components/forms/FormField/FormField.jsx b/frontend/components/forms/FormField/FormField.jsx new file mode 100644 index 0000000000..9570d54658 --- /dev/null +++ b/frontend/components/forms/FormField/FormField.jsx @@ -0,0 +1,63 @@ +import React, { Component, PropTypes } from 'react'; +import classnames from 'classnames'; + +const baseClass = 'form-field'; + +class FormField extends Component { + static propTypes = { + children: PropTypes.element, + className: PropTypes.string, + error: PropTypes.string, + hint: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), + label: PropTypes.string, + name: PropTypes.string, + type: PropTypes.string, + }; + + renderLabel = () => { + const { error, label, name } = this.props; + const labelWrapperClasses = classnames( + `${baseClass}__label`, + { [`${baseClass}__label--error`]: error } + ); + + if (!label) { + return false; + } + + return ( + + ); + } + + renderHint = () => { + const { hint } = this.props; + + if (hint) { + return {hint}; + } + + return false; + } + + render () { + const { renderLabel, renderHint } = this; + const { children, className, type } = this.props; + + const formFieldClass = classnames(baseClass, className, { + [`${baseClass}--${type}`]: type, + }); + + return ( +
+ {renderLabel()} + {children} + {renderHint()} +
+ ); + } +} + +export default FormField; diff --git a/frontend/components/forms/FormField/_styles.scss b/frontend/components/forms/FormField/_styles.scss new file mode 100644 index 0000000000..038b1dba32 --- /dev/null +++ b/frontend/components/forms/FormField/_styles.scss @@ -0,0 +1,33 @@ +.form-field { + margin-bottom: $pad-small; + + &__label { + font-size: 16px; + font-weight: $bold; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.5px; + color: $text-dark; + display: block; + margin-bottom: 4px; + + &--error { + color: $alert; + } + } + + &__hint { + font-size: 14px; + font-weight: $normal; + line-height: 1.57; + letter-spacing: 1px; + color: $accent-text; + + code { + color: $brand-light; + background-color: $accent-light; + padding: 2px; + font-family: 'SourceCodePro', $monospace; + } + } +} diff --git a/frontend/components/forms/FormField/index.js b/frontend/components/forms/FormField/index.js new file mode 100644 index 0000000000..97a6f4f355 --- /dev/null +++ b/frontend/components/forms/FormField/index.js @@ -0,0 +1 @@ +export default from './FormField'; diff --git a/frontend/components/forms/InviteUserForm/InviteUserForm.jsx b/frontend/components/forms/InviteUserForm/InviteUserForm.jsx index 61b4a30eda..f73b72032f 100644 --- a/frontend/components/forms/InviteUserForm/InviteUserForm.jsx +++ b/frontend/components/forms/InviteUserForm/InviteUserForm.jsx @@ -135,6 +135,7 @@ class InviteUserForm extends Component { autofocus error={errors.name} name="name" + iconName="username" onChange={onInputChange('name')} placeholder="Name" value={name} @@ -142,6 +143,7 @@ class InviteUserForm extends Component { { + const { onChange, value } = this.props; + + return onChange(!value); + }; + render () { - const { children, className, name, onChange } = this.props; + const { handleChange } = this; + const { children, className, name, value } = this.props; const checkBoxClass = classnames(baseClass, className); + const formFieldProps = pick(this.props, ['hint', 'label', 'error', 'name']); + return ( - + + + ); } } -export default InputField; +export default Checkbox; diff --git a/frontend/components/forms/fields/Checkbox/_styles.scss b/frontend/components/forms/fields/Checkbox/_styles.scss index 51262a19c9..05baccd097 100644 --- a/frontend/components/forms/fields/Checkbox/_styles.scss +++ b/frontend/components/forms/fields/Checkbox/_styles.scss @@ -1,7 +1,11 @@ .kolide-checkbox { + @include clearfix; + position: relative; + &__input { visibility: hidden; margin: 0; + position: absolute; &:checked + .kolide-checkbox__tick { &::after { @@ -42,4 +46,15 @@ visibility: visible; } } + + &__label { + font-size: 13px; + font-weight: $normal; + line-height: 20px; + letter-spacing: 0.5px; + color: $text-medium; + display: inline-block; + padding-left: 15px; + float: left; + } } diff --git a/frontend/components/forms/fields/Dropdown/Dropdown.jsx b/frontend/components/forms/fields/Dropdown/Dropdown.jsx index c668283ed1..51add0a4ea 100644 --- a/frontend/components/forms/fields/Dropdown/Dropdown.jsx +++ b/frontend/components/forms/fields/Dropdown/Dropdown.jsx @@ -1,38 +1,55 @@ import React, { Component, PropTypes } from 'react'; import Select from 'react-select'; -import { noop } from 'lodash'; +import { noop, pick } from 'lodash'; import dropdownOptionInterface from 'interfaces/dropdownOption'; +import FormField from 'components/forms/FormField'; + +const baseClass = 'input-dropdown'; class Dropdown extends Component { static propTypes = { options: PropTypes.arrayOf(dropdownOptionInterface).isRequired, - onSelect: PropTypes.func, + onChange: PropTypes.func, className: PropTypes.string, + error: PropTypes.string, + hint: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), + label: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string, clearable: PropTypes.bool, }; static defaultProps = { - onSelect: noop, + onChange: noop, clearable: false, placeholder: 'Select One...', }; + handleChange = ({ value }) => { + const { onChange } = this.props; + + return onChange(value); + }; + render () { - const { options, className, placeholder, value, clearable, onSelect } = this.props; + const { handleChange } = this; + const { options, className, placeholder, value, clearable } = this.props; + + const formFieldProps = pick(this.props, ['hint', 'label', 'error', 'name']); return ( - + ); } } diff --git a/frontend/components/forms/fields/Dropdown/_styles.scss b/frontend/components/forms/fields/Dropdown/_styles.scss index 761c500b6a..2b9e24dcf9 100644 --- a/frontend/components/forms/fields/Dropdown/_styles.scss +++ b/frontend/components/forms/fields/Dropdown/_styles.scss @@ -87,3 +87,24 @@ } } } + +.input-dropdown { + &__wrapper { + margin-bottom: $pad-base; + } + + &__hint { + font-size: 14px; + font-weight: $normal; + line-height: 1.57; + letter-spacing: 1px; + color: $accent-text; + + code { + color: $brand-light; + background-color: $accent-light; + padding: 2px; + font-family: 'SourceCodePro', $monospace; + } + } +} diff --git a/frontend/components/forms/fields/InputField/InputField.jsx b/frontend/components/forms/fields/InputField/InputField.jsx index 58f8612399..c47ac5ffd9 100644 --- a/frontend/components/forms/fields/InputField/InputField.jsx +++ b/frontend/components/forms/fields/InputField/InputField.jsx @@ -1,5 +1,8 @@ import React, { Component, PropTypes } from 'react'; import classnames from 'classnames'; +import { pick } from 'lodash'; + +import FormField from 'components/forms/FormField'; const baseClass = 'input-field'; @@ -7,6 +10,7 @@ class InputField extends Component { static propTypes = { autofocus: PropTypes.bool, error: PropTypes.string, + hint: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), inputClassName: PropTypes.string, // eslint-disable-line react/forbid-prop-types inputWrapperClass: PropTypes.string, inputOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types @@ -49,43 +53,21 @@ class InputField extends Component { return onChange(value); } - renderLabel = () => { - const { error, label, labelClassName, name } = this.props; - const labelWrapperClasses = classnames( - `${baseClass}__label`, - labelClassName, - { [`${baseClass}__label--error`]: error } - ); - - if (!label) { - return false; - } - - return ( - - ); - } - render () { const { error, inputClassName, inputOptions, inputWrapperClass, name, placeholder, type, value } = this.props; - const { onInputChange, renderLabel } = this; + const { onInputChange } = this; const shouldShowPasswordClass = type === 'password'; const inputClasses = classnames(baseClass, inputClassName, { [`${baseClass}--password`]: shouldShowPasswordClass, [`${baseClass}--error`]: error, [`${baseClass}__textarea`]: type === 'textarea', }); - const inputWrapperClasses = classnames(`${baseClass}__wrapper`, inputWrapperClass); + + const formFieldProps = pick(this.props, ['hint', 'label', 'error', 'name']); if (type === 'textarea') { return ( -
- {renderLabel()} +