@kiwicom/orbit-components
Version:
Orbit-components is a React component library which provides developers with the easiest possible way of building Kiwi.com’s products.
402 lines (401 loc) • 14.9 kB
JavaScript
"use strict";
"use client";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireDefault(require("react"));
var _styledComponents = _interopRequireWildcard(require("styled-components"));
var _helpers = require("./helpers");
var _InputSelectOption = _interopRequireDefault(require("./InputSelectOption"));
var _InputSelect = require("./InputSelect.styled");
var _CloseCircle = _interopRequireDefault(require("../icons/CloseCircle"));
var _InputField = _interopRequireDefault(require("../InputField"));
var _useRandomId = require("../hooks/useRandomId");
var _useClickOutside = _interopRequireDefault(require("../hooks/useClickOutside"));
var _keyMaps = _interopRequireDefault(require("../common/keyMaps"));
var _Box = _interopRequireDefault(require("../Box"));
var _Text = _interopRequireDefault(require("../Text"));
var _Stack = _interopRequireDefault(require("../Stack"));
var _useMediaQuery = _interopRequireDefault(require("../hooks/useMediaQuery"));
var _Modal = _interopRequireWildcard(require("../Modal"));
var _ModalCloseButton = _interopRequireDefault(require("../Modal/ModalCloseButton"));
var _Button = _interopRequireDefault(require("../Button"));
var _Heading = _interopRequireDefault(require("../Heading"));
var _ModalSection = require("../Modal/ModalSection");
var _ModalHeader = require("../Modal/ModalHeader");
const StyledModalSection = (0, _styledComponents.default)(_ModalSection.ModalSectionWrapper).withConfig({
displayName: "InputSelect__StyledModalSection",
componentId: "sc-13l56eg-0"
})(["padding:0;"]);
const StyledModalHeader = (0, _styledComponents.default)(_ModalHeader.ModalHeaderWrapper).withConfig({
displayName: "InputSelect__StyledModalHeader",
componentId: "sc-13l56eg-1"
})(["", ";"], ({
theme
}) => (0, _styledComponents.css)(["padding:", " !important;margin-bottom:0 !important;"], theme.orbit.spaceMedium));
const InputSelect = /*#__PURE__*/_react.default.forwardRef(({
onChange,
options,
defaultSelected,
prevSelected,
prevSelectedLabel = "Previously selected",
id,
onFocus,
label,
showAll = true,
showAllLabel = showAll ? "All options" : "Other options",
help,
error,
onBlur,
placeholder,
labelClose = "Close",
emptyState = "No results found.",
onOptionSelect,
onClose,
disabled,
maxHeight = "400px",
maxWidth,
onKeyDown,
spaceAfter,
...props
}, ref) => {
const randomId = (0, _useRandomId.useRandomIdSeed)();
const labelRef = _react.default.useRef(null);
const inputId = id || randomId("input");
const dropdownId = randomId("dropdown");
const dropdownRef = _react.default.useRef(null);
const [isOpened, setIsOpened] = _react.default.useState(false);
const [inputValue, setInputValue] = _react.default.useState(defaultSelected ? options.find(opt => opt.value === defaultSelected.value)?.title : "");
const [selectedOption, setSelectedOption] = _react.default.useState(defaultSelected || null);
const [activeIdx, setActiveIdx] = _react.default.useState(0);
const [activeDescendant, setActiveDescendant] = _react.default.useState("");
const [isScrolled, setIsScrolled] = _react.default.useState(false);
const [topOffset, setTopOffset] = _react.default.useState(0);
const refs = {};
const {
isLargeMobile
} = (0, _useMediaQuery.default)();
const groupedOptions = _react.default.useMemo(() => (0, _helpers.groupOptions)(options, showAll, prevSelected), [options, prevSelected, showAll]);
const [results, setResults] = _react.default.useState(groupedOptions);
const handleClose = selection => {
if (!selection) {
if (inputValue === "") {
if (onOptionSelect) onOptionSelect(null);
setSelectedOption(null);
} else if (inputValue !== selectedOption?.title) {
setInputValue(selectedOption?.title || "");
}
setResults(groupedOptions);
setActiveIdx(0);
}
if (onClose && isOpened) onClose(selection || (inputValue === "" ? null : selectedOption));
setIsOpened(false);
};
const handleCloseClick = () => {
handleClose();
};
(0, _useClickOutside.default)(labelRef, handleCloseClick);
const handleFocus = ev => {
if (onFocus) onFocus(ev);
setIsOpened(true);
setResults(results || groupedOptions);
};
const handleBlur = ev => {
if (onBlur) onBlur(ev);
};
const handleInputChange = ev => {
const {
value
} = ev.currentTarget;
if (onChange) onChange(ev);
if (value.length === 0) {
setResults(groupedOptions);
} else {
const filtered = options.filter(({
title
}) => {
return title.toLowerCase().includes(value.toLowerCase());
});
setResults({
groups: [],
all: filtered,
flattened: filtered
});
}
if (!isOpened) setIsOpened(true);
setInputValue(value);
setActiveIdx(0);
};
const handleDropdownKey = ev => {
if (!isOpened && (ev.keyCode === _keyMaps.default.ENTER || ev.keyCode === _keyMaps.default.ARROW_DOWN || ev.keyCode === _keyMaps.default.ARROW_UP)) {
setIsOpened(true);
return;
}
if (isOpened && ev.keyCode === _keyMaps.default.ESC) handleClose();
if (isOpened && ev.keyCode === _keyMaps.default.ENTER) {
ev.preventDefault();
if (results.all.length !== 0) {
const option = results.flattened[activeIdx];
setSelectedOption(option);
setInputValue(option.title);
if (onOptionSelect) onOptionSelect(option);
handleClose(option);
}
}
if (ev.keyCode === _keyMaps.default.ARROW_DOWN) {
if (results.flattened.length - 1 > activeIdx) {
const nextIdx = activeIdx + 1;
setActiveIdx(nextIdx);
setActiveDescendant(refs[nextIdx].current?.id);
if (dropdownRef && dropdownRef.current) {
dropdownRef.current.scrollTop = refs[nextIdx].current?.offsetTop;
}
}
}
if (ev.keyCode === _keyMaps.default.ARROW_UP) {
if (activeIdx > 0) {
const prevIdx = activeIdx - 1;
setActiveIdx(prevIdx);
setActiveDescendant(refs[prevIdx].current?.id);
if (dropdownRef && dropdownRef.current) {
dropdownRef.current.scrollTop = refs[prevIdx].current?.offsetTop;
}
}
}
};
const input = /*#__PURE__*/_react.default.createElement(_InputField.default, (0, _extends2.default)({
help: isLargeMobile && help,
error: isLargeMobile && error,
label: isLargeMobile && label,
disabled: disabled,
onFocus: handleFocus,
onBlur: handleBlur,
onChange: handleInputChange,
id: inputId,
placeholder: placeholder,
autoFocus: !isLargeMobile,
role: "combobox",
value: inputValue,
onKeyDown: ev => {
if (onKeyDown) onKeyDown(ev);
handleDropdownKey(ev);
},
ariaHasPopup: isOpened,
ariaExpanded: isOpened,
ariaAutocomplete: "list",
ariaActiveDescendant: activeDescendant,
ariaControls: isOpened ? dropdownId : undefined,
autoComplete: "off",
ref: ref,
prefix: selectedOption && selectedOption.prefix,
suffix: String(inputValue).length > 1 && /*#__PURE__*/_react.default.createElement(_InputSelect.StyledCloseButton, {
onClick: ev => {
ev.preventDefault();
if (onOptionSelect) onOptionSelect(null);
setInputValue("");
setResults(groupedOptions);
setSelectedOption(null);
setActiveIdx(0);
},
$disabled: disabled
}, /*#__PURE__*/_react.default.createElement(_CloseCircle.default, {
color: "primary",
ariaLabel: "Clear"
}))
}, props));
const renderOptions = () => {
if (results.groups.length === 0) {
return results.all.map((option, idx) => {
const {
title,
description,
prefix,
value: optValue
} = option;
const optionId = randomId(title);
const isSelected = optValue === selectedOption?.value;
const optionRef = /*#__PURE__*/_react.default.createRef();
refs[idx] = optionRef;
return /*#__PURE__*/_react.default.createElement(_InputSelectOption.default, {
key: optionId,
id: optionId,
active: activeIdx === idx,
isSelected: isSelected,
ref: optionRef,
title: title,
description: description,
prefix: prefix,
onClick: ev => {
ev.preventDefault();
setActiveIdx(idx);
setResults(groupedOptions);
if (onOptionSelect) onOptionSelect(option);
if (isLargeMobile) setIsOpened(false);
if (!isSelected) {
setInputValue(title);
setSelectedOption(option);
handleClose(option);
}
}
});
});
}
let idx = -1;
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, results.groups.map((group, groupIdx) => {
const prevSelectedOption = prevSelected && groupIdx === 0;
const {
group: groupTitle
} = group[0];
const groupId = randomId(prevSelectedOption ? "prevSelected" : `${groupTitle}`);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
key: groupId
}, /*#__PURE__*/_react.default.createElement(_Box.default, {
padding: "small"
}, /*#__PURE__*/_react.default.createElement(_Text.default, {
type: "secondary"
}, prevSelectedOption ? prevSelectedLabel : groupTitle)), group.map(option => {
idx += 1;
const optionIdx = idx;
const optionRef = /*#__PURE__*/_react.default.createRef();
refs[optionIdx] = optionRef;
const {
title,
description,
prefix,
value: optValue
} = option;
const optionId = randomId(title);
const isSelected = optValue === selectedOption?.value;
return /*#__PURE__*/_react.default.createElement(_InputSelectOption.default, {
key: optionId,
id: optionId,
active: !!isLargeMobile && activeIdx === optionIdx,
isSelected: isSelected,
ref: optionRef,
title: title,
description: description,
prefix: prefix,
onClick: ev => {
ev.preventDefault();
if (onOptionSelect) onOptionSelect(option);
setActiveIdx(optionIdx);
setResults(groupedOptions);
if (isLargeMobile) setIsOpened(false);
if (!isSelected) {
setInputValue(title);
setSelectedOption(option);
handleClose(option);
}
}
});
}));
}), /*#__PURE__*/_react.default.createElement(_Box.default, {
padding: "small"
}, /*#__PURE__*/_react.default.createElement(_Text.default, {
type: "secondary"
}, showAllLabel)), results.all.map(option => {
const {
title,
description,
prefix,
value: optValue,
group
} = option;
if (group && !showAll) return null;
idx += 1;
const optionRef = /*#__PURE__*/_react.default.createRef();
const optionIdx = idx;
refs[optionIdx] = optionRef;
const optionId = randomId(`all_${title}`);
const isSelected = optValue === selectedOption?.value;
return /*#__PURE__*/_react.default.createElement(_InputSelectOption.default, {
key: optionId,
id: optionId,
active: activeIdx === optionIdx,
isSelected: isSelected,
ref: optionRef,
title: title,
description: description,
prefix: prefix,
onClick: ev => {
ev.preventDefault();
if (onOptionSelect) onOptionSelect(option);
setActiveIdx(optionIdx);
setResults(groupedOptions);
if (isLargeMobile) setIsOpened(false);
if (!isSelected) {
setInputValue(title);
setSelectedOption(option);
handleClose(option);
}
}
});
}));
};
const noResults = typeof emptyState === "string" ? /*#__PURE__*/_react.default.createElement(_Box.default, {
padding: "medium"
}, /*#__PURE__*/_react.default.createElement(_Text.default, null, emptyState)) : emptyState;
const dropdown = isOpened && /*#__PURE__*/_react.default.createElement(_InputSelect.StyledDropdown, {
role: "listbox",
id: dropdownId,
"aria-labelledby": inputId,
$hasLabel: !!label,
$maxHeight: maxHeight,
$maxWidth: maxWidth,
ref: dropdownRef
}, results.all.length === 0 ? noResults : renderOptions());
return isLargeMobile ? /*#__PURE__*/_react.default.createElement(_InputSelect.StyledLabel, {
htmlFor: inputId,
ref: labelRef,
spaceAfter: spaceAfter
}, input, dropdown) : /*#__PURE__*/_react.default.createElement(_InputSelect.StyledLabel, {
htmlFor: inputId,
ref: labelRef,
spaceAfter: spaceAfter
}, /*#__PURE__*/_react.default.createElement(_InputField.default, {
label: label,
help: help,
error: error,
onFocus: () => setIsOpened(true),
readOnly: true,
role: "textbox",
placeholder: placeholder,
value: inputValue,
prefix: selectedOption && selectedOption.prefix
}), isOpened && /*#__PURE__*/_react.default.createElement(_InputSelect.StyledModalWrapper, {
$maxHeight: maxHeight,
isScrolled: isScrolled && topOffset > 50
}, /*#__PURE__*/_react.default.createElement(_Modal.default, {
labelClose: labelClose,
onClose: handleCloseClick,
fixedFooter: true,
onScroll: ev => {
if (!isLargeMobile) {
ev.preventDefault();
setIsScrolled(true);
setTopOffset(ev.currentTarget.scrollTop);
}
},
mobileHeader: false,
autoFocus: true
}, /*#__PURE__*/_react.default.createElement(StyledModalHeader, null, label && /*#__PURE__*/_react.default.createElement(_Stack.default, {
align: "center",
justify: "between"
}, /*#__PURE__*/_react.default.createElement(_Box.default, null, /*#__PURE__*/_react.default.createElement(_Heading.default, {
type: "title2"
}, label)), /*#__PURE__*/_react.default.createElement(_ModalCloseButton.default, {
onClick: handleCloseClick,
title: labelClose
})), input), /*#__PURE__*/_react.default.createElement(StyledModalSection, null, dropdown), /*#__PURE__*/_react.default.createElement(_Modal.ModalFooter, {
flex: "100%"
}, /*#__PURE__*/_react.default.createElement(_Button.default, {
type: "secondary",
fullWidth: true,
onClick: handleCloseClick
}, labelClose)))));
});
InputSelect.displayName = "InputSelect";
var _default = InputSelect;
exports.default = _default;