feat(popover): Add popout animation to popover

Summary:
- Adds popout animation to popover
- Fade out animation is pending

Test Plan: - Manual

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D2807
This commit is contained in:
Juan Tejada 2016-03-27 15:39:43 -07:00
parent dc1ff47bb0
commit c340338d4d
3 changed files with 48 additions and 35 deletions

View file

@ -1,6 +1,6 @@
import _ from 'underscore'; import _ from 'underscore';
import React, {Component, PropTypes} from 'react'; import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom'; import {findDOMNode} from 'react-dom';
import Actions from '../flux/actions'; import Actions from '../flux/actions';
@ -30,7 +30,6 @@ const OFFSET_PADDING = 11.5;
class FixedPopover extends Component { class FixedPopover extends Component {
static propTypes = { static propTypes = {
className: PropTypes.string,
children: PropTypes.element, children: PropTypes.element,
direction: PropTypes.string, direction: PropTypes.string,
fallbackDirection: PropTypes.string, fallbackDirection: PropTypes.string,
@ -58,8 +57,9 @@ class FixedPopover extends Component {
componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
this.focusElementWithTabIndex()
findDOMNode(this.refs.popoverContainer).addEventListener('animationend', this.onAnimationEnd)
window.addEventListener('resize', this.onWindowResize) window.addEventListener('resize', this.onWindowResize)
this.focusElementWithTabIndex();
_.defer(this.onPopoverRendered) _.defer(this.onPopoverRendered)
} }
@ -76,15 +76,20 @@ class FixedPopover extends Component {
} }
componentDidUpdate() { componentDidUpdate() {
this.focusElementWithTabIndex(); this.focusElementWithTabIndex()
_.defer(this.onPopoverRendered) _.defer(this.onPopoverRendered)
} }
componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
findDOMNode(this.refs.popoverContainer).removeEventListener('animationend', this.onAnimationEnd)
window.removeEventListener('resize', this.onWindowResize) window.removeEventListener('resize', this.onWindowResize)
} }
onAnimationEnd = () => {
_.defer(this.focusElementWithTabIndex);
}
onWindowResize() { onWindowResize() {
Actions.closePopover() Actions.closePopover()
} }
@ -115,7 +120,7 @@ class FixedPopover extends Component {
onBlur = (event)=> { onBlur = (event)=> {
const target = event.nativeEvent.relatedTarget; const target = event.nativeEvent.relatedTarget;
if (!target || (!ReactDOM.findDOMNode(this).contains(target))) { if (!target || (!React.findDOMNode(this).contains(target))) {
Actions.closePopover(); Actions.closePopover();
} }
}; };
@ -127,7 +132,7 @@ class FixedPopover extends Component {
}; };
getCurrentRect = ()=> { getCurrentRect = ()=> {
return ReactDOM.findDOMNode(this.refs.popover).getBoundingClientRect(); return findDOMNode(this.refs.popover).getBoundingClientRect();
}; };
getWindowDimensions = ()=> { getWindowDimensions = ()=> {
@ -137,11 +142,12 @@ class FixedPopover extends Component {
} }
}; };
static Directions = Directions;
focusElementWithTabIndex = ()=> { focusElementWithTabIndex = ()=> {
if (!this.mounted) {
return;
}
// Automatically focus the element inside us with the lowest tab index // Automatically focus the element inside us with the lowest tab index
const popoverNode = ReactDOM.findDOMNode(this); const popoverNode = React.findDOMNode(this);
// _.sortBy ranks in ascending numerical order. // _.sortBy ranks in ascending numerical order.
const focusable = popoverNode.querySelectorAll("[tabIndex], input"); const focusable = popoverNode.querySelectorAll("[tabIndex], input");
@ -215,7 +221,7 @@ class FixedPopover extends Component {
return null; return null;
}; };
computePopoverStyles = ({originRect, direction, offset, visible})=> { computePopoverStyles = ({originRect, direction, offset})=> {
const {Up, Down, Left, Right} = Directions const {Up, Down, Left, Right} = Directions
let containerStyle = {}; let containerStyle = {};
let popoverStyle = {}; let popoverStyle = {};
@ -227,6 +233,7 @@ class FixedPopover extends Component {
// Place container on the top left corner of the rect // Place container on the top left corner of the rect
top: originRect.top, top: originRect.top,
left: originRect.left, left: originRect.left,
width: originRect.width,
} }
popoverStyle = { popoverStyle = {
// Center, place on top of container, and adjust 10px for the pointer // Center, place on top of container, and adjust 10px for the pointer
@ -244,6 +251,7 @@ class FixedPopover extends Component {
// Place container on the bottom left corner of the rect // Place container on the bottom left corner of the rect
top: originRect.top + originRect.height, top: originRect.top + originRect.height,
left: originRect.left, left: originRect.left,
width: originRect.width,
} }
popoverStyle = { popoverStyle = {
// Center and adjust 10px for the pointer (already positioned at the bottom of container) // Center and adjust 10px for the pointer (already positioned at the bottom of container)
@ -261,6 +269,7 @@ class FixedPopover extends Component {
// Place container on the top left corner of the rect // Place container on the top left corner of the rect
top: originRect.top, top: originRect.top,
left: originRect.left, left: originRect.left,
height: originRect.height,
} }
popoverStyle = { popoverStyle = {
// Center, place on left of container, and adjust 10px for the pointer // Center, place on left of container, and adjust 10px for the pointer
@ -278,6 +287,7 @@ class FixedPopover extends Component {
// Place container on the top right corner of the rect // Place container on the top right corner of the rect
top: originRect.top, top: originRect.top,
left: originRect.left + originRect.width, left: originRect.left + originRect.width,
height: originRect.height,
} }
popoverStyle = { popoverStyle = {
// Center and adjust 10px for the pointer // Center and adjust 10px for the pointer
@ -294,17 +304,6 @@ class FixedPopover extends Component {
break; break;
} }
const visibilityProps = {}
if (visible) {
visibilityProps.visibility = 'visible';
visibilityProps.opacity = 1;
} else {
visibilityProps.visibility = 'hidden';
visibilityProps.opacity = 0;
}
popoverStyle = _.extend({}, popoverStyle, visibilityProps)
pointerStyle = _.extend({}, pointerStyle, visibilityProps)
// Set the zoom directly on the style element. Otherwise it won't work with // Set the zoom directly on the style element. Otherwise it won't work with
// mask image of our shadow pointer element. This is probably a Chrome bug // mask image of our shadow pointer element. This is probably a Chrome bug
pointerStyle.zoom = 0.5; pointerStyle.zoom = 0.5;
@ -315,27 +314,26 @@ class FixedPopover extends Component {
render() { render() {
const {offset, direction, visible} = this.state; const {offset, direction, visible} = this.state;
const {children, originRect} = this.props; const {children, originRect} = this.props;
if (!originRect) {
return <span />;
}
const blurTrapStyle = {top: originRect.top, left: originRect.left, height: originRect.height, width: originRect.width} const blurTrapStyle = {top: originRect.top, left: originRect.left, height: originRect.height, width: originRect.width}
const {containerStyle, popoverStyle, pointerStyle} = ( const {containerStyle, popoverStyle, pointerStyle} = (
this.computePopoverStyles({originRect, direction, offset, visible}) this.computePopoverStyles({originRect, direction, offset})
); );
const animateClass = visible ? ' popout' : '';
return ( return (
<div> <div>
<div ref="blurTrap" className="fixed-popover-blur-trap" style={blurTrapStyle}/> <div ref="blurTrap" className="fixed-popover-blur-trap" style={blurTrapStyle}/>
<div <div
ref="popoverContainer"
style={containerStyle} style={containerStyle}
className="fixed-popover-container" className={`fixed-popover-container${animateClass}`}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
onBlur={this.onBlur}> onBlur={this.onBlur}>
<div ref="popover" className="fixed-popover" style={popoverStyle}> <div ref="popover" className={`fixed-popover`} style={popoverStyle}>
{children} {children}
</div> </div>
<div className="fixed-popover-pointer" style={pointerStyle} /> <div className={`fixed-popover-pointer`} style={pointerStyle} />
<div className="fixed-popover-pointer shadow" style={pointerStyle} /> <div className={`fixed-popover-pointer shadow`} style={pointerStyle} />
</div> </div>
</div> </div>
); );

View file

@ -25,10 +25,6 @@ class PopoverStore extends NylasStore {
this.listenTo(Actions.closePopover, this.closePopover); this.listenTo(Actions.closePopover, this.closePopover);
} }
isPopoverOpen = ()=> {
return this.isOpen;
};
renderPopover = (child, props, callback)=> { renderPopover = (child, props, callback)=> {
const popover = ( const popover = (
<FixedPopover {...props}>{child}</FixedPopover> <FixedPopover {...props}>{child}</FixedPopover>

View file

@ -11,6 +11,8 @@
} }
.fixed-popover-container { .fixed-popover-container {
visibility: hidden;
opacity: 0;
position: absolute; position: absolute;
z-index: 40; z-index: 40;
@ -81,8 +83,25 @@
background-color: fade(@black, 22%); background-color: fade(@black, 22%);
} }
.fixed-popover,.fixed-popover-pointer,.fixed-popover-pointer.shadow { &.popout {
transition: opacity 100ms ease-in-out; visibility: visible;
opacity: 1;
animation: popout-animation 300ms ease;
}
@keyframes popout-animation {
from {
visibility: hidden;
opacity: 0;
transform: scale(0);
}
80% {
transform: scale(1.1);
}
to {
visibility: visible;
opacity: 1;
transform: scale(1);
}
} }
} }