Skip to content

Commit

Permalink
feat: add simplified checkbox component for usage in other components (
Browse files Browse the repository at this point in the history
…#2619)

* 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.

* Remove unnecessary template.

* Fix linter error.

* Address PR feedback.

* Trailing comma.
  • Loading branch information
crisbeto authored and andrewseguin committed Jan 27, 2017
1 parent 408c58d commit 3b6cab0
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 26 deletions.
43 changes: 19 additions & 24 deletions src/lib/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
@import '../core/theming/theming';
@import '../core/style/elevation';
@import '../core/style/variables';
@import '../core/style/checkbox-common';
@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 {
Expand Down Expand Up @@ -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;
Expand All @@ -223,7 +218,7 @@ md-checkbox {

[dir='rtl'] & {
margin: {
left: $md-checkbox-item-spacing;
left: $_md-checkbox-item-spacing;
right: auto;
}
}
Expand Down Expand Up @@ -264,14 +259,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);
}
Expand All @@ -280,14 +275,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;
}
}
}
Expand Down Expand Up @@ -419,10 +414,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;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/core/_core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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`.
Expand Down
10 changes: 8 additions & 2 deletions src/lib/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -114,6 +115,9 @@ export * from './compatibility/compatibility';
// Animation
export * from './animation/animation';

// Selection
export * from './selection/index';

// Coercion
export {coerceBooleanProperty} from './coercion/boolean-property';
export {coerceNumberProperty} from './coercion/number-property';
Expand All @@ -131,7 +135,8 @@ export {CompatibilityModule, NoConflictStyleCompatibilityMode} from './compatibi
PortalModule,
OverlayModule,
A11yModule,
MdOptionModule
MdOptionModule,
MdSelectionModule,
],
exports: [
MdLineModule,
Expand All @@ -141,7 +146,8 @@ export {CompatibilityModule, NoConflictStyleCompatibilityMode} from './compatibi
PortalModule,
OverlayModule,
A11yModule,
MdOptionModule
MdOptionModule,
MdSelectionModule,
],
})
export class MdCoreModule {
Expand Down
10 changes: 10 additions & 0 deletions src/lib/core/selection/index.ts
Original file line number Diff line number Diff line change
@@ -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 { }
44 changes: 44 additions & 0 deletions src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
53 changes: 53 additions & 0 deletions src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@import '../../style/checkbox-common';

// 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;

// Used to render the checkmark/mixedmark inside of the box.
&::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;
}
59 changes: 59 additions & 0 deletions src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Component,
ViewEncapsulation,
Input,
ElementRef,
Renderer,
} from '@angular/core';

export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate';

/**
* 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 <md-checkbox> 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({
moduleId: module.id,
encapsulation: ViewEncapsulation.None,
selector: 'md-pseudo-checkbox',
styleUrls: ['pseudo-checkbox.css'],
template: '',
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';
}
}
10 changes: 10 additions & 0 deletions src/lib/core/style/_checkbox-common.scss
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 3b6cab0

Please sign in to comment.