feat: support server port reuse

This commit is contained in:
2025-05-31 01:59:04 +08:00
parent a676061b3e
commit ac7d1efb8d
32 changed files with 1111 additions and 139 deletions

View File

@@ -23,11 +23,7 @@ export function DataTableViewOptions<TData>({
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<Button variant="outline" size="sm" className="ml-auto h-8 flex">
<Settings2 />
Columns
</Button>

View File

@@ -0,0 +1,53 @@
import { StandardSchemaV1Issue } from "@tanstack/react-form";
import { AlertCircle } from "lucide-react";
import { useMemo } from "react";
interface ErrorDisplayProps {
errors?:
| string
| StandardSchemaV1Issue
| Array<string | StandardSchemaV1Issue | undefined>;
}
export function FormFieldErrors({ errors }: ErrorDisplayProps) {
const errorList = useMemo(
() =>
(Array.isArray(errors) ? errors : [errors]).filter(Boolean) as Array<
string | StandardSchemaV1Issue
>,
[errors]
);
if (!errorList.length) {
return null;
}
return (
<ul className="mt-1 space-y-1 text-sm text-destructive">
{errorList.map((error, index) => {
if (typeof error === "string") {
return (
<li key={index} className="flex items-center space-x-2">
<AlertCircle
size={16}
className="flex-shrink-0 text-destructive"
/>
<span>{error}</span>
</li>
);
}
return (
<li key={index} className="flex flex-col space-y-0.5">
<div className="flex items-center space-x-2">
<AlertCircle
size={16}
className="flex-shrink-0 text-destructive"
/>
<span>{error.message}</span>
</div>
</li>
);
})}
</ul>
);
}

View File

@@ -3,7 +3,7 @@
import * as LabelPrimitive from "@radix-ui/react-label";
import * as React from "react";
import { cn } from "@/presentation/utils";
import { cn } from "@/presentation/utils/index";
function Label({
className,

View File

@@ -0,0 +1,143 @@
import { Slot } from "@radix-ui/react-slot";
import * as React from "react";
import { Label } from "@/components/ui/label";
import { cn } from "@/presentation/utils";
import {
createFormHook,
createFormHookContexts,
useStore,
} from "@tanstack/react-form";
const {
fieldContext,
formContext,
useFieldContext: useFormFieldContext,
useFormContext,
} = createFormHookContexts();
const { useAppForm, withForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormItem,
},
formComponents: {},
});
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
);
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider>
);
}
const useFieldContext = () => {
const { id } = React.useContext(FormItemContext);
const { name, store, ...fieldContext } = useFormFieldContext();
const errors = useStore(store, (state) => state.meta.errors);
if (!fieldContext) {
throw new Error("useFieldContext should be used within <FormItem>");
}
return {
id,
name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
errors,
store,
...fieldContext,
};
};
function FormLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
const { formItemId, errors } = useFieldContext();
return (
<Label
data-slot="form-label"
data-error={!!errors.length}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const { errors, formItemId, formDescriptionId, formMessageId } =
useFieldContext();
return (
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
errors.length
? `${formDescriptionId} ${formMessageId}`
: `${formDescriptionId}`
}
aria-invalid={!!errors.length}
{...props}
/>
);
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
const { formDescriptionId } = useFieldContext();
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
const { errors, formMessageId } = useFieldContext();
const body = errors.length
? String(errors.at(0)?.message ?? "")
: props.children;
if (!body) return null;
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
{...props}
>
{body}
</p>
);
}
export { useAppForm, useFormContext, useFieldContext, withForm };