monday-ui-react-core
Version:
Official monday.com UI resources for application development in React.js
297 lines (276 loc) • 7.54 kB
JSX
/* eslint-disable react/jsx-props-no-spreading,react/require-default-props,react/forbid-prop-types */
import React, { useCallback, useMemo, useState } from "react";
import Select, { components } from "react-select";
import AsyncSelect from "react-select/async";
import NOOP from "lodash/noop";
import { WindowedMenuList } from "react-windowed-select";
import PropTypes from "prop-types";
import cx from "classnames";
import MenuComponent from "./components/menu/menu";
import DropdownIndicatorComponent from "./components/DropdownIndicator/DropdownIndicator";
import OptionComponent from "./components/option/option";
import SingleValueComponent from "./components/singleValue/singleValue";
import ClearIndicatorComponent from "./components/ClearIndicator/ClearIndicator";
import { defaultCustomStyles } from "./DropdownConstants";
import { SIZES } from "../../constants/sizes";
import styles, { customTheme } from "./Dropdown.styles";
import "./Dropdown.scss";
const Dropdown = ({
className,
placeholder,
disabled,
onMenuOpen,
onMenuClose,
onFocus,
onBlur,
onChange,
searchable,
options,
defaultValue,
value,
noOptionsMessage,
openMenuOnFocus,
openMenuOnClick,
clearable,
OptionRenderer,
optionRenderer,
ValueRenderer,
valueRenderer,
menuRenderer,
rtl,
size,
asyncOptions,
cacheOptions,
defaultOptions,
isVirtualized,
menuPortalTarget,
extraStyles,
menuIsOpen,
tabIndex,
id
}) => {
const [isOpen, setOpen] = useState(false);
const finalOptionRenderer = optionRenderer || OptionRenderer;
const finalValueRenderer = valueRenderer || ValueRenderer;
const handleMenuOpen = useCallback(
data => {
onMenuOpen(data);
setOpen(true);
},
[onMenuOpen, setOpen]
);
const handleMenuClose = useCallback(
data => {
onMenuClose(data);
setOpen(false);
},
[setOpen, onMenuClose]
);
const customStyles = useMemo(() => extraStyles(styles({ size, rtl })), [size, rtl, extraStyles]);
const Menu = useCallback(props => <MenuComponent {...props} isOpen={isOpen} Renderer={menuRenderer} />, [
isOpen,
menuRenderer
]);
const DropdownIndicator = useCallback(props => <DropdownIndicatorComponent {...props} size={size} />, [size]);
const Option = useCallback(props => <OptionComponent {...props} Renderer={finalOptionRenderer} />, [
finalOptionRenderer
]);
const Input = useCallback(props => <components.Input {...props} aria-label="Dropdown input" />, []);
const SingleValue = useCallback(props => <SingleValueComponent {...props} Renderer={finalValueRenderer} />, [
finalValueRenderer
]);
const ClearIndicator = useCallback(props => <ClearIndicatorComponent {...props} size={size} />, [size]);
const DropDownComponent = asyncOptions ? AsyncSelect : Select;
const asyncAdditions = {
...(asyncOptions && {
loadOptions: asyncOptions,
cacheOptions,
...(defaultOptions && { defaultOptions })
})
};
const additions = {
...(!asyncOptions && { options })
};
return (
<DropDownComponent
className={cx("dropdown-wrapper", className)}
components={{
DropdownIndicator,
Menu,
ClearIndicator,
Input,
...(finalOptionRenderer && { Option }),
...(finalValueRenderer && { SingleValue }),
...(isVirtualized && { MenuList: WindowedMenuList })
}}
size={size}
noOptionsMessage={noOptionsMessage}
placeholder={placeholder}
isDisabled={disabled}
isClearable={clearable}
isSearchable={searchable}
defaultValue={defaultValue}
value={value}
onMenuOpen={handleMenuOpen}
onMenuClose={handleMenuClose}
onFocus={onFocus}
onBlur={onBlur}
onChange={onChange}
openMenuOnFocus={openMenuOnFocus}
openMenuOnClick={openMenuOnClick}
isRtl={rtl}
styles={customStyles}
theme={customTheme}
menuPortalTarget={menuPortalTarget}
menuIsOpen={menuIsOpen}
tabIndex={tabIndex}
id={id}
{...asyncAdditions}
{...additions}
/>
);
};
Dropdown.size = SIZES;
Dropdown.defaultProps = {
className: "",
placeholder: "",
onMenuOpen: NOOP,
onMenuClose: NOOP,
onKeyDown: NOOP,
onFocus: NOOP,
onBlur: NOOP,
onChange: NOOP,
searchable: true,
options: [],
noOptionsMessage: NOOP,
clearable: true,
size: SIZES.MEDIUM,
extraStyles: defaultCustomStyles,
tabIndex: "0",
id: undefined
};
Dropdown.propTypes = {
/**
* Custom style
*/
className: PropTypes.string,
/**
* Placeholder to show when no value was selected
*/
placeholder: PropTypes.string,
/**
* If set to true, dropdown will be disabled
*/
disabled: PropTypes.bool,
/**
* Called when menu is opened
*/
onMenuOpen: PropTypes.func,
/**
* Called when menu is closed
*/
onMenuClose: PropTypes.func,
/**
* Called when key is pressed in the dropdown
*/
onKeyDown: PropTypes.func,
/**
* Called when focused
*/
onFocus: PropTypes.func,
/**
* Called when blurred
*/
onBlur: PropTypes.func,
/**
* Called when selected value has changed
*/
onChange: PropTypes.func,
/**
* If true, search in options will be enabled
*/
searchable: PropTypes.bool,
/**
* The dropdown options
*/
options: PropTypes.arrayOf(PropTypes.object),
/**
* Text to display when there are no options
*/
noOptionsMessage: PropTypes.func,
/**
* If set to true, the menu will open when focused
*/
openMenuOnFocus: PropTypes.bool,
/**
* If set to true, the menu will open when clicked
*/
openMenuOnClick: PropTypes.bool,
/**
* If set to true, clear button will be added
*/
clearable: PropTypes.bool,
/**
* custom option render function
*/
optionRenderer: PropTypes.func,
/**
* custom value render function
*/
valueRenderer: PropTypes.func,
/**
* custom menu render function
*/
menuRenderer: PropTypes.func,
/**
* If set to true, the dropdown will be in Right to Left mode
*/
rtl: PropTypes.bool,
/**
* Set default selected value
*/
defaultValue: PropTypes.object,
/**
* Select menu size from `Dropdown.size` - Dropdown.size.LARGE | Dropdown.size.MEDIUM | Dropdown.size.SMALL
*/
size: PropTypes.string,
/**
* If provided Dropdown will work in async mode. Can be either promise or callback
*/
asyncOptions: PropTypes.oneOfType([
PropTypes.func, // callback
PropTypes.shape({
then: PropTypes.func.isRequired,
catch: PropTypes.func.isRequired
}) // Promise
]),
/**
* If set to true, fetched async options will be cached
*/
cacheOptions: PropTypes.bool,
/**
* If set, `asyncOptions` will be invoked with its value on mount and the resolved results will be loaded
*/
defaultOptions: PropTypes.oneOfType([PropTypes.bool, PropTypes.arrayOf(PropTypes.object)]),
/**
* If set to true, the menu will use virtualization. Virtualized async works only with
*/
isVirtualized: PropTypes.bool,
/**
* Whether the menu should use a portal, and where it should attach
*/
menuPortalTarget: PropTypes.element,
/**
* Custom function to override existing styles, ex: base => {...base, ...myCustomOverrides}
*/
extraStyles: PropTypes.func,
/**
* Tab index for keyboard navigation purposes
*/
tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* ID for the select container
*/
id: PropTypes.string
};
export default Dropdown;