Skip to content
This repository has been archived by the owner on Jul 28, 2020. It is now read-only.

Commit

Permalink
Merge pull request #923 from sociomantic-tsunami/Issue-919-PopperWrap…
Browse files Browse the repository at this point in the history
…perHooks

Issue 919 popper wrapper hooks
  • Loading branch information
damian-rodriguez-sociomantic authored Mar 7, 2019
2 parents a032ad3 + e109cd9 commit bb4e3f4
Showing 1 changed file with 152 additions and 203 deletions.
355 changes: 152 additions & 203 deletions src/PopperWrapper/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,225 +9,174 @@

/* global document, addEventListener, removeEventListener */

import React, { Component } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import { Manager, Reference, Popper } from 'react-popper';
import PropTypes from 'prop-types';

export default class PopperWrapper extends Component
{
static propTypes =
{
/**
* Reference node to attach popper
*/
children : PropTypes.node,
/**
* id of the DOM element used as container
*/
container : PropTypes.string,
/**
* Show / Hide popper
*/
isVisible : PropTypes.bool,
/**
* pop up width matches reference width
*/
matchRefWidth : PropTypes.bool,
/**
* Click Outside callback: ( e ) => ...
*/
onClickOutside : PropTypes.func,
/**
* Popper content node
*/
popper : PropTypes.node,
/**
* Popper offset
*/
popperOffset : PropTypes.oneOf( [ 'S', 'M', 'L', 'XL', 'none' ] ),
/**
* Popper position
*/
popperPosition : PropTypes.oneOf( [
'auto',
'auto-start',
'auto-end',
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
] ),
}

static defaultProps =
{
children : undefined,
container : undefined,
isVisible : false,
matchRefWidth : undefined,
onClickOutside : undefined,
popper : undefined,
popperOffset : 'none',
popperPosition : 'auto',
}

static displayName = 'PopperWrapper'

referenceRef = React.createRef();
popperRef = React.createRef();

constructor()
{
super();

this.handleClickOutSide = this.handleClickOutSide.bind( this );
}
const componentName = 'PopperWrapper';

componentDidMount()
const PopperWrapper = ( props ) =>
{
const {
children,
container,
isVisible,
matchRefWidth,
onClickOutside,
popper,
popperOffset,
popperPosition,
} = props;

const referenceRef = useRef();
const popperRef = useRef();
const scheduleUpdateRef = useRef();

useEffect( () =>
{
if ( this.props.isVisible && this.props.onClickOutside )
if ( isVisible && scheduleUpdateRef.current )
{
addEventListener( 'mousedown', this.handleClickOutSide, false );
scheduleUpdateRef.current();
}
}
}, [ isVisible, popperOffset ] );

componentDidUpdate( prevProps )
useEffect( () =>
{
if ( this.props.isVisible )
if ( isVisible && onClickOutside )
{
this.scheduleUpdate();
}
addEventListener( 'mousedown', handleClickOutSide );

if ( prevProps.isVisible && prevProps.onClickOutside )
{
if ( this.props.isVisible )
return () =>
{
if ( this.props.onClickOutside )
{
if ( prevProps.onClickOutside !==
this.props.onClickOutside )
{
removeEventListener(
'mousedown',
this.handleClickOutSide,
false,
);

addEventListener(
'mousedown',
this.handleClickOutSide,
false,
);
}
}
else
{
removeEventListener(
'mousedown',
this.handleClickOutSide,
false,
);
}
}
else
{
removeEventListener(
'mousedown',
this.handleClickOutSide,
false,
);
}
removeEventListener( 'mousedown', handleClickOutSide );
};
}
else if ( this.props.isVisible && this.props.onClickOutside )
{
addEventListener( 'mousedown', this.handleClickOutSide, false );
}
}

componentWillUnmount()
{
if ( this.props.onClickOutside )
{
removeEventListener( 'mousedown', this.handleClickOutSide, false );
}
}
}, [ scheduleUpdateRef.current, isVisible, onClickOutside ] );

