From dd03ff22c2d1384d58a78c576c8d6d2d2ea0e59d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 12 Jan 2017 22:29:18 +0100 Subject: [PATCH 1/5] feat: add simplified checkbox component for usage in other components Adds the `md-pseudo-checkbox`, which is a simplified version of `md-checkbox`, that doesn't have the expensive SVG animations or the form control logic. This will be useful for multiple selection in `md-select`, as well as other components in the future. Relates to #2412. --- src/lib/checkbox/checkbox.scss | 43 +++++++-------- src/lib/core/_core.scss | 2 + src/lib/core/core.ts | 10 +++- src/lib/core/selection/index.ts | 10 ++++ .../_pseudo-checkbox-theme.scss | 44 +++++++++++++++ .../pseudo-checkbox/pseudo-checkbox.html | 0 .../pseudo-checkbox/pseudo-checkbox.scss | 52 ++++++++++++++++++ .../pseudo-checkbox/pseudo-checkbox.ts | 53 +++++++++++++++++++ src/lib/core/style/_checkbox-variables.scss | 10 ++++ 9 files changed, 198 insertions(+), 26 deletions(-) create mode 100644 src/lib/core/selection/index.ts create mode 100644 src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss create mode 100644 src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html create mode 100644 src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss create mode 100644 src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts create mode 100644 src/lib/core/style/_checkbox-variables.scss diff --git a/src/lib/checkbox/checkbox.scss b/src/lib/checkbox/checkbox.scss index 1e0a0d040002..0a440777443f 100644 --- a/src/lib/checkbox/checkbox.scss +++ b/src/lib/checkbox/checkbox.scss @@ -1,26 +1,21 @@ @import '../core/theming/theming'; @import '../core/style/elevation'; -@import '../core/style/variables'; +@import '../core/style/checkbox-variables'; @import '../core/ripple/ripple'; - -// The width/height of the checkbox element. -$md-checkbox-size: $md-toggle-size !default; -// The width of the line used to draw the checkmark / mixedmark. -$md-checkbox-mark-stroke-size: 2/15 * $md-checkbox-size !default; -// The width of the checkbox border shown when the checkbox is unchecked. -$md-checkbox-border-width: 2px; -// The base duration used for the majority of transitions for the checkbox. -$md-checkbox-transition-duration: 90ms; -// The amount of spacing between the checkbox and its label. -$md-checkbox-item-spacing: $md-toggle-padding; - // Manual calculation done on SVG $_md-checkbox-mark-path-length: 22.910259; $_md-checkbox-indeterminate-checked-easing-function: cubic-bezier(0.14, 0, 0, 1); // The ripple size of the checkbox -$md-checkbox-ripple-size: 15px; +$_md-checkbox-ripple-size: 15px; + +// The amount of spacing between the checkbox and its label. +$_md-checkbox-item-spacing: $md-toggle-padding; + +// The width of the line used to draw the checkmark / mixedmark. +$_md-checkbox-mark-stroke-size: 2 / 15 * $md-checkbox-size !default; + // Fades in the background of the checkbox when it goes from unchecked -> {checked,indeterminate}. @keyframes md-checkbox-fade-in-background { @@ -213,7 +208,7 @@ md-checkbox { height: $md-checkbox-size; line-height: 0; margin: auto; - margin-right: $md-checkbox-item-spacing; + margin-right: $_md-checkbox-item-spacing; order: 0; position: relative; vertical-align: middle; @@ -223,7 +218,7 @@ md-checkbox { [dir='rtl'] & { margin: { - left: $md-checkbox-item-spacing; + left: $_md-checkbox-item-spacing; right: auto; } } @@ -266,14 +261,14 @@ md-checkbox { stroke: { dashoffset: $_md-checkbox-mark-path-length; dasharray: $_md-checkbox-mark-path-length; - width: $md-checkbox-mark-stroke-size; + width: $_md-checkbox-mark-stroke-size; } } .md-checkbox-mixedmark { @extend %md-checkbox-mark; - height: floor($md-checkbox-mark-stroke-size); + height: floor($_md-checkbox-mark-stroke-size); opacity: 0; transform: scaleX(0) rotate(0deg); } @@ -282,14 +277,14 @@ md-checkbox { .md-checkbox-inner-container { order: 1; margin: { - left: $md-checkbox-item-spacing; + left: $_md-checkbox-item-spacing; right: auto; } [dir='rtl'] & { margin: { left: auto; - right: $md-checkbox-item-spacing; + right: $_md-checkbox-item-spacing; } } } @@ -421,10 +416,10 @@ md-checkbox { .md-checkbox-ripple { position: absolute; - left: -$md-checkbox-ripple-size; - top: -$md-checkbox-ripple-size; - right: -$md-checkbox-ripple-size; - bottom: -$md-checkbox-ripple-size; + left: -$_md-checkbox-ripple-size; + top: -$_md-checkbox-ripple-size; + right: -$_md-checkbox-ripple-size; + bottom: -$_md-checkbox-ripple-size; border-radius: 50%; z-index: 1; pointer-events: none; diff --git a/src/lib/core/_core.scss b/src/lib/core/_core.scss index cc4c22ed2528..c8c97c5ed129 100644 --- a/src/lib/core/_core.scss +++ b/src/lib/core/_core.scss @@ -5,6 +5,7 @@ @import 'ripple/ripple'; @import 'option/option'; @import 'option/option-theme'; +@import 'selection/pseudo-checkbox/pseudo-checkbox-theme'; // Mixin that renders all of the core styles that are not theme-dependent. @mixin md-core() { @@ -27,6 +28,7 @@ @mixin md-core-theme($theme) { @include md-ripple-theme($theme); @include md-option-theme($theme); + @include md-pseudo-checkbox-theme($theme); // Wrapper element that provides the theme background when the // user's content isn't inside of a `md-sidenav-container`. diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 52e670be9632..f3ec14684841 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -7,6 +7,7 @@ import {MdRippleModule} from './ripple/ripple'; import {PortalModule} from './portal/portal-directives'; import {OverlayModule} from './overlay/overlay-directives'; import {A11yModule} from './a11y/index'; +import {MdSelectionModule} from './selection/index'; // RTL @@ -114,6 +115,9 @@ export * from './compatibility/default-mode'; // Animation export * from './animation/animation'; +// Selection +export * from './selection/index'; + // Coercion export {coerceBooleanProperty} from './coercion/boolean-property'; export {coerceNumberProperty} from './coercion/number-property'; @@ -132,7 +136,8 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode PortalModule, OverlayModule, A11yModule, - MdOptionModule + MdOptionModule, + MdSelectionModule ], exports: [ MdLineModule, @@ -142,7 +147,8 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode PortalModule, OverlayModule, A11yModule, - MdOptionModule + MdOptionModule, + MdSelectionModule ], }) export class MdCoreModule { diff --git a/src/lib/core/selection/index.ts b/src/lib/core/selection/index.ts new file mode 100644 index 000000000000..4ebd17960d72 --- /dev/null +++ b/src/lib/core/selection/index.ts @@ -0,0 +1,10 @@ +import {NgModule} from '@angular/core'; +import {MdPseudoCheckbox} from './pseudo-checkbox/pseudo-checkbox'; + +export * from './pseudo-checkbox/pseudo-checkbox'; + +@NgModule({ + exports: [MdPseudoCheckbox], + declarations: [MdPseudoCheckbox] +}) +export class MdSelectionModule { } diff --git a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss new file mode 100644 index 000000000000..f806bbed969e --- /dev/null +++ b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss @@ -0,0 +1,44 @@ +@import '../../theming/theming'; + + +@mixin md-pseudo-checkbox-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + + // The color of the checkbox's checkmark / mixedmark. + $checkbox-mark-color: md-color($background, background); + + // NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors, + // this does not work well with elements layered on top of one another. To get around this we + // blend the colors together based on the base color and the theme background. + $white-30pct-opacity-on-dark: #686868; + $black-26pct-opacity-on-light: #b0b0b0; + $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + + md-pseudo-checkbox::after { + color: $checkbox-mark-color; + } + + .md-pseudo-checkbox-checked, .md-pseudo-checkbox-indeterminate { + border: none; + + &.md-primary { + background: md-color($primary, 500); + } + + &.md-accent { + background: md-color($accent, 500); + } + + &.md-warn { + background: md-color($warn, 500); + } + + &.md-pseudo-checkbox-disabled { + background: $disabled-color; + } + } +} diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss new file mode 100644 index 000000000000..29f847ad5347 --- /dev/null +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss @@ -0,0 +1,52 @@ +@import '../../style/checkbox-variables'; + +// Padding inside of a pseudo checkbox. +$_md-pseudo-checkbox-padding: $md-checkbox-border-width * 2; + +// Size of the checkmark in a pseudo checkbox. +$_md-pseudo-checkmark-size: $md-checkbox-size - (2 * $_md-pseudo-checkbox-padding); + + +md-pseudo-checkbox { + width: $md-checkbox-size; + height: $md-checkbox-size; + border: $md-checkbox-border-width solid; + border-radius: 2px; + cursor: pointer; + display: inline-block; + vertical-align: middle; + box-sizing: border-box; + position: relative; + transition: + border-color $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function, + background-color $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function; + + &::after { + position: absolute; + opacity: 0; + content: ""; + border-bottom: $md-checkbox-border-width solid currentColor; + transition: opacity $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function; + } +} + +.md-pseudo-checkbox-disabled { + cursor: default; +} + +.md-pseudo-checkbox-indeterminate::after { + top: ($md-checkbox-size - $md-checkbox-border-width) / 2; + left: $md-checkbox-border-width; + width: $md-checkbox-size - ($md-checkbox-border-width * 2); + opacity: 1; +} + +.md-pseudo-checkbox-checked::after { + top: ($md-checkbox-size / 2) - ($_md-pseudo-checkmark-size / 4) - ($md-checkbox-size / 10); + left: $_md-pseudo-checkbox-padding - $md-checkbox-border-width / 2; + width: $_md-pseudo-checkmark-size; + height: ($_md-pseudo-checkmark-size - $md-checkbox-border-width) / 2; + border-left: $md-checkbox-border-width solid currentColor; + transform: rotate(-45deg); + opacity: 1; +} diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts new file mode 100644 index 000000000000..80047187df7e --- /dev/null +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -0,0 +1,53 @@ +import { + Component, + ViewEncapsulation, + Input, + ElementRef, + Renderer, +} from '@angular/core'; + +export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; + +/** + * Simplified checkbox without any of the underlying form control logic + * and fancy animations. To be used when composing other components. + * @docs-private + */ +@Component({ + moduleId: module.id, + encapsulation: ViewEncapsulation.None, + selector: 'md-pseudo-checkbox', + styleUrls: ['pseudo-checkbox.css'], + templateUrl: 'pseudo-checkbox.html', + host: { + '[class.md-pseudo-checkbox-indeterminate]': 'state === "indeterminate"', + '[class.md-pseudo-checkbox-checked]': 'state === "checked"', + '[class.md-pseudo-checkbox-disabled]': 'disabled', + }, +}) +export class MdPseudoCheckbox { + /** Display state of the checkbox. */ + @Input() state: MdPseudoCheckboxState = 'unchecked'; + + /** Whether the checkbox is disabled. */ + @Input() disabled: boolean = false; + + /** Color of the checkbox. */ + @Input() + get color(): string { return this._color; }; + set color(value: string) { + if (value) { + let nativeElement = this._elementRef.nativeElement; + + this._renderer.setElementClass(nativeElement, `md-${this.color}`, false); + this._renderer.setElementClass(nativeElement, `md-${value}`, true); + this._color = value; + } + } + + private _color: string; + + constructor(private _elementRef: ElementRef, private _renderer: Renderer) { + this.color = 'accent'; + } +} diff --git a/src/lib/core/style/_checkbox-variables.scss b/src/lib/core/style/_checkbox-variables.scss new file mode 100644 index 000000000000..b63f4a5b3eba --- /dev/null +++ b/src/lib/core/style/_checkbox-variables.scss @@ -0,0 +1,10 @@ +@import './variables'; + +// The width/height of the checkbox element. +$md-checkbox-size: $md-toggle-size !default; + +// The width of the checkbox border shown when the checkbox is unchecked. +$md-checkbox-border-width: 2px; + +// The base duration used for the majority of transitions for the checkbox. +$md-checkbox-transition-duration: 90ms; From 0aa074ce11f0490399b35eab1da1923a0f63fff4 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 12 Jan 2017 22:37:15 +0100 Subject: [PATCH 2/5] Remove unnecessary template. --- src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html | 0 src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts index 80047187df7e..3b2c23e2d377 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -18,7 +18,7 @@ export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; encapsulation: ViewEncapsulation.None, selector: 'md-pseudo-checkbox', styleUrls: ['pseudo-checkbox.css'], - templateUrl: 'pseudo-checkbox.html', + template: '', host: { '[class.md-pseudo-checkbox-indeterminate]': 'state === "indeterminate"', '[class.md-pseudo-checkbox-checked]': 'state === "checked"', From d88365dd1058ce706f573cc4b2331d281b732da3 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 12 Jan 2017 22:52:35 +0100 Subject: [PATCH 3/5] Fix linter error. --- src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss index 29f847ad5347..1ef15662fa65 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss @@ -24,7 +24,7 @@ md-pseudo-checkbox { &::after { position: absolute; opacity: 0; - content: ""; + content: ''; border-bottom: $md-checkbox-border-width solid currentColor; transition: opacity $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function; } From 0c7c67e24506c1a97008b68ed4cdfd5654200012 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Fri, 20 Jan 2017 19:26:34 +0100 Subject: [PATCH 4/5] Address PR feedback. --- src/lib/checkbox/checkbox.scss | 2 +- src/lib/core/core.ts | 2 +- .../selection/pseudo-checkbox/pseudo-checkbox.scss | 3 ++- .../core/selection/pseudo-checkbox/pseudo-checkbox.ts | 10 ++++++++-- ...{_checkbox-variables.scss => _checkbox-common.scss} | 0 5 files changed, 12 insertions(+), 5 deletions(-) rename src/lib/core/style/{_checkbox-variables.scss => _checkbox-common.scss} (100%) diff --git a/src/lib/checkbox/checkbox.scss b/src/lib/checkbox/checkbox.scss index 0a440777443f..2bdd06862260 100644 --- a/src/lib/checkbox/checkbox.scss +++ b/src/lib/checkbox/checkbox.scss @@ -1,6 +1,6 @@ @import '../core/theming/theming'; @import '../core/style/elevation'; -@import '../core/style/checkbox-variables'; +@import '../core/style/checkbox-common'; @import '../core/ripple/ripple'; // Manual calculation done on SVG diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index f3ec14684841..78bb26611d3e 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -148,7 +148,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode OverlayModule, A11yModule, MdOptionModule, - MdSelectionModule + MdSelectionModule, ], }) export class MdCoreModule { diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss index 1ef15662fa65..f82b1bb1a7c9 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss @@ -1,4 +1,4 @@ -@import '../../style/checkbox-variables'; +@import '../../style/checkbox-common'; // Padding inside of a pseudo checkbox. $_md-pseudo-checkbox-padding: $md-checkbox-border-width * 2; @@ -21,6 +21,7 @@ md-pseudo-checkbox { border-color $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function, background-color $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function; + // Used to render the checkmark/mixedmark inside of the box. &::after { position: absolute; opacity: 0; diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts index 3b2c23e2d377..eab9b353bfe6 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -9,8 +9,14 @@ import { export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; /** - * Simplified checkbox without any of the underlying form control logic - * and fancy animations. To be used when composing other components. + * Component that shows a simplified checkbox without including any kind of "real" checkbox. + * Meant to be used when the checkbox is purely decorative and a large number of them will be + * included, such as for the options in a multi-select. Uses no SVGs or complex animations. + * + * Note that this component will be completely invisible to screen-reader users. This is *not* + * interchangeable with and should *not* be used if the user would directly interact + * with the checkbox. The pseudo-checkbox should only be used as an implementation detail of + * more complex components that appropriately handle selected / checked state. * @docs-private */ @Component({ diff --git a/src/lib/core/style/_checkbox-variables.scss b/src/lib/core/style/_checkbox-common.scss similarity index 100% rename from src/lib/core/style/_checkbox-variables.scss rename to src/lib/core/style/_checkbox-common.scss From 168b64f544474659be46a2ac7ad81bf2634dfa0b Mon Sep 17 00:00:00 2001 From: crisbeto Date: Fri, 20 Jan 2017 19:27:41 +0100 Subject: [PATCH 5/5] Trailing comma. --- src/lib/core/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 78bb26611d3e..5e3d145ae470 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -137,7 +137,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode OverlayModule, A11yModule, MdOptionModule, - MdSelectionModule + MdSelectionModule, ], exports: [ MdLineModule,