style: fmt code

This commit is contained in:
ReaJason
2026-04-26 21:33:15 +08:00
parent d10323a054
commit ee560ff7ee
150 changed files with 948 additions and 1522 deletions
+8 -16
View File
@@ -1,18 +1,14 @@
import type { VariantProps } from "class-variance-authority";
import { Check, Copy } from "lucide-react";
import {
type HTMLProps,
type ReactNode,
useCallback,
useEffect,
useState,
} from "react";
import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react";
import CopyToClipboard from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next";
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import java from "react-syntax-highlighter/dist/esm/languages/prism/java";
import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/material-dark";
import { toast } from "sonner";
import { Button, type buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
@@ -68,17 +64,13 @@ export default function CodeViewer({
showLineNumbers = true,
wrapLongLines = true,
}: Readonly<CodeViewerProps>) {
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined =
wrapLongLines
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
: undefined;
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
: undefined;
return (
<div className="rounded-lg border">
<div
className={cn(
"flex items-center border-b p-2 justify-end",
header && "justify-between",
)}
className={cn("flex items-center justify-end border-b p-2", header && "justify-between")}
>
{header}
<div className="flex items-center gap-2">
@@ -86,7 +78,7 @@ export default function CodeViewer({
<CopyButton value={code} variant="ghost" size="sm" />
</div>
</div>
<div className="relative overflow-hidden text-xs wrap-all">
<div className="wrap-all relative overflow-hidden text-xs">
<SyntaxHighlighter
language={language}
style={materialDark}
+4 -12
View File
@@ -1,13 +1,9 @@
import { Check, Copy } from "lucide-react";
import {
type ComponentPropsWithoutRef,
useCallback,
useEffect,
useState,
} from "react";
import { type ComponentPropsWithoutRef, 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";
import { cn } from "@/lib/utils";
@@ -48,7 +44,7 @@ export function CopyableField({
return (
<div className={cn("flex flex-col gap-1 py-1", className)} {...divProps}>
<div className="flex items-center justify-between gap-2 h-6">
<div className="flex h-6 items-center justify-between gap-2">
<Label className="text-sm text-muted-foreground">{label}</Label>
{value && (
<CopyToClipboard.CopyToClipboard text={value} onCopy={handleCopy}>
@@ -59,11 +55,7 @@ export function CopyableField({
className="h-8 w-8"
disabled={hasCopied}
>
{hasCopied ? (
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
{hasCopied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</CopyToClipboard.CopyToClipboard>
)}
+6 -13
View File
@@ -1,8 +1,10 @@
"use client";
import { Image, type ImageProps } from "fumadocs-core/framework";
import type { ComponentProps } from "react";
import { Image, type ImageProps } from "fumadocs-core/framework";
import Zoom, { type UncontrolledProps } from "react-medium-image-zoom";
import "@/components/image-zoom.css";
export type ImageZoomProps = ImageProps & {
@@ -22,20 +24,14 @@ function getImageSrc(src: ImageProps["src"]): string {
if (typeof src === "object") {
// Next.js
if ("default" in src)
return (src as { default: { src: string } }).default.src;
if ("default" in src) return (src as { default: { src: string } }).default.src;
return src.src;
}
return "";
}
export function ImageZoom({
zoomInProps,
children,
rmiz,
...props
}: ImageZoomProps) {
export function ImageZoom({ zoomInProps, children, rmiz, ...props }: ImageZoomProps) {
return (
<Zoom
zoomMargin={20}
@@ -48,10 +44,7 @@ export function ImageZoom({
}}
>
{children ?? (
<Image
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px"
{...props}
/>
<Image sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px" {...props} />
)}
</Zoom>
);
+1
View File
@@ -1,5 +1,6 @@
import { LanguagesIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "./ui/button";
export function LanguageSwitcher() {
@@ -1,9 +1,9 @@
import { type MotionProps, motion } from "motion/react";
import { cn } from "@/lib/utils";
interface LineShadowTextProps
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>,
MotionProps {
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>, MotionProps {
shadowColor?: string;
as?: React.ElementType;
}
@@ -27,7 +27,7 @@ export function LineShadowText({
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:absolute after:top-[0.04em] after:left-[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",
+2 -1
View File
@@ -1,6 +1,7 @@
import defaultMdxComponents from "fumadocs-ui/mdx";
import type { MDXComponents } from "mdx/types";
import defaultMdxComponents from "fumadocs-ui/mdx";
export function getMDXComponents(components?: MDXComponents) {
return {
...defaultMdxComponents,
@@ -1,11 +1,9 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
import {
Select,
SelectContent,
@@ -13,7 +11,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema";
const JDKVersion = [
{ name: "Java6", value: "50" },
@@ -37,9 +34,7 @@ export function JREVersionFormField({
render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="targetJdkVersion">
{t("common:targetJdkVersion")}
</FieldLabel>
<FieldLabel htmlFor="targetJdkVersion">{t("common:targetJdkVersion")}</FieldLabel>
<Select
onValueChange={(v) => {
if (Number.parseInt(v ?? "0", 10) >= 53) {
@@ -51,13 +46,8 @@ export function JREVersionFormField({
}}
value={field.value}
>
<SelectTrigger
id="targetJdkVersion"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectTrigger id="targetJdkVersion" aria-invalid={fieldState.invalid}>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{JDKVersion.map((v) => (
@@ -1,7 +1,10 @@
import type { MemShellFormSchema } from "@/types/schema";
import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
import { BehinderTabContent } from "@/components/memshell/tabs/behinder-tab";
import { CommandTabContent } from "@/components/memshell/tabs/command-tab";
@@ -10,12 +13,7 @@ import { GodzillaTabContent } from "@/components/memshell/tabs/godzilla-tab";
import { NeoRegTabContent } from "@/components/memshell/tabs/neoreg-tab";
import { Suo5TabContent } from "@/components/memshell/tabs/suo5-tab";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Field, FieldContent, FieldDescription, FieldLabel } from "@/components/ui/field";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -26,17 +24,9 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
type MainConfig,
type ServerConfig,
ShellToolType,
} from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { type MainConfig, type ServerConfig, ShellToolType } from "@/types/memshell";
import { Spinner } from "../ui/spinner";
import { JREVersionFormField } from "./jreversion-field";
import { ServerVersionFormField } from "./serverversion-field";
@@ -76,9 +66,7 @@ export default function MainConfigCard({
if (!serverToolMap) {
return [];
}
const tools = Object.keys(serverToolMap).map(
(tool) => tool as ShellToolType,
);
const tools = Object.keys(serverToolMap).map((tool) => tool as ShellToolType);
return Array.from(new Set([...tools, ShellToolType.Custom]));
}, [serverToolMap]);
@@ -134,9 +122,7 @@ export default function MainConfigCard({
const currentTargetJdk = form.getValues("targetJdkVersion") as string;
const currentJdkVersion = Number.parseInt(currentTargetJdk, 10);
const shouldRaiseJdkVersion =
(server === "SpringWebFlux" ||
server === "XXLJOB" ||
server === "Dubbo") &&
(server === "SpringWebFlux" || server === "XXLJOB" || server === "Dubbo") &&
currentJdkVersion <= 52;
const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50";
if (currentTargetJdk !== nextJdkVersion) {
@@ -247,39 +233,27 @@ export default function MainConfigCard({
</CardHeader>
<CardContent>
{!mainConfig ? (
<div className="flex items-center justify-center p-4 gap-4 h-100">
<div className="flex h-100 items-center justify-center gap-4 p-4">
<Spinner />
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
<span className="text-sm text-muted-foreground">{t("loading")}</span>
</div>
) : (
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="server"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="server">
{t("common:server")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FieldLabel htmlFor="server">{t("common:server")}</FieldLabel>
<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger id="server">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{serverOptions.map((serverOption) => (
<SelectItem
key={serverOption}
value={serverOption}
>
<SelectItem key={serverOption} value={serverOption}>
{serverOption}
</SelectItem>
))}
@@ -303,26 +277,20 @@ export default function MainConfigCard({
/>
<ServerVersionFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="shellTool"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="shellTool">
{t("common:shellTool")}
</FieldLabel>
<FieldLabel htmlFor="shellTool">{t("common:shellTool")}</FieldLabel>
<Select
value={field.value}
onValueChange={(v) =>
handleShellToolChange(v as string)
}
onValueChange={(v) => handleShellToolChange(v as string)}
>
<SelectTrigger id="shellTool">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{shellTools.map((tool) => (
@@ -338,21 +306,17 @@ export default function MainConfigCard({
/>
<JREVersionFormField form={form} />
</div>
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<div className="mt-4 flex flex-col gap-4 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}
/>
<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" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
@@ -366,15 +330,11 @@ export default function MainConfigCard({
name="probe"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="probe"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Switch id="probe" checked={field.value} onCheckedChange={field.onChange} />
<Label htmlFor="probe">{t("common:probe")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:probe.description")}</p>
@@ -388,17 +348,11 @@ export default function MainConfigCard({
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>
<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" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
@@ -417,12 +371,10 @@ export default function MainConfigCard({
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="lambdaSuffix">
{t("common:lambdaSuffix")}
</Label>
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
@@ -436,15 +388,11 @@ export default function MainConfigCard({
name="shrink"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<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" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
@@ -463,12 +411,10 @@ export default function MainConfigCard({
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Label htmlFor="staticInitialize">{t("common:staticInitialize")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
@@ -489,11 +435,7 @@ export default function MainConfigCard({
<BehinderTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} />
<Suo5TabContent
tabValue="Suo5v2"
form={form}
shellTypes={shellTypes}
/>
<Suo5TabContent tabValue="Suo5v2" form={form} shellTypes={shellTypes} />
<NeoRegTabContent form={form} shellTypes={shellTypes} />
<CustomTabContent form={form} shellTypes={shellTypes} />
<ProxyTabContent form={form} shellTypes={shellTypes} />
@@ -1,13 +1,15 @@
import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
import { PackageIcon } from "lucide-react";
import { useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { FieldLabel, FieldSet } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Spinner } from "@/components/ui/spinner";
import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
export default function PackageConfigCard({
packerConfig,
@@ -84,11 +86,9 @@ export default function PackageConfigCard({
)}
/>
) : (
<div className="flex items-center justify-center p-4 gap-4 h-50">
<div className="flex h-50 items-center justify-center gap-4 p-4">
<Spinner />
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
<span className="text-sm text-muted-foreground">{t("loading")}</span>
</div>
)}
</CardContent>
+2 -1
View File
@@ -1,5 +1,6 @@
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function QuickUsage() {
@@ -13,7 +14,7 @@ export function QuickUsage() {
</CardTitle>
</CardHeader>
<CardContent>
<ol className="flex flex-col gap-4 list-decimal list-inside text-sm">
<ol className="flex list-inside list-decimal flex-col gap-4 text-sm">
<li>{t("memshell:quickUsage.step1")}</li>
<li>{t("memshell:quickUsage.step2")}</li>
<li>{t("memshell:quickUsage.step3")}</li>
+7 -10
View File
@@ -1,10 +1,12 @@
import type { MemShellResult } from "@/types/memshell";
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { downloadBytes, formatBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
export function AgentResult({
packMethod,
@@ -26,11 +28,10 @@ export function AgentResult({
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<ol className="list-inside list-decimal space-y-4 text-sm">
<li className="flex items-center justify-between">
<span>
{t("common:download")} MemShellAgent.jar (
{formatBytes(atob(packResult).length)})
{t("common:download")} MemShellAgent.jar ({formatBytes(atob(packResult).length)})
</span>
<Button
size="sm"
@@ -56,9 +57,7 @@ export function AgentResult({
variant="outline"
className="w-28"
type="button"
onClick={() =>
window.open("https://github.com/jattach/jattach/releases")
}
onClick={() => window.open("https://github.com/jattach/jattach/releases")}
>
{t("common:download")}
</Button>
@@ -72,9 +71,7 @@ export function AgentResult({
</li>
<li>{t("memshell:tips.get-pid")}</li>
<li>
{isPureAgent
? t("memshell:tips.execute-command")
: t("memshell:tips.execute-command1")}
{isPureAgent ? t("memshell:tips.execute-command") : t("memshell:tips.execute-command1")}
</li>
<li>{t("memshell:tips.try-to-use-shell")}</li>
</ol>
@@ -1,6 +1,7 @@
import { FileTextIcon } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { useTranslation } from "react-i18next";
import { Fragment } from "react/jsx-runtime";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { notNeedUrlPattern } from "@/lib/utils";
@@ -15,12 +16,11 @@ import {
ShellToolType,
type Suo5ShellToolConfig,
} from "@/types/memshell";
import { CopyableField } from "../../copyable-field";
import { FeedbackAlert } from "./feedback-alert";
export function BasicInfo({
generateResult,
}: Readonly<{ generateResult?: MemShellResult }>) {
export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShellResult }>) {
const { t } = useTranslation(["memshell", "common"]);
const isDubbo = generateResult?.shellConfig.server === "Dubbo";
return (
@@ -35,11 +35,8 @@ export function BasicInfo({
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<CopyableField
label={t("common:server")}
text={generateResult?.shellConfig.server}
/>
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<CopyableField label={t("common:server")} text={generateResult?.shellConfig.server} />
<CopyableField
label={t("mainConfig.shellTool")}
text={generateResult?.shellConfig.shellTool}
@@ -55,29 +52,21 @@ export function BasicInfo({
value={generateResult?.injectorConfig.urlPattern}
/>
</div>
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom &&
!isDubbo && <Separator className="my-1" />}
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && !isDubbo && (
<Separator className="my-1" />
)}
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
{generateResult?.shellConfig.shellTool === ShellToolType.Behinder && (
<>
<CopyableField
label={t("shellToolConfig.behinderScriptType")}
text="jsp"
/>
<CopyableField label={t("shellToolConfig.behinderScriptType")} text="jsp" />
<CopyableField
label={t("shellToolConfig.behinderEncryptType")}
text={t("shellToolConfig.behinderDefaultEncryptType")}
/>
<CopyableField
label={t("shellToolConfig.behinder.pass")}
text={
(generateResult?.shellToolConfig as BehinderShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as BehinderShellToolConfig)
.pass
}
text={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
value={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
/>
<CopyableField
label={t("shellToolConfig.behinder.header")}
@@ -90,25 +79,13 @@ export function BasicInfo({
<>
<CopyableField
label={t("shellToolConfig.godzilla.pass")}
text={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.pass
}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
/>
<CopyableField
label={t("shellToolConfig.godzilla.key")}
text={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.key
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.key
}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
/>
<CopyableField
label={t("shellToolConfig.godzilla.encryptor")}
@@ -125,38 +102,27 @@ export function BasicInfo({
/>
</>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command &&
!isDubbo && (
<Fragment>
<CopyableField
hidden={generateResult?.shellConfig.shellType.includes(
"WebSocket",
)}
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
<CopyableField
hidden={
!(
generateResult?.shellConfig.shellType ===
"BypassNginxWebSocket" ||
generateResult?.shellConfig.shellType ===
"BypassNginxJakartaWebSocket"
)
}
label={t("shellToolConfig.httpHeader")}
text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
/>
</Fragment>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command && !isDubbo && (
<Fragment>
<CopyableField
hidden={generateResult?.shellConfig.shellType.includes("WebSocket")}
label={t("common:paramName")}
text={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
value={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
/>
<CopyableField
hidden={
!(
generateResult?.shellConfig.shellType === "BypassNginxWebSocket" ||
generateResult?.shellConfig.shellType === "BypassNginxJakartaWebSocket"
)
}
label={t("shellToolConfig.httpHeader")}
text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
/>
</Fragment>
)}
{(generateResult?.shellConfig.shellTool === ShellToolType.Suo5 ||
generateResult?.shellConfig.shellTool === ShellToolType.Suo5v2) && (
<CopyableField
@@ -176,14 +142,8 @@ export function BasicInfo({
<>
<CopyableField
label={t("shellToolConfig.antSword.pass")}
text={
(generateResult?.shellToolConfig as AntSwordShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as AntSwordShellToolConfig)
.pass
}
text={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
value={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
/>
<CopyableField
label={t("shellToolConfig.httpHeader")}
@@ -192,14 +152,9 @@ export function BasicInfo({
/>
</>
)}
{generateResult?.shellConfig.shellTool ===
ShellToolType.NeoreGeorg && (
{generateResult?.shellConfig.shellTool === ShellToolType.NeoreGeorg && (
<>
<CopyableField
label={t("shellToolConfig.neoreGeorgKey")}
text="key"
value="key"
/>
<CopyableField label={t("shellToolConfig.neoreGeorgKey")} text="key" value="key" />
<CopyableField
label={t("shellToolConfig.neoreGeorgHeader")}
text={`${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerValue}`}
@@ -209,7 +164,7 @@ export function BasicInfo({
)}
</div>
<Separator className="my-1" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<CopyableField
label={t("mainConfig.injectorClassName")}
value={generateResult?.injectorClassName}
@@ -1,5 +1,6 @@
import { CircleHelpIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import {
AlertDialog,
AlertDialogAction,
@@ -1,10 +1,12 @@
import type { MemShellResult } from "@/types/memshell";
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { downloadBytes, formatBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
export function JarResult({
packMethod,
@@ -25,11 +27,10 @@ export function JarResult({
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<ol className="list-inside list-decimal space-y-4 text-sm">
<li className="flex items-center justify-between">
<span>
{t("common:download")} {packMethod}Shell.jar (
{formatBytes(atob(packResult).length)})
{t("common:download")} {packMethod}Shell.jar ({formatBytes(atob(packResult).length)})
</span>
<Button
size="sm"
@@ -1,6 +1,7 @@
import { DownloadIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer";
import { Button } from "@/components/ui/button";
import {
@@ -26,14 +27,9 @@ export function MultiPackResult({
const showCode = packMethod === "JSP";
const { t } = useTranslation();
const packResults = allPackResults as Record<string, string> | undefined;
const packMethods = useMemo(
() => Object.keys(packResults ?? {}),
[packResults],
);
const packMethods = useMemo(() => Object.keys(packResults ?? {}), [packResults]);
const [selectedMethod, setSelectedMethod] = useState(
() => packMethods[0] ?? "",
);
const [selectedMethod, setSelectedMethod] = useState(() => packMethods[0] ?? "");
const packResult = useMemo(() => {
if (!selectedMethod) {
@@ -55,16 +51,12 @@ export function MultiPackResult({
}, [packMethods, selectedMethod]);
const handleDownload = useCallback(() => {
const fileName =
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
if (packMethod === "JSP") {
const fileExtension = selectedMethod.includes("JSPX") ? ".jspx" : ".jsp";
const content = new Blob([packResult], { type: "text/plain" });
return downloadContent(content, fileName, fileExtension);
} else if (
packMethod === "JavaDeserialize" ||
packMethod.includes("Hessian")
) {
} else if (packMethod === "JavaDeserialize" || packMethod.includes("Hessian")) {
const content = new Blob([base64ToBytes(packResult)], {
type: "application/octet-stream",
});
@@ -73,20 +65,13 @@ export function MultiPackResult({
const base64Content = packResults?.[packMethods[0]] ?? "";
return downloadBytes(base64Content, shellClassName);
}
}, [
packMethod,
packMethods,
packResult,
packResults,
selectedMethod,
shellClassName,
]);
}, [packMethod, packMethods, packResult, packResults, selectedMethod, shellClassName]);
return (
<CodeViewer
code={packResult ?? ""}
header={
<div className="flex items-center justify-between text-xs gap-2">
<div className="flex items-center justify-between gap-2 text-xs">
<Select
onValueChange={(value) => {
setSelectedMethod(value as string);
@@ -94,9 +79,7 @@ export function MultiPackResult({
value={selectedMethod}
>
<SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4">
<span className="text-muted-foreground">
{t("common:packerMethod")}:&nbsp;
</span>
<span className="text-muted-foreground">{t("common:packerMethod")}:&nbsp;</span>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
@@ -1,13 +1,16 @@
import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer";
import type { MemShellResult } from "@/types/memshell";
import { DownloadIcon } from "lucide-react";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer";
import { Button } from "@/components/ui/button";
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
import { AgentResult } from "./agent";
import { JarResult } from "./jar-result";
import { MultiPackResult } from "./multi-packer";
import { useCallback } from "react";
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { DownloadIcon } from "lucide-react";
export function ResultComponent({
packResult,
@@ -55,16 +58,12 @@ export function ResultComponent({
const shellClassName = generateResult?.shellClassName;
const handleDownload = useCallback(() => {
const fileName =
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
if (packMethod.includes("JSP")) {
const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp";
const content = new Blob([packResult as string], { type: "text/plain" });
return downloadContent(content, fileName, fileExtension);
} else if (
packMethod.includes("JavaCommons") ||
packMethod.includes("Hessian")
) {
} else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) {
const content = new Blob([base64ToBytes(packResult as string)], {
type: "application/octet-stream",
});
@@ -78,7 +77,7 @@ export function ResultComponent({
<CodeViewer
code={packResult ?? ""}
header={
<div className="flex items-center justify-between text-xs gap-2">
<div className="flex items-center justify-between gap-2 text-xs">
<span>
{t("common:packerMethod")}{packMethod}
</span>
@@ -1,11 +1,9 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
import {
Select,
SelectContent,
@@ -13,7 +11,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema";
export function ServerVersionFormField({
form,
@@ -30,17 +27,10 @@ export function ServerVersionFormField({
render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="serverVersion">
{t("common:serverVersion")}
</FieldLabel>
<FieldLabel htmlFor="serverVersion">{t("common:serverVersion")}</FieldLabel>
<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger
id="serverVersion"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectTrigger id="serverVersion" aria-invalid={fieldState.invalid}>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{serverVersionOptions.map((v) => (
+8 -19
View File
@@ -1,3 +1,5 @@
import type { MemShellResult } from "@/types/memshell";
import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -6,7 +8,7 @@ import { QuickUsage } from "@/components/memshell/quick-usage";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { downloadBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
import CodeViewer from "../code-viewer";
import { BasicInfo } from "./results/basic-info";
import { ResultComponent } from "./results/result-component";
@@ -30,13 +32,9 @@ export default function ShellResult({
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="packResult">
{t("common:generateResult")}
</TabsTrigger>
<TabsTrigger value="packResult">{t("common:generateResult")}</TabsTrigger>
<TabsTrigger value="shell">{t("memshell:shellClass")}</TabsTrigger>
<TabsTrigger value="injector">
{t("memshell:injectorClass")}
</TabsTrigger>
<TabsTrigger value="injector">{t("memshell:injectorClass")}</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} />
@@ -50,11 +48,7 @@ export default function ShellResult({
<TabsContent value="shell" className="mt-4">
<CodeViewer
showLineNumbers={false}
header={
<div className="text-xs truncate">
{generateResult?.shellClassName}
</div>
}
header={<div className="truncate text-xs">{generateResult?.shellClassName}</div>}
button={
<Button
variant="ghost"
@@ -66,10 +60,7 @@ export default function ShellResult({
toast.warning(t("memshell:tips.shellBytesEmpty"));
return;
}
downloadBytes(
generateResult?.shellBytesBase64Str,
generateResult?.shellClassName,
);
downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName);
}}
>
<DownloadIcon className="h-4 w-4" />
@@ -85,9 +76,7 @@ export default function ShellResult({
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
header={
<div className="text-xs">{generateResult?.injectorClassName}</div>
}
header={<div className="text-xs">{generateResult?.injectorClassName}</div>}
button={
<Button
variant="ghost"
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -19,7 +22,7 @@ export function AntSwordTabContent({
return (
<TabsContent value="AntSword">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller
control={form.control}
@@ -29,24 +32,18 @@ export function AntSwordTabContent({
<FieldLabel>
{t("shellToolConfig.antSword.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -58,10 +55,7 @@ export function AntSwordTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -19,7 +22,7 @@ export function BehinderTabContent({
return (
<TabsContent value="Behinder">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller
control={form.control}
@@ -29,24 +32,18 @@ export function BehinderTabContent({
<FieldLabel>
{t("shellToolConfig.behinder.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -58,10 +55,7 @@ export function BehinderTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -1,11 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Shuffle } from "lucide-react";
import { Fragment, useEffect, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import type { MemShellFormSchema } from "@/types/schema";
export function OptionalClassFormField({
form,
@@ -16,12 +18,8 @@ export function OptionalClassFormField({
const [useRandomClassName, setUseRandomClassName] = useState(
() => !(initialShellClassName?.trim() || initialInjectorClassName?.trim()),
);
const [savedShellClassName, setSavedShellClassName] = useState(
initialShellClassName,
);
const [savedInjectorClassName, setSavedInjectorClassName] = useState(
initialInjectorClassName,
);
const [savedShellClassName, setSavedShellClassName] = useState(initialShellClassName);
const [savedInjectorClassName, setSavedInjectorClassName] = useState(initialInjectorClassName);
const shellClassName = form.watch("shellClassName");
const injectorClassName = form.watch("injectorClassName");
@@ -38,10 +36,7 @@ export function OptionalClassFormField({
}, [injectorClassName, useRandomClassName]);
useEffect(() => {
if (
useRandomClassName &&
(shellClassName?.trim() || injectorClassName?.trim())
) {
if (useRandomClassName && (shellClassName?.trim() || injectorClassName?.trim())) {
setUseRandomClassName(false);
}
}, [injectorClassName, shellClassName, useRandomClassName]);
@@ -61,7 +56,7 @@ export function OptionalClassFormField({
return (
<Fragment>
<div className="pt-2 flex items-center justify-between gap-3">
<div className="flex items-center justify-between gap-3 pt-2">
<div className="flex items-center gap-2 text-sm font-medium">
<Shuffle className="h-4 w-4" />
<span>{t("mainConfig.randomClassName")}</span>
@@ -82,11 +77,7 @@ export function OptionalClassFormField({
<FieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("common:optional")}
</FieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input id="shellClassName" {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -100,11 +91,7 @@ export function OptionalClassFormField({
<FieldLabel htmlFor="injectClassName">
{t("mainConfig.injectorClassName")} {t("common:optional")}
</FieldLabel>
<Input
id="injectClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input id="injectClassName" {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -1,14 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { useQuery } from "@tanstack/react-query";
import { ChevronDown, ChevronRight, InfoIcon } from "lucide-react";
import { useState } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
@@ -19,13 +18,9 @@ import {
SelectValue,
} from "@/components/ui/select";
import { TabsContent } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -60,43 +55,36 @@ export function CommandTabContent({
return (
<TabsContent value="Command">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
{server !== "Dubbo" && (
<Controller
control={form.control}
name="commandParamName"
render={({ field }) => (
<Field
className="gap-1"
hidden={shellType.includes("WebSocket")}
>
<Field className="gap-1" hidden={shellType.includes("WebSocket")}>
<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" />
<InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
)}
<div
className="grid grid-cols-1 md:grid-cols-2 gap-2"
className="grid grid-cols-1 gap-2 md:grid-cols-2"
hidden={
shellType !== "BypassNginxWebSocket" &&
shellType !== "BypassNginxJakartaWebSocket"
shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxJakartaWebSocket"
}
>
<Controller
@@ -105,10 +93,7 @@ export function CommandTabContent({
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -120,16 +105,13 @@ export function CommandTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
</div>
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-2 w-full py-2 text-sm font-medium hover:underline">
<CollapsibleTrigger className="flex w-full items-center gap-2 py-2 text-sm font-medium hover:underline">
{isAdvancedOpen ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -138,22 +120,16 @@ export function CommandTabContent({
{t("common:advancedConfig")}
</CollapsibleTrigger>
<CollapsibleContent className="space-y-2 pt-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="encryptor"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:encryptor")}</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RAW"
>
<Select onValueChange={field.onChange} value={field.value} defaultValue="RAW">
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{data?.encryptors?.map((v) => (
@@ -178,9 +154,7 @@ export function CommandTabContent({
defaultValue="RuntimeExec"
>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{data?.implementationClasses?.map((v) => (
@@ -202,11 +176,8 @@ export function CommandTabContent({
<FieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
<Input {...field} placeholder={t("common:commandTemplate.placeholder")} />
<p className="mt-1 text-xs text-muted-foreground">
{t("common:commandTemplate.description")}
</p>
</Field>
@@ -1,7 +1,10 @@
import type { MemShellFormSchema } from "@/types/schema";
import { useEffect, useRef, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
@@ -10,7 +13,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -85,7 +88,7 @@ export default function CustomTabContent({
return (
<TabsContent value="Custom">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller
control={form.control}
@@ -120,9 +123,7 @@ export default function CustomTabContent({
const reader = new FileReader();
reader.onload = (event) => {
const base64String =
(event.target?.result as string)?.split(
",",
)[1] || "";
(event.target?.result as string)?.split(",")[1] || "";
field.onChange(base64String);
};
reader.readAsDataURL(file);
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -19,9 +22,9 @@ export function GodzillaTabContent({
return (
<TabsContent value="Godzilla">
<Card>
<CardContent className="flex flex-col gap-2 mt-4">
<CardContent className="mt-4 flex flex-col gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="godzillaPass"
@@ -30,10 +33,7 @@ export function GodzillaTabContent({
<FieldLabel>
{t("shellToolConfig.godzilla.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -45,10 +45,7 @@ export function GodzillaTabContent({
<FieldLabel>
{t("shellToolConfig.godzilla.key")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -58,10 +55,7 @@ export function GodzillaTabContent({
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -73,10 +67,7 @@ export function GodzillaTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -19,19 +22,16 @@ export function NeoRegTabContent({
return (
<TabsContent value="NeoreGeorg">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -43,10 +43,7 @@ export function NeoRegTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
+9 -13
View File
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -23,13 +26,12 @@ export function ProxyTabContent({
return (
<TabsContent value="Proxy">
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<div
className="grid grid-cols-1 md:grid-cols-2 gap-2"
className="grid grid-cols-1 gap-2 md:grid-cols-2"
hidden={
shellType !== "BypassNginxWebSocket" &&
shellType !== "BypassNginxJakartaWebSocket"
shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxJakartaWebSocket"
}
>
<Controller
@@ -38,10 +40,7 @@ export function ProxyTabContent({
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -53,10 +52,7 @@ export function ProxyTabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -1,5 +1,8 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
@@ -10,7 +13,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { cn, notNeedUrlPattern } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema";
export function ShellTypeFormField({
form,
@@ -23,7 +25,7 @@ export function ShellTypeFormField({
const shellType = useWatch({ control: form.control, name: "shellType" });
const needUrlPattern = !notNeedUrlPattern(shellType);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="shellType"
@@ -38,9 +40,7 @@ export function ShellTypeFormField({
value={field.value}
>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent key={shellTypes?.join(",")}>
{shellTypes?.length ? (
@@ -50,9 +50,7 @@ export function ShellTypeFormField({
</SelectItem>
))
) : (
<SelectItem value=" ">
{t("tips.shellToolNotSelected")}
</SelectItem>
<SelectItem value=" ">{t("tips.shellToolNotSelected")}</SelectItem>
)}
</SelectContent>
</Select>
+8 -11
View File
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -21,19 +24,16 @@ export function Suo5TabContent({
return (
<TabsContent value={tabValue}>
<Card>
<CardContent className="space-y-2 mt-4">
<CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
@@ -45,10 +45,7 @@ export function Suo5TabContent({
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
<Input {...field} placeholder={t("common:placeholders.input")} />
</Field>
)}
/>
+11 -26
View File
@@ -1,19 +1,17 @@
import type { ProbeShellResult, ResponseBodyConfig } from "@/types/probeshell";
import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { CopyableField } from "@/components/copyable-field";
import { FeedbackAlert } from "@/components/memshell/results/feedback-alert";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { ProbeShellResult, ResponseBodyConfig } from "@/types/probeshell";
export function BasicInfo({
generateResult,
}: Readonly<{ generateResult?: ProbeShellResult }>) {
export function BasicInfo({ generateResult }: Readonly<{ generateResult?: ProbeShellResult }>) {
const { t } = useTranslation();
const isBodyContent =
generateResult?.probeConfig.probeMethod === "ResponseBody";
const isBodyContent = generateResult?.probeConfig.probeMethod === "ResponseBody";
const isFilterContent = generateResult?.probeConfig.probeContent === "Filter";
const isBodyCommand =
isBodyContent && generateResult?.probeConfig.probeContent === "Command";
const isBodyCommand = isBodyContent && generateResult?.probeConfig.probeContent === "Command";
return (
<Card>
<CardHeader>
@@ -30,29 +28,16 @@ export function BasicInfo({
{!isFilterContent && isBodyContent && (
<CopyableField
label={t("common:paramName")}
value={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.reqParamName
}
text={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.reqParamName
}
value={(generateResult?.probeContentConfig as ResponseBodyConfig).reqParamName}
text={(generateResult?.probeContentConfig as ResponseBodyConfig).reqParamName}
/>
)}
{isBodyCommand &&
(generateResult?.probeContentConfig as ResponseBodyConfig)
.commandTemplate && (
(generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate && (
<CopyableField
label={t("common:commandTemplate")}
value={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.commandTemplate
}
text={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.commandTemplate
}
value={(generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate}
text={(generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate}
/>
)}
<CopyableField
@@ -1,14 +1,13 @@
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Select,
@@ -19,18 +18,10 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { SwitchField } from "@/components/ui/switch-field";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// Hoisted static JSX to avoid recreation on each render (rendering-hoist-jsx)
const infoIcon = (
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
);
const infoIcon = <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />;
const PROBE_OPTIONS = [
{ value: "Server" as const, label: "server" },
@@ -85,14 +76,11 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
Sleep: ["Server"],
} as const;
const allowedValues =
filterMap[watchedProbeMethod as keyof typeof filterMap];
const allowedValues = filterMap[watchedProbeMethod as keyof typeof filterMap];
if (!allowedValues) return PROBE_OPTIONS;
return PROBE_OPTIONS.filter((opt) =>
allowedValues.includes(opt.value as never),
);
return PROBE_OPTIONS.filter((opt) => allowedValues.includes(opt.value as never));
}, [watchedProbeMethod]);
const resetFormValues = useCallback(() => {
@@ -116,9 +104,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
const isFilter = watchedProbeContent === "Filter";
const needParam =
!isFilter &&
(isCommandBody ||
watchedProbeContent === "Bytecode" ||
watchedProbeContent === "ScriptEngine");
(isCommandBody || watchedProbeContent === "Bytecode" || watchedProbeContent === "ScriptEngine");
const isSleepMethod = watchedProbeMethod === "Sleep";
const isServerContent = watchedProbeContent === "Server";
@@ -140,9 +126,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<Select onValueChange={field.onChange} defaultValue={field.value}>
<div>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
</div>
<SelectContent>
@@ -162,24 +146,12 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control}
name="server"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<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")}
/>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<SelectTrigger id="server" aria-invalid={fieldState.invalid}>
<SelectValue data-placeholder={t("placeholders.select")} />
</SelectTrigger>
<SelectContent>
{Object.keys(servers ?? {})
@@ -191,9 +163,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</FieldContent>
</Field>
)}
@@ -204,11 +174,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control}
name="host"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<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]} />}
@@ -223,20 +189,10 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
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")}
/>
<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) => (
@@ -246,15 +202,13 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
{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">
<div className="mt-4 flex flex-col gap-4 lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<SwitchField
control={form.control}
name="debug"
@@ -287,7 +241,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
/>
</div>
{isBodyMethod && needParam && (
<div className="space-y-2 pt-4 border-t mt-4">
<div className="mt-4 space-y-2 border-t pt-4">
<Controller
control={form.control}
name="reqParamName"
@@ -305,9 +259,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
</Tooltip>
</div>
<Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
@@ -322,11 +274,8 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<FieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
<Input {...field} placeholder={t("common:commandTemplate.placeholder")} />
<p className="mt-1 text-xs text-muted-foreground">
{t("common:commandTemplate.description")}
</p>
</Field>
@@ -334,31 +283,17 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
/>
)}
{isSleepMethod && isServerContent && (
<div className="space-y-2 pt-4 border-t mt-4">
<div className="mt-4 space-y-2 border-t pt-4">
<Controller
control={form.control}
name="sleepServer"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<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")}
/>
<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 }) => (
@@ -368,9 +303,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</FieldContent>
</Field>
)}
@@ -379,11 +312,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control}
name="seconds"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<Field className="gap-1" orientation="vertical" data-invalid={fieldState.invalid}>
<FieldLabel>{t("probeshell:sleepSeconds")}</FieldLabel>
<Input
type="number"
@@ -391,9 +320,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
@@ -408,11 +335,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<FieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")}
</FieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
<Input id="shellClassName" {...field} placeholder={t("placeholders.input")} />
</Field>
)}
/>
@@ -1,12 +1,14 @@
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
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";
type Option = {
name: string;
@@ -41,10 +43,7 @@ export default function PackageConfigCard({
setOptions(mappedOptions);
const currentValue = form.getValues("packingMethod");
if (
filteredOptions.length > 0 &&
(!currentValue || !filteredOptions.includes(currentValue))
) {
if (filteredOptions.length > 0 && (!currentValue || !filteredOptions.includes(currentValue))) {
form.setValue("packingMethod", filteredOptions[0]);
}
}, [form, packerConfig]);
@@ -89,9 +88,7 @@ export default function PackageConfigCard({
) : (
<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")}
</span>
<span className="text-sm text-muted-foreground">{t("loading")}</span>
</div>
)}
</CardContent>
@@ -1,5 +1,6 @@
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function QuickUsage() {
@@ -13,7 +14,7 @@ export function QuickUsage() {
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<ol className="list-inside list-decimal space-y-4 text-sm">
<li>{t("probeshell:quickUsage.step1")}</li>
<li>{t("probeshell:quickUsage.step2")}</li>
<li>{t("probeshell:quickUsage.step3")}</li>
@@ -1,7 +1,10 @@
import type { ProbeShellResult } from "@/types/probeshell";
import { useTranslation } from "react-i18next";
import { QuickUsage } from "@/components/probeshell/quick-usage";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { ProbeShellResult } from "@/types/probeshell";
import CodeViewer from "../code-viewer";
import { MultiPackResult } from "../memshell/results/multi-packer";
import { BasicInfo } from "./basic-info";
@@ -26,9 +29,7 @@ export default function ShellResult({
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-1">
<TabsTrigger value="packResult">
{t("common:generateResult")}
</TabsTrigger>
<TabsTrigger value="packResult">{t("common:generateResult")}</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} />
@@ -44,13 +45,11 @@ export default function ShellResult({
<CodeViewer
code={packResult}
header={
<div className="flex items-center justify-between text-xs gap-2">
<div className="flex items-center justify-between gap-2 text-xs">
<span>
{t("common:packerMethod")}{packMethod}
</span>
<span className="text-muted-foreground">
({packResult?.length})
</span>
<span className="text-muted-foreground">({packResult?.length})</span>
</div>
}
wrapLongLines={!showCode}
+3 -8
View File
@@ -1,3 +1,5 @@
import { create } from "@orama/orama";
import { useDocsSearch } from "fumadocs-core/search/client";
import {
SearchDialog,
SearchDialogClose,
@@ -9,8 +11,6 @@ import {
SearchDialogOverlay,
type SharedProps,
} from "fumadocs-ui/components/dialog/search";
import { useDocsSearch } from "fumadocs-core/search/client";
import { create } from "@orama/orama";
import { useI18n } from "fumadocs-ui/contexts/i18n";
function initOrama() {
@@ -29,12 +29,7 @@ export default function DefaultSearchDialog(props: SharedProps) {
});
return (
<SearchDialog
search={search}
onSearchChange={setSearch}
isLoading={query.isLoading}
{...props}
>
<SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
+2 -4
View File
@@ -1,10 +1,8 @@
export function TailwindIndicator() {
return (
<div className="fixed bottom-1 right-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="fixed right-1 bottom-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">
sm
</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div className="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div>
+15 -38
View File
@@ -1,5 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import type * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
@@ -8,26 +10,19 @@ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
}
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
}
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
}
function AlertDialogOverlay({
className,
...props
}: AlertDialogPrimitive.Backdrop.Props) {
function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
return (
<AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay"
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
"fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -49,7 +44,7 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-background p-6 ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
className,
)}
{...props}
@@ -58,10 +53,7 @@ function AlertDialogContent({
);
}
function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
@@ -74,10 +66,7 @@ function AlertDialogHeader({
);
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
@@ -90,15 +79,12 @@ function AlertDialogFooter({
);
}
function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
"mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
className,
)}
{...props}
@@ -130,7 +116,7 @@ function AlertDialogDescription({
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3",
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className,
)}
{...props}
@@ -138,17 +124,8 @@ function AlertDialogDescription({
);
}
function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
);
function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>) {
return <Button data-slot="alert-dialog-action" className={cn(className)} {...props} />;
}
function AlertDialogCancel({
+5 -7
View File
@@ -1,6 +1,7 @@
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const alertVariants = cva(
@@ -39,7 +40,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="alert-title"
className={cn(
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className,
)}
{...props}
@@ -47,15 +48,12 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
);
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className,
)}
{...props}
+11 -26
View File
@@ -1,6 +1,7 @@
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import { cn } from "@/lib/utils";
function Avatar({
@@ -15,7 +16,7 @@ function Avatar({
data-slot="avatar"
data-size={size}
className={cn(
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className,
)}
{...props}
@@ -27,24 +28,18 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn(
"rounded-full aspect-square size-full object-cover",
className,
)}
className={cn("aspect-square size-full rounded-full object-cover", className)}
{...props}
/>
);
}
function AvatarFallback({
className,
...props
}: AvatarPrimitive.Fallback.Props) {
function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className,
)}
{...props}
@@ -57,7 +52,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
<span
data-slot="avatar-badge"
className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
@@ -73,7 +68,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="avatar-group"
className={cn(
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className,
)}
{...props}
@@ -81,15 +76,12 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
);
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2",
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className,
)}
{...props}
@@ -97,11 +89,4 @@ function AvatarGroupCount({
);
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
};
export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge };
+3 -6
View File
@@ -10,14 +10,11 @@ const badgeVariants = cva(
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
+6 -17
View File
@@ -12,7 +12,7 @@ function Card({
data-slot="card"
data-size={size}
className={cn(
"ring-foreground/10 bg-card text-card-foreground gap-2 overflow-hidden rounded-xl pb-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
"group/card flex flex-col gap-2 overflow-hidden rounded-xl bg-card pb-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className,
)}
{...props}
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"gap-1 rounded-t-xl px-6 pt-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 pt-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4",
className,
)}
{...props}
@@ -50,7 +50,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
);
@@ -60,10 +60,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
{...props}
/>
);
@@ -84,7 +81,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-footer"
className={cn(
"rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center",
"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4",
className,
)}
{...props}
@@ -92,12 +89,4 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
+2 -6
View File
@@ -5,15 +5,11 @@ function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
}
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
return (
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
);
return <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />;
}
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
return (
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
);
return <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />;
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
+27 -37
View File
@@ -1,5 +1,6 @@
import { cva, type VariantProps } from "class-variance-authority";
import { useMemo } from "react";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
@@ -51,29 +52,26 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
);
}
const fieldVariants = cva(
"group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
responsive: [
"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
defaultVariants: {
orientation: "vertical",
const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
variants: {
orientation: {
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
responsive: [
"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
);
defaultVariants: {
orientation: "vertical",
},
});
function Field({
className,
@@ -94,26 +92,20 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
className,
)}
className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
{...props}
/>
);
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
className,
)}
{...props}
@@ -139,9 +131,9 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="field-description"
className={cn(
"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className,
)}
{...props}
@@ -169,7 +161,7 @@ function FieldSeparator({
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content"
>
{children}
@@ -196,9 +188,7 @@ function FieldError({
return null;
}
const uniqueErrors = [
...new Map(errors.map((error) => [error?.message, error])).values(),
];
const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
if (uniqueErrors?.length === 1) {
return uniqueErrors[0]?.message;
@@ -223,7 +213,7 @@ function FieldError({
<div
role="alert"
data-slot="field-error"
className={cn("text-destructive text-sm font-normal", className)}
className={cn("text-sm font-normal text-destructive", className)}
{...props}
>
{content}
+3 -2
View File
@@ -1,6 +1,7 @@
import { Input as InputPrimitive } from "@base-ui/react/input";
import type * as React from "react";
import { Input as InputPrimitive } from "@base-ui/react/input";
import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
@@ -9,7 +10,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 rounded-md border bg-transparent px-2.5 py-1 text-sm shadow-xs transition-[color,box-shadow] file:h-6 file:text-sm file:font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"h-8 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 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 file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+1 -1
View File
@@ -8,7 +8,7 @@ function Label({ className, ...props }: React.ComponentProps<"label">) {
<label
data-slot="label"
className={cn(
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
+4 -3
View File
@@ -1,13 +1,14 @@
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
import { CircleIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
return (
<RadioGroupPrimitive
data-slot="radio-group"
className={cn("grid gap-2 w-full", className)}
className={cn("grid w-full gap-2", className)}
{...props}
/>
);
@@ -18,14 +19,14 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
<RadioPrimitive.Root
data-slot="radio-group-item"
className={cn(
"border-input text-primary dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex size-4 rounded-full shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
>
<RadioPrimitive.Indicator
data-slot="radio-group-indicator"
className="group-aria-invalid/radio-group-item:text-destructive text-primary flex size-4 items-center justify-center"
className="flex size-4 items-center justify-center text-primary group-aria-invalid/radio-group-item:text-destructive"
>
<CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current" />
</RadioPrimitive.Indicator>
+15 -25
View File
@@ -1,6 +1,8 @@
import type * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root;
@@ -38,16 +40,14 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
"flex w-full items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon
render={
<ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
}
render={<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />}
/>
</SelectPrimitive.Trigger>
);
@@ -80,7 +80,7 @@ function SelectContent({
<SelectPrimitive.Popup
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto",
"relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-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",
className,
)}
{...props}
@@ -94,34 +94,27 @@ function SelectContent({
);
}
function SelectLabel({
className,
...props
}: SelectPrimitive.GroupLabel.Props) {
function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {
return (
<SelectPrimitive.GroupLabel
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
{...props}
/>
);
}
function SelectItem({
className,
children,
...props
}: SelectPrimitive.Item.Props) {
function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<SelectPrimitive.ItemText className="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
{children}
</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator
@@ -135,14 +128,11 @@ function SelectItem({
);
}
function SelectSeparator({
className,
...props
}: SelectPrimitive.Separator.Props) {
function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border -mx-1 my-1 h-px pointer-events-none", className)}
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props}
/>
);
@@ -156,7 +146,7 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
className={cn(
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full",
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@@ -174,7 +164,7 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full",
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
+2 -6
View File
@@ -2,17 +2,13 @@ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
...props
}: SeparatorPrimitive.Props) {
function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
return (
<SeparatorPrimitive
data-slot="separator"
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",
className,
)}
{...props}
+1 -1
View File
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("bg-muted rounded-md animate-pulse", className)}
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
+1
View File
@@ -1,4 +1,5 @@
import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+5 -19
View File
@@ -1,23 +1,13 @@
import { InfoIcon } from "lucide-react";
import { memo } from "react";
import {
type Control,
Controller,
type FieldValues,
type Path,
} from "react-hook-form";
import { type Control, Controller, type FieldValues, type Path } from "react-hook-form";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// Hoisted static JSX to avoid recreation on each render
const infoIcon = (
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
);
const infoIcon = <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />;
interface SwitchFieldProps<T extends FieldValues> {
readonly name: Path<T>;
@@ -38,11 +28,7 @@ function SwitchFieldInner<T extends FieldValues>({
name={name}
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id={name}
checked={field.value}
onCheckedChange={field.onChange}
/>
<Switch id={name} checked={field.value} onCheckedChange={field.onChange} />
<Label htmlFor={name}>{label}</Label>
<Tooltip>
<TooltipTrigger>{infoIcon}</TooltipTrigger>
+2 -2
View File
@@ -14,14 +14,14 @@ function Switch({
data-slot="switch"
data-size={size}
className={cn(
"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50",
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-checked:bg-primary data-disabled:cursor-not-allowed data-disabled:opacity-50 data-unchecked:bg-input data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:data-unchecked:bg-input/80",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform"
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground"
/>
</SwitchPrimitive.Root>
);
+6 -13
View File
@@ -3,19 +3,12 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
function Tabs({
className,
orientation = "horizontal",
...props
}: TabsPrimitive.Root.Props) {
function Tabs({ className, orientation = "horizontal", ...props }: TabsPrimitive.Root.Props) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
className={cn(
"gap-2 group/tabs flex data-[orientation=horizontal]:flex-col",
className,
)}
className={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)}
{...props}
/>
);
@@ -56,10 +49,10 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className,
)}
{...props}
@@ -71,7 +64,7 @@ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return (
<TabsPrimitive.Panel
data-slot="tabs-content"
className={cn("text-sm flex-1 outline-none", className)}
className={cn("flex-1 text-sm outline-none", className)}
{...props}
/>
);
+1 -1
View File
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea
data-slot="textarea"
className={cn(
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+5 -17
View File
@@ -2,17 +2,8 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
import { cn } from "@/lib/utils";
function TooltipProvider({
delay = 0,
...props
}: TooltipPrimitive.Provider.Props) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delay={delay}
{...props}
/>
);
function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />;
}
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
@@ -36,10 +27,7 @@ function TooltipContent({
children,
...props
}: TooltipPrimitive.Popup.Props &
Pick<
TooltipPrimitive.Positioner.Props,
"align" | "alignOffset" | "side" | "sideOffset"
>) {
Pick<TooltipPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Positioner
@@ -52,13 +40,13 @@ function TooltipContent({
<TooltipPrimitive.Popup
data-slot="tooltip-content"
className={cn(
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 rounded-md px-3 py-1.5 text-xs bg-foreground text-background z-50 max-w-xs origin-(--transform-origin) wrap-break-word",
"z-50 max-w-xs origin-(--transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs wrap-break-word text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal>