import React, { ReactElement, useEffect, useState } from 'react'; import { debounce } from 'debounce'; import styled from 'styled-components/macro'; import tw from 'twin.macro'; import Input from '@/components/elements/Input'; import Label from '@/components/elements/Label'; import InputSpinner from '@/components/elements/InputSpinner'; const Dropdown = styled.div<{ expanded: boolean }>` ${tw`absolute mt-1 w-full rounded-md bg-neutral-900 shadow-lg z-10`}; ${props => !props.expanded && tw`hidden`}; `; interface SearchableSelectProps { id: string; name: string; nullable: boolean; selected: T | null; items: T[]; setItems: (items: T[]) => void; onSearch: (query: string) => Promise; onSelect: (item: T) => void; getSelectedText: (item: T | null) => string; children: React.ReactNode; } function SearchableSelect ({ id, name, selected, items, setItems, onSearch, onSelect, getSelectedText, children }: SearchableSelectProps) { const [ loading, setLoading ] = useState(false); const [ expanded, setExpanded ] = useState(false); const [ inputText, setInputText ] = useState(''); const onFocus = () => { setInputText(''); setItems([]); setExpanded(true); }; const search = debounce((query: string) => { if (!expanded) { return; } if (query === '' || query.length < 2) { setItems([]); return; } setLoading(true); onSearch(query).then(() => setLoading(false)); }, 250); useEffect(() => { setInputText(getSelectedText(selected) || ''); setExpanded(false); }, [ selected ]); useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key !== 'Escape') { return; } setInputText(getSelectedText(selected) || ''); setExpanded(false); }; window.addEventListener('keydown', handler); return () => { window.removeEventListener('keydown', handler); }; }, [ expanded ]); const onClick = (item: T) => (e: React.MouseEvent) => { e.preventDefault(); onSelect(item); }; // This shit is really stupid but works, so is it really stupid? const c = React.Children.map(children, child => React.cloneElement(child as ReactElement, { onClick: onClick.bind(child), })); return (
{ setInputText(e.currentTarget.value); search(e.currentTarget.value); }} />
{ items.length < 1 ? inputText.length < 2 ?

Please type 2 or more characters.

:

No results found.

:
    {c}
}
); } interface OptionProps { id: string | number; item: T; active: boolean; onClick?: (item: T) => (e: React.MouseEvent) => void; children: React.ReactNode; } export function Option ({ id, item, active, onClick, children }: OptionProps) { if (onClick === undefined) { // eslint-disable-next-line @typescript-eslint/no-empty-function onClick = () => () => {}; } if (active) { return (
  • {children}
  • ); } return (
  • {children}
  • ); } export default SearchableSelect;