reorganize/document, add detectedType prop for LookupInput

This commit is contained in:
2024-05-09 20:19:32 -05:00
parent 69c6fd6504
commit ec4036a45b
+78 -43
View File
@@ -11,20 +11,39 @@ import {
} from "@heroicons/react/20/solid"; } from "@heroicons/react/20/solid";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
import { Maybe } from "true-myth";
/**
* Props for the LookupInput component.
*/
type LookupInputProps = { type LookupInputProps = {
isLoading?: boolean; isLoading?: boolean;
// When a type of registry is detected when a user changes their input, this is called. /**
onRegistry?: (type: TargetType) => Promise<any>; * Callback function called when a type of registry is detected when a user changes their input.
// When a user hits submit, this is called. * @param type - The detected type of registry.
onSubmit?: (props: SubmitProps) => Promise<any>; * @returns A promise.
onChange?: (target: { target: string; targetType: ObjectType | null }) => any; */
onRegistry?: (type: TargetType) => Promise<void>;
/**
* Callback function called when a user hits submit.
* @param props - The submit props.
* @returns A promise.
*/
onSubmit?: (props: SubmitProps) => Promise<void>;
/**
* Callback function called when a user changes their input (text search) or explicitly changes the type of search.
* @param target - The target object containing the search target and target type.
* @returns Nothing.
*/
onChange?: (target: { target: string; targetType: ObjectType | null }) => void;
detectedType: Maybe<ObjectType>;
}; };
const LookupInput: FunctionComponent<LookupInputProps> = ({ const LookupInput: FunctionComponent<LookupInputProps> = ({
isLoading, isLoading,
onSubmit, onSubmit,
onChange, onChange,
detectedType,
}: LookupInputProps) => { }: LookupInputProps) => {
const { register, handleSubmit, getValues } = useForm<SubmitProps>({ const { register, handleSubmit, getValues } = useForm<SubmitProps>({
defaultValues: { defaultValues: {
@@ -46,44 +65,39 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
json: "JSON", json: "JSON",
}; };
/**
* Represents the selected value in the LookupInput component.
*/
const [selected, setSelected] = useState<ObjectType | "auto">("auto"); const [selected, setSelected] = useState<ObjectType | "auto">("auto");
function getAutoTargetText(): string { /**
return "Autodetect"; * Retrieves the target type based on the provided value.
} * @param value - The value to retrieve the target type for.
* @returns The target type as ObjectType or null.
/*
* Returns the type of object that the user has selected. Handles the extra "auto" option like null.
* @param value The value of the select element.
*/ */
function getTargetType(value?: string | null): ObjectType | null { function retrieveTargetType(value?: string | null): ObjectType | null {
// Pull the value from the select element if it's not provided (useful for eventing values). // If the value is null and the selected value is null, return null.
if (value == null) value = selected; if (value == null) value = selected;
if (value == "auto" || value == null) return null; // 'auto' means 'do whatever' so we return null.
else return value as ObjectType; if (value == "auto" ) return null;
return value as ObjectType;
} }
return ( const searchIcon = (
<form <>
className="pb-3"
onSubmit={
onSubmit != undefined
? onPromise(handleSubmit(onSubmit))
: preventDefault
}
>
<div className="col">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative flex">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"> <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<MagnifyingGlassIcon <MagnifyingGlassIcon
className="h-5 w-5 text-zinc-400" className="h-5 w-5 text-zinc-400"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
</>
);
const searchInput = (
<input <input
className={clsx( className={clsx(
"lg:py-4.5 custom-select block w-full rounded-l-md border border-transparent", "lg:py-4.5 custom-select block w-full rounded-l-md border border-transparent",
@@ -99,11 +113,15 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
if (onChange != undefined) if (onChange != undefined)
onChange({ onChange({
target: getValues("target"), target: getValues("target"),
targetType: getTargetType(), // dropdown target will be pulled from state anyways, so no need to provide it here
targetType: retrieveTargetType(null),
}); });
}, },
})} })}
></input> />
);
const dropdown = (
<Listbox <Listbox
value={selected} value={selected}
onChange={(value) => { onChange={(value) => {
@@ -112,22 +130,23 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
if (onChange != undefined) if (onChange != undefined)
onChange({ onChange({
target: getValues("target"), target: getValues("target"),
targetType: getTargetType(value), // we provide the value as the state will not have updated yet for this context
targetType: retrieveTargetType(value),
}); });
}} }}
disabled={isLoading} disabled={isLoading}
> >
<div className="relative md:min-w-[10rem]"> <div className="relative">
<Listbox.Button <Listbox.Button
className={clsx( className={clsx(
"relative h-full w-full cursor-default rounded-r-lg bg-zinc-700 py-2 pl-3 pr-10", "relative h-full w-full cursor-default rounded-r-lg bg-zinc-700 py-2 pl-3 pr-10 text-right",
"text-left focus:outline-none focus-visible:border-indigo-500 sm:text-sm text-xs md:text-base lg:text-lg", "text-left text-xs focus:outline-none focus-visible:border-indigo-500 sm:text-sm md:text-base lg:text-lg",
"focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 " "focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 "
)} )}
> >
{/* Fetch special text for 'auto' mode, otherwise just use the options. */} {/* Fetch special text for 'auto' mode, otherwise just use the options. */}
<span className="block"> <span className="block">
{selected == "auto" ? getAutoTargetText() : options[selected]} {selected == "auto" ? detectedType.unwrapOr("???") : options[selected]}
</span> </span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon <ChevronUpDownIcon
@@ -144,7 +163,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
> >
<Listbox.Options <Listbox.Options
className={clsx( className={clsx(
"scrollbar-thin absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-700 py-1", "scrollbar-thin absolute right-0 mt-1 max-h-60 min-w-full overflow-auto rounded-md bg-zinc-700 py-1",
"text-zinc-200 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" "text-zinc-200 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
)} )}
> >
@@ -163,7 +182,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
<> <>
<span <span
className={clsx( className={clsx(
"block text-xs md:text-sm lg:text-base", "block whitespace-nowrap text-right text-xs md:text-sm lg:text-base",
selected ? "font-medium" : null selected ? "font-medium" : null
)} )}
> >
@@ -171,10 +190,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
</span> </span>
{selected ? ( {selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-blue-500"> <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-blue-500">
<CheckIcon <CheckIcon className="h-5 w-5" aria-hidden="true" />
className="h-5 w-5"
aria-hidden="true"
/>
</span> </span>
) : null} ) : null}
</> </>
@@ -185,6 +201,25 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
</Transition> </Transition>
</div> </div>
</Listbox> </Listbox>
);
return (
<form
className="pb-3"
onSubmit={
onSubmit != undefined
? onPromise(handleSubmit(onSubmit))
: preventDefault
}
>
<div className="col">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative flex">
{searchIcon}
{searchInput}
{dropdown}
</div> </div>
</div> </div>
<div className="col"> <div className="col">