predix-ui
Version:
px-* web components as React styled components
351 lines (299 loc) • 9.8 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import PopperContent from './PopperContent';
import { omit, mapToCssModules, getTarget } from '../utils';
const DEFAULT_DELAYS = {
show: 0,
hide: 0
};
const defaultProps = {
isOpen: false,
disabled: false,
target: null,
innerClassName: null,
cssModule: null,
className: null,
container: null,
modifiers: null,
hideArrow: false,
placement: 'right',
placementPrefix: 'bs-popover',
delay: DEFAULT_DELAYS,
toggle: () => {}
};
class Popover extends React.Component {
constructor(props) {
super(props);
this.addTargetEvents = this.addTargetEvents.bind(this);
this.handleDocumentClick = this.handleDocumentClick.bind(this);
this.removeTargetEvents = this.removeTargetEvents.bind(this);
this.getRef = this.getRef.bind(this);
this.toggle = this.toggle.bind(this);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
}
componentDidMount() {
this._target = getTarget(this.props.target);
this.handleProps();
}
componentDidUpdate() {
this.handleProps();
}
componentWillUnmount() {
this.clearShowTimeout();
this.clearHideTimeout();
this.removeTargetEvents();
}
getRef(ref) {
this._popover = ref;
}
getDelay(key) {
const { delay } = this.props;
if (typeof delay === 'object') {
return delay[key] === 'NaN' ? DEFAULT_DELAYS[key] : delay[key];
}
return delay;
}
handleProps() {
if (this.props.isOpen) {
this.show();
} else {
this.hide();
}
}
show() {
this.clearHideTimeout();
this.addTargetEvents();
if (!this.props.isOpen) {
this.clearShowTimeout();
this._showTimeout = setTimeout(this.toggle, this.getDelay('show'));
}
}
hide() {
this.clearShowTimeout();
this.removeTargetEvents();
if (this.props.isOpen) {
this.clearHideTimeout();
this._hideTimeout = setTimeout(this.toggle, this.getDelay('hide'));
}
}
clearShowTimeout() {
clearTimeout(this._showTimeout);
this._showTimeout = undefined;
}
clearHideTimeout() {
clearTimeout(this._hideTimeout);
this._hideTimeout = undefined;
}
handleDocumentClick(e) {
if (
e.target !== this._target &&
!this._target.contains(e.target) &&
e.target !== this._popover &&
!(this._popover && this._popover.contains(e.target))
) {
if (this._hideTimeout) {
this.clearHideTimeout();
}
if (this.props.isOpen) {
this.toggle(e);
}
}
}
addTargetEvents() {
['click', 'touchstart'].forEach(event =>
document.addEventListener(event, this.handleDocumentClick, true));
}
removeTargetEvents() {
['click', 'touchstart'].forEach(event =>
document.removeEventListener(event, this.handleDocumentClick, true));
}
toggle(e) {
if (this.props.disabled) {
return e && e.preventDefault();
}
return this.props.toggle(e);
}
render() {
if (!this.props.isOpen) {
return null;
}
const attributes = omit(this.props, Object.keys(PropTypes));
const classes = mapToCssModules(
classNames('popover-inner', this.props.innerClassName),
this.props.cssModule
);
const popperClasses = classNames(
'popover',
'show',
`bs-popover-${this.props.placement}`,
this.props.className
);
return (
<PopperContent
className={popperClasses}
target={this.props.target}
isOpen={this.props.isOpen}
hideArrow={this.props.hideArrow}
placement={this.props.placement}
placementPrefix={this.props.placementPrefix}
container={this.props.container}
modifiers={this.props.modifiers}
>
<div {...attributes} className={classes} ref={this.getRef} />
<style>{`
.popover{
top: 0;
left: 0;
z-index: 1060;
max-width: 276px;
font-style: normal;
font-weight: 400;
line-height: 1.5;
text-align: left;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
white-space: normal;
line-break: auto;
font-size: 1rem;
word-wrap: break-word;
background-clip: padding-box;
border: 1px solid var(--px-popover-border-color, #000);
background: var(--px-popover-background-color, #fff);
color: var(--px-popover-text-color, #000);
box-shadow: 0 1px 3px 0 var(--px-popover-shadow-color, #000);
position: absolute;
display: block;
}
.popover,
.popover .arrow {
position: absolute;
display: block
}
.popover .arrow {
width: 1rem;
height: .5rem;
margin: 0 .3rem
}
.popover .arrow:after,.popover .arrow:before {
position: absolute;
display: block;
content: "";
border-color: transparent;
border-style: solid
}
.bs-popover-auto[x-placement^=top],.bs-popover-top {
margin-bottom: .5rem
}
.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow {
bottom: calc((.5rem + 1px) * -1)
}
.bs-popover-auto[x-placement^=top] .arrow:after,.bs-popover-auto[x-placement^=top] .arrow:before,.bs-popover-top .arrow:after,.bs-popover-top .arrow:before {
border-width: .5rem .5rem 0
}
.bs-popover-auto[x-placement^=top] .arrow:before,.bs-popover-top .arrow:before {
bottom: 0;
border-top-color: rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=top] .arrow:after,.bs-popover-top .arrow:after {
bottom: 1px;
border-top-color: #fff
}
.bs-popover-auto[x-placement^=right],.bs-popover-right {
margin-left: .5rem
}
.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow {
left: calc((.5rem + 1px) * -1);
width: .5rem;
height: 1rem;
margin: .3rem 0
}
.bs-popover-auto[x-placement^=right] .arrow:after,.bs-popover-auto[x-placement^=right] .arrow:before,.bs-popover-right .arrow:after,.bs-popover-right .arrow:before {
border-width: .5rem .5rem .5rem 0
}
.bs-popover-auto[x-placement^=right] .arrow:before,.bs-popover-right .arrow:before {
left: 0;
border-right-color: rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=right] .arrow:after,.bs-popover-right .arrow:after {
left: 1px;
border-right-color: #fff
}
.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom {
margin-top: .5rem
}
.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow {
top: calc((.5rem + 1px) * -1)
}
.bs-popover-auto[x-placement^=bottom] .arrow:after,.bs-popover-auto[x-placement^=bottom] .arrow:before,.bs-popover-bottom .arrow:after,.bs-popover-bottom .arrow:before {
border-width: 0 .5rem .5rem
}
.bs-popover-auto[x-placement^=bottom] .arrow:before,.bs-popover-bottom .arrow:before {
top: 0;
border-bottom-color: rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=bottom] .arrow:after,.bs-popover-bottom .arrow:after {
top: 1px;
border-bottom-color: #fff
}
.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before {
position: absolute;
top: 0;
left: 50%;
display: block;
width: 1rem;
margin-left: -.5rem;
content: "";
border-bottom: 1px solid #f7f7f7
}
.bs-popover-auto[x-placement^=left],.bs-popover-left {
margin-right: .5rem
}
.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow {
right: calc((.5rem + 1px) * -1);
width: .5rem;
height: 1rem;
margin: .3rem 0
}
.bs-popover-auto[x-placement^=left] .arrow:after,.bs-popover-auto[x-placement^=left] .arrow:before,.bs-popover-left .arrow:after,.bs-popover-left .arrow:before {
border-width: .5rem 0 .5rem .5rem
}
.bs-popover-auto[x-placement^=left] .arrow:before,.bs-popover-left .arrow:before {
right: 0;
border-left-color: rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=left] .arrow:after,.bs-popover-left .arrow:after {
right: 1px;
border-left-color: #fff
}
`}
</style>
</PopperContent>
);
}
}
Popover.defaultProps = defaultProps;
Popover.propTypes = {
innerClassName: PropTypes.string,
target: PropTypes.element,
container: PropTypes.element,
cssModule: PropTypes.objectOf(PropTypes.string),
modifiers: PropTypes.objectOf(PropTypes.string),
disabled: PropTypes.bool,
isOpen: PropTypes.bool,
hideArrow: PropTypes.bool,
placement: PropTypes.string,
placementPrefix: PropTypes.string,
className: PropTypes.string,
delay: PropTypes.shape({
show: PropTypes.number,
hide: PropTypes.number
}),
toggle: PropTypes.func
};
export default Popover;