UNPKG

optimizely-oui

Version:

Optimizely's Component Library.

311 lines (297 loc) 10.4 kB
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;