Skip to content

Commit

Permalink
feat(): add slide-toggle component.
Browse files Browse the repository at this point in the history
  • Loading branch information
devversion committed May 12, 2016
1 parent dfe683b commit c7c45d9
Show file tree
Hide file tree
Showing 11 changed files with 470 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/components/slide-toggle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# MdSlideToggle
`MdSlideToggle` is a two-state control, which can be also called `switch`

### Screenshots
![image](https://cloud.githubusercontent.com/assets/4987015/14860895/25cc0dc0-0cab-11e6-9e57-9f6d513444b1.png)

## `<md-slide-toggle>`
### Bound Properties

| Name | Type | Description |
| --- | --- | --- |
| `disabled` | boolean | Disables the `slide-toggle` |
| `color` | `"primary" | "accent" | "warn"` | The color palette of `the slide-toggle` |

### Examples
A basic slide-toggle would have the following markup.
```html
<md-slide-toggle [(ngModel)]="slideToggleModel">
Default Slide Toggle
</md-slide-toggle>
```

Slide toggle can be also disabled.
```html
<md-slide-toggle disabled>
Disabled Slide Toggle
</md-slide-toggle>
```

## Theming
A slide-toggle is default using the `accent` palette for its styling.

Modifiying the color on a `slide-toggle` can be easily done, by using the following classes.
- `md-primary`
- `md-warn`

Here is an example markup, which uses the primary color.
```html
<md-slide-toggle class="md-primary">
Primary Slide Toggle
</md-slide-toggle>
```


9 changes: 9 additions & 0 deletions src/components/slide-toggle/slide-toggle.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="md-slide-toggle-container">
<div class="md-slide-toggle-bar"></div>
<div class="md-slide-toggle-thumb-container">
<div class="md-slide-toggle-thumb"></div>
</div>
</div>
<div class="md-slide-toggle-label">
<ng-content></ng-content>
</div>
133 changes: 133 additions & 0 deletions src/components/slide-toggle/slide-toggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
@import "../../core/style/variables";
@import "../../core/style/mixins";
@import "../../core/style/elevation";

//TODO(): remove the default theme.
@import "../../core/style/default-theme";

$md-slide-toggle-width: 36px !default;
$md-slide-toggle-height: 24px !default;
$md-slide-toggle-bar-height: 14px !default;
$md-slide-toggle-thumb-size: 20px !default;
$md-slide-toggle-margin: 16px !default;

@mixin md-switch-checked($palette) {
.md-slide-toggle-thumb {
background-color: md-color($palette);
}

.md-slide-toggle-bar {
background-color: md-color($palette, 0.5);
}
}

:host {
display: flex;
height: $md-slide-toggle-height;

margin: $md-slide-toggle-margin 0;
line-height: $md-slide-toggle-height;

white-space: nowrap;
cursor: pointer;
user-select: none;

outline: none;

&.md-checked {
@include md-switch-checked($md-accent);

&.md-primary {
@include md-switch-checked($md-primary);
}

&.md-warn {
@include md-switch-checked($md-warn);
}

.md-slide-toggle-thumb-container {
transform: translate3d(100%, 0, 0);
}
}

&[disabled] {
cursor: default;

.md-slide-toggle-container {
cursor: default;
}

.md-slide-toggle-thumb {
background-color: md-color($md-grey, 400);
}
.md-slide-toggle-bar {
background-color: md-color($md-foreground, divider);
}
}
}

// Root container for the composition of the slide-toggle / switch indicator.
.md-slide-toggle-container {
cursor: grab;
width: $md-slide-toggle-width;
height: $md-slide-toggle-height;

position: relative;
user-select: none;

margin-right: 8px;
}

// The thumb container is responsible for the dragging functionality.
// It moves around and holds the actual circle as a thumb.
.md-slide-toggle-thumb-container {
position: absolute;
top: $md-slide-toggle-height / 2 - $md-slide-toggle-thumb-size / 2;
left: 0;
z-index: 1;

width: $md-slide-toggle-width - $md-slide-toggle-thumb-size;

transform: translate3d(0, 0, 0);

transition: $swift-linear;
transition-property: transform;
}

// The thumb will be elevated from the slide-toggle bar.
// Also the thumb is bound to its parent thumb-container, which manages the movement of the thumb.
.md-slide-toggle-thumb {
position: absolute;
margin: 0;
left: 0;
top: 0;

height: $md-slide-toggle-thumb-size;
width: $md-slide-toggle-thumb-size;
border-radius: 50%;

background-color: md-color($md-background, background);
@include md-elevation(1);
}

// Horizontal bar for the slide-toggle.
// The slide-toggle bar is shown behind the thumb container.
.md-slide-toggle-bar {
position: absolute;
left: 1px;
top: $md-slide-toggle-height / 2 - $md-slide-toggle-bar-height / 2;

width: $md-slide-toggle-width - 2px;
height: $md-slide-toggle-bar-height;

background-color: md-color($md-grey, 500);

border-radius: 8px;
}

.md-slide-toggle-bar,
.md-slide-toggle-thumb {
transition: $swift-linear;
transition-property: background-color;
transition-delay: 0.05s;
}
136 changes: 136 additions & 0 deletions src/components/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
it,
describe,
expect,
beforeEach,
inject,
async
} from '@angular/core/testing';
import {TestComponentBuilder} from '@angular/compiler/testing';
import {By} from '@angular/platform-browser';
import {Component} from '@angular/core';
import {MdSlideToggle} from './slide-toggle';

