UNPKG

react-smart-select

Version:

A very flexible and smart React select component

357 lines (338 loc) 15.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var styled = require('styled-components'); var React = require('react'); var reactDom = require('@floating-ui/react-dom'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var styled__default = /*#__PURE__*/_interopDefaultLegacy(styled); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); 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 _taggedTemplateLiteralLoose(strings, raw) { if (!raw) { raw = strings.slice(0); } strings.raw = raw; return strings; } var IGNORE_CLASS$1 = 'ignore-click-outside'; var DROPDOWN_CLASS$1 = 'rss-dropdown'; var SELECT_CLASS$1 = 'smart-select'; var OPTION_CLASS = 'rss-option'; var LABEL_CLASS$1 = 'rss-label'; var MOBILE_BREAKPOINT = 375; var _templateObject$6; // --------------------------------------------------------------------- var Background = /*#__PURE__*/styled__default["default"].div(_templateObject$6 || (_templateObject$6 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\ndisplay: none;\n\n@media (max-width: ", "px) {\n display: ", ";\n background: rgba(0,0,0,.5);\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 0;\n left: 0;\n top: 0;\n}\n"])), MOBILE_BREAKPOINT, function (_ref) { var open = _ref.open; return open ? 'block' : 'none'; }); var _templateObject$5; // --------------------------------------------------------------------- var Dropdown = /*#__PURE__*/styled__default["default"].ul(_templateObject$5 || (_templateObject$5 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\nbox-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%),\n 0 8px 10px 1px rgb(0 0 0 / 14%),\n 0 3px 14px 2px rgb(0 0 0 / 12%);\n\ndisplay: none;\n\nborder-radius: 0 0 4px 4px;\nmin-width: 100%;\nz-index: 1000;\nborder: 0;\n\nlist-style: none;\npadding: 0;\nmargin: 0;\n\nbackground: inherit;\nfont-family: inherit;\ntext-align: inherit;\nfont-size: inherit;\ncolor: inherit;\n\n@media (max-width: ", "px) {\n inset: 50% 1rem auto 1rem !important;\n transform: translateY(-50%) !important;\n position: fixed !important;\n\n max-height: calc(85% - 1rem);\n min-width: unset;\n min-height: 50%;\n height: auto;\n width: auto;\n\n border-radius: 4px;\n overflow: auto;\n}\n"])), MOBILE_BREAKPOINT); var _templateObject$4; // --------------------------------------------------------------------- var Wrapper = /*#__PURE__*/styled__default["default"].div(_templateObject$4 || (_templateObject$4 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\nborder-radius: 4px 4px 0 0;\nbox-sizing: border-box;\ndisplay: inline-block;\nposition: relative;\nmin-width: 8rem;\n\nbackground: #f5f5f5;\nfont-family: Roboto,sans-serif;\ntext-align: left;\nfont-size: 1rem;\ncolor: rgba(0,0,0,.87);\n"]))); var _templateObject$3; // --------------------------------------------------------------------- var Label = /*#__PURE__*/styled__default["default"].button(_templateObject$3 || (_templateObject$3 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\ntransition: color .15s ease-in-out,\n background-color .15s ease-in-out,\n border-color .15s ease-in-out,\n box-shadow .15s ease-in-out;\n\nborder-bottom-color: ", ";\nborder-width: 0 0 2px;\n\njustify-content: space-between;\ncursor: pointer;\ndisplay: flex;\nwidth: 100%;\n\nletter-spacing: .009375em;\npadding: .375rem .75rem;\nline-height: 1.75rem;\nwhite-space: nowrap;\nmargin: 0;\n\nbackground: inherit;\nborder-radius: inherit;\nfont-family: inherit;\ntext-align: inherit;\nfont-size: inherit;\ncolor: inherit;\n\n&:hover {\n background: rgba(0,0,0,.15);\n}\n\n&:after {\n transform: ", ";\n font-weight: normal;\n font-style: normal;\n content: \"\\25BE\";\n text-align: right;\n margin-left: 1em;\n flex-grow: 1;\n}\n"], ["\ntransition: color .15s ease-in-out,\n background-color .15s ease-in-out,\n border-color .15s ease-in-out,\n box-shadow .15s ease-in-out;\n\nborder-bottom-color: ", ";\nborder-width: 0 0 2px;\n\njustify-content: space-between;\ncursor: pointer;\ndisplay: flex;\nwidth: 100%;\n\nletter-spacing: .009375em;\npadding: .375rem .75rem;\nline-height: 1.75rem;\nwhite-space: nowrap;\nmargin: 0;\n\nbackground: inherit;\nborder-radius: inherit;\nfont-family: inherit;\ntext-align: inherit;\nfont-size: inherit;\ncolor: inherit;\n\n&:hover {\n background: rgba(0,0,0,.15);\n}\n\n&:after {\n transform: ", ";\n font-weight: normal;\n font-style: normal;\n content: \"\\\\25BE\";\n text-align: right;\n margin-left: 1em;\n flex-grow: 1;\n}\n"])), function (_ref) { var open = _ref.open; return open ? '#6200ee' : 'rgba(0,0,0,.4)'; }, function (_ref2) { var open = _ref2.open; return open ? 'scale(1, -1)' : 'none'; }); var _templateObject$2; // --------------------------------------------------------------------- var Option$1 = /*#__PURE__*/styled__default["default"].li(_templateObject$2 || (_templateObject$2 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\nbackground: inherit;\npadding: 0;\nmargin: 0;\n\n> input {\n pointer-events: none;\n position:absolute;\n z-index: -9999;\n opacity: 0;\n}\n\ninput:focus-visible + label {\n outline: auto;\n}\n\n> label {\n cursor: pointer;\n display: block;\n\n padding: .75rem 1rem;\n white-space: nowrap;\n text-align: inherit;\n font-size: inherit;\n margin: 0;\n\n background: transparent;\n font-family: inherit;\n text-align: inherit;\n font-size: inherit;\n color: inherit;\n\n &.selected:hover {\n background: rgba(98,0,238,.16);\n }\n\n &.selected {\n background: rgba(98,0,238,.08);\n }\n\n &:hover {\n background: rgba(0,0,0,.05);\n }\n}\n"]))); var _templateObject$1; // --------------------------------------------------------------------- var Single = /*#__PURE__*/styled__default["default"](Option$1)(_templateObject$1 || (_templateObject$1 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\ninput[type=checkbox] + label .check {\n display: none;\n}\n"]))); var _templateObject; // --------------------------------------------------------------------- var Multi = /*#__PURE__*/styled__default["default"](Option$1)(_templateObject || (_templateObject = /*#__PURE__*/_taggedTemplateLiteralLoose(["\ninput[type=checkbox] + label .check {\n display: inline-block;\n position: relative;\n height: 1rem;\n width: 1rem;\n top: 0.3rem;\n\n border: 2px solid #6200ee;\n background: transparent;\n border-radius: 3px;\n margin-right: 8px;\n}\n\ninput[type=checkbox]:checked + label .check {\n background: #6200ee;\n\n &:after {\n position: absolute;\n height: 0.6rem;\n width: 0.3rem;\n left: 0.25rem;\n content: \"\";\n\n border-bottom: 3px solid white;\n border-right: 3px solid white;\n transform: rotate(45deg);\n border-left: none;\n border-top: none;\n }\n}\n"]))); // --------------------------------------------------------------------- function useClickOutside(ref, onClick) { React.useEffect(function () { if (typeof onClick !== 'function') { return undefined; } var handler = function handler(evt) { var source = evt.target; var check; var found; if (ref.current === null) { return; } while (source && source.parentNode) { check = source.classList.contains(IGNORE_CLASS$1); found = source === ref.current || check === true; source = source.parentNode; if (found === true) { return; } } evt.stopPropagation(); evt.preventDefault(); onClick(evt); }; document.addEventListener('mousedown', handler, true); return function () { document.removeEventListener('mousedown', handler, true); }; }, [ref, onClick]); } function useToggle(initialValue) { if (initialValue === void 0) { initialValue = false; } var _useState = React.useState(initialValue), value = _useState[0], setValue = _useState[1]; var onToggle = React.useCallback(function (evt) { if (evt) { evt.preventDefault(); } setValue(!value); }, [value]); return [value, onToggle]; } function useRandomId() { return React.useMemo(function () { return Math.random().toString(36).substring(2, 6) || '-'; }, []); } function formatSimple(option) { return option.label; } // --------------------------------------------------------------------- var SmartOption = function SmartOption(props) { var option = props.option, index = props.index, value = props.value, multi = props.multi, style = props.style, onClick = props.onClick, _props$formatOption = props.formatOption, formatOption = _props$formatOption === void 0 ? formatSimple : _props$formatOption; var Styled = multi ? Multi : Single; var _values = value; var _value = value; var current = option.value; var selected; if (multi === true) { selected = _values.some(function (v) { return v.value === current; }); } else { selected = value ? _value.value === current : false; } var className = selected ? OPTION_CLASS + " selected" : OPTION_CLASS; var formatted = formatOption(option, selected); var id = useRandomId(); return React__default["default"].createElement(Styled, { key: current, role: "menuitem" }, React__default["default"].createElement("input", { type: "checkbox", id: id, name: current, checked: selected, "data-index": index, onChange: onClick }), React__default["default"].createElement("label", { htmlFor: id, className: className, style: style }, React__default["default"].createElement("span", { className: "check" }), formatted)); }; var Option = /*#__PURE__*/React__default["default"].memo(SmartOption); // --------------------------------------------------------------------- var toggle = function toggle(values, selected, index, options) { var uncheck = values.some(function (item) { return item.value === selected.value; }); var value = selected.value; if (index === '0') { return uncheck ? [] : options; } if (options.length === values.length) { return options.slice(1).filter(function (item) { return item.value !== value; }); } if (uncheck) { return values.filter(function (item) { return item.value !== value; }); } if (options.length !== values.length + 2) { return values.concat(selected); } return options; }; var multi = function multi(values, selected) { var uncheck = values.some(function (item) { return item.value === selected.value; }); var value = selected.value; var updated; if (uncheck) { updated = values.filter(function (item) { return item.value !== value; }); } else { updated = values.concat(selected); } return updated; }; var formatLabel = function formatLabel(placeholder) { var empty = placeholder || ' '; return function (value, options) { if (Array.isArray(value)) { // we want to preserve options order if (value.length === 0) { return empty; } return options.filter(function (o) { return value.some(function (v) { return v.value === o.value; }); }).map(function (o) { return o.label; }).join(', '); } return value ? value.label : empty; }; }; var DROPDOWN_CLASS = DROPDOWN_CLASS$1; var SELECT_CLASS = SELECT_CLASS$1; var IGNORE_CLASS = IGNORE_CLASS$1; var LABEL_CLASS = LABEL_CLASS$1; var placement = 'bottom-start'; var OPTIONS = { middleware: [/*#__PURE__*/reactDom.shift()], placement: placement }; // --------------------------------------------------------------------- /* eslint-disable react/jsx-props-no-spreading */ var SmartSelect = function SmartSelect(props) { var _props$disabled = props.disabled, disabled = _props$disabled === void 0 ? false : _props$disabled, placeholder = props.placeholder, className = props.className, onChange = props.onChange, options = props.options, _props$toggle = props.toggle, toggle$1 = _props$toggle === void 0 ? false : _props$toggle, _props$multi = props.multi, multi$1 = _props$multi === void 0 ? toggle$1 || false : _props$multi, _props$value = props.value, value = _props$value === void 0 ? multi$1 ? [] : null : _props$value; var _useFloating = reactDom.useFloating(OPTIONS), x = _useFloating.x, y = _useFloating.y, refs = _useFloating.refs, reference = _useFloating.reference, floating = _useFloating.floating, strategy = _useFloating.strategy, update = _useFloating.update; var formatLabel$1 = props.formatLabel || formatLabel(placeholder); var _useToggle = useToggle(), open = _useToggle[0], onToggle = _useToggle[1]; var btnId = useRandomId(); var onClick = React.useCallback(function (evt) { var index = evt.currentTarget.dataset.index || '0'; var num = parseInt(index, 10); var selected = options[num]; // if "toggle" is true, the first element of the list selects // and deselects the whole list. if any other element of the // list is deselected, the first element is deselected too if (multi$1 === true) { var values = value; var updater = toggle$1 ? toggle : multi; var updated = updater(values, selected, index, options); onChange(updated, selected, options); return; } onChange(selected, selected, options); onToggle(); }, [options, value, multi$1, toggle$1, onToggle, onChange]); // ------------------------------------------------------------------- useClickOutside(refs.floating, open ? onToggle : undefined); React.useLayoutEffect(function () { if (open === true) { update(); } }, [open, update]); // ------------------------------------------------------------------- var wrapClass = className ? SELECT_CLASS + " " + className : SELECT_CLASS; var labelClass = open ? LABEL_CLASS + " " + IGNORE_CLASS : LABEL_CLASS; var label = formatLabel$1(value, options); var hidden = !open; var renderOption = function renderOption(option, index) { var formatOption = props.formatOption; var style = props.optionStyle; var key = option.value; return React__default["default"].createElement(Option, { option: option, index: index, value: value, multi: multi$1, key: key, style: style, formatOption: formatOption, onClick: onClick }); }; var roundX = x === null ? 0 : Math.round(x); var roundY = y === null ? 0 : Math.round(y); var dropdown = _extends({}, props.dropdownStyle, { display: open ? 'block' : undefined, position: strategy, left: roundX, top: roundY }); return React__default["default"].createElement(Wrapper, { className: wrapClass, style: props.style }, React__default["default"].createElement(Label, { type: "button", ref: reference, id: btnId, className: labelClass, "aria-haspopup": true, "aria-expanded": open, style: props.labelStyle, open: open, disabled: disabled, onClick: onToggle }, label), React__default["default"].createElement(Background, { open: open }), React__default["default"].createElement(Dropdown, { role: "menu", ref: floating, className: DROPDOWN_CLASS, "aria-labelledby": btnId, "aria-hidden": hidden, style: dropdown }, options.map(renderOption))); }; var smartSelect = /*#__PURE__*/React__default["default"].memo(SmartSelect); exports["default"] = smartSelect; //# sourceMappingURL=react-smart-select.cjs.development.js.map