@activecollab/components
Version:
ActiveCollab Components
456 lines • 18.9 kB
JavaScript
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