This repository has been archived by the owner on Sep 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the FAB Speed Dial and FAB Toolbar components.
This PR adds support for two new components: 1. fabSpeedDial - Creates a list of actions that expand from a single floating action button. 2. fabToolbar - Creates a list of actions that transform from a floating action button into a toolbar. Todo - The following are tasks we would like to implement in a future release. * Update keyboard support in-line with suggestions (i.e. tab to focus, then use arrow keys). * Add aria support. * Update animations to match new specs. * Use $animateCss to clean up code. Fix opacity bug causing issues in the demo. Wrap sd demo in a single md-content container. Closes #3108
- Loading branch information
1 parent
9521a1e
commit 545582d
Showing
14 changed files
with
1,073 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<div ng-controller="AppCtrl" layout="column"> | ||
<md-content class="md-padding" layout="column"> | ||
<p> | ||
You may supply a direction of <code>left</code>, <code>up</code>, <code>down</code>, or | ||
<code>right</code> through the <code>md-direction</code> attribute. | ||
</p> | ||
|
||
<div class="lock-size" layout="row" layout-align="center center"> | ||
<md-fab-speed-dial md-open="demo.isOpen" md-direction="{{demo.selectedDirection}}" | ||
ng-class="demo.selectedMode"> | ||
<md-fab-trigger> | ||
<md-button aria-label="menu" class="md-fab md-warn"> | ||
<md-icon md-svg-src="img/icons/menu.svg"></md-icon> | ||
</md-button> | ||
</md-fab-trigger> | ||
|
||
<md-fab-actions> | ||
<md-button aria-label="twitter" class="md-fab md-raised md-mini"> | ||
<md-icon md-svg-src="img/icons/twitter.svg"></md-icon> | ||
</md-button> | ||
<md-button aria-label="facebook" class="md-fab md-raised md-mini"> | ||
<md-icon md-svg-src="img/icons/facebook.svg"></md-icon> | ||
</md-button> | ||
<md-button aria-label="Google hangout" class="md-fab md-raised md-mini"> | ||
<md-icon md-svg-src="img/icons/hangout.svg"></md-icon> | ||
</md-button> | ||
</md-fab-actions> | ||
</md-fab-speed-dial> | ||
</div> | ||
|
||
<div layout="row" layout-align="space-around"> | ||
<div layout="column"> | ||
<b>Direction</b> | ||
|
||
<md-radio-group ng-model="demo.selectedDirection"> | ||
<md-radio-button ng-repeat="direction in demo.availableDirections" | ||
ng-value="direction" class="text-capitalize"> | ||
{{direction}} | ||
</md-radio-button> | ||
</md-radio-group> | ||
</div> | ||
|
||
<div layout="column"> | ||
<b>Open/Closed</b> | ||
|
||
<md-radio-group ng-model="demo.isOpen"> | ||
<md-radio-button ng-value="true">Open</md-radio-button> | ||
<md-radio-button ng-value="false">Closed</md-radio-button> | ||
</md-radio-group> | ||
</div> | ||
|
||
<div layout="column"> | ||
<b>Animation Modes</b> | ||
|
||
<md-radio-group ng-model="demo.selectedMode"> | ||
<md-radio-button ng-repeat="mode in demo.availableModes" ng-value="mode"> | ||
{{mode}} | ||
</md-radio-button> | ||
</md-radio-group> | ||
</div> | ||
</div> | ||
|
||
<p class="note"> | ||
Note that you can also hover over the directive's area or tab through each button to open and | ||
activate the speed dial menu. | ||
</p> | ||
</md-content> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
(function() { | ||
'use strict'; | ||
|
||
angular.module('fabSpeedDialBasicUsageDemo', ['ngMaterial']) | ||
.controller('AppCtrl', function($scope) { | ||
$scope.demo = { | ||
topDirections: ['left', 'up'], | ||
bottomDirections: ['down', 'right'], | ||
|
||
isOpen: false, | ||
|
||
availableModes: ['md-fling', 'md-scale'], | ||
selectedMode: 'md-fling', | ||
|
||
availableDirections: ['up', 'down', 'left', 'right'], | ||
selectedDirection: 'up' | ||
}; | ||
}); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.text-capitalize { | ||
text-transform: capitalize; | ||
} | ||
|
||
.md-fab:hover, .md-fab.md-focused { | ||
background-color: #000 !important; | ||
} | ||
|
||
p.note { | ||
font-size: 1.2rem; | ||
} | ||
|
||
.lock-size { | ||
min-width: 300px; | ||
min-height: 300px; | ||
width: 300px; | ||
height: 300px; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
(function() { | ||
'use strict'; | ||
|
||
angular | ||
.module('material.components.fabActions', ['material.core']) | ||
.directive('mdFabActions', MdFabActionsDirective); | ||
|
||
/** | ||
* @ngdoc directive | ||
* @name mdFabActions | ||
* @module material.components.fabSpeedDial | ||
* | ||
* @restrict E | ||
* | ||
* @description | ||
* The `<md-fab-actions>` directive is used inside of a `<md-fab-speed-dial>` or | ||
* `<md-fab-toolbar>` directive to mark the an element (or elements) as the actions and setup the | ||
* proper event listeners. | ||
* | ||
* @usage | ||
* See the `<md-fab-speed-dial>` or `<md-fab-toolbar>` directives for example usage. | ||
*/ | ||
function MdFabActionsDirective() { | ||
return { | ||
restrict: 'E', | ||
|
||
require: ['^?mdFabSpeedDial', '^?mdFabToolbar'], | ||
|
||
link: function(scope, element, attributes, controllers) { | ||
// Grab whichever parent controller is used | ||
var controller = controllers[0] || controllers[1]; | ||
|
||
// Make the children open/close their parent directive | ||
if (controller) { | ||
angular.forEach(element.children(), function(child) { | ||
angular.element(child).on('focus', controller.open); | ||
angular.element(child).on('blur', controller.close); | ||
}); | ||
} | ||
|
||
// After setting up the listeners, wrap every child in a new div and add a class that we can | ||
// scale/fling independently | ||
element.children().wrap('<div class="md-fab-action-item">'); | ||
} | ||
} | ||
} | ||
|
||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
(function() { | ||
'use strict'; | ||
|
||
angular | ||
.module('material.components.fabSpeedDial', [ | ||
'material.core', | ||
'material.components.fabTrigger', | ||
'material.components.fabActions' | ||
]) | ||
.directive('mdFabSpeedDial', MdFabSpeedDialDirective) | ||
.animation('.md-fling', MdFabSpeedDialFlingAnimation) | ||
.animation('.md-scale', MdFabSpeedDialScaleAnimation); | ||
|
||
/** | ||
* @ngdoc directive | ||
* @name mdFabSpeedDial | ||
* @module material.components.fabSpeedDial | ||
* | ||
* @restrict E | ||
* | ||
* @description | ||
* The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually | ||
* `<md-button>`s) for quick access to common actions. | ||
* | ||
* There are currently two animations available by applying one of the following classes to | ||
* the component: | ||
* | ||
* - `md-fling` - The speed dial items appear from underneath the trigger and move into their | ||
* appropriate positions. | ||
* - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%. | ||
* | ||
* @usage | ||
* <hljs lang="html"> | ||
* <md-fab-speed-dial direction="up" class="md-fling"> | ||
* <md-fab-trigger> | ||
* <md-button aria-label="Add..."><md-icon icon="/img/icons/plus.svg"></md-icon></md-button> | ||
* </md-fab-trigger> | ||
* | ||
* <md-fab-actions> | ||
* <md-button aria-label="Add User"> | ||
* <md-icon icon="/img/icons/user.svg"></md-icon> | ||
* </md-button> | ||
* | ||
* <md-button aria-label="Add Group"> | ||
* <md-icon icon="/img/icons/group.svg"></md-icon> | ||
* </md-button> | ||
* </md-fab-actions> | ||
* </md-fab-speed-dial> | ||
* </hljs> | ||
* | ||
* @param {string=} md-direction From which direction you would like the speed dial to appear | ||
* relative to the trigger element. | ||
* @param {expression=} md-open Programmatically control whether or not the speed-dial is visible. | ||
*/ | ||
function MdFabSpeedDialDirective() { | ||
return { | ||
restrict: 'E', | ||
|
||
scope: { | ||
direction: '@?mdDirection', | ||
isOpen: '=?mdOpen' | ||
}, | ||
|
||
bindToController: true, | ||
controller: FabSpeedDialController, | ||
controllerAs: 'vm', | ||
|
||
link: FabSpeedDialLink | ||
}; | ||
|
||
function FabSpeedDialLink(scope, element) { | ||
// Prepend an element to hold our CSS variables so we can use them in the animations below | ||
element.prepend('<div class="md-css-variables"></div>'); | ||
} | ||
|
||
function FabSpeedDialController($scope, $element, $animate) { | ||
var vm = this; | ||
|
||
// Define our open/close functions | ||
// Note: Used by fabTrigger and fabActions directives | ||
vm.open = function() { | ||
$scope.$apply('vm.isOpen = true'); | ||
}; | ||
|
||
vm.close = function() { | ||
$scope.$apply('vm.isOpen = false'); | ||
}; | ||
|
||
setupDefaults(); | ||
setupListeners(); | ||
setupWatchers(); | ||
|
||
// Set our default variables | ||
function setupDefaults() { | ||
// Set the default direction to 'down' if none is specified | ||
vm.direction = vm.direction || 'down'; | ||
|
||
// Set the default to be closed | ||
vm.isOpen = vm.isOpen || false; | ||
} | ||
|
||
// Setup our event listeners | ||
function setupListeners() { | ||
$element.on('mouseenter', vm.open); | ||
$element.on('mouseleave', vm.close); | ||
} | ||
|
||
// Setup our watchers | ||
function setupWatchers() { | ||
// Watch for changes to the direction and update classes/attributes | ||
$scope.$watch('vm.direction', function(newDir, oldDir) { | ||
// Add the appropriate classes so we can target the direction in the CSS | ||
$animate.removeClass($element, 'md-' + oldDir); | ||
$animate.addClass($element, 'md-' + newDir); | ||
}); | ||
|
||
|
||
// Watch for changes to md-open | ||
$scope.$watch('vm.isOpen', function(isOpen) { | ||
var toAdd = isOpen ? 'md-is-open' : ''; | ||
var toRemove = isOpen ? '' : 'md-is-open'; | ||
|
||
$animate.setClass($element, toAdd, toRemove); | ||
}); | ||
} | ||
} | ||
} | ||
|
||
function MdFabSpeedDialFlingAnimation() { | ||
function runAnimation(element) { | ||
var el = element[0]; | ||
var ctrl = element.controller('mdFabSpeedDial'); | ||
var items = el.querySelectorAll('.md-fab-action-item'); | ||
|
||
// Grab our element which stores CSS variables | ||
var variablesElement = el.querySelector('.md-css-variables'); | ||
|
||
// Setup JS variables based on our CSS variables | ||
var startZIndex = variablesElement.style.zIndex; | ||
|
||
// Always reset the items to their natural position/state | ||
angular.forEach(items, function(item, index) { | ||
var styles = item.style; | ||
|
||
styles.transform = ''; | ||
styles.transitionDelay = ''; | ||
styles.opacity = 1; | ||
|
||
// Make the items closest to the trigger have the highest z-index | ||
item.style.zIndex = (items.length - index) + startZIndex; | ||
}); | ||
|
||
// If the control is closed, hide the items behind the trigger | ||
if (!ctrl.isOpen) { | ||
angular.forEach(items, function(item, index) { | ||
var newPosition, axis; | ||
|
||
switch (ctrl.direction) { | ||
case 'up': | ||
newPosition = item.scrollHeight * (index + 1); | ||
axis = 'Y'; | ||
break; | ||
case 'down': | ||
newPosition = -item.scrollHeight * (index + 1); | ||
axis = 'Y'; | ||
break; | ||
case 'left': | ||
newPosition = item.scrollWidth * (index + 1); | ||
axis = 'X'; | ||
break; | ||
case 'right': | ||
newPosition = -item.scrollWidth * (index + 1); | ||
axis = 'X'; | ||
break; | ||
} | ||
|
||
item.style.transform = 'translate' + axis + '(' + newPosition + 'px)'; | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
addClass: function(element, className, done) { | ||
if (element.hasClass('md-fling')) { | ||
runAnimation(element); | ||
} | ||
}, | ||
removeClass: function(element, className, done) { | ||
runAnimation(element); | ||
} | ||
} | ||
} | ||
|
||
function MdFabSpeedDialScaleAnimation() { | ||
var delay = 65; | ||
|
||
function runAnimation(element) { | ||
var el = element[0]; | ||
var ctrl = element.controller('mdFabSpeedDial'); | ||
var items = el.querySelectorAll('.md-fab-action-item'); | ||
|
||
// Always reset the items to their natural position/state | ||
angular.forEach(items, function(item, index) { | ||
var styles = item.style, | ||
offsetDelay = index * delay; | ||
|
||
styles.opacity = ctrl.isOpen ? 1 : 0; | ||
styles.transform = ctrl.isOpen ? 'scale(1)' : 'scale(0)'; | ||
styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms'; | ||
}); | ||
} | ||
|
||
return { | ||
addClass: function(element, className, done) { | ||
runAnimation(element); | ||
}, | ||
|
||
removeClass: function(element, className, done) { | ||
runAnimation(element); | ||
} | ||
} | ||
} | ||
})(); |
Oops, something went wrong.