diff --git a/ui_framework/src/components/color_picker/color_picker.js b/ui_framework/src/components/color_picker/color_picker.js index 6c7ada1a9683..41965cb48a41 100644 --- a/ui_framework/src/components/color_picker/color_picker.js +++ b/ui_framework/src/components/color_picker/color_picker.js @@ -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 ( -
+
- - { showColorLabel ? this.getColorLabel() : null } +
+ + { showColorLabel ? this.getColorLabel() : null } +
+ { + this.state.showColorSelector ? +
+ +
+ : null + }
- { - this.state.showColorSelector ? -
- -
- : null - } -
+ ); } } diff --git a/ui_framework/src/components/index.js b/ui_framework/src/components/index.js index fe3d7d3eaf83..4e731c522824 100644 --- a/ui_framework/src/components/index.js +++ b/ui_framework/src/components/index.js @@ -96,6 +96,10 @@ export { KuiModalOverlay, } from './modal'; +export { + KuiOutsideClickDetector, +} from './outside_click_detector'; + export { KuiPager, KuiPagerButtonGroup, diff --git a/ui_framework/src/components/outside_click_detector/__snapshots__/outside_click_detector.test.js.snap b/ui_framework/src/components/outside_click_detector/__snapshots__/outside_click_detector.test.js.snap new file mode 100644 index 000000000000..6345a66dd3ab --- /dev/null +++ b/ui_framework/src/components/outside_click_detector/__snapshots__/outside_click_detector.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiOutsideClickDetector is rendered 1`] = `
`; diff --git a/ui_framework/src/components/outside_click_detector/index.js b/ui_framework/src/components/outside_click_detector/index.js new file mode 100644 index 000000000000..a05f19936044 --- /dev/null +++ b/ui_framework/src/components/outside_click_detector/index.js @@ -0,0 +1,3 @@ +export { + KuiOutsideClickDetector, +} from './outside_click_detector'; diff --git a/ui_framework/src/components/outside_click_detector/outside_click_detector.js b/ui_framework/src/components/outside_click_detector/outside_click_detector.js new file mode 100644 index 000000000000..cf5e15836a42 --- /dev/null +++ b/ui_framework/src/components/outside_click_detector/outside_click_detector.js @@ -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); + } +} diff --git a/ui_framework/src/components/outside_click_detector/outside_click_detector.test.js b/ui_framework/src/components/outside_click_detector/outside_click_detector.test.js new file mode 100644 index 000000000000..942abf51010b --- /dev/null +++ b/ui_framework/src/components/outside_click_detector/outside_click_detector.test.js @@ -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( + {}}> +
+ + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/ui_framework/src/components/popover/popover.js b/ui_framework/src/components/popover/popover.js index ce55ab06d937..4c2eec8e5d81 100644 --- a/ui_framework/src/components/popover/popover.js +++ b/ui_framework/src/components/popover/popover.js @@ -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 = ( +
+ { children } +
+ ); - 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 = ( -
- { children } -
- ); - - return ( + return ( +
{ button } { body }
- ); - } -} +
+ ); +}; KuiPopover.propTypes = { isOpen: PropTypes.bool,