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.
feat(tooltip): add tooltip component
- Loading branch information
Showing
11 changed files
with
451 additions
and
1 deletion.
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
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
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 @@ | ||
Create a tooltip. |
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,101 @@ | ||
@include keyframes(tooltipBackgroundShow) { | ||
0% { | ||
@include transform(scale(0.2)); | ||
opacity: 0.25; | ||
} | ||
50% { | ||
opacity: 1; | ||
} | ||
100% { | ||
@include transform(scale(1.0)); | ||
opacity: 1; | ||
} | ||
} | ||
@include keyframes(tooltipBackgroundHide) { | ||
0% { opacity: 1; } | ||
100% { opacity: 0; } | ||
} | ||
|
||
material-tooltip { | ||
position: absolute; | ||
font-size: 14px; | ||
z-index: $z-index-tooltip; | ||
overflow: hidden; | ||
pointer-events: none; | ||
color: white; | ||
border-radius: 4px; | ||
|
||
&[tooltip-direction="bottom"] { | ||
@include transform(translate3d(0, -30%, 0)); | ||
margin-top: 8px; | ||
} | ||
&[tooltip-direction="top"] { | ||
@include transform(translate3d(0, 30%, 0)); | ||
margin-bottom: 8px; | ||
} | ||
|
||
.tooltip-background { | ||
background: rgb(115,115,115); | ||
position: absolute; | ||
left: 50%; | ||
width: 256px; | ||
height: 256px; | ||
margin-left: -128px; | ||
margin-top: -128px; | ||
border-radius: 256px; | ||
|
||
opacity: 0.25; | ||
@include transform(scale(0.2)); | ||
} | ||
|
||
.tooltip-content { | ||
max-width: 240px; | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
|
||
padding: 8px; | ||
background: transparent; | ||
opacity: 0.3; | ||
@include transition(inherit); | ||
} | ||
|
||
&.tooltip-show, | ||
&.tooltip-hide { | ||
@include transition(0.2s ease-out); | ||
transition-property: transform, opacity; | ||
-webkit-transition-property: -webkit-transform, opacity; | ||
} | ||
|
||
&.tooltip-show { | ||
pointer-events: auto; | ||
@include transform(translate3d(0,0,0)); | ||
|
||
.tooltip-background { | ||
@include transform(scale(1.0)); | ||
opacity: 1.0; | ||
@include animation(tooltipBackgroundShow linear); | ||
} | ||
.tooltip-content { | ||
opacity: 0.99; | ||
} | ||
} | ||
&.tooltip-hide .tooltip-background { | ||
@include transform(scale(1.0)); | ||
opacity: 0; | ||
@include animation(tooltipBackgroundHide 0.2s linear); | ||
} | ||
|
||
/** | ||
* Depending on the tooltip's size as a multiple of 32 (set by JS), | ||
* change the background's animation duration. | ||
* The larger the tooltip, the less time the background should take to ripple outwards. | ||
*/ | ||
@for $i from 1 through 8 { | ||
&[width-32="#{$i}"].tooltip-show .tooltip-background { | ||
$duration: 1000 - $i * 100; | ||
animation-duration: #{$duration}ms; | ||
-webkit-animation-duration: #{$duration}ms; | ||
} | ||
} | ||
} |
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,32 @@ | ||
<div ng-controller="AppCtrl"> | ||
<material-content class="material-content-padding"> | ||
|
||
<material-button class="material-button-fab material-button-fab-top-left"> | ||
<material-tooltip visible="showTooltip"> | ||
Refresh | ||
</material-tooltip> | ||
<material-icon icon="/img/icons/ic_refresh_24px.svg"> | ||
</material-icon> | ||
</material-button> | ||
|
||
<br/> | ||
<br/> | ||
<br/> | ||
<br/> | ||
<br/> | ||
|
||
<p> | ||
<div> | ||
The tooltip is visible when the button is hovered, focused, or touched. | ||
</div> | ||
<div> | ||
Additionally, the tooltip's visibility is bound to the checkbox below. | ||
</div> | ||
</p> | ||
|
||
<material-checkbox ng-model="showTooltip"> | ||
Tooltip is shown | ||
</material-checkbox> | ||
|
||
</material-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,4 @@ | ||
angular.module('tooltipDemo1', ['ngMaterial']) | ||
|
||
.controller('AppCtrl', function($scope) { | ||
}); |
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,10 @@ | ||
{ | ||
"module": "material.components.tooltip", | ||
"name": "Tooltip", | ||
"demos": { | ||
"demo1": { | ||
"name": "Tooltip Basic Usage", | ||
"files": ["demo1/*"] | ||
} | ||
} | ||
} |
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,187 @@ | ||
/** | ||
* @ngdoc module | ||
* @name material.components.tooltip | ||
*/ | ||
angular.module('material.components.tooltip', []) | ||
|
||
.directive('materialTooltip', [ | ||
'$timeout', | ||
'$window', | ||
'$$rAF', | ||
'$document', | ||
MaterialTooltipDirective | ||
]); | ||
|
||
/** | ||
* @ngdoc directive | ||
* @name materialTooltip | ||
* @module material.components.tooltip | ||
* @description | ||
* Tooltips are used to describe elements that are interactive and primarily graphical (not textual). | ||
* | ||
* Place a `<material-tooltip>` as a child of the element it describes. | ||
* | ||
* A tooltip will activate when the user focuses, hovers over, or touches the parent. | ||
* | ||
* @usage | ||
* <hljs lang="html"> | ||
* <material-icon icon="/img/icons/ic_play_arrow_24px.svg"> | ||
* <material-tooltip> | ||
* Play Music | ||
* </material-tooltip> | ||
* </material-icon> | ||
* </hljs> | ||
* | ||
* @param {expression=} visible Boolean bound to whether the tooltip is | ||
* currently visible. | ||
*/ | ||
function MaterialTooltipDirective($timeout, $window, $$rAF, $document) { | ||
|
||
var TOOLTIP_SHOW_DELAY = 400; | ||
var TOOLTIP_WINDOW_EDGE_SPACE = 8; | ||
// We have to append tooltips to the body, because we use | ||
// getBoundingClientRect(). | ||
// to find where to append the tooltip. | ||
var tooltipParent = angular.element(document.body); | ||
|
||
return { | ||
restrict: 'E', | ||
transclude: true, | ||
require: '^?materialContent', | ||
template: | ||
'<div class="tooltip-background"></div>' + | ||
'<div class="tooltip-content" ng-transclude></div>', | ||
scope: { | ||
visible: '=?' | ||
}, | ||
link: postLink | ||
}; | ||
|
||
function postLink(scope, element, attr, contentCtrl) { | ||
var parent = element.parent(); | ||
|
||
// We will re-attach tooltip when visible | ||
element.detach(); | ||
element.attr('role', 'tooltip'); | ||
element.attr('id', attr.id || Util.nextUid()); | ||
|
||
parent.on('focus mouseenter touchstart', function() { | ||
setVisible(true); | ||
}); | ||
parent.on('blur mouseleave touchend touchcancel', function() { | ||
// Don't hide the tooltip if the parent is still focused. | ||
if (document.activeElement === parent[0]) return; | ||
setVisible(false); | ||
}); | ||
|
||
scope.$watch('visible', function(isVisible) { | ||
if (isVisible) showTooltip(); | ||
else hideTooltip(); | ||
}); | ||
|
||
var debouncedOnResize = $$rAF.debounce(onWindowResize); | ||
angular.element($window).on('resize', debouncedOnResize); | ||
function onWindowResize() { | ||
// Reposition on resize | ||
if (scope.visible) positionTooltip(); | ||
} | ||
|
||
// Be sure to completely cleanup the element on destroy | ||
scope.$on('$destroy', function() { | ||
scope.visible = false; | ||
element.remove(); | ||
angular.element($window).off('resize', debouncedOnResize); | ||
}); | ||
|
||
// ******* | ||
// Methods | ||
// ******* | ||
|
||
// If setting visible to true, debounce to TOOLTIP_SHOW_DELAY ms | ||
// If setting visible to false and no timeout is active, instantly hide the tooltip. | ||
function setVisible(value) { | ||
setVisible.value = !!value; | ||
|
||
if (!setVisible.queued) { | ||
if (value) { | ||
setVisible.queued = true; | ||
$timeout(function() { | ||
scope.visible = setVisible.value; | ||
setVisible.queued = false; | ||
}, TOOLTIP_SHOW_DELAY); | ||
|
||
} else { | ||
$timeout(function() { scope.visible = false; }); | ||
} | ||
} | ||
} | ||
|
||
function showTooltip() { | ||
// Insert the element before positioning it, so we can get position | ||
// (tooltip is hidden by default) | ||
element.removeClass('tooltip-hide'); | ||
parent.attr('aria-describedby', element.attr('id')); | ||
tooltipParent.append(element); | ||
|
||
// Wait until the element has been in the dom for two frames before | ||
// fading it in. | ||
// Additionally, we position the tooltip twice to avoid positioning bugs | ||
//positionTooltip(); | ||
$$rAF(function() { | ||
|
||
$$rAF(function() { | ||
positionTooltip(); | ||
if (!scope.visible) return; | ||
element.addClass('tooltip-show'); | ||
}); | ||
|
||
}); | ||
} | ||
|
||
function hideTooltip() { | ||
element.removeClass('tooltip-show').addClass('tooltip-hide'); | ||
parent.removeAttr('aria-describedby'); | ||
$timeout(function() { | ||
if (scope.visible) return; | ||
element.detach(); | ||
}, 200, false); | ||
} | ||
|
||
function positionTooltip(rerun) { | ||
var tipRect = element[0].getBoundingClientRect(); | ||
var parentRect = parent[0].getBoundingClientRect(); | ||
|
||
if (contentCtrl) { | ||
parentRect.top += contentCtrl.$element.prop('scrollTop'); | ||
parentRect.left += contentCtrl.$element.prop('scrollLeft'); | ||
} | ||
|
||
// Default to bottom position if possible | ||
var tipDirection = 'bottom'; | ||
var newPosition = { | ||
left: parentRect.left + parentRect.width / 2 - tipRect.width / 2, | ||
top: parentRect.top + parentRect.height | ||
}; | ||
|
||
// If element bleeds over left/right of the window, place it on the edge of the window. | ||
newPosition.left = Math.min( | ||
newPosition.left, | ||
$window.innerWidth - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE | ||
); | ||
newPosition.left = Math.max(newPosition.left, TOOLTIP_WINDOW_EDGE_SPACE); | ||
|
||
// If element bleeds over the bottom of the window, place it above the parent. | ||
if (newPosition.top + tipRect.height > $window.innerHeight) { | ||
newPosition.top = parentRect.top - tipRect.height; | ||
tipDirection = 'top'; | ||
} | ||
|
||
element.css({top: newPosition.top + 'px', left: newPosition.left + 'px'}); | ||
// Tell the CSS the size of this tooltip, as a multiple of 32. | ||
element.attr('width-32', Math.ceil(tipRect.width / 32)); | ||
element.attr('tooltip-direction', tipDirection); | ||
} | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.