wix-style-react
Version:
448 lines (341 loc) • 15.5 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import InputWithOptions from '../InputWithOptions/InputWithOptions';
import Input from '../Input';
import { classes } from './MultiSelectCheckbox.st.css';
import { listItemSelectBuilder } from '../ListItemSelect';
import { listItemSectionBuilder } from '../ListItemSection';
import { optionValidator } from '../DropdownLayout/DropdownLayout';
const OPEN_DROPDOWN_CHARS = ['Enter', 'ArrowDown', 'Space', ' '];
class MultiSelectCheckbox extends InputWithOptions {
createOptions(options) {
return options.map(option => {
if (this._isUsingCustomRenderFunction(option)) {
return this._patchOptionWithSelectionMechanism(option);
} else if (this._isDivider(option)) {
return listItemSectionBuilder({
type: 'divider',
...option,
});
} else {
const builder = listItemSelectBuilder({
...option,
checkbox: true,
title: option.value,
label: option.label,
});
return this._patchOptionWithSelectionMechanism(builder);
}
});
}
_patchOptionWithSelectionMechanism(option) {
const value = option.value;
return {
...option,
value: props =>
value({
...props,
selected: this.isSelectedId(option.id),
}),
};
}
_isUsingCustomRenderFunction({ value }) {
return typeof value === 'function';
}
_isDivider({ value }) {
return value === '-';
}
isSelectedId(optionId) {
return this.props.selectedOptions.indexOf(optionId) !== -1;
}
dropdownAdditionalProps() {
return {
options: this.createOptions(this.props.options),
closeOnSelect: false,
selectedHighlight: false,
};
}
selectedOptionsToText() {
return this.props.selectedOptions
.map(selectedOption =>
this.props.options.find(option => option.id === selectedOption),
)
.filter(selectedOption => selectedOption)
.map(this.props.valueParser)
.join(this.props.delimiter);
}
inputAdditionalProps() {
const value =
this.props.value !== undefined
? this.props.value
: this.selectedOptionsToText();
return {
readOnly: false,
disableEditing: true,
inputElement: <Input textOverflow="ellipsis" disableEditing />,
value,
};
}
inputClasses() {
return classes.showPointer;
}
_onSelect(option) {
this.showOptions();
if (this.closeOnSelect()) {
this.setState({ showOptions: false });
}
// The option object is not the original since it was injected a checkbox
const originalOption = this.props.options.find(op => option.id === op.id);
if (this.isSelectedId(originalOption.id)) {
this.props.onDeselect &&
this.props.onDeselect(originalOption.id, originalOption);
} else {
this.props.onSelect &&
this.props.onSelect(originalOption.id, originalOption);
}
}
_onInputClicked(event) {
this.state.showOptions ? this.hideOptions() : this.showOptions();
if (this.props.onInputClicked) {
this.props.onInputClicked(event);
}
}
_onKeyDown(event) {
if (OPEN_DROPDOWN_CHARS.includes(event.key)) {
event.preventDefault();
this.showOptions();
}
this.dropdownLayout && this.dropdownLayout._onKeyDown(event);
}
_onFocus(e) {
if (this.props.disabled) {
return;
}
this._focused = true;
this.setState({ isEditing: false });
if (this.props.onFocus) {
this.props.onFocus(e);
}
}
}
MultiSelectCheckbox.displayName = 'MultiSelectCheckbox';
MultiSelectCheckbox.propTypes = {
/** Associate a control with the regions that it controls */
ariaControls: PropTypes.string,
/** Associate a region with its descriptions. Similar to aria-controls but instead associating descriptions to the region and description identifiers are separated with a space. */
ariaDescribedby: PropTypes.string,
/** Define a string that labels the current element in case where a text label is not visible on the screen */
ariaLabel: PropTypes.string,
/** Focus the element on mount (standard React input autoFocus) */
autoFocus: PropTypes.bool,
/** Select the entire text of the element on focus (standard React input autoSelect) */
autoSelect: PropTypes.bool,
/** Control the border style of input */
border: PropTypes.oneOf(['standard', 'round', 'bottomLine']),
/** Specifies a CSS class name to be appended to the component’s root element */
className: PropTypes.string,
/** Displays clear button (X) on a non-empty input */
clearButton: PropTypes.bool,
/** Closes `DropdownLayout` when option is selected */
closeOnSelect: PropTypes.bool,
/** Render a custom input component instead of the default html input tag */
customInput: PropTypes.node,
/** Applies a data-hook HTML attribute that can be used in the tests */
dataHook: PropTypes.string,
/** Sets a default value for those who want to use this component un-controlled */
defaultValue: PropTypes.string,
/** Specifies the delimiter symbol to be displayed between the selected options in the input */
delimiter: PropTypes.string,
/** Specifies whether input should be disabled or not */
disabled: PropTypes.bool,
/** Restricts input editing */
disableEditing: PropTypes.bool,
/** Sets the offset of the dropdown from the left in pixels */
dropdownOffsetLeft: PropTypes.string,
/** Sets the width of the dropdown in pixels */
dropdownWidth: PropTypes.string,
/** Adds a fixed footer container at the bottom of options list in `<DropdownLayout/>` */
fixedFooter: PropTypes.node,
/** Adds a fixed header container at the top of options list in `<DropdownLayout/>` */
fixedHeader: PropTypes.node,
/** Highlights and scrolls view to the specified option when dropdown layout is opened. It does not select the specified option. */
focusOnOption: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Scrolls view to the selected option when dropdown layout is opened */
focusOnSelectedOption: PropTypes.bool,
/** USED FOR TESTING - forces focus state on the input */
forceFocus: PropTypes.bool,
/** USED FOR TESTING - forces hover state on the input */
forceHover: PropTypes.bool,
/** Specifies whether there are more items to be loaded */
hasMore: PropTypes.bool,
/** Specifies whether status suffix should be hidden */
hideStatusSuffix: PropTypes.bool,
/** Highlight word parts that match search criteria in bold */
highlight: PropTypes.bool,
/** Assigns an unique identifier for the root element */
id: PropTypes.string,
/** Specifies whether `<DropdownLayout/>` is in a container component. If true, some styles such as shadows, positioning and padding will be added to the component `contentContainer`. */
inContainer: PropTypes.bool,
/** Specifies whether lazy loading of the dropdown layout items is enabled */
infiniteScroll: PropTypes.bool,
/** Allows to render a custom input component instead of the default `<Input/>` */
inputElement: PropTypes.element,
/** Defines a callback function which is called on a request to render more list items */
loadMore: PropTypes.func,
/** Sets the default hover behavior:
* - `false` - no initially hovered list item
* - `true` - hover first selectable option
* - any `number/string` specify the id of an option to be hovered
*/
markedOption: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
PropTypes.number,
]),
/** Sets a maximum value of an input. Similar to HTML5 max attribute. */
max: PropTypes.number,
/** Sets the maximum height of the `dropdownLayout` in pixels */
maxHeightPixels: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Sets the maximum number of characters that can be entered into a field */
maxLength: PropTypes.number,
/** Specifies whether input should have a dropdown menu arrow on the right side */
menuArrow: PropTypes.bool,
/** Sets a minimum value of an input. Similar to HTML5 min attribute. */
min: PropTypes.number,
/** Sets the minimum width of `dropdownLayout` in pixels */
minWidthPixels: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Reference element data when a form is submitted */
name: PropTypes.string,
/** Specifies whether input shouldn’t have rounded corners on its left */
noLeftBorderRadius: PropTypes.bool,
/** Specifies whether input shouldn’t have rounded corners on its right */
noRightBorderRadius: PropTypes.bool,
/** Defines a standard input `onBlur` callback */
onBlur: PropTypes.func,
/** Displays clear button (X) on a non-empty input and calls a callback function with no arguments */
onClear: PropTypes.func,
/** Defines a callback function which is called whenever the user presses the escape key */
onClose: PropTypes.func,
/** Defines a callback function called on `compositionstart`/`compositionend` events */
onCompositionChange: PropTypes.func,
/** Defines a callback function called on option deselect. Function receives the id of the unselected option as the first argument, and the actual option object as the second argument. */
onDeselect: PropTypes.func,
/** Defines a callback handler that is called when user presses -enter- */
onEnterPressed: PropTypes.func,
/** Defines a callback handler that is called when user presses -escape- */
onEscapePressed: PropTypes.func,
/** Defines a standard input `onFocus` callback */
onFocus: PropTypes.func,
/** Defines a standard input `onClick` callback. */
onInputClicked: PropTypes.func,
/** Defines a standard input `onKeyDown` callback */
onKeyDown: PropTypes.func,
/** Defines a standard input `onKeyUp` callback */
onKeyUp: PropTypes.func,
/** Defines a callback function which is called when user performs a submit action. Submit action triggers are:
* "Enter", "Tab", [typing any defined delimiters], paste action.
* `onManuallyInput(values: Array<string>): void` - the array of strings is the result of splitting the input value by the given delimiters. */
onManuallyInput: PropTypes.func,
/** Defines a callback function which is called whenever the user enters dropdown layout with the mouse cursor */
onMouseEnter: PropTypes.func,
/** Defines a callback function which is called whenever the user exits from dropdown layout with a mouse cursor */
onMouseLeave: PropTypes.func,
/** Defines a callback function which is called whenever an option becomes focused (hovered/active). Receives the relevant option object from the original `props.options` array. */
onOptionMarked: PropTypes.func,
/** Defines a callback function which is called when options dropdown is hidden */
onOptionsHide: PropTypes.func,
/** Defines a callback function which is called when options dropdown is shown */
onOptionsShow: PropTypes.func,
/** Defines a callback handler that is called when user pastes text from a clipboard (using mouse or keyboard shortcut) */
onPaste: PropTypes.func,
/** Defines a callback function which is called whenever user selects a different option in the list */
onSelect: PropTypes.func,
/** Array of objects:
* - `id <string / number>` *required*: the id of the option, should be unique;
* - value `<function / string / node>` *required*: can be a string, react element or a builder function;
* - disabled `<bool>` *default value- false*: whether this option is disabled or not;
* - linkTo `<string>`: when provided the option will be an anchor to the given value;
* - title `<bool>` *default value- false* **deprecated**: please use `listItemSectionBuilder` for rendering a title;
* - overrideStyle `<bool>` *default value- false* **deprecated**: please use `overrideOptionStyle` for override option styles;
* - overrideOptionStyle `<bool>` *default value- false* - when set to `true`, the option will be responsible to its own styles. No styles will be applied from the DropdownLayout itself;
* - label `<string>`: the string displayed within an input when the option is selected. This is used when using `<DropdownLayout/>` with an `<Input/>`.
*/
options: PropTypes.arrayOf(optionValidator),
/** Handles container overflow */
overflow: PropTypes.string,
/** Sets a pattern that typed value must match to be valid (regex) */
pattern: PropTypes.string,
/** Sets a placeholder message to display */
placeholder: PropTypes.string,
/** Allows to pass all common popover props. Check `<Popover/>` API for a full list. */
popoverProps: PropTypes.shape({
appendTo: PropTypes.oneOf(['window', 'scrollParent', 'parent', 'viewport']),
maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
minWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
flip: PropTypes.bool,
fixed: PropTypes.bool,
placement: PropTypes.oneOf([
'auto-start',
'auto',
'auto-end',
'top-start',
'top',
'top-end',
'right-start',
'right',
'right-end',
'bottom-end',
'bottom',
'bottom-start',
'left-end',
'left',
'left-start',
]),
dynamicWidth: PropTypes.bool,
}),
/** Pass a component you want to show as the prefix of the input, e.g., text string, icon */
prefix: PropTypes.node,
/** Specifies whether input is read only */
readOnly: PropTypes.bool,
/** Specifies whether input is mandatory */
required: PropTypes.bool,
/** Flip component horizontally so it would be more suitable to RTL */
rtl: PropTypes.bool,
/** Specifies whether selected option will be highlighted when dropdown is reopened */
selectedHighlight: PropTypes.bool,
/** Specifies selected option by its id */
selectedId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Specifies an array of selected options ids */
selectedOptions: PropTypes.array,
/** Controls whether to show options if input is empty */
showOptionsIfEmptyInput: PropTypes.bool,
/** Controls the size of the input */
size: PropTypes.oneOf(['small', 'medium', 'large']),
/** Specify the status of a field */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */
statusMessage: PropTypes.node,
/** Pass a component you want to show as the suffix of the input, e.g., text string, icon */
suffix: PropTypes.node,
/** Indicates that element can be focused and where it participates in sequential keyboard navigation */
tabIndex: PropTypes.number,
/** Handles text overflow behavior. It can either `clip` (default) or display `ellipsis`. */
textOverflow: PropTypes.string,
/** Controls placement of a status tooltip */
tooltipPlacement: PropTypes.string,
/** Specifies the type of `<input>` element to display. Default type is text. */
type: PropTypes.string,
/** Specifies whether component should be shown or hidden */
visible: PropTypes.bool,
/** Specifies the current value of the element */
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
valueParser: PropTypes.func,
};
MultiSelectCheckbox.defaultProps = {
...InputWithOptions.defaultProps,
delimiter: ', ',
selectedOptions: [],
closeOnSelect: false,
valueParser: option => option.label || option.value,
};
export default MultiSelectCheckbox;