mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-08 02:08:15 -06:00
reorganize/document, add detectedType prop for LookupInput
This commit is contained in:
@@ -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,24 +65,144 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchIcon = (
|
||||||
|
<>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
|
<MagnifyingGlassIcon
|
||||||
|
className="h-5 w-5 text-zinc-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchInput = (
|
||||||
|
<input
|
||||||
|
className={clsx(
|
||||||
|
"lg:py-4.5 custom-select block w-full rounded-l-md border border-transparent",
|
||||||
|
"bg-zinc-700 py-2 pl-10 pr-3 text-sm placeholder-zinc-400 placeholder:translate-y-2 focus:text-zinc-200",
|
||||||
|
" focus:outline-none sm:text-sm md:py-3 md:text-base lg:text-lg"
|
||||||
|
)}
|
||||||
|
disabled={isLoading}
|
||||||
|
placeholder="A domain, an IP address, a TLD, an RDAP URL..."
|
||||||
|
type="search"
|
||||||
|
{...register("target", {
|
||||||
|
required: true,
|
||||||
|
onChange: () => {
|
||||||
|
if (onChange != undefined)
|
||||||
|
onChange({
|
||||||
|
target: getValues("target"),
|
||||||
|
// dropdown target will be pulled from state anyways, so no need to provide it here
|
||||||
|
targetType: retrieveTargetType(null),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const dropdown = (
|
||||||
|
<Listbox
|
||||||
|
value={selected}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSelected(value);
|
||||||
|
|
||||||
|
if (onChange != undefined)
|
||||||
|
onChange({
|
||||||
|
target: getValues("target"),
|
||||||
|
// we provide the value as the state will not have updated yet for this context
|
||||||
|
targetType: retrieveTargetType(value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Listbox.Button
|
||||||
|
className={clsx(
|
||||||
|
"relative h-full w-full cursor-default rounded-r-lg bg-zinc-700 py-2 pl-3 pr-10 text-right",
|
||||||
|
"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 "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Fetch special text for 'auto' mode, otherwise just use the options. */}
|
||||||
|
<span className="block">
|
||||||
|
{selected == "auto" ? detectedType.unwrapOr("???") : options[selected]}
|
||||||
|
</span>
|
||||||
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
className="h-5 w-5 text-zinc-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Listbox.Button>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Listbox.Options
|
||||||
|
className={clsx(
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{Object.entries(options).map(([key, value]) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={key}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx(
|
||||||
|
"relative cursor-default select-none py-2 pl-10 pr-4",
|
||||||
|
active ? "bg-zinc-800 text-zinc-300" : null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={key}
|
||||||
|
>
|
||||||
|
{({ selected }) => (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"block whitespace-nowrap text-right text-xs md:text-sm lg:text-base",
|
||||||
|
selected ? "font-medium" : null
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
{selected ? (
|
||||||
|
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-blue-500">
|
||||||
|
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="pb-3"
|
className="pb-3"
|
||||||
@@ -78,113 +217,9 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
Search
|
Search
|
||||||
</label>
|
</label>
|
||||||
<div className="relative flex">
|
<div className="relative flex">
|
||||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
{searchIcon}
|
||||||
<MagnifyingGlassIcon
|
{searchInput}
|
||||||
className="h-5 w-5 text-zinc-400"
|
{dropdown}
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
className={clsx(
|
|
||||||
"lg:py-4.5 custom-select block w-full rounded-l-md border border-transparent",
|
|
||||||
"bg-zinc-700 py-2 pl-10 pr-3 text-sm placeholder-zinc-400 placeholder:translate-y-2 focus:text-zinc-200",
|
|
||||||
" focus:outline-none sm:text-sm md:py-3 md:text-base lg:text-lg"
|
|
||||||
)}
|
|
||||||
disabled={isLoading}
|
|
||||||
placeholder="A domain, an IP address, a TLD, an RDAP URL..."
|
|
||||||
type="search"
|
|
||||||
{...register("target", {
|
|
||||||
required: true,
|
|
||||||
onChange: () => {
|
|
||||||
if (onChange != undefined)
|
|
||||||
onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
targetType: getTargetType(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
></input>
|
|
||||||
<Listbox
|
|
||||||
value={selected}
|
|
||||||
onChange={(value) => {
|
|
||||||
setSelected(value);
|
|
||||||
|
|
||||||
if (onChange != undefined)
|
|
||||||
onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
targetType: getTargetType(value),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<div className="relative md:min-w-[10rem]">
|
|
||||||
<Listbox.Button
|
|
||||||
className={clsx(
|
|
||||||
"relative h-full w-full cursor-default rounded-r-lg bg-zinc-700 py-2 pl-3 pr-10",
|
|
||||||
"text-left focus:outline-none focus-visible:border-indigo-500 sm:text-sm text-xs 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 "
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* Fetch special text for 'auto' mode, otherwise just use the options. */}
|
|
||||||
<span className="block">
|
|
||||||
{selected == "auto" ? getAutoTargetText() : options[selected]}
|
|
||||||
</span>
|
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
||||||
<ChevronUpDownIcon
|
|
||||||
className="h-5 w-5 text-zinc-200"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Listbox.Button>
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
leave="transition ease-in duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<Listbox.Options
|
|
||||||
className={clsx(
|
|
||||||
"scrollbar-thin absolute mt-1 max-h-60 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"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{Object.entries(options).map(([key, value]) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={key}
|
|
||||||
className={({ active }) =>
|
|
||||||
clsx(
|
|
||||||
"relative cursor-default select-none py-2 pl-10 pr-4",
|
|
||||||
active ? "bg-zinc-800 text-zinc-300" : null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
value={key}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
"block text-xs md:text-sm lg:text-base",
|
|
||||||
selected ? "font-medium" : null
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
{selected ? (
|
|
||||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-blue-500">
|
|
||||||
<CheckIcon
|
|
||||||
className="h-5 w-5"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</Listbox.Options>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</Listbox>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
|
|||||||
Reference in New Issue
Block a user