UNPKG

@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) 15.3 kB
"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 _clsx = _interopRequireDefault(require("clsx")); var _helpers = require("./helpers"); var _InputSelectOption = _interopRequireDefault(require("./InputSelectOption")); var _CloseCircle = _interopRequireDefault(require("../icons/CloseCircle")); var _InputField = _interopRequireDefault(require("../InputField")); var _useRandomId = require("../hooks/useRandomId"); var _useClickOutside = _interopRequireDefault(require("../hooks/useClickOutside")); 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"); var _tailwind = require("../common/tailwind"); const InputSelect = ({ 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, readOnly, labelClear, ref, ...props }) => { 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 || String(defaultSelected.title) : ""); const [selectedOption, setSelectedOption] = _react.default.useState(defaultSelected || null); const [activeIdx, setActiveIdx] = _react.default.useState(0); const [activeDescendant, setActiveDescendant] = _react.default.useState(undefined); const [isScrolled, setIsScrolled] = _react.default.useState(false); const [topOffset, setTopOffset] = _react.default.useState(0); const refs = _react.default.useMemo(() => { return {}; }, []); 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); _react.default.useEffect(() => { if (inputValue.length === 0) { setResults(groupedOptions); } else { const filtered = options.filter(({ title }) => { return title.toLowerCase().includes(inputValue.toLowerCase()); }); setResults({ groups: [], all: filtered, flattened: filtered }); } }, [inputValue, options, groupedOptions]); _react.default.useEffect(() => { setActiveDescendant(refs[activeIdx]?.current?.id || undefined); }, [activeIdx, refs]); 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); if (!readOnly) { setIsOpened(true); setResults(results || groupedOptions); } }; const handleBlur = ev => { if (onBlur) onBlur(ev); }; const handleInputChange = ev => { const { value } = ev.currentTarget; if (onChange) onChange(ev); if (!isOpened) setIsOpened(true); setInputValue(value); setActiveIdx(0); }; const handleDropdownKey = ev => { if (!isOpened && (ev.code === "Enter" || ev.code === "ArrowDown" || ev.code === "ArrowUp")) { setIsOpened(true); return; } if (isOpened && ev.code === "Escape") handleClose(); if (isOpened && ev.code === "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.code === "ArrowDown") { if (results.flattened.length - 1 > activeIdx) { const nextIdx = activeIdx + 1; setActiveIdx(nextIdx); if (dropdownRef && dropdownRef.current) { dropdownRef.current.scrollTop = refs[nextIdx].current?.offsetTop; } } } if (ev.code === "ArrowUp") { if (activeIdx > 0) { const prevIdx = activeIdx - 1; setActiveIdx(prevIdx); 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: !readOnly ? ev => { if (onKeyDown) onKeyDown(ev); handleDropdownKey(ev); } : undefined, ariaHasPopup: isOpened, ariaExpanded: isOpened, ariaAutocomplete: "list", ariaActiveDescendant: activeDescendant, ariaControls: isOpened ? dropdownId : undefined, autoComplete: "off", ref: ref, readOnly: readOnly, prefix: selectedOption && selectedOption.prefix, suffix: !readOnly && String(inputValue).length > 1 && /*#__PURE__*/_react.default.createElement("button", { className: (0, _clsx.default)("me-200 appearance-none border-0 bg-transparent p-0", disabled && "pointer-events-none cursor-not-allowed"), type: "button", onClick: ev => { ev.preventDefault(); if (onOptionSelect) onOptionSelect(null); setInputValue(""); setResults(groupedOptions); setSelectedOption(null); setActiveIdx(0); } }, /*#__PURE__*/_react.default.createElement(_CloseCircle.default, { color: "primary", ariaLabel: labelClear })) }, 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}_${optValue}`); 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: "300" }, /*#__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}_${optValue}`); 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: "300" }, /*#__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}_${optValue}`); 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: "400" }, /*#__PURE__*/_react.default.createElement(_Text.default, null, emptyState)) : emptyState; const dropdown = isOpened && /*#__PURE__*/_react.default.createElement("div", { className: (0, _clsx.default)("font-base bg-white-normal lm:absolute lm:inset-x-0 lm:overflow-y-scroll lm:shadow-level1 lm:rounded-100 z-[3] flex w-full flex-col", label ? "lm:top-[calc(theme(height.form-box-normal)+theme(spacing.800))]" : "lm:top-[calc(theme(height.form-box-normal)+theme(spacing.200))]"), style: isLargeMobile ? { maxHeight, maxWidth } : undefined, role: "listbox", id: dropdownId, "aria-labelledby": inputId, ref: dropdownRef }, results.all.length === 0 ? noResults : renderOptions()); return /*#__PURE__*/_react.default.createElement("div", { className: (0, _clsx.default)("orbit-input-select relative block", spaceAfter && _tailwind.spaceAfterClasses[spaceAfter]), ref: labelRef }, isLargeMobile ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, input, dropdown) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_InputField.default, { label: label, help: help, error: error, onFocus: !readOnly ? () => setIsOpened(true) : ev => onFocus ? onFocus(ev) : undefined, readOnly: true, role: "textbox", placeholder: placeholder, value: inputValue, prefix: selectedOption && selectedOption.prefix, dataTest: props.dataTest }), isOpened && /*#__PURE__*/_react.default.createElement("div", { className: (0, _clsx.default)("[&_.orbit-input-field-field]:mt-200 [&_.orbit-modal-footer]:shadow-none [&_.orbit-modal-header-container]:sticky [&_.orbit-modal-header-container]:top-0 [&_.orbit-modal-wrapper-content]:h-full", isScrolled && topOffset > 50 && "[&_.orbit-modal-header-container]:pb-400 [&_.orbit-modal-header-container]:shadow-fixed") }, /*#__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, ariaLabel: label }, /*#__PURE__*/_react.default.createElement(_ModalHeader.ModalHeaderWrapper, { className: "!p-400 !mb-0" }, 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(_ModalSection.ModalSectionWrapper, { className: "!p-0" }, dropdown), /*#__PURE__*/_react.default.createElement(_Modal.ModalFooter, { flex: "100%" }, /*#__PURE__*/_react.default.createElement(_Button.default, { type: "secondary", fullWidth: true, onClick: handleCloseClick }, labelClose)))))); }; var _default = exports.default = InputSelect;