diff --git a/.eslintignore b/.eslintignore index 841de086c..fc2805bac 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ /artifacts /node_modules +/storybook-static \ No newline at end of file diff --git a/lib/Checkbox/Checkbox.css b/lib/Checkbox/Checkbox.css index 2791ce6cb..064e43836 100644 --- a/lib/Checkbox/Checkbox.css +++ b/lib/Checkbox/Checkbox.css @@ -6,57 +6,28 @@ .checkbox { display: flex; + flex-direction: column; position: relative; color: var(--color-text); line-height: var(--line-height); + margin-bottom: 0; &.fullWidth { width: 100%; } - - &.marginBottom0 { - margin-bottom: 0; - } - - /** - * Inline checkbox - */ - &.inline { - display: inline-flex; - - /** - * If we only render the checkbox (no label) - */ - - &.noLabel { - height: auto; - - & label.checkboxLabel { - height: auto; - padding: 6px; - margin: 0; - - &::before { - left: 0; - right: 0; - } - } - } - } -} - -/* Apply spacing between inline checkboxes when stacked */ -.inline + .inline { - margin-left: var(--gutter-static-two-thirds); - margin-right: 0; } -[dir="rtl"] .inline + .inline { - margin-right: var(--gutter-static-two-thirds); - margin-left: 0; +/** + * Label + */ +.label { + margin-bottom: 0; } -.labelCheck { +/** + * Custom checkbox styling + */ +.checkboxIcon { position: relative; width: var(--checkbox-size); height: var(--checkbox-size); @@ -66,31 +37,103 @@ display: flex; overflow: hidden; flex-shrink: 0; - top: 1px; & svg { opacity: 0; + } +} + +.inner { + min-height: var(--control-min-size-desktop); + display: flex; + align-items: baseline; + cursor: pointer; + flex-grow: 2; + position: relative; + border-radius: var(--radius); + font-size: var(--font-size-medium); +} + +/** + * Label text + */ +.labelText { + margin: 0 0 0 var(--gutter-static-one-third); + display: flex; + align-items: center; + min-height: var(--control-min-size-desktop); +} + +[dir="rtl"] .labelText { + margin: 0 var(--gutter-static-one-third) 0 0; +} + +/** + * Input + */ +.input { + position: absolute; + z-index: 10; + opacity: 0; + cursor: pointer; +} + +.noLabel .input { + transform: scale(1.9); +} + +/** + * Vertical label alignment + */ +.vertical .label { + flex-direction: column; + align-items: flex-start; + + & .labelText { + margin: 0 0 var(--control-label-margin-bottom) 0; + min-height: 0; + } - & path { - stroke: #000; - } + & .inner { + align-items: center; } } +/** + * Inline checkbox + */ +.inline { + display: inline-flex; + + &.noLabel .inner { + align-items: center; + } +} + +/* Apply spacing between inline checkboxes when stacked */ +.inline + .inline { + margin-left: var(--gutter-static-two-thirds); + margin-right: 0; +} + +[dir="rtl"] .inline + .inline { + margin-right: var(--gutter-static-two-thirds); + margin-left: 0; +} + /** * Checkbox feedback */ .checkboxFeedback { - font-size: var(--font-size-small); + font-size: var(--font-size-medium); } -/* Warning and error states */ .hasWarning { & .checkboxFeedback { color: var(--warn); } - & .labelCheck { + & .checkboxIcon { border-color: var(--warn); } } @@ -100,30 +143,16 @@ color: var(--error); } - & .labelCheck { + & .checkboxIcon { border-color: var(--error); } } -.checkboxLabel { - padding: var(--input-vertical-padding) 0; - display: flex; - align-items: baseline; - cursor: pointer; - flex-grow: 2; - position: relative; - border-radius: var(--radius); - font-size: var(--font-size-medium); - margin-bottom: 0; - - &.disabledLabel { - cursor: not-allowed; - color: var(--color-text-p2); - - & .labelCheck { - opacity: 0.5; - } - } +/** + * Interaction styles (hover, focus, active) + */ +.checkboxInteractionStylesControl { + composes: interactionStylesControl from "../sharedStyles/interactionStyles.css"; } .checkboxInteractionStyles { @@ -134,48 +163,24 @@ composes: isFocused from "../sharedStyles/interactionStyles.css"; } -.formLabel { - composes: checkboxLabel; - font-weight: bold; - text-transform: Uppercase; -} - -.labelText { - margin: 0 0 0 var(--gutter-static-one-third); - display: flex; - align-items: center; - min-height: var(--control-min-size-desktop); -} - -[dir="rtl"] .labelText { - margin: 0 var(--gutter-static-one-third) 0 0; -} - -/* fix for shrinkage issues with checkbox as a flex-child */ -:global input[type='checkbox'] { - min-width: 16px; -} - /** - * Custom input styling + * Disabled */ -input.checkboxInput { - margin: 0 9px; - margin-bottom: 2px; - position: absolute; - clip: rect(1px, 1px, 1px, 1px); - z-index: -1; -} +.disabled { + & .checkboxIcon { + opacity: 0.5; + } -/* disabled styles */ -.checkboxInput:disabled .labelCheck { - opacity: 0.65; + &:hover .inner { + cursor: default; + } } /** * Checked state */ -.checkboxInput:checked + .labelCheck { + +.input:checked + .checkboxIcon { background-color: #000; & svg { diff --git a/lib/Checkbox/Checkbox.js b/lib/Checkbox/Checkbox.js index 989f95ddd..1fd88531c 100644 --- a/lib/Checkbox/Checkbox.js +++ b/lib/Checkbox/Checkbox.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { createRef } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import uniqueId from 'lodash/uniqueId'; @@ -22,13 +22,11 @@ class Checkbox extends React.Component { disabled: PropTypes.bool, error: PropTypes.node, fullWidth: PropTypes.bool, - hover: PropTypes.bool, id: PropTypes.string, inline: PropTypes.bool, + inputRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), label: PropTypes.node, labelClass: PropTypes.string, - labelStyle: PropTypes.object, - marginBottom0: PropTypes.bool, name: PropTypes.string, onBlur: PropTypes.func, onChange: PropTypes.func, @@ -39,85 +37,84 @@ class Checkbox extends React.Component { PropTypes.bool, PropTypes.string ]), + vertical: PropTypes.bool, warning: PropTypes.node, }; + static defaultProps = { + vertical: false, + }; + constructor(props) { super(props); - - this.state = { - inputInFocus: false - }; - this.id = this.props.id || uniqueId('checkbox-'); - this.handleChange = this.handleChange.bind(this); - this.handleFocus = this.handleFocus.bind(this); - this.handleBlur = this.handleBlur.bind(this); - this.getFeedbackClasses = this.getFeedbackClasses.bind(this); - this.getFeedbackElement = this.getFeedbackElement.bind(this); + // Typecheck for ref callbacks + if (typeof props.inputRef === 'function') { + this.input = (ref) => { + props.inputRef(ref); + this.input.current = ref; + }; + } else { + this.input = props.inputRef || createRef(); + } } - getLabelStyle() { + state = { + inputInFocus: false + }; + + getInnerClasses = () => { + const { inputInFocus } = this.state; + const { disabled, innerClass } = this.props; + return classNames( - css.checkboxLabel, - { [css[this.props.labelStyle]]: !this.props.labelClass && this.props.labelStyle }, - { [css.checkboxInteractionStyles]: !this.props.disabled }, - { [css.disabledLabel]: this.props.disabled }, - { [css.labelFocused]: this.state.inputInFocus }, - this.props.labelClass, + css.inner, + { [css.checkboxInteractionStyles]: !disabled }, + { [css.disabledLabel]: disabled }, + { [css.labelFocused]: inputInFocus }, + innerClass, ); } - getRootStyle() { + getRootClasses = () => { + const { className, fullWidth, vertical, label, disabled, inline, error, warning } = this.props; + return classNames( [`${css.checkbox}`], - { [`${css.noLabel}`]: !this.props.label }, - { [`${css.hover}`]: this.props.hover }, - { [`${css.hovered}`]: (this.props.hover && this.state.inputInFocus) }, - { [`${css.fullWidth}`]: this.props.fullWidth }, - { [`${css.marginBottom0}`]: this.props.marginBottom0 }, - { [`${css.inline}`]: this.props.inline }, - this.props.className, - this.getFeedbackClasses(), + { [`${css.fullWidth}`]: fullWidth }, + { [`${css.inline}`]: inline || vertical || !label }, + { [`${css.vertical}`]: vertical }, + { [`${css.disabled}`]: disabled }, + { [`${css.noLabel}`]: !label }, + { [`${css.hasError}`]: !!error }, + { [`${css.hasWarning}`]: !!warning }, + className, ); } - // Add feedback class on warning or error - getFeedbackClasses() { - const { warning, error } = this.props; - const classes = []; - - // Check if checkbox has error or warning - if (error) { - classes.push(css.hasError); - } else if (warning) { - classes.push(css.hasWarning); - } - - // Add the feedback class if we have feedback classes - if (classes.length) { - classes.push(css.hasFeedback); - } + getLabelClasses = () => { + const { disabled, labelClass } = this.props; - return classes; + return classNames( + [`${css.label}`], + { [css.checkboxInteractionStylesControl]: !disabled }, + labelClass, + ); } - // Get feedback element (if the checkbox has any feedback to show) - getFeedbackElement() { - const hasFeedback = !!this.getFeedbackClasses().length; + renderFeedbackElement = () => { + const { error, warning } = this.props; + const hasFeedback = error || warning; + if (!hasFeedback) { return false; } - const error = this.props.error; - const warning = this.props.warning; - - // Currently prefering error over warning return (
{error || warning}
); } - handleChange(e) { + handleChange = (e) => { const { readOnly, onChange } = this.props; // Disable onChange handler if the field is read-only @@ -128,7 +125,7 @@ class Checkbox extends React.Component { } } - handleFocus(e) { + handleFocus = (e) => { this.setState({ inputInFocus: true, }); @@ -138,7 +135,7 @@ class Checkbox extends React.Component { } } - handleBlur(e) { + handleBlur = (e) => { this.setState({ inputInFocus: false, }); @@ -148,6 +145,24 @@ class Checkbox extends React.Component { } } + renderLabelText = () => { + const { label, required, readOnly } = this.props; + + return ( + + { label } + { required && } + { readOnly && ( + + + + + + ) } + + ); + } + render() { const { autoFocus, @@ -157,62 +172,57 @@ class Checkbox extends React.Component { name, readOnly, required, - value + value, + vertical, + error, } = this.props; // eslint-disable-next-line react/forbid-foreign-prop-types const [, inputCustom] = separateComponentProps(this.props, Checkbox.propTypes); return ( -
-