Skip to content

Commit

Permalink
Merge ec7f15b into eab0f4b
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcfaul committed Feb 6, 2019
2 parents eab0f4b + ec7f15b commit cfdaa12
Show file tree
Hide file tree
Showing 19 changed files with 1,751 additions and 0 deletions.
15 changes: 15 additions & 0 deletions packages/patternfly-4/react-core/src/components/Select/Select.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { HTMLProps, FormEvent } from 'react';

export interface SelectProps extends HTMLProps<HTMLOptionElement> {
isExpanded?: boolean;
onToggle(value: boolean): void;
placeholderText?: string;
selectOptions?: ReactNode[];
selections?: string;
variant?: string;
width?: string | number;
}

declare const Select: React.FunctionComponent<SelectProps>;

export default Select;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Select, SelectOption } from '@patternfly/react-core';
import SingleSelectInput from './examples/SingleSelectInput';

export default {
title: 'Select',
components: {
Select,
SelectOption
},
examples: [{ component: SingleSelectInput, title: 'Single Select Input' }]
};
119 changes: 119 additions & 0 deletions packages/patternfly-4/react-core/src/components/Select/Select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import SingleSelect from './SingleSelect';
import SelectToggle from './SelectToggle';
import { SelectContext } from './selectConstants';

// seed for the aria-labelledby ID
let currentId = 0;

const propTypes = {
/** Content rendered inside the Select */
children: PropTypes.node,
/** Classes applied to the root of the Select */
className: PropTypes.string,
/** Flag to indicate if select is expanded */
isExpanded: PropTypes.bool,
/** Placeholder text of Select */
placeholderText: PropTypes.string,
/** Array of SelectOption nodes that will be rendered */
selectOptions: PropTypes.array,
/** Selected item(s) structure */
selections: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
/** Callback for selection behavior */
onSelect: PropTypes.func.isRequired,
/** Callback for toggle button behavior */
onToggle: PropTypes.func.isRequired,
/** Variant of rendered Select */
variant: PropTypes.oneOf(['single']),
/** Width of the select container as a number of px or string percentage */
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Additional props are spread to the container <ul> */
'': PropTypes.any
};

const defaultProps = {
children: null,
className: '',
isExpanded: false,
selectOptions: null,
selections: null,
placeholderText: null,
variant: 'single',
width: '100%'
};

class Select extends React.Component {
parentRef = React.createRef();
state = { openedOnEnter: false };

onEnter = () => {
this.setState({ openedOnEnter: true });
};

onClose = () => {
this.setState({ openedOnEnter: false });
};

render() {
const {
children,
className,
variant,
onToggle,
onSelect,
isExpanded,
selectOptions,
selections,
placeholderText,
width,
...props
} = this.props;
const { openedOnEnter } = this.state;
const renderedChildren = children || selectOptions;
let childPlaceholderText = null;
if (!selections && !placeholderText) {
const childPlaceholder = renderedChildren.filter(child => child.props.isPlaceholder === true);
childPlaceholderText =
(childPlaceholder[0] && childPlaceholder[0].props.value) ||
(renderedChildren[0] && renderedChildren[0].props.value);
}

return (
<div
className={css(styles.select, isExpanded && styles.modifiers.expanded, className)}
ref={this.parentRef}
style={{ width }}
>
<SelectContext.Provider value={onSelect}>
{variant === 'single' && (
<React.Fragment>
<SelectToggle
id={`pf-toggle-id-${currentId++}`}
parentRef={this.parentRef.current}
isExpanded={isExpanded}
onToggle={onToggle}
onEnter={this.onEnter}
onClose={this.onClose}
>
{selections || placeholderText || childPlaceholderText}
</SelectToggle>
{isExpanded && (
<SingleSelect {...props} selected={selections} openedOnEnter={openedOnEnter}>
{renderedChildren}
</SingleSelect>
)}
</React.Fragment>
)}
</SelectContext.Provider>
</div>
);
}
}

Select.propTypes = propTypes;
Select.defaultProps = defaultProps;

export default Select;
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { mount } from 'enzyme';
import Select from './Select';
import SelectOption from './SelectOption';

const selectOptions = [
<SelectOption value="Mr" />,
<SelectOption value="Mrs" />,
<SelectOption value="Ms" />,
<SelectOption value="Other" />
];

describe('select', () => {
describe('single select', () => {
test('renders closed successfully', () => {
const view = mount(<Select variant="single">{selectOptions}</Select>);
expect(view).toMatchSnapshot();
});

test('renders expanded successfully', () => {
const view = mount(
<Select variant="single" isExpanded>
{selectOptions}
</Select>
);
expect(view).toMatchSnapshot();
});

test('renders with selectOptions parameter', () => {
const view = mount(<Select variant="single" selectOptions={selectOptions} />);
expect(view).toMatchSnapshot();
});
});
});

