Fix TypeScript compilation errors

- Add isIndeterminate support to Checkbox component
- Use showAddressModal in SortableTableRow for multi-address display
- Remove unused onClose parameter in search modal
- Remove unused infoPopoverPlacement and related code in node.tsx
This commit is contained in:
sagitchu
2026-03-15 23:31:39 +08:00
parent 8124e59de5
commit efb613b0b5
4 changed files with 33 additions and 68 deletions
+5 -4
View File
@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react"; import { CheckIcon, MinusIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -11,17 +11,18 @@ function Checkbox({
return ( return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow transition-transform duration-100 active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow transition-transform duration-100 active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
className, className,
)} )}
data-slot="checkbox" data-slot="checkbox"
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className="flex items-center justify-center text-current data-[state=checked]:animate-in data-[state=checked]:zoom-in-75 data-[state=checked]:duration-150" className="flex items-center justify-center text-current data-[state=checked]:animate-in data-[state=checked]:zoom-in-75 data-[state=checked]:duration-150 data-[state=indeterminate]:animate-in data-[state=indeterminate]:zoom-in-75 data-[state=indeterminate]:duration-150"
data-slot="checkbox-indicator" data-slot="checkbox-indicator"
> >
<CheckIcon className="h-3.5 w-3.5" /> <CheckIcon className="h-3.5 w-3.5 data-[state=indeterminate]:hidden" />
<MinusIcon className="h-3.5 w-3.5 hidden data-[state=indeterminate]:block" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
); );
+21 -27
View File
@@ -784,44 +784,38 @@ const SortableTableRow = ({
<TableCell <TableCell
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.inbound} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`} className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.inbound} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
> >
<div <button
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all select-text ${ type="button"
hasMultipleAddresses(forward.inIp) className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
? "hover:bg-default-200 hover:shadow-sm cursor-pointer" hasMultipleAddresses(forward.inIp) ? "hover:bg-default-200 hover:shadow-sm cursor-pointer" : "cursor-default"
: "cursor-text"
}`} }`}
title="点击复制" title={formatInAddress(forward.inIp, forward.inPort)}
onClick={(e) => { onClick={() => {
e.stopPropagation(); if (hasMultipleAddresses(forward.inIp)) {
copyToClipboard( showAddressModal(forward.inIp, forward.inPort, "入口端口");
(forward.inIp || "").replace(/:\d+$/, "") || "默认IP", }
"入口地址",
);
}} }}
> >
{formatInAddress(forward.inIp, forward.inPort)} {formatInAddress(forward.inIp, forward.inPort)}
</div> </button>
</TableCell> </TableCell>
<TableCell <TableCell
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`} className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
> >
<div <button
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all select-text ${ type="button"
hasMultipleAddresses(forward.remoteAddr) className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
? "hover:bg-default-200 hover:shadow-sm cursor-pointer" hasMultipleAddresses(forward.remoteAddr) ? "hover:bg-default-200 hover:shadow-sm cursor-pointer" : "cursor-default"
: "cursor-text"
}`} }`}
title="点击复制" title={formatRemoteAddress(forward.remoteAddr)}
onClick={(e) => { onClick={() => {
e.stopPropagation(); if (hasMultipleAddresses(forward.remoteAddr)) {
copyToClipboard( showAddressModal(forward.remoteAddr, null, "目标地址");
forward.remoteAddr.split(",")[0].split(":")[0], }
"落地地址",
);
}} }}
> >
{formatRemoteAddress(forward.remoteAddr)} {formatRemoteAddress(forward.remoteAddr)}
</div> </button>
</TableCell> </TableCell>
<TableCell className={FORWARD_GROUPED_TABLE_COLUMN_CLASS.strategy}> <TableCell className={FORWARD_GROUPED_TABLE_COLUMN_CLASS.strategy}>
<Chip <Chip
@@ -5986,7 +5980,7 @@ export default function ForwardPage() {
onOpenChange={setIsSearchModalOpen} onOpenChange={setIsSearchModalOpen}
> >
<ModalContent> <ModalContent>
{(onClose) => ( {() => (
<> <>
<ModalHeader className="flex flex-col gap-1"> <ModalHeader className="flex flex-col gap-1">
搜索筛选用户规则 搜索筛选用户规则
-36
View File
@@ -374,40 +374,6 @@ export default function NodePage() {
const [upgradeProgress, setUpgradeProgress] = useState< const [upgradeProgress, setUpgradeProgress] = useState<
Record<number, { stage: string; percent: number; message: string }> Record<number, { stage: string; percent: number; message: string }>
>({}); >({});
const [infoPopoverPlacement, setInfoPopoverPlacement] = useState<
Record<number, "left" | "bottom">
>({});
const updateInfoPopoverPlacement = useCallback(
(nodeId: number, triggerElement: HTMLElement | null) => {
if (!triggerElement) {
return;
}
const rect = triggerElement.getBoundingClientRect();
const cardElement = triggerElement.closest("[data-node-card='true']");
const cardRect =
cardElement instanceof HTMLElement
? cardElement.getBoundingClientRect()
: null;
const estimatedPanelWidth = 288;
const containerPadding = 16;
const availableLeftSpace = cardRect
? rect.left - cardRect.left
: rect.left;
const nextPlacement: "left" | "bottom" =
availableLeftSpace >= estimatedPanelWidth + containerPadding
? "left"
: "bottom";
setInfoPopoverPlacement((prev) =>
prev[nodeId] === nextPlacement
? prev
: { ...prev, [nodeId]: nextPlacement },
);
},
[],
);
const handleNodeOffline = useCallback((nodeId: number) => { const handleNodeOffline = useCallback((nodeId: number) => {
setNodeList((prev) => setNodeList((prev) =>
@@ -1744,8 +1710,6 @@ export default function NodePage() {
!node.expiryReminderDismissed, !node.expiryReminderDismissed,
); );
const hasInfoTrigger = hasRemark || hasExpiryInfo; const hasInfoTrigger = hasRemark || hasExpiryInfo;
const infoCount = Number(hasExpiryInfo) + Number(hasRemark);
const infoPlacement = infoPopoverPlacement[node.id] ?? "left";
return ( return (
<SortableItem key={node.id} id={node.id}> <SortableItem key={node.id} id={node.id}>
@@ -11,6 +11,7 @@ export interface CheckboxProps
classNames?: Record<string, string>; classNames?: Record<string, string>;
color?: string; color?: string;
isDisabled?: boolean; isDisabled?: boolean;
isIndeterminate?: boolean;
isSelected?: boolean; isSelected?: boolean;
onValueChange?: (value: boolean) => void; onValueChange?: (value: boolean) => void;
size?: string; size?: string;
@@ -20,6 +21,7 @@ export function Checkbox({
children, children,
className, className,
isDisabled, isDisabled,
isIndeterminate,
isSelected, isSelected,
onValueChange, onValueChange,
...props ...props
@@ -32,6 +34,10 @@ export function Checkbox({
onValueChange?.(value === true); onValueChange?.(value === true);
}; };
const checkedValue: boolean | "indeterminate" = isIndeterminate
? "indeterminate"
: Boolean(isSelected);
return ( return (
<div <div
className={cn( className={cn(
@@ -41,7 +47,7 @@ export function Checkbox({
)} )}
> >
<BaseCheckbox <BaseCheckbox
checked={Boolean(isSelected)} checked={checkedValue}
disabled={isDisabled} disabled={isDisabled}
onCheckedChange={handleCheckedChange} onCheckedChange={handleCheckedChange}
{...props} {...props}