'use client'; import { getClassNames } from '@websolutespa/bom-core'; import { ComponentPropsWithRef, forwardRef, ReactNode, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { UIComponentWithRef, UIStyledComponentProps } from '../../components/types'; import { getCssResponsive } from '../../components/utils'; import { FormControlCss, FormFocusCss } from '../styles'; import { highligthText } from '../utils/utils'; import { AutocompleteContext, IAutocomplete, IAutocompleteItem } from './autocomplete-context'; import { AutocompleteDropdown } from './autocomplete-dropdown'; type Props = ComponentPropsWithRef<'input'> & { initialValue?: IAutocompleteItem; before?: ReactNode; after?: ReactNode; source: (query: string) => Promise; onAutocomplete?: (value: IAutocompleteItem) => void; onDropdownVisibleChange?: (visible: boolean) => void; }; export type AutocompleteProps = UIStyledComponentProps; export type AutocompleteInputProps = UIStyledComponentProps; export type AutocompleteComponent = UIComponentWithRef; const StyledAutocomplete = styled.div` ${FormControlCss} display: flex; align-items: center; width: 100%; cursor: pointer; &>input { flex-grow: 1; appearance: none; min-width: 0; &::placeholder { color: var(--form-color-placeholder); opacity: 0.3; } } &>:first-child:not(input) { margin: 0 0 0 0.5em; } &>:last-child:not(input):not(.button) { margin: 0 0.5em 0 0; } &>.button { line-height: inherit; font-size: inherit; padding-top: var(--form-control-padding); padding-bottom: var(--form-control-padding); margin: -2px; } &>:last-child.button { border-radius: 0 var(--border-radius) var(--border-radius) 0; } &>:first-child.button { border-radius: var(--border-radius) 0 0 var(--border-radius); } &>:last-child.button { border-radius: 0 var(--border-radius) var(--border-radius) 0; } ${props => getCssResponsive(props)} `; const StyledAutocompleteInput = styled.div` display: block; appearance: none; padding: var(--form-control-padding); font-size: var(--form-control-font-size); font-weight: var(--form-control-font-weight); line-height: var(--form-control-line-height); color: inherit; background-color: transparent; border: none; outline: none; &:not(:first-child) { padding-left: calc(var(--form-control-padding) * 0.5); } ${props => getCssResponsive(props)} `; const StyledAutocompleteItem = styled.div` display: block; width: 100%; padding: var(--form-control-padding); font-size: var(--form-control-font-size); font-weight: var(--form-control-font-weight); line-height: var(--form-control-line-height); color: inherit; background-color: transparent; border-color: var(--color-neutral-200); transition: border 150ms ease-in 0s, outline-color 150ms ease-in 0s, color 200ms ease-out 0s; cursor: pointer; &:not(:last-child) { border-bottom: 1px solid var(--color-neutral-200); } ${FormFocusCss} `; export const Autocomplete: AutocompleteComponent = forwardRef(({ className, initialValue, before, after, source, onFocus, onBlur, onDropdownVisibleChange = () => { }, onAutocomplete, id, name, ...props }, ref) => { const innerRef = useRef(null); const dropdownRef = useRef(null); useImperativeHandle(ref, () => innerRef.current as HTMLInputElement); const [items, setItems] = useState([]); const [visible, setVisible] = useState(false); const [value, setValue] = useState(initialValue ? initialValue.id : null); const [focus, setFocus] = useState(false); const updateVisible = (next: boolean) => { onDropdownVisibleChange(next); setVisible(next); }; const setInnerValue = (value = '') => { if (innerRef.current) { innerRef.current.value = value; } }; const onFocus_ = (event: React.FocusEvent) => { onFocus && onFocus(event); setFocus(true); setInnerValue(''); console.log('Autocomplete.onFocus_'); }; const onBlur_ = (event: React.FocusEvent) => { onBlur && onBlur(event); setFocus(false); updateVisible(false); setItems([]); setInnerValue(value ? value.name : ''); console.log('Autocomplete.onBlur_'); }; const onChange_ = async (event: React.ChangeEvent) => { const query = event.target.value.toLowerCase(); setInnerValue(query); const items = await source(query); setItems(items); console.log('Autocomplete.onChange_'); }; const onAutocomplete_ = (item: IAutocompleteItem) => { setValue(item); if (item) { setInnerValue(item.name); } onAutocomplete && onAutocomplete(item); console.log('Autocomplete.onAutocomplete_'); }; const context: IAutocomplete = useMemo(() => { const updateValue = (item: IAutocompleteItem) => { setInnerValue(item.name); onAutocomplete && onAutocomplete(item); updateVisible(false); }; return { visible, value, ref: innerRef, updateValue, updateVisible, }; }, [visible, value, updateVisible, onAutocomplete]); useEffect(() => { if (initialValue) { setInnerValue(initialValue.name); } }, [initialValue]); const classNames = getClassNames('autocomplete', { disabled: props.disabled, readonly: props.readOnly, hidden: props.hidden, }, className); return ( <> {before} {after} {items && ( 0}> {items.map((item, i) => ( onAutocomplete_(item)} dangerouslySetInnerHTML={{ __html: highligthText(item.name, innerRef.current?.value) }}> ))} )} ); }); Autocomplete.displayName = 'Autocomplete';