Popover react component
export const ARROW_WIDTH = 16; // ширина основания стрелки
export const ARROW_HEIGHT = 13; // высота стрелки
export const ARROW_OFFSET_TOP = 20; // смещение стрелки от верхнего края
export const ARROW_OFFSET_RIGHT = 20; // смещение стрелки от правого края
export const ARROW_OFFSET_BOTTOM = 20; // смещение стрелки от нижнего края
export const ARROW_OFFSET_LEFT = 20; // смещение стрелки от левого края
export const ARROW_DISTANCE = 10;// удаление стрелки от рута
export const POPOVER_WIDTH_FIXED = 300; // значение фиксированной ширины поповера
export const POPOVER_HEIGHT_FIXED = 200; // значение фиксированной высоты поповера
export const POPOVER_HEIGHT_MIN = ARROW_WIDTH + 24; // фиксированная минимальная высота поповера
export const BACKGROUND_COLOR = '#FFF9F0';
export const BORDER_COLOR = '#FFB335';
export const BORDER_RADIUS = 5;
export const PADDING = 23;
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import * as defaultPopoverOptions from './constants';
const setStyleValue = (props, value) => {
if (props.popoverOptions && props.popoverOptions[value]) {
return props.popoverOptions[value];
}
return defaultPopoverOptions[value];
};
class Popover extends React.Component {
state = {
show: false,
rootSize: {},
};
_onMouseEnter() {
this.setState({ show: true });
}
_onMouseLeave() {
this.setState({ show: false });
}
centeringStyles(offset) {
switch (offset) {
case 'top':
return {
top: 'initial',
right: 'initial',
bottom: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.height}px`,
left: `${-((setStyleValue(this.props, 'POPOVER_WIDTH_FIXED') / 2) -
(this.state.rootSize.width / 2))}px`,
};
case 'top-left':
return {
top: 'initial',
right: `${-setStyleValue(this.props, 'ARROW_OFFSET_RIGHT') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2) + (this.state.rootSize.width / 2)}px`,
bottom: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.height}px`,
left: 'initial',
};
case 'top-right':
return {
top: 'initial',
right: 'initial',
bottom: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.height}px`,
left: `${-setStyleValue(this.props, 'ARROW_OFFSET_LEFT') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2) + (this.state.rootSize.width / 2)}px`,
};
case 'right':
return {
top: `${-(setStyleValue(this.props, 'POPOVER_HEIGHT_FIXED') / 2) +
(this.state.rootSize.height / 2)}px`,
right: 'initial',
bottom: 'initial',
left: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
};
case 'right-top':
return {
top: 'initial',
right: 'initial',
bottom: `${(this.state.rootSize.height / 2) -
setStyleValue(this.props, 'ARROW_OFFSET_BOTTOM') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2)}px`,
left: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
};
case 'right-bottom':
return {
top: `${(this.state.rootSize.height / 2) -
setStyleValue(this.props, 'ARROW_OFFSET_TOP') - (setStyleValue(this.props, 'ARROW_WIDTH') / 2)}px`,
right: 'initial',
bottom: 'initial',
left: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
};
case 'bottom':
return {
top: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') - 2 + this.state.rootSize.height}px`,
right: 'initial',
bottom: 'initial',
left: `${-((setStyleValue(this.props, 'POPOVER_WIDTH_FIXED') / 2) -
(this.state.rootSize.width / 2))}px`,
};
case 'bottom-left':
return {
top: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') - 2 + this.state.rootSize.height}px`,
right: `${-setStyleValue(this.props, 'ARROW_OFFSET_RIGHT') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2) + (this.state.rootSize.width / 2)}px`,
bottom: 'initial',
left: 'initial',
};
case 'bottom-right':
return {
top: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') - 2 + this.state.rootSize.height}px`,
right: 'initial',
bottom: 'initial',
left: `${-setStyleValue(this.props, 'ARROW_OFFSET_LEFT') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2) + (this.state.rootSize.width / 2)}px`,
};
case 'left':
return {
top: `${-(setStyleValue(this.props, 'POPOVER_HEIGHT_FIXED') / 2) +
(this.state.rootSize.height / 2)}px`,
right: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
bottom: 'initial',
left: 'initial',
};
case 'left-top':
return {
top: 'initial',
right: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
bottom: `${(this.state.rootSize.height / 2) -
setStyleValue(this.props, 'ARROW_OFFSET_BOTTOM') -
(setStyleValue(this.props, 'ARROW_WIDTH') / 2)}px`,
left: 'initial',
};
case 'left-bottom':
return {
top: `${(this.state.rootSize.height / 2) -
setStyleValue(this.props, 'ARROW_OFFSET_TOP') - (setStyleValue(this.props, 'ARROW_WIDTH') / 2)}px`,
right: `${setStyleValue(this.props, 'ARROW_HEIGHT') +
setStyleValue(this.props, 'ARROW_DISTANCE') + this.state.rootSize.width}px`,
bottom: 'initial',
left: 'initial',
};
default:
return '';
}
}
render() {
const {
className,
popoverText,
children,
popoverRootStyle,
popoverStyle,
popoverOffsetType,
} = this.props;
const { show } = this.state;
return (
<div
className={className}
>
<div
className="root-wrapper"
onMouseEnter={(e) => {
e.stopPropagation();
this.setState({ rootSize: e.target.getBoundingClientRect() }, this._onMouseEnter());
}}
onMouseLeave={() => { this._onMouseLeave(); }}
style={popoverRootStyle}
>
{children}
{(show && popoverText) ?
(<div
className={`popover popover_${popoverOffsetType}`}
style={{ ...popoverStyle, ...this.centeringStyles(popoverOffsetType) }}
>
{popoverText}
</div>) : (null)
}
</div>
</div>
);
}
}
Popover.propTypes = {
className: PropTypes.string,
popoverText: PropTypes.string,
children: PropTypes.element.isRequired,
popoverRootStyle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
popoverOptions: PropTypes.shape({
ARROW_WIDTH: PropTypes.number,
ARROW_HEIGHT: PropTypes.number,
ARROW_OFFSET_TOP: PropTypes.number,
ARROW_OFFSET_RIGHT: PropTypes.number,
ARROW_OFFSET_BOTTOM: PropTypes.number,
ARROW_OFFSET_LEFT: PropTypes.number,
POPOVER_WIDTH_FIXED: PropTypes.number,
POPOVER_HEIGHT_FIXED: PropTypes.number,
POPOVER_HEIGHT_MIN: PropTypes.number,
BACKGROUND_COLOR: PropTypes.string,
BORDER_COLOR: PropTypes.string,
BORDER_RADIUS: PropTypes.number,
PADDING: PropTypes.number,
}),
popoverStyle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
popoverOffsetType: PropTypes.oneOf([
'top', // width is fixed, text centered
'top-right',
'top-left',
'right', // height is fixed, text centered
'right-top', // min-height is fixed
'right-bottom', // min-height is fixed
'bottom', // width is fixed, text centered
'bottom-right',
'bottom-left',
'left', // height is fixed, text centered
'left-top', // min-height is fixed
'left-bottom', // min-height is fixed
'',
]),
};
Popover.defaultProps = {
className: '',
popoverText: '',
popoverRootStyle: null,
popoverStyle: null,
popoverOffsetType: '',
popoverOptions: {
ARROW_WIDTH: defaultPopoverOptions.ARROW_WIDTH,
ARROW_HEIGHT: defaultPopoverOptions.ARROW_HEIGHT,
ARROW_OFFSET_TOP: defaultPopoverOptions.ARROW_OFFSET_TOP,
ARROW_OFFSET_RIGHT: defaultPopoverOptions.ARROW_OFFSET_RIGHT,
ARROW_OFFSET_BOTTOM: defaultPopoverOptions.ARROW_OFFSET_BOTTOM,
ARROW_OFFSET_LEFT: defaultPopoverOptions.ARROW_OFFSET_LEFT,
POPOVER_WIDTH_FIXED: defaultPopoverOptions.POPOVER_WIDTH_FIXED,
POPOVER_HEIGHT_FIXED: defaultPopoverOptions.POPOVER_HEIGHT_FIXED,
POPOVER_HEIGHT_MIN: defaultPopoverOptions.POPOVER_HEIGHT_MIN,
BACKGROUND_COLOR: defaultPopoverOptions.BACKGROUND_COLOR,
BORDER_COLOR: defaultPopoverOptions.BORDER_COLOR,
BORDER_RADIUS: defaultPopoverOptions.BORDER_RADIUS,
PADDING: defaultPopoverOptions.PADDING,
},
};
export default styled(Popover)`
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
.root-wrapper {
font-size: 0;
position: absolute;
//width: 100%;
//height: 100%;
}
.popover {
position: absolute;
padding: ${props => setStyleValue(props, 'PADDING')}px;
background: ${props => setStyleValue(props, 'BACKGROUND_COLOR')};
font-size: initial;
border: 1px solid ${props => setStyleValue(props, 'BORDER_COLOR')};
border-radius: ${props => setStyleValue(props, 'BORDER_RADIUS')}px;
opacity: 1;
z-index: 1;
white-space: nowrap;
&:before,
&:after {
position: absolute;
content: '';
width: 0;
height: 0;
}
// top
&_top { // width is fixed, text centered
width: ${props => setStyleValue(props, 'POPOVER_WIDTH_FIXED')}px!important;
max-width: initial!important;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:before,
&:after {
top: initial;
right: initial;
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
left: calc(50% - ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px);
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_top-left {
&:before,
&:after {
top: initial;
right: ${props => setStyleValue(props, 'ARROW_OFFSET_RIGHT')}px;
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
left: initial;
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_top-right {
&:before,
&:after {
top: initial;
right: initial;
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
left: ${props => setStyleValue(props, 'ARROW_OFFSET_LEFT')}px;
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px
solid ${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
bottom: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-top: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
// right
&_right { // height is fixed, text centered
height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_FIXED')}px!important;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:before,
&:after {
top: calc(50% - ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px);
right: initial;
bottom: initial;
left: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
left: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_right-top { // min-height is fixed
height: initial;
min-height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_MIN')}px!important;
&:before,
&:after {
top: initial;
right: initial;
bottom: ${props => setStyleValue(props, 'ARROW_OFFSET_BOTTOM')}px;
left: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
left: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_right-bottom { // min-height is fixed
height: initial;
min-height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_MIN')}px!important;
&:before,
&:after {
top: ${props => setStyleValue(props, 'ARROW_OFFSET_TOP')}px;
right: initial;
bottom: initial;
left: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
left: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-right: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
// bottom
&_bottom { // width is fixed, text centered
width: ${props => setStyleValue(props, 'POPOVER_WIDTH_FIXED')}px!important;
max-width: initial!important;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:before,
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
right: initial;
bottom: initial;
left: calc(50% - ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px);
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_bottom-right {
&:before,
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
right: initial;
bottom: initial;
left: ${props => setStyleValue(props, 'ARROW_OFFSET_LEFT')}px;
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_bottom-left {
&:before,
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
right: ${props => setStyleValue(props, 'ARROW_OFFSET_RIGHT')}px;
bottom: initial;
left: initial;
border-left: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
border-right: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
}
&:after {
top: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-bottom: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_left { // height is fixed, text centered
height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_FIXED')}px!important;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:before,
&:after {
top: calc(50% - ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px);
right: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
bottom: initial;
left: initial;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
right: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_left-top { // min-height is fixed
height: initial;
min-height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_MIN')}px!important;
&:before,
&:after {
top: initial;
right: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
bottom: ${props => setStyleValue(props, 'ARROW_OFFSET_BOTTOM')}px;
left: initial;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
right: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
&_left-bottom { // min-height is fixed
height: initial;
min-height: ${props => setStyleValue(props, 'POPOVER_HEIGHT_MIN')}px!important;
&:before,
&:after {
top: ${props => setStyleValue(props, 'ARROW_OFFSET_TOP')}px;
right: -${props => setStyleValue(props, 'ARROW_HEIGHT')}px;
bottom: initial;
left: initial;
border-top: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid
transparent;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BORDER_COLOR')};
border-bottom: ${props => setStyleValue(props, 'ARROW_WIDTH') / 2}px solid transparent;
}
&:after {
right: -${props => setStyleValue(props, 'ARROW_HEIGHT') - 2}px;
border-left: ${props => setStyleValue(props, 'ARROW_HEIGHT')}px solid
${props => setStyleValue(props, 'BACKGROUND_COLOR')};
}
}
}
`;