Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Created March 23, 2015 17:23
Show Gist options
  • Save mattmccray/a868a5ede93d5395a9b6 to your computer and use it in GitHub Desktop.
Save mattmccray/a868a5ede93d5395a9b6 to your computer and use it in GitHub Desktop.
Popover (portal) support as an HOC (decorator)...
import {stopEvent, hoistStatics} from 'util'
let Types= React.PropTypes,
_globalContainer= null
export const PopoverSupport= Wrapped => {
class PopoverSupport extends React.Component {
// static propTypes= Wrapped.propTypes // Won't this be nice?
constructor( props, context) {
super( props, context)
this.state= {
popoverVisible: props.popoverVisible || false
}
this.options= _.defaults({}, this.props.options, {
relativeTo: null
})
}
componentWillUnmount() {
this._unrenderLayer()
// this._destroyContainer() // It's global now, so we can keep it around.
this._isMounted= false
this._child= null
this._options= null
}
componentDidMount() {
this._createContainer()
this._isMounted= true
this._renderLayer()
}
componentDidUpdate() {
this._renderLayer()
}
onContainerClick( e) {
this.hide()
}
onPopoverClick( e) {
stopEvent( e)
}
// Public API
configure( options={}) {
this.options= _.extend( this.options, options)
return this
}
toggle( renderer) {
if( this.state.popoverVisible) {
this.hide()
}
else {
this.show( renderer)
}
return this
}
show( renderer) {
// this._createContainer()
this.setState({ popoverVisible: true})
this._renderer= renderer || this._child.renderPopover.bind( this._child)
return this
}
hide() {
this.setState({ popoverVisible: false})
return this
}
render() {
let props= this.props
return <Wrapped ref={ c => this._child= c} {...props} popoverManager={ this}/>
}
_containerStyles() {
let isVisible= this.state.popoverVisible
return {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0, 0.35)',
zIndex: 999999999,
pointerEvents: isVisible ? 'all' : 'none',
display: isVisible ? 'block' : 'none'
}
}
_popoverStyles() {
let refNode= React.findDOMNode( this.options.relativeTo || this._child),
posInfo= refNode.getBoundingClientRect(),
{top, left}= posInfo
return {
display: (this.state.popoverVisible ? 'block' : 'none'),
position: 'absolute',
top, left
}
}
_renderLayer() {
if(! this._isMounted) return
let content= null
if( this.state.popoverVisible) {
content= this._renderer()
}
else {
content= <span key={100}/>
}
React.render(
<div className="Popover-container"
style={ this._containerStyles()}
onClick={ this.onContainerClick.bind( this)}>
<div className="Popover-content"
style={ this._popoverStyles()}
onClick={ this.onPopoverClick.bind( this)}>
{ content }
</div>
</div>,
_globalContainer
)
}
_unrenderLayer() {
if( _globalContainer) {
React.unmountComponentAtNode( _globalContainer)
}
}
_createContainer() {
if(! _globalContainer) {
_globalContainer= document.createElement( 'div')
_globalContainer.className= "popover-layer-container"
document.body.appendChild( _globalContainer)
}
}
_destroyContainer() {
if( _globalContainer) {
document.body.removeChild( _globalContainer)
_globalContainer= null
}
}
}
hoistStatics( Wrapped, PopoverSupport)
return PopoverSupport
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment