163 lines
4.9 KiB
TypeScript
163 lines
4.9 KiB
TypeScript
/* -------------------------------------------------------------------------- */
|
|
/* Imports */
|
|
/* -------------------------------------------------------------------------- */
|
|
import React from "react";
|
|
import classNames from "classnames";
|
|
import "../index.css";
|
|
import { Combobox, Transition } from "@headlessui/react";
|
|
import { Fragment, useEffect, useState } from "react";
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Component props */
|
|
/* -------------------------------------------------------------------------- */
|
|
type Hint = {
|
|
id: string;
|
|
caption: string;
|
|
};
|
|
|
|
type Props<T> = {
|
|
onChange: (query: string) => void;
|
|
onSelected: (value: Hint) => void;
|
|
IsEmptyItems: () => React.ReactNode;
|
|
hints: Hint[];
|
|
displayValueResolver?: (element: T) => any;
|
|
disabled?: boolean;
|
|
inGroup?: boolean;
|
|
className?: string;
|
|
maxScrollSize?: number;
|
|
elementScrollSize?: number;
|
|
placeHolder?: string;
|
|
};
|
|
/* -------------------------------------------------------------------------- */
|
|
/* 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 SearchBar<T>({
|
|
onChange,
|
|
onSelected,
|
|
hints,
|
|
displayValueResolver,
|
|
disabled,
|
|
inGroup = false,
|
|
className,
|
|
maxScrollSize = 140,
|
|
elementScrollSize = 36,
|
|
placeHolder = "Search...",
|
|
IsEmptyItems,
|
|
...props
|
|
}: Props<T>) {
|
|
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>
|
|
<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={placeHolder}
|
|
displayValue={(value: T) =>
|
|
displayValueResolver ? displayValueResolver(value) : value
|
|
}
|
|
/>
|
|
</div>
|
|
<div>
|
|
{query.length > 0 && (
|
|
<div className={`${inputList}`}>
|
|
<Transition
|
|
as={Fragment}
|
|
leave="transition ease-in duration-100"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
afterLeave={() => setQuery("")}
|
|
>
|
|
<Combobox.Options>
|
|
{hints.length === 0 && query !== "" ? IsEmptyItems() : 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>
|
|
);
|
|
}
|