Skip to content

Commit 16c1cf9

Browse files
committed
feat(): add slide-toggle component.
1 parent 9a6ab35 commit 16c1cf9

File tree

11 files changed

+418
-0
lines changed

11 files changed

+418
-0
lines changed

src/components/slide-toggle/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# MdSlideToggle
2+
`MdSlideToggle` is a two-state control, which can be also called `switch`
3+
4+
### Screenshots
5+
![image](https://cloud.githubusercontent.com/assets/4987015/14860895/25cc0dc0-0cab-11e6-9e57-9f6d513444b1.png)
6+
7+
## `<md-slide-toggle>`
8+
### Bound Properties
9+
10+
| Name | Type | Description |
11+
| --- | --- | --- |
12+
| `disabled` | boolean | Disables the `slide-toggle` |
13+
14+
### Examples
15+
A basic slide-toggle would have the following markup.
16+
```html
17+
<md-slide-toggle [(ngModel)]="slideToggleModel">
18+
Default Slide Toggle
19+
</md-slide-toggle>
20+
```
21+
22+
Slide toggle can be also disabled.
23+
```html
24+
<md-slide-toggle disabled>
25+
Disabled Slide Toggle
26+
</md-slide-toggle>
27+
```
28+
29+
## Theming
30+
A slide-toggle is default using the `accent` palette for its styling.
31+
32+
Modifiying the color on a `slide-toggle` can be easily done, by using the following classes.
33+
- `md-primary`
34+
- `md-warn`
35+
36+
Here is an example markup, which uses the primary color.
37+
```html
38+
<md-slide-toggle class="md-primary">
39+
Primary Slide Toggle
40+
</md-slide-toggle>
41+
```
42+
43+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div class="md-container">
2+
<div class="md-bar"></div>
3+
<div class="md-thumb-container">
4+
<div class="md-thumb"></div>
5+
</div>
6+
</div>
7+
<div class="md-label">
8+
<ng-content></ng-content>
9+
</div>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
@import "../../core/style/variables";
2+
@import "../../core/style/mixins";
3+
@import "../../core/style/elevation";
4+
5+
//TODO(): remove the default theme.
6+
@import "../../core/style/default-theme";
7+
8+
$md-slide-toggle-width: 36px !default;
9+
$md-slide-toggle-height: 24px !default;
10+
$md-slide-toggle-bar-height: 14px !default;
11+
$md-slide-toggle-thumb-size: 20px !default;
12+
$md-slide-toggle-margin: 16px !default;
13+
14+
:host {
15+
display: flex;
16+
height: $md-slide-toggle-height;
17+
18+
margin: $md-slide-toggle-margin 0;
19+
line-height: $md-slide-toggle-height;
20+
21+
white-space: nowrap;
22+
cursor: pointer;
23+
user-select: none;
24+
25+
outline: none;
26+
27+
&[disabled] {
28+
cursor: default;
29+
30+
.md-container {
31+
cursor: default;
32+
}
33+
}
34+
35+
.md-container {
36+
cursor: grab;
37+
width: $md-slide-toggle-width;
38+
height: $md-slide-toggle-height;
39+
40+
position: relative;
41+
user-select: none;
42+
43+
margin-right: 8px;
44+
}
45+
46+
.md-thumb-container {
47+
position: absolute;
48+
top: $md-slide-toggle-height / 2 - $md-slide-toggle-thumb-size / 2;
49+
left: 0;
50+
z-index: 1;
51+
52+
width: $md-slide-toggle-width - $md-slide-toggle-thumb-size;
53+
54+
transform: translate3d(0, 0, 0);
55+
56+
transition: $swift-linear;
57+
transition-property: transform;
58+
59+
.md-thumb {
60+
position: absolute;
61+
margin: 0;
62+
left: 0;
63+
top: 0;
64+
65+
height: $md-slide-toggle-thumb-size;
66+
width: $md-slide-toggle-thumb-size;
67+
border-radius: 50%;
68+
69+
background-color: md-color($md-background, background);
70+
@include md-elevation(1);
71+
}
72+
}
73+
74+
&.md-checked .md-thumb-container {
75+
transform: translate3d(100%, 0, 0);
76+
}
77+
78+
&.md-dragging .md-container {
79+
cursor: grabbing;
80+
}
81+
82+
.md-bar {
83+
position: absolute;
84+
left: 1px;
85+
top: $md-slide-toggle-height / 2 - $md-slide-toggle-bar-height / 2;
86+
87+
width: $md-slide-toggle-width - 2px;
88+
height: $md-slide-toggle-bar-height;
89+
90+
background-color: md-color($md-grey, 500);
91+
92+
border-radius: 8px;
93+
}
94+
95+
.md-bar,
96+
.md-thumb {
97+
transition: $swift-linear;
98+
transition-property: background-color;
99+
transition-delay: 0.05s;
100+
}
101+
102+
@mixin md-switch-checked($palette) {
103+
.md-thumb {
104+
background-color: md-color($palette);
105+
}
106+
107+
.md-bar {
108+
background-color: md-color($palette, 0.5);
109+
}
110+
}
111+
112+
&.md-checked {
113+
@include md-switch-checked($md-accent);
114+
115+
&.md-primary {
116+
@include md-switch-checked($md-primary);
117+
}
118+
119+
&.md-warn {
120+
@include md-switch-checked($md-warn);
121+
}
122+
}
123+
124+
&[disabled] {
125+
.md-thumb {
126+
background-color: md-color($md-grey, 400);
127+
}
128+
.md-bar {
129+
background-color: md-color($md-foreground, divider);
130+
}
131+
}
132+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {Component} from 'angular2/core';
2+
import {
3+
it,
4+
describe,
5+
expect,
6+
beforeEach,
7+
inject,
8+
TestComponentBuilder
9+
} from 'angular2/testing';
10+
import {By} from 'angular2/platform/browser';
11+
import {MdSlideToggle} from './slide-toggle';
12+
13+
export function main() {
14+
describe('MdSlideToggle', () => {
15+
let builder: TestComponentBuilder;
16+
17+
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
18+
builder = tcb;
19+
}));
20+
21+
it('should update the model correctly', (done: () => void) => {
22+
return builder.createAsync(TestApp).then((fixture) => {
23+
let testComponent = fixture.debugElement.componentInstance;
24+
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;
25+
26+
fixture.detectChanges();
27+
28+
expect(slideToggleEl.classList.contains('md-checked')).toBe(false);
29+
30+
testComponent.slideModel = true;
31+
fixture.detectChanges();
32+
33+
expect(slideToggleEl.classList.contains('md-checked')).toBe(true);
34+
35+
done();
36+
});
37+
});
38+
39+
it('should correctly update aria-disabled', (done: () => void) => {
40+
return builder.createAsync(TestApp).then((fixture) => {
41+
let testComponent = fixture.debugElement.componentInstance;
42+
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;
43+
44+
fixture.detectChanges();
45+
46+
expect(slideToggleEl.getAttribute('aria-disabled')).toBe('false');
47+
48+
testComponent.isDisabled = true;
49+
fixture.detectChanges();
50+
51+
expect(slideToggleEl.getAttribute('aria-disabled')).toBe('true');
52+
53+
done();
54+
});
55+
});
56+
57+
it('should correctly update aria-checked', (done: () => void) => {
58+
return builder.createAsync(TestApp).then((fixture) => {
59+
let testComponent = fixture.debugElement.componentInstance;
60+
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;
61+
62+
fixture.detectChanges();
63+
64+
expect(slideToggleEl.getAttribute('aria-checked')).toBe('false');
65+
66+
testComponent.slideModel = true;
67+
fixture.detectChanges();
68+
69+
expect(slideToggleEl.getAttribute('aria-checked')).toBe('true');
70+
71+
done();
72+
});
73+
});
74+
75+
it('should set the toggle to checked on click', (done: () => void) => {
76+
return builder.createAsync(TestApp).then((fixture) => {
77+
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;
78+
79+
slideToggleEl.click();
80+
81+
expect(slideToggleEl.classList.contains('md-checked')).toBe(true);
82+
83+
done();
84+
});
85+
});
86+
87+
});
88+
}
89+
90+
@Component({
91+
selector: 'test-app',
92+
template: `
93+
<md-slide-toggle [(ngModel)]="slideModel" [disabled]="isDisabled">
94+
<span>Test Slide Toggle</span>
95+
</md-slide-toggle>
96+
`,
97+
directives: [MdSlideToggle]
98+
})
99+
class TestApp {
100+
isDisabled: boolean = false;
101+
slideModel: boolean = false;
102+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
Component,
3+
ElementRef,
4+
OnInit
5+
} from 'angular2/core';
6+
import {ControlValueAccessor} from 'angular2/common';
7+
import {NgControl} from 'angular2/common';
8+
import {Optional} from 'angular2/core';
9+
import {Renderer} from 'angular2/core';
10+
11+
@Component({
12+
selector: 'md-slide-toggle',
13+
inputs: ['disabled'],
14+
host: {
15+
'[attr.aria-disabled]': 'isAriaDisabled()',
16+
'(click)': 'onClick()'
17+
},
18+
templateUrl: './components/slide-toggle/slide-toggle.html',
19+
styleUrls: ['./components/slide-toggle/slide-toggle.css']
20+
})
21+
export class MdSlideToggle implements OnInit, ControlValueAccessor {
22+
23+
private nativeElement: HTMLElement;
24+
private switchContainer: HTMLElement;
25+
private thumbContainer: HTMLElement;
26+
27+
private onChange = (_: any) => {};
28+
private onTouched = () => {};
29+
30+
private _checked: any;
31+
private _disabled: boolean;
32+
33+
constructor(elementRef: ElementRef,
34+
private renderer: Renderer,
35+
@Optional() ngControl: NgControl) {
36+
37+
this.nativeElement = elementRef.nativeElement;
38+
39+
if (ngControl) {
40+
ngControl.valueAccessor = this;
41+
}
42+
}
43+
44+
ngOnInit() {
45+
this.switchContainer = <HTMLElement> this.nativeElement.querySelector('.md-container');
46+
this.thumbContainer = <HTMLElement> this.nativeElement.querySelector('.md-thumb-container');
47+
}
48+
49+
onClick() {
50+
if (!this.disabled) {
51+
this.checked = !this.checked;
52+
this.onTouched();
53+
}
54+
}
55+
56+
writeValue(value: any): void {
57+
this.checked = value;
58+
}
59+
60+
registerOnChange(fn: any): void {
61+
this.onChange = fn;
62+
}
63+
64+
registerOnTouched(fn: any): void {
65+
this.onTouched = fn;
66+
}
67+
68+
isAriaDisabled(): string {
69+
return this.disabled ? 'true' : 'false';
70+
}
71+
72+
get disabled(): string | boolean {
73+
return this._disabled;
74+
}
75+
76+
set disabled(value: string | boolean) {
77+
this._disabled = value === '' || !!value;
78+
79+
this.renderer
80+
.setElementAttribute(this.nativeElement, 'disabled', this._disabled ? 'true' : null);
81+
}
82+
83+
get checked() {
84+
return !!this._checked;
85+
}
86+
87+
set checked(value) {
88+
this._checked = !!value;
89+
90+
this.onTouched();
91+
92+
// Update the ngModel value accessor.
93+
this.onChange(this._checked);
94+
95+
this.renderer
96+
.setElementAttribute(this.nativeElement, 'aria-checked', this._checked);
97+
98+
this.nativeElement.classList.toggle('md-checked', this.checked);
99+
}
100+
101+
}

0 commit comments

Comments
 (0)