feat: add more page

This commit is contained in:
ReaJason
2025-08-13 23:53:40 +08:00
parent 8c06216e96
commit c925954035
60 changed files with 2403 additions and 284 deletions
+2 -2
View File
@@ -1,5 +1,3 @@
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { VariantProps } from "class-variance-authority";
import { Check, Copy } from "lucide-react";
import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react";
@@ -8,6 +6,8 @@ import { useTranslation } from "react-i18next";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { toast } from "sonner";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface CopyButtonProps extends React.ComponentProps<"button"> {
value: string;
+2 -2
View File
@@ -1,10 +1,10 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Check, Copy } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
interface CopyableFieldProps {
label: string;
+18 -5
View File
@@ -1,3 +1,5 @@
import { NavLink } from "react-router";
import { Outlet } from "react-router-dom";
import { LanguageSwitcher } from "@/components/language-switcher";
import { ModeToggle } from "@/components/mode-toggle.tsx";
import { ThemeProvider } from "@/components/theme-provider.tsx";
@@ -5,7 +7,8 @@ import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";
import VersionBadge from "@/components/version-badge";
import { GitHubIcon } from "@/icon";
import { Outlet } from "react-router-dom";
import { siteConfig } from "@/lib/config";
import { MobileNav } from "../modile-nav";
export default function RootLayout() {
return (
@@ -13,11 +16,21 @@ export default function RootLayout() {
<Toaster />
<div className="flex flex-col h-screen">
<header className="sticky top-0 z-50 border-b bg-background px-2 sm:px-4 py-2 sm:py-3">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 sm:gap-0">
<div className="flex items-center">
<h2 className="text-base sm:text-lg font-semibold text-center">MemShellParty - JavaWeb</h2>
<div className="flex items-center gap-2">
<MobileNav className="flex lg:hidden" items={siteConfig.navItems} />
<div className="hidden lg:flex">
<NavLink to="/" className="text-base sm:text-lg font-semibold text-center">
MemShellParty
</NavLink>
</div>
<div className="flex items-center gap-0.5 sm:gap-1">
<nav className="items-center gap-0.5 hidden lg:flex">
{siteConfig.navItems.map((item) => (
<Button key={item.href} variant="ghost" size="sm" asChild>
<NavLink to={item.href}>{item.label}</NavLink>
</Button>
))}
</nav>
<div className="ml-auto flex items-center gap-2 md:flex-1 md:justify-end">
<VersionBadge />
<LanguageSwitcher />
<Button
@@ -0,0 +1,40 @@
import { MotionProps, motion } from "motion/react";
import { cn } from "@/lib/utils";
interface LineShadowTextProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>, MotionProps {
shadowColor?: string;
as?: React.ElementType;
}
export function LineShadowText({
children,
shadowColor = "black",
className,
as: Component = "span",
...props
}: LineShadowTextProps) {
const MotionComponent = motion.create(Component);
const content = typeof children === "string" ? children : null;
if (!content) {
throw new Error("LineShadowText only accepts string content");
}
return (
<MotionComponent
style={{ "--shadow-color": shadowColor } as React.CSSProperties}
className={cn(
"relative z-0 inline-flex",
"after:absolute after:left-[0.04em] after:top-[0.04em] after:content-[attr(data-text)]",
"after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]",
"after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent",
"after:animate-line-shadow",
className,
)}
data-text={content}
{...props}
>
{content}
</MotionComponent>
);
}
@@ -0,0 +1,59 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, VariantProps } from "class-variance-authority";
import React from "react";
import { cn } from "@/lib/utils";
const rainbowButtonVariants = cva(
cn(
"relative cursor-pointer group transition-all animate-rainbow",
"inline-flex items-center justify-center gap-2 shrink-0",
"rounded-sm outline-none focus-visible:ring-[3px] aria-invalid:border-destructive",
"text-sm font-medium whitespace-nowrap",
"disabled:pointer-events-none disabled:opacity-50",
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
),
{
variants: {
variant: {
default:
"border-0 bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-primary-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.125rem)_solid_transparent] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
outline:
"border border-input border-b-transparent bg-[linear-gradient(#ffffff,#ffffff),linear-gradient(#ffffff_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-accent-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#0a0a0a,#0a0a0a),linear-gradient(#0a0a0a_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-xl px-3 text-xs",
lg: "h-11 rounded-xl px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
interface RainbowButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof rainbowButtonVariants> {
asChild?: boolean;
}
const RainbowButton = React.forwardRef<HTMLButtonElement, RainbowButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(rainbowButtonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
RainbowButton.displayName = "RainbowButton";
export { RainbowButton, rainbowButtonVariants, type RainbowButtonProps };
+138 -129
View File
@@ -1,3 +1,19 @@
import {
ArrowUpRightIcon,
AxeIcon,
CommandIcon,
NetworkIcon,
ServerIcon,
ShieldOffIcon,
SwordIcon,
WaypointsIcon,
ZapIcon,
} from "lucide-react";
import { JSX, useCallback, useEffect, useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { JreTip } from "@/components/tips/jre-tip.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import {
FormControl,
FormDescription,
@@ -13,23 +29,6 @@ import { Switch } from "@/components/ui/switch.tsx";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { FormSchema } from "@/types/schema.ts";
import { JDKVersion, MainConfig, ServerConfig, ShellToolType } from "@/types/shell.ts";
import { JreTip } from "@/components/tips/jre-tip.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import {
ArrowUpRightIcon,
AxeIcon,
CommandIcon,
NetworkIcon,
ServerIcon,
ShieldOffIcon,
SwordIcon,
WaypointsIcon,
ZapIcon,
} from "lucide-react";
import { JSX, useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "./tools/antsword-tab";
import { BehinderTabContent } from "./tools/behinder-tab";
import { CommandTabContent } from "./tools/command-tab";
@@ -73,6 +72,51 @@ export function MainConfigCard({
const shellTool = form.watch("shellTool");
const { t } = useTranslation();
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
form.setValue("shellType", shellTypes[0]);
}
}, [shellTypes, form]);
const handleServerChange = useCallback(
(value: string) => {
if (mainConfig) {
const newShellToolMap = mainConfig[value];
setShellToolMap(newShellToolMap);
const newShellTools = Object.keys(newShellToolMap);
setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]);
const currentShellTool = form.getValues("shellTool");
const firstTool = newShellTools[0];
let currentShellTypes = null;
if (!newShellToolMap[currentShellTool]) {
form.setValue("shellTool", firstTool);
currentShellTypes = newShellToolMap[firstTool];
} else {
currentShellTypes = newShellToolMap[currentShellTool];
}
setShellTypes(currentShellTypes);
// 特殊环境的 JDK 版本
if (
(value === "SpringWebFlux" || value === "XXLJOB") &&
Number.parseInt(form.getValues("targetJdkVersion") as string) < 52
) {
form.setValue("targetJdkVersion", "52");
} else {
form.resetField("targetJdkVersion");
}
form.resetField("bypassJavaModule");
form.resetField("urlPattern");
}
},
[form, mainConfig],
);
// 处理一下默认值 server 不刷新 shellType 的问题
useEffect(() => {
if (mainConfig) {
@@ -81,120 +125,85 @@ export function MainConfigCard({
handleServerChange(initialServer);
}
}
}, [mainConfig, form]);
}, [mainConfig, form, handleServerChange]);
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
form.setValue("shellType", shellTypes[0]);
}
}, [shellTypes, form]);
const handleShellToolChange = useCallback(
(value: string) => {
const resetCommand = () => {
form.resetField("commandParamName");
form.resetField("implementationClass");
form.resetField("encryptor");
};
const handleServerChange = (value: string) => {
if (mainConfig) {
const newShellToolMap = mainConfig[value];
setShellToolMap(newShellToolMap);
const resetGodzilla = () => {
form.resetField("godzillaKey");
form.resetField("godzillaPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const newShellTools = Object.keys(newShellToolMap);
setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]);
const resetBehinder = () => {
form.resetField("behinderPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const currentShellTool = form.getValues("shellTool");
const resetSuo5 = () => {
form.resetField("headerName");
form.resetField("headerValue");
};
const firstTool = newShellTools[0];
let currentShellTypes = null;
const resetAntSword = () => {
form.resetField("antSwordPass");
form.resetField("headerName");
form.resetField("headerValue");
};
if (!newShellToolMap[currentShellTool]) {
form.setValue("shellTool", firstTool);
currentShellTypes = newShellToolMap[firstTool];
} else {
currentShellTypes = newShellToolMap[currentShellTool];
const resetNeoreGeorg = () => {
form.setValue("headerName", "Referer");
form.resetField("headerValue");
};
const resetCustom = () => {
form.resetField("shellClassBase64");
};
if (shellToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
currentShellTypes = servers?.[form.getValues("server")] as string[];
} else {
currentShellTypes = shellToolMap[value];
}
setShellTypes(currentShellTypes);
form.resetField("urlPattern");
form.resetField("shellClassName");
form.resetField("injectorClassName");
if (value === ShellToolType.Godzilla) {
resetGodzilla();
} else if (value === ShellToolType.Behinder) {
resetBehinder();
} else if (value === ShellToolType.Command) {
resetCommand();
} else if (value === ShellToolType.Suo5) {
resetSuo5();
} else if (value === ShellToolType.AntSword) {
resetAntSword();
} else if (value === ShellToolType.NeoreGeorg) {
resetNeoreGeorg();
} else if (value === ShellToolType.Custom) {
resetCustom();
}
}
setShellTypes(currentShellTypes);
form.setValue("shellTool", value);
},
[form, servers, shellToolMap],
);
// 特殊环境的 JDK 版本
if (
(value === "SpringWebFlux" || value === "XXLJOB") &&
Number.parseInt(form.getValues("targetJdkVersion") as string) < 52
) {
form.setValue("targetJdkVersion", "52");
} else {
form.resetField("targetJdkVersion");
}
form.resetField("bypassJavaModule");
form.resetField("urlPattern");
}
};
const handleShellToolChange = (value: string) => {
const resetCommand = () => {
form.resetField("commandParamName");
form.resetField("implementationClass");
form.resetField("encryptor");
};
const resetGodzilla = () => {
form.resetField("godzillaKey");
form.resetField("godzillaPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetBehinder = () => {
form.resetField("behinderPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetSuo5 = () => {
form.resetField("headerName");
form.resetField("headerValue");
};
const resetAntSword = () => {
form.resetField("antSwordPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetNeoreGeorg = () => {
form.setValue("headerName", "Referer");
form.resetField("headerValue");
};
const resetCustom = () => {
form.resetField("shellClassBase64");
};
if (shellToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
currentShellTypes = servers?.[form.getValues("server")] as string[];
} else {
currentShellTypes = shellToolMap[value];
}
setShellTypes(currentShellTypes);
form.resetField("urlPattern");
form.resetField("shellClassName");
form.resetField("injectorClassName");
if (value === ShellToolType.Godzilla) {
resetGodzilla();
} else if (value === ShellToolType.Behinder) {
resetBehinder();
} else if (value === ShellToolType.Command) {
resetCommand();
} else if (value === ShellToolType.Suo5) {
resetSuo5();
} else if (value === ShellToolType.AntSword) {
resetAntSword();
} else if (value === ShellToolType.NeoreGeorg) {
resetNeoreGeorg();
} else if (value === ShellToolType.Custom) {
resetCustom();
}
}
form.setValue("shellTool", value);
};
const debugId = useId();
const bypassId = useId();
const shrinkId = useId();
return (
<FormProvider {...form}>
@@ -291,9 +300,9 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
<Switch id={debugId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormLabel htmlFor="debug">{t("mainConfig.debug")}</FormLabel>
<FormLabel htmlFor={debugId}>{t("mainConfig.debug")}</FormLabel>
</FormItem>
)}
/>
@@ -303,9 +312,9 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} />
<Switch id={bypassId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<Label htmlFor="bypassJavaModule">{t("mainConfig.bypassJavaModule")}</Label>
<Label htmlFor={bypassId}>{t("mainConfig.bypassJavaModule")}</Label>
</FormItem>
)}
/>
@@ -315,9 +324,9 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="shrink" checked={field.value} onCheckedChange={field.onChange} />
<Switch id={shrinkId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<Label htmlFor="shrink">{t("mainConfig.shrink")}</Label>
<Label htmlFor={shrinkId}>{t("mainConfig.shrink")}</Label>
</FormItem>
)}
/>
+97
View File
@@ -0,0 +1,97 @@
import React from "react";
import { NavLink } from "react-router";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
export function MobileNav({
items,
className,
}: Readonly<{
items: { href: string; label: string }[];
className?: string;
}>) {
const [open, setOpen] = React.useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
className={cn(
"extend-touch-target h-8 touch-manipulation items-center justify-start gap-2.5 !p-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 active:bg-transparent dark:hover:bg-transparent",
className,
)}
>
<div className="relative flex h-8 w-4 items-center justify-center">
<div className="relative size-4">
<span
className={cn(
"bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100",
open ? "top-[0.4rem] -rotate-45" : "top-1",
)}
/>
<span
className={cn(
"bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100",
open ? "top-[0.4rem] rotate-45" : "top-2.5",
)}
/>
</div>
<span className="sr-only">Toggle Menu</span>
</div>
<span className="flex h-8 items-center text-lg leading-none font-medium">Menu</span>
</Button>
</PopoverTrigger>
<PopoverContent
className="bg-background/90 no-scrollbar h-(--radix-popper-available-height) w-(--radix-popper-available-width) overflow-y-auto rounded-none border-none p-0 shadow-none backdrop-blur duration-100"
align="start"
side="bottom"
alignOffset={-16}
sideOffset={14}
>
<div className="flex flex-col gap-12 overflow-auto px-6 py-6">
<div className="flex flex-col gap-4">
<div className="text-muted-foreground text-sm font-medium">Menu</div>
<div className="flex flex-col gap-3">
<MobileLink href="/" onOpenChange={setOpen}>
Home
</MobileLink>
{items.map((item) => (
<MobileLink key={item.href} href={item.href} onOpenChange={setOpen}>
{item.label}
</MobileLink>
))}
</div>
</div>
</div>
</PopoverContent>
</Popover>
);
}
function MobileLink({
href,
className,
children,
onOpenChange,
...props
}: Readonly<{
href: string;
onOpenChange?: (open: boolean) => void;
children: React.ReactNode;
className?: string;
}>) {
return (
<NavLink
to={href}
onClick={() => {
onOpenChange?.(false);
}}
className={cn("text-2xl font-medium", className)}
{...props}
>
{children}
</NavLink>
);
}
+4 -4
View File
@@ -1,12 +1,12 @@
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import { FormSchema } from "@/types/schema.ts";
import { PackerConfig } from "@/types/shell.ts";
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
type Option = {
name: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
export function QuickUsage() {
const { t } = useTranslation();
+2 -2
View File
@@ -1,7 +1,7 @@
import { downloadBytes, formatBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell";
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { downloadBytes, formatBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell";
import { Button } from "../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Separator } from "../ui/separator";
+2 -2
View File
@@ -1,3 +1,5 @@
import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { shouldHidden } from "@/lib/utils";
import {
AntSwordShellToolConfig,
@@ -9,8 +11,6 @@ import {
ShellToolType,
Suo5ShellToolConfig,
} from "@/types/shell";
import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { CopyableField } from "../copyable-field";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Separator } from "../ui/separator";
+1 -1
View File
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { downloadBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button";
export function JarResult({
@@ -1,5 +1,5 @@
import { GenerateResult } from "@/types/shell";
import { TFunction } from "i18next";
import { GenerateResult } from "@/types/shell";
import { CodeViewer } from "../code-viewer";
import { AgentResult } from "./agent";
import { JarResult } from "./jar-result";
+3 -3
View File
@@ -1,12 +1,12 @@
import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { CodeViewer } from "@/components/code-viewer.tsx";
import { QuickUsage } from "@/components/quick-usage.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
import { downloadBytes } from "@/lib/utils.ts";
import { GenerateResult } from "@/types/shell.ts";
import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { BasicInfo } from "./results/basic-info";
import { ResultComponent } from "./results/result-component";
+1 -1
View File
@@ -1,6 +1,6 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
import { InfoIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
export function JreTip() {
const { t } = useTranslation();
+5 -3
View File
@@ -1,8 +1,8 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
import { TabsContent } from "../ui/tabs";
import { OptionalClassFormField } from "./classname-field";
@@ -42,7 +42,9 @@ export function AntSwordTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormControl>
<Input {...field} placeholder={t("placeholders.input")} />
</FormControl>
</FormFieldItem>
)}
/>
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
+10 -8
View File
@@ -1,15 +1,17 @@
import { ChevronDown, ChevronUp, Settings } from "lucide-react";
import { Fragment, useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx";
import { FormSchema } from "@/types/schema.ts";
import { ChevronDown, ChevronUp, Settings } from "lucide-react";
import { Fragment, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button";
export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) {
const { t } = useTranslation();
const [showAdvanced, setShowAdvanced] = useState(false);
const shellClassNameId = useId();
const injectClassNameId = useId();
return (
<Fragment>
<div className="pt-2">
@@ -32,10 +34,10 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="shellClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
<FormFieldLabel htmlFor={shellClassNameId}>
{t("mainConfig.shellClassName")} {t("optional")}
</FormFieldLabel>
<Input id="shellClassName" {...field} placeholder={t("placeholders.input")} />
<Input id={shellClassNameId} {...field} placeholder={t("placeholders.input")} />
</FormFieldItem>
)}
/>
@@ -44,10 +46,10 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="injectorClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
<FormFieldLabel htmlFor={injectClassNameId}>
{t("mainConfig.injectorClassName")} {t("optional")}
</FormFieldLabel>
<Input id="injectorClassName" {...field} placeholder={t("placeholders.input")} />
<Input id={injectClassNameId} {...field} placeholder={t("placeholders.input")} />
</FormFieldItem>
)}
/>
+2 -2
View File
@@ -1,8 +1,8 @@
import { env } from "@/config";
import { FormSchema } from "@/types/schema";
import { useQuery } from "@tanstack/react-query";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { env } from "@/config";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
+9 -8
View File
@@ -1,9 +1,9 @@
import { t } from "i18next";
import { useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { Card, CardContent } from "@/components/ui/card";
import { TabsContent } from "@/components/ui/tabs";
import { FormSchema } from "@/types/schema";
import { t } from "i18next";
import { useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { FormControl, FormField, FormFieldItem, FormFieldLabel, FormMessage } from "../ui/form";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
@@ -18,7 +18,8 @@ export default function CustomTabContent({
shellTypes,
}: Readonly<{ form: UseFormReturn<FormSchema>; shellTypes: Array<string> }>) {
const [isFile, setIsFile] = useState(false);
const optionOneId = useId();
const optionTwoId = useId();
return (
<FormProvider {...form}>
<TabsContent value="Custom">
@@ -43,12 +44,12 @@ export default function CustomTabContent({
className="flex items-center space-x-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="base64" id="option-one" />
<Label htmlFor="option-one">Base64</Label>
<RadioGroupItem value="base64" id={optionOneId} />
<Label htmlFor={optionOneId}>Base64</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="file" id="option-two" />
<Label htmlFor="option-two">File</Label>
<RadioGroupItem value="file" id={optionTwoId} />
<Label htmlFor={optionTwoId}>File</Label>
</div>
</RadioGroup>
<FormControl className="mt-2">
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
+2 -3
View File
@@ -1,10 +1,9 @@
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
import { FormSchema } from "@/types/schema.ts";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
export function ShellTypeFormField({
form,
shellTypes,
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input";
@@ -1,9 +1,9 @@
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormField, FormFieldItem, FormFieldLabel, FormMessage } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx";
import { shouldHidden } from "@/lib/utils";
import { FormSchema } from "@/types/schema.ts";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) {
const { t } = useTranslation();
+2 -3
View File
@@ -1,8 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
+36
View File
@@ -0,0 +1,36 @@
import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };
+92
View File
@@ -0,0 +1,92 @@
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className,
)}
{...props}
/>
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />;
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean;
}) {
const Comp = asChild ? SlotPrimitive.Slot : "a";
return (
<Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} />
);
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
/>
);
}
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
+3 -3
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -42,7 +42,7 @@ function Button({
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";
const Comp = asChild ? SlotPrimitive.Slot : "button";
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
}
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import { Checkbox as CheckboxPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
+209
View File
@@ -0,0 +1,209 @@
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { ContextMenu as ContextMenuPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function ContextMenu({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
}
function ContextMenuTrigger({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />;
}
function ContextMenuGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />;
}
function ContextMenuPortal({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />;
}
function ContextMenuSub({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
}
function ContextMenuRadioGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />;
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
);
}
function ContextMenuSubContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
);
}
function ContextMenuContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
);
}
function ContextMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
);
}
function ContextMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
);
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
{...props}
/>
);
}
function ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function ContextMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};
+121
View File
@@ -0,0 +1,121 @@
import { XIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
);
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
}
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
+7 -9
View File
@@ -1,18 +1,16 @@
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
useFormState,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
const Form = FormProvider;
@@ -115,11 +113,11 @@ function FormFieldLabel({ className, ...props }: React.ComponentProps<typeof Lab
);
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
function FormControl({ ...props }: React.ComponentProps<typeof SlotPrimitive.Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
<SlotPrimitive.Slot
data-slot="form-control"
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
+1 -1
View File
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Label as LabelPrimitive } from "radix-ui";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/utils";
+142
View File
@@ -0,0 +1,142 @@
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean;
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
{...props}
/>
);
}
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn("relative", className)} {...props} />
);
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
);
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className,
)}
{...props}
/>
);
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
className,
)}
{...props}
/>
</div>
);
}
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};
+40
View File
@@ -0,0 +1,40 @@
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "@/lib/utils";
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
}
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { CircleIcon } from "lucide-react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
+46
View File
@@ -0,0 +1,46 @@
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar };
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Select as SelectPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Separator as SeparatorPrimitive } from "radix-ui";
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Switch as SwitchPrimitive } from "radix-ui";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
+73
View File
@@ -0,0 +1,73 @@
import * as React from "react";
import { cn } from "@/lib/utils";
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
);
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
{...props}
/>
);
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
return (
<caption data-slot="table-caption" className={cn("text-muted-foreground mt-4 text-sm", className)} {...props} />
);
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
+1 -1
View File
@@ -1,5 +1,5 @@
import { Tabs as TabsPrimitive } from "radix-ui";
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Tooltip as TooltipPrimitive } from "radix-ui";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,8 +1,8 @@
import { env } from "@/config";
import { useQuery } from "@tanstack/react-query";
import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react";
import type React from "react";
import { useTranslation } from "react-i18next";
import { env } from "@/config";
import { Button } from "./ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";