describe('API', () => {
test('click on item', () => {
const mockToggle = jest.fn();
const mockSelect = jest.fn();
const view = mount(
<Select variant="single" onToggle={mockToggle} onSelect={mockSelect} selectOptions={selectOptions} isExpanded />
);
view
.find('button')
.at(1)
.simulate('click');
expect(mockToggle.mock.calls).toHaveLength(0);
expect(mockSelect.mock.calls).toHaveLength(1);
});

test('selectOptions and children console error ', () => {
const myMock = jest.fn();
global.console = { error: myMock };
mount(
<Select variant="single" selectOptions={selectOptions} isExpanded>
<div> child test </div>
<div> child test </div>
</Select>
);
expect(myMock).toBeCalled();
});

test('selectOptions only, no console error ', () => {
const myMock = jest.fn();
global.console = { error: myMock };
mount(<Select variant="single" selectOptions={selectOptions} isExpanded />);
expect(myMock).not.toBeCalled();
});

test('children only, no console error', () => {
const myMock = jest.fn();
global.console = { error: myMock };
mount(
<Select variant="single" isExpanded>
{selectOptions}
</Select>
);
expect(myMock).not.toBeCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { HTMLProps, FormEvent } from 'react';
import { Omit } from '../../typeUtils';

export interface SelectOptionProps extends Omit<HTMLProps<HTMLOptionElement>, 'disabled'> {
value?: string;
isValid?: boolean;
isDisabled?: boolean;
isPlaceholder?: boolean;
onClick?: Function;
sendRef?: Function;
}

declare const SelectOption: React.FunctionComponent<SelectOptionProps>;

export default SelectOption;
103 changes: 103 additions & 0 deletions packages/patternfly-4/react-core/src/components/Select/SelectOption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import { SelectContext } from './selectConstants';

const propTypes = {
/** the value for the option */
children: PropTypes.string,
/** additional classes added to the Select Option */
className: PropTypes.string,
/** the value for the option */
value: PropTypes.string,
/** flag indicating if the option is disabled */
isDisabled: PropTypes.bool,
/** flag indicating if the option acts as a placeholder */
isPlaceholder: PropTypes.bool,
/** Optional on click callback */
onClick: PropTypes.func,
/** Callback for ref tracking */
sendRef: PropTypes.func,
/** Additional props are spread to the container <button> */
'': PropTypes.any
};

const defaultProps = {
children: null,
className: '',
value: null,
isDisabled: false,
isPlaceholder: false,
onClick: Function.prototype
};

class SelectOption extends React.Component {
ref = React.createRef();

componentDidMount() {
this.props.sendRef(this.ref, this.props.index);
}

onKeyDown = event => {
if (event.key === 'Tab') return;
event.preventDefault();
if (event.key === 'ArrowUp') {
this.props.keyHandler(this.props.index, 'up');
} else if (event.key === 'ArrowDown') {
this.props.keyHandler(this.props.index, 'down');
} else if (event.key === 'Enter') {
this.ref.current.click && this.ref.current.click();
}
};

render() {
const {
children,
className,
value,
onClick,
isDisabled,
isPlaceholder,
selected,
sendRef,
keyHandler,
index,
...props
} = this.props;
return (
<SelectContext.Consumer>
{onSelect => (
<li role="presentation">
<button
{...props}
className={css(
styles.selectMenuItem,
selected && styles.selectMenuItemMatch,
isDisabled && styles.modifiers.disabled,
className
)}
onClick={event => {
if (!isDisabled) {
onClick && onClick(event);
onSelect && onSelect(event, value || children, isPlaceholder);
}
}}
role="option"
aria-selected={selected || null}
ref={this.ref}
onKeyDown={this.onKeyDown}
>
{value || children}
</button>
</li>
)}
</SelectContext.Consumer>
);
}
}

SelectOption.propTypes = propTypes;
SelectOption.defaultProps = defaultProps;

export default SelectOption;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { shallow } from 'enzyme';
import SelectOption from './SelectOption';

describe('select options', () => {
test('renders with value parameter successfully', () => {
const view = shallow(<SelectOption value="test" sendRef={jest.fn()} />);
expect(view).toMatchSnapshot();
});

test('renders with children successfully', () => {
const view = shallow(<SelectOption sendRef={jest.fn()}>test</SelectOption>);
expect(view).toMatchSnapshot();
});

describe('hover', () => {
test('renders with hover successfully', () => {
const view = shallow(<SelectOption isHovered value="test" sendRef={jest.fn()} />);
expect(view).toMatchSnapshot();
});
});

describe('disabled', () => {
test('renders disabled successfully', () => {
const view = shallow(<SelectOption isDisabled value="test" sendRef={jest.fn()} />);
expect(view).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { HTMLProps, ReactType, ReactNode } from 'react';

export interface SelectToggleProps extends HTMLProps<HTMLButtonElement> {
id?: string;
children?: ReactNode;
isExpanded?: boolean;
onToggle?: Function;
parentRef?: HTMLElement;
isFocused?: boolean;
isHovered?: boolean;
isActive?: boolean;
isPlain?: boolean;
type?: string;
iconComponent?: ReactType;
}

declare const SelectToggle: React.FunctionComponent<SelectToggleProps>;

export default SelectToggle;
Loading

0 comments on commit cfdaa12

Please sign in to comment.