From 04a3b0705fa20bc049430651bfd69c14ceead0de Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Tue, 31 Jul 2018 17:16:33 +0200 Subject: [PATCH] feat(pf4-dropdown): introduce new component Dropdown Update docs of dropdown affects: @patternfly/react-core ISSUES CLOSED: #387 --- package.json | 2 +- .../src/components/Dropdown/Dropdown.d.ts | 26 + .../src/components/Dropdown/Dropdown.docs.js | 17 + .../src/components/Dropdown/Dropdown.js | 85 + .../src/components/Dropdown/Dropdown.test.js | 153 + .../src/components/Dropdown/DropdownItem.d.ts | 7 + .../src/components/Dropdown/DropdownItem.js | 56 + .../components/Dropdown/DropdownItem.test.js | 51 + .../src/components/Dropdown/DropdownMenu.js | 30 + .../components/Dropdown/DropdownToggle.d.ts | 12 + .../src/components/Dropdown/DropdownToggle.js | 44 + .../Dropdown/DropdownToggle.test.js | 99 + .../src/components/Dropdown/Item.d.ts | 9 + .../src/components/Dropdown/Item.js | 38 + .../src/components/Dropdown/KebabToggle.d.ts | 9 + .../src/components/Dropdown/KebabToggle.js | 46 + .../src/components/Dropdown/Separator.d.ts | 9 + .../src/components/Dropdown/Separator.js | 38 + .../src/components/Dropdown/Toggle.js | 89 + .../src/components/Dropdown/Toggle.test.js | 13 + .../__snapshots__/Dropdown.test.js.snap | 2984 +++++++++++++++++ .../__snapshots__/DropdownItem.test.js.snap | 101 + .../__snapshots__/DropdownToggle.test.js.snap | 214 ++ .../__snapshots__/Toggle.test.js.snap | 137 + .../Dropdown/examples/DirectionUpDropdown.js | 50 + .../Dropdown/examples/KebabDropdown.js | 49 + .../examples/PositionRightDropdown.js | 50 + .../Dropdown/examples/SimpleDropdown.js | 49 + .../src/components/Dropdown/index.d.ts | 9 + .../src/components/Dropdown/index.js | 5 + packages/react-core/src/components/index.d.ts | 2 + packages/react-core/src/components/index.js | 1 + 32 files changed, 4483 insertions(+), 1 deletion(-) create mode 100644 packages/react-core/src/components/Dropdown/Dropdown.d.ts create mode 100644 packages/react-core/src/components/Dropdown/Dropdown.docs.js create mode 100644 packages/react-core/src/components/Dropdown/Dropdown.js create mode 100644 packages/react-core/src/components/Dropdown/Dropdown.test.js create mode 100644 packages/react-core/src/components/Dropdown/DropdownItem.d.ts create mode 100644 packages/react-core/src/components/Dropdown/DropdownItem.js create mode 100644 packages/react-core/src/components/Dropdown/DropdownItem.test.js create mode 100644 packages/react-core/src/components/Dropdown/DropdownMenu.js create mode 100644 packages/react-core/src/components/Dropdown/DropdownToggle.d.ts create mode 100644 packages/react-core/src/components/Dropdown/DropdownToggle.js create mode 100644 packages/react-core/src/components/Dropdown/DropdownToggle.test.js create mode 100644 packages/react-core/src/components/Dropdown/Item.d.ts create mode 100644 packages/react-core/src/components/Dropdown/Item.js create mode 100644 packages/react-core/src/components/Dropdown/KebabToggle.d.ts create mode 100644 packages/react-core/src/components/Dropdown/KebabToggle.js create mode 100644 packages/react-core/src/components/Dropdown/Separator.d.ts create mode 100644 packages/react-core/src/components/Dropdown/Separator.js create mode 100644 packages/react-core/src/components/Dropdown/Toggle.js create mode 100644 packages/react-core/src/components/Dropdown/Toggle.test.js create mode 100644 packages/react-core/src/components/Dropdown/__snapshots__/Dropdown.test.js.snap create mode 100644 packages/react-core/src/components/Dropdown/__snapshots__/DropdownItem.test.js.snap create mode 100644 packages/react-core/src/components/Dropdown/__snapshots__/DropdownToggle.test.js.snap create mode 100644 packages/react-core/src/components/Dropdown/__snapshots__/Toggle.test.js.snap create mode 100644 packages/react-core/src/components/Dropdown/examples/DirectionUpDropdown.js create mode 100644 packages/react-core/src/components/Dropdown/examples/KebabDropdown.js create mode 100644 packages/react-core/src/components/Dropdown/examples/PositionRightDropdown.js create mode 100644 packages/react-core/src/components/Dropdown/examples/SimpleDropdown.js create mode 100644 packages/react-core/src/components/Dropdown/index.d.ts create mode 100644 packages/react-core/src/components/Dropdown/index.js diff --git a/package.json b/package.json index 7f9e3257136..1b3997e7c64 100644 --- a/package.json +++ b/package.json @@ -206,4 +206,4 @@ "dependencies": { "babel-plugin-transform-imports": "^1.5.0" } -} +} \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/Dropdown.d.ts b/packages/react-core/src/components/Dropdown/Dropdown.d.ts new file mode 100644 index 00000000000..a317820ad3d --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Dropdown.d.ts @@ -0,0 +1,26 @@ +import { SFC, HTMLProps, ReactType, ReactNode } from 'react'; +import { OneOf } from '../../typeUtils'; +import { DropdownToggleProps } from './DropdownToggle'; + +export const DropdownPosition: { + right: 'right'; + left: 'left'; +} + +export const DropdownDirection: { + up: 'up'; + down: 'down'; +} + +export interface DropdownProps extends HTMLProps { + children?: ReactNode; + isOpen?: Boolean; + isAction?: Boolean; + position?: OneOf; + direction?: OneOf; + toggle?: DropdownToggleProps; +} + +declare const Dropdown: SFC; + +export default Dropdown; \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/Dropdown.docs.js b/packages/react-core/src/components/Dropdown/Dropdown.docs.js new file mode 100644 index 00000000000..b0e6c1bff8c --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Dropdown.docs.js @@ -0,0 +1,17 @@ +import { Dropdown, KebabToggle, DropdownItem, DropdownSeparator, DropdownToggle } from '@patternfly/react-core'; +import Simple from './examples/SimpleDropdown'; +import Kebab from './examples/KebabDropdown'; +import PositionRigh from './examples/PositionRightDropdown'; +import DirectionUp from './examples/DirectionUpDropdown'; + +export default { + title: 'Dropdown', + components: { + Dropdown, + KebabToggle, + DropdownItem, + DropdownSeparator, + DropdownToggle + }, + examples: [Simple, PositionRigh, DirectionUp, Kebab] +}; diff --git a/packages/react-core/src/components/Dropdown/Dropdown.js b/packages/react-core/src/components/Dropdown/Dropdown.js new file mode 100644 index 00000000000..eec81c7eb35 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Dropdown.js @@ -0,0 +1,85 @@ +import React, { Children, cloneElement } from 'react'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; +import DropdownMenu from './DropdownMenu'; + +export const DropdownPosition = { + right: 'right', + left: 'left' +}; + +export const DropdownDirection = { + up: 'up', + down: 'down' +}; + +const propTypes = { + /** HTML ID of dropdown element */ + id: PropTypes.string, + /** Anything which can be rendered as dropdown items */ + children: PropTypes.node, + /** Classess applied to root element of dropdown */ + className: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool, + /** Display the toggle with no border or background */ + isPlain: PropTypes.bool, + /** Indicates where menu will be alligned horizontally */ + position: PropTypes.oneOf(Object.keys(DropdownPosition)), + /** Display menu above or below dropdown toggle */ + direction: PropTypes.oneOf(Object.keys(DropdownDirection)), + /** Placeholder to use custom toggle elements */ + toggle: PropTypes.node, + /** Function callback called when user selects item */ + onSelect: PropTypes.func +}; + +const defaultProps = { + id: + new Date().getTime() + + Math.random() + .toString(36) + .slice(2), + toggle: null, + children: null, + className: '', + isOpen: false, + isPlain: false, + position: DropdownPosition.left, + direction: DropdownDirection.down, + onSelect: Function.prototype +}; + +class Dropdown extends React.Component { + render() { + const { className, children, isOpen, toggle, direction, onSelect, isPlain, position, id, ...props } = this.props; + return ( +
{ + this.parentRef = ref; + }} + > + {toggle && Children.map(toggle, oneToggle => cloneElement(oneToggle, { parentRef: this.parentRef, isOpen }))} + onSelect && onSelect(event)}> + {children} + +
+ ); + } +} + +Dropdown.propTypes = propTypes; +Dropdown.defaultProps = defaultProps; + +export default Dropdown; diff --git a/packages/react-core/src/components/Dropdown/Dropdown.test.js b/packages/react-core/src/components/Dropdown/Dropdown.test.js new file mode 100644 index 00000000000..329eb82a956 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Dropdown.test.js @@ -0,0 +1,153 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Dropdown, { DropdownPosition, DropdownDirection } from './Dropdown'; +import DropdownItem from './DropdownItem'; +import DropdownSeparator from './Separator'; +import DropdownToggle from './DropdownToggle'; +import KebabToggle from './KebabToggle'; + +const DropItems = () => ( + + Link + Action + Disabled Link + + Disabled Action + + + Separated Link + Separated Action + +); + +describe('dropdown', () => { + test('regular', () => { + const view = mount( + Dropdown}> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('right aligned', () => { + const view = mount( + Dropdown}> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('dropup', () => { + const view = mount( + Dropdown}> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('dropup + right aligned', () => { + const view = mount( + Dropdown} + > + + + ); + expect(view).toMatchSnapshot(); + }); + + test('expanded', () => { + const view = mount( + Dropdown}> + + + ); + expect(view).toMatchSnapshot(); + }); +}); + +describe('KebabToggle', () => { + test('regular', () => { + const view = mount( + }> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('right aligned', () => { + const view = mount( + }> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('dropup', () => { + const view = mount( + }> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('dropup + right aligned', () => { + const view = mount( + } + > + + + ); + expect(view).toMatchSnapshot(); + }); + + test('expanded', () => { + const view = mount( + }> + + + ); + expect(view).toMatchSnapshot(); + }); + + test('plain', () => { + const view = mount( + }> + + + ); + expect(view).toMatchSnapshot(); + }); +}); + +describe('API', () => { + test('click on item', () => { + const mockToggle = jest.fn(); + const mockSelect = jest.fn(); + const view = mount( + + + + ); + + view + .find({ role: 'menuitem' }) + .first() + .simulate('click'); + expect(mockToggle.mock.calls).toHaveLength(0); + expect(mockSelect.mock.calls).toHaveLength(1); + }); +}); diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.d.ts b/packages/react-core/src/components/Dropdown/DropdownItem.d.ts new file mode 100644 index 00000000000..df846b3fe83 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownItem.d.ts @@ -0,0 +1,7 @@ +import { SFC, HTMLProps, ReactType, ReactNode } from 'react'; + +export interface DropdownItemProps extends HTMLProps { + component?: ReactType; + children?: ReactNode; + isDisabled?: Boolean; +} \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.js b/packages/react-core/src/components/Dropdown/DropdownItem.js new file mode 100644 index 00000000000..998fb05363d --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownItem.js @@ -0,0 +1,56 @@ +import React from 'react'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; +import { componentShape } from '../../internal/componentShape'; + +const propTypes = { + /** Anything which can be rendered as dropdown item */ + children: PropTypes.node, + /** Classess applied to root element of dropdown item */ + className: PropTypes.string, + /** Indicates which component will be used as dropdown item */ + component: componentShape, + /** Render dropdown item as disabled option */ + isDisabled: PropTypes.bool, + /** Forces display of the hover state of the element */ + isHovered: PropTypes.bool, + /** Accesibility role */ + role: PropTypes.string, + /** Default hyperlink location */ + href: PropTypes.string +}; + +const defaultProps = { + children: null, + className: '', + isHovered: false, + component: 'a', + isDisabled: false, + href: '#', + role: 'menuitem' +}; + +const DropdownItem = ({ className, children, isHovered, component: Component, isDisabled, role, ...props }) => { + const aditionalProps = props; + if (Component === 'a') { + aditionalProps['aria-disabled'] = isDisabled; + aditionalProps.tabIndex = isDisabled ? -1 : aditionalProps.tabIndex; + } else if (Component === 'button') { + aditionalProps.disabled = isDisabled; + } + return ( + + {children} + + ); +}; + +DropdownItem.propTypes = propTypes; +DropdownItem.defaultProps = defaultProps; + +export default DropdownItem; diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.test.js b/packages/react-core/src/components/Dropdown/DropdownItem.test.js new file mode 100644 index 00000000000..a3b3cdb3b26 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownItem.test.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import DropdownItem from './DropdownItem'; +import DropdownSeparator from './Separator'; + +describe('dropdown items', () => { + test('a', () => { + const view = shallow(Something); + expect(view).toMatchSnapshot(); + }); + + test('button', () => { + const view = shallow(Something); + expect(view).toMatchSnapshot(); + }); + + test('separator', () => { + const view = shallow(); + expect(view).toMatchSnapshot(); + }); + + describe('hover', () => { + test('a', () => { + const view = shallow(Something); + expect(view).toMatchSnapshot(); + }); + test('button', () => { + const view = shallow( + + Something + + ); + expect(view).toMatchSnapshot(); + }); + }); + + describe('disabled', () => { + test('a', () => { + const view = shallow(Something); + expect(view).toMatchSnapshot(); + }); + test('button', () => { + const view = shallow( + + Something + + ); + expect(view).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/react-core/src/components/Dropdown/DropdownMenu.js b/packages/react-core/src/components/Dropdown/DropdownMenu.js new file mode 100644 index 00000000000..2b26f023fa7 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownMenu.js @@ -0,0 +1,30 @@ +import React from 'react'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** Anything which can be rendered as dropdown items */ + children: PropTypes.node, + /** Classess applied to root element of dropdown menu */ + className: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool +}; + +const defaultProps = { + children: null, + className: '', + isOpen: true +}; + +const DropdownMenu = ({ className, isOpen, children, ...props }) => ( + +); + +DropdownMenu.propTypes = propTypes; +DropdownMenu.defaultProps = defaultProps; + +export default DropdownMenu; diff --git a/packages/react-core/src/components/Dropdown/DropdownToggle.d.ts b/packages/react-core/src/components/Dropdown/DropdownToggle.d.ts new file mode 100644 index 00000000000..ed095054925 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownToggle.d.ts @@ -0,0 +1,12 @@ +import { SFC, HTMLProps, ReactType, ReactNode } from 'react'; + +export interface DropdownToggleProps extends HTMLProps { + children?: ReactNode; + isOpen?: Boolean; + onToggle?: Function; + parentRef: HTMLElement; +} + +declare const DropdownToggle: SFC; + +export default DropdownToggle; \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/DropdownToggle.js b/packages/react-core/src/components/Dropdown/DropdownToggle.js new file mode 100644 index 00000000000..f93b7f2477f --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownToggle.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { CaretDownIcon } from '@patternfly/react-icons'; +import PropTypes from 'prop-types'; +import Toggle from './Toggle'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; + +const DropdownToggle = ({ children, ...props }) => ( + + {children} + + +); + +DropdownToggle.propTypes = { + /** Anything which can be rendered as dropdown toggle */ + children: PropTypes.node.isRequired, + /** Classess applied to root element of dropdown toggle */ + className: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool, + /** Callback called when toggle is clicked */ + onToggle: PropTypes.func, + /** Element which wraps toggle */ + parentRef: PropTypes.any, + /** Forces focus state */ + isFocused: PropTypes.bool, + /** Forces hover state */ + isHovered: PropTypes.bool, + /** Forces active state */ + isActive: PropTypes.bool +}; + +DropdownToggle.defaultProps = { + className: '', + isOpen: false, + parentRef: null, + isFocused: false, + isHovered: false, + isActive: false, + onToggle: Function.prototype +}; + +export default DropdownToggle; diff --git a/packages/react-core/src/components/Dropdown/DropdownToggle.test.js b/packages/react-core/src/components/Dropdown/DropdownToggle.test.js new file mode 100644 index 00000000000..7554dbeda6c --- /dev/null +++ b/packages/react-core/src/components/Dropdown/DropdownToggle.test.js @@ -0,0 +1,99 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import DropdownToggle from './DropdownToggle'; + +describe('API', () => { + test('click on closed', () => { + const mockToggle = jest.fn(); + const view = mount( + + Dropdown + + ); + + view + .find('button') + .first() + .simulate('click'); + expect(mockToggle.mock.calls[0][0]).toBe(true); + }); + + test('click on opened', () => { + const mockToggle = jest.fn(); + const view = mount( + + Dropdown + + ); + + view + .find('button') + .first() + .simulate('click'); + expect(mockToggle.mock.calls[0][0]).toBe(false); + }); + + test('click on document', () => { + const map = {}; + document.addEventListener = jest.fn((event, cb) => { + map[event] = cb; + }); + const mockToggle = jest.fn(); + mount( + + Dropdown + + ); + + map.mousedown({ target: document }); + expect(mockToggle.mock.calls[0][0]).toBe(false); + }); + + test('on click outside has been removed', () => { + const map = {}; + document.removeEventListener = jest.fn((event, cb) => { + map[event] = cb; + }); + const mockToggle = jest.fn(); + const view = mount( + + Dropdown + + ); + view.unmount(); + setTimeout(() => { + map.mousedown({ target: document }); + expect(mockToggle.mock.calls).toHaveLength(0); + expect(document.removeEventListener.mock.calls).toHaveLength(1); + }); + }); +}); + +describe('state', () => { + test('hover', () => { + const view = mount( + + Dropdown + + ); + expect(view).toMatchSnapshot(); + }); + + test('active', () => { + const view = mount( + + Dropdown + + ); + expect(view).toMatchSnapshot(); + }); + + test('focus', () => { + const view = mount( + + Dropdown + + ); + expect(view).toMatchSnapshot(); + }); +}); diff --git a/packages/react-core/src/components/Dropdown/Item.d.ts b/packages/react-core/src/components/Dropdown/Item.d.ts new file mode 100644 index 00000000000..b7dda036aaa --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Item.d.ts @@ -0,0 +1,9 @@ +import { SFC, HTMLProps, ReactType } from 'react'; +import { DropdownItemProps } from './DropdownItem'; + +export interface ItemProps extends DropdownItemProps { +} + +declare const Item: SFC; + +export default Item; \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/Item.js b/packages/react-core/src/components/Dropdown/Item.js new file mode 100644 index 00000000000..b6d66b73d1d --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Item.js @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DropdownItem from './DropdownItem'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import { componentShape } from '../../internal/componentShape'; + +const Item = ({ className, ...props }) => ( + +); + +Item.propTypes = { + /** Anything which can be rendered as dropdown item */ + children: PropTypes.node, + /** Classess applied to root element of dropdown item */ + className: PropTypes.string, + /** Indicates which component will be used as dropdown item */ + component: componentShape, + /** Render dropdown item as disabled option */ + isDisabled: PropTypes.bool, + /** Forces display of the hover state of the element */ + isHovered: PropTypes.bool, + /** Accesibility role */ + role: PropTypes.string, + /** Default hyperlink location */ + href: PropTypes.string +}; +Item.defaultProps = { + children: null, + className: '', + isHovered: false, + component: 'a', + isDisabled: false, + href: '#', + role: 'menuitem' +}; + +export default Item; diff --git a/packages/react-core/src/components/Dropdown/KebabToggle.d.ts b/packages/react-core/src/components/Dropdown/KebabToggle.d.ts new file mode 100644 index 00000000000..c72a354de9d --- /dev/null +++ b/packages/react-core/src/components/Dropdown/KebabToggle.d.ts @@ -0,0 +1,9 @@ +import { SFC, HTMLProps, ReactType } from 'react'; +import { DropdownToggleProps } from './DropdownToggle'; + +export interface KebabProps extends DropdownToggleProps { +} + +declare const Kebab: SFC; + +export default Kebab; \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/KebabToggle.js b/packages/react-core/src/components/Dropdown/KebabToggle.js new file mode 100644 index 00000000000..8e439084e90 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/KebabToggle.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { EllipsisVIcon } from '@patternfly/react-icons'; +import Toggle from './Toggle'; + +const defaultAriaLabel = 'Actions'; + +const Kebab = ({ ...props }) => ( + + + +); + +Kebab.propTypes = { + /** Anything which can be rendered as dropdown toggle */ + children: PropTypes.node, + /** Classess applied to root element of dropdown toggle */ + className: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool, + /** Label Toggle button */ + 'aria-label': PropTypes.string, + /** Callback called when toggle is clicked */ + onToggle: PropTypes.func, + /** Element which wraps toggle */ + parentRef: PropTypes.any, + /** Forces focus state */ + isFocused: PropTypes.bool, + /** Forces hover state */ + isHovered: PropTypes.bool, + /** Forces active state */ + isActive: PropTypes.bool +}; +Kebab.defaultProps = { + children: null, + className: '', + isOpen: false, + 'aria-label': defaultAriaLabel, + parentRef: null, + isFocused: false, + isHovered: false, + isActive: false, + onToggle: Function.prototype +}; + +export default Kebab; diff --git a/packages/react-core/src/components/Dropdown/Separator.d.ts b/packages/react-core/src/components/Dropdown/Separator.d.ts new file mode 100644 index 00000000000..d64f0c55836 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Separator.d.ts @@ -0,0 +1,9 @@ +import { SFC, HTMLProps, ReactType } from 'react'; +import { DropdownItemProps } from './DropdownItem'; + +export interface SeparatorProps extends DropdownItemProps { +} + +declare const Separator: SFC; + +export default Separator; \ No newline at end of file diff --git a/packages/react-core/src/components/Dropdown/Separator.js b/packages/react-core/src/components/Dropdown/Separator.js new file mode 100644 index 00000000000..fdc478e7f55 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Separator.js @@ -0,0 +1,38 @@ +import React from 'react'; +import DropdownItem from './DropdownItem'; +import PropTypes from 'prop-types'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import { componentShape } from '../../internal/componentShape'; + +const Separator = ({ className, ...props }) => ( + +); + +Separator.propTypes = { + /** Anything which can be rendered as dropdown item */ + children: PropTypes.node, + /** Classess applied to root element of dropdown item */ + className: PropTypes.string, + /** Indicates which component will be used as dropdown item */ + component: componentShape, + /** Render dropdown item as disabled option */ + isDisabled: PropTypes.bool, + /** Forces display of the hover state of the element */ + isHovered: PropTypes.bool, + /** Accesibility role */ + role: PropTypes.string, + /** Default hyperlink location */ + href: PropTypes.string +}; +Separator.defaultProps = { + children: null, + className: '', + isHovered: false, + component: 'a', + isDisabled: false, + href: '#', + role: 'menuitem' +}; + +export default Separator; diff --git a/packages/react-core/src/components/Dropdown/Toggle.js b/packages/react-core/src/components/Dropdown/Toggle.js new file mode 100644 index 00000000000..b6392b851d3 --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Toggle.js @@ -0,0 +1,89 @@ +import React, { Component } from 'react'; +import styles from '@patternfly/patternfly-next/components/Dropdown/styles.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** Anything which can be rendered as dropdown toggle */ + children: PropTypes.node, + /** Classess applied to root element of dropdown toggle */ + className: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool, + /** Callback called when toggle is clicked */ + onToggle: PropTypes.func, + /** Element which wraps toggle */ + parentRef: PropTypes.any, + /** Forces focus state */ + isFocused: PropTypes.bool, + /** Forces hover state */ + isHovered: PropTypes.bool, + /** Forces active state */ + isActive: PropTypes.bool +}; + +const defaultProps = { + children: null, + className: '', + isOpen: false, + parentRef: null, + isFocused: false, + isHovered: false, + isActive: false, + onToggle: Function.prototype +}; + +class DropdownToggle extends Component { + componentDidMount = () => { + document.addEventListener('mousedown', this.onDocClick); + document.addEventListener('keydown', this.onEscPress); + }; + + componentWillUnmount = () => { + document.removeEventListener('mousedown', this.onDocClick); + document.removeEventListener('keydown', this.onEscPress); + }; + + onDocClick = event => { + if (this.props.parentRef && !this.props.parentRef.contains(event.target)) { + this.props.onToggle && this.props.onToggle(false); + } + }; + + onEscPress = event => { + const keyCode = event.keyCode || event.which; + if (keyCode === 27) { + this.props.onToggle && this.props.onToggle(false); + this.toggle.focus(); + } + }; + + render() { + const { className, children, isOpen, isFocused, isActive, isHovered, onToggle, parentRef, ...props } = this.props; + return ( + + ); + } +} + +DropdownToggle.propTypes = propTypes; +DropdownToggle.defaultProps = defaultProps; + +export default DropdownToggle; diff --git a/packages/react-core/src/components/Dropdown/Toggle.test.js b/packages/react-core/src/components/Dropdown/Toggle.test.js new file mode 100644 index 00000000000..8a51ed91eca --- /dev/null +++ b/packages/react-core/src/components/Dropdown/Toggle.test.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import DropdownToggle from './DropdownToggle'; +import KebabToggle from './KebabToggle'; + +test('Dropdown toggle', () => { + const view = mount(Dropdown); + expect(view).toMatchSnapshot(); +}); +test('Kebab toggle', () => { + const view = mount(); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Dropdown/__snapshots__/Dropdown.test.js.snap b/packages/react-core/src/components/Dropdown/__snapshots__/Dropdown.test.js.snap new file mode 100644 index 00000000000..b8ecb47ff6c --- /dev/null +++ b/packages/react-core/src/components/Dropdown/__snapshots__/Dropdown.test.js.snap @@ -0,0 +1,2984 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KebabToggle dropup + right aligned 1`] = ` +.pf-c-dropdown__toggle { + display: block; + position: relative; + min-width: 32px; + min-height: 32px; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #282d33; + background-color: transparent; +} +.pf-m-disabled { + display: block; +} +.pf-m-disabled { + display: block; +} +.pf-c-dropdown__separator { + display: block; + height: 1px; + background-color: #ededed; +} +.pf-c-dropdown__separator { + display: block; + height: 1px; + background-color: #ededed; +} +.pf-c-dropdown__menu { + display: block; + position: absolute; + z-index: 100; + min-width: 100%; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + background: #ffffff; + background-clip: padding-box; + border: 1px solid transparent; + 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-dropdown.pf-m-top.pf-m-align-right { + display: inline-block; + position: relative; +} + + + } +> +