@drivy/cobalt
Version:
Opinionated design system for Drivy's projects.
283 lines (282 loc) • 12.3 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { Combobox, Portal, createListCollection, useCombobox } from "@ark-ui/react";
import classnames from "classnames";
import { nanoid } from "nanoid";
import { Fragment as external_react_Fragment, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { getA11yOnClick } from "../../../helpers/index.js";
import { useOnMountEffect } from "../../../hooks/useOnMountEffect.js";
import { CheckIcon, Icon } from "../../Icon/index.js";
import { Hint } from "../Hint.js";
function sanitizeItem(item) {
if ("string" == typeof item) return {
label: item,
value: item
};
{
const { value, label } = item;
return {
...item,
label: label ?? value
};
}
}
function sanitizeItems(items) {
return items.map((item)=>sanitizeItem(item));
}
const HIGHLIGHT_CLASSNAME = "autocomplete_matching_query";
const regExpEscape = (s)=>s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
const AutocompleteMatchHighlight = ({ label, query, matchClassName = HIGHLIGHT_CLASSNAME })=>{
if (0 === query.trim().length) return label;
const queryRX = RegExp(`(${regExpEscape(query)})`, "gi");
const parts = label.split(queryRX);
return /*#__PURE__*/ jsx(Fragment, {
children: parts.map((part, index)=>{
const key = `${index}-${part}`;
return part.match(queryRX) ? /*#__PURE__*/ jsx("span", {
className: classnames(matchClassName),
children: part
}, key) : /*#__PURE__*/ jsx(external_react_Fragment, {
children: part
}, key);
})
});
};
const KEY_CODE_ENTER = 13;
const ignoreItemValue = nanoid();
const _Autocomplete = /*#__PURE__*/ forwardRef(({ id, className, label, hint, fullWidth, icon, status, focusOnInit = false, popoverClassName, minQueryLength = 1, onQueryChange, onSelectItem, renderItem, items, onKeyDown, disabled, defaultValue, onClearValue, allowCustomValue = true, ...inputProps }, ref)=>{
const [defaultInputValue, setDefaultInputValue] = useState(defaultValue);
const [autocompleteKey, setAutocompleteKey] = useState(true);
const sanitizedInitialItems = useMemo(()=>sanitizeItems(items), [
items
]);
const [value, setValue] = useState(defaultValue || "");
const [popoverItems, setPopoverItems] = useState(sanitizedInitialItems);
const comboboxRef = useRef(null);
const collection = useMemo(()=>createListCollection({
items: popoverItems.length ? popoverItems : sanitizeItems([
{
value: ignoreItemValue
}
])
}), [
popoverItems
]);
const handleInputChange = (details)=>{
if ("input-change" !== details.reason) return;
onQueryChange ? onQueryChange(details.inputValue) : setPopoverItems(sanitizedInitialItems.filter((item)=>item.label.toLowerCase().includes(details.inputValue.toLowerCase())));
};
useEffect(()=>{
setPopoverItems(sanitizeItems(items));
}, [
items
]);
const inputRef = useRef(null);
const combobox = useCombobox({
...id ? {
id
} : {},
allowCustomValue: allowCustomValue,
autoFocus: focusOnInit,
value: [
value
],
collection: collection,
onInputValueChange: handleInputChange,
openOnClick: 0 === minQueryLength && collection.items.length > 0,
onValueChange: (details)=>{
details.items[0] ? setValue(details.items[0].value) : setValue("");
},
onInteractOutside: ()=>{
setTimeout(()=>inputRef.current?.blur(), 30);
},
positioning: {
gutter: -3,
flip: false
}
});
useEffect(()=>{
comboboxRef.current = combobox;
}, [
combobox
]);
useImperativeHandle(ref, ()=>({
query: combobox.inputValue,
input: inputRef.current,
open: ()=>{
combobox.setOpen(true);
},
focus: ()=>{
combobox.focus();
},
clearValue: ()=>{
combobox.clearValue();
setValue("");
setDefaultInputValue("");
onClearValue?.();
onQueryChange?.("");
},
setSelectedItem: (selectedItem)=>{
const sanitizedItem = sanitizeItem(selectedItem);
const existingItem = popoverItems.find((item)=>item.value === sanitizedItem.value);
if (existingItem) {
setValue(sanitizedItem.value);
combobox.setOpen(false);
} else {
combobox.setOpen(false);
setPopoverItems([]);
setDefaultInputValue(sanitizedItem.value);
setValue(sanitizedItem.value);
setAutocompleteKey(!autocompleteKey);
}
}
}), [
combobox,
autocompleteKey,
onClearValue,
onQueryChange,
popoverItems
]);
useOnMountEffect(()=>{
focusOnInit && combobox.setOpen(collection.items.length > 0);
});
const validItems = useMemo(()=>collection.items.filter((item)=>item.value !== ignoreItemValue), [
collection.items
]);
useEffect(()=>{
if (validItems.length) {
if (!comboboxRef.current?.open && comboboxRef.current?.focused) comboboxRef.current.setOpen(true);
} else if (comboboxRef.current?.open) comboboxRef.current.setOpen(false);
}, [
validItems
]);
const handleItemSelection = (selectedItem, e)=>{
if (onSelectItem) {
const processSelection = onSelectItem(selectedItem, combobox.inputValue);
if (!processSelection) {
e.preventDefault();
e.stopPropagation();
return;
}
}
};
const autocomplete = /*#__PURE__*/ jsxs(Combobox.RootProvider, {
value: combobox,
className: classnames("cobalt-Autocomplete", className, {
"cobalt-Autocomplete--empty": 0 === validItems.length
}),
children: [
label && /*#__PURE__*/ jsx(Combobox.Label, {
className: "cobalt-FormField__Label",
children: label
}),
/*#__PURE__*/ jsxs(Combobox.Control, {
className: classnames("cobalt-TextField", {
"cobalt-TextField--error": "error" === status,
"cobalt-TextField--success": "success" === status,
"cobalt-TextField--withIcon": icon,
"cobalt-TextField--withValue": inputRef.current && inputRef.current.value.length > 0
}),
children: [
/*#__PURE__*/ jsx(Combobox.Input, {
className: "cobalt-TextField__Input",
ref: inputRef,
disabled: disabled,
defaultValue: defaultInputValue,
...inputProps,
onKeyDown: (e)=>{
onKeyDown?.(e);
if (e.keyCode === KEY_CODE_ENTER && combobox.open) if (combobox.highlightedItem) handleItemSelection(combobox.highlightedItem, e);
else e.preventDefault();
}
}),
icon && /*#__PURE__*/ jsx(Icon, {
source: icon,
color: "primary",
className: "cobalt-TextField__Icon"
}),
value.length > 0 && !disabled && /*#__PURE__*/ jsx(Combobox.ClearTrigger, {
className: "cobalt-Autocomplete__clear-button",
"data-testid": "clear",
onClick: ()=>{
setDefaultInputValue("");
setValue("");
setAutocompleteKey(!autocompleteKey);
0 === minQueryLength && setTimeout(()=>{
inputRef.current?.click();
}, 0);
onClearValue?.();
onQueryChange?.("");
},
children: /*#__PURE__*/ jsx(Icon, {
source: "close",
size: 16
})
})
]
}),
/*#__PURE__*/ jsx(Portal, {
children: /*#__PURE__*/ jsx(Combobox.Positioner, {
className: "cobalt-Autocomplete__positioner",
children: /*#__PURE__*/ jsx(Combobox.Content, {
className: classnames("cobalt-Autocomplete__content", popoverClassName, {
"cobalt-Autocomplete__content--empty": 0 === validItems.length
}),
children: validItems.map((item, index)=>/*#__PURE__*/ jsx(Combobox.Item, {
item: item,
className: "cobalt-Autocomplete__item",
children: renderItem ? /*#__PURE__*/ jsx("div", {
...getA11yOnClick((e)=>handleItemSelection(item, e)),
children: renderItem(item, combobox.inputValue)
}) : /*#__PURE__*/ jsxs("div", {
className: classnames("cobalt-Autocomplete__item-wrapper", {
"cobalt-Autocomplete__item-wrapper--disabled": item.disabled
}),
...getA11yOnClick((e)=>handleItemSelection(item, e)),
children: [
icon && /*#__PURE__*/ jsx("span", {
className: "cobalt-Autocomplete__item-icon",
children: /*#__PURE__*/ jsx(Icon, {
source: icon,
color: "primary"
})
}),
/*#__PURE__*/ jsx(Combobox.ItemText, {
className: "cobalt-Autocomplete__item-label",
children: item.label
}),
/*#__PURE__*/ jsx(Combobox.ItemIndicator, {
className: "cobalt-Autocomplete_selected-item-indicator",
children: /*#__PURE__*/ jsx(CheckIcon, {
size: 16
})
})
]
})
}, `${item.label}-${item.value}-${index}`))
})
})
})
]
}, `${autocompleteKey}`);
return label || hint ? /*#__PURE__*/ jsxs("div", {
className: classnames("cobalt-FormField", {
"cobalt-FormField--withHint": hint,
"cobalt-FormField--fullWidth": fullWidth
}),
children: [
autocomplete,
hint && /*#__PURE__*/ jsx(Hint, {
status: status,
children: /*#__PURE__*/ jsx("span", {
dangerouslySetInnerHTML: {
__html: hint
}
})
})
]
}) : autocomplete;
});
_Autocomplete.displayName = "Autocomplete";
const Autocomplete = _Autocomplete;
export { Autocomplete, AutocompleteMatchHighlight };
//# sourceMappingURL=index.js.map