Last active
February 22, 2019 19:51
-
-
Save Morgantheplant/0dacbffd4309401208bade116fe58907 to your computer and use it in GitHub Desktop.
abstracted popup logic
This file contains hidden or 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
import PropTypes from 'prop-types'; | |
import React, {Component} from 'react'; | |
// wrapper component that will call dismiss if a click is detected outside of itself or its children | |
class DismissableClick extends Component { | |
constructor(props) { | |
super(props); | |
this.handleMouseDown = this.handleMouseDown.bind(this); | |
this.removeListener = this.removeListener.bind(this); | |
} | |
componentDidMount() { | |
this.props.onDismiss && document.addEventListener('mousedown', this.handleMouseDown); | |
} | |
componentWillUnmount() { | |
this.props.onDismiss && this.removeListener(); | |
} | |
handleMouseDown({ target }) { | |
if (this.node.contains(target)) { | |
return; | |
} | |
this.removeListener(); | |
this.props.onDismiss(); | |
} | |
removeListener() { | |
document.removeEventListener('mousedown', this.handleMouseDown, false); | |
} | |
render() { | |
const { children, component, onDismiss, ...restProps } = this.props; | |
restProps.ref = onDismiss ? node => (this.node = node) : undefined; | |
return React.createElement(component || 'div', restProps, children); | |
} | |
} | |
/** | |
* PortalPopUp - Write a short description of your component here. | |
*/ | |
// maps events to component's internal methods | |
const defaultTriggerMap = { | |
onClick: 'showPopUpEvent', | |
onMouseOut: 'hidePopUp', | |
onMouseOver: 'showPopUpEvent', | |
ref: 'showPopUpEvent' | |
}; | |
// we can pass this in as a prop later if we need padding to be configurable | |
// this can also be done via CSS so making it a constant | |
const PADDING = 10; | |
// this calculates where to position the popup panel | |
const positionStrategy = { | |
bottom: (rect) => ({ left: rect.x, top: rect.y + rect.height + PADDING }) | |
}; | |
// using the trigger map this attaches hide/remove action to component events | |
const addTriggersToProps = function addTriggersToProps(restProps, triggers) { | |
const { TriggerMap } = this.props; | |
// allow triggers to be passed in as Array or String | |
const triggerAry = Array.isArray(triggers) ? triggers : [triggers]; | |
return triggerAry.reduce((acc, trigger) => { | |
const triggerAction = TriggerMap[trigger]; | |
if (triggerAction) { | |
acc[trigger] = this[triggerAction]; | |
} | |
return acc; | |
}, restProps); | |
}; | |
export class PortalPopUp extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
currentToolBarPosition: null, | |
showPopUp: props.showPopUp | |
}; | |
this.setPopUpPosition = this.setPopUpPosition.bind(this); | |
this.showPopUpEvent = this.showPopUpEvent.bind(this); | |
this.toggleShown = this.toggleShown.bind(this); | |
this.hidePopUp = this.setPopUpPosition.bind(this, null); | |
} | |
setPopUpPosition(el) { | |
let coords = null; | |
if (el) { | |
const rect = el.getBoundingClientRect(); | |
coords = positionStrategy[this.props.position](rect); | |
} | |
this.setState(() => ({ | |
currentToolBarPosition: coords | |
})); | |
} | |
showPopUpEvent(e) { | |
this.setPopUpPosition(e.target); | |
} | |
toggleShown(shouldShow) { | |
this.setState(showPopUp => ({ | |
showPopUp: shouldShow !== 'undefined' ? shouldShow : !prevState.showPopUp | |
})); | |
} | |
render() { | |
const { | |
component, | |
triggers, | |
showPopUp, | |
popup, | |
position, | |
children, | |
panelClassName, | |
dismissWithClick, | |
TriggerMap, | |
...restProps | |
} = this.props; | |
const rootProps = addTriggersToProps.call(this, restProps, triggers); | |
return React.createElement( | |
component || 'div', | |
rootProps, | |
children, | |
showPopUp && | |
this.state.currentToolBarPosition && | |
<Portal> | |
<DismissableClick | |
onDismiss={dismissWithClick ? this.hidePopUp : null} | |
className={`panel ${position} ${panelClassName || ''}`} | |
style={{ | |
position: 'absolute', | |
left: this.state.currentToolBarPosition.left, | |
top: this.state.currentToolBarPosition.top | |
}}> | |
{popup({ | |
...this.state, | |
toggleShown: this.toggleShown, | |
setPopUpPosition: this.setPopUpPosition | |
})} | |
</DismissableClick> | |
</Portal> | |
); | |
} | |
} | |
//// example of it in use with click | |
class LinkElement extends Component { | |
constructor(props) { | |
super(props); | |
this.handleRemove = this.handleRemove.bind(this); | |
} | |
handleRemove() { | |
const { entityKey, handleAction } = this.props; | |
if (entityKey) { | |
const action = toggleLink(entityKey); | |
handleAction(action); | |
} | |
} | |
goToLink() { | |
const href = this.props.getHrefFromProps(this.props); | |
if (href) { | |
const newWindow = window.open(href, '_blank'); | |
// reset the "opener" property ie noopener | |
newWindow.opener = null; | |
} | |
} | |
getHrefFromProps() { | |
return this.props.contentState.getEntity(this.props.entityKey).getData().url; | |
} | |
render() { | |
const href = this.getHrefFromProps(); | |
return ( | |
<PopupPortal | |
href={href} | |
component="a" | |
dismissWithClick={true} | |
triggers={['onClick']} | |
popup={() => | |
<div className="cherry"> | |
<span className="ml-L">{this.props.labels.GO_TO_LINK}</span> | |
<a href={href} className="mr-S ml-S" title={href} onClick={this.goToLink}> | |
{href} | |
</a>|<button className="btn btn-link">{this.props.labels.EDIT}</button>|<button | |
onClick={this.handleRemove} | |
className="btn btn-link"> | |
{this.props.labels.REMOVE} | |
</button> | |
</div>}> | |
{this.props.children} | |
</PopupPortal> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment