Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { default as formStyles } from '@patternfly/patternfly/components/Form/form.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import { keyHandler } from '../../helpers/util';
import FocusTrap from 'focus-trap-react';

const propTypes = {
/** Content rendered inside the CheckboxSelect */
children: PropTypes.node.isRequired,
/** Additional classes added to the CheckboxSelect control */
className: PropTypes.string,
/** Flag indicating the Select is expanded */
isExpanded: PropTypes.bool,
/** Flag indicating whether checkboxes are grouped */
isGrouped: PropTypes.bool,
/** Currently checked options */
checked: PropTypes.arrayOf(PropTypes.string),
/** Additional props are spread to the container <select> */
'': PropTypes.any
};

const defaultProps = {
className: '',
isExpanded: false,
isGrouped: false,
checked: []
};

class CheckboxSelect extends React.Component {
refCollection = [];

extendChildren(props) {
const { children, isGrouped, checked } = this.props;
const { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy } = props;
if (isGrouped) {
let index = 0;
return React.Children.map(children, group =>
React.cloneElement(group, {
titleId: group.props.label.replace(/\W/g, '-'),
children: (
<fieldset aria-labelledby={group.props.label.replace(/\W/g, '-')} className={css(formStyles.formFieldset)}>
{group.props.children.map(option =>
React.cloneElement(option, {
isChecked: checked && checked.includes(option.props.value),
sendRef: this.sendRef,
keyHandler: this.childKeyHandler,
index: index++
})
)}
</fieldset>
)
})
);
}
return (
<fieldset
{...props}
aria-label={ariaLabel}
aria-labelledby={(!ariaLabel && ariaLabelledBy) || null}
className={css(formStyles.formFieldset)}
>
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isChecked: checked && checked.includes(child.props.value),
sendRef: this.sendRef,
keyHandler: this.childKeyHandler,
index
})
)}
</fieldset>
);
}

sendRef = (ref, index) => {
this.refCollection[index] = ref;
};

childKeyHandler = (index, position) => {
keyHandler(index, position, this.refCollection, this.props.isGrouped ? this.refCollection : this.props.children);
};

render() {
const { children, className, isExpanded, checked, isGrouped, ...props } = this.props;
this.renderedChildren = this.extendChildren(props);
return (
<FocusTrap focusTrapOptions={{ clickOutsideDeactivates: true }}>
<div className={css(styles.selectMenu, className)}>
<form noValidate className={css(formStyles.form)}>
<div className={css(formStyles.formGroup)}>{this.renderedChildren}</div>
</form>
</div>
</FocusTrap>
);
}
}

CheckboxSelect.propTypes = propTypes;
CheckboxSelect.defaultProps = defaultProps;

export default CheckboxSelect;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { HTMLProps, FormEvent } from 'react';
import { Omit } from '../../typeUtils';

export interface CheckboxSelectGroupProps extends Omit<HTMLProps<HTMLOptionElement>> {
label?: string;
}

declare const CheckboxSelectGroup: React.FunctionComponent<CheckboxSelectGroupProps>;

export default CheckboxSelectGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';

const propTypes = {
/** Checkboxes within group */
children: PropTypes.node,
/** Additional classes added to the CheckboxSelectGroup control */
className: PropTypes.string,
/** Group label */
label: PropTypes.string,
/** Additional props are spread to the container <select> */
'': PropTypes.any
};

const defaultProps = {
children: null,
className: '',
label: ''
};

const CheckboxSelectGroup = ({ children, className, label, titleId, ...props }) => (
<React.Fragment>
<div {...props} className={css(styles.selectMenuGroup, className)}>
<div className={css(styles.selectMenuGroupTitle)} id={titleId || ''} aria-hidden>
{label}
</div>
{children}
</div>
</React.Fragment>
);

CheckboxSelectGroup.propTypes = propTypes;
CheckboxSelectGroup.defaultProps = defaultProps;

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

describe('checkbox select options', () => {
test('renders with children successfully', () => {
const view = shallow(
<CheckboxSelectGroup label="test">
<div>child</div>
</CheckboxSelectGroup>
);
expect(view).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { HTMLProps, FormEvent } from 'react';
import { Omit } from '../../typeUtils';

export interface CheckboxSelectOptionProps extends Omit<HTMLProps<HTMLOptionElement>, 'disabled'> {
value?: string;
isDisabled?: boolean;
isChecked?: boolean;
onClick?: Function;
}

declare const CheckboxSelectOption: React.FunctionComponent<CheckboxSelectOptionProps>;

export default CheckboxSelectOption;
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { default as checkStyles } from '@patternfly/patternfly/components/Check/check.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import { SelectContext, KeyTypes } 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,
/** Optional on click callback */
onClick: PropTypes.func,
/** Additional props are spread to the container <button> */
'': PropTypes.any
};

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

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

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

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

render() {
const {
children,
className,
value,
onClick,
isDisabled,
isChecked,
sendRef,
keyHandler,
index,
...props
} = this.props;
return (
<SelectContext.Consumer>
{({ onSelect }) => (
<label
{...props}
className={css(
checkStyles.check,
styles.selectMenuItem,
isDisabled && styles.modifiers.disabled,
className
)}
onKeyDown={this.onKeyDown}
>
<input
id={value}
className={css(checkStyles.checkInput)}
type="checkbox"
onChange={event => {
if (!isDisabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should also be a disabled attribute on the <input>.

onClick && onClick(event);
onSelect && onSelect(event, value);
}
}}
ref={this.ref}
checked={isChecked || false}
disabled={isDisabled}
/>
<span className={css(checkStyles.checkLabel, isDisabled && styles.modifiers.disabled)}>{value}</span>
</label>
)}
</SelectContext.Consumer>
);
}
}

CheckboxSelectOption.propTypes = propTypes;
CheckboxSelectOption.defaultProps = defaultProps;

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

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

describe('hover', () => {
test('renders with checked successfully', () => {
const view = mount(<CheckboxSelectOption isChecked value="test" sendRef={jest.fn()} />);
expect(view).toMatchSnapshot();
});
});

describe('disabled', () => {
test('renders disabled successfully', () => {
const view = mount(<CheckboxSelectOption isDisabled value="test" sendRef={jest.fn()} />);
expect(view).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { HTMLProps, FormEvent } from 'react';

export const SelectVariant : {
single: 'single'
export const SelectVariant: {
single: 'single';
checkbox: 'checkbox';
};

export interface SelectProps extends HTMLProps<HTMLOptionElement> {
isExpanded?: boolean;
isGrouped?: boolean;
onToggle(value: boolean): void;
placeholderText?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to maintain the API and not cause breaking changes because we are not ready for a major release.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, will revert that until we can change the API.

selections?: string;
onSelect(event: React.SyntheticEvent<HTMLDivElement>, selection: string): void;
placeholderText?: string | ReactNode;
selections?: string | Array<string>;
variant?: string;
width?: string | number;
ariaLabelledBy?: string;
}

declare const Select: React.FunctionComponent<SelectProps>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Select, SelectOption } from '@patternfly/react-core';
import { Select, SelectOption, CheckboxSelectOption, CheckboxSelectGroup } from '@patternfly/react-core';
import SingleSelectInput from './examples/SingleSelectInput';
import CheckboxSelectInput from './examples/CheckboxSelectInput';
import GroupedCheckboxSelectInput from './examples/GroupedCheckboxSelectInput';

export default {
title: 'Select',
components: {
Select,
SelectOption
SelectOption,
CheckboxSelectOption,
CheckboxSelectGroup
},
variablesRoot: 'pf-c-select',
examples: [{ component: SingleSelectInput, title: 'Single Select Input' }]
examples: [
{ component: SingleSelectInput, title: 'Single Select Input' },
{ component: CheckboxSelectInput, title: 'Checkbox Select Input' },
{ component: GroupedCheckboxSelectInput, title: 'Grouped Checkbox Select Input' }
]
};
Loading