Just an experiment. Not suitable for production. Right-click context menu React component.
A Pen by devHamsters on CodePen.
<div id="root"></div> |
class ContextMenu extends React.Component { | |
state = { | |
visible: false, | |
}; | |
componentDidMount() { | |
document.addEventListener('contextmenu', this._handleContextMenu); | |
document.addEventListener('click', this._handleClick); | |
document.addEventListener('scroll', this._handleScroll); | |
}; | |
componentWillUnmount() { | |
document.removeEventListener('contextmenu', this._handleContextMenu); | |
document.removeEventListener('click', this._handleClick); | |
document.removeEventListener('scroll', this._handleScroll); | |
} | |
_handleContextMenu = (event) => { | |
event.preventDefault(); | |
this.setState({ visible: true }); | |
const clickX = event.clientX; | |
const clickY = event.clientY; | |
const screenW = window.innerWidth; | |
const screenH = window.innerHeight; | |
const rootW = this.root.offsetWidth; | |
const rootH = this.root.offsetHeight; | |
const right = (screenW - clickX) > rootW; | |
const left = !right; | |
const top = (screenH - clickY) > rootH; | |
const bottom = !top; | |
if (right) { | |
this.root.style.left = `${clickX + 5}px`; | |
} | |
if (left) { | |
this.root.style.left = `${clickX - rootW - 5}px`; | |
} | |
if (top) { | |
this.root.style.top = `${clickY + 5}px`; | |
} | |
if (bottom) { | |
this.root.style.top = `${clickY - rootH - 5}px`; | |
} | |
}; | |
_handleClick = (event) => { | |
const { visible } = this.state; | |
const wasOutside = !(event.target.contains === this.root); | |
if (wasOutside && visible) this.setState({ visible: false, }); | |
}; | |
_handleScroll = () => { | |
const { visible } = this.state; | |
if (visible) this.setState({ visible: false, }); | |
}; | |
render() { | |
const { visible } = this.state; | |
return(visible || null) && | |
<div ref={ref => {this.root = ref}} className="contextMenu"> | |
<div className="contextMenu--option">Share this</div> | |
<div className="contextMenu--option">New window</div> | |
<div className="contextMenu--option">Visit official site</div> | |
<div className="contextMenu--option contextMenu--option__disabled">View full version</div> | |
<div className="contextMenu--option">Settings</div> | |
<div className="contextMenu--separator" /> | |
<div className="contextMenu--option">About this app</div> | |
</div> | |
}; | |
} | |
ReactDOM.render(<ContextMenu />, document.getElementById('root')); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js"></script> |
#root { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
background: linear-gradient(75deg, #00E39F, #00C4E1); | |
&::before { | |
content: 'Right click anywhere on the screen!'; | |
font-family: sans-serif; | |
font-size: 12px; | |
color: white; | |
} | |
} | |
.contextMenu { | |
position: fixed; | |
background: white; | |
box-shadow: 0px 2px 10px #999999; | |
&--option { | |
padding: 6px 50px 5px 10px; | |
min-width: 160px; | |
cursor: default; | |
font-size: 12px; | |
&:hover { | |
background: linear-gradient(to top, #555, #333); | |
color: white; | |
} | |
&:active { | |
color: #e9e9e9; | |
background: linear-gradient(to top, #555, #444); | |
} | |
&__disabled { | |
color: #999999; | |
pointer-events: none; | |
} | |
} | |
&--separator { | |
width: 100%; | |
height: 1px; | |
background: #CCCCCC; | |
margin: 0 0 0 0; | |
} | |
} |