handleClickOutSide( e )
const handleClickOutSide = useCallback( ( e ) =>
{
if ( !( this.referenceRef.current.contains( e.target ) ||
this.popperRef.current.contains( e.target ) ) )
if ( !( referenceRef.current.contains( e.target ) ||
popperRef.current.contains( e.target ) ) )
{
this.props.onClickOutside();
onClickOutside();
}
}

render()
{
const {
children,
container,
isVisible,
matchRefWidth,
popper,
popperOffset,
popperPosition,
} = this.props;

const offset = {
'S' : '8px',
'M' : '16px',
'L' : '24px',
'XL' : '32px',
'none' : undefined,
}[ popperOffset ];

return (
<Manager>
<Reference
innerRef = { ( ref ) => this.referenceRef.current = ref } >
{ ( { ref } ) => (
<div ref = { ref }>
{ children }
</div>
) }
</Reference>
{ isVisible && ReactDOM.createPortal(
<Popper
placement = { popperPosition }
innerRef = { ( ref ) => this.popperRef.current = ref }
modifiers = { offset ? {
offset : {
offset : `0, ${offset}`,
},
} : offset }>
{ ( { ref, style, scheduleUpdate } ) =>
{
this.scheduleUpdate = scheduleUpdate;

return (
<div
ref = { ref }
style = { matchRefWidth ? {
'width' : this.referenceRef.current
.clientWidth,
...style,
} : style }>
{ popper }
</div> );
} }
</Popper>,
document.getElementById( container ) || document.body,
}, [ onClickOutside ] );

const offset = {
'S' : '8px',
'M' : '16px',
'L' : '24px',
'XL' : '32px',
'none' : undefined,
}[ popperOffset ];

return (
<Manager>
<Reference
innerRef = { ( ref ) => referenceRef.current = ref }>
{ ( { ref } ) => (
<div ref = { ref }>
{ children }
</div>
) }
</Manager>
);
}
}
</Reference>
{ isVisible && ReactDOM.createPortal(
<Popper
placement = { popperPosition }
innerRef = { ( ref ) => popperRef.current = ref }
modifiers = { offset ? {
offset : {
offset : `0, ${offset}`,
},
} : offset }>
{ ( { ref, style, scheduleUpdate } ) =>
{
scheduleUpdateRef.current = scheduleUpdate;

return (
<div
ref = { ref }
style = { matchRefWidth ? {
'width' : referenceRef.current
.clientWidth,
...style,
} : style }>
{ popper }
</div> );
} }
</Popper>,
document.getElementById( container ) || document.body,
) }
</Manager>
);
};


PopperWrapper.propTypes =
{
/**
* Reference node to attach popper
*/
children : PropTypes.node,
/**
* id of the DOM element used as container
*/
container : PropTypes.string,
/**
* Show / Hide popper
*/
isVisible : PropTypes.bool,
/**
* pop up width matches reference width
*/
matchRefWidth : PropTypes.bool,
/**
* Click Outside callback: ( e ) => ...
*/
onClickOutside : PropTypes.func,
/**
* Popper content node
*/
popper : PropTypes.node,
/**
* Popper offset
*/
popperOffset : PropTypes.oneOf( [ 'S', 'M', 'L', 'XL', 'none' ] ),
/**
* Popper position
*/
popperPosition : PropTypes.oneOf( [
'auto',
'auto-start',
'auto-end',
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
] ),
};

PopperWrapper.defaultProps =
{
children : undefined,
container : undefined,
isVisible : false,
matchRefWidth : undefined,
onClickOutside : undefined,
popper : undefined,
popperOffset : 'none',
popperPosition : 'auto',
};

PopperWrapper.displayComponent = componentName;

export default PopperWrapper;

0 comments on commit bb4e3f4

Please sign in to comment.