UNPKG

@activecollab/components

Version:

ActiveCollab Components

456 lines • 18.9 kB
import _taggedTemplateLiteralLoose from "@babel/runtime/helpers/esm/taggedTemplateLiteralLoose"; import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; import _styled, { default as _styled2, default as _styled3, default as _styled4, default as _styled5 } from "styled-components"; var _templateObject, _templateObject2, _templateObject3, _templateObject4; const _excluded = ["options", "selected", "loading", "loadingText", "onChange", "onInputChange", "placeholder", "handleEmptyAction", "disabled", "size", "invalid", "type", "renderChip", "forceCloseMenu", "renderOption", "limitChips", "hiddenNumberText", "inPortal", "inputWrapperClassName", "scrollWrapper", "onClose", "onClear", "onDeselect", "defaultValue", "open", "onOpen", "triggerMode", "noResultText", "emptyValue", "disableVirtualization", "hideClearButton", "errorMessage", "onCancel"]; import React, { useCallback, useState, useEffect, useRef, useMemo, Fragment } from "react"; import { RemoveScroll } from "react-remove-scroll"; import classNames from "classnames"; import { StyledComboBoxCloseSmallIcon, StyledComboBoxCollapseExpandSingleIcon, StyledComboBoxInput, StyledComboBoxList, StyledChipWrapper, StyledInfoIcon, StyledInfoSmallIcon } from "./Styles"; import { Portal } from "../../helpers"; import { ConditionalWrapper } from "../../helpers/ConditionWrapper/ConditionWrapper"; import { useIsFirstRender } from "../../hooks/useIsFirstRender"; import { useOnClickOutside } from "../../hooks/useOnClickOutside"; import useForkRef from "../../utils/useForkRef"; import { Autocomplete } from "../Autocomplete"; import { isOptionGroup } from "../Autocomplete/Autocomplete"; import { Avatar } from "../Avatar"; import { Checkbox } from "../Checkbox"; import { Chip } from "../Chip"; import { IconButton } from "../IconButton"; import { InputAdornment } from "../Input/InputAdornment"; import { StyledInput, StyledInputWrapper } from "../Input/Styles"; import { SpinnerLoader } from "../Loaders/Spinner/SpinnerLoader"; import { MultiAvatar } from "../MultiAvatar"; import { Popper } from "../Popper"; import { RadioButton } from "../RadioButton"; import { OptionContent } from "../Select/OptionContent/OptionContent"; import { Tooltip } from "../Tooltip"; import { Trigger } from "../Trigger"; import { Typography } from "../Typography/Typography"; const sizeMap = { small: 18, regular: 24, big: 30, biggest: 40 }; export const ComboBox = _ref => { var _comboBoxRef$current2, _comboBoxRef$current3; let { options = [], selected, loading = false, loadingText, onChange, onInputChange, placeholder, handleEmptyAction, disabled = false, size = "regular", invalid = false, type = "single", renderChip, forceCloseMenu = type === "single", renderOption, limitChips = 2, hiddenNumberText, inPortal = false, inputWrapperClassName, scrollWrapper, onClose, onClear, onDeselect, defaultValue, open: defaultOpen = false, onOpen, triggerMode, noResultText, emptyValue, disableVirtualization, hideClearButton = false, errorMessage, onCancel } = _ref, prop = _objectWithoutPropertiesLoose(_ref, _excluded); const selectedName = useMemo(() => { let value = ""; if (!selected) { return value; } options.map(v => { if (v != null && v.options) { return v.options.find(q => { if (q.id === selected) { value = q.name; return; } }); } else { if (v.id === selected) { value = v.name; return; } } }); return value; }, [options, selected]); const [value, setValue] = useState(selectedName); const [focused, setFocused] = useState(false); const handleOnChange = useCallback(e => { setValue(e.target.value); onInputChange == null || onInputChange(e.target.value); }, [onInputChange]); const childNode = useRef(); const elementRef = useRef(null); const handleRef = useForkRef(childNode, elementRef); const wrapperRef = useRef(null); const chipWrapper = useRef(null); const comboBoxRef = useRef(null); const [open, setOpen] = useState(defaultOpen); const isFirstRender = useIsFirstRender(); useOnClickOutside(wrapperRef, e => { var _comboBoxRef$current; if (e.target && (_comboBoxRef$current = comboBoxRef.current) != null && _comboBoxRef$current.contains(e.target)) { return; } onCancel == null || onCancel(selectedName); setOpen(false); }); const handleOnKeyDown = useCallback(e => { if (e.key === "Escape" && open) { setOpen(false); setValue(selectedName); onCancel == null || onCancel(selectedName); e.stopPropagation(); } if (e.key === "Enter" && type === "multiple") { setValue(""); } if (e.key === "Enter" && !open && focused) { setOpen(true); onOpen == null || onOpen(); } }, [focused, onCancel, onOpen, open, selectedName, type]); useEffect(() => { var _childNode$current, _childNode$current2; open && (childNode == null || (_childNode$current = childNode.current) == null ? void 0 : _childNode$current.focus()); !open && (childNode == null || (_childNode$current2 = childNode.current) == null ? void 0 : _childNode$current2.blur()); }, [childNode, open]); useEffect(() => { setValue(selectedName); }, [selectedName]); useEffect(() => { if (open !== defaultOpen) setOpen(defaultOpen); // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultOpen]); useEffect(() => { if (!open && !isFirstRender && !focused) { if (typeof onClose === "function") { onClose(); } } }, [focused, isFirstRender, onClose, open]); const emptyAction = useCallback(v => { handleEmptyAction == null || handleEmptyAction(v); setOpen(false); }, [handleEmptyAction]); const handleChange = useCallback(selectedValue => { if (selectedValue !== selected && typeof onChange === "function") { onChange(selectedValue); } if (forceCloseMenu) { setOpen(false); } }, [forceCloseMenu, onChange, selected]); const handleOpen = useCallback(() => { if (!disabled) { setOpen(true); onOpen == null || onOpen(); } }, [disabled, onOpen]); const handleRenderOption = useCallback((option, props) => { if (typeof renderOption === "function") { return renderOption(option, props); } if (type === "multiple") { return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(OptionContent, { imageUrl: option.image, color: option.color, textColor: option.textColor, name: option.name }), /*#__PURE__*/React.createElement(Checkbox, props)); } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(OptionContent, { imageUrl: option.image, color: option.color, textColor: option.textColor, name: option.name }), /*#__PURE__*/React.createElement(RadioButton, props)); }, [type, renderOption]); const handleDeselect = useCallback(e => { e.stopPropagation(); if (typeof onDeselect === "function") { onDeselect(); } else if (type === "multiple") { onChange == null || onChange([]); } else { onChange == null || onChange(""); } onClear == null || onClear(); setOpen(false); }, [type, onChange, onClear, onDeselect]); const handleMouseDown = useCallback(e => { e.preventDefault(); }, []); const handleBlur = useCallback(() => { setValue(selectedName); setFocused(!focused); }, [focused, selectedName]); const renderChipAdornment = useCallback((option, index) => { if (typeof renderChip === "function") { return renderChip(option, index); } const avatarProps = { size: 18 }; switch (size) { case "big": avatarProps.size = 22; break; case "small": avatarProps.size = 14; break; } const startAdornment = option.image ? /*#__PURE__*/React.createElement(_StyledAvatar, _extends({ url: option.image }, avatarProps, { $_css: { "marginTop": "0.125rem", "marginBottom": "0.125rem" }, $_css2: { "marginRight": "0.125rem" } })) : undefined; const onClose = e => { e.preventDefault(); e.stopPropagation(); if (Array.isArray(selected) && selected.includes(option.id)) { if (typeof onChange === "function") { const newSelected = selected.filter(item => item !== option.id); onChange(newSelected); } } }; let showOnClose = true; if (disabled) { showOnClose = false; } if (option.color) { showOnClose = false; } return /*#__PURE__*/React.createElement(Chip, { className: "c-combo-box-chip", startAdornment: startAdornment, label: option.name, key: index, backgroundColor: option.color, color: option.chipColor, onClose: showOnClose ? onClose : undefined, size: size }); }, [disabled, onChange, renderChip, selected, size]); const autoSize = useMemo(() => { if (type === "multiple" && open && Array.isArray(selected) && selected.length > 1) { return "auto"; } return size; }, [open, selected, size, type]); const startAdornment = useMemo(() => { if (type === "multiple" && Array.isArray(selected) && selected.length > 0) { const elements = []; let total = 0; options.forEach((option, index) => { const isGrouped = isOptionGroup(option); if (selected.includes(option.id) && !isGrouped) { total = total + 1; elements.push(renderChipAdornment(option, index)); } else if (isGrouped) { const groupedOptions = option.options.filter(o => selected.includes(o.id)); total = total + groupedOptions.length; if (groupedOptions.length > 0) { groupedOptions.forEach((o, _index) => { elements.push(renderChipAdornment(o, index + "_" + _index)); }); } } }); let hidden = 0; if (!open && limitChips > 0 && elements.length > limitChips) { hidden = elements.splice(limitChips, elements.length - limitChips).length; } return /*#__PURE__*/React.createElement(Fragment, null, elements, hidden > 0 && /*#__PURE__*/React.createElement(_StyledTypography, { variant: size === "small" ? "Caption 1" : size === "regular" ? "Body 2" : "Body 1" }, typeof hiddenNumberText === "function" ? hiddenNumberText(hidden) : "+" + hidden)); } if (type === "single" && selected) { let optionItem; options.forEach(option => { if (isOptionGroup(option)) { optionItem = option.options.find(child => child.id === selected); } else if (!optionItem && option.id === selected) { optionItem = option; } }); if (optionItem && optionItem.image) { const avatarSize = size === "big" ? 22 : size === "regular" ? 18 : 14; if (Array.isArray(optionItem.image)) { return /*#__PURE__*/React.createElement(MultiAvatar, { url: optionItem.image, size: avatarSize }); } return /*#__PURE__*/React.createElement(Avatar, { url: optionItem.image, size: avatarSize }); } } return undefined; }, [type, selected, options, open, limitChips, hiddenNumberText, renderChipAdornment, size]); const showXIcon = Array.isArray(selected) && type === "multiple" && selected.length > 0 && !hideClearButton; const hasSelected = type === "multiple" && Array.isArray(selected) && selected.length > 0; const showMenu = useMemo(() => { const hasOptions = (options == null ? void 0 : options.length) > 0; const valueExists = option => (option.id.toString().includes(value) || option.name.includes(value)) && !(option != null && option.hidden); const isValueInOptions = options == null ? void 0 : options.some(option => isOptionGroup(option) ? option.options.some(valueExists) : valueExists(option)); return hasOptions && (!!defaultValue || !value) || value && (isValueInOptions || emptyValue && !!handleEmptyAction || noResultText); }, [defaultValue, emptyValue, handleEmptyAction, noResultText, options, value]); return /*#__PURE__*/React.createElement("div", { className: "c-combo-box" }, type === "multiple" ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_StyledStyledInputWrapper, { $disabled: disabled, $size: size, $invalid: invalid, ref: comboBoxRef, $mode: triggerMode, className: classNames("c-combo-box-input-wrapper", inputWrapperClassName), onClick: () => { var _elementRef$current; (_elementRef$current = elementRef.current) == null || _elementRef$current.click(); }, $_css3: autoSize === "auto" ? "auto" : undefined, $_css4: autoSize === "auto" && scrollWrapper ? "scroll" : undefined }, /*#__PURE__*/React.createElement(_StyledStyledChipWrapper, { $autoSize: autoSize, $open: open, ref: chipWrapper, $_css5: autoSize === "auto" && scrollWrapper ? sizeMap[size] * 3 + 8 + "px" : undefined }, startAdornment, /*#__PURE__*/React.createElement(StyledInput, { className: "c-combo-box-input", onBlur: handleBlur, onFocus: () => { setFocused(!focused); }, onClick: handleOpen, ref: handleRef, value: loading && loadingText ? loadingText : value, onKeyDown: handleOnKeyDown, onChange: handleOnChange, placeholder: !hasSelected ? placeholder : undefined, $mode: triggerMode, disabled: disabled, $size: size, $loading: loading, style: { display: "flex", flex: 1, alignSelf: "center", width: autoSize === "auto" ? "auto" : "100%" } })), !disabled && /*#__PURE__*/React.createElement(React.Fragment, null, loading ? /*#__PURE__*/React.createElement(SpinnerLoader, null) : /*#__PURE__*/React.createElement(React.Fragment, null, showXIcon && /*#__PURE__*/React.createElement(_StyledTrigger, { onMouseDown: handleMouseDown, onClick: handleDeselect, "data-testid": "deselect-all" }, /*#__PURE__*/React.createElement(StyledComboBoxCloseSmallIcon, null)), invalid && errorMessage && /*#__PURE__*/React.createElement(Tooltip, { title: errorMessage }, size === "regular" ? /*#__PURE__*/React.createElement(StyledInfoSmallIcon, null) : /*#__PURE__*/React.createElement(StyledInfoIcon, null)), showMenu && /*#__PURE__*/React.createElement(StyledComboBoxCollapseExpandSingleIcon, { $open: open }))))) : /*#__PURE__*/React.createElement(StyledComboBoxInput, { className: classNames("c-combo-box-input", inputWrapperClassName), onBlur: handleBlur, wrapperClick: handleOpen, onFocus: () => { setFocused(!focused); handleOpen(); }, onClick: handleOpen, mode: triggerMode, wrapRef: comboBoxRef, ref: handleRef, value: loading && loadingText ? loadingText : value, onKeyDown: handleOnKeyDown, onChange: handleOnChange, placeholder: placeholder, disabled: disabled, size: size, invalid: invalid, $loading: loading, startAdornment: startAdornment, endAdornment: !disabled ? /*#__PURE__*/React.createElement(InputAdornment, { disablePointerEvents: disabled }, loading && /*#__PURE__*/React.createElement(SpinnerLoader, null), !loading && selected && !hideClearButton ? /*#__PURE__*/React.createElement(IconButton, { onMouseDown: handleMouseDown, onClick: handleDeselect, variant: "text gray", size: "small" }, /*#__PURE__*/React.createElement(StyledComboBoxCloseSmallIcon, null)) : null, invalid && errorMessage && /*#__PURE__*/React.createElement(Tooltip, { title: errorMessage }, size === "regular" ? /*#__PURE__*/React.createElement(StyledInfoSmallIcon, null) : /*#__PURE__*/React.createElement(StyledInfoIcon, null)), showMenu && /*#__PURE__*/React.createElement(StyledComboBoxCollapseExpandSingleIcon, { $open: open })) : null }), !disabled ? /*#__PURE__*/React.createElement(ConditionalWrapper, { condition: inPortal, wrap: children => /*#__PURE__*/React.createElement(Portal, null, children) }, /*#__PURE__*/React.createElement(Popper, { anchorEl: comboBoxRef.current, open: open, placement: "bottom", style: { zIndex: 10, width: (_comboBoxRef$current2 = comboBoxRef.current) == null ? void 0 : _comboBoxRef$current2.clientWidth }, ref: wrapperRef, strategy: "fixed" }, /*#__PURE__*/React.createElement(RemoveScroll, { noIsolation: true, allowPinchZoom: true }, /*#__PURE__*/React.createElement(StyledComboBoxList, { className: "c-combo-box--list", $width: (_comboBoxRef$current3 = comboBoxRef.current) == null ? void 0 : _comboBoxRef$current3.clientWidth, $hide: !showMenu }, /*#__PURE__*/React.createElement(Autocomplete, _extends({}, prop, { emptyValue: emptyValue, noResultText: noResultText, inputEl: childNode, selected: selected, handleChange: handleChange, renderOption: handleRenderOption, options: options, type: type, defaultValue: defaultValue, handleEmptyAction: emptyAction, disableVirtualization: disableVirtualization })))))) : null); }; ComboBox.displayName = "ComboBox"; var _StyledAvatar = _styled(Avatar)(_templateObject || (_templateObject = _taggedTemplateLiteralLoose(["\n ", "\n ", "\n "])), p => p.$_css, p => p.$_css2); var _StyledTypography = _styled2(Typography)(_templateObject2 || (_templateObject2 = _taggedTemplateLiteralLoose(["\n display: flex;\n flex-shrink: 0;\n align-items: center;\n "]))); var _StyledStyledInputWrapper = _styled3(StyledInputWrapper)(_templateObject3 || (_templateObject3 = _taggedTemplateLiteralLoose(["\n display: flex;\n justify-content: space-between;\n flex: 1;\n height: ", ";\n overflow-y: ", ";\n "])), p => p.$_css3, p => p.$_css4); var _StyledStyledChipWrapper = _styled4(StyledChipWrapper)(_templateObject4 || (_templateObject4 = _taggedTemplateLiteralLoose(["\n max-height: ", ";\n "])), p => p.$_css5); var _StyledTrigger = _styled5(Trigger).withConfig({ displayName: "ComboBox___StyledTrigger", componentId: "sc-uvsz9l-0" })(["display:flex;justify-content:center;"]); //# sourceMappingURL=ComboBox.js.map