diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.d.ts b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.d.ts new file mode 100644 index 00000000000..8d25664378d --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.d.ts @@ -0,0 +1,20 @@ +import { FunctionComponent, HTMLProps, ReactNode } from 'react'; +import { OneOf } from '../../typeUtils'; + +export interface ContextSelectorProps extends HTMLProps { + children?: ReactNode; + isOpen?: boolean; + onToggle?(value: boolean): void; + onSelect?(event: React.SyntheticEvent, value: ReactNode): void; + screenReaderLabel?: string; + toggleText?: string; + searchButtonAriaLabel?: string; + searchInputValue?: string; + onSearchInputChange?(value: string): void; + searchInputPlaceholder?: string; + onSearchButtonClick?(event: React.SyntheticEvent): void; +} + +declare const ContextSelector: FunctionComponent; + +export default ContextSelector; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.docs.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.docs.js new file mode 100644 index 00000000000..1f7228d60bb --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.docs.js @@ -0,0 +1,11 @@ +import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core'; +import Simple from './examples/SimpleContextSelector'; + +export default { + title: 'ContextSelector', + components: { + ContextSelector, + ContextSelectorItem + }, + examples: [{ component: Simple, title: 'Simple ContextSelector' }] +}; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.js new file mode 100644 index 00000000000..290e6a168fb --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.js @@ -0,0 +1,147 @@ +import React from 'react'; +import styles from '@patternfly/patternfly/components/ContextSelector/context-selector.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; +import FocusTrap from 'focus-trap-react'; +import ContextSelectorToggle from './ContextSelectorToggle'; +import ContextSelectorMenuList from './ContextSelectorMenuList'; +import { ContextSelectorContext } from './contextSelectorConstants'; +import { Button, ButtonVariant } from '../Button'; +import { TextInput } from '../TextInput'; +import { SearchIcon } from '@patternfly/react-icons'; +import { InputGroup } from '../InputGroup'; +import { KEY_CODES } from '../../helpers/constants'; + +// seed for the aria-labelledby ID +let currentId = 0; +const newId = currentId++; + +const propTypes = { + /** content rendered inside the Context Selector */ + children: PropTypes.node, + /** Classes applied to root element of Context Selector */ + className: PropTypes.string, + /** Flag to indicate if Context Selector is opened */ + isOpen: PropTypes.bool, + /** Function callback called when user clicks toggle button */ + onToggle: PropTypes.func, + /** Function callback called when user selects item */ + onSelect: PropTypes.func, + /** Labels the Context Selector for Screen Readers */ + screenReaderLabel: PropTypes.string, + /** Text that appears in the Context Selector Toggle */ + toggleText: PropTypes.string, + /** aria-label for the Context Selector Search Button */ + searchButtonAriaLabel: PropTypes.string, + /** Value in the Search field */ + searchInputValue: PropTypes.string, + /** Function callback called when user changes the Search Input */ + onSearchInputChange: PropTypes.func, + /** Search Input placeholder */ + searchInputPlaceholder: PropTypes.string, + /** Function callback for when Search Button is clicked */ + onSearchButtonClick: PropTypes.func +}; + +const defaultProps = { + children: null, + className: '', + isOpen: false, + onToggle: () => {}, + onSelect: () => {}, + screenReaderLabel: '', + toggleText: '', + searchButtonAriaLabel: 'Search menu items', + searchInputValue: '', + onSearchInputChange: () => {}, + searchInputPlaceholder: 'Search', + onSearchButtonClick: () => {} +}; + +class ContextSelector extends React.Component { + parentRef = React.createRef(); + + onEnterPressed = event => { + if (event.charCode === KEY_CODES.ENTER) { + this.props.onSearchButtonClick(); + } + }; + + render() { + const toggleId = `pf-context-selector-toggle-id-${newId}`; + const screenReaderLabelId = `pf-context-selector-label-id-${newId}`; + const searchButtonId = `pf-context-selector-search-button-id-${newId}`; + const { + children, + className, + isOpen, + onToggle, + onSelect, + screenReaderLabel, + toggleText, + searchButtonAriaLabel, + searchInputValue, + onSearchInputChange, + searchInputPlaceholder, + onSearchButtonClick, + ...props + } = this.props; + return ( +
+ {screenReaderLabel && ( + + )} + + {isOpen && ( +
+ {isOpen && ( + +
+ + + + +
+ + {children} + +
+ )} +
+ )} +
+ ); + } +} + +ContextSelector.propTypes = propTypes; +ContextSelector.defaultProps = defaultProps; + +export default ContextSelector; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.test.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.test.js new file mode 100644 index 00000000000..0c37498c512 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelector.test.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import ContextSelector from './ContextSelector'; +import ContextSelectorItem from './ContextSelectorItem'; + +const items = [ + My Project, + OpenShift Cluster, + Production Ansible, + AWS, + Azure +]; + +test('Renders ContextSelector', () => { + const view = shallow( {items} ); + expect(view).toMatchSnapshot(); +}); + +test('Renders ContextSelector open', () => { + const view = shallow( {items} ); + expect(view).toMatchSnapshot(); +}); + +test('Verify onToggle is called ', () => { + const mockfn = jest.fn(); + const view = mount( {items} ); + view + .find('button') + .at(0) + .simulate('click'); + expect(mockfn.mock.calls).toHaveLength(1); +}); diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.d.ts b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.d.ts new file mode 100644 index 00000000000..ccc7fd543a5 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.d.ts @@ -0,0 +1,12 @@ +import { FunctionComponent, HTMLProps, ReactType } from 'react'; + +export interface ContextSelectorItemProps extends HTMLProps { + isDisabled?: boolean; + isSelected?: boolean; + isHovered?: boolean; + onClick?: Function; +} + +declare const ContextSelectorItem: FunctionComponent; + +export default ContextSelectorItem; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.js new file mode 100644 index 00000000000..82712c77e10 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.js @@ -0,0 +1,72 @@ +import React from 'react'; +import styles from '@patternfly/patternfly/components/ContextSelector/context-selector.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; +import { ContextSelectorContext } from './contextSelectorConstants'; + +const propTypes = { + /** Anything which can be rendered as Context Selector item */ + children: PropTypes.node, + /** Classes applied to root element of the Context Selector item */ + className: PropTypes.string, + /** Render Context Selector item as disabled */ + isDisabled: PropTypes.bool, + /** Forces display of the hover state of the element */ + isHovered: PropTypes.bool, + /** Callback for click event */ + onClick: PropTypes.func, + /** Additional props are spread to the button element */ + '': PropTypes.any +}; + +const defaultProps = { + children: null, + className: '', + isHovered: false, + isDisabled: false, + onClick: () => {} +}; + +class ContextSelectorItem extends React.Component { + ref = React.createRef(); + + componentDidMount() { + /* eslint-disable-next-line */ + this.props.sendRef(this.props.index, this.ref.current); + } + + render() { + const { className, children, isHovered, onClick, isDisabled, index, sendRef, ...props } = this.props; + return ( + + {({ onSelect }) => ( +
  • + +
  • + )} +
    + ); + } +} + +ContextSelectorItem.propTypes = propTypes; +ContextSelectorItem.defaultProps = defaultProps; + +export default ContextSelectorItem; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.test.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.test.js new file mode 100644 index 00000000000..ac165f7e5fc --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorItem.test.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import ContextSelectorItem from './ContextSelectorItem'; + +test('Renders ContextSelectorItem', () => { + const view = shallow( + + My Project + + ); + expect(view).toMatchSnapshot(); +}); + +test('Renders ContextSelectorItem disabled and hovered', () => { + const view = shallow( + + My Project + + ); + expect(view).toMatchSnapshot(); +}); + +test('Verify onClick is called ', () => { + const mockfn = jest.fn(); + const view = mount( + + My Project + + ); + view + .find('button') + .at(0) + .simulate('click'); + expect(mockfn.mock.calls).toHaveLength(1); +}); diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.js new file mode 100644 index 00000000000..3ea23b6436f --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.js @@ -0,0 +1,53 @@ +import React from 'react'; +import styles from '@patternfly/patternfly/components/ContextSelector/context-selector.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** Content rendered inside the Context Selector Menu */ + children: PropTypes.node, + /** Classess applied to root element of Context Selector menu */ + className: PropTypes.string, + /** Flag to indicate if Context Selector menu is opened */ + isOpen: PropTypes.bool, + /** Additional props are spread to the container component */ + '': PropTypes.any +}; + +const defaultProps = { + children: null, + className: '', + isOpen: true +}; + +class ContextSelectorMenuList extends React.Component { + refsCollection = []; + + sendRef = (index, ref) => { + this.refsCollection[index] = ref; + }; + + extendChildren() { + return React.Children.map(this.props.children, (child, index) => + React.cloneElement(child, { + sendRef: this.sendRef, + index + }) + ); + } + + render() { + const { className, isOpen, children, ...props } = this.props; + + return ( + + ); + } +} + +ContextSelectorMenuList.propTypes = propTypes; +ContextSelectorMenuList.defaultProps = defaultProps; + +export default ContextSelectorMenuList; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.test.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.test.js new file mode 100644 index 00000000000..88a2993ccf8 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorMenuList.test.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import ContextSelectorItem from './ContextSelectorItem'; +import ContextSelectorMenuList from './ContextSelectorMenuList'; + +const items = [ + My Project, + OpenShift Cluster, + Production Ansible, + AWS, + Azure +]; + +test('Renders ContextSelectorMenuList open', () => { + const view = shallow( + + {items} + + ); + expect(view).toMatchSnapshot(); +}); + +test('Renders ContextSelectorMenuList closed', () => { + const view = shallow({items}); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.js new file mode 100644 index 00000000000..97d5a37e8b0 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.js @@ -0,0 +1,135 @@ +import React, { Component } from 'react'; +import { CaretDownIcon } from '@patternfly/react-icons'; +import styles from '@patternfly/patternfly/components/ContextSelector/context-selector.css'; +import { css } from '@patternfly/react-styles'; +import PropTypes from 'prop-types'; +import { KEY_CODES } from '../../helpers/constants'; + +const propTypes = { + /** HTML ID of toggle */ + id: PropTypes.string.isRequired, + /** Classes applied to root element of toggle */ + className: PropTypes.string, + /** Text that appears in the Context Selector Toggle */ + toggleText: PropTypes.string, + /** Flag to indicate if menu is opened */ + isOpen: PropTypes.bool, + /** Callback called when toggle is clicked */ + onToggle: PropTypes.func, + /** Callback for toggle open on keyboard entry */ + onEnter: 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, + /** Additional props are spread to the container + ); + } +} + +ContextSelectorToggle.propTypes = propTypes; +ContextSelectorToggle.defaultProps = defaultProps; + +export default ContextSelectorToggle; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.test.js b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.test.js new file mode 100644 index 00000000000..71d1c50fad7 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/ContextSelectorToggle.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import ContextSelectorToggle from './ContextSelectorToggle'; + +test('Renders ContextSelectorToggle', () => { + const view = shallow(); + expect(view).toMatchSnapshot(); +}); + +test('Verify onToggle is called ', () => { + const mockfnOnToggle = jest.fn(); + const view = mount(); + view + .find('button') + .at(0) + .simulate('click'); + expect(mockfnOnToggle.mock.calls).toHaveLength(1); +}); + +test('Verify ESC press ', () => { + const view = mount(); + view.simulate('keyDown', { key: 'Escape' }); + expect(view).toMatchSnapshot(); +}); + +test('Verify ESC press with not isOpen', () => { + const view = mount(); + view.simulate('keyDown', { key: 'Escape' }); + expect(view).toMatchSnapshot(); +}); + +test('Verify keydown tab ', () => { + const view = mount(); + view.simulate('keyDown', { key: 'Tab' }); + expect(view).toMatchSnapshot(); +}); + +test('Verify keydown enter ', () => { + const view = mount(); + view.simulate('keyDown', { key: 'Enter' }); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelector.test.js.snap b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelector.test.js.snap new file mode 100644 index 00000000000..a10035dd543 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelector.test.js.snap @@ -0,0 +1,192 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Renders ContextSelector 1`] = ` +.pf-c-context-selector { + display: inline-block; + position: relative; + width: 15.625rem; + max-width: 100%; +} + +
    + +
    +`; + +exports[`Renders ContextSelector open 1`] = ` +.pf-c-context-selector__menu-input { + display: block; + position: relative; + padding: 0.5rem 1rem 1rem 1rem; + border-bottom: 1px solid #ededed; +} +.pf-c-context-selector__menu { + display: block; + color: #282d33; + position: absolute; + z-index: 100; + min-width: 100%; + padding-top: 0.5rem; + background-color: #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-context-selector.pf-m-expanded { + display: inline-block; + position: relative; + width: 15.625rem; + max-width: 100%; +} + +
    + +
    + +
    + + + + +
    + + + + + My Project + + + OpenShift Cluster + + + Production Ansible + + + AWS + + + Azure + + + + +
    +
    +
    +`; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorItem.test.js.snap b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorItem.test.js.snap new file mode 100644 index 00000000000..d491ae77586 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorItem.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Renders ContextSelectorItem 1`] = ` + + + +`; + +exports[`Renders ContextSelectorItem disabled and hovered 1`] = ` + + + +`; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorMenuList.test.js.snap b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorMenuList.test.js.snap new file mode 100644 index 00000000000..5d6a52d585b --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorMenuList.test.js.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Renders ContextSelectorMenuList closed 1`] = ` +.pf-c-context-selector__menu-list { + display: block; + max-height: 12.5rem; + overflow-y: scroll; +} + + +`; + +exports[`Renders ContextSelectorMenuList open 1`] = ` +.pf-c-context-selector__menu-list { + display: block; + max-height: 12.5rem; + overflow-y: scroll; +} + + +`; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorToggle.test.js.snap b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorToggle.test.js.snap new file mode 100644 index 00000000000..ed506502eb8 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/__snapshots__/ContextSelectorToggle.test.js.snap @@ -0,0 +1,349 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Renders ContextSelectorToggle 1`] = ` +.pf-c-context-selector__toggle-text { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle { + display: flex; + position: relative; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + color: #282d33; + white-space: nowrap; + cursor: pointer; +} + + +`; + +exports[`Verify ESC press 1`] = ` +.pf-c-context-selector__toggle-text { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle { + display: flex; + position: relative; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + color: #282d33; + white-space: nowrap; + cursor: pointer; +} + + + + +`; + +exports[`Verify ESC press with not isOpen 1`] = ` +.pf-c-context-selector__toggle-text { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle { + display: flex; + position: relative; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + color: #282d33; + white-space: nowrap; + cursor: pointer; +} + + + + +`; + +exports[`Verify keydown enter 1`] = ` +.pf-c-context-selector__toggle-text { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle { + display: flex; + position: relative; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + color: #282d33; + white-space: nowrap; + cursor: pointer; +} + + + + +`; + +exports[`Verify keydown tab 1`] = ` +.pf-c-context-selector__toggle-text { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle-icon { + display: block; +} +.pf-c-context-selector__toggle { + display: flex; + position: relative; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.375rem 0.5rem 0.375rem 0.5rem; + color: #282d33; + white-space: nowrap; + cursor: pointer; +} + + + + +`; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/contextSelectorConstants.js b/packages/patternfly-4/react-core/src/components/ContextSelector/contextSelectorConstants.js new file mode 100644 index 00000000000..c566379c630 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/contextSelectorConstants.js @@ -0,0 +1,5 @@ +import React from 'react'; + +export const ContextSelectorContext = React.createContext({ + onSelect: () => {} +}); diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/examples/SimpleContextSelector.js b/packages/patternfly-4/react-core/src/components/ContextSelector/examples/SimpleContextSelector.js new file mode 100644 index 00000000000..38309f43158 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/examples/SimpleContextSelector.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core'; + +class SimpleContextSelector extends React.Component { + items = [ + 'My Project', + 'OpenShift Cluster', + 'Production Ansible', + 'AWS', + 'Azure', + 'My Project 2', + 'OpenShift Cluster ', + 'Production Ansible 2 ', + 'AWS 2', + 'Azure 2' + ]; + + state = { + isOpen: false, + selected: this.items[0], + searchValue: '', + filteredItems: this.items + }; + + onToggle = (event, isOpen) => { + this.setState({ + isOpen + }); + }; + + onSelect = (event, value) => { + this.setState({ + selected: value, + isOpen: !this.state.isOpen + }); + }; + + onSearchInputChange = value => { + this.setState({ searchValue: value }); + }; + + onSearchButtonClick = event => { + const filtered = + this.state.searchValue === '' + ? this.items + : this.items.filter(str => str.toLowerCase().indexOf(this.state.searchValue.toLowerCase()) !== -1); + + this.setState({ filteredItems: filtered || [] }); + }; + + render() { + const { isOpen, selected, searchValue, filteredItems } = this.state; + return ( + + {filteredItems.map((item, index) => ( + {item} + ))} + + ); + } +} + +export default SimpleContextSelector; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/index.d.ts b/packages/patternfly-4/react-core/src/components/ContextSelector/index.d.ts new file mode 100644 index 00000000000..a69dda91f4f --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/index.d.ts @@ -0,0 +1,2 @@ +export { default as ContextSelector, ContextSelectorProps } from './ContextSelector'; +export { default as ContextSelectorItem, ContextSelectorItemProps } from './ContextSelectorItem'; diff --git a/packages/patternfly-4/react-core/src/components/ContextSelector/index.js b/packages/patternfly-4/react-core/src/components/ContextSelector/index.js new file mode 100644 index 00000000000..92122688a86 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/ContextSelector/index.js @@ -0,0 +1,2 @@ +export { default as ContextSelector } from './ContextSelector'; +export { default as ContextSelectorItem } from './ContextSelectorItem'; diff --git a/packages/patternfly-4/react-core/src/components/TextInput/TextInput.js b/packages/patternfly-4/react-core/src/components/TextInput/TextInput.js index a885fda75e3..9130c743d38 100644 --- a/packages/patternfly-4/react-core/src/components/TextInput/TextInput.js +++ b/packages/patternfly-4/react-core/src/components/TextInput/TextInput.js @@ -36,7 +36,7 @@ const propTypes = { isReadOnly: PropTypes.bool, /** Custom flag to show that the input requires an associated id or aria-label. */ 'aria-label': props => { - if (!props.id && !props['aria-label']) { + if (!props.id && !props['aria-label'] && !props['aria-labelledby']) { return new Error('TextInput requires either an id or aria-label to be specified'); } return null; diff --git a/packages/patternfly-4/react-core/src/components/TextInput/TextInput.test.js b/packages/patternfly-4/react-core/src/components/TextInput/TextInput.test.js index 5cd395c2b6c..cf272bf96a7 100644 --- a/packages/patternfly-4/react-core/src/components/TextInput/TextInput.test.js +++ b/packages/patternfly-4/react-core/src/components/TextInput/TextInput.test.js @@ -37,23 +37,30 @@ test('invalid text input', () => { expect(view).toMatchSnapshot(); }); -test('should throw console error when no aria-label or id is given', () => { +test('should throw console error when no aria-label, id or aria-labelledby is given', () => { const myMock = jest.fn(); global.console = { error: myMock }; shallow(); expect(myMock).toBeCalled(); }); -test('should not throw console error when id is given but no aria-label', () => { +test('should not throw console error when id is given but no aria-label or aria-labelledby', () => { const myMock = jest.fn(); global.console = { error: myMock }; shallow(); expect(myMock).not.toBeCalled(); }); -test('should not throw console error when aria-label is given but no id', () => { +test('should not throw console error when aria-label is given but no id or aria-labelledby', () => { const myMock = jest.fn(); global.console = { error: myMock }; shallow(); expect(myMock).not.toBeCalled(); }); + +test('should not throw console error when aria-labelledby is given but no id or aria-label', () => { + const myMock = jest.fn(); + global.console = { error: myMock }; + shallow(); + expect(myMock).not.toBeCalled(); +}); diff --git a/packages/patternfly-4/react-core/src/components/index.ts b/packages/patternfly-4/react-core/src/components/index.ts index 27209051b7a..001f7c6c0f9 100644 --- a/packages/patternfly-4/react-core/src/components/index.ts +++ b/packages/patternfly-4/react-core/src/components/index.ts @@ -12,6 +12,7 @@ export * from './Button'; export * from './Card'; export * from './Checkbox'; export * from './ChipGroup'; +export * from './ContextSelector'; export * from './DataList'; export * from './Dropdown'; export * from './EmptyState'; diff --git a/packages/patternfly-4/react-core/src/helpers/constants.ts b/packages/patternfly-4/react-core/src/helpers/constants.ts index 2438360edee..16d77cd2cad 100644 --- a/packages/patternfly-4/react-core/src/helpers/constants.ts +++ b/packages/patternfly-4/react-core/src/helpers/constants.ts @@ -1,5 +1,5 @@ -export const KEY_CODES = { ARROW_UP: 38, ARROW_DOWN: 40, ESCAPE_KEY: 27, TAB: 9, ENTER: 13 }; +export const KEY_CODES = { ARROW_UP: 38, ARROW_DOWN: 40, ESCAPE_KEY: 27, TAB: 9, ENTER: 13, SPACE: ' ' }; export const SIDE = { RIGHT: 'right', LEFT: 'left', BOTH: 'both', NONE: 'none' }; -export const KEYHANDLER_DIRECTION = { UP: 'up', DOWN: 'down'}; +export const KEYHANDLER_DIRECTION = { UP: 'up', DOWN: 'down' }; diff --git a/packages/patternfly-4/react-integration/demo-app-ts/src/App.tsx b/packages/patternfly-4/react-integration/demo-app-ts/src/App.tsx index c83d8a798a2..6b0082e072e 100755 --- a/packages/patternfly-4/react-integration/demo-app-ts/src/App.tsx +++ b/packages/patternfly-4/react-integration/demo-app-ts/src/App.tsx @@ -1,21 +1,23 @@ import { - Avatar, - AvatarProps, - Tabs, - Tab, - Tooltip, - Popover, Alert, AlertActionCloseButton, AlertActionLink, AlertVariant, + Avatar, + AvatarProps, + ContextSelector, + ContextSelectorItem, InputGroup, InputGroupText, + Popover, Select, SelectOption, SelectVariant, + Tab, + Tabs, TextInput, - Title + Title, + Tooltip } from '@patternfly/react-core'; import React, { Component } from 'react'; import logo from './logo.svg'; @@ -52,7 +54,9 @@ class App extends Component { return (
    - PF4 Integration Sandbox + + PF4 Integration Sandbox +
    @@ -101,9 +105,25 @@ class App extends Component { + {}} + isOpen={true} + searchInputValue="" + onToggle={() => {}} + onSelect={() => {}} + screenReaderLabel="screenReader Label" + searchInputPlaceholder="test" + searchButtonAriaLabel="Aria Lable" + > + My Project + OpenShift Cluster + Production Ansible + AWS + Azure +
    ); } } - export default App;