@bryancode/react-select
Version:
A customizable modular select built for React JS
401 lines (387 loc) • 13.1 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var classNames = _interopDefault(require('classnames'));
var reactDom = require('react-dom');
var md = require('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 = React.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__*/React.forwardRef(function (props, ref) {
var _useContentRenderer = useContentRenderer(props),
model = _useContentRenderer.model;
return React__default.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 reactDom.createPortal(this.props.children, element);
};
return PortalContainer;
}(React.Component);
function DropdownIcon(props) {
return React__default.createElement("div", {
className: 'min-w-10 flex items-center justify-center select-none',
onClick: props.onClick
}, props.open ? React__default.createElement(md.MdArrowDropUp, {
size: 26
}) : React__default.createElement(md.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__default.createElement("div", {
style: props.style,
className: model.className
}, React__default.createElement(React__default.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__default.createElement("div", {
className: "empty"
}, React__default.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__default.createElement("div", {
onMouseDown: function onMouseDown() {
return onOptionSelected == null ? void 0 : onOptionSelected(value);
},
className: optionClassName(index, options, selected)
}, React__default.createElement("span", {
className: "text-sm leading-5"
}, value.content));
}
/**
* Hook that alerts clicks outside of the passed ref
*/
function useClickOutside(ref) {
var _useState = React.useState(false),
outside = _useState[0],
setOutside = _useState[1];
React.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 = React.useState(false),
open = _useState[0],
setOpen = _useState[1];
var _useState2 = React.useState(''),
text = _useState2[0],
setText = _useState2[1];
var _useState3 = React.useState(),
current = _useState3[0],
setCurrent = _useState3[1];
var Content = React.useMemo(function () {
return (components == null ? void 0 : components.ContentRenderComponent) || ContentRenderer;
}, [components]);
var Icon = React.useMemo(function () {
return (components == null ? void 0 : components.DropdownIconComponent) || DropdownIcon;
}, [components]);
var Container = React.useMemo(function () {
return (components == null ? void 0 : components.OptionsContainerComponent) || OptionContainer;
}, [components]);
var EmptyListComponent = React.useMemo(function () {
return (components == null ? void 0 : components.EmptyListComponent) || EmptyList;
}, [components]);
var OptionComponent = React.useMemo(function () {
return (components == null ? void 0 : components.OptionComponent) || Option;
}, [components]);
var filteredOptions = React.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);
};
React.useEffect(function () {
if (!outside) return;
setOpen(false);
}, [outside]);
React.useEffect(function () {
if (filteredOptions.length <= 0) {
props.onEmpty == null ? void 0 : props.onEmpty(text);
}
}, [filteredOptions]);
React.useEffect(function () {
if (current != null) return;
var option = filteredOptions.find(function (v) {
return v.value == value;
});
setCurrent(option);
}, [current, value, filteredOptions]);
React.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__*/React.forwardRef(function Select(props, ref) {
var _props$config2;
var container = React.useRef(null);
var input = React.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 = React.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]);
React.useImperativeHandle(ref, function () {
return _extends({}, model, {
value: model.current,
openDropdown: function openDropdown() {
return operations.openDropdown();
},
clearInput: function clearInput() {
return operations.clearText();
}
});
});
return React__default.createElement("div", {
ref: container,
className: model.containerClassNames
}, React__default.createElement("div", {
className: model.selectClassNames
}, React__default.createElement(Content, {
ref: input,
text: model.text,
option: model.current,
placeholder: props.placeholder,
onChange: operations.handleInputChange,
onClick: operations.toogleOpen
}), React__default.createElement(Icon, {
open: model.open,
onClick: operations.toogleOpen
})), React__default.createElement(PortalContainer, {
portal: (_props$config2 = props.config) == null ? void 0 : _props$config2.portal
}, React__default.createElement(Container, {
open: model.open,
options: model.filteredOptions,
style: optionContainerStyles,
EmptyListComponent: function EmptyListComponent() {
return React__default.createElement(_EmptyListComponent, null);
},
OptionComponent: function OptionComponent(option) {
var _option$value, _option$value2, _model$current;
return React__default.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
});
}
})));
});
exports.ContentRenderer = ContentRenderer;
exports.DropdownIcon = DropdownIcon;
exports.EmptyList = EmptyList;
exports.Option = Option;
exports.OptionContainer = OptionContainer;
exports.PortalContainer = PortalContainer;
exports.Select = select;
exports.positionContainerInElement = positionContainerInElement;
exports.useClickOutside = useClickOutside;
exports.useContentRenderer = useContentRenderer;
exports.useOptionContainer = useOptionContainer;
exports.useSelect = useSelect;
//# sourceMappingURL=react-select.cjs.development.js.map