optimizely-oui
Version:
Optimizely's Component Library.
311 lines (297 loc) • 10.4 kB
JavaScript
import PropTypes from "prop-types";
import React from "react";
import classNames from "classnames";
import Button from "../Button";
import Dropdown from "../Dropdown";
import Link from "../Link";
class SelectDropdown extends React.Component {
static propTypes = {
/**
* Button text object with label and content value
* When this prop is defined, value and initialPlaceholder
* are not used.
*/
buttonContent: PropTypes.shape({
label: PropTypes.string,
content: PropTypes.string,
}),
/**
* Style value that is passed to the OUI button that controls the dropdown.
*/
buttonStyle: PropTypes.string,
/** CSS class names. */
className: PropTypes.string,
/**
* Show error by default.
*/
displayError: PropTypes.bool,
/**
* Dropdown direction.
*/
dropdownDirection: PropTypes.oneOf(["right", "left", "up"]),
/**
* Should activator and dropdown be full width of container
*/
fullWidth: PropTypes.bool,
/**
* An initial value for the dropdown before anything is selected
*/
initialPlaceholder: PropTypes.node,
/**
* The select is greyed out if it is disabled.
*/
isDisabled: PropTypes.bool,
/**
* Whether this dropdown supports Multi Select
* If true, automatically sets shouldHideChildrenOnClick to false
*/
isMultiSelect: PropTypes.bool,
/**
* Whether this dropdown is required
* Only applies if "label" prop is supplied
*/
isRequired: PropTypes.bool,
/**
* Dropdown items that can be selected from the select dropdown.
*/
items: PropTypes.arrayOf(
PropTypes.shape({
activatorLabel: PropTypes.node,
description: PropTypes.string,
isSelected: PropTypes.bool,
label: PropTypes.node.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]).isRequired,
linkNewWindow: PropTypes.bool,
linkText: PropTypes.string,
linkURL: PropTypes.string,
})
).isRequired,
/**
* Label to use above the activator
*/
label: PropTypes.string,
/**
* Max width of the activator container.
*/
maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* The minimum width of the dropdown list; any valid CSS width value.
*/
minDropdownWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Note to use below the activator
*/
note: PropTypes.string,
/**
* Function that is called when user selects
* an item from dropdown list.
*/
onChange: PropTypes.func.isRequired,
/** Control the placement of the dropdown */
placement: PropTypes.oneOf([
"top",
"top-start",
"top-end",
"bottom",
"bottom-start",
"bottom-end",
"right",
"right-start",
"right-end",
"left",
"left-start",
"left-end",
]),
/**
* Identifier used to create data-test-section attributes for testing.
*/
testSection: PropTypes.string,
/**
* Identifier used to create data-track-id attributes for Heap testing.
*/
trackId: PropTypes.string,
/**
* Value of currently selected item.
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
/**
* Width of the activator container.
*/
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* zIndex of dropdown group
*/
zIndex: PropTypes.number,
};
static defaultProps = {
buttonStyle: "outline",
initialPlaceholder: "",
displayError: false,
dropdownDirection: "right",
fullWidth: false,
width: "100%",
trackId: "",
testSection: "",
value: "",
};
renderActivator = ({ buttonRef, onClick, onBlur }) => {
const {
buttonContent,
buttonStyle,
value,
width,
isRequired,
label,
maxWidth,
note,
isDisabled,
initialPlaceholder,
trackId,
testSection,
} = this.props;
let selectedItem;
this.props.items.forEach(item => {
if (item.value === value) {
selectedItem = item;
}
});
const outerClass = classNames({
["oui-form-bad-news"]: this.props.displayError,
"oui-dropdown-group__activator": true,
});
let activatorLabel = "";
if (selectedItem) {
activatorLabel = selectedItem.activatorLabel || selectedItem.label;
} else if (initialPlaceholder) {
activatorLabel = initialPlaceholder;
}
return (
<div style={{ width: width, maxWidth: maxWidth }} className={outerClass}>
{label && (
<p className="oui-label">
{label}
{isRequired && <span className="oui-label--required"></span>}
</p>
)}
<Button
title={activatorLabel}
ariaHasPopup={true}
isDisabled={isDisabled}
style={buttonStyle}
size="narrow"
testSection={testSection}
width="full"
buttonRef={buttonRef}
onClick={onClick}
onBlur={onBlur}
>
<div className="flex flex-align--center" data-track-id={trackId}>
<span className="oui-dropdown-group__activator-label flex--1">
{buttonContent && buttonContent.label ? (
<div className="line--tight text--left push--right">
<div className="micro muted">
{buttonContent.label}
<span className="oui-assistive-text">:</span>
</div>
<div>{buttonContent.content}</div>
</div>
) : (
activatorLabel
)}
</span>
<span className="push--left oui-arrow-inline--down" />
</div>
</Button>
{note && (
<div className="oui-form-note" data-test-section={testSection && testSection + "-note"}>
{note}
</div>
)}
</div>
);
};
handleLinkClick = e => {
e.stopPropagation();
};
renderContents = () => {
const { buttonContent, isMultiSelect, items, label, onChange, value, minDropdownWidth, dropdownDirection } =
this.props;
return (
<Dropdown.Contents
minWidth={minDropdownWidth}
direction={dropdownDirection}
ariaLabel={(buttonContent && buttonContent.label) || label}
>
{items.map((entry, index) => (
<Dropdown.ListItem key={entry.value}>
<Dropdown.BlockLink
value={entry.value}
onClick={onChange}
isLink={entry.value !== value}
isItemSelected={entry.isSelected}
isMultiSelect={isMultiSelect}
testSection={"dropdown-block-link-" + entry.value}
>
{entry.label}
{entry.description && <div className="micro muted">{entry.description}</div>}
</Dropdown.BlockLink>
{entry.linkText && entry.linkURL && (
<div className="micro muted">
<Link
title={entry.linkText}
onClick={this.handleLinkClick}
newWindow={entry.linkNewWindow}
href={entry.linkURL}
>
{entry.linkText}
</Link>
</div>
)}
</Dropdown.ListItem>
))}
</Dropdown.Contents>
);
};
render() {
const {
buttonContent,
buttonStyle,
className,
displayError,
dropdownDirection,
fullWidth,
initialPlaceholder,
isDisabled,
isMultiSelect,
isRequired,
items,
label,
maxWidth,
minDropdownWidth,
note,
onChange,
placement,
testSection,
trackId,
value,
width,
zIndex,
...props
} = this.props;
return (
<Dropdown
{...(zIndex ? { zIndex } : {})}
className={className}
placement={placement}
isDisabled={isDisabled}
fullWidth={fullWidth}
renderActivator={this.renderActivator}
shouldHideChildrenOnClick={!isMultiSelect}
{...props}
>
{this.renderContents()}
</Dropdown>
);
}
}
export default SelectDropdown;