-
Notifications
You must be signed in to change notification settings - Fork 293
Expand file tree
/
Copy pathAutocomplete.tsx
More file actions
108 lines (104 loc) · 3.19 KB
/
Autocomplete.tsx
File metadata and controls
108 lines (104 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { Popover, PopoverAnchor } from '@radix-ui/react-popover';
import React, { useRef, useState } from 'react';
import classNames from 'classnames';
import type { TextFieldProps } from './TextField';
import { TextField } from './TextField';
import { PopoverContent } from '../popover/Popover';
import { Typography } from '../typography/Typography';
import { GenericLoaderSpinner } from '../utilities/loaders';
import { IconSize } from '../Icon';
interface AutocompleteProps
extends Omit<TextFieldProps, 'inputId' | 'onChange' | 'onSelect'> {
name: string;
onChange: (value: string) => void;
onSelect: (value: string) => void;
selectedValue?: string;
options: Array<{ value: string; label: string }>;
isLoading?: boolean;
}
const Autocomplete = ({
name,
isLoading,
options,
onChange,
onSelect,
selectedValue,
defaultValue,
...restProps
}: AutocompleteProps) => {
const [input, setInput] = useState(defaultValue || '');
const [isOpen, setIsOpen] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
const handleChange = (val: string) => {
setInput(val);
onChange(val);
};
const handleSelect = (opt: { value: string; label: string }) => {
setInput(opt.label);
onSelect(opt.value);
setIsOpen(false);
inputRef.current?.focus();
};
const handleBlur = () => {
setIsOpen(false);
setInput(options.find((opt) => opt.value === selectedValue)?.label || '');
};
return (
<Popover open={isOpen}>
<PopoverAnchor asChild>
<TextField
inputRef={(ref) => {
inputRef.current = ref;
}}
inputId={name}
{...restProps}
onChange={(e) => {
handleChange(e.target.value);
setIsOpen(true);
}}
onFocus={() => setIsOpen(true)}
onBlur={handleBlur}
value={input}
autoComplete="off"
/>
</PopoverAnchor>
<PopoverContent
className="rounded-16 border border-border-subtlest-tertiary bg-background-popover p-4 data-[side=bottom]:mt-1 data-[side=top]:mb-1"
side="bottom"
align="start"
avoidCollisions
sameWidthAsAnchor
onOpenAutoFocus={(e) => e.preventDefault()} // keep focus in input
onCloseAutoFocus={(e) => e.preventDefault()} // avoid refocus jumps
>
{!isLoading ? (
<div className="flex w-full flex-col">
{options?.length > 0 ? (
options.map((opt) => (
<button
type="button"
className={classNames(
'text-left',
selectedValue === opt.value && 'font-bold',
)}
key={opt.value}
onMouseDown={(e) => {
e.preventDefault();
handleSelect(opt);
}}
>
{opt.label}
</button>
))
) : (
<Typography>No results</Typography>
)}
</div>
) : (
<GenericLoaderSpinner className="mx-auto" size={IconSize.Small} />
)}
</PopoverContent>
</Popover>
);
};
export default Autocomplete;