Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

layer clickaway option for render-to-layer/popover #2359

Merged
merged 1 commit into from
Dec 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/src/app/components/pages/components/popover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ let PopoverPage = React.createClass({
header: 'default: no-op',
desc: 'This is a callback that fires when the popover thinks it should close. (e.g. clickAway or offScreen)',
},
{
name: 'useLayerForClickAway',
type: 'bool',
header: 'default: false',
desc: 'If true, the popover will render on top of an invisible layer, which ' +
'will prevent clicks to the underlying elements, and trigger an onRequestClose(clickAway) event.',
},
{
name: 'zDepth',
type: 'oneOf [0,1,2,3,4,5]',
Expand Down
2 changes: 1 addition & 1 deletion src/menus/icon-menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ const IconMenu = React.createClass({
open={open}
anchorEl={anchorEl}
childContextTypes={this.constructor.childContextTypes}
useLayerForClickAway={false}
onRequestClose={this.close}
context={this.context}>
{menu}
Expand Down Expand Up @@ -200,7 +201,6 @@ const IconMenu = React.createClass({
if (this.props.closeOnItemTouchTap) {
let isKeyboard = Events.isKeyboard(event);


this._timeout = setTimeout(() => {
if (!this.isMounted()) {
return;
Expand Down
1 change: 1 addition & 0 deletions src/menus/menu-item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ const MenuItem = React.createClass({
anchorOrigin={{horizontal: 'right', vertical: 'top'}}
anchorEl={this.state.anchorEl}
open={this.state.open}
useLayerForClickAway={false}
onRequestClose={this._onRequestClose}>
<Menu desktop={desktop} disabled={disabled} style={nestedMenuStyle}>
{React.Children.map(menuItems, this._cloneMenuItem)}
Expand Down
22 changes: 18 additions & 4 deletions src/popover/popover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Transitions from '../styles/transitions';
import Paper from '../paper';
import throttle from 'lodash.throttle';
import AutoPrefix from '../styles/auto-prefix';
import DefaultRawTheme from '../styles/raw-themes/light-raw-theme';
import ThemeManager from '../styles/theme-manager';

const Popover = React.createClass({
mixins: [
Expand All @@ -27,6 +29,7 @@ const Popover = React.createClass({
open: React.PropTypes.bool,
style: React.PropTypes.object,
targetOrigin: PropTypes.origin,
useLayerForClickAway: React.PropTypes.bool,
zDepth: PropTypes.zDepth,
},

Expand All @@ -46,6 +49,7 @@ const Popover = React.createClass({
vertical: 'top',
horizontal: 'left',
},
useLayerForClickAway:true,
zDepth: 1,
};
},
Expand All @@ -55,28 +59,38 @@ const Popover = React.createClass({

return {
open: this.props.open,
muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme),
};
},

contextTypes: {
//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},

getChildContext() {
return {
};
},

windowListeners: {
resize: 'setPlacementThrottled',
scroll: 'setPlacementThrottled',
},

componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps, nextContext) {
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
if (nextProps.open !== this.state.open) {
if (nextProps.open) {
this.anchorEl = nextProps.anchorEl || this.props.anchorEl;
this.setState({
open: true,
muiTheme: newMuiTheme,
});
} else {
this.setState({
open: false,
muiTheme: newMuiTheme,
}, () => {
this._animateClose();
});
Expand Down Expand Up @@ -118,7 +132,7 @@ const Popover = React.createClass({
position: 'fixed',
top: anchor.top,
left: anchor.left,
zIndex: 20,
zIndex: this.state.muiTheme.zIndex.popover,
opacity:1,
overflow:'auto',
maxHeight:'100%',
Expand Down Expand Up @@ -201,7 +215,7 @@ const Popover = React.createClass({
AutoPrefix.set(innerInnerInner.style, 'transform', `scaleY(${value})`);
AutoPrefix.set(rootStyle, 'opacity', value);
AutoPrefix.set(innerStyle, 'opacity', value);
AutoPrefix.set(innerInnerInner, 'opacity', value);
AutoPrefix.set(innerInnerInner.style, 'opacity', value);
AutoPrefix.set(el.style, 'opacity', value);
},

Expand Down
100 changes: 67 additions & 33 deletions src/render-to-layer.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Events from './utils/events';
import Dom from './utils/dom';
import debounce from 'lodash.debounce';
import Dom from './utils/dom';
import DefaultRawTheme from './styles/raw-themes/light-raw-theme';
import ThemeManager from './styles/theme-manager';

// heavily inspired by https://github.com/Khan/react-components/blob/master/js/layered-component-mixin.jsx
const RenderToLayer = React.createClass({

propTypes: {
componentClickAway: React.PropTypes.func,
open: React.PropTypes.bool.isRequired,
useLayerForClickAway: React.PropTypes.bool,
},

getDefaultProps() {
return {
useLayerForClickAway:true,
};
},

getInitialState() {
return {
muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme),
};
},

//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},

getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},

//to update theme inside state whenever a new theme is passed down
//from the parent / owner using context
componentWillReceiveProps(nextProps, nextContext) {
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
this.setState({muiTheme: newMuiTheme});
},

componentDidMount() {
this._renderLayer();
},
Expand All @@ -16,36 +53,25 @@ const RenderToLayer = React.createClass({
},

componentWillUnmount() {
this._unbindClickAway();
if (this._layer) {
this._unrenderLayer();
}
},

_checkClickAway(e) {
if (!this.canClickAway) {
onClickAway(e) {
if (e.defaultPrevented) {
return;
}

const el = this._layer;
if (e.target !== el && (e.target === window)
|| (document.documentElement.contains(e.target) && !Dom.isDescendant(el, e.target))) {
if (this.props.componentClickAway) {
if (this.props.componentClickAway && this.props.open) {
this.props.componentClickAway(e);
}
}
},

_preventClickAway(e) {
if (e.detail === this) {
return;
}
this.canClickAway = false;
},

_allowClickAway() {
this.canClickAway = true;
},

getLayer() {
return this._layer;
},
Expand All @@ -60,12 +86,34 @@ const RenderToLayer = React.createClass({
this._layer = document.createElement('div');
document.body.appendChild(this._layer);
}
this._bindClickAway();
if (this.props.useLayerForClickAway) {
this._layer.addEventListener('touchstart', this.onClickAway);
this._layer.addEventListener('click', this.onClickAway);
this._layer.style.position = 'fixed';
this._layer.style.top = 0;
this._layer.style.bottom = 0;
this._layer.style.left = 0;
this._layer.style.right = 0;
this._layer.style.zIndex = this.state.muiTheme.zIndex.layer;
}
else {
setTimeout(() => {
window.addEventListener('touchstart', this.onClickAway);
window.addEventListener('click', this.onClickAway);
}, 0);
}
if (this.reactUnmount) {
this.reactUnmount.cancel();
}
} else if (this._layer) {
this._unbindClickAway();
if (this.props.useLayerForClickAway) {
this._layer.style.position = 'relative';
this._layer.removeEventListener('touchstart', this.onClickAway);
this._layer.removeEventListener('click', this.onClickAway);
} else {
window.removeEventListener('touchstart', this.onClickAway);
window.removeEventListener('click', this.onClickAway);
}
this._unrenderLayer();
} else {
return;
Expand Down Expand Up @@ -103,20 +151,6 @@ const RenderToLayer = React.createClass({
this.reactUnmount();
},

_bindClickAway() {
if (typeof (this.canClickAway) === 'undefined') {
this.canClickAway = true;
}
Events.on(window, 'focus', this._checkClickAway);
Events.on(document, 'mousedown', this._checkClickAway);
Events.on(document, 'touchend', this._checkClickAway);
},

_unbindClickAway() {
Events.off(window, 'focus', this._checkClickAway);
Events.off(document, 'mousedown', this._checkClickAway);
Events.off(document, 'touchend', this._checkClickAway);
},
});

export default RenderToLayer;
4 changes: 4 additions & 0 deletions src/styles/raw-themes/dark-raw-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import Spacing from '../spacing';
export default {
spacing: Spacing,
fontFamily: 'Roboto, sans-serif',
zIndex: {
layer: 20,
popover: 20,
},
palette: {
primary1Color: Colors.cyan700,
primary2Color: Colors.cyan700,
Expand Down
4 changes: 4 additions & 0 deletions src/styles/raw-themes/light-raw-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import Spacing from '../spacing';
export default {
spacing: Spacing,
fontFamily: 'Roboto, sans-serif',
zIndex: {
layer: 20,
popover: 20,
},
palette: {
primary1Color: Colors.cyan500,
primary2Color: Colors.cyan700,
Expand Down
1 change: 1 addition & 0 deletions src/styles/theme-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export default {
borderColor: rawTheme.palette.borderColor,
},
isRtl: false,
zIndex: rawTheme.zIndex,
};

//add properties to objects inside 'returnObj' that depend on existing properties
Expand Down