Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(fabSpeedDial): Make hovering an option via CSS.
Browse files Browse the repository at this point in the history
Initially, the speed dial was designed to always open
when the user hovered over any portion of the speed
dial (including the area where the actions would
eventually appear). However, this made the speed dial
unusable on mobile (and sometimes unusable on desktop)
because it disallowed the user from pressing anything
underneath the actions.

Add the `md-hover-full` CSS class to allow developer
configuration of this behavior.

Ensure fabToolbar also works this way, and fix jumpy
animation.

Also updated the docs/demos and made the demos easier
to use on mobile.

BREAKING CHANGE - The fabSpeedDial no longer automatically
opens when hovering over the invisible actions. Add the
`md-hover-full` class to enable this interaction.

Fixes #4259.
  • Loading branch information
topherfangio committed Sep 25, 2015
1 parent f04f094 commit c1b3a2a
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 93 deletions.
6 changes: 3 additions & 3 deletions src/components/fabSpeedDial/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</div>

<div layout="row" layout-align="space-around">
<div layout="column">
<div layout="column" layout-align="start center">
<b>Direction</b>

<md-radio-group ng-model="demo.selectedDirection">
Expand All @@ -40,7 +40,7 @@
</md-radio-group>
</div>

<div layout="column">
<div layout="column" layout-align="start center">
<b>Open/Closed</b>

<md-radio-group ng-model="demo.isOpen">
Expand All @@ -49,7 +49,7 @@
</md-radio-group>
</div>

<div layout="column">
<div layout="column" layout-align="start center">
<b>Animation Modes</b>

<md-radio-group ng-model="demo.selectedMode">
Expand Down
127 changes: 73 additions & 54 deletions src/components/fabSpeedDial/demoMoreOptions/index.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,69 @@
<div layout="column" ng-controller="DemoCtrl as demo">
<md-content class="md-padding" layout="column">

<md-toolbar>
<h3>
<md-button>Test</md-button>
<md-button>Test</md-button>
<md-button hide-sm>Test</md-button>
<md-button hide-sm>Test</md-button>
<md-button hide-sm>Test</md-button>
<md-button hide-sm>Test</md-button>
<md-button hide-sm>Test</md-button>
</h3>
</md-toolbar>

<p>
The speed dial supports many advanced usage scenarios. This demo shows many of them mixed
together.
The speed dial supports many advanced usage scenarios.
<br />
This demo shows many of them mixed together.
</p>

<div class="lock-size" layout="row" layout-align="center center">
<md-fab-speed-dial ng-hide="demo.hidden" md-direction="down" class="md-fling"
md-open="demo.isOpen"
ng-mouseenter="demo.isOpen=true" ng-mouseleave="demo.isOpen=false">
<md-fab-trigger>
<md-button aria-label="menu" class="md-fab md-warn">
<md-tooltip md-direction="top">Menu</md-tooltip>
<md-icon md-svg-src="img/icons/menu.svg" aria-label="menu"></md-icon>
<md-fab-speed-dial ng-hide="demo.hidden" md-direction="left" md-open="demo.isOpen"
class="md-scale md-fab-top-right" ng-class="{ 'md-hover-full': demo.hover }"
ng-mouseenter="demo.isOpen=true" ng-mouseleave="demo.isOpen=false">
<md-fab-trigger>
<md-button aria-label="menu" class="md-fab md-warn">
<md-tooltip md-direction="top" md-visible="tooltipVisible">Menu</md-tooltip>
<md-icon md-svg-src="img/icons/menu.svg" aria-label="menu"></md-icon>
</md-button>
</md-fab-trigger>

<md-fab-actions>
<div ng-repeat="item in demo.items">
<md-button aria-label="{{item.name}}" class="md-fab md-raised md-mini"
ng-click="demo.openDialog($event, item)">
<md-tooltip md-direction="{{item.direction}}" md-visible="tooltipVisible"
md-autohide="false">
{{item.name}}
</md-tooltip>

<md-icon md-svg-src="{{item.icon}}" aria-label="{{item.name}}"></md-icon>
</md-button>
</md-fab-trigger>

<md-fab-actions>
<div ng-repeat="item in demo.items">
<md-button aria-label="{{item.name}}" class="md-fab md-raised md-mini"
ng-click="demo.openDialog($event, item)">
<md-tooltip md-direction="{{item.direction}}">{{item.name}}</md-tooltip>
<md-icon md-svg-src="{{item.icon}}" aria-label="{{item.name}}"></md-icon>
</md-button>
</div>
</md-fab-actions>
</md-fab-speed-dial>
</div>
</div>
</md-fab-actions>
</md-fab-speed-dial>
</md-content>

