UNPKG

@drivy/cobalt

Version:

Opinionated design system for Drivy's projects.

283 lines (282 loc) 12.3 kB
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