refactor: migrate radix-ui to base-ui

This commit is contained in:
ReaJason
2025-12-16 21:55:21 +08:00
parent 0f473311f9
commit 930edbc8b0
55 changed files with 2117 additions and 3269 deletions
+379 -399
View File
@@ -1,17 +1,14 @@
import { InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -21,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
@@ -29,7 +27,6 @@ import {
} from "@/components/ui/tooltip";
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { Separator } from "../ui/separator";
const PROBE_OPTIONS = [
{ value: "Server" as const, label: "server" },
@@ -109,338 +106,6 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
resetFormValues();
}, [resetFormValues]);
const ContentOptionsSelect = useMemo(
() => (
<FormField
control={form.control}
name="probeContent"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:probeContent")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("common:placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{filteredOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(`probeshell:probeContent.${opt.label}`)}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, filteredOptions, t],
);
const RequestParamField = useMemo(
() => (
<div className="space-y-2 pt-4 border-t mt-4">
<FormField
control={form.control}
name="reqParamName"
render={({ field }) => (
<FormFieldItem>
<div className="flex items-center gap-1">
<FormFieldLabel>
{t("common:paramName")} {t("common:optional")}
</FormFieldLabel>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<FormControl>
<Input placeholder={t("placeholders.input")} {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const CommandTemplateField = useMemo(
() => (
<div className="space-y-2">
<FormField
control={form.control}
name="commandTemplate"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
</FormControl>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const SleepFields = useMemo(
() => (
<div className="space-y-2 pt-4 border-t mt-4">
<FormField
control={form.control}
name="sleepServer"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:sleepServer")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{MIDDLEWARE_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="seconds"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:sleepSeconds")}</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder={t("placeholders.input")}
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const DNSLogSection = useMemo(
() => (
<FormField
control={form.control}
name="host"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:dnslog.host")}</FormFieldLabel>
<FormControl>
<Input placeholder={t("placeholders.input")} {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, t],
);
const MiddlewareSelect = useMemo(
() => (
<FormField
control={form.control}
name="server"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("server")}</FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.keys(servers ?? {})
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB")
.map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, servers, t],
);
const SwitchGroup = useMemo(
() => (
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<FormField
control={form.control}
name="debug"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex items-center gap-1">
<FormLabel htmlFor="debug">{t("debug")}</FormLabel>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex items-center gap-1">
<Label htmlFor="bypass">{t("byPassJavaModule")}</Label>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex items-center gap-1">
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="shrink"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex items-center gap-1">
<Label htmlFor="shrink">{t("shrink")}</Label>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="staticInitialize"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex items-center gap-1">
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
</div>
),
[form.control, t],
);
const isBodyMethod = watchedProbeMethod === "ResponseBody";
const isCommandBody = watchedProbeContent === "Command";
const needParam =
@@ -451,69 +116,384 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
const isServerContent = watchedProbeContent === "Server";
return (
<FormProvider {...form}>
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5 w-5" />
<span>{t("mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<FormField
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5 w-5" />
<span>{t("mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Controller
control={form.control}
name="probeMethod"
render={({ field, fieldState }) => (
<Field className="gap-1">
<FieldLabel>{t("probeshell:probeMethod")}</FieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<div>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</div>
<SelectContent>
{PROBE_METHOD_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
{watchedProbeMethod === "ResponseBody" && (
<Controller
control={form.control}
name="probeMethod"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:probeMethod")}</FormFieldLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue />
name="server"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent>
<FieldLabel htmlFor="server">{t("server")}</FieldLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger
id="server"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{PROBE_METHOD_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
<SelectContent>
{Object.keys(servers ?? {})
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB")
.map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
{watchedProbeMethod === "ResponseBody" && MiddlewareSelect}
{watchedProbeMethod === "DNSLog" && DNSLogSection}
{watchedProbeMethod && ContentOptionsSelect}
{SwitchGroup}
{isBodyMethod && needParam && RequestParamField}
{isBodyMethod && isCommandBody && CommandTemplateField}
{isSleepMethod && isServerContent && SleepFields}
<Separator />
<FormField
)}
{watchedProbeMethod === "DNSLog" && (
<Controller
control={form.control}
name="shellClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")}
</FormFieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</FormFieldItem>
name="host"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:dnslog.host")}</FieldLabel>
<Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</CardContent>
</Card>
</FormProvider>
)}
{watchedProbeMethod && (
<Controller
control={form.control}
name="probeContent"
render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="probeContent">
{t("probeshell:probeContent")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="probeContent"
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{filteredOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(`probeshell:probeContent.${opt.label}`)}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
)}
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<Controller
control={form.control}
name="debug"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="debug">{t("common:debug")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="shrink"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="staticInitialize"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
</div>
{isBodyMethod && needParam && (
<div className="space-y-2 pt-4 border-t mt-4">
<Controller
control={form.control}
name="reqParamName"
render={({ field, fieldState }) => (
<Field className="gap-1">
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
</FieldLabel>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
)}
{isBodyMethod && isCommandBody && (
<Controller
control={form.control}
name="commandTemplate"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</Field>
)}
/>
)}
{isSleepMethod && isServerContent && (
<div className="space-y-2 pt-4 border-t mt-4">
<Controller
control={form.control}
name="sleepServer"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent>
<FieldLabel htmlFor="sleepServer">
{t("probeshell:sleepServer")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="sleepServer"
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{MIDDLEWARE_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
<Controller
control={form.control}
name="seconds"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:sleepSeconds")}</FieldLabel>
<Input
type="number"
placeholder={t("placeholders.input")}
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
)}
<Separator />
<Controller
control={form.control}
name="shellClassName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")}
</FieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</Field>
)}
/>
</CardContent>
</Card>
);
}
@@ -1,14 +1,9 @@
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
@@ -64,40 +59,35 @@ export default function PackageConfigCard({
</CardHeader>
<CardContent>
{options.length > 0 ? (
<FormProvider {...form}>
<FormField
control={form.control}
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>{t("packerMethod")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={value} id={value} />
</FormControl>
<FormLabel className="text-xs" htmlFor={value}>
{name}
</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</FormProvider>
<Controller
control={form.control}
name="packingMethod"
render={({ field }) => (
<div className="space-y-3">
<FieldLabel>{t("packerMethod")}</FieldLabel>
<div>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<div key={value} className="flex items-center space-x-3">
<div>
<RadioGroupItem value={value} id={value} />
</div>
<FieldLabel className="text-xs" htmlFor={value}>
{name}
</FieldLabel>
</div>
))}
</RadioGroup>
</div>
</div>
)}
/>
) : (
<div className="flex items-center justify-center p-4 space-x-2">
<div className="flex items-center justify-center p-4">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm text-muted-foreground">
{t("loading")}