@boomerang-io/carbon-addons-boomerang-react
Version:
Carbon Addons for Boomerang apps
114 lines (111 loc) • 5.47 kB
JavaScript
import React from 'react';
import cx from 'classnames';
import { ComboBox } from '@carbon/react';
import { Information } from '@carbon/react/icons';
import TooltipHover from '../TooltipHover/TooltipHover.js';
import { prefix } from '../../internal/settings.js';
/*
IBM Confidential
694970X, 69497O0
© Copyright IBM Corp. 2022, 2024
*/
function ComboBoxComponent({ disableClear = false, id, label, labelText, titleText, tooltipClassName = `${prefix}--bmrg-select__tooltip`, tooltipContent, tooltipProps = { direction: "top" }, onChange, onInputChange, shouldFilterItem, ...restComboBoxProps }) {
// Set the initial selected item to the label or single value passed
const selectedItemRef = React.useRef(restComboBoxProps.initialSelectedItem?.label ?? restComboBoxProps.initialSelectedItem);
const queryRef = React.useRef(selectedItemRef.current);
const [hasQuery, setHasQuery] = React.useState(false);
// Support several props for the label text
const labelValue = titleText || label || labelText;
/**
* The following three functions are to support a better ComboBox filtering experience
* than the default or passing a `shouldFilterItem` function. With the latter, if you have a selected item
* only that will be displayed if you do a naive filtering of the input.
* We want to:
* 1. Filter options based on the input text and plain value or label of the item
* 2. After selecting a value, show all options when opening the combobox without having to clear the selection
* 3. Filter the values when you enter a query with an item selected
*/
/**
* Keep track of the selected value with a ref so it doesn' re-render and to ensure that
* onInputChange has a fresh value. `onChange` is called, then `onInputChange` when selecting an item
*/
const defaultOnChange = React.useCallback(({ selectedItem }) => {
if (!selectedItem) {
selectedItemRef.current = selectedItem;
}
if (typeof selectedItem === "string" || typeof selectedItem === "number") {
selectedItemRef.current = selectedItem;
}
else {
selectedItemRef.current = selectedItem?.label;
}
// Additional check if the onInputChange function is not called
// Isn't triggered if the query value matches the selected one
if (queryRef.current === selectedItemRef.current) {
setHasQuery(false);
}
// Call consumer
if (onChange) {
onChange({ selectedItem });
}
}, [onChange]);
/**
* When an item is selected, the `onInputChange` handler is called with the value selected
* so it is difficult to disambiguate between a keydown event and a select event
* Take a simple approach here. If the selectedItem and input values match, there isn't a query
* If they don't, there is a query. We use this to determine if we should filter the values.
*/
const defaultInputChange = (input) => {
queryRef.current = input;
if (input !== selectedItemRef.current) {
setHasQuery(true);
}
else {
setHasQuery(false);
}
// Call consumer
if (onInputChange) {
onInputChange(input);
}
};
/**
* Determine if I should filter the items or not
* Selected value and no query means show everything, otherwise filter based on the input
* No point in optimizing this because re-renders will only occur on query changes
* and we need fresh values on those events to determine how to filter
*/
const defaultShouldFilterItem = ({ item, inputValue }) => {
if (selectedItemRef.current && !hasQuery) {
return true;
}
if (typeof item === "string" || typeof item === "number") {
return String(item).toLowerCase().includes(inputValue?.toLowerCase());
}
if (item && item.label) {
return String(item.label).toLowerCase().includes(inputValue?.toLowerCase());
}
return item;
};
/**
* If a function is passed, use that
* If a false or null value is explicitely passed, then use default filtering behavior in component
* Otherwise use our filtering logic as the new default
*/
let finalShouldFilterItem;
if (typeof shouldFilterItem === "function") {
finalShouldFilterItem = shouldFilterItem;
}
else if (shouldFilterItem === false || shouldFilterItem === null) {
finalShouldFilterItem = undefined;
}
else {
finalShouldFilterItem = defaultShouldFilterItem;
}
return (React.createElement("div", { key: id, className: cx(`${prefix}--bmrg-select`, { "--disableClear": disableClear }) },
React.createElement(ComboBox, { id: id, titleText: labelValue && (React.createElement("div", { style: { display: "flex" } },
React.createElement("div", null, titleText || labelText || label),
tooltipContent && (React.createElement("div", { className: tooltipClassName },
React.createElement(TooltipHover, { ...tooltipProps, tooltipText: tooltipContent },
React.createElement(Information, { size: 16, fill: "currentColor" })))))), onChange: defaultOnChange, onInputChange: defaultInputChange, shouldFilterItem: finalShouldFilterItem, ...restComboBoxProps })));
}
export { ComboBoxComponent as default };