Skip to content

Instantly share code, notes, and snippets.

@ianstormtaylor
Created September 13, 2016 18:21
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianstormtaylor/7924e6abc71c1efa40411a4553afe4dc to your computer and use it in GitHub Desktop.
Save ianstormtaylor/7924e6abc71c1efa40411a4553afe4dc to your computer and use it in GitHub Desktop.
A React component example that makes it simple to position an element relative to a Slate node.
import React from 'react'
import Portal from 'react-portal'
import getOffsets from 'positions'
import { findDOMNode } from 'slate'
/**
* No-op.
*
* @type {Function}
*/
function noop() {}
/**
* A component that opens a portal relative to a Slate node.
*
* @type {Component}
*/
class SlateNodePortal extends React.Component {
/**
* Property types.
*
* @type {Object}
*/
static propTypes = {
children: React.PropTypes.any.isRequired,
menuAnchor: React.PropTypes.string,
node: React.PropTypes.object.isRequired,
nodeAnchor: React.PropTypes.string,
onClose: React.PropTypes.func,
onOpen: React.PropTypes.func,
onUpdate: React.PropTypes.func,
};
/**
* Default properties.
*
* @type {Object}
*/
static defaultProps = {
menuAnchor: 'bottom middle',
nodeAnchor: 'bottom middle',
onClose: noop,
onOpen: noop,
onUpdate: noop,
};
/**
* On portal open, store a reference to it and update the menu's position.
*
* @param {Element} portal
*/
onOpen = (portal) => {
this.portal = portal
this.update()
this.props.onOpen(portal)
}
/**
* On portal update, update the menu's position.
*/
onUpdate = () => {
this.update()
this.props.onUpdate()
}
/**
* On portal close, remove the portal reference.
*/
onClose = () => {
this.portal = null
this.props.onClose()
}
/**
* Update the menu's position, relative to the current node.
*/
update = () => {
if (!this.portal) return
const { node, nodeAnchor, menuAnchor } = this.props
const menuEl = this.portal.firstChild
menuEl.style.position = 'absolute'
const nodeEl = findDOMNode(node)
const offset = getOffsets(menuEl, menuAnchor, nodeEl, nodeAnchor)
menuEl.style.top = `${offset.top}px`
menuEl.style.left = `${offset.left}px`
}
/**
* Render.
*
* @return {Element}
*/
render = () => {
const {
children,
menuAnchor,
node,
nodeAnchor,
onClose,
onOpen,
onUpdate,
...props
} = this.props
return (
<Portal
{...props}
onOpen={this.onOpen}
onUpdate={this.onUpdate}
onClose={this.onClose}
>
{children}
</Portal>
)
}
}
/**
* Export.
*
* @type {Component}
*/
export default SlateNodePortal
@mxstbr
Copy link

mxstbr commented May 2, 2017

Sidenote for folks finding this: the <Portal /> needs an isOpened prop (isOpen if you're on react-portal v4) set, otherwise it doesn't show up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment