diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..8698e4b --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,156 @@ +/* -------------------------------------------------------------------------- */ +/* Imports */ +/* -------------------------------------------------------------------------- */ +import React from "react"; +import classNames from "classnames"; +import "../index.css"; +import { Combobox, Transition } from "@headlessui/react"; +import { Fragment, useEffect, useState } from "react"; +import { Scrollbar } from "react-scrollbars-custom"; + +/* -------------------------------------------------------------------------- */ +/* Component props */ +/* -------------------------------------------------------------------------- */ +type Hint = { + id: string; + caption: string; +}; + +type Props = { + onChange: (query: string) => void; + onSelected?: (value: Hint) => void; + hints: Hint[]; + disabled?: boolean; + inGroup?: boolean; + className?: string; + maxScrollSize?: number; + elementScrollSize?: number; +}; +/* -------------------------------------------------------------------------- */ +/* styles */ +/* -------------------------------------------------------------------------- */ +const inputStyle = ` + w-full + cursor-default + rounded + overflow-hidden + border + border-solid + shadow-none + border-gray-100 + focus:outline-none + focus:border-gray-200 + hover:border-gray-200 + py-2 pl-3 + text-sm + text-gray-900 +`; + +const inputList = ` + absolute z-10 mt-1 w-full max-h-56 + bg-white shadow-lg + rounded py-1 + overflow-hidden + focus:outline-none + text-base + sm:text-sm`; + +const inputInGroup = [ + `border-none + hover:none + active:none + focus:none + `, +]; + +/* -------------------------------------------------------------------------- */ +/* Component implementation */ +/* -------------------------------------------------------------------------- */ + +export default function TextInputBox({ + onChange, + onSelected, + hints, + disabled, + inGroup, + className, + maxScrollSize = 140, + elementScrollSize = 36, + ...props +}: Props) { + const [selected, setSelected] = useState<any>(hints); + const [query, setQuery] = useState(""); + + useEffect(() => { + onChange(query); + }, [query, onChange]); + + const handleSelected = (value: Hint) => { + setSelected(value); + onSelected && onSelected(value); + }; + return ( + <div className={classNames("w-60", className)}> + <Combobox value={selected} {...props} onChange={handleSelected}> + <div className="relative"> + <div className="relative w-full bg-white text-left focus:outline-none sm:text-sm"> + <Combobox.Input + className={classNames([ + [`${inputStyle}`], + { [`${inputInGroup}`]: inGroup }, + className, + ])} + onChange={(event) => setQuery(event.target.value)} + placeholder={"Search..."} + displayValue={(hint: Hint | undefined) => hint?.caption ?? ""} + /> + </div> + <div> + {query.length > 0 && ( + <div className={`${inputList}`}> + <Transition + as={Fragment} + leave="transition ease-in duration-100" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <Combobox.Options> + {hints.length === 0 && query !== "" ? ( + <p className="">Nothing found.</p> + ) : null} + {/* <Scrollbar + style={{ + height: hints.length * elementScrollSize, + maxHeight: maxScrollSize, + }} + > */} + {hints.map((item: any, id: number) => ( + <Combobox.Option + key={id} + className={({ active, selected }) => + classNames( + active + ? "text-gray-900 bg-blue-50" + : "font-normal ", + "cursor-default select-none relative py-2 pl-3 pr-9", + selected + ? "text-gray-900 bg-blue-100" + : "font-normal " + ) + } + value={item} + > + <div>{item.caption}</div> + </Combobox.Option> + ))} + {/* </Scrollbar> */} + </Combobox.Options> + </Transition> + </div> + )} + </div> + </div> + </Combobox> + </div> + ); +}