diff --git a/packages/shared/src/components/fields/Autocomplete.tsx b/packages/shared/src/components/fields/Autocomplete.tsx new file mode 100644 index 00000000000..12d74beb2bc --- /dev/null +++ b/packages/shared/src/components/fields/Autocomplete.tsx @@ -0,0 +1,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 { + 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(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 ( + + + { + inputRef.current = ref; + }} + inputId={name} + {...restProps} + onChange={(e) => { + handleChange(e.target.value); + setIsOpen(true); + }} + onFocus={() => setIsOpen(true)} + onBlur={handleBlur} + value={input} + autoComplete="off" + /> + + e.preventDefault()} // keep focus in input + onCloseAutoFocus={(e) => e.preventDefault()} // avoid refocus jumps + > + {!isLoading ? ( +
+ {options?.length > 0 ? ( + options.map((opt) => ( + + )) + ) : ( + No results + )} +
+ ) : ( + + )} +
+
+ ); +}; + +export default Autocomplete; diff --git a/packages/shared/src/components/fields/ControlledTextField.tsx b/packages/shared/src/components/fields/ControlledTextField.tsx new file mode 100644 index 00000000000..00ff70a4758 --- /dev/null +++ b/packages/shared/src/components/fields/ControlledTextField.tsx @@ -0,0 +1,34 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import React from 'react'; +import type { TextFieldProps } from './TextField'; +import { TextField } from './TextField'; + +type ControlledTextFieldProps = Pick< + TextFieldProps, + 'name' | 'label' | 'leftIcon' | 'placeholder' | 'hint' +>; + +const ControlledTextField = ({ + name, + hint, + ...restProps +}: ControlledTextFieldProps) => { + const { control } = useFormContext(); + + return ( + ( + + )} + /> + ); +}; +export default ControlledTextField; diff --git a/packages/shared/src/components/fields/ControlledTextarea.tsx b/packages/shared/src/components/fields/ControlledTextarea.tsx new file mode 100644 index 00000000000..b9120036b2c --- /dev/null +++ b/packages/shared/src/components/fields/ControlledTextarea.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import Textarea from './Textarea'; +import type { BaseFieldProps } from './BaseFieldContainer'; + +const ControlledTextarea = ({ + name, + ...restProps +}: Pick, 'name' | 'label'>) => { + const { control } = useFormContext(); + + return ( + ( +