'use client'; import * as React from 'react'; import { ForwardedRef, forwardRef, useMemo } from 'react'; import { mergeProps, useButton, useComboBox, useFilter, useFocusRing, useSearchField } from 'react-aria'; import { useComboBoxState, useSearchFieldState } from 'react-stately'; import { useBreakpoint } from '../../hook/breakpoints.hook.js'; import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js'; import { ClearIcon, SearchIcon } from '../icon/index.js'; import { ErrorMessage, Hint, Label, ProgressIndicator } from '../index.js'; import { styles as autocompleteStyles } from './autocomplete.styles.js'; import { type AutocompleteProps } from './autocomplete.types.js'; import { AutocompleteListBox, AutocompletePopover } from './components/index.js'; const STATIC_IS_OPEN_STATE = { isOpen: true, setOpen: () => { return; }, open: () => { return; }, close: () => { return; }, toggle: () => { return; }, }; function Autocomplete( { size = 'medium', invalid = false, isDisabled, footer, portalContainer, errorMessage, hintMessage, noOptionsMessage, className, width = 'full', loadingState, comboBoxState, ...props }: AutocompleteProps, ref: ForwardedRef, ) { // eslint-disable-next-line @typescript-eslint/unbound-method const { contains } = useFilter({ sensitivity: 'base' }); const internalState = useComboBoxState({ isDisabled, ...props, defaultFilter: contains }); const state = comboBoxState ?? internalState; const { isFocusVisible, focusProps } = useFocusRing(); const { isFocusVisible: isInputFocusVisible, focusProps: inputFocusProps } = useFocusRing(); const inputRef = React.useRef(null); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion React.useImperativeHandle(ref, () => inputRef.current!); const listBoxRef = React.useRef(null); const popoverRef = React.useRef(null); const { inputProps, listBoxProps, labelProps, descriptionProps, errorMessageProps } = useComboBox( { ...props, isDisabled, inputRef, listBoxRef, popoverRef, }, state, ); const breakpoint = useBreakpoint(); const { clearButton: clearButtonStyle, ...styles } = autocompleteStyles({ width: resolveResponsiveVariant(width, breakpoint), isDisabled, isInputFocusVisible, size: resolveResponsiveVariant(size, breakpoint), invalid, isFocusVisible, }); // Get props for the clear button from useSearchField const searchProps = { label: props.label, value: state.inputValue, onChange: (v: string) => state.setInputValue(v), }; const searchState = useSearchFieldState(searchProps); const { clearButtonProps } = useSearchField( { ...searchProps, 'aria-label': props['aria-label'], 'aria-labelledby': props['aria-labelledby'] }, searchState, inputRef, ); const clearButtonRef = React.useRef(null); const { buttonProps } = useButton(clearButtonProps, clearButtonRef); const outerRef = React.useRef(null); const isNoOptionPopOverOpen = useMemo(() => { return !!( noOptionsMessage && ((!state.isOpen && state.isFocused && searchProps.value.length > 0 && state.selectedItems.length === 0) || (state.collection.size === 0 && searchProps.value.length > 0)) ); }, [ noOptionsMessage, state.isOpen, state.isFocused, state.selectedItems, state.collection.size, searchProps.value.length, ]); return (
{props.label && } {hintMessage && {hintMessage}} {errorMessage && }
{loadingState ? ( ) : ( )}
{isNoOptionPopOverOpen && (
{noOptionsMessage}
{footer && (
{footer}
)}
)} {state.isOpen && ( {footer && (
{footer}
)}
)}
); } const _Autocomplete = forwardRef(Autocomplete) as unknown as { displayName: string } & (( props: AutocompleteProps & { ref?: ForwardedRef }, ) => ReturnType); _Autocomplete.displayName = 'Autocomplete'; export { _Autocomplete as Autocomplete };