[UI Framework] Add KuiOutsideClickDetector (#13521) (#13618)

* Add KuiOutsideClickDetector.
* Convert KuiColorPicker and KuiPopover to use KuiOutsideClickDetector.
This commit is contained in:
CJ Cenizal 2017-08-21 09:00:43 -07:00 committed by GitHub
parent d5ce2e60fb
commit e087c4b7d9
7 changed files with 131 additions and 109 deletions

View file

@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { ChromePicker } from 'react-color';
import { KuiOutsideClickDetector } from '../outside_click_detector';
import { KuiColorPickerSwatch } from './color_picker_swatch';
export class KuiColorPicker extends React.Component {
@ -12,16 +13,9 @@ export class KuiColorPicker extends React.Component {
this.state = {
showColorSelector: false,
};
// Use this variable to differentiate between clicks on the element that should not cause the pop up
// to close, and external clicks that should cause the pop up to close.
this.clickedMyself = false;
}
closeColorSelector = () => {
if (this.clickedMyself) {
this.clickedMyself = false;
return;
}
this.setState({ showColorSelector: false });
};
@ -33,21 +27,6 @@ export class KuiColorPicker extends React.Component {
this.props.onChange(color.hex);
};
onClickRootElement = () => {
// This prevents clicking on the element from closing it, due to the event handler on the
// document object.
this.clickedMyself = true;
};
componentDidMount() {
// When the user clicks somewhere outside of the color picker, we will dismiss it.
document.addEventListener('click', this.closeColorSelector);
}
componentWillUnmount() {
document.removeEventListener('click', this.closeColorSelector);
}
getColorLabel() {
const { color } = this.props;
const colorValue = color === null ? '(transparent)' : color;
@ -65,30 +44,31 @@ export class KuiColorPicker extends React.Component {
const { color, className, showColorLabel } = this.props;
const classes = classNames('kuiColorPicker', className);
return (
<div
className={classes}
data-test-subj={this.props['data-test-subj']}
onClick={this.onClickRootElement}
>
<KuiOutsideClickDetector onOutsideClick={this.closeColorSelector}>
<div
className="kuiColorPicker__preview"
onClick={this.toggleColorSelector}
className={classes}
data-test-subj={this.props['data-test-subj']}
>
<KuiColorPickerSwatch color={color} aria-label={this.props['aria-label']} />
{ showColorLabel ? this.getColorLabel() : null }
<div
className="kuiColorPicker__preview"
onClick={this.toggleColorSelector}
>
<KuiColorPickerSwatch color={color} aria-label={this.props['aria-label']} />
{ showColorLabel ? this.getColorLabel() : null }
</div>
{
this.state.showColorSelector ?
<div className="kuiColorPickerPopUp" data-test-subj="colorPickerPopup">
<ChromePicker
color={color ? color : '#ffffff'}
disableAlpha={true}
onChange={this.handleColorSelection}
/>
</div>
: null
}
</div>
{
this.state.showColorSelector ?
<div className="kuiColorPickerPopUp" data-test-subj="colorPickerPopup">
<ChromePicker
color={color ? color : '#ffffff'}
disableAlpha={true}
onChange={this.handleColorSelection}
/>
</div>
: null
}
</div>
</KuiOutsideClickDetector>
);
}
}

View file

@ -96,6 +96,10 @@ export {
KuiModalOverlay,
} from './modal';
export {
KuiOutsideClickDetector,
} from './outside_click_detector';
export {
KuiPager,
KuiPagerButtonGroup,

View file

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`KuiOutsideClickDetector is rendered 1`] = `<div />`;

View file

@ -0,0 +1,3 @@
export {
KuiOutsideClickDetector,
} from './outside_click_detector';

View file

@ -0,0 +1,48 @@
import {
Children,
cloneElement,
Component,
} from 'react';
import PropTypes from 'prop-types';
export class KuiOutsideClickDetector extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onOutsideClick: PropTypes.func.isRequired,
}
onClickOutside = event => {
if (!this.wrapperRef) {
return;
}
if (this.wrapperRef === event.target) {
return;
}
if (this.wrapperRef.contains(event.target)) {
return;
}
this.props.onOutsideClick();
}
componentDidMount() {
document.addEventListener('click', this.onClickOutside);
}
componentWillUnmount() {
document.removeEventListener('click', this.onClickOutside);
}
render() {
const props = Object.assign({}, this.props.children.props, {
ref: node => {
this.wrapperRef = node;
},
});
const child = Children.only(this.props.children);
return cloneElement(child, props);
}
}

View file

@ -0,0 +1,17 @@
import React from 'react';
import { render } from 'enzyme';
import { KuiOutsideClickDetector } from './outside_click_detector';
describe('KuiOutsideClickDetector', () => {
test('is rendered', () => {
const component = render(
<KuiOutsideClickDetector onOutsideClick={() => {}}>
<div />
</KuiOutsideClickDetector>
);
expect(component)
.toMatchSnapshot();
});
});

View file

@ -1,7 +1,9 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { KuiOutsideClickDetector } from '../outside_click_detector';
const anchorPositionToClassNameMap = {
'center': '',
'left': 'kuiPopover--anchorLeft',
@ -10,80 +12,45 @@ const anchorPositionToClassNameMap = {
export const ANCHOR_POSITIONS = Object.keys(anchorPositionToClassNameMap);
export class KuiPopover extends Component {
constructor(props) {
super(props);
export const KuiPopover = ({
anchorPosition,
bodyClassName,
button,
isOpen,
children,
className,
closePopover,
...rest,
}) => {
const classes = classNames(
'kuiPopover',
anchorPositionToClassNameMap[anchorPosition],
className,
{
'kuiPopover-isOpen': isOpen,
},
);
// Use this variable to differentiate between clicks on the element that should not cause the pop up
// to close, and external clicks that should cause the pop up to close.
this.didClickMyself = false;
}
const bodyClasses = classNames('kuiPopover__body', bodyClassName);
closePopover = () => {
if (this.didClickMyself) {
this.didClickMyself = false;
return;
}
const body = (
<div className={bodyClasses}>
{ children }
</div>
);
this.props.closePopover();
};
onClickRootElement = () => {
// This prevents clicking on the element from closing it, due to the event handler on the
// document object.
this.didClickMyself = true;
};
componentDidMount() {
// When the user clicks somewhere outside of the color picker, we will dismiss it.
document.addEventListener('click', this.closePopover);
}
componentWillUnmount() {
document.removeEventListener('click', this.closePopover);
}
render() {
const {
anchorPosition,
bodyClassName,
button,
isOpen,
children,
className,
closePopover, // eslint-disable-line no-unused-vars
...rest,
} = this.props;
const classes = classNames(
'kuiPopover',
anchorPositionToClassNameMap[anchorPosition],
className,
{
'kuiPopover-isOpen': isOpen,
},
);
const bodyClasses = classNames('kuiPopover__body', bodyClassName);
const body = (
<div className={bodyClasses}>
{ children }
</div>
);
return (
return (
<KuiOutsideClickDetector onOutsideClick={closePopover}>
<div
onClick={this.onClickRootElement}
className={classes}
{...rest}
>
{ button }
{ body }
</div>
);
}
}
</KuiOutsideClickDetector>
);
};
KuiPopover.propTypes = {
isOpen: PropTypes.bool,