export function main() {
describe('MdSlideToggle', () => {
let builder: TestComponentBuilder;

beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));

it('should update the model correctly', async(() => {
return builder.createAsync(SlideToggleTestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

fixture.detectChanges();

expect(slideToggleEl.classList).not.toContain('md-checked');

testComponent.slideModel = true;
fixture.detectChanges();

expect(slideToggleEl.classList).toContain('md-checked');
});
}));

it('should apply class based on color attribute', async(() => {
return builder.createAsync(SlideToggleTestApp).then(fixture => {
let testComponent = fixture.debugElement.componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

testComponent.slideColor = 'primary';
fixture.detectChanges();

expect(slideToggleEl.classList).toContain('md-primary');

testComponent.slideColor = 'accent';
fixture.detectChanges();

expect(slideToggleEl.classList).toContain('md-accent');
});
}));

it('should correctly update the disabled attribute', async(() => {
return builder.createAsync(SlideToggleTestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

fixture.detectChanges();

expect(slideToggleEl.getAttribute('disabled')).toBeFalsy();

testComponent.isDisabled = true;
fixture.detectChanges();

expect(slideToggleEl.getAttribute('disabled')).toBeTruthy();
});
}));

it('should correctly update aria-disabled', async(() => {
return builder.createAsync(SlideToggleTestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

fixture.detectChanges();

expect(slideToggleEl.getAttribute('aria-disabled')).toBe('false');

testComponent.isDisabled = true;
fixture.detectChanges();

expect(slideToggleEl.getAttribute('aria-disabled')).toBe('true');
});
}));

it('should correctly update aria-checked', async(() => {
return builder.createAsync(SlideToggleTestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

fixture.detectChanges();

expect(slideToggleEl.getAttribute('aria-checked')).toBe('false');

testComponent.slideModel = true;
fixture.detectChanges();

expect(slideToggleEl.getAttribute('aria-checked')).toBe('true');
});
}));

it('should set the toggle to checked on click', async(() => {
return builder.createAsync(SlideToggleTestApp).then((fixture) => {
let slideToggle = fixture.debugElement.query(By.css('md-slide-toggle')).componentInstance;
let slideToggleEl = fixture.debugElement.query(By.css('md-slide-toggle')).nativeElement;

fixture.detectChanges();

expect(slideToggle.checked).toBe(false);
expect(slideToggleEl.classList).not.toContain('md-checked');

slideToggleEl.click();
fixture.detectChanges();

expect(slideToggleEl.classList).toContain('md-checked');
expect(slideToggle.checked).toBe(true);
});
}));

});
}

@Component({
selector: 'slide-toggle-test-app',
template: `
<md-slide-toggle [(ngModel)]="slideModel" [disabled]="isDisabled" [color]="slideColor">
<span>Test Slide Toggle</span>
</md-slide-toggle>
`,
directives: [MdSlideToggle]
})
class SlideToggleTestApp {
isDisabled: boolean = false;
slideModel: boolean = false;
}
Loading

0 comments on commit c7c45d9

Please sign in to comment.