<md-content class="md-padding" layout="row">
<div flex="50">
<md-content class="md-padding" layout="row" layout-sm="column" layout-align="space-around">
<div flex-gt-sm="45">
<h3>Tooltips</h3>

<p>
Each action item supports a tooltip using the standard approach as can be seen above.
</p>

<h3>ngHide</h3>

<p>
The speed dial also supports hiding using the standard <code>ng-hide</code> attribute. View
the source to see how to apply the animation effect.

<md-checkbox ng-model="demo.hidden">
Hide the speed dial.
</md-checkbox>
</p>

<h3>ngRepeat</h3>

<p>
Expand All @@ -43,53 +76,39 @@ <h3>ngRepeat</h3>
that wraps your items.
</p>
</div>
<div flex="50">
<h3>$mdDialog</h3>

<div flex-gt-sm="45">
<h3>Hovering</h3>

<p>
You can also use the buttons to open a dialog. When clicked, the buttons above will open a
dialog showing a message which item was clicked.
You can also easily setup the speed dial to open on hover using the
<code>ng-mouseenter</code> and <code>ng-mouseleave</code> attributes.
</p>
</div>
</md-content>

<md-content class="md-padding" layout="row">
<div flex="50">
<h3>ngHide</h3>

<p>
The speed dial also supports hiding using the standard <code>ng-hide</code> attribute.
If you want the user to be able to hover over the empty area where the
actions will eventually appear, you must also add the
<code>md-hover-full</code> class to the speed dial element.

<md-checkbox ng-model="demo.hidden">
Hide the speed dial.
<md-checkbox ng-model="demo.hover">
Enable "full hover" mode.
</md-checkbox>
</p>
</div>
<div flex="50">
<h3>Tooltips</h3>

<p>
Each action item supports a tooltip using the standard approach as can be seen above.
Notice that in "full hover" mode, you cannot click on the last "Test" buttons on the toolbar
as they are hidden by the speed dial. See the example code and docs for more information.
</p>
</div>
</md-content>

<md-content class="md-padding" layout="row">
<div flex="50">
<h3>Hovering</h3>

<p>
You can also easily setup the speed dial to open on hover using the
<code>ng-mouseenter</code> and <code>ng-mouseleave</code> attributes.
</p>
<h3>$mdDialog</h3>

<p>
See the example code for more information.
You can also use the buttons to open a dialog. When clicked, the buttons above will open a
dialog showing a message which item was clicked.
</p>
</div>
</md-content>


<script type="text/ng-template" id="dialog.html">
<md-dialog>
<md-dialog-content>Hello User! You clicked {{dialog.item.name}}.</md-dialog-content>
Expand Down
22 changes: 18 additions & 4 deletions src/components/fabSpeedDial/demoMoreOptions/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@
'use strict';

