mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-05 20:24:26 +08:00
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:
parent
dc1ff47bb0
commit
c340338d4d
3 changed files with 48 additions and 35 deletions
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue