diff --git a/src/components/Checkbox/Checkbox.scss b/src/components/Checkbox/Checkbox.scss index f17399560a0d3a..0ab128bf4ef53c 100644 --- a/src/components/Checkbox/Checkbox.scss +++ b/src/components/Checkbox/Checkbox.scss @@ -5,110 +5,111 @@ // // Office UI Fabric // -------------------------------------------------- -// Choice fields (radio buttons and checkboxes) styles - -// Unselected, radio button (default) -.ms-ChoiceField { - @include ms-font-m; - @include ms-u-normalize; +// Checkbox styles + +$ms-checkbox-field-size: 20px; +$ms-checkbox-transition-duration: 200ms; +$ms-checkbox-transition-timing: cubic-bezier(.4, 0, .23, 1); + +.ms-Checkbox { + box-sizing: border-box; + color: $ms-color-neutralPrimary; + font-family: $ms-font-family-base; + font-size: $ms-font-size-m; + font-weight: $ms-font-weight-regular; min-height: 36px; + border: 1px solid transparent; position: relative; + @include padding-left(8px); .ms-Label { font-size: $ms-font-size-m; - padding: 0 0 0 26px; + @include padding(0, 0, 0, 26px); + display: inline-block; } } -//== State: Disabled choicefield -.ms-ChoiceField-input:disabled { - + .ms-ChoiceField-field { - pointer-events: none; - cursor: default; - - &:before { - background-color: $ms-color-neutralTertiaryAlt; - color: $ms-color-neutralTertiaryAlt; - } +// The hidden input +.ms-Checkbox-input { + position: absolute; + opacity: 0; + top: 8px +} - &:after { - border-color: $ms-color-neutralLight; - } +// The checkbox square container +.ms-Checkbox-field::before { + content: ''; + display: inline-block; + border: 2px solid $ms-color-neutralTertiary; + width: $ms-checkbox-field-size; + height: $ms-checkbox-field-size; + font-weight: normal; + position: absolute; + box-sizing: border-box; + transition-property: background, border, border-color; + transition-duration: $ms-checkbox-transition-duration; + transition-timing-function: $ms-checkbox-transition-timing; +} - .ms-Label { - color: $ms-color-neutralTertiary; - } +// The check mark icon +.ms-Checkbox-field::after { + @include ms-Icon--CheckMark; + font-family: 'FabricMDL2Icons'; + display: none; + position: absolute; + font-weight: 900; + background-color: transparent; + font-size: $ms-font-size-s-plus; + top: 0; + color: $ms-color-white; + line-height: $ms-checkbox-field-size; + width: $ms-checkbox-field-size; + text-align: center; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-black; + } - @media screen and (-ms-high-contrast: active) { - &:before { - background-color: $ms-color-contrastBlackDisabled; - color: $ms-color-contrastBlackDisabled; - } + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-white; + } +} - &:after { - border-color: $ms-color-contrastBlackDisabled; - } +// The checkbox field +.ms-Checkbox-field { + display: inline-block; + cursor: pointer; + margin-top: 8px; + position: relative; + vertical-align: top; + user-select: none; - .ms-Label { - color: $ms-color-contrastBlackDisabled; - } + &:hover { + &::before { + border-color: $ms-color-neutralSecondaryAlt; } - @media screen and (-ms-high-contrast: black-on-white) { - &:before { - background-color: $ms-color-contrastWhiteDisabled; - color: $ms-color-contrastWhiteDisabled; - } - - &:after { - border-color: $ms-color-contrastWhiteDisabled; - } - - .ms-Label { - color: $ms-color-contrastWhiteDisabled; - } + .ms-Label { + color: $ms-color-black; } } -} - -// The original unstyled input element -.ms-ChoiceField-input { - position: absolute; - opacity: 0; - top: 8px; - &:focus:not(:disabled) { - + .ms-ChoiceField-field:after { + &:focus { + &::before { border-color: $ms-color-neutralSecondaryAlt; } - } -} -// The choicefield radio button or checkbox -.ms-ChoiceField-field { - display: inline-block; - cursor: pointer; - margin-top: 8px; - position: relative; + &.is-disabled::before { + border-color: $ms-color-neutralTertiaryAlt; + } - // The actual styled choicefield element - radio button by default - &:after { - content: ''; - display: inline-block; - border: 1px $ms-color-neutralTertiaryAlt solid; - width: 19px; - height: 19px; - cursor: pointer; - position: relative; - font-weight: normal; - left: -1px; - top: -1px; - border-radius: 50%; - position: absolute; + &.is-checked::before { + border-color: $ms-color-themeDarkAlt; + } } - &:hover { - &:after { + &:active { + &::before { border-color: $ms-color-neutralSecondaryAlt; } @@ -116,88 +117,91 @@ color: $ms-color-black; } } -} -// A selected radio button -.ms-ChoiceField-input:checked { - + .ms-ChoiceField-field { - // Circle indicating a checked radio button - &:before { - background-color: $ms-color-neutralSecondary; - border-color: $ms-color-neutralSecondary; - color: $ms-color-neutralSecondary; - border-radius: 50%; - content: '\00a0'; - display: inline-block; - position: absolute; - top: 4px; - bottom: 0; - left: 4px; - width: 11px; - height: 11px; - box-sizing: border-box; + //== State: A checkbox is checked + // + &.is-checked { + &::before { + border: 10px solid $ms-color-themePrimary; + background-color: $ms-color-themePrimary; @media screen and (-ms-high-contrast: active) { - border-color: $ms-color-white; - background-color: $ms-color-white; + border-color: $ms-color-contrastBlackSelected; } @media screen and (-ms-high-contrast: black-on-white) { - border-color: $ms-color-black; - background-color: $ms-color-black; + border-color: $ms-color-contrastWhiteSelected; } } - &:hover:before { - background-color: $ms-color-neutralDark; - color: $ms-color-neutralDark; + &::after { + display: block; } - } -} -// Checkbox -.ms-ChoiceField-input[type="checkbox"] { - + .ms-ChoiceField-field:after { - border-radius: 0; + &:hover, + &:focus { + &::before { + border-color: $ms-color-themeDarkAlt; + } + } } -} -// A selected checkbox -.ms-ChoiceField-input[type="checkbox"]:checked { - + .ms-ChoiceField-field { - &:before { - @include ms-Icon; - content: '\e041'; - background-color: transparent; - border-radius: 0; - font-size: $ms-font-size-s-plus; - top: 3px; - left: 3px; + //== State: A disabled checkbox + // + &.is-disabled { + cursor: default; + + &:hover, + &:focus { + &::before { + border-color: $ms-color-neutralTertiaryAlt; + } + } + + &::before { + background-color: $ms-color-neutralTertiaryAlt; + border-color: $ms-color-neutralTertiaryAlt; + color: $ms-color-neutralTertiaryAlt; @media screen and (-ms-high-contrast: active) { - color: $ms-color-white; - border-color: transparent; - background-color: transparent; + border-color: $ms-color-contrastBlackDisabled; } @media screen and (-ms-high-contrast: black-on-white) { - color: $ms-color-black; - border-color: transparent; - background-color: transparent; + border-color: $ms-color-contrastWhiteDisabled; + } + } + + .ms-Label { + color: $ms-color-neutralTertiary; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteDisabled; } } } -} + //== State: A checkbox in focus + // + &.is-inFocus { + &::before { + border-color: $ms-color-neutralSecondaryAlt; + } -//== Component: Choicefield group -// -// Choice field groups contain multiple radio buttons or checkboxes -.ms-ChoiceFieldGroup { - margin-bottom: 4px; + &.is-disabled::before { + border-color: $ms-color-neutralTertiaryAlt; + } + + &.is-checked::before { + border-color: $ms-color-themeDarkAlt; + } + } } -// TODO: overrides that need to be removed. -.ms-ChoiceField { - user-select: none; -} \ No newline at end of file +.is-focusVisible .ms-Checkbox.is-inFocus { + border: 1px solid $ms-color-neutralPrimary; +} diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index 7cbc66e3525bb8..66bf8f67153193 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -16,7 +16,8 @@ export class Checkbox extends React.Component { }; private _id: string; - private _checkBox: HTMLInputElement; + private _checkbox: HTMLElement; + private _checkboxInput: HTMLInputElement; constructor(props: ICheckboxProps) { super(props); @@ -30,6 +31,11 @@ export class Checkbox extends React.Component { this._handleInputChange = this._handleInputChange.bind(this); } + public componentDidMount() { + this._checkboxInput.addEventListener('focus', this._onFocus.bind(this), false); + this._checkboxInput.addEventListener('blur', this._onBlur.bind(this), false); + } + public componentWillReceiveProps(newProps: ICheckboxProps) { if (newProps.isChecked !== this.props.isChecked) { this.setState({ @@ -38,25 +44,34 @@ export class Checkbox extends React.Component { } } + public componentWillUnmount() { + this._checkboxInput.removeEventListener('focus', this._onFocus.bind(this)); + this._checkboxInput.removeEventListener('blur', this._onBlur.bind(this)); + } + public render() { let { text, isEnabled, className } = this.props; let { isChecked } = this.state; return ( -
+
this._checkbox = c } + > this._checkBox = c } + ref={ (c): HTMLInputElement => this._checkboxInput = c } id={ this._id } name={ this._id } - className='ms-ChoiceField-input' + className='ms-Checkbox-input' type='checkbox' - role='checkbox' checked={ isChecked } disabled={ !isEnabled } onChange={ this._handleInputChange } - aria-checked={ isChecked } /> -
@@ -64,11 +79,19 @@ export class Checkbox extends React.Component { } public focus() { - if (this._checkBox) { - this._checkBox.focus(); + if (this._checkboxInput) { + this._checkboxInput.focus(); } } + private _onFocus(): void { + this._checkbox.classList.add('is-inFocus'); + } + + private _onBlur(): void { + this._checkbox.classList.remove('is-inFocus'); + } + private _handleInputChange(evt: React.FormEvent) { let { onChanged } = this.props; const isChecked = (evt.target as HTMLInputElement).checked; diff --git a/src/components/ChoiceGroup/ChoiceGroup.scss b/src/components/ChoiceGroup/ChoiceGroup.scss index 20b6222343a41b..206d150389e462 100644 --- a/src/components/ChoiceGroup/ChoiceGroup.scss +++ b/src/components/ChoiceGroup/ChoiceGroup.scss @@ -3,13 +3,17 @@ // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. // -// Office UI Fabric // -------------------------------------------------- -// Choice fields (radio buttons and checkboxes) styles +// ChoiceField styles + +$ms-choiceField-field-size: 20px; +$ms-choiceField-transition-duration: 200ms; +$ms-choiceField-transition-duration-inner: 150ms; +$ms-choiceField-transition-timing: cubic-bezier(.4, 0, .23, 1); //== Component: Choicefield group // -// Choice field groups contain multiple radio buttons or checkboxes + .ms-ChoiceFieldGroup { @include ms-baseFont; margin-bottom: 4px; @@ -20,110 +24,106 @@ } } -// TODO: remove overrides and scrub CSS - -// Unselected, radio button (default) .ms-ChoiceField { - @include ms-font-m; - @include ms-u-normalize; + box-sizing: border-box; + color: $ms-color-neutralPrimary; + font-family: $ms-font-family-base; + font-size: $ms-font-size-m; + font-weight: $ms-font-weight-regular; min-height: 36px; + border: 1px solid transparent; position: relative; + @include padding-left(8px); .ms-Label { font-size: $ms-font-size-m; - padding: 0 0 0 26px; + @include padding(0, 0, 0, 26px); + display: inline-block; } } -//== State: Disabled choicefield -.ms-ChoiceField-input:disabled { - + .ms-ChoiceField-field { - pointer-events: none; - cursor: default; - - &:before { - background-color: $ms-color-neutralTertiaryAlt; - color: $ms-color-neutralTertiaryAlt; - } - - &:after { - border-color: $ms-color-neutralLight; - } - - .ms-Label { - color: $ms-color-neutralTertiary; - } - - @media screen and (-ms-high-contrast: active) { - &:before { - background-color: $ms-color-contrastBlackDisabled; - color: $ms-color-contrastBlackDisabled; - } - - &:after { - border-color: $ms-color-contrastBlackDisabled; - } - - .ms-Label { - color: $ms-color-contrastBlackDisabled; - } - } - - @media screen and (-ms-high-contrast: black-on-white) { - &:before { - background-color: $ms-color-contrastWhiteDisabled; - color: $ms-color-contrastWhiteDisabled; - } - - &:after { - border-color: $ms-color-contrastWhiteDisabled; - } +// The hidden input +.ms-ChoiceField-input { + position: absolute; + opacity: 0; + top: 8px +} - .ms-Label { - color: $ms-color-contrastWhiteDisabled; - } - } - } +// The choiceField container +.ms-ChoiceField-field::before { + content: ''; + display: inline-block; + border: 2px solid $ms-color-neutralTertiary; + width: $ms-choiceField-field-size; + height: $ms-choiceField-field-size; + font-weight: normal; + position: absolute; + box-sizing: border-box; + transition-property: background, border, border-color; + transition-duration: $ms-choiceField-transition-duration; + transition-timing-function: $ms-choiceField-transition-timing; + border-radius: 50%; } -// The original unstyled input element -.ms-ChoiceField-input { +// The circle +.ms-ChoiceField-field::after { + content: ''; + width: 0; + height: 0; + border-radius: 50%; position: absolute; - opacity: 0; top: 8px; + @include left(8px); + bottom: 0; + @include right(0); + transition-property: top, left, right, width, height; + transition-duration: $ms-choiceField-transition-duration-inner; + transition-timing-function: $ms-choiceField-transition-timing; + box-sizing: border-box; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackDisabled; + } - &:focus:not(:disabled) { - + .ms-ChoiceField-field:after { - border-color: $ms-color-neutralSecondaryAlt; - } + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteDisabled; } } -// The choicefield radio button or checkbox +// The choiceField field .ms-ChoiceField-field { display: inline-block; cursor: pointer; margin-top: 8px; position: relative; + vertical-align: top; + user-select: none; - // The actual styled choicefield element - radio button by default - &:after { - content: ''; - display: inline-block; - border: 1px $ms-color-neutralTertiaryAlt solid; - width: 19px; - height: 19px; - cursor: pointer; - position: relative; - font-weight: normal; - left: -1px; - top: -1px; - border-radius: 50%; - position: absolute; + &:hover { + &::before { + border-color: $ms-color-neutralSecondaryAlt; + } + + .ms-Label { + color: $ms-color-black; + } } - &:hover { - &:after { + &:focus { + &::before { + border-color: $ms-color-neutralSecondaryAlt; + } + + &.is-disabled::before { + border-color: $ms-color-neutralTertiaryAlt; + } + + &.is-checked::before { + border-color: $ms-color-themeDarkAlt; + } + } + &:active { + &::before { border-color: $ms-color-neutralSecondaryAlt; } @@ -131,88 +131,102 @@ color: $ms-color-black; } } -} -// A selected radio button -.ms-ChoiceField-input:checked { - + .ms-ChoiceField-field { - // Circle indicating a checked radio button - &:before { - background-color: $ms-color-neutralSecondary; - border-color: $ms-color-neutralSecondary; - color: $ms-color-neutralSecondary; - border-radius: 50%; - content: '\00a0'; - display: inline-block; - position: absolute; - top: 4px; - bottom: 0; - left: 4px; - width: 11px; - height: 11px; - box-sizing: border-box; + //== State: A choiceField is checked + // + &.is-checked { + &::before { + border: 2px solid $ms-color-themePrimary; + background-color: transparent; @media screen and (-ms-high-contrast: active) { - border-color: $ms-color-white; - background-color: $ms-color-white; + border-color: $ms-color-contrastBlackSelected; } @media screen and (-ms-high-contrast: black-on-white) { - border-color: $ms-color-black; - background-color: $ms-color-black; + border-color: $ms-color-contrastWhiteSelected; } } - &:hover:before { - background-color: $ms-color-neutralDark; - color: $ms-color-neutralDark; + &::after { + background-color: $ms-color-themePrimary; + top: 5px; + @include left(5px); + width: 10px; + height: 10px; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + } } - } -} -// Checkbox -.ms-ChoiceField-input[type="checkbox"] { - + .ms-ChoiceField-field:after { - border-radius: 0; + &:hover, + &:focus { + &::before { + border-color: $ms-color-themeDarkAlt; + } + } } -} -// A selected checkbox -.ms-ChoiceField-input[type="checkbox"]:checked { - + .ms-ChoiceField-field { - &:before { - @include ms-Icon; - content: '\e041'; - background-color: transparent; - border-radius: 0; - font-size: $ms-font-size-s-plus; - top: 3px; - left: 3px; + //== State: A disabled choiceField + // + &.is-disabled { + cursor: default; + + &:hover, + &:focus { + &::before { + border-color: $ms-color-neutralTertiaryAlt; + } + } + + &::before { + background-color: $ms-color-neutralTertiaryAlt; + border-color: $ms-color-neutralTertiaryAlt; + color: $ms-color-neutralTertiaryAlt; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-contrastWhiteDisabled; + } + } + + .ms-Label { + color: $ms-color-neutralTertiary; @media screen and (-ms-high-contrast: active) { - color: $ms-color-white; - border-color: transparent; - background-color: transparent; + color: $ms-color-contrastBlackDisabled; } @media screen and (-ms-high-contrast: black-on-white) { - color: $ms-color-black; - border-color: transparent; - background-color: transparent; + color: $ms-color-contrastWhiteDisabled; } } } -} + //== State: A choiceField in focus + // + &.is-inFocus { + &::before { + border-color: $ms-color-neutralSecondaryAlt; + } -//== Component: Choicefield group -// -// Choice field groups contain multiple radio buttons or checkboxes -.ms-ChoiceFieldGroup { - margin-bottom: 4px; -} + &.is-disabled::before { + border-color: $ms-color-neutralTertiaryAlt; + } -// TODO: overrides that need to be removed. + &.is-checked::before { + border-color: $ms-color-themeDarkAlt; + } + } +} .ms-ChoiceField--image { $fieldMinWidth: 164px; @@ -223,6 +237,7 @@ font-size: 0; @include margin-right(6px); + @include padding-left(0px); @include ms-bgColor-neutralLighter; .ms-ChoiceField-field--image { @@ -230,23 +245,20 @@ box-sizing: border-box; min-width: $fieldMinWidth; - cursor: pointer; padding: $fieldPadding $fieldPadding 12px $fieldPadding; - text-align: center; - - transition: all 200ms ease; + transition: all $ms-choiceField-transition-duration ease; .ms-ChoiceField-innerField { position: relative; .ms-ChoiceField-imageWrapper { - transition: opacity 200ms ease; + transition: opacity $ms-choiceField-transition-duration ease; &.is-hidden { position: absolute; - left: 0; + @include left(0); top: 0; width: 100%; height: 100%; @@ -269,7 +281,7 @@ .ms-ChoiceField-icon { display: none; position: absolute; - left: 0; + @include left(0); line-height: $ms-font-size-s; @include ms-fontSize-l; @@ -285,27 +297,17 @@ @include ms-fontColor-neutralPrimary; } } - } - - .ms-ChoiceField-input:checked { - & + .ms-ChoiceField-field--image .ms-ChoiceField-labelWrapper .ms-ChoiceField-icon { - display: inline-block; - } - & + .ms-ChoiceField-field--image { + &.is-checked { @include ms-bgColor-themeLighter; + + .ms-ChoiceField-labelWrapper .ms-ChoiceField-icon { + display: inline-block; + } } } } -.is-focusVisible .ms-ChoiceField-input:focus + .ms-ChoiceField-field--image:before { - position: absolute; - top: -2px; - right: -2px; - bottom: -2px; - left: -2px; - +.is-focusVisible .ms-ChoiceField.is-inFocus { border: 1px solid $ms-color-neutralPrimary; - - content: ''; } diff --git a/src/components/ChoiceGroup/ChoiceGroup.tsx b/src/components/ChoiceGroup/ChoiceGroup.tsx index 01d0127e96b8ea..84a2cdf8f5b56e 100644 --- a/src/components/ChoiceGroup/ChoiceGroup.tsx +++ b/src/components/ChoiceGroup/ChoiceGroup.tsx @@ -17,7 +17,9 @@ export class ChoiceGroup extends React.Component { + inputElement.addEventListener('focus', this._onFocus.bind(this), false); + inputElement.addEventListener('blur', this._onBlur.bind(this), false); + }); } public componentWillReceiveProps(newProps: IChoiceGroupProps) { @@ -41,6 +52,15 @@ export class ChoiceGroup extends React.Component { + if (inputElement) { + inputElement.removeEventListener('focus', this._onFocus.bind(this)); + inputElement.removeEventListener('blur', this._onBlur.bind(this)); + } + }); + } + public render() { let { label, options, className, required } = this.props; let { keyChecked } = this.state; @@ -52,25 +72,30 @@ export class ChoiceGroup extends React.Component -
+
this._choiceFieldGroup = c } + >
{ this.props.label ? : null }
- { options.map(option => ( + { options.map((option, index) => (
{ this._choiceFieldElements.push(c); return c; } } > this._inputElement = c } + ref={ (c): HTMLInputElement => { this._inputElements.push(c); return c; } } id={ `${this._id}-${option.key}` } className='ms-ChoiceField-input' type='radio' name={ this._id } disabled={ option.isDisabled } checked={ option.key === keyChecked } - aria-checked={ option.key === keyChecked } onChange={ this._handleInputChange.bind(this, option) } aria-describedby={ `${this._descriptionId}-${option.key}` } /> @@ -83,9 +108,21 @@ export class ChoiceGroup extends React.Component { option.imageSrc diff --git a/src/demo/pages/CheckboxPage/examples/Checkbox.Basic.Example.tsx b/src/demo/pages/CheckboxPage/examples/Checkbox.Basic.Example.tsx index e77760d62807b0..eac06c966bf4d8 100644 --- a/src/demo/pages/CheckboxPage/examples/Checkbox.Basic.Example.tsx +++ b/src/demo/pages/CheckboxPage/examples/Checkbox.Basic.Example.tsx @@ -14,6 +14,7 @@ export class CheckboxBasicExample extends React.Component {
+
); diff --git a/src/demo/pages/ChoiceGroupPage/examples/ChoiceGroup.Basic.Example.tsx b/src/demo/pages/ChoiceGroupPage/examples/ChoiceGroup.Basic.Example.tsx index 0108d007bbda00..d0f936e299e31c 100644 --- a/src/demo/pages/ChoiceGroupPage/examples/ChoiceGroup.Basic.Example.tsx +++ b/src/demo/pages/ChoiceGroupPage/examples/ChoiceGroup.Basic.Example.tsx @@ -41,6 +41,12 @@ export class ChoiceGroupBasicExample extends React.Component