angular.module('fabSpeedDialDemoMoreOptions', ['ngMaterial'])
.controller('DemoCtrl', function($mdDialog) {
.controller('DemoCtrl', function($scope, $mdDialog, $timeout) {
var self = this;

self.hidden = false;
self.isOpen = false;
self.hover = false;

// On opening, add a delayed property which shows tooltips after the speed dial has opened
// so that they have the proper position; if closing, immediately hide the tooltips
$scope.$watch('demo.isOpen', function(isOpen) {
if (isOpen) {
$timeout(function() {
$scope.tooltipVisible = self.isOpen;
}, 600);
} else {
$scope.tooltipVisible = self.isOpen;
}
});

self.items = [
{name: "Twitter", icon: "img/icons/twitter.svg", direction: "left" },
{name: "Facebook", icon: "img/icons/facebook.svg", direction: "right" },
{name: "Google Hangout", icon: "img/icons/hangout.svg", direction: "left" }
{ name: "Twitter", icon: "img/icons/twitter.svg", direction: "bottom" },
{ name: "Facebook", icon: "img/icons/facebook.svg", direction: "top" },
{ name: "Google Hangout", icon: "img/icons/hangout.svg", direction: "bottom" }
];

self.openDialog = function($event, item) {
Expand Down
37 changes: 30 additions & 7 deletions src/components/fabSpeedDial/demoMoreOptions/style.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
.lock-size {
min-width: 300px;
min-height: 300px;
width: 300px;
height: 300px;
margin-left: auto;
margin-right: auto;
// Line the fab up properly on different screen sizes/layouts
.md-fab-top-right {
top: 16px;
}

@media only screen and (max-device-width: 600px) {
.md-fab-top-right {
top: 9px;
right: 9px;
}
}

.md-fab.demo-fab.trigger-fab, .md-fab.demo-fab.action-fab {
Expand All @@ -23,3 +26,23 @@ md-content div {
padding: 15px;
}
}


md-fab-speed-dial {
// Add a simple scale transition to the trigger when hiding/showing the speed dial
md-fab-trigger {
transition: all 0.3s ease-in-out;
transform: scale(1);
}

// Note: you MUST use an existing CSS class for the animation to fire properly
&.md-scale, &.md-fling {
&.ng-hide {
display: flex !important;

md-fab-trigger {
transform: scale(0);
}
}
}
}
14 changes: 14 additions & 0 deletions src/components/fabSpeedDial/fabSpeedDial.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
* These CSS classes use `position: absolute`, so you need to ensure that the container element
* also uses `position: absolute` or `position: relative` in order for them to work.
*
* Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
* open or close the speed dial. However, if you wish to allow users to hover over the empty
* space where the actions will appear, you must also add the `md-hover-full` class to the speed
* dial element. Without this, the hover effect will only occur on top of the trigger.
*
* @usage
* <hljs lang="html">
* <md-fab-speed-dial md-direction="up" class="md-fling">
Expand Down Expand Up @@ -182,6 +187,12 @@
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 = parseInt(window.getComputedStyle(variablesElement).zIndex);

// Always reset the items to their natural position/state
angular.forEach(items, function(item, index) {
var styles = item.style,
Expand All @@ -190,6 +201,9 @@
styles.opacity = ctrl.isOpen ? 1 : 0;
styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';

// Make the items closest to the trigger have the highest z-index
styles.zIndex = (items.length - index) + startZIndex;
});
}

Expand Down
21 changes: 20 additions & 1 deletion src/components/fabSpeedDial/fabSpeedDial.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,27 @@ md-fab-speed-dial {
display: flex;
align-items: center;

// Include the top/left/bottom/right fab positions
// Include the top/left/bottom/right fab positions and set the z-index for absolute positioning
@include fab-all-positions();
z-index: $z-index-fab;

// Allow users to enable/disable hovering over the entire speed dial (i.e. the empty space where
// items will eventually appear)
&:not(.md-hover-full) {
// Turn off pointer events when closed
pointer-events: none;

md-fab-trigger, .md-fab-action-item {
// Always make the trigger and action items always have pointer events (the tooltip looks
// for the first parent with pointer-events, so we must set this for tooltips to work)
pointer-events: auto;
}

&.md-is-open {
// Turn on pointer events when open
pointer-events: auto;
}
}

.md-css-variables {
z-index: $z-index-fab;
Expand Down
34 changes: 10 additions & 24 deletions src/components/fabToolbar/fabToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@
var width = el.offsetWidth;
var height = el.offsetHeight;

// Make a square
var scale = width * 2;
// Make it twice as big as it should be since we scale from the center
var scale = 2 * (width / triggerElement.offsetWidth);

// Set some basic styles no matter what animation we're doing
backgroundElement.style.backgroundColor = color;
Expand All @@ -137,22 +137,9 @@
// Turn on toolbar pointer events when closed
toolbarElement.style.pointerEvents = 'initial';

// Set the width/height to take up the full toolbar width
backgroundElement.style.width = scale + 'px';
backgroundElement.style.height = scale + 'px';

// Set the top/left to move up/left (or right) by the scale width/height
backgroundElement.style.top = -(scale / 2) + 'px';

if (element.hasClass('md-right')) {
backgroundElement.style.left = -(scale / 2) + 'px';
backgroundElement.style.right = null;
}

if (element.hasClass('md-left')) {
backgroundElement.style.right = -(scale / 2) + 'px';
backgroundElement.style.left = null;
}
backgroundElement.style.width = triggerElement.offsetWidth + 'px';
backgroundElement.style.height = triggerElement.offsetHeight + 'px';
backgroundElement.style.transform = 'scale(' + scale + ')';

// Set the next close animation to have the proper delays
backgroundElement.style.transitionDelay = '0ms';
Expand All @@ -166,20 +153,19 @@
// Turn off toolbar pointer events when closed
toolbarElement.style.pointerEvents = 'none';

// Otherwise, set the width/height to the trigger's width/height
backgroundElement.style.width = triggerElement.offsetWidth + 'px';
backgroundElement.style.height = triggerElement.offsetHeight + 'px';
// Scale it back down to the trigger's size
backgroundElement.style.transform = 'scale(1)';

// Reset the position
backgroundElement.style.top = '0px';
backgroundElement.style.top = '0';

if (element.hasClass('md-right')) {
backgroundElement.style.left = '0px';
backgroundElement.style.left = '0';
backgroundElement.style.right = null;
}

if (element.hasClass('md-left')) {
backgroundElement.style.right = '0px';
backgroundElement.style.right = '0';
backgroundElement.style.left = null;
}

Expand Down

0 comments on commit c1b3a2a

Please sign in to comment.