mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-05 23:15:58 -06:00
feat: enhance LookupInput with visual badges and flicker-free detection
- Add Badge component to display autodetection status with color coding - Show green "Auto" badge when type is detected, red when unknown - Prevent badge flickering during empty-to-non-empty input transitions - Increase lock icon size from 14x14 to 16x16 pixels with blue color - Track input state to manage smooth badge transitions
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import type { FunctionComponent } from "react";
|
import type { FunctionComponent } from "react";
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { onPromise, preventDefault } from "@/lib/utils";
|
import { onPromise, preventDefault } from "@/lib/utils";
|
||||||
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/rdap/schemas";
|
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/rdap/schemas";
|
||||||
import { TargetTypeEnum } from "@/rdap/schemas";
|
import { TargetTypeEnum } from "@/rdap/schemas";
|
||||||
import { MagnifyingGlassIcon, ReloadIcon, LockClosedIcon } from "@radix-ui/react-icons";
|
import { MagnifyingGlassIcon, ReloadIcon, LockClosedIcon } from "@radix-ui/react-icons";
|
||||||
import { TextField, Select, Flex, Checkbox, Text, IconButton } from "@radix-ui/themes";
|
import { TextField, Select, Flex, Checkbox, Text, IconButton, Badge } from "@radix-ui/themes";
|
||||||
import type { Maybe } from "true-myth";
|
import type { Maybe } from "true-myth";
|
||||||
import { placeholders } from "@/rdap/constants";
|
import { placeholders } from "@/rdap/constants";
|
||||||
|
|
||||||
@@ -85,6 +85,17 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
*/
|
*/
|
||||||
const [selected, setSelected] = useState<SimplifiedTargetType | "auto">("auto");
|
const [selected, setSelected] = useState<SimplifiedTargetType | "auto">("auto");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the current input value to determine if the field is empty.
|
||||||
|
*/
|
||||||
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks whether we're waiting for type detection after transitioning from empty to non-empty input.
|
||||||
|
* Prevents badge from flickering during initial input.
|
||||||
|
*/
|
||||||
|
const [showingPlaceholder, setShowingPlaceholder] = useState<boolean>(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the target type based on the provided value.
|
* Retrieves the target type based on the provided value.
|
||||||
* @param value - The value to retrieve the target type for.
|
* @param value - The value to retrieve the target type for.
|
||||||
@@ -102,6 +113,16 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
return result.success ? result.data : null;
|
return result.success ? result.data : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the placeholder flag when type detection completes.
|
||||||
|
* This prevents badge flickering when transitioning from empty to non-empty input.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (showingPlaceholder) {
|
||||||
|
setShowingPlaceholder(false);
|
||||||
|
}
|
||||||
|
}, [detectedType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="pb-2.5"
|
className="pb-2.5"
|
||||||
@@ -120,9 +141,22 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
{...register("target", {
|
{...register("target", {
|
||||||
required: true,
|
required: true,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
|
const targetValue = getValues("target");
|
||||||
|
const oldIsEmpty = inputValue.trim() === "";
|
||||||
|
const newIsEmpty = targetValue.trim() === "";
|
||||||
|
|
||||||
|
// Transitioning from empty to non-empty - show placeholder until detection completes
|
||||||
|
if (oldIsEmpty && !newIsEmpty) {
|
||||||
|
setShowingPlaceholder(true);
|
||||||
|
} else if (newIsEmpty) {
|
||||||
|
// Input is now empty - clear placeholder
|
||||||
|
setShowingPlaceholder(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInputValue(targetValue);
|
||||||
if (onChange != undefined)
|
if (onChange != undefined)
|
||||||
void onChange({
|
void onChange({
|
||||||
target: getValues("target"),
|
target: targetValue,
|
||||||
targetType: retrieveTargetType(null),
|
targetType: retrieveTargetType(null),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -177,14 +211,26 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selected == "auto" ? (
|
{selected == "auto" ? (
|
||||||
detectedType.isJust ? (
|
showingPlaceholder || inputValue.trim() === "" ? (
|
||||||
<Text>Auto ({targetShortNames[detectedType.value]})</Text>
|
|
||||||
) : (
|
|
||||||
objectNames["auto"]
|
objectNames["auto"]
|
||||||
|
) : detectedType.isJust ? (
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Badge color="green">Auto</Badge>
|
||||||
|
{targetShortNames[detectedType.value]}
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Badge color="red">Auto</Badge>
|
||||||
|
Unknown
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Flex align="center" gap="2">
|
<Flex align="center" gap="2">
|
||||||
<LockClosedIcon width="14" height="14" />
|
<LockClosedIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
style={{ color: "var(--blue-9)" }}
|
||||||
|
/>
|
||||||
{objectNames[selected]}
|
{objectNames[selected]}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user