diff --git a/config/build.config.js b/config/build.config.js
index 8c404d2adb6..53d86cf3195 100644
--- a/config/build.config.js
+++ b/config/build.config.js
@@ -85,6 +85,7 @@ module.exports = {
'src/components/radioButton/radioButton.js',
'src/components/sidenav/sidenav.js',
'src/components/slider/slider.js',
+ 'src/components/switch/switch.js',
'src/components/tabs/tabs.js',
'src/components/tabs/util/*.js',
'src/components/toast/toast.js',
diff --git a/src/base/utils.js b/src/base/utils.js
index feccb5a8594..fb90f0242e5 100644
--- a/src/base/utils.js
+++ b/src/base/utils.js
@@ -38,7 +38,6 @@ var Util = {
/**
* Converts snake_case to camelCase.
- * Also there is special case for Moz prefix starting with upper case letter.
* @param name Name to normalize
*/
camelCase: function camelCase(name) {
diff --git a/src/components/animate/_effects.scss b/src/components/animate/_effects.scss
index 8b879c20d58..95b60853bb9 100644
--- a/src/components/animate/_effects.scss
+++ b/src/components/animate/_effects.scss
@@ -5,8 +5,6 @@ material-ink-bar {
margin-top: -2px;
}
-
-
canvas.material-ink-ripple {
pointer-events: none;
position: absolute;
diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js
index cdbb59ea40d..a011b3f8028 100644
--- a/src/components/checkbox/checkbox.js
+++ b/src/components/checkbox/checkbox.js
@@ -59,7 +59,7 @@ function materialCheckboxDirective(inputDirectives, $expectAria) {
require: '?ngModel',
template:
'
' +
'',
@@ -109,7 +109,7 @@ function materialCheckboxDirective(inputDirectives, $expectAria) {
}
}
function listener(ev) {
- if ( Util.isDisabled(element) ) return;
+ if (element[0].hasAttribute('disabled')) return;
scope.$apply(function() {
checked = !checked;
diff --git a/src/components/radioButton/_radio-button.scss b/src/components/radioButton/_radio-button.scss
index 612793efe45..14d28b588a3 100644
--- a/src/components/radioButton/_radio-button.scss
+++ b/src/components/radioButton/_radio-button.scss
@@ -1,5 +1,6 @@
-material-radio-button {
+material-radio-button,
+.material-switch-thumb { // Used in switch
display: block;
margin: 15px;
white-space: nowrap;
diff --git a/src/components/radioButton/radioButton.js b/src/components/radioButton/radioButton.js
index 595891f872a..dc6e69805b8 100644
--- a/src/components/radioButton/radioButton.js
+++ b/src/components/radioButton/radioButton.js
@@ -235,7 +235,7 @@ function materialRadioButtonDirective($expectAria) {
$expectAria(element, Constant.ARIA.PROPERTY.LABEL, element.text());
function listener(ev) {
- if ( Util.isDisabled(element) ) return;
+ if (element[0].hasAttribute('disabled')) return;
scope.$apply(function() {
rgCtrl.setViewValue(attr.value, ev && ev.type);
diff --git a/src/components/switch/_switch.scss b/src/components/switch/_switch.scss
new file mode 100644
index 00000000000..15ac8f57551
--- /dev/null
+++ b/src/components/switch/_switch.scss
@@ -0,0 +1,32 @@
+material-switch {
+ position: relative;
+ width: $switch-width;
+ height: $baseline-grid * 3;
+
+ @include flex-display();
+ @include flex-justify-content(center);
+ @include flex-align-items(center);
+
+ .material-switch-bar {
+ width: 32px;
+ height: 1px;
+ background-color: #5a5a5a;
+ pointer-events: none;
+ }
+
+ /* check _radio-button.scss */
+ .material-switch-thumb {
+ position: absolute;
+ margin: 0;
+ left: 0;
+ top: 0;
+
+ -webkit-transition: -webkit-transform 0.2s linear;
+ transition: transform 0.2s linear;
+ @include transform-translate3d(0,0,0);
+
+ &.material-checked {
+ @include transform-translate3d($switch-width - 16,0,0);
+ }
+ }
+}
diff --git a/src/components/switch/demo1/index.html b/src/components/switch/demo1/index.html
new file mode 100644
index 00000000000..fa2021685db
--- /dev/null
+++ b/src/components/switch/demo1/index.html
@@ -0,0 +1,5 @@
+
+
+
+ Value: {{val}}
+
diff --git a/src/components/switch/demo1/script.js b/src/components/switch/demo1/script.js
new file mode 100644
index 00000000000..0bf309404de
--- /dev/null
+++ b/src/components/switch/demo1/script.js
@@ -0,0 +1 @@
+angular.module('myApp', ['ngMaterial']);
diff --git a/src/components/switch/module.json b/src/components/switch/module.json
new file mode 100644
index 00000000000..48c8e376cde
--- /dev/null
+++ b/src/components/switch/module.json
@@ -0,0 +1,10 @@
+{
+ "module": "material.components.switch",
+ "name": "Switch",
+ "demos": {
+ "demo1": {
+ "name": "Switch Basic Usage",
+ "files": ["demo1/*"]
+ }
+ }
+}
diff --git a/src/components/switch/switch.js b/src/components/switch/switch.js
new file mode 100644
index 00000000000..f5c3b896a43
--- /dev/null
+++ b/src/components/switch/switch.js
@@ -0,0 +1,39 @@
+angular.module('material.components.switch', [
+ 'material.components.checkbox',
+ 'material.components.radioButton',
+])
+
+.directive('materialSwitch', [
+ 'materialCheckboxDirective',
+ 'materialRadioButtonDirective',
+ MaterialSwitch
+]);
+
+function MaterialSwitch(checkboxDirectives, radioButtonDirectives) {
+ var checkboxDirective = checkboxDirectives[0];
+ var radioButtonDirective = radioButtonDirectives[0];
+
+ return {
+ restrict: 'E',
+ transclude: true,
+ template:
+ '' +
+ '' +
+ radioButtonDirective.template +
+ '
',
+ require: '?ngModel',
+ compile: compile
+ };
+
+ function compile(element, attr) {
+
+ var thumb = angular.element(element[0].querySelector('.material-switch-thumb'));
+ //Copy down disabled attributes for checkboxDirective to use
+ thumb.attr('disabled', attr.disabled);
+ thumb.attr('ngDisabled', attr.ngDisabled);
+
+ return function postLink(scope, element, attr, ngModelCtrl) {
+ checkboxDirective.link(scope, thumb, attr, ngModelCtrl);
+ };
+ }
+}
diff --git a/src/components/switch/switch.spec.js b/src/components/switch/switch.spec.js
new file mode 100644
index 00000000000..31e267a4dac
--- /dev/null
+++ b/src/components/switch/switch.spec.js
@@ -0,0 +1,47 @@
+describe('', function() {
+ var CHECKED_CSS = 'material-checked';
+
+ beforeEach(module('material.components.switch'));
+
+ it('should set checked css class and aria-checked attributes', inject(function($compile, $rootScope) {
+ var element = $compile('' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
')($rootScope);
+
+ $rootScope.$apply(function(){
+ $rootScope.blue = false;
+ $rootScope.green = true;
+ });
+
+ var cbElements = element.find('.material-switch-thumb');
+
+ expect(cbElements.eq(0).hasClass(CHECKED_CSS)).toEqual(false);
+ expect(cbElements.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
+ expect(cbElements.eq(0).attr('aria-checked')).toEqual('false');
+ expect(cbElements.eq(1).attr('aria-checked')).toEqual('true');
+ expect(cbElements.eq(0).attr('role')).toEqual('checkbox');
+ }));
+
+ it('should be disabled with disabled attr', inject(function($compile, $rootScope) {
+ var element = $compile('' +
+ '' +
+ '' +
+ '
')($rootScope);
+
+ var switchThumb = element.find('.material-switch-thumb');
+
+ $rootScope.$apply('blue = false');
+
+ switchThumb.triggerHandler('click');
+ expect($rootScope.blue).toBe(false);
+
+ switchThumb.removeAttr('disabled');
+
+ switchThumb.triggerHandler('click');
+ expect($rootScope.blue).toBe(true);
+ }));
+
+});
diff --git a/src/main.scss b/src/main.scss
index 23163f25796..8e9502ec407 100644
--- a/src/main.scss
+++ b/src/main.scss
@@ -24,6 +24,7 @@
"components/sidenav/sidenav",
"components/form/form",
"components/slider/slider",
+"components/switch/switch",
"components/toast/toast",
"components/toolbar/toolbar",
"components/tabs/tabs",
diff --git a/src/theme/_variables.scss b/src/theme/_variables.scss
index de47416b473..6189f6b8d5a 100644
--- a/src/theme/_variables.scss
+++ b/src/theme/_variables.scss
@@ -155,6 +155,11 @@ $slider-track-height: 2px;
$slider-thumb-width: 10px;
$slider-thumb-height: $slider-thumb-width;
+// Switch
+//--------------------------------------------
+
+$switch-width: $baseline-grid * 8;
+
// Checkbox
//--------------------------------------------