diff --git a/packages/patternfly-4/react-core/package.json b/packages/patternfly-4/react-core/package.json index be27efa7e73..a59fb7a03c7 100644 --- a/packages/patternfly-4/react-core/package.json +++ b/packages/patternfly-4/react-core/package.json @@ -29,6 +29,7 @@ "dependencies": { "@patternfly/react-icons": "^2.9.4", "@patternfly/react-styles": "^2.3.0", + "@tippy.js/react": "^1.1.1", "exenv": "^1.2.2", "focus-trap-react": "^4.0.1" }, diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.d.ts b/packages/patternfly-4/react-core/src/components/Popover/Popover.d.ts index 83329729270..b6bc683a336 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/Popover.d.ts +++ b/packages/patternfly-4/react-core/src/components/Popover/Popover.d.ts @@ -1,11 +1,59 @@ -import { SFC, HTMLProps, ReactNode } from 'react'; -import { OneOf } from '@patternfly/react-core/src/typeUtils'; - -export interface PopoverProps extends HTMLProps { - position: OneOf; - children: ReactNode; - header: string; - onClose?(event: React.SyntheticEvent): void +import React, { SFC, HTMLProps, ReactNode } from 'react'; +import { Omit } from '../../typeUtils'; +import { Instance, BasicPlacement } from 'tippy.js'; + +export const PopoverPosition: { + top: 'top'; + bottom: 'bottom'; + left: 'left'; + right: 'right'; +}; + +export interface PopoverProps extends Omit, 'children' | 'size'> { + /** Popover position */ + position?: BasicPlacement; + /** If true, tries to keep the popover in view by flipping it if necessary */ + enableFlip?: boolean; + /** Popover additional class */ + className?: string; + /** The reference element to which the popover is relatively placed to */ + children: React.ReactElement; + /** Accessible label, required when header is not present */ + 'aria-label'?: string; + /** Header content, leave empty for no header */ + headerContent?: ReactNode; + /** Body content */ + bodyContent: ReactNode; + /** + * True to show the popover programmatically. Used in conjunction with the shouldClose prop. + * By default, the popover child element handles click events automatically. If you want to control this programmatically, + * the popover will not auto-close if the Close button is clicked, ESC key is used, or if a click occurs outside the popover. + * Instead, the consumer is responsible for closing the popover themselves by adding a callback listener for the shouldClose prop. + */ + isVisible?: boolean; + /** + * Callback function that is only invoked when isVisible is also controlled. Called when the popover Close button is + * clicked or the ESC key is used + */ + shouldClose?(instance: Instance): void; + /** The element to append the popover to, defaults to body */ + appendTo?: Element | ((ref: Element) => Element); + /** Hides the popover when a click occurs outside */ + hideOnOutsideClick?: boolean; + /** Lifecycle function invoked when the popover begins to transition out. */ + onHide?(instance: Instance): void; + /** Lifecycle function invoked when the popover has fully transitioned out. */ + onHidden?(instance: Instance): void; + /** Lifecycle function invoked when the popover begins to transition in. */ + onShow?(instance: Instance): void; + /** Lifecycle function invoked when the popover has fully transitioned in. */ + onShown?(instance: Instance): void; + /** Lifecycle function invoked when the popover has been mounted to the DOM. */ + onMount?(instance: Instance): void; + /** z-index of the popover */ + zIndex?: number; + /** Size of the popover */ + size: 'small' | 'regular' | 'large'; } declare const Popover: SFC; diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.js b/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.js new file mode 100644 index 00000000000..a0dec2d92d5 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.js @@ -0,0 +1,21 @@ +import { Popover, PopoverPosition } from '@patternfly/react-core'; +import SimplePopover from './examples/SimplePopover'; +import AdvancedPopover from './examples/AdvancedPopover'; +import HeadlessPopover from './examples/HeadlessPopover'; + +export default { + title: 'Popover', + components: { + Popover + }, + enumValues: { + 'Object.keys(PopoverPosition).map(key => PopoverPosition[key])': Object.keys(PopoverPosition).map( + key => PopoverPosition[key] + ) + }, + examples: [ + { component: SimplePopover, title: 'Simple Popover' }, + { component: AdvancedPopover, title: 'Programmatically Controlled Popover' }, + { component: HeadlessPopover, title: 'Headless Popover' } + ] +}; diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.txt b/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.txt deleted file mode 100644 index 2180a4673e4..00000000000 --- a/packages/patternfly-4/react-core/src/components/Popover/Popover.docs.txt +++ /dev/null @@ -1,30 +0,0 @@ -import { - PopoverPosition, - PopoverDialog, - PopoverArrow, - PopoverContent, - PopoverCloseButton, - PopoverHeader, - PopoverBody -} from '@patternfly/react-core'; -import SimplePopover from './examples/SimplePopover'; -import HeadlessPopover from './examples/HeadlessPopover'; - -export default { - title: 'Popover', - components: { - PopoverDialog, - PopoverArrow, - PopoverContent, - PopoverCloseButton, - PopoverHeader, - PopoverBody - }, - enumValues: { - 'Object.values(PopoverPosition)': Object.values(PopoverPosition) - }, - examples: [ - { component: SimplePopover, title: 'Closable Popover Position' }, - { component: HeadlessPopover, title: 'Headless Popover' } - ] -}; diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.js b/packages/patternfly-4/react-core/src/components/Popover/Popover.js index ed0e087c539..4b1f1f42f07 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/Popover.js +++ b/packages/patternfly-4/react-core/src/components/Popover/Popover.js @@ -1,40 +1,304 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Tippy from '@tippy.js/react'; +import FocusTrap from 'focus-trap-react'; +import { KEY_CODES } from '../../internal/constants'; import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; -import { css, getModifier } from '@patternfly/react-styles'; -import PopoverDialog, { PopoverPosition } from './PopoverDialog'; +import { StyleSheet, css, getModifier } from '@patternfly/react-styles'; import PopoverArrow from './PopoverArrow'; import PopoverContent from './PopoverContent'; -import PopoverHeader from './PopoverHeader'; import PopoverBody from './PopoverBody'; +import PopoverHeader from './PopoverHeader'; import PopoverCloseButton from './PopoverCloseButton'; +import GenerateId from '../../internal/GenerateId/GenerateId'; + +// Need to unset tippy default styles +// Also for enableFlip, need to make arrow aware of parent x-placement attribute in order to work +const overrides = StyleSheet.parse(` + .pf-tippy-theme { + &.tippy-tooltip { + background-color: unset; + font-size: unset; + color: unset; + border-radius: unset; + max-width: unset; + text-align: unset; + } + } + .tippy-popper[x-placement^=top] .pf-c-popover__arrow { + bottom: 0; + left: 50%; + transform: var(--pf-c-popover__arrow--m-top--Transform); + } + .tippy-popper[x-placement^=bottom] .pf-c-popover__arrow { + top: 0; + left: 50%; + transform: var(--pf-c-popover__arrow--m-bottom--Transform); + } + .tippy-popper[x-placement^=left] .pf-c-popover__arrow { + top: 50%; + right: 0; + transform: var(--pf-c-popover__arrow--m-left--Transform); + } + .tippy-popper[x-placement^=right] .pf-c-popover__arrow { + top: 50%; + left: 0; + transform: var(--pf-c-popover__arrow--m-right--Transform); + } +`); +overrides.inject(); -const Popover = ({ position, header, onClose, children, className }) => ( - - - - - {header} - {children} - - -); +export const PopoverPosition = { + top: 'top', + bottom: 'bottom', + left: 'left', + right: 'right' +}; const propTypes = { /** Popover position */ - position: PropTypes.oneOf(Object.values(PopoverPosition)), - /** Popover header text */ - header: PropTypes.string.isRequired, - /** Popover body text */ - children: PropTypes.string.isRequired, - /** Popover onClose function */ - onClose: PropTypes.func + position: PropTypes.oneOf(Object.keys(PopoverPosition).map(key => PopoverPosition[key])), + /** If true, tries to keep the popover in view by flipping it if necessary */ + enableFlip: PropTypes.bool, + /** Popover additional class */ + className: PropTypes.string, + /** The reference element to which the popover is relatively placed to */ + children: PropTypes.element.isRequired, + /** Accessible label, required when header is not present */ + 'aria-label': props => { + if (!props.headerContent && !props['aria-label']) { + return new Error('aria-label is required when header is not used'); + } + return null; + }, + /** Header content, leave empty for no header */ + headerContent: PropTypes.node, + /** Body content */ + bodyContent: PropTypes.node.isRequired, + /** + * True to show the popover programmatically. Used in conjunction with the shouldClose prop. + * By default, the popover child element handles click events automatically. If you want to control this programmatically, + * the popover will not auto-close if the Close button is clicked, ESC key is used, or if a click occurs outside the popover. + * Instead, the consumer is responsible for closing the popover themselves by adding a callback listener for the shouldClose prop. + */ + isVisible: PropTypes.bool, + /** + * Callback function that is only invoked when isVisible is also controlled. Called when the popover Close button is + * clicked or the ESC key is used + */ + shouldClose: PropTypes.func, + /** The element to append the popover to, defaults to body */ + appendTo: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + /** Hides the popover when a click occurs outside (only works if isVisible is not controlled by the user) */ + hideOnOutsideClick: PropTypes.bool, + /** Lifecycle function invoked when the popover begins to transition out. */ + onHide: PropTypes.func, + /** Lifecycle function invoked when the popover has fully transitioned out. */ + onHidden: PropTypes.func, + /** Lifecycle function invoked when the popover begins to transition in. */ + onShow: PropTypes.func, + /** Lifecycle function invoked when the popover has fully transitioned in. */ + onShown: PropTypes.func, + /** Lifecycle function invoked when the popover has been mounted to the DOM. */ + onMount: PropTypes.func, + /** z-index of the popover */ + zIndex: PropTypes.number, + /** Size of the popover */ + size: PropTypes.oneOf(['small', 'regular', 'large']), + /** Aria label for the Close button */ + closeBtnAriaLabel: PropTypes.string }; -Popover.propTypes = propTypes; -Popover.defaultProps = { - onClose: () => {}, - position: 'top' +const defaultProps = { + position: 'top', + enableFlip: true, + className: null, + isVisible: null, + shouldClose: () => undefined, + 'aria-label': '', + headerContent: null, + appendTo: () => document.body, + hideOnOutsideClick: true, + onHide: () => undefined, + onHidden: () => undefined, + onShow: () => undefined, + onShown: () => undefined, + onMount: () => undefined, + zIndex: 9999, + size: 'regular', + closeBtnAriaLabel: 'Close' }; +class Popover extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false + }; + } + + hideOrNotify = () => { + if (this.props.isVisible === null) { + // Handle closing + this.tip.hide(); + } else { + // notify consumer + this.props.shouldClose(this.tip); + } + }; + + handleEscKeyClick = event => { + if (event.keyCode === KEY_CODES.ESCAPE_KEY && this.tip.state.isVisible) { + this.hideOrNotify(); + } + }; + + componentDidMount() { + document.addEventListener('keydown', this.handleEscKeyClick, false); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleEscKeyClick, false); + } + + storeTippyInstance = tip => { + this.tip = tip; + }; + + closePopover = () => { + this.hideOrNotify(); + }; + + hideAllPopovers = () => { + document.querySelectorAll('.tippy-popper').forEach(popper => { + popper._tippy && popper._tippy.hide(); + }); + }; + + onHide = tip => { + this.state.isOpen && this.setState({ isOpen: false }); + return this.props.onHide(tip); + }; + + onHidden = tip => this.props.onHidden(tip); + + onMount = tip => this.props.onMount(tip); + + onShow = tip => { + const { hideOnOutsideClick, isVisible, onShow } = this.props; + // hide all other open popovers first if events are managed by us + !hideOnOutsideClick && isVisible === null && this.hideAllPopovers(); + this.state.isOpen === false && this.setState({ isOpen: true }); + return onShow(tip); + }; + + onShown = tip => this.props.onShown(tip); + + render() { + const { + position, + enableFlip, + children, + className, + 'aria-label': ariaLabel, + headerContent, + bodyContent, + isVisible, + shouldClose, + appendTo, + hideOnOutsideClick, + onHide, + onHidden, + onShow, + onShown, + onMount, + zIndex, + size, + closeBtnAriaLabel, + ...rest + } = this.props; + const content = ( + + {randomId => + this.state.isOpen && ( + +
+ + + + {headerContent && {headerContent}} + {bodyContent} + +
+
+ ) + } +
+ ); + const handleEvents = isVisible === null; + const shouldHideOnClick = () => { + if (handleEvents) { + if (hideOnOutsideClick === true) { + return true; + } + return 'toggle'; + } + return false; + }; + return ( + + {children} + + ); + } +} + +Popover.propTypes = propTypes; +Popover.defaultProps = defaultProps; + export default Popover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.test.js b/packages/patternfly-4/react-core/src/components/Popover/Popover.test.js index ae7acfe265f..eb7e9fcc796 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/Popover.test.js +++ b/packages/patternfly-4/react-core/src/components/Popover/Popover.test.js @@ -1,21 +1,22 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import { Popover } from './index'; -import { Button } from '@patternfly/react-core'; test('popover renders close-button, header and body', () => { - const view = mount(popover body); - expect(view).toMatchSnapshot(); -}); - -test('popover is calling onClose when clicking the close button', () => { - const onClose = jest.fn(); - const view = mount( - - popover body + const view = shallow( + Popover Header} + bodyContent={ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla turpis. +
+ } + > +
Toggle Popover
); - expect(onClose.mock.calls).toHaveLength(0); - view.find(Button).simulate('click'); - expect(onClose.mock.calls).toHaveLength(1); + expect(view).toMatchSnapshot(); }); diff --git a/packages/patternfly-4/react-core/src/components/Popover/Popover.tsx.txt b/packages/patternfly-4/react-core/src/components/Popover/Popover.tsx.txt new file mode 100644 index 00000000000..902d06616df --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Popover/Popover.tsx.txt @@ -0,0 +1,246 @@ +import React, { HTMLProps, ReactNode } from 'react'; +import { Omit } from '../../typeUtils'; +import PropTypes from 'prop-types'; +import Tippy from '@tippy.js/react'; +import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; +import { StyleSheet, css, getModifier } from '@patternfly/react-styles'; +import PopoverArrow from './PopoverArrow'; +import PopoverContent from './PopoverContent'; +import PopoverBody from './PopoverBody'; +import PopoverHeader from './PopoverHeader'; +import PopoverCloseButton from './PopoverCloseButton'; +import GenerateId from '../../internal/GenerateId/GenerateId'; +import { Instance, BasicPlacement } from 'tippy.js'; + +// Need to unset tippy default styles +// Also for enableFlip, need to make arrow aware of parent x-placement attribute in order to work +const overrides = StyleSheet.parse(` + .pf-tippy-theme { + &.tippy-tooltip { + background-color: unset; + font-size: unset; + color: unset; + border-radius: unset; + max-width: unset; + text-align: unset; + } + } + .tippy-popper[x-placement^=top] .pf-c-popover__arrow { + bottom: 0; + left: 50%; + transform: var(--pf-c-popover__arrow--m-top--Transform); + } + .tippy-popper[x-placement^=bottom] .pf-c-popover__arrow { + top: 0; + left: 50%; + transform: var(--pf-c-popover__arrow--m-bottom--Transform); + } + .tippy-popper[x-placement^=left] .pf-c-popover__arrow { + top: 50%; + right: 0; + transform: var(--pf-c-popover__arrow--m-left--Transform); + } + .tippy-popper[x-placement^=right] .pf-c-popover__arrow { + top: 50%; + left: 0; + transform: var(--pf-c-popover__arrow--m-right--Transform); + } +`); +overrides.inject(); + +export const PopoverPosition = { + top: 'top', + bottom: 'bottom', + left: 'left', + right: 'right' +}; + +export interface PopoverProps extends Omit, 'children'> { + /** Popover position */ + position?: BasicPlacement; + /** If true, tries to keep the popover in view by flipping it if necessary */ + enableFlip?: boolean; + /** Popover additional class */ + className?: string; + /** The reference element to which the popover is relatively placed to */ + children: React.ReactElement; + /** Accessible label, required when header is not present */ + 'aria-label'?: string; + /** Header content, leave empty for no header */ + headerContent?: ReactNode; + /** Body content */ + bodyContent: ReactNode; + /** True to show the popover */ + isVisible: boolean; + /** The element to append the popover to, defaults to body */ + appendTo?: Element | ((ref: Element) => Element); + /** Hides the popover when a click occurs outside */ + hideOnOutsideClick?: boolean; + /** Lifecycle function invoked when the popover begins to transition out. */ + onHide?(instance: Instance): void; + /** Lifecycle function invoked when the popover has fully transitioned out. */ + onHidden?(instance: Instance): void; + /** Lifecycle function invoked when the popover begins to transition in. */ + onShow?(instance: Instance): void; + /** Lifecycle function invoked when the popover has fully transitioned in. */ + onShown?(instance: Instance): void; + /** Lifecycle function invoked when the popover has been mounted to the DOM. */ + onMount?(instance: Instance): void; + /** z-index of the popover */ + zIndex?: number; +} + +const propTypes = { + /** Popover position */ + position: PropTypes.oneOf(Object.keys(PopoverPosition).map(key => PopoverPosition[key])), + /** If true, tries to keep the popover in view by flipping it if necessary */ + enableFlip: PropTypes.bool, + /** Popover additional class */ + className: PropTypes.string, + /** The reference element to which the popover is relatively placed to */ + children: PropTypes.element.isRequired, + /** Accessible label, required when header is not present */ + 'aria-label': props => { + if (!props.headerContent) { + return new Error('aria-label is required when header is not used'); + } + return null; + }, + /** Header content, leave empty for no header */ + headerContent: PropTypes.node, + /** Body content */ + bodyContent: PropTypes.node.isRequired, + /** True to show the popover */ + isVisible: PropTypes.bool.isRequired, + /** The element to append the popover to, defaults to body */ + appendTo: PropTypes.func, + /** Hides the popover when a click occurs outside */ + hideOnOutsideClick: PropTypes.bool, + /** Lifecycle function invoked when the popover begins to transition out. */ + onHide: PropTypes.func, + /** Lifecycle function invoked when the popover has fully transitioned out. */ + onHidden: PropTypes.func, + /** Lifecycle function invoked when the popover begins to transition in. */ + onShow: PropTypes.func, + /** Lifecycle function invoked when the popover has fully transitioned in. */ + onShown: PropTypes.func, + /** Lifecycle function invoked when the popover has been mounted to the DOM. */ + onMount: PropTypes.func, + /** z-index of the popover */ + zIndex: PropTypes.number +}; + +class Popover extends React.Component { + tip: any; + + constructor(props: PopoverProps) { + super(props); + } + + public static defaultProps = { + position: 'top', + enableFlip: true, + className: null, + 'aria-label': '', + headerContent: null, + appendTo: () => document.body, + hideOnOutsideClick: true, + onHide: () => undefined, + onHidden: () => undefined, + onShow: () => undefined, + onShown: () => undefined, + onMount: () => undefined, + zIndex: 9999 + }; + + storeTippyInstance = tip => { + this.tip = tip; + }; + + closePopover = () => { + this.tip.hide(); + }; + + render() { + const { + position, + enableFlip, + children, + className, + 'aria-label': ariaLabel, + headerContent, + bodyContent, + isVisible, + appendTo, + hideOnOutsideClick, + onHide, + onHidden, + onShow, + onShown, + onMount, + zIndex, + ...rest + } = this.props; + const content: React.ReactElement = ( + + {randomId => ( +
+ + + + {headerContent && {headerContent}} + {bodyContent} + +
+ )} +
+ ); + return ( + + {children} + + ); + } +} + +export default Popover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverArrow.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverArrow.js index 28521f1a22e..233606c4295 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverArrow.js +++ b/packages/patternfly-4/react-core/src/components/Popover/PopoverArrow.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; import { css } from '@patternfly/react-styles'; -const PopoverArrow = ({ className, ...props }) =>
; +const PopoverArrow = ({ className, ...rest }) =>
; PopoverArrow.propTypes = { /** Popover arrow additional className */ diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverBody.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverBody.js index 75acebfb115..d46d761938e 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverBody.js +++ b/packages/patternfly-4/react-core/src/components/Popover/PopoverBody.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; import { css } from '@patternfly/react-styles'; -const PopoverBody = ({ children, id }) => ( -
+const PopoverBody = ({ children, id, ...rest }) => ( +
{children}
); diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverCloseButton.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverCloseButton.js index f6b19762232..cf7b176fa00 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverCloseButton.js +++ b/packages/patternfly-4/react-core/src/components/Popover/PopoverCloseButton.js @@ -5,9 +5,9 @@ import { css } from '@patternfly/react-styles'; import { Button } from '@patternfly/react-core'; import { TimesIcon } from '@patternfly/react-icons'; -const PopoverCloseButton = ({ onClose }) => ( -
-
@@ -15,7 +15,9 @@ const PopoverCloseButton = ({ onClose }) => ( PopoverCloseButton.propTypes = { /** PopoverCloseButton onClose function */ - onClose: PropTypes.func.isRequired + onClose: PropTypes.func.isRequired, + /** Aria label for the Close button */ + 'aria-label': PropTypes.string.isRequired }; export default PopoverCloseButton; diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverContent.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverContent.js index ef69a03c23c..7285d8bd57a 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverContent.js +++ b/packages/patternfly-4/react-core/src/components/Popover/PopoverContent.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; import { css } from '@patternfly/react-styles'; -const PopoverContent = ({ className, children, ...props }) => ( -
+const PopoverContent = ({ className, children, ...rest }) => ( +
{children}
); diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.d.ts b/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.d.ts deleted file mode 100644 index 883247d1916..00000000000 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SFC, HTMLProps, ReactNode } from 'react'; -import { OneOf, Omit } from '../../typeUtils'; - -export const PopoverPosition: { - top: 'top'; - bottom: 'bottom'; - left: 'left'; - right: 'right'; -}; - -export interface PopoverDialogProps extends Omit, 'children'> { - children: ReactNode - position: OneOf; -} - -declare const PopoverDialog: SFC; - -export default PopoverDialog; - diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.js deleted file mode 100644 index 92aba019e1b..00000000000 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverDialog.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; -import { css, getModifier } from '@patternfly/react-styles'; - -export const PopoverPosition = { - top: 'top', - bottom: 'bottom', - left: 'left', - right: 'right' -}; - -const PopoverDialog = ({ position, children, className, ...props }) => ( -
- {children} -
-); - -PopoverDialog.propTypes = { - /** PopoverDialog position */ - position: PropTypes.oneOf(Object.values(PopoverPosition)), - /** PopoverDialog additional class */ - className: PropTypes.string, - /** PopoverDialog body */ - children: PropTypes.node.isRequired -}; - -PopoverDialog.defaultProps = { - position: 'top', - className: null -}; - -export default PopoverDialog; diff --git a/packages/patternfly-4/react-core/src/components/Popover/PopoverHeader.js b/packages/patternfly-4/react-core/src/components/Popover/PopoverHeader.js index c7fc9c7492a..570ebfcc261 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/PopoverHeader.js +++ b/packages/patternfly-4/react-core/src/components/Popover/PopoverHeader.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import styles from '@patternfly/patternfly-next/components/Popover/popover.css'; import { css } from '@patternfly/react-styles'; -const PopoverHeader = ({ children, id }) => ( -
+const PopoverHeader = ({ children, id, ...rest }) => ( +

{children}

diff --git a/packages/patternfly-4/react-core/src/components/Popover/__snapshots__/Popover.test.js.snap b/packages/patternfly-4/react-core/src/components/Popover/__snapshots__/Popover.test.js.snap index d93b8df4d49..865ff156685 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/__snapshots__/Popover.test.js.snap +++ b/packages/patternfly-4/react-core/src/components/Popover/__snapshots__/Popover.test.js.snap @@ -1,174 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`popover renders close-button, header and body 1`] = ` -.pf-c-popover__arrow { - display: block; - position: absolute; - width: 1.5625rem; - height: 1.5625rem; - pointer-events: none; - background-color: #ffffff; - box-shadow: 0 0.0625rem 0.0625rem 0rem rgba(3, 3, 3, 0.05), 0 0.25rem 0.5rem 0.25rem rgba(3, 3, 3, 0.06); -} -.pf-c-button.pf-m-plain { - display: inline-block; - position: relative; - padding: 0.375rem 1rem; - font-size: 1rem; - font-weight: 500; - line-height: 1.5; - text-align: center; - white-space: nowrap; - border: 0px; - border-radius: 3px; - text-decoration: none; - color: #72767b; -} -.pf-c-popover__close { - display: block; - position: absolute; - top: 0.5rem; - right: 1rem; -} -.pf-c-popover__header-title { - display: block; - font-size: undefined; - font-weight: undefined; - line-height: undefined; -} -.pf-c-popover__header { - display: block; - margin-bottom: 1rem; -} -.pf-c-popover__body { - display: block; - word-wrap: break-word; -} -.pf-c-popover__content { - display: block; - position: relative; - padding: 2rem 2rem 2rem 2rem; - background-color: #ffffff; -} -.pf-c-popover.pf-m-top { - display: block; - position: relative; - min-width: 6.25rem; - max-width: 18.75rem; - box-shadow: 0 0.0625rem 0.0625rem 0rem rgba(3, 3, 3, 0.05), 0 0.25rem 0.5rem 0.25rem rgba(3, 3, 3, 0.06); -} - - - -
- -
- - -
- -
- - -
-
- -
-

- popover header -

-
-
- -
- popover body -
-
-
-
-
- - + [Function] + + } + distance={15} + flip={true} + hideOnClick={false} + interactive={true} + interactiveBorder={0} + isVisible={true} + lazy={true} + onCreate={[Function]} + onHidden={[Function]} + onHide={[Function]} + onMount={[Function]} + onShow={[Function]} + onShown={[Function]} + performance={true} + placement="top" + popperOptions={ + Object { + "modifiers": Object { + "hide": Object { + "enabled": true, + }, + "preventOverflow": Object { + "enabled": true, + }, + }, + } + } + size="regular" + theme="pf-tippy" + trigger="manual" + zIndex={9999} +> +
+ Toggle Popover +
+ `; diff --git a/packages/patternfly-4/react-core/src/components/Popover/examples/AdvancedPopover.js b/packages/patternfly-4/react-core/src/components/Popover/examples/AdvancedPopover.js new file mode 100644 index 00000000000..7fa66f40a43 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Popover/examples/AdvancedPopover.js @@ -0,0 +1,88 @@ +import React from 'react'; +import { Popover, PopoverPosition, Checkbox, Button } from '@patternfly/react-core'; + +class AdvancedPopover extends React.Component { + constructor(props) { + super(props); + this.state = { + position: PopoverPosition.top, + show: false, + keepInViewChecked: true + }; + } + + handleClick = () => { + this.setState({ + show: !this.state.show + }); + }; + + handleKeepInViewChange = checked => { + this.setState({ keepInViewChecked: checked }); + }; + + handleProgrammaticChange = checked => { + this.setState({ + show: checked + }); + }; + + shouldClose = tip => { + this.setState({ show: false }); + }; + + render() { + return ( +
+
+ Popover Position + + + +
+ +
+ document.getElementById('___gatsby')} + headerContent={
Popover Header
} + bodyContent={ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla turpis. +
+ } + > + +
+
+
+ ); + } +} + +export default AdvancedPopover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.js b/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.js new file mode 100644 index 00000000000..b62b5035b4b --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Popover, Button } from '@patternfly/react-core'; + +class HeadlessPopover extends React.Component { + render() { + return ( + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla turpis. +
+ } + aria-label="Headless Popover" + > + +
+ ); + } +} + +export default HeadlessPopover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.txt b/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.txt deleted file mode 100644 index 016efc5fd2a..00000000000 --- a/packages/patternfly-4/react-core/src/components/Popover/examples/HeadlessPopover.txt +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { - PopoverDialog, - PopoverArrow, - PopoverContent, - PopoverCloseButton, - PopoverHeader, - PopoverBody -} from '@patternfly/react-core'; - -class HeadlessPopover extends React.Component { - constructor(props) { - super(props); - this.popoverRef = React.createRef(); - this.outsideRef = React.createRef(); - this.state = { position: 'top', show: true }; - this.handleClick = this.handleClick.bind(this); - } - - componentDidMount() { - document.addEventListener('mousedown', this.handleClick, false); - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.handleClick, false); - } - - handleClick(event) { - const node = this.popoverRef && this.popoverRef.current; - const outside = this.outsideRef && this.outsideRef.current; - if (!outside || !outside.contains(event.target)) { - return; - } - if (!node || !node.contains(event.target)) { - this.setState({ show: false }); - } - } - - render() { - return ( -
-
- {this.state.show && ( - - - - this.setState({ show: false })} /> - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla - turpis. - - - - )} -
-
- ); - } -} - -export default HeadlessPopover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.js b/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.js new file mode 100644 index 00000000000..4dd9701b70c --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Popover, Button } from '@patternfly/react-core'; + +class SimplePopover extends React.Component { + render() { + return ( + Popover Header
} + bodyContent={ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla turpis. +
+ } + > + + + ); + } +} + +export default SimplePopover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.txt b/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.txt deleted file mode 100644 index 671bc12572f..00000000000 --- a/packages/patternfly-4/react-core/src/components/Popover/examples/SimplePopover.txt +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import { - PopoverDialog, - PopoverArrow, - PopoverContent, - PopoverCloseButton, - PopoverHeader, - PopoverBody -} from '@patternfly/react-core'; - -class SimplePopover extends React.Component { - constructor(props) { - super(props); - this.popoverRef = React.createRef(); - this.outsideRef = React.createRef(); - this.state = { position: 'top', show: true }; - this.handleClick = this.handleClick.bind(this); - } - - componentDidMount() { - document.addEventListener('mousedown', this.handleClick, false); - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.handleClick, false); - } - - handleClick(event) { - const node = this.popoverRef && this.popoverRef.current; - const outside = this.outsideRef && this.outsideRef.current; - if (!outside || !outside.contains(event.target)) { - return - } - if (!node || !node.contains(event.target)) { - this.setState({ show: false }); - } - } - - render() { - return ( -
- Popover Position - -
-
- {this.state.show && ( - - - - this.setState({ show: false })} /> - Popover Header - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id feugiat augue, nec fringilla - turpis. - - - - )} -
-
-
- ); - } -} - -export default SimplePopover; diff --git a/packages/patternfly-4/react-core/src/components/Popover/index.d.ts b/packages/patternfly-4/react-core/src/components/Popover/index.d.ts index 02df6c38e8c..d64864f1fe7 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/index.d.ts +++ b/packages/patternfly-4/react-core/src/components/Popover/index.d.ts @@ -1 +1 @@ -export { default as Popover } from './Popover'; +export { default as Popover, PopoverPosition, PopoverProps } from './Popover'; diff --git a/packages/patternfly-4/react-core/src/components/Popover/index.js b/packages/patternfly-4/react-core/src/components/Popover/index.js index e332a5777a9..1ca56c50bc9 100644 --- a/packages/patternfly-4/react-core/src/components/Popover/index.js +++ b/packages/patternfly-4/react-core/src/components/Popover/index.js @@ -1,7 +1 @@ -export { default as Popover } from './Popover'; -export { default as PopoverDialog, PopoverPosition } from './PopoverDialog'; -export { default as PopoverArrow } from './PopoverArrow'; -export { default as PopoverContent } from './PopoverContent'; -export { default as PopoverHeader } from './PopoverHeader'; -export { default as PopoverBody } from './PopoverBody'; -export { default as PopoverCloseButton } from './PopoverCloseButton'; +export { default as Popover, PopoverPosition } from './Popover'; diff --git a/packages/patternfly-4/react-core/src/components/index.d.ts b/packages/patternfly-4/react-core/src/components/index.d.ts index fee0e9a952a..eaa8b8bed41 100644 --- a/packages/patternfly-4/react-core/src/components/index.d.ts +++ b/packages/patternfly-4/react-core/src/components/index.d.ts @@ -20,7 +20,7 @@ export * from './LoginPage'; export * from './Modal'; export * from './Nav'; export * from './Page'; -// export * from './Popover'; // Not ready yet +export * from './Popover'; export * from './Progress'; export * from './Radio'; export * from './Select'; diff --git a/packages/patternfly-4/react-core/src/components/index.js b/packages/patternfly-4/react-core/src/components/index.js index 59d922cf415..f8b8f4fbc27 100644 --- a/packages/patternfly-4/react-core/src/components/index.js +++ b/packages/patternfly-4/react-core/src/components/index.js @@ -19,7 +19,7 @@ export * from './List'; export * from './LoginPage'; export * from './Modal'; export * from './Nav'; -// export * from './Popover'; // Not ready yet +export * from './Popover'; export * from './Page'; export * from './Progress'; export * from './Radio'; diff --git a/yarn.lock b/yarn.lock index e0fc49c6871..05d105f367e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1269,6 +1269,13 @@ react-split-pane "^0.1.77" react-treebeard "^2.1.0" +"@tippy.js/react@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@tippy.js/react/-/react-1.1.1.tgz#91476726529e31d4c9f9074d995d0fa31f3b6e2d" + dependencies: + prop-types "^15.6.2" + tippy.js "^3.2.0" + "@types/c3@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@types/c3/-/c3-0.6.0.tgz#74fe73461f18386ae460e8113ea4883ab7fbeab1" @@ -12089,6 +12096,10 @@ popper.js@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" +popper.js@^1.14.6: + version "1.14.6" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.6.tgz#ab20dd4edf9288b8b3b6531c47c361107b60b4b0" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -15680,6 +15691,12 @@ tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" +tippy.js@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-3.3.0.tgz#90d4f90be9c80fdc0d0025f49378e3d9f60508d3" + dependencies: + popper.js "^1.14.6" + title-case@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"