Skip to content

Commit

Permalink
feat(Popover): Add PF4 Popover (patternfly#754)
Browse files Browse the repository at this point in the history
* feat(Popover): Add PF4 Popover

Signed-off-by: Boaz Shuster <boaz.shuster.github@gmail.com>

* change example and docs from js to txt otherwise build blows up

* update snapshot
  • Loading branch information
boaz0 authored and tlabaj committed Nov 26, 2018
1 parent 45c3647 commit 17cf0c0
Show file tree
Hide file tree
Showing 28 changed files with 630 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/patternfly-4/react-charts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@
"glob": "^7.1.2",
"npmlog": "^4.1.2"
}
}
}
2 changes: 1 addition & 1 deletion packages/patternfly-4/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@
"glob": "^7.1.2",
"npmlog": "^4.1.2"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SFC, HTMLProps, ReactNode } from 'react';
import { OneOf } from '@patternfly/react-core/src/typeUtils';

export interface PopoverProps extends HTMLProps<HTMLDivElement> {
position: OneOf<typeof PopoverPosition, keyof typeof PopoverPosition>;
children: ReactNode;
header: string;
onClose?(event: React.SyntheticEvent<HTMLButtonElement>): void
}

declare const Popover: SFC<PopoverProps>;

export default Popover;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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' }
]
};
40 changes: 40 additions & 0 deletions packages/patternfly-4/react-core/src/components/Popover/Popover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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';
import PopoverDialog, { PopoverPosition } from './PopoverDialog';
import PopoverArrow from './PopoverArrow';
import PopoverContent from './PopoverContent';
import PopoverHeader from './PopoverHeader';
import PopoverBody from './PopoverBody';
import PopoverCloseButton from './PopoverCloseButton';

const Popover = ({ position, header, onClose, children, className }) => (
<PopoverDialog position={position}>
<PopoverArrow />
<PopoverContent>
<PopoverCloseButton onClose={onClose} />
<PopoverHeader id={`popover-${position}-header`}>{header}</PopoverHeader>
<PopoverBody id={`popover-${position}-body`}>{children}</PopoverBody>
</PopoverContent>
</PopoverDialog>
);

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
};

Popover.propTypes = propTypes;
Popover.defaultProps = {
onClose: () => {},
position: 'top'
};

export default Popover;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { mount } from 'enzyme';
import { Popover } from './index';
import { Button } from '@patternfly/react-core';

test('popover renders close-button, header and body', () => {
const view = mount(<Popover header="popover header">popover body</Popover>);
expect(view).toMatchSnapshot();
});

test('popover is calling onClose when clicking the close button', () => {
const onClose = jest.fn();
const view = mount(
<Popover header="popover header" onClose={onClose}>
popover body
</Popover>
);
expect(onClose.mock.calls).toHaveLength(0);
view.find(Button).simulate('click');
expect(onClose.mock.calls).toHaveLength(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SFC, HTMLProps } from 'react';

export interface PopoverArrowProps extends HTMLProps<HTMLDivElement> {
}

declare const PopoverArrow: SFC<PopoverArrowProps>;

export default PopoverArrow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
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 }) => <div className={css(styles.popoverArrow, className)} {...props} />;

PopoverArrow.propTypes = {
/** Popover arrow additional className */
className: PropTypes.string
};

PopoverArrow.defaultProps = {
className: null
};

export default PopoverArrow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SFC, ReactNode } from 'react';

export interface PopoverBodyProps {
id: string
children: ReactNode;
}

declare const PopoverBody: SFC<PopoverBodyProps>;

export default PopoverBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
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 }) => (
<div className={css(styles.popoverBody)} id={id}>
{children}
</div>
);

PopoverBody.propTypes = {
/** PopoverBody id */
id: PropTypes.string.isRequired,
/** PopoverBody content */
children: PropTypes.node.isRequired
};

export default PopoverBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SFC } from 'react';

export interface PopoverCloseButtonProps {
onClose(event: React.SyntheticEvent<HTMLButtonElement>): void
}

declare const PopoverCloseButton : SFC<PopoverCloseButtonProps>;

export default PopoverCloseButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from '@patternfly/patternfly-next/components/Popover/popover.css';
import { css } from '@patternfly/react-styles';
import { Button } from '@patternfly/react-core';
import { TimesIcon } from '@patternfly/react-icons';

const PopoverCloseButton = ({ onClose }) => (
<div className={css(styles.popoverClose)}>
<Button onClick={onClose} variant="plain" aria-label="Action">
<TimesIcon />
</Button>
</div>
);

PopoverCloseButton.propTypes = {
/** PopoverCloseButton onClose function */
onClose: PropTypes.func.isRequired
};

export default PopoverCloseButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SFC, HTMLProps } from 'react';

export interface PopoverContentProps extends HTMLProps<HTMLDivElement> {
}

declare const PopoverContent: SFC<PopoverContentProps>;

export default PopoverContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
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 }) => (
<div className={css(styles.popoverContent, className)} {...props}>
{children}
</div>
);

PopoverContent.propTypes = {
/** PopoverContent additional class */
className: PropTypes.string,
/** PopoverContent content */
children: PropTypes.node.isRequired,
};

PopoverContent.defaultProps = {
className: null
};

export default PopoverContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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<HTMLProps<HTMLDivElement>, 'children'> {
children: ReactNode
position: OneOf<typeof PopoverPosition, keyof typeof PopoverPosition>;
}

declare const PopoverDialog: SFC<PopoverDialogProps>;

export default PopoverDialog;

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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 }) => (
<div
className={css(styles.popover, getModifier(styles, position, styles.modifiers.top), className)}
role="dialog"
aria-modal="true"
{...props}
>
{children}
</div>
);

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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SFC, ReactNode } from 'react';

export interface PopoverHeaderProps {
id: string
children: ReactNode;
}

declare const PopoverHeader: SFC<PopoverHeaderProps>;

export default PopoverHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
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 }) => (
<header className={css(styles.popoverHeader)}>
<h1 className={css(styles.popoverHeaderTitle)} id={id}>
{children}
</h1>
</header>
);

PopoverHeader.propTypes = {
/** popover id */
id: PropTypes.string.isRequired,
/** header node */
children: PropTypes.node.isRequired
};

export default PopoverHeader;
Loading

0 comments on commit 17cf0c0

Please sign in to comment.