react-smart-select
Version:
A very flexible and smart React select component
357 lines (338 loc) • 15.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var styled = require('styled-components');
var React = require('react');
var reactDom = require('-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