diff --git a/config/build.config.js b/config/build.config.js index 8c2134a409..f7d876b691 100644 --- a/config/build.config.js +++ b/config/build.config.js @@ -97,6 +97,7 @@ module.exports = { 'src/components/whiteframe/whiteframe.js', 'src/components/divider/divider.js', 'src/components/linearProgress/linearProgress.js', + 'src/components/circularProgress/circularProgress.js', // Non-visual Components 'src/components/swipe/swipe.js', diff --git a/src/components/circularProgress/README.md b/src/components/circularProgress/README.md new file mode 100644 index 0000000000..149233fba2 --- /dev/null +++ b/src/components/circularProgress/README.md @@ -0,0 +1 @@ +Circular progress indicators, created with the `` directive. \ No newline at end of file diff --git a/src/components/circularProgress/_circularProgress.scss b/src/components/circularProgress/_circularProgress.scss new file mode 100644 index 0000000000..a256176bb7 --- /dev/null +++ b/src/components/circularProgress/_circularProgress.scss @@ -0,0 +1,139 @@ +$circle-size:48px; +$circle-background:transparent; +$circle-color:$color-blue-500; +$inset-size:36px; +$inset-color:white; +$transition-length:.3s; +$shadow:6px 6px 10px rgba(0, 0, 0, 0.2); +material-circular-progress { + display: block; + width: $circle-size + 6; + height: $circle-size + 6; + background-color: $circle-background; + border-radius: 50%; + padding: 3px; + + .wrapper1, .wrapper2 { + width: $circle-size; + height: $circle-size; + position: absolute; + border-radius: 50%; + } + + .circle { + .mask, .fill, .shadow { + width: $circle-size; + height: $circle-size; + position: absolute; + border-radius: 50%; + } + + .shadow { } + + .mask, .fill { + -webkit-backface-visibility: hidden; + transition: -webkit-transform $transition-length; + transition: -ms-transform $transition-length; + transition: transform $transition-length; + } + + .mask { + clip: rect(0px, $circle-size, $circle-size, $circle-size/2); + .fill { + clip: rect(0px, $circle-size/2, $circle-size, 0px); + background-color: $circle-color; + } + } + } + + .inset { + width: $inset-size; + height: $inset-size; + position: absolute; + margin-left: ($circle-size - $inset-size)/2; + margin-top: ($circle-size - $inset-size)/2; + background-color: $inset-color; + border-radius: 50%; + } + + &[mode=indeterminate] { + .wrapper1, .wrapper2 { + -ms-transform-origin: 50% 50%; /* IE 9 */ + webkit-transform-origin: 50% 50%; /* Chrome, Safari, Opera */ + transform-origin: 50% 50%; + } + + .wrapper1{ + @include animation(indeterminate_rotate1 3s infinite linear); + } + + .wrapper2{ + @include animation(indeterminate_rotate2 1.5s infinite linear); + } + + .fill, .mask.full{ + @include animation(indeterminate_size_fill 1.5s infinite linear); + } + + .fill.fix { + @include animation(indeterminate_size_fix 1.5s infinite linear); + } + } +} + +@include keyframes(indeterminate_rotate1) { + 0%{ + @include transform(rotate(0deg)); + } + 100%{ + @include transform(rotate(360deg)); + } +} + +@include keyframes(indeterminate_rotate2) { + 0%{ + @include transform(rotate(0deg)); + } + 70%{ + @include transform(rotate(0deg)); + } + 100%{ + @include transform(rotate(360deg)); + } +} + +@include keyframes(indeterminate_size_fill) { + 0%{ + @include transform(rotate(5deg)); + } + 10%{ + @include transform(rotate(5deg)); + } + 50%{ + @include transform(rotate(135deg)); + } + 70%{ + @include transform(rotate(135deg)); + } + 100%{ + @include transform(rotate(5deg)); + } +} + +@include keyframes(indeterminate_size_fix) { + 0%{ + @include transform(rotate(10deg)); + } + 10%{ + @include transform(rotate(10deg)); + } + 50%{ + @include transform(rotate(270deg)); + } + 70%{ + @include transform(rotate(270deg)); + } + 100%{ + @include transform(rotate(10deg)); + } +} \ No newline at end of file diff --git a/src/components/circularProgress/circularProgress.js b/src/components/circularProgress/circularProgress.js new file mode 100644 index 0000000000..52b8e62e5d --- /dev/null +++ b/src/components/circularProgress/circularProgress.js @@ -0,0 +1,120 @@ +/** + * @ngdoc module + * @name material.components.circularProgress + * @description Circular Progress module! + */ +angular.module('material.components.circularProgress', [ + 'material.animations', + 'material.services.aria' +]) + .directive('materialCircularProgress', [ + '$$rAF', + '$materialEffects', + MaterialCircularProgressDirective + ]); + +/** + * @ngdoc directive + * @name materialCircularProgress + * @module material.components.circularProgress + * @restrict E + * +* @description + * The circular progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. + * + * For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take. + * + * For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator. + * + * @param {string} mode Select from one of two modes: determinate and indeterminate. + * @param {number=} value In determinate mode, this number represents the percentage of the circular progress. Default: 0 + * @param {number=} diameter This specifies the diamter of the circular progress. Default: 48 + * + * @usage + * + * + * + * + * + * + * + * + * + */ +function MaterialCircularProgressDirective($$rAF, $materialEffects) { + var fillRotations = new Array(101), + fixRotations = new Array(101); + + for (var i = 0; i < 101; i++) { + var percent = i / 100; + var rotation = Math.floor(percent * 180); + + fillRotations[i] = 'rotate(' + rotation.toString() + 'deg)'; + fixRotations[i] = 'rotate(' + (rotation * 2).toString() + 'deg)'; + } + + return { + restrict: 'E', + template: + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + compile: compile + }; + + function compile(tElement, tAttrs, transclude) { + tElement.attr('aria-valuemin', 0); + tElement.attr('aria-valuemax', 100); + tElement.attr('role', 'progressbar'); + + return postLink; + } + + function postLink(scope, element, attr) { + var circle = element[0], + fill = circle.querySelectorAll('.fill, .mask.full'), + fix = circle.querySelectorAll('.fill.fix'), + i, clamped, fillRotation, fixRotation; + + var diameter = attr.diameter || 48; + var scale = diameter/48; + + circle.style[$materialEffects.TRANSFORM] = 'scale(' + scale.toString() + ')'; + + attr.$observe('value', function(value) { + clamped = clamp(value); + fillRotation = fillRotations[clamped]; + fixRotation = fixRotations[clamped]; + + element.attr('aria-valuenow', clamped); + + for (i = 0; i < fill.length; i++) { + fill[i].style[$materialEffects.TRANSFORM] = fillRotation; + } + + for (i = 0; i < fix.length; i++) { + fix[i].style[$materialEffects.TRANSFORM] = fixRotation; + } + }); + } + + function clamp(value) { + if (value > 100) { + return 100; + } + + if (value < 0) { + return 0; + } + + return Math.ceil(value || 0); + } +} \ No newline at end of file diff --git a/src/components/circularProgress/circularProgress.spec.js b/src/components/circularProgress/circularProgress.spec.js new file mode 100644 index 0000000000..5036a538f2 --- /dev/null +++ b/src/components/circularProgress/circularProgress.spec.js @@ -0,0 +1,18 @@ +describe('materialCircularProgress', function() { + beforeEach(module('material.components.circularProgress')); + + it('should update aria-valuenow', inject(function($compile, $rootScope) { + var element = $compile('
' + + '' + + '' + + '
')($rootScope); + + $rootScope.$apply(function() { + $rootScope.progress = 50; + }); + + var progress = element.find('material-circular-progress'); + + expect(progress.eq(0).attr('aria-valuenow')).toEqual('50'); + })); +}); \ No newline at end of file diff --git a/src/components/circularProgress/demo1/index.html b/src/components/circularProgress/demo1/index.html new file mode 100644 index 0000000000..823032481b --- /dev/null +++ b/src/components/circularProgress/demo1/index.html @@ -0,0 +1,7 @@ +
+

Determinate

+ + +

Indeterminate

+ +
\ No newline at end of file diff --git a/src/components/circularProgress/demo1/script.js b/src/components/circularProgress/demo1/script.js new file mode 100644 index 0000000000..15f29e31d2 --- /dev/null +++ b/src/components/circularProgress/demo1/script.js @@ -0,0 +1,14 @@ +angular.module('circularProgressDemo1', ['ngMaterial']) + .controller('AppCtrl', ['$scope', '$interval', + function($scope, $interval) { + $scope.mode = 'query'; + $scope.determinateValue = 30; + + $interval(function() { + $scope.determinateValue += 1; + if ($scope.determinateValue > 100) { + $scope.determinateValue = 30; + } + }, 100, 0, true); + } + ]); diff --git a/src/components/circularProgress/demo1/style.css b/src/components/circularProgress/demo1/style.css new file mode 100644 index 0000000000..76e6846d73 --- /dev/null +++ b/src/components/circularProgress/demo1/style.css @@ -0,0 +1,3 @@ +body { + padding: 20px; +} \ No newline at end of file diff --git a/src/components/circularProgress/module.json b/src/components/circularProgress/module.json new file mode 100644 index 0000000000..5b030d5b6a --- /dev/null +++ b/src/components/circularProgress/module.json @@ -0,0 +1,10 @@ +{ + "module": "material.components.circularProgress", + "name": "Circular Progress", + "demos": { + "demo1": { + "name": "Circular Progress Basic Usage", + "files": ["demo1/*"] + } + } +} \ No newline at end of file diff --git a/src/main.scss b/src/main.scss index a46b823865..6126c8bded 100644 --- a/src/main.scss +++ b/src/main.scss @@ -30,4 +30,5 @@ "components/list/list", "components/divider/divider", "components/whiteframe/whiteframe", -"components/linearProgress/linearProgress"; +"components/linearProgress/linearProgress", +"components/circularProgress/circularProgress";