refactor: cleanup

This commit is contained in:
zhom
2026-05-14 20:04:19 +04:00
parent 597efb7e58
commit 56b0da990b
53 changed files with 473 additions and 350 deletions
+7 -7
View File
@@ -233,8 +233,8 @@ export function AccountPage({
<TabsContent value="account" className="mt-4">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<div className="grid place-items-center w-12 h-12 rounded-full bg-accent text-foreground shrink-0">
<LuUser className="w-6 h-6" />
<div className="grid place-items-center size-12 rounded-full bg-accent text-foreground shrink-0">
<LuUser className="size-6" />
</div>
<div className="min-w-0 flex-1">
{isLoggedIn && user ? (
@@ -309,7 +309,7 @@ export function AccountPage({
disabled={isRefreshing}
className="h-8 text-xs gap-1.5"
>
<LuRefreshCw className="w-3 h-3" />
<LuRefreshCw className="size-3" />
{t("account.refresh")}
</Button>
<LoadingButton
@@ -322,7 +322,7 @@ export function AccountPage({
}}
className="h-8 text-xs gap-1.5"
>
<LuLogOut className="w-3 h-3" />
<LuLogOut className="size-3" />
{t("account.logout")}
</LoadingButton>
</>
@@ -332,7 +332,7 @@ export function AccountPage({
onClick={onOpenSignIn}
className="h-8 text-xs gap-1.5"
>
<LuCloud className="w-3 h-3" />
<LuCloud className="size-3" />
{t("account.signIn")}
</Button>
)}
@@ -410,9 +410,9 @@ export function AccountPage({
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground"
>
{showToken ? (
<LuEyeOff className="w-3.5 h-3.5" />
<LuEyeOff className="size-3.5" />
) : (
<LuEye className="w-3.5 h-3.5" />
<LuEye className="size-3.5" />
)}
</button>
</div>
+5 -5
View File
@@ -37,7 +37,7 @@ export function AppUpdateToast({
return (
<div className="flex items-start p-4 w-full max-w-md rounded-lg border shadow-lg bg-card border-border text-card-foreground">
<div className="mr-3 mt-0.5">
<LuCheckCheck className="flex-shrink-0 w-5 h-5" />
<LuCheckCheck className="flex-shrink-0 size-5" />
</div>
<div className="flex-1 min-w-0">
@@ -59,9 +59,9 @@ export function AppUpdateToast({
variant="ghost"
size="sm"
onClick={onDismiss}
className="p-0 w-6 h-6 shrink-0"
className="p-0 size-6 shrink-0"
>
<FaTimes className="w-3 h-3" />
<FaTimes className="size-3" />
</Button>
</div>
@@ -72,7 +72,7 @@ export function AppUpdateToast({
size="sm"
className="flex gap-2 items-center text-xs"
>
<LuCheckCheck className="w-3 h-3" />
<LuCheckCheck className="size-3" />
{t("appUpdate.toast.restartNow")}
</RippleButton>
) : (
@@ -83,7 +83,7 @@ export function AppUpdateToast({
size="sm"
className="flex gap-2 items-center text-xs"
>
<FaExternalLinkAlt className="w-3 h-3" />
<FaExternalLinkAlt className="size-3" />
{t("appUpdate.toast.viewRelease")}
</RippleButton>
)
+10 -8
View File
@@ -36,16 +36,18 @@ export function CloneProfileDialog({
const inputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (isOpen && profile) {
const defaultName = `${profile.name} (Copy)`;
setName(defaultName);
setTimeout(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, 0);
} else {
if (!(isOpen && profile)) {
setIsLoading(false);
return;
}
setName(`${profile.name} (Copy)`);
const handle = window.setTimeout(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, 0);
return () => {
window.clearTimeout(handle);
};
}, [isOpen, profile]);
if (!profile) return null;
+6 -6
View File
@@ -335,7 +335,7 @@ export function CookieCopyDialog({
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<LuCookie className="w-5 h-5" />
<LuCookie className="size-5" />
{t("cookies.copy.title")}
</DialogTitle>
<DialogDescription>
@@ -372,7 +372,7 @@ export function CookieCopyDialog({
disabled={isRunning}
>
<div className="flex items-center gap-2">
{IconComponent && <IconComponent className="w-4 h-4" />}
{IconComponent && <IconComponent className="size-4" />}
<span>{profile.name}</span>
{isRunning && (
<span className="text-xs text-muted-foreground">
@@ -437,7 +437,7 @@ export function CookieCopyDialog({
</div>
<div className="relative">
<LuSearch className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<LuSearch className="absolute left-2 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
placeholder={t("cookies.copy.searchPlaceholder")}
value={searchQuery}
@@ -450,7 +450,7 @@ export function CookieCopyDialog({
{isLoadingCookies ? (
<div className="flex items-center justify-center h-40">
<div className="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" />
<div className="animate-spin size-6 border-2 border-primary border-t-transparent rounded-full" />
</div>
) : error ? (
<div className="p-4 text-center text-destructive bg-destructive/10 rounded-md">
@@ -565,9 +565,9 @@ function DomainRow({
}}
>
{isExpanded ? (
<LuChevronDown className="w-4 h-4" />
<LuChevronDown className="size-4" />
) : (
<LuChevronRight className="w-4 h-4" />
<LuChevronRight className="size-4" />
)}
<span className="font-medium">{domain.domain}</span>
<span className="text-xs text-muted-foreground">
+4 -4
View File
@@ -429,7 +429,7 @@ export function CookieManagementDialog({
}
}}
>
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
<LuUpload className="size-10 text-muted-foreground mb-4" />
<p className="text-sm text-muted-foreground text-center">
{t("cookies.management.dropPrompt")}
<br />
@@ -556,7 +556,7 @@ export function CookieManagementDialog({
{isLoadingExportCookies ? (
<div className="flex items-center justify-center h-24">
<div className="animate-spin h-5 w-5 border-2 border-primary border-t-transparent rounded-full" />
<div className="animate-spin size-5 border-2 border-primary border-t-transparent rounded-full" />
</div>
) : !exportCookieData || exportCookieData.domains.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground border rounded-md">
@@ -645,9 +645,9 @@ function ExportDomainRow({
}}
>
{isExpanded ? (
<LuChevronDown className="w-3.5 h-3.5" />
<LuChevronDown className="size-3.5" />
) : (
<LuChevronRight className="w-3.5 h-3.5" />
<LuChevronRight className="size-3.5" />
)}
<span className="font-medium truncate">{domain.domain}</span>
<span className="text-xs text-muted-foreground shrink-0">
+36 -23
View File
@@ -1,7 +1,14 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { GoPlus } from "react-icons/go";
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
@@ -116,6 +123,8 @@ export function CreateProfileDialog({
crossOsUnlocked = false,
}: CreateProfileDialogProps) {
const { t } = useTranslation();
const proxyListboxIdAntiDetect = useId();
const proxyListboxIdRegular = useId();
const [profileName, setProfileName] = useState("");
const [currentStep, setCurrentStep] = useState<
"browser-selection" | "browser-config"
@@ -605,11 +614,11 @@ export function CreateProfileDialog({
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
<div className="flex justify-center items-center size-8">
{(() => {
const IconComponent = getBrowserIcon("wayfern");
return IconComponent ? (
<IconComponent className="w-6 h-6" />
<IconComponent className="size-6" />
) : null;
})()}
</div>
@@ -631,11 +640,11 @@ export function CreateProfileDialog({
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
<div className="flex justify-center items-center size-8">
{(() => {
const IconComponent = getBrowserIcon("camoufox");
return IconComponent ? (
<IconComponent className="w-6 h-6" />
<IconComponent className="size-6" />
) : null;
})()}
</div>
@@ -676,9 +685,9 @@ export function CreateProfileDialog({
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
<div className="flex justify-center items-center size-8">
{IconComponent && (
<IconComponent className="w-6 h-6" />
<IconComponent className="size-6" />
)}
</div>
<div className="text-left">
@@ -729,7 +738,7 @@ export function CreateProfileDialog({
{/* Ephemeral Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="ephemeral"
checked={ephemeral}
@@ -749,7 +758,7 @@ export function CreateProfileDialog({
{/* Password Option */}
{!ephemeral && (
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="enable-password"
checked={enablePassword}
@@ -814,7 +823,7 @@ export function CreateProfileDialog({
{/* Wayfern Download Status */}
{isLoadingReleaseTypes && (
<div className="flex gap-3 items-center p-3 rounded-md border">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
{t("createProfile.version.fetching")}
</p>
@@ -922,7 +931,7 @@ export function CreateProfileDialog({
{/* Camoufox Download Status */}
{isLoadingReleaseTypes && (
<div className="flex gap-3 items-center p-3 rounded-md border">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
{t("createProfile.version.fetching")}
</p>
@@ -1041,7 +1050,7 @@ export function CreateProfileDialog({
<div className="space-y-3">
{isLoadingReleaseTypes && (
<div className="flex gap-3 items-center">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
{t("createProfile.version.fetching")}
</p>
@@ -1154,7 +1163,7 @@ export function CreateProfileDialog({
}}
className="px-2 h-7 text-xs"
>
<GoPlus className="mr-1 w-3 h-3" />{" "}
<GoPlus className="mr-1 size-3" />{" "}
{t("createProfile.proxy.addProxy")}
</RippleButton>
</div>
@@ -1168,6 +1177,7 @@ export function CreateProfileDialog({
variant="outline"
role="combobox"
aria-expanded={proxyPopoverOpen}
aria-controls={proxyListboxIdAntiDetect}
className="w-full justify-between font-normal"
>
{(() => {
@@ -1190,10 +1200,11 @@ export function CreateProfileDialog({
t("createProfile.proxy.noProxy")
);
})()}
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
id={proxyListboxIdAntiDetect}
className="w-[240px] p-0"
sideOffset={8}
>
@@ -1217,7 +1228,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
!selectedProxyId
? "opacity-100"
: "opacity-0",
@@ -1236,7 +1247,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedProxyId === proxy.id
? "opacity-100"
: "opacity-0",
@@ -1261,7 +1272,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedProxyId ===
`vpn-${vpn.id}`
? "opacity-100"
@@ -1412,7 +1423,7 @@ export function CreateProfileDialog({
<div className="space-y-3">
{isLoadingReleaseTypes && (
<div className="flex gap-3 items-center">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<div className="size-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
Fetching available versions...
</p>
@@ -1520,7 +1531,7 @@ export function CreateProfileDialog({
}}
className="px-2 h-7 text-xs"
>
<GoPlus className="mr-1 w-3 h-3" />{" "}
<GoPlus className="mr-1 size-3" />{" "}
{t("createProfile.proxy.addProxy")}
</RippleButton>
</div>
@@ -1534,6 +1545,7 @@ export function CreateProfileDialog({
variant="outline"
role="combobox"
aria-expanded={proxyPopoverOpen}
aria-controls={proxyListboxIdRegular}
className="w-full justify-between font-normal"
>
{(() => {
@@ -1556,10 +1568,11 @@ export function CreateProfileDialog({
t("createProfile.proxy.noProxy")
);
})()}
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
id={proxyListboxIdRegular}
className="w-[240px] p-0"
sideOffset={8}
>
@@ -1583,7 +1596,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
!selectedProxyId
? "opacity-100"
: "opacity-0",
@@ -1602,7 +1615,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedProxyId === proxy.id
? "opacity-100"
: "opacity-0",
@@ -1627,7 +1640,7 @@ export function CreateProfileDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedProxyId ===
`vpn-${vpn.id}`
? "opacity-100"
+12 -12
View File
@@ -174,42 +174,42 @@ function formatEtaCompact(seconds: number): string {
function getToastIcon(type: ToastProps["type"], stage?: string) {
switch (type) {
case "success":
return <LuCheckCheck className="flex-shrink-0 w-4 h-4 text-foreground" />;
return <LuCheckCheck className="flex-shrink-0 size-4 text-foreground" />;
case "error":
return (
<LuTriangleAlert className="flex-shrink-0 w-4 h-4 text-foreground" />
<LuTriangleAlert className="flex-shrink-0 size-4 text-foreground" />
);
case "download":
if (stage === "completed") {
return (
<LuCheckCheck className="flex-shrink-0 w-4 h-4 text-foreground" />
<LuCheckCheck className="flex-shrink-0 size-4 text-foreground" />
);
}
return <LuDownload className="flex-shrink-0 w-4 h-4 text-foreground" />;
return <LuDownload className="flex-shrink-0 size-4 text-foreground" />;
case "version-update":
return (
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
);
case "fetching":
return (
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
);
case "twilight-update":
return (
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
);
case "sync-progress":
return (
<LuRefreshCw className="flex-shrink-0 w-4 h-4 animate-spin text-foreground" />
<LuRefreshCw className="flex-shrink-0 size-4 animate-spin text-foreground" />
);
case "loading":
return (
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
<div className="flex-shrink-0 size-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
);
default:
return (
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
<div className="flex-shrink-0 size-4 rounded-full border-2 animate-spin border-foreground border-t-transparent" />
);
}
}
@@ -235,7 +235,7 @@ export function UnifiedToast(props: ToastProps) {
className="ml-2 p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors flex-shrink-0"
aria-label={t("common.buttons.cancel")}
>
<LuX className="w-3 h-3" />
<LuX className="size-3" />
</button>
)}
</div>
@@ -272,7 +272,7 @@ export function UnifiedToast(props: ToastProps) {
<>Looking for updates for {progress.current_browser}</>
)}
</p>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<div className="flex-1 bg-muted rounded-full h-1.5 min-w-0">
<div
className="bg-foreground h-1.5 rounded-full transition-all duration-150"
+1 -1
View File
@@ -106,7 +106,7 @@ function DataTableActionBarAction({
{...props}
>
{isPending ? (
<div className="w-3.5 h-3.5 rounded-full border border-current animate-spin border-t-transparent" />
<div className="size-3.5 rounded-full border border-current animate-spin border-t-transparent" />
) : (
children
)}
+2 -2
View File
@@ -155,13 +155,13 @@ export function DeleteGroupDialog({
setDeleteAction(value as "move" | "delete");
}}
>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<RadioGroupItem value="move" id="move" />
<Label htmlFor="move" className="text-sm">
{t("groups.moveToDefault")}
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<RadioGroupItem value="delete" id="delete" />
<Label
htmlFor="delete"
+1 -1
View File
@@ -105,7 +105,7 @@ export function DeviceCodeVerifyDialog({
disabled={isOpeningLogin}
className="w-full gap-1.5"
>
<LuExternalLink className="w-3.5 h-3.5" />
<LuExternalLink className="size-3.5" />
{t("sync.cloud.openLogin")}
</Button>
<div className="space-y-2">
+1 -1
View File
@@ -137,7 +137,7 @@ export function DnsBlocklistDialog({
className="w-full"
>
<LuRefreshCw
className={`mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
className={`mr-2 size-4 ${isRefreshing ? "animate-spin" : ""}`}
/>
{t("dnsBlocklist.refreshAll")}
</Button>
+18 -18
View File
@@ -483,7 +483,7 @@ export function ExtensionManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex">
<FaChrome className="w-3.5 h-3.5 text-muted-foreground" />
<FaChrome className="size-3.5 text-muted-foreground" />
</span>
</TooltipTrigger>
<TooltipContent>
@@ -495,7 +495,7 @@ export function ExtensionManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex">
<FaFirefox className="w-3.5 h-3.5 text-muted-foreground" />
<FaFirefox className="size-3.5 text-muted-foreground" />
</span>
</TooltipTrigger>
<TooltipContent>
@@ -508,7 +508,7 @@ export function ExtensionManagementDialog({
};
const renderExtensionIcon = (ext: Extension, size: "sm" | "md" = "md") => {
const sizeClass = size === "sm" ? "w-4 h-4" : "w-5 h-5";
const sizeClass = size === "sm" ? "size-4" : "size-5";
if (extensionIcons[ext.id]) {
return (
// biome-ignore lint/performance/noImgElement: base64 data URI icons cannot use next/image
@@ -533,7 +533,7 @@ export function ExtensionManagementDialog({
{!subPage && (
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<LuPuzzle className="w-5 h-5" />
<LuPuzzle className="size-5" />
{t("extensions.title")}
{limitedMode && <ProBadge />}
</DialogTitle>
@@ -615,7 +615,7 @@ export function ExtensionManagementDialog({
document.getElementById("ext-file-input")?.click()
}
>
<LuUpload className="w-4 h-4" />
<LuUpload className="size-4" />
{t("extensions.upload")}
</RippleButton>
</label>
@@ -697,7 +697,7 @@ export function ExtensionManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
syncDot.animate ? "animate-pulse" : ""
}`}
/>
@@ -743,14 +743,14 @@ export function ExtensionManagementDialog({
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0"
className="size-7 p-0"
onClick={() => {
setEditingExtension(ext);
setEditExtensionName(ext.name);
setPendingUpdateFile(null);
}}
>
<LuPencil className="w-3.5 h-3.5" />
<LuPencil className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -762,12 +762,12 @@ export function ExtensionManagementDialog({
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0"
className="size-7 p-0"
onClick={() => {
setExtensionToDelete(ext);
}}
>
<LuTrash2 className="w-3.5 h-3.5" />
<LuTrash2 className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -795,7 +795,7 @@ export function ExtensionManagementDialog({
className="flex gap-2 items-center"
disabled={limitedMode}
>
<GoPlus className="w-4 h-4" />
<GoPlus className="size-4" />
{t("extensions.createGroup")}
</RippleButton>
</div>
@@ -865,7 +865,7 @@ export function ExtensionManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-2 h-2 rounded-full shrink-0 ${groupSyncDot.color} ${
className={`size-2 rounded-full shrink-0 ${groupSyncDot.color} ${
groupSyncDot.animate
? "animate-pulse"
: ""
@@ -956,7 +956,7 @@ export function ExtensionManagementDialog({
]);
}}
>
<LuPencil className="w-4 h-4" />
<LuPencil className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -972,7 +972,7 @@ export function ExtensionManagementDialog({
setGroupToDelete(group);
}}
>
<LuTrash2 className="w-4 h-4" />
<LuTrash2 className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -1086,14 +1086,14 @@ export function ExtensionManagementDialog({
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 shrink-0"
className="size-6 p-0 shrink-0"
onClick={() => {
setEditGroupExtensionIds((prev) =>
prev.filter((id) => id !== extId),
);
}}
>
<LuTrash2 className="w-3 h-3" />
<LuTrash2 className="size-3" />
</Button>
</div>
);
@@ -1219,7 +1219,7 @@ export function ExtensionManagementDialog({
<span className="truncate">
{editingExtension.homepage_url}
</span>
<LuExternalLink className="w-3 h-3 shrink-0" />
<LuExternalLink className="size-3 shrink-0" />
</a>
</>
)}
@@ -1245,7 +1245,7 @@ export function ExtensionManagementDialog({
document.getElementById("ext-edit-file-input")?.click()
}
>
<LuUpload className="w-3 h-3 mr-1" />
<LuUpload className="size-3 mr-1" />
{t("extensions.selectFile")}
</RippleButton>
<input
+1 -1
View File
@@ -165,7 +165,7 @@ export function GroupAssignmentDialog({
setCreateDialogOpen(true);
}}
>
<GoPlus className="mr-1 w-3 h-3" />{" "}
<GoPlus className="mr-1 size-3" />{" "}
{t("groupManagement.createGroup")}
</RippleButton>
</div>
+4 -4
View File
@@ -273,7 +273,7 @@ export function GroupManagementDialog({
}}
className="flex gap-2 items-center"
>
<GoPlus className="w-4 h-4" />
<GoPlus className="size-4" />
{t("proxies.management.create")}
</RippleButton>
</div>
@@ -326,7 +326,7 @@ export function GroupManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
syncDot.animate ? "animate-pulse" : ""
}`}
/>
@@ -383,7 +383,7 @@ export function GroupManagementDialog({
handleEditGroup(group);
}}
>
<LuPencil className="w-4 h-4" />
<LuPencil className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -401,7 +401,7 @@ export function GroupManagementDialog({
handleDeleteGroup(group);
}}
>
<LuTrash2 className="w-4 h-4" />
<LuTrash2 className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
+7 -7
View File
@@ -208,9 +208,9 @@ const HomeHeader = ({
behavior: "smooth",
});
}}
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-5 h-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center size-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
>
<LuChevronLeft className="w-3 h-3" />
<LuChevronLeft className="size-3" />
</button>
)}
<div
@@ -283,9 +283,9 @@ const HomeHeader = ({
behavior: "smooth",
});
}}
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-5 h-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 grid place-items-center size-5 rounded-full bg-card/90 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors shadow-sm"
>
<LuChevronRight className="w-3 h-3" />
<LuChevronRight className="size-3" />
</button>
)}
</div>
@@ -304,7 +304,7 @@ const HomeHeader = ({
}}
className="pr-7 pl-8 w-52 h-7 text-xs"
/>
<LuSearch className="absolute left-2.5 top-1/2 w-3.5 h-3.5 transform -translate-y-1/2 text-muted-foreground pointer-events-none" />
<LuSearch className="absolute left-2.5 top-1/2 size-3.5 transform -translate-y-1/2 text-muted-foreground pointer-events-none" />
{searchQuery ? (
<button
type="button"
@@ -314,7 +314,7 @@ const HomeHeader = ({
className="absolute right-1.5 top-1/2 p-0.5 rounded-sm transition-colors transform -translate-y-1/2 hover:bg-accent"
aria-label={t("header.clearSearch")}
>
<LuX className="w-3.5 h-3.5 text-muted-foreground hover:text-foreground" />
<LuX className="size-3.5 text-muted-foreground hover:text-foreground" />
</button>
) : null}
</div>
@@ -331,7 +331,7 @@ const HomeHeader = ({
}}
className="flex gap-1.5 items-center h-7 px-2.5 text-xs"
>
<GoPlus className="w-3.5 h-3.5" />
<GoPlus className="size-3.5" />
{t("header.newProfile")}
</Button>
</span>
+3 -3
View File
@@ -383,7 +383,7 @@ export function ImportProfileDialog({
>
<div className="flex gap-2 items-center">
{IconComponent && (
<IconComponent className="w-4 h-4" />
<IconComponent className="size-4" />
)}
<div className="flex flex-col">
<span className="font-medium">
@@ -475,7 +475,7 @@ export function ImportProfileDialog({
<SelectItem key={browser} value={browser}>
<div className="flex gap-2 items-center">
{IconComponent && (
<IconComponent className="w-4 h-4" />
<IconComponent className="size-4" />
)}
<span>{getBrowserDisplayName(browser)}</span>
</div>
@@ -507,7 +507,7 @@ export function ImportProfileDialog({
onClick={() => void handleBrowseFolder()}
title={t("importProfile.browseFolderTitle")}
>
<FaFolder className="w-4 h-4" />
<FaFolder className="size-4" />
</Button>
</div>
<p className="mt-2 text-xs text-muted-foreground">
+9 -9
View File
@@ -225,7 +225,7 @@ export function IntegrationsDialog({
</TabsList>
<TabsContent value="api" className="space-y-4 mt-4">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="api-enabled"
checked={apiServerPort !== null}
@@ -251,7 +251,7 @@ export function IntegrationsDialog({
<Label className="text-sm font-medium">
{t("integrations.apiPortLabel")}
</Label>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Button
size="sm"
disabled={
@@ -336,7 +336,7 @@ export function IntegrationsDialog({
<Label className="text-sm font-medium">
{t("integrations.apiTokenLabel")}
</Label>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<div className="relative flex-1">
<Input
type={showApiToken ? "text" : "password"}
@@ -354,9 +354,9 @@ export function IntegrationsDialog({
}}
>
{showApiToken ? (
<EyeOff className="h-4 w-4" />
<EyeOff className="size-4" />
) : (
<Eye className="h-4 w-4" />
<Eye className="size-4" />
)}
</Button>
</div>
@@ -376,7 +376,7 @@ export function IntegrationsDialog({
</TabsContent>
<TabsContent value="mcp" className="space-y-4 mt-4">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="mcp-enabled"
checked={settings.mcp_enabled && mcpConfig !== null}
@@ -407,7 +407,7 @@ export function IntegrationsDialog({
<Label className="text-sm font-medium">
{t("integrations.mcp.url")}
</Label>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<div className="relative flex-1">
<Input
type={showMcpToken ? "text" : "password"}
@@ -425,9 +425,9 @@ export function IntegrationsDialog({
}}
>
{showMcpToken ? (
<EyeOff className="h-4 w-4" />
<EyeOff className="size-4" />
) : (
<Eye className="h-4 w-4" />
<Eye className="size-4" />
)}
</Button>
</div>
+1 -1
View File
@@ -17,7 +17,7 @@ export const LoadingButton = ({ isLoading, className, ...props }: Props) => {
disabled={props.disabled || isLoading}
>
{isLoading ? (
<LuLoaderCircle className="h-4 w-4 animate-spin" />
<LuLoaderCircle className="size-4 animate-spin" />
) : (
props.children
)}
+4 -4
View File
@@ -26,6 +26,10 @@ interface LocationProxyDialogProps {
onClose: () => void;
}
function LoadingSpinner() {
return <Loader2 className="size-4 animate-spin text-muted-foreground" />;
}
export function LocationProxyDialog({
isOpen,
onClose,
@@ -219,10 +223,6 @@ export function LocationProxyDialog({
const cityOptions = cities.map((c) => ({ value: c.code, label: c.name }));
const ispOptions = isps.map((i) => ({ value: i.code, label: i.name }));
const LoadingSpinner = () => (
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
);
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-md">
+1 -1
View File
@@ -434,7 +434,7 @@ const MultipleSelector = React.forwardRef<
handleUnselect(option);
}}
>
<LuX className="w-3 h-3 text-muted-foreground hover:text-foreground" />
<LuX className="size-3 text-muted-foreground hover:text-foreground" />
</button>
</Badge>
);
+3 -3
View File
@@ -131,9 +131,9 @@ export function PermissionDialog({
const getPermissionIcon = (type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="w-8 h-8" />;
return <BsMic className="size-8" />;
case "camera":
return <BsCamera className="w-8 h-8" />;
return <BsCamera className="size-8" />;
}
};
@@ -195,7 +195,7 @@ export function PermissionDialog({
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md">
<DialogHeader className="text-center">
<div className="flex justify-center items-center mx-auto mb-4 w-16 h-16 bg-primary/15 rounded-full">
<div className="flex justify-center items-center mx-auto mb-4 size-16 bg-primary/15 rounded-full">
{getPermissionIcon(permissionType)}
</div>
<DialogTitle className="text-xl">
+38 -40
View File
@@ -350,11 +350,11 @@ function ExtCell({
disabled={isSaving}
className="flex items-center gap-1.5 h-7 px-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded transition-colors duration-100 w-full text-left disabled:opacity-50"
>
<LuPuzzle className="w-3 h-3 shrink-0" />
<LuPuzzle className="size-3 shrink-0" />
<span className="truncate flex-1" title={label}>
{label}
</span>
<LuChevronDown className="w-3 h-3 shrink-0 text-muted-foreground" />
<LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent className="w-56 p-0" align="start">
@@ -369,7 +369,7 @@ function ExtCell({
void onPick(null);
}}
>
{groupId === null && <LuCheck className="mr-2 w-3.5 h-3.5" />}
{groupId === null && <LuCheck className="mr-2 size-3.5" />}
<span className={groupId === null ? "" : "ml-5"}>
{meta.t("profiles.table.extDefault")}
</span>
@@ -382,7 +382,7 @@ function ExtCell({
void onPick(g.id);
}}
>
{groupId === g.id && <LuCheck className="mr-2 w-3.5 h-3.5" />}
{groupId === g.id && <LuCheck className="mr-2 size-3.5" />}
<span className={groupId === g.id ? "" : "ml-5"}>
{g.name}
</span>
@@ -445,11 +445,11 @@ function DnsCell({
: meta.t("dnsBlocklist.none")
}
>
<FiWifi className="w-3 h-3 shrink-0" />
<FiWifi className="size-3 shrink-0" />
<span className="flex-1 truncate uppercase text-[10px] font-mono tracking-wide">
{level ?? "—"}
</span>
<LuChevronDown className="w-3 h-3 shrink-0 text-muted-foreground" />
<LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent className="w-48 p-0" align="start">
@@ -462,7 +462,7 @@ function DnsCell({
void onPick(null);
}}
>
{level === null && <LuCheck className="mr-2 w-3.5 h-3.5" />}
{level === null && <LuCheck className="mr-2 size-3.5" />}
<span className={level === null ? "" : "ml-5"}>
{meta.t("dnsBlocklist.none")}
</span>
@@ -475,9 +475,7 @@ function DnsCell({
void onPick(l.value);
}}
>
{level === l.value && (
<LuCheck className="mr-2 w-3.5 h-3.5" />
)}
{level === l.value && <LuCheck className="mr-2 size-3.5" />}
<span className={level === l.value ? "" : "ml-5"}>
{meta.t(l.labelKey)}
</span>
@@ -1960,7 +1958,7 @@ export function ProfilesDataTable({
return (
<Tooltip>
<TooltipTrigger asChild>
<span className="flex justify-center items-center w-4 h-4">
<span className="flex justify-center items-center size-4">
<button
type="button"
className="flex justify-center items-center p-0 border-none cursor-pointer"
@@ -1969,9 +1967,9 @@ export function ProfilesDataTable({
}}
aria-label={t("common.aria.selectProfile")}
>
<span className="w-4 h-4 group">
<OsIcon className="w-4 h-4 text-muted-foreground group-hover:hidden" />
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none w-4 h-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
<span className="size-4 group">
<OsIcon className="size-4 text-muted-foreground group-hover:hidden" />
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none size-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
</span>
</button>
</span>
@@ -1999,14 +1997,14 @@ export function ProfilesDataTable({
sideOffset={4}
horizontalOffset={8}
>
<span className="flex justify-center items-center w-4 h-4">
<span className="flex justify-center items-center size-4">
<Checkbox
checked={isSelected}
onCheckedChange={(value) => {
meta.handleCheckboxChange(profile.id, !!value);
}}
aria-label={t("common.aria.selectRow")}
className="w-4 h-4"
className="size-4"
/>
</span>
</NonHoverableTooltip>
@@ -2025,9 +2023,9 @@ export function ProfilesDataTable({
return (
<Tooltip>
<TooltipTrigger asChild>
<span className="flex justify-center items-center w-4 h-4 cursor-not-allowed">
<span className="flex justify-center items-center size-4 cursor-not-allowed">
{IconComponent && (
<IconComponent className="w-4 h-4 opacity-50" />
<IconComponent className="size-4 opacity-50" />
)}
</span>
</TooltipTrigger>
@@ -2047,14 +2045,14 @@ export function ProfilesDataTable({
sideOffset={4}
horizontalOffset={8}
>
<span className="flex justify-center items-center w-4 h-4">
<span className="flex justify-center items-center size-4">
<Checkbox
checked={isSelected}
onCheckedChange={(value) => {
meta.handleCheckboxChange(profile.id, !!value);
}}
aria-label={t("common.aria.selectRow")}
className="w-4 h-4"
className="size-4"
/>
</span>
</NonHoverableTooltip>
@@ -2067,7 +2065,7 @@ export function ProfilesDataTable({
sideOffset={4}
horizontalOffset={8}
>
<span className="flex relative justify-center items-center w-4 h-4">
<span className="flex relative justify-center items-center size-4">
<button
type="button"
className="flex justify-center items-center p-0 border-none cursor-pointer"
@@ -2076,11 +2074,11 @@ export function ProfilesDataTable({
}}
aria-label={t("common.aria.selectProfile")}
>
<span className="w-4 h-4 group">
<span className="size-4 group">
{IconComponent && (
<IconComponent className="w-4 h-4 group-hover:hidden" />
<IconComponent className="size-4 group-hover:hidden" />
)}
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none w-4 h-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
<span className="peer border-input dark:bg-input/30 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none size-4 hidden group-hover:block pointer-events-none items-center justify-center duration-150" />
</span>
</button>
</span>
@@ -2194,7 +2192,7 @@ export function ProfilesDataTable({
<Tooltip>
<TooltipTrigger asChild>
<span>
<LuTriangleAlert className="w-4 h-4 text-warning" />
<LuTriangleAlert className="size-4 text-warning" />
</span>
</TooltipTrigger>
<TooltipContent>
@@ -2217,7 +2215,7 @@ export function ProfilesDataTable({
: meta.t("profiles.actions.launch")
}
className={cn(
"h-7 w-7 p-0 grid place-items-center",
"size-7 p-0 grid place-items-center",
!canLaunch && "opacity-50 cursor-not-allowed",
canLaunch && "cursor-pointer",
isFollower && "border-accent",
@@ -2231,11 +2229,11 @@ export function ProfilesDataTable({
}
>
{isLaunching || isStopping ? (
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
) : isRunning ? (
<LuSquare className="w-3.5 h-3.5 fill-current" />
<LuSquare className="size-3.5 fill-current" />
) : (
<LuPlay className="w-3.5 h-3.5 fill-current" />
<LuPlay className="size-3.5 fill-current" />
)}
</RippleButton>
</span>
@@ -2265,9 +2263,9 @@ export function ProfilesDataTable({
>
{meta.t("common.labels.name")}
{column.getIsSorted() === "asc" ? (
<LuChevronUp className="ml-2 w-4 h-4" />
<LuChevronUp className="ml-2 size-4" />
) : column.getIsSorted() === "desc" ? (
<LuChevronDown className="ml-2 w-4 h-4" />
<LuChevronDown className="ml-2 size-4" />
) : null}
</Button>
);
@@ -2382,7 +2380,7 @@ export function ProfilesDataTable({
<Tooltip>
<TooltipTrigger asChild>
<span>
<LuLock className="w-3 h-3 text-muted-foreground" />
<LuLock className="size-3 text-muted-foreground" />
</span>
</TooltipTrigger>
<TooltipContent>
@@ -2606,7 +2604,7 @@ export function ProfilesDataTable({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedId === null
? "opacity-100"
: "opacity-0",
@@ -2633,7 +2631,7 @@ export function ProfilesDataTable({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
effectiveProxyId === proxy.id &&
!effectiveVpn
? "opacity-100"
@@ -2659,7 +2657,7 @@ export function ProfilesDataTable({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
effectiveVpnId === vpn.id
? "opacity-100"
: "opacity-0",
@@ -2701,7 +2699,7 @@ export function ProfilesDataTable({
)
}
>
<span className="mr-2 h-4 w-4" />+{" "}
<span className="mr-2 size-4" />+{" "}
{country.name}
</CommandItem>
))}
@@ -2793,11 +2791,11 @@ export function ProfilesDataTable({
<span className="flex justify-center items-center h-9 w-full">
{dot.encrypted ? (
<LuLock
className={`w-3 h-3 ${dot.color.replace("bg-", "text-")}${dot.animate ? " animate-pulse" : ""}`}
className={`size-3 ${dot.color.replace("bg-", "text-")}${dot.animate ? " animate-pulse" : ""}`}
/>
) : (
<span
className={`w-2 h-2 rounded-full ${dot.color}${dot.animate ? " animate-pulse" : ""}`}
className={`size-2 rounded-full ${dot.color}${dot.animate ? " animate-pulse" : ""}`}
/>
)}
</span>
@@ -2818,7 +2816,7 @@ export function ProfilesDataTable({
<div className="flex justify-end items-center h-9 w-full">
<Button
variant="ghost"
className="p-0 w-7 h-7"
className="p-0 size-7"
disabled={!meta.isClient}
onClick={() => {
setProfileForInfoDialog(profile);
@@ -2827,7 +2825,7 @@ export function ProfilesDataTable({
<span className="sr-only">
{t("profiles.aria.profileInfo")}
</span>
<LuInfo className="w-4 h-4" />
<LuInfo className="size-4" />
</Button>
</div>
);
+48 -48
View File
@@ -97,11 +97,11 @@ interface ProfileInfoDialogProps {
function OSIcon({ os }: { os: string }) {
switch (os) {
case "macos":
return <FaApple className="w-3.5 h-3.5" />;
return <FaApple className="size-3.5" />;
case "windows":
return <FaWindows className="w-3.5 h-3.5" />;
return <FaWindows className="size-3.5" />;
case "linux":
return <FaLinux className="w-3.5 h-3.5" />;
return <FaLinux className="size-3.5" />;
default:
return null;
}
@@ -317,7 +317,7 @@ export function ProfileInfoDialog({
const actions: ActionItem[] = [
{
icon: <LuGlobe className="w-4 h-4" />,
icon: <LuGlobe className="size-4" />,
label: t("profiles.actions.viewNetwork"),
onClick: () => {
handleAction(() => onOpenTrafficDialog?.(profile.id));
@@ -325,7 +325,7 @@ export function ProfileInfoDialog({
disabled: isCrossOs,
},
{
icon: <LuRefreshCw className="w-4 h-4" />,
icon: <LuRefreshCw className="size-4" />,
label: t("profiles.actions.syncSettings"),
onClick: () => {
handleAction(() => onOpenProfileSyncDialog?.(profile));
@@ -334,7 +334,7 @@ export function ProfileInfoDialog({
hidden: profile.ephemeral === true,
},
{
icon: <LuGroup className="w-4 h-4" />,
icon: <LuGroup className="size-4" />,
label: t("profiles.actions.assignToGroup"),
onClick: () => {
handleAction(() => onAssignProfilesToGroup?.([profile.id]));
@@ -343,7 +343,7 @@ export function ProfileInfoDialog({
runningBadge: isRunning,
},
{
icon: <LuFingerprint className="w-4 h-4" />,
icon: <LuFingerprint className="size-4" />,
label: t("profiles.actions.changeFingerprint"),
onClick: () => {
handleAction(() => onConfigureCamoufox?.(profile));
@@ -353,7 +353,7 @@ export function ProfileInfoDialog({
hidden: !isCamoufoxOrWayfern || !onConfigureCamoufox,
},
{
icon: <LuUsers className="w-4 h-4" />,
icon: <LuUsers className="size-4" />,
label: t("profiles.synchronizer.launchWithSync"),
onClick: () => {
handleAction(() => onLaunchWithSync?.(profile));
@@ -363,7 +363,7 @@ export function ProfileInfoDialog({
hidden: profile.browser !== "wayfern" || !onLaunchWithSync,
},
{
icon: <LuCopy className="w-4 h-4" />,
icon: <LuCopy className="size-4" />,
label: t("profiles.actions.copyCookiesToProfile"),
onClick: () => {
handleAction(() => onCopyCookiesToProfile?.(profile));
@@ -376,7 +376,7 @@ export function ProfileInfoDialog({
!onCopyCookiesToProfile,
},
{
icon: <LuCookie className="w-4 h-4" />,
icon: <LuCookie className="size-4" />,
label: t("profileInfo.actions.manageCookies"),
onClick: () => {
handleAction(() => onOpenCookieManagement?.(profile));
@@ -389,7 +389,7 @@ export function ProfileInfoDialog({
!onOpenCookieManagement,
},
{
icon: <LuSettings className="w-4 h-4" />,
icon: <LuSettings className="size-4" />,
label: t("profiles.actions.clone"),
onClick: () => {
handleAction(() => onCloneProfile?.(profile));
@@ -399,7 +399,7 @@ export function ProfileInfoDialog({
hidden: profile.ephemeral === true,
},
{
icon: <LuPuzzle className="w-4 h-4" />,
icon: <LuPuzzle className="size-4" />,
label: t("profileInfo.actions.assignExtensionGroup"),
onClick: () => {
handleAction(() => onAssignExtensionGroup?.([profile.id]));
@@ -409,21 +409,21 @@ export function ProfileInfoDialog({
hidden: profile.ephemeral === true,
},
{
icon: <LuShieldCheck className="w-4 h-4" />,
icon: <LuShieldCheck className="size-4" />,
label: t("profileInfo.network.bypassRulesTitle"),
onClick: () => {
handleAction(() => onOpenBypassRules?.(profile));
},
},
{
icon: <LuShield className="w-4 h-4" />,
icon: <LuShield className="size-4" />,
label: t("dnsBlocklist.title"),
onClick: () => {
handleAction(() => onOpenDnsBlocklist?.(profile));
},
},
{
icon: <LuLink className="w-4 h-4" />,
icon: <LuLink className="size-4" />,
label: t("profiles.actions.launchHook"),
onClick: () => {
handleAction(() => onOpenLaunchHook?.(profile));
@@ -431,7 +431,7 @@ export function ProfileInfoDialog({
hidden: !onOpenLaunchHook,
},
{
icon: <LuKey className="w-4 h-4" />,
icon: <LuKey className="size-4" />,
label: t("profiles.actions.setPassword"),
onClick: () => {
handleAction(() => onSetPassword?.(profile));
@@ -444,7 +444,7 @@ export function ProfileInfoDialog({
!onSetPassword,
},
{
icon: <LuKey className="w-4 h-4" />,
icon: <LuKey className="size-4" />,
label: t("profiles.actions.changePassword"),
onClick: () => {
handleAction(() => onChangePassword?.(profile));
@@ -454,7 +454,7 @@ export function ProfileInfoDialog({
hidden: profile.password_protected !== true || !onChangePassword,
},
{
icon: <LuLockOpen className="w-4 h-4" />,
icon: <LuLockOpen className="size-4" />,
label: t("profiles.actions.removePassword"),
onClick: () => {
handleAction(() => onRemovePassword?.(profile));
@@ -465,7 +465,7 @@ export function ProfileInfoDialog({
destructive: true,
},
{
icon: <LuTrash2 className="w-4 h-4" />,
icon: <LuTrash2 className="size-4" />,
label: t("profiles.actions.delete"),
onClick: () => {
handleAction(() => onDeleteProfile?.(profile));
@@ -646,12 +646,12 @@ function ProfileInfoLayout({
}[] = [
{
id: "overview",
icon: <LuClipboard className="w-3.5 h-3.5" />,
icon: <LuClipboard className="size-3.5" />,
label: t("profileInfo.sections.overview"),
},
{
id: "fingerprint",
icon: <LuFingerprint className="w-3.5 h-3.5" />,
icon: <LuFingerprint className="size-3.5" />,
label: t("profileInfo.sections.fingerprint"),
badge: profile.password_protected
? t("profileInfo.badges.locked")
@@ -660,13 +660,13 @@ function ProfileInfoLayout({
},
{
id: "network",
icon: <LuGlobe className="w-3.5 h-3.5" />,
icon: <LuGlobe className="size-3.5" />,
label: t("profileInfo.sections.network"),
badge: profile.proxy_id || profile.vpn_id ? networkLabel : undefined,
},
{
id: "cookies",
icon: <LuCookie className="w-3.5 h-3.5" />,
icon: <LuCookie className="size-3.5" />,
label: t("profileInfo.sections.cookies"),
badge:
cookieCount !== null && cookieCount > 0
@@ -676,26 +676,26 @@ function ProfileInfoLayout({
},
{
id: "extensions",
icon: <LuPuzzle className="w-3.5 h-3.5" />,
icon: <LuPuzzle className="size-3.5" />,
label: t("profileInfo.sections.extensions"),
badge: extensionGroupName ?? undefined,
hidden: !extensionAction,
},
{
id: "sync",
icon: <LuRefreshCw className="w-3.5 h-3.5" />,
icon: <LuRefreshCw className="size-3.5" />,
label: t("profileInfo.sections.sync"),
hidden: !syncAction,
},
{
id: "automation",
icon: <LuLink className="w-3.5 h-3.5" />,
icon: <LuLink className="size-3.5" />,
label: t("profileInfo.sections.launchHook"),
badge: profile.launch_hook ? t("profileInfo.badges.active") : undefined,
},
{
id: "security",
icon: <LuKey className="w-3.5 h-3.5" />,
icon: <LuKey className="size-3.5" />,
label: t("profileInfo.sections.security"),
},
];
@@ -704,7 +704,7 @@ function ProfileInfoLayout({
<>
{/* Top bar */}
<div className="flex items-center gap-2 h-11 px-3 border-b border-border shrink-0">
<LuUsers className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
<LuUsers className="size-3.5 text-muted-foreground shrink-0" />
<div className="flex items-center gap-1.5 text-xs min-w-0 flex-1">
<span className="font-semibold">
{t("profileInfo.breadcrumbRoot")}
@@ -720,7 +720,7 @@ function ProfileInfoLayout({
disabled={isDisabled}
onClick={() => onCloneProfile(profile)}
>
<LuCopy className="w-3 h-3" />
<LuCopy className="size-3" />
{t("profileInfo.duplicate")}
</Button>
)}
@@ -728,9 +728,9 @@ function ProfileInfoLayout({
type="button"
aria-label={t("common.buttons.close")}
onClick={onClose}
className="grid place-items-center w-7 h-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors duration-100"
className="grid place-items-center size-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors duration-100"
>
<LuX className="w-3.5 h-3.5" />
<LuX className="size-3.5" />
</button>
</div>
@@ -773,7 +773,7 @@ function ProfileInfoLayout({
disabled={deleteAction.disabled}
className="flex items-center gap-2 h-7 px-2 rounded-md text-xs transition-colors duration-100 text-destructive hover:bg-destructive/10 disabled:opacity-50 disabled:pointer-events-none"
>
<LuTrash2 className="w-3.5 h-3.5 shrink-0" />
<LuTrash2 className="size-3.5 shrink-0" />
<span className="flex-1 text-left">
{t("profileInfo.sections.delete")}
</span>
@@ -789,7 +789,7 @@ function ProfileInfoLayout({
{/* Hero */}
<div className="flex items-center gap-3">
<div className="rounded-lg bg-muted p-2.5 shrink-0">
<ProfileIcon className="w-7 h-7 text-foreground" />
<ProfileIcon className="size-7 text-foreground" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
@@ -809,7 +809,7 @@ function ProfileInfoLayout({
<>
<span className="text-muted-foreground">·</span>
<span className="inline-flex items-center gap-1 text-success">
<span className="w-1.5 h-1.5 rounded-full bg-success" />
<span className="size-1.5 rounded-full bg-success" />
{t("common.status.running")}
</span>
</>
@@ -826,7 +826,7 @@ function ProfileInfoLayout({
<>
<span className="text-muted-foreground">·</span>
<span className="inline-flex items-center gap-1 text-muted-foreground">
<LuLock className="w-3 h-3" />
<LuLock className="size-3" />
{t("profiles.passwordProtectedBadge")}
</span>
</>
@@ -875,9 +875,9 @@ function ProfileInfoLayout({
aria-label={t("common.buttons.copy")}
>
{copied ? (
<LuClipboardCheck className="w-3.5 h-3.5" />
<LuClipboardCheck className="size-3.5" />
) : (
<LuClipboard className="w-3.5 h-3.5" />
<LuClipboard className="size-3.5" />
)}
</button>
</div>
@@ -1082,7 +1082,7 @@ function _SectionAction({
>
{icon}
<span className="flex-1">{label}</span>
<LuChevronRight className="w-3.5 h-3.5 text-muted-foreground" />
<LuChevronRight className="size-3.5 text-muted-foreground" />
</button>
);
}
@@ -1132,7 +1132,7 @@ function LaunchHookEditor({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuLink className="w-4 h-4" />
<LuLink className="size-4" />
{t("profileInfo.sections.launchHook")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1216,7 +1216,7 @@ function SyncSectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuRefreshCw className="w-4 h-4" />
<LuRefreshCw className="size-4" />
{t("profileInfo.sections.sync")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1331,7 +1331,7 @@ function NetworkSectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuGlobe className="w-4 h-4" />
<LuGlobe className="size-4" />
{t("profileInfo.sections.network")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1464,7 +1464,7 @@ function ExtensionsSectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuPuzzle className="w-4 h-4" />
<LuPuzzle className="size-4" />
{t("profileInfo.sections.extensions")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1553,7 +1553,7 @@ function CookiesSectionInline({
return (
<div className="flex flex-col gap-3 min-h-0 flex-1">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuCookie className="w-4 h-4" />
<LuCookie className="size-4" />
{t("profileInfo.sections.cookies")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1651,7 +1651,7 @@ function FingerprintSectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuFingerprint className="w-4 h-4" />
<LuFingerprint className="size-4" />
{t("profileInfo.sections.fingerprint")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1705,7 +1705,7 @@ function FingerprintSectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuFingerprint className="w-4 h-4" />
<LuFingerprint className="size-4" />
{t("profileInfo.sections.fingerprint")}
</div>
<p className="text-xs text-muted-foreground">
@@ -1863,7 +1863,7 @@ function SecuritySectionInline({
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm font-semibold">
<LuKey className="w-4 h-4" />
<LuKey className="size-4" />
{t("profileInfo.sections.security")}
</div>
<p className="text-xs text-muted-foreground">
@@ -2226,7 +2226,7 @@ export function ProfileBypassRulesDialog({
onClick={handleAddRule}
disabled={!newRule.trim()}
>
<LuPlus className="w-4 h-4 mr-1" />
<LuPlus className="size-4 mr-1" />
{t("profileInfo.network.addRule")}
</Button>
</div>
@@ -2249,7 +2249,7 @@ export function ProfileBypassRulesDialog({
}}
className="text-muted-foreground hover:text-destructive transition-colors shrink-0"
>
<LuX className="w-3.5 h-3.5" />
<LuX className="size-3.5" />
</button>
</div>
))}
+10 -8
View File
@@ -53,14 +53,16 @@ export function ProfilePasswordDialog({
const firstInputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (isOpen) {
setOldPassword("");
setPassword("");
setConfirm("");
setIsSubmitting(false);
setLockoutSecondsRemaining(null);
setTimeout(() => firstInputRef.current?.focus(), 0);
}
if (!isOpen) return;
setOldPassword("");
setPassword("");
setConfirm("");
setIsSubmitting(false);
setLockoutSecondsRemaining(null);
const handle = window.setTimeout(() => firstInputRef.current?.focus(), 0);
return () => {
window.clearTimeout(handle);
};
}, [isOpen]);
// Tick down the lockout timer
+1 -1
View File
@@ -237,7 +237,7 @@ export function ProfileSelectorDialog({
profile.browser,
);
return IconComponent ? (
<IconComponent className="w-4 h-4" />
<IconComponent className="size-4" />
) : null;
})()}
</div>
+4 -4
View File
@@ -188,7 +188,7 @@ export function ProfileSyncDialog({
{isCheckingConfig ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="grid gap-4 py-4">
@@ -216,7 +216,7 @@ export function ProfileSyncDialog({
disabled={isSaving}
className="grid gap-3"
>
<div className="flex items-start space-x-3">
<div className="flex items-start gap-x-3">
<RadioGroupItem value="Disabled" id="sync-disabled" />
<Label htmlFor="sync-disabled" className="cursor-pointer">
<span className="font-medium">
@@ -228,7 +228,7 @@ export function ProfileSyncDialog({
</Label>
</div>
<div className="flex items-start space-x-3">
<div className="flex items-start gap-x-3">
<RadioGroupItem value="Regular" id="sync-regular" />
<Label htmlFor="sync-regular" className="cursor-pointer">
<span className="font-medium">
@@ -240,7 +240,7 @@ export function ProfileSyncDialog({
</Label>
</div>
<div className="flex items-start space-x-3">
<div className="flex items-start gap-x-3">
<RadioGroupItem
value="Encrypted"
id="sync-encrypted"
+12 -6
View File
@@ -2,7 +2,7 @@
import { invoke } from "@tauri-apps/api/core";
import { emit } from "@tauri-apps/api/event";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useId, useState } from "react";
import { useTranslation } from "react-i18next";
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
import { toast } from "sonner";
@@ -55,6 +55,7 @@ export function ProxyAssignmentDialog({
vpnConfigs = [],
}: ProxyAssignmentDialogProps) {
const { t } = useTranslation();
const proxyListboxId = useId();
const [selectedId, setSelectedId] = useState<string | null>(null);
const [selectionType, setSelectionType] = useState<"none" | "proxy" | "vpn">(
"none",
@@ -183,6 +184,7 @@ export function ProxyAssignmentDialog({
variant="outline"
role="combobox"
aria-expanded={proxyPopoverOpen}
aria-controls={proxyListboxId}
className="w-full justify-between font-normal"
>
{(() => {
@@ -199,10 +201,14 @@ export function ProxyAssignmentDialog({
);
return proxy ? proxy.name : t("proxyAssignment.noneOption");
})()}
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[240px] p-0" sideOffset={8}>
<PopoverContent
id={proxyListboxId}
className="w-[240px] p-0"
sideOffset={8}
>
<Command>
<CommandInput
placeholder={t("proxyAssignment.searchPlaceholder")}
@@ -219,7 +225,7 @@ export function ProxyAssignmentDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectionType === "none"
? "opacity-100"
: "opacity-0",
@@ -243,7 +249,7 @@ export function ProxyAssignmentDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectionType === "proxy" &&
selectedId === proxy.id
? "opacity-100"
@@ -269,7 +275,7 @@ export function ProxyAssignmentDialog({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectionType === "vpn" && selectedId === vpn.id
? "opacity-100"
: "opacity-0",
+3 -3
View File
@@ -118,12 +118,12 @@ export function ProxyCheckButton({
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0"
className="size-7 p-0"
onClick={handleCheck}
disabled={isCurrentlyChecking || disabled}
>
{isCurrentlyChecking ? (
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
) : result?.is_valid && result.country_code ? (
<span className="relative inline-flex items-center justify-center">
<FlagIcon countryCode={result.country_code} className="h-2.5" />
@@ -132,7 +132,7 @@ export function ProxyCheckButton({
) : result && !result.is_valid ? (
<span className="text-destructive text-sm"></span>
) : (
<FiCheck className="w-3 h-3" />
<FiCheck className="size-3" />
)}
</Button>
</TooltipTrigger>
+5 -5
View File
@@ -108,13 +108,13 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
}}
className="flex gap-4"
>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<RadioGroupItem value="json" id="format-json" />
<Label htmlFor="format-json" className="cursor-pointer">
{t("proxies.exportDialog.json")}
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<RadioGroupItem value="txt" id="format-txt" />
<Label htmlFor="format-txt" className="cursor-pointer">
{t("proxies.exportDialog.txt")}
@@ -154,9 +154,9 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
className="flex gap-2 items-center"
>
{copied ? (
<LuCheck className="w-4 h-4" />
<LuCheck className="size-4" />
) : (
<LuCopy className="w-4 h-4" />
<LuCopy className="size-4" />
)}
{copied
? t("proxies.exportDialog.copied")
@@ -167,7 +167,7 @@ export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
disabled={!exportContent || isLoading}
className="flex gap-2 items-center"
>
<LuDownload className="w-4 h-4" />
<LuDownload className="size-4" />
{t("common.buttons.download")}
</RippleButton>
</DialogFooter>
+1 -1
View File
@@ -315,7 +315,7 @@ export function ProxyImportDialog({ isOpen, onClose }: ProxyImportDialogProps) {
}
}}
>
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
<LuUpload className="size-10 text-muted-foreground mb-4" />
<p className="text-sm text-muted-foreground text-center">
{t("proxies.importDialog.dropzonePrompt")}
<br />
+11 -11
View File
@@ -451,7 +451,7 @@ export function ProxyManagementDialog({
}}
className="flex gap-2 items-center"
>
<LuUpload className="w-4 h-4" />
<LuUpload className="size-4" />
{t("common.buttons.import")}
</RippleButton>
<RippleButton
@@ -463,7 +463,7 @@ export function ProxyManagementDialog({
className="flex gap-2 items-center"
disabled={storedProxies.length === 0}
>
<LuDownload className="w-4 h-4" />
<LuDownload className="size-4" />
{t("common.buttons.export")}
</RippleButton>
</div>
@@ -473,7 +473,7 @@ export function ProxyManagementDialog({
onClick={handleCreateProxy}
className="flex gap-2 items-center"
>
<GoPlus className="w-4 h-4" />
<GoPlus className="size-4" />
{t("proxies.management.create")}
</RippleButton>
</div>
@@ -519,7 +519,7 @@ export function ProxyManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
syncDot.animate
? "animate-pulse"
: ""
@@ -605,7 +605,7 @@ export function ProxyManagementDialog({
handleEditProxy(proxy);
}}
>
<LuPencil className="w-4 h-4" />
<LuPencil className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -627,7 +627,7 @@ export function ProxyManagementDialog({
(proxyUsage[proxy.id] ?? 0) > 0
}
>
<LuTrash2 className="w-4 h-4" />
<LuTrash2 className="size-4" />
</Button>
</span>
</TooltipTrigger>
@@ -681,7 +681,7 @@ export function ProxyManagementDialog({
}}
className="flex gap-2 items-center"
>
<LuUpload className="w-4 h-4" />
<LuUpload className="size-4" />
{t("common.buttons.import")}
</RippleButton>
</div>
@@ -690,7 +690,7 @@ export function ProxyManagementDialog({
onClick={handleCreateVpn}
className="flex gap-2 items-center"
>
<GoPlus className="w-4 h-4" />
<GoPlus className="size-4" />
{t("proxies.management.create")}
</RippleButton>
</div>
@@ -738,7 +738,7 @@ export function ProxyManagementDialog({
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-2 h-2 rounded-full shrink-0 ${syncDot.color} ${
className={`size-2 rounded-full shrink-0 ${syncDot.color} ${
syncDot.animate
? "animate-pulse"
: ""
@@ -814,7 +814,7 @@ export function ProxyManagementDialog({
handleEditVpn(vpn);
}}
>
<LuPencil className="w-4 h-4" />
<LuPencil className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
@@ -834,7 +834,7 @@ export function ProxyManagementDialog({
(vpnUsage[vpn.id] ?? 0) > 0
}
>
<LuTrash2 className="w-4 h-4" />
<LuTrash2 className="size-4" />
</Button>
</span>
</TooltipTrigger>
+11 -11
View File
@@ -290,7 +290,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
ref={logoRef}
type="button"
aria-label={t("header.donutLogo")}
className="grid place-items-center w-7 h-7 rounded-md cursor-pointer select-none text-foreground bg-transparent"
className="grid place-items-center size-7 rounded-md cursor-pointer select-none text-foreground bg-transparent"
onClick={handleClick}
onPointerDown={() => {
setIsPressed(true);
@@ -322,12 +322,12 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
"animate-[wiggle_0.3s_ease-in-out]",
)}
>
<Logo className="w-5 h-5 will-change-transform" />
<Logo className="size-5 will-change-transform" />
</span>
</span>
</button>
) : (
<div className="w-7 h-7" />
<div className="size-7" />
)}
<div className="w-5 h-px bg-border my-1" />
@@ -345,7 +345,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
aria-label={t(labelKey)}
aria-current={active ? "page" : undefined}
className={cn(
"relative grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
"relative grid place-items-center size-7 rounded-md transition-colors duration-100",
active
? "text-foreground bg-accent"
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
@@ -357,7 +357,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
className="absolute left-[-7px] top-1.5 bottom-1.5 w-[2px] rounded-full bg-foreground"
/>
)}
<Icon className="w-3.5 h-3.5" />
<Icon className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="right">{t(labelKey)}</TooltipContent>
@@ -377,13 +377,13 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
aria-label={t("rail.more.label")}
aria-expanded={moreOpen}
className={cn(
"grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
"grid place-items-center size-7 rounded-md transition-colors duration-100",
moreOpen
? "text-foreground bg-accent"
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
)}
>
<GoKebabHorizontal className="w-3.5 h-3.5" />
<GoKebabHorizontal className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="right">{t("rail.more.label")}</TooltipContent>
@@ -399,7 +399,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
aria-label={t("rail.settings")}
aria-current={currentPage === "settings" ? "page" : undefined}
className={cn(
"relative grid place-items-center w-7 h-7 rounded-md transition-colors duration-100",
"relative grid place-items-center size-7 rounded-md transition-colors duration-100",
currentPage === "settings"
? "text-foreground bg-accent"
: "text-muted-foreground hover:text-card-foreground hover:bg-accent/50",
@@ -411,7 +411,7 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
className="absolute left-[-7px] top-1.5 bottom-1.5 w-[2px] rounded-full bg-foreground"
/>
)}
<GoGear className="w-3.5 h-3.5" />
<GoGear className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="right">{t("rail.settings")}</TooltipContent>
@@ -438,8 +438,8 @@ export function RailNav({ currentPage, onNavigate }: RailNavProps) {
}}
className="flex items-center gap-2 w-full px-2 py-1.5 rounded-md hover:bg-accent transition-colors duration-100 text-left"
>
<span className="grid place-items-center w-5 h-5 rounded bg-muted text-muted-foreground shrink-0">
<Icon className="w-3 h-3" />
<span className="grid place-items-center size-5 rounded bg-muted text-muted-foreground shrink-0">
<Icon className="size-3" />
</span>
<span className="flex flex-col min-w-0">
<span className="text-xs font-medium text-foreground truncate">
+7 -5
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useId, useState } from "react";
import { useTranslation } from "react-i18next";
import { LuCheck, LuChevronsUpDown, LuDownload } from "react-icons/lu";
import { LoadingButton } from "@/components/loading-button";
@@ -44,6 +44,7 @@ export function ReleaseTypeSelector({
}: ReleaseTypeSelectorProps) {
const { t } = useTranslation();
const [popoverOpen, setPopoverOpen] = useState(false);
const listboxId = useId();
const effectivePlaceholder =
placeholder ?? t("releaseTypeSelector.placeholder");
@@ -91,13 +92,14 @@ export function ReleaseTypeSelector({
variant="outline"
role="combobox"
aria-expanded={popoverOpen}
aria-controls={listboxId}
className="justify-between w-full"
>
{selectedDisplayText}
<LuChevronsUpDown className="ml-2 w-4 h-4 opacity-50 shrink-0" />
<LuChevronsUpDown className="ml-2 size-4 opacity-50 shrink-0" />
</RippleButton>
</PopoverTrigger>
<PopoverContent className="p-0">
<PopoverContent id={listboxId} className="p-0">
<Command>
<CommandEmpty>
{t("releaseTypeSelector.noReleaseTypes")}
@@ -126,7 +128,7 @@ export function ReleaseTypeSelector({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
selectedReleaseType === option.type
? "opacity-100"
: "opacity-0",
@@ -187,7 +189,7 @@ export function ReleaseTypeSelector({
variant="outline"
className="w-full"
>
<LuDownload className="mr-2 w-4 h-4" />
<LuDownload className="mr-2 size-4" />
{isDownloading
? t("releaseTypeSelector.downloading")
: t("releaseTypeSelector.downloadBrowser")}
+7 -7
View File
@@ -165,9 +165,9 @@ export function SettingsDialog({
const getPermissionIcon = useCallback((type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="w-4 h-4" />;
return <BsMic className="size-4" />;
case "camera":
return <BsCamera className="w-4 h-4" />;
return <BsCamera className="size-4" />;
}
}, []);
@@ -738,7 +738,7 @@ export function SettingsDialog({
<button
type="button"
aria-label={label}
className="w-8 h-8 rounded-md border shadow-sm cursor-pointer"
className="size-8 rounded-md border shadow-sm cursor-pointer"
style={{ backgroundColor: colorValue }}
/>
</PopoverTrigger>
@@ -887,7 +887,7 @@ export function SettingsDialog({
key={permission.permission_type}
className="flex justify-between items-center p-3 rounded-lg border"
>
<div className="flex items-center space-x-3">
<div className="flex items-center gap-x-3">
{getPermissionIcon(permission.permission_type)}
<div>
<div className="text-sm font-medium">
@@ -900,7 +900,7 @@ export function SettingsDialog({
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
{getStatusBadge(permission.isGranted)}
{!permission.isGranted && (
<LoadingButton
@@ -1170,7 +1170,7 @@ export function SettingsDialog({
</Label>
{!isLinux && (
<div className="flex items-start space-x-3 p-3 rounded-lg border">
<div className="flex items-start gap-x-3 p-3 rounded-lg border">
<Checkbox
id="disable-auto-updates"
checked={settings.disable_auto_updates ?? false}
@@ -1192,7 +1192,7 @@ export function SettingsDialog({
</div>
)}
<div className="flex items-start space-x-3 p-3 rounded-lg border">
<div className="flex items-start gap-x-3 p-3 rounded-lg border">
<Checkbox
id="keep-decrypted-profiles-in-ram"
checked={settings.keep_decrypted_profiles_in_ram ?? false}
@@ -303,7 +303,7 @@ export function SharedCamoufoxConfigForm({
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="randomize-fingerprint"
checked={config.randomize_fingerprint_on_launch ?? false}
@@ -323,7 +323,7 @@ export function SharedCamoufoxConfigForm({
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="auto-location-advanced"
checked={isAutoLocationEnabled}
@@ -367,7 +367,7 @@ export function SharedCamoufoxConfigForm({
<div className="space-y-3">
<Label>{t("fingerprint.blockingOptions")}</Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="block-images"
checked={config.block_images ?? false}
@@ -379,7 +379,7 @@ export function SharedCamoufoxConfigForm({
{t("fingerprint.blockImages")}
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="block-webrtc"
checked={config.block_webrtc ?? false}
@@ -391,7 +391,7 @@ export function SharedCamoufoxConfigForm({
{t("fingerprint.blockWebRTC")}
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="block-webgl"
checked={config.block_webgl ?? false}
@@ -1025,7 +1025,7 @@ export function SharedCamoufoxConfigForm({
<Label>{t("fingerprint.battery")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="battery-charging"
checked={fingerprintConfig["battery:charging"] ?? false}
@@ -1176,7 +1176,7 @@ export function SharedCamoufoxConfigForm({
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="randomize-fingerprint-auto"
checked={config.randomize_fingerprint_on_launch ?? false}
@@ -1199,7 +1199,7 @@ export function SharedCamoufoxConfigForm({
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="auto-location"
checked={isAutoLocationEnabled}
+1 -1
View File
@@ -96,7 +96,7 @@ export function SyncAllDialog({ isOpen, onClose }: SyncAllDialogProps) {
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="py-4">
+8 -8
View File
@@ -248,7 +248,7 @@ export function SyncConfigDialog({
{isLoggedIn && user ? (
<div className="grid gap-4 py-4">
<div className="flex gap-2 items-center text-sm">
<div className="w-2 h-2 rounded-full bg-success" />
<div className="size-2 rounded-full bg-success" />
{t("sync.cloud.connected")}
</div>
@@ -353,7 +353,7 @@ export function SyncConfigDialog({
<TabsContent value="cloud">
{isCloudLoading ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="grid gap-4 py-4">
@@ -373,7 +373,7 @@ export function SyncConfigDialog({
<TabsContent value="self-hosted">
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
<div className="size-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="grid gap-4 py-4">
@@ -419,9 +419,9 @@ export function SyncConfigDialog({
}
>
{showToken ? (
<LuEyeOff className="w-4 h-4 text-muted-foreground hover:text-foreground" />
<LuEyeOff className="size-4 text-muted-foreground hover:text-foreground" />
) : (
<LuEye className="w-4 h-4 text-muted-foreground hover:text-foreground" />
<LuEye className="size-4 text-muted-foreground hover:text-foreground" />
)}
</button>
</TooltipTrigger>
@@ -434,19 +434,19 @@ export function SyncConfigDialog({
{connectionStatus === "testing" && (
<div className="flex gap-2 items-center text-sm text-muted-foreground">
<div className="w-4 h-4 rounded-full border-2 border-current animate-spin border-t-transparent" />
<div className="size-4 rounded-full border-2 border-current animate-spin border-t-transparent" />
{t("sync.status.syncing")}
</div>
)}
{connectionStatus === "connected" && (
<div className="flex gap-2 items-center text-sm text-muted-foreground">
<div className="w-2 h-2 rounded-full bg-success" />
<div className="size-2 rounded-full bg-success" />
{t("sync.status.connected")}
</div>
)}
{connectionStatus === "error" && (
<div className="flex gap-2 items-center text-sm text-muted-foreground">
<div className="w-2 h-2 rounded-full bg-destructive" />
<div className="size-2 rounded-full bg-destructive" />
{t("sync.status.disconnected")}
</div>
)}
+20 -16
View File
@@ -108,24 +108,28 @@ export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
// Re-apply custom theme after mount
useEffect(() => {
if (!isLoading && theme === "custom") {
const reapplyCustomTheme = async () => {
try {
const { invoke } = await import("@tauri-apps/api/core");
const settings = await invoke<AppSettings>("get_app_settings");
if (settings?.theme === "custom" && settings.custom_theme) {
applyThemeColors(settings.custom_theme);
}
} catch (error) {
console.warn("Failed to reapply custom theme:", error);
}
};
setTimeout(() => {
void reapplyCustomTheme();
}, 100);
} else if (!isLoading) {
if (isLoading) return;
if (theme !== "custom") {
clearThemeColors();
return;
}
const reapplyCustomTheme = async () => {
try {
const { invoke } = await import("@tauri-apps/api/core");
const settings = await invoke<AppSettings>("get_app_settings");
if (settings?.theme === "custom" && settings.custom_theme) {
applyThemeColors(settings.custom_theme);
}
} catch (error) {
console.warn("Failed to reapply custom theme:", error);
}
};
const handle = window.setTimeout(() => {
void reapplyCustomTheme();
}, 100);
return () => {
window.clearTimeout(handle);
};
}, [isLoading, theme]);
// Listen for system theme changes when in "system" mode
+2 -2
View File
@@ -398,7 +398,7 @@ export function TrafficDetailsDialog({
<div className="flex items-center justify-center gap-6 mt-2">
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded"
className="size-3 rounded"
style={{ backgroundColor: "var(--chart-1)" }}
/>
<span className="text-xs text-muted-foreground">
@@ -407,7 +407,7 @@ export function TrafficDetailsDialog({
</div>
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded"
className="size-3 rounded"
style={{ backgroundColor: "var(--chart-2)" }}
/>
<span className="text-xs text-muted-foreground">
+2 -2
View File
@@ -229,7 +229,7 @@ const ChartTooltipContent = React.forwardRef<
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"size-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
@@ -321,7 +321,7 @@ const ChartLegendContent = React.forwardRef<
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
className="size-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
+3 -3
View File
@@ -245,7 +245,7 @@ export const ColorPickerSelection = memo(
{...props}
>
<div
className="absolute w-4 h-4 rounded-full border-2 border-white -translate-x-1/2 -translate-y-1/2 pointer-events-none"
className="absolute size-4 rounded-full border-2 border-white -translate-x-1/2 -translate-y-1/2 pointer-events-none"
style={{
left: `${positionX * 100}%`,
top: `${positionY * 100}%`,
@@ -281,7 +281,7 @@ export const ColorPickerHue = ({
<Slider.Track className="relative my-0.5 h-3 w-full grow rounded-full bg-[linear-gradient(90deg,#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF,#FF0000)]">
<Slider.Range className="absolute h-full" />
</Slider.Track>
<Slider.Thumb className="block w-4 h-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
<Slider.Thumb className="block size-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</Slider.Root>
);
};
@@ -315,7 +315,7 @@ export const ColorPickerAlpha = ({
<div className="absolute inset-0 bg-gradient-to-r from-transparent rounded-full to-black/50" />
<Slider.Range className="absolute h-full bg-transparent rounded-full" />
</Slider.Track>
<Slider.Thumb className="block w-4 h-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
<Slider.Thumb className="block size-4 rounded-full border shadow transition-colors border-primary/50 bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</Slider.Root>
);
};
+5 -3
View File
@@ -47,6 +47,7 @@ export function Combobox({
}: ComboboxProps) {
const { t } = useTranslation();
const [open, setOpen] = React.useState(false);
const listboxId = React.useId();
const resolvedPlaceholder = placeholder ?? t("common.buttons.select");
const resolvedSearchPlaceholder =
@@ -59,16 +60,17 @@ export function Combobox({
variant="outline"
role="combobox"
aria-expanded={open}
aria-controls={listboxId}
disabled={disabled}
className={cn("w-full justify-between", className)}
>
{value
? options.find((option) => option.value === value)?.label
: resolvedPlaceholder}
<LuChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
<LuChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<PopoverContent id={listboxId} className="w-full p-0">
<Command>
<CommandInput placeholder={resolvedSearchPlaceholder} />
<CommandList>
@@ -85,7 +87,7 @@ export function Combobox({
>
<LuCheck
className={cn(
"mr-2 h-4 w-4",
"mr-2 size-4",
value === option.value ? "opacity-100" : "opacity-0",
)}
/>
+2 -2
View File
@@ -55,12 +55,12 @@ export function CopyToClipboard({
{copied ? t("common.srOnly.copied") : t("common.srOnly.copy")}
</span>
<LuCopy
className={`h-4 w-4 transition-all duration-150 ${
className={`size-4 transition-all duration-150 ${
copied ? "scale-0" : "scale-100"
}`}
/>
<LuCheck
className={`absolute inset-0 m-auto h-4 w-4 text-foreground transition-all duration-150 ${
className={`absolute inset-0 m-auto size-4 text-foreground transition-all duration-150 ${
copied ? "scale-100" : "scale-0"
}`}
/>
+2 -2
View File
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"aspect-square size-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<LuCircle className="h-2.5 w-2.5 fill-current text-current" />
<LuCircle className="size-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
+4 -4
View File
@@ -70,18 +70,18 @@ export function VpnCheckButton({
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0"
className="size-7 p-0"
onClick={handleCheck}
disabled={isCurrentlyChecking || disabled}
>
{isCurrentlyChecking ? (
<div className="w-3 h-3 rounded-full border border-current animate-spin border-t-transparent" />
<div className="size-3 rounded-full border border-current animate-spin border-t-transparent" />
) : result?.is_valid ? (
<FiCheck className="w-3 h-3 text-success" />
<FiCheck className="size-3 text-success" />
) : result && !result.is_valid ? (
<span className="text-destructive text-sm"></span>
) : (
<FiCheck className="w-3 h-3" />
<FiCheck className="size-3" />
)}
</Button>
</TooltipTrigger>
+3 -3
View File
@@ -219,7 +219,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
}
}}
>
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
<LuUpload className="size-10 text-muted-foreground mb-4" />
<p className="text-sm text-muted-foreground text-center">
{t("vpns.import.dropzonePrompt")}
</p>
@@ -244,7 +244,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
{step === "vpn-preview" && vpnPreview && (
<div className="space-y-4">
<div className="flex items-center gap-3 p-4 bg-muted/30 rounded-lg">
<LuShield className="w-8 h-8 text-primary" />
<LuShield className="size-8 text-primary" />
<div>
<div className="font-medium">
{t("vpns.import.configurationLabel", {
@@ -292,7 +292,7 @@ export function VpnImportDialog({ isOpen, onClose }: VpnImportDialogProps) {
>
{vpnImportResult.success ? (
<div className="flex items-center gap-3">
<LuShield className="w-8 h-8 text-success" />
<LuShield className="size-8 text-success" />
<div>
<div className="font-medium text-success">
{t("vpns.import.importedSuccess")}
+5 -5
View File
@@ -228,7 +228,7 @@ export function WayfernConfigForm({
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="randomize-fingerprint"
checked={config.randomize_fingerprint_on_launch ?? false}
@@ -248,7 +248,7 @@ export function WayfernConfigForm({
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="auto-location-advanced"
checked={isAutoLocationEnabled}
@@ -954,7 +954,7 @@ export function WayfernConfigForm({
<Label>{t("fingerprint.battery")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="battery-charging"
checked={fingerprintConfig.batteryCharging ?? false}
@@ -1133,7 +1133,7 @@ export function WayfernConfigForm({
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="randomize-fingerprint-auto"
checked={config.randomize_fingerprint_on_launch ?? false}
@@ -1156,7 +1156,7 @@ export function WayfernConfigForm({
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="auto-location"
checked={isAutoLocationEnabled}
@@ -79,7 +79,7 @@ export function WindowResizeWarningDialog({
<p className="text-sm text-muted-foreground">{description}</p>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-x-2">
<Checkbox
id="dont-show-again"
checked={dontShowAgain}
+11
View File
@@ -206,3 +206,14 @@
[data-sonner-toast] select {
pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms;
animation-iteration-count: 1;
transition-duration: 0.01ms;
scroll-behavior: auto;
}
}