UNPKG

@bryancode/react-select

Version:

A customizable modular select built for React JS

383 lines (372 loc) 12.4 kB
import React, { forwardRef, useMemo, Component, useState, useEffect, useRef, useImperativeHandle } from 'react'; import classNames from 'classnames'; import { createPortal } from 'react-dom'; import { MdArrowDropUp, MdArrowDropDown } from 'react-icons/md'; function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function useContentRenderer(props) { var currentText = useMemo(function () { if (!props.option) return undefined; return props.option.content; }, [props.option]); var className = classNames(props.className, { 'w-full outline-none px-2 py-3 flex-1 cursor-pointer': true, 'placeholder-gray-700': !currentText, 'placeholder-black': !!currentText }); return { model: { currentText: currentText, className: className }, operations: {} }; } var ContentRenderer = /*#__PURE__*/forwardRef(function (props, ref) { var _useContentRenderer = useContentRenderer(props), model = _useContentRenderer.model; return React.createElement("input", Object.assign({}, props, { ref: ref, value: props.text, className: model.className, placeholder: model.currentText || props.placeholder })); }); var PortalContainer = /*#__PURE__*/function (_Component) { _inheritsLoose(PortalContainer, _Component); function PortalContainer() { var _this; _this = _Component.apply(this, arguments) || this; _this.state = { element: null }; return _this; } var _proto = PortalContainer.prototype; _proto.componentDidMount = function componentDidMount() { if (!this.props.portal) { return; } var element = document.createElement('div'); document.body.appendChild(element); this.setState({ element: element }); }; _proto.render = function render() { var element = this.state.element; if (!element) return this.props.children; return createPortal(this.props.children, element); }; return PortalContainer; }(Component); function DropdownIcon(props) { return React.createElement("div", { className: 'min-w-10 flex items-center justify-center select-none', onClick: props.onClick }, props.open ? React.createElement(MdArrowDropUp, { size: 26 }) : React.createElement(MdArrowDropDown, { size: 26 })); } function useOptionContainer(props) { var className = classNames('option-container', { 'flex': props.open, 'hidden': !props.open }); return { model: { className: className }, operations: {} }; } function OptionContainer(props) { var options = props.options, OptionComponent = props.OptionComponent, EmptyListComponent = props.EmptyListComponent; var _useOptionContainer = useOptionContainer(props), model = _useOptionContainer.model; return React.createElement("div", { style: props.style, className: model.className }, React.createElement(React.Fragment, null, options.map(function (value, index) { return OptionComponent == null ? void 0 : OptionComponent({ value: value, index: index, options: options }); }), options.length <= 0 && (EmptyListComponent == null ? void 0 : EmptyListComponent()))); } function EmptyList() { return React.createElement("div", { className: "empty" }, React.createElement("span", null, "List empty")); } function optionClassName(index, options, selected) { return classNames('option', { 'bg-teal-200': !!selected, 'border-b': index !== options.length - 1 }); } function Option(props) { var value = props.value, index = props.index, options = props.options, selected = props.selected, onOptionSelected = props.onOptionSelected; return React.createElement("div", { onMouseDown: function onMouseDown() { return onOptionSelected == null ? void 0 : onOptionSelected(value); }, className: optionClassName(index, options, selected) }, React.createElement("span", { className: "text-sm leading-5" }, value.content)); } /** * Hook that alerts clicks outside of the passed ref */ function useClickOutside(ref) { var _useState = useState(false), outside = _useState[0], setOutside = _useState[1]; useEffect(function () { /** * Alert if clicked on outside of element */ function handleClickOutside(event) { setOutside(!!(ref.current && !ref.current.contains(event.target))); } // Bind the event listener document.addEventListener("mousedown", handleClickOutside); return function () { // Unbind the event listener on clean up document.removeEventListener("mousedown", handleClickOutside); }; }, [ref]); return outside; } var positionContainerInElement = function positionContainerInElement(rect) { var top = rect.top, right = rect.right, left = rect.left, width = rect.width; return { top: top + 46, right: right, left: left, width: width, zIndex: 9999999999 }; }; function useSelect(props, outside) { var _props$options = props.options, options = _props$options === void 0 ? [] : _props$options, _props$value = props.value, value = _props$value === void 0 ? '' : _props$value, components = props.components; var _useState = useState(false), open = _useState[0], setOpen = _useState[1]; var _useState2 = useState(''), text = _useState2[0], setText = _useState2[1]; var _useState3 = useState(), current = _useState3[0], setCurrent = _useState3[1]; var Content = useMemo(function () { return (components == null ? void 0 : components.ContentRenderComponent) || ContentRenderer; }, [components]); var Icon = useMemo(function () { return (components == null ? void 0 : components.DropdownIconComponent) || DropdownIcon; }, [components]); var Container = useMemo(function () { return (components == null ? void 0 : components.OptionsContainerComponent) || OptionContainer; }, [components]); var EmptyListComponent = useMemo(function () { return (components == null ? void 0 : components.EmptyListComponent) || EmptyList; }, [components]); var OptionComponent = useMemo(function () { return (components == null ? void 0 : components.OptionComponent) || Option; }, [components]); var filteredOptions = useMemo(function () { var result = options.filter(function (v) { if (typeof v.content !== 'string') { return v.value.toString().toLowerCase().includes(text.toLowerCase()); } return v.content.toLowerCase().includes(text.toLowerCase()); }); return result; }, [options, text]); var containerClassNames = classNames('select-container', props.className); var selectClassNames = classNames('select', props.selectClassName); var handleOptionSelected = function handleOptionSelected(option) { setOpen(false); setText(''); setCurrent(option); props.onChange == null ? void 0 : props.onChange(option); }; var handleInputChange = function handleInputChange(event) { setText(event.target.value); setCurrent(undefined); }; var clearText = function clearText() { return setText(''); }; var toogleOpen = function toogleOpen() { return setOpen(function (open) { return !open; }); }; var openDropdown = function openDropdown() { return setOpen(true); }; useEffect(function () { if (!outside) return; setOpen(false); }, [outside]); useEffect(function () { if (filteredOptions.length <= 0) { props.onEmpty == null ? void 0 : props.onEmpty(text); } }, [filteredOptions]); useEffect(function () { if (current != null) return; var option = filteredOptions.find(function (v) { return v.value == value; }); setCurrent(option); }, [current, value, filteredOptions]); useEffect(function () { if (!current) return; var opt = filteredOptions.find(function (v) { return v.value === (current == null ? void 0 : current.value); }); if (opt && current === opt) return; setCurrent(opt); }, [filteredOptions]); return { model: { open: open, text: text, current: current, containerClassNames: containerClassNames, selectClassNames: selectClassNames, filteredOptions: filteredOptions, components: { Content: Content, Icon: Icon, Container: Container, EmptyListComponent: EmptyListComponent, OptionComponent: OptionComponent } }, operations: { handleInputChange: handleInputChange, toogleOpen: toogleOpen, openDropdown: openDropdown, clearText: clearText, handleOptionSelected: handleOptionSelected } }; } var select = /*#__PURE__*/forwardRef(function Select(props, ref) { var _props$config2; var container = useRef(null); var input = useRef(null); var outside = useClickOutside(container); var _useSelect = useSelect(props, outside), model = _useSelect.model, operations = _useSelect.operations; var _model$components = model.components, Content = _model$components.Content, Icon = _model$components.Icon, Container = _model$components.Container, _EmptyListComponent = _model$components.EmptyListComponent, _OptionComponent = _model$components.OptionComponent; var optionContainerStyles = useMemo(function () { var _props$config; if ((_props$config = props.config) != null && _props$config.portal && container.current) { return positionContainerInElement(container.current.getBoundingClientRect()); } return { position: 'absolute', width: '100%' }; }, [container.current, props.config]); useImperativeHandle(ref, function () { return _extends({}, model, { value: model.current, openDropdown: function openDropdown() { return operations.openDropdown(); }, clearInput: function clearInput() { return operations.clearText(); } }); }); return React.createElement("div", { ref: container, className: model.containerClassNames }, React.createElement("div", { className: model.selectClassNames }, React.createElement(Content, { ref: input, text: model.text, option: model.current, placeholder: props.placeholder, onChange: operations.handleInputChange, onClick: operations.toogleOpen }), React.createElement(Icon, { open: model.open, onClick: operations.toogleOpen })), React.createElement(PortalContainer, { portal: (_props$config2 = props.config) == null ? void 0 : _props$config2.portal }, React.createElement(Container, { open: model.open, options: model.filteredOptions, style: optionContainerStyles, EmptyListComponent: function EmptyListComponent() { return React.createElement(_EmptyListComponent, null); }, OptionComponent: function OptionComponent(option) { var _option$value, _option$value2, _model$current; return React.createElement(_OptionComponent, { key: (_option$value = option.value) == null ? void 0 : _option$value.value, index: option.index, options: option.options, value: option.value, selected: ((_option$value2 = option.value) == null ? void 0 : _option$value2.value) == ((_model$current = model.current) == null ? void 0 : _model$current.value), onOptionSelected: operations.handleOptionSelected }); } }))); }); export { ContentRenderer, DropdownIcon, EmptyList, Option, OptionContainer, PortalContainer, select as Select, positionContainerInElement, useClickOutside, useContentRenderer, useOptionContainer, useSelect }; //# sourceMappingURL=react-select.esm.js.map