fix: improve UI interactions and page consistency

This commit is contained in:
xenos
2026-07-16 14:37:13 +02:00
parent 95f84248ab
commit cea4ece698
6 changed files with 95 additions and 74 deletions
+3 -3
View File
@@ -198,11 +198,11 @@ export function AccountPage({
return ( return (
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}> <Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
<DialogContent className="flex max-h-[calc(100vh-4rem)] max-w-2xl flex-col"> <DialogContent className="flex max-h-[calc(100vh-5rem)] max-w-3xl flex-col">
<div <div
className={cn( className={cn(
"flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4", "min-h-0 flex-1 overflow-y-auto",
subPage && "mx-auto w-full max-w-2xl", subPage && "mx-auto w-full max-w-3xl",
)} )}
> >
<AnimatedTabs defaultValue="account"> <AnimatedTabs defaultValue="account">
@@ -1101,7 +1101,7 @@ export function ExtensionManagementDialog({
return ( return (
<> <>
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}> <Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
<DialogContent className="flex max-h-[90vh] max-w-[min(80rem,calc(100%-4rem))] flex-col"> <DialogContent className="flex max-h-[85vh] max-w-[min(80rem,calc(100%-4rem))] flex-col">
{!subPage && ( {!subPage && (
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
+16 -15
View File
@@ -557,7 +557,7 @@ export function GroupManagementDialog({
return ( return (
<> <>
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}> <Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
<DialogContent className="flex max-h-[90vh] max-w-[min(60rem,calc(100%-4rem))] flex-col"> <DialogContent className="flex max-h-[85vh] max-w-[min(80rem,calc(100%-4rem))] flex-col">
{!subPage && ( {!subPage && (
<DialogHeader> <DialogHeader>
<DialogTitle>{t("groups.management")}</DialogTitle> <DialogTitle>{t("groups.management")}</DialogTitle>
@@ -567,15 +567,13 @@ export function GroupManagementDialog({
</DialogHeader> </DialogHeader>
)} )}
<div className="flex min-h-0 w-full flex-1 flex-col gap-4"> <div className="@container flex min-h-0 w-full flex-1 flex-col">
<div className="flex items-start justify-between gap-3"> <div className="flex shrink-0 flex-wrap items-center justify-between gap-2">
<div className="flex flex-col gap-1"> <div className="inline-flex h-7 items-center justify-center gap-1.5 rounded-md bg-accent px-3 text-sm font-medium whitespace-nowrap text-foreground">
<h2 className="text-base font-semibold"> <span>{t("groups.pageTitle")}</span>
{t("groups.pageTitle")} <span className="text-xs text-muted-foreground tabular-nums">
</h2> {groups.length}
<p className="text-xs text-muted-foreground"> </span>
{t("groups.pageDescription")}
</p>
</div> </div>
<RippleButton <RippleButton
size="sm" size="sm"
@@ -583,31 +581,34 @@ export function GroupManagementDialog({
setCreateDialogOpen(true); setCreateDialogOpen(true);
}} }}
className="flex shrink-0 items-center gap-2" className="flex shrink-0 items-center gap-2"
aria-label={t("proxies.management.create")}
> >
<GoPlus className="size-4" /> <GoPlus className="size-4" />
{t("proxies.management.create")} <span className="hidden @2xl:inline">
{t("proxies.management.create")}
</span>
</RippleButton> </RippleButton>
</div> </div>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="mt-4 rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error} {error}
</div> </div>
)} )}
{/* Groups list */} {/* Groups list */}
{isLoading ? ( {isLoading ? (
<div className="text-sm text-muted-foreground"> <div className="mt-4 text-sm text-muted-foreground">
{t("common.buttons.loading")} {t("common.buttons.loading")}
</div> </div>
) : groups.length === 0 ? ( ) : groups.length === 0 ? (
<div className="text-sm text-muted-foreground"> <div className="mt-4 text-sm text-muted-foreground">
{t("groups.noGroupsDescription")} {t("groups.noGroupsDescription")}
</div> </div>
) : ( ) : (
<FadingScrollArea <FadingScrollArea
className={cn( className={cn(
"min-h-0 flex-1", "mt-4 min-h-0 flex-1",
selectedGroupsForBulk.length > 0 && "pb-16", selectedGroupsForBulk.length > 0 && "pb-16",
)} )}
style={ style={
+68 -51
View File
@@ -1665,6 +1665,30 @@ export function ProfilesDataTable({
}; };
}, [browserState.isClient, loadAllTags]); }, [browserState.isClient, loadAllTags]);
// A running browser keeps the name it launched with, so close any inline
// rename that was open when the profile entered a runtime transition.
React.useEffect(() => {
if (!profileToRename) return;
const profileId = profileToRename.id;
const isRuntimeLocked =
(browserState.isClient && runningProfiles.has(profileId)) ||
launchingProfiles.has(profileId) ||
stoppingProfiles.has(profileId);
if (isRuntimeLocked) {
setProfileToRename(null);
setNewProfileName("");
setRenameError(null);
}
}, [
profileToRename,
runningProfiles,
launchingProfiles,
stoppingProfiles,
browserState.isClient,
]);
// Automatically deselect profiles that become running, updating, launching, or stopping // Automatically deselect profiles that become running, updating, launching, or stopping
React.useEffect(() => { React.useEffect(() => {
const newSet = new Set(selectedProfiles); const newSet = new Set(selectedProfiles);
@@ -2479,7 +2503,15 @@ export function ProfilesDataTable({
const profile = row.original as BrowserProfile; const profile = row.original as BrowserProfile;
const rawName: string = row.getValue("name"); const rawName: string = row.getValue("name");
const name = getBrowserDisplayName(rawName); const name = getBrowserDisplayName(rawName);
const isEditing = meta.profileToRename?.id === profile.id; const isRuntimeLocked =
(meta.isClient && meta.runningProfiles.has(profile.id)) ||
meta.launchingProfiles.has(profile.id) ||
meta.stoppingProfiles.has(profile.id);
const isCrossOsBlocked = isCrossOsProfile(profile);
const isEditing =
meta.profileToRename?.id === profile.id &&
!isRuntimeLocked &&
!isCrossOsBlocked;
if (isEditing) { if (isEditing) {
return ( return (
@@ -2530,45 +2562,44 @@ export function ProfilesDataTable({
/> />
); );
const isCrossOs = isCrossOsProfile(profile);
const isCrossOsBlocked = isCrossOs;
const isRunning =
meta.isClient && meta.runningProfiles.has(profile.id);
const isLaunching = meta.launchingProfiles.has(profile.id);
const isStopping = meta.stoppingProfiles.has(profile.id);
const isDisabled =
isRunning || isLaunching || isStopping || isCrossOsBlocked;
const lockedEmail = meta.getProfileLockEmail(profile.id); const lockedEmail = meta.getProfileLockEmail(profile.id);
const isLocked = meta.isProfileLockedByAnother(profile.id); const isLocked = meta.isProfileLockedByAnother(profile.id);
const nameControl = isRuntimeLocked ? (
return ( <div className="mr-auto h-6 max-w-full min-w-0 cursor-text overflow-hidden rounded px-2 py-1 text-left select-text">
<div className="flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden"> {display}
<button </div>
type="button" ) : (
className={cn( <button
"mr-auto h-6 max-w-full min-w-0 overflow-hidden rounded border-none bg-transparent px-2 py-1 text-left", type="button"
isDisabled className={cn(
? "cursor-not-allowed opacity-60" "mr-auto h-6 max-w-full min-w-0 overflow-hidden rounded border-none bg-transparent px-2 py-1 text-left",
: "cursor-pointer hover:bg-accent/50", isCrossOsBlocked
)} ? "cursor-not-allowed opacity-60"
onClick={() => { : "cursor-pointer hover:bg-accent/50",
if (isDisabled) return; )}
onClick={() => {
if (isCrossOsBlocked) return;
meta.setProfileToRename(profile);
meta.setNewProfileName(profile.name);
meta.setRenameError(null);
}}
onKeyDown={(e) => {
if (isCrossOsBlocked) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
meta.setProfileToRename(profile); meta.setProfileToRename(profile);
meta.setNewProfileName(profile.name); meta.setNewProfileName(profile.name);
meta.setRenameError(null); meta.setRenameError(null);
}} }
onKeyDown={(e) => { }}
if (isDisabled) return; >
if (e.key === "Enter" || e.key === " ") { {display}
e.preventDefault(); </button>
meta.setProfileToRename(profile); );
meta.setNewProfileName(profile.name);
meta.setRenameError(null); return (
} <div className="flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden">
}} {nameControl}
>
{display}
</button>
{isLocked && ( {isLocked && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@@ -2595,14 +2626,7 @@ export function ProfilesDataTable({
cell: ({ row, table }) => { cell: ({ row, table }) => {
const meta = table.options.meta as TableMeta; const meta = table.options.meta as TableMeta;
const profile = row.original; const profile = row.original;
const isCrossOs = isCrossOsProfile(profile); const isDisabled = isCrossOsProfile(profile);
const isCrossOsBlocked = isCrossOs;
const isRunning =
meta.isClient && meta.runningProfiles.has(profile.id);
const isLaunching = meta.launchingProfiles.has(profile.id);
const isStopping = meta.stoppingProfiles.has(profile.id);
const isDisabled =
isRunning || isLaunching || isStopping || isCrossOsBlocked;
return ( return (
<TagsCell <TagsCell
@@ -2628,14 +2652,7 @@ export function ProfilesDataTable({
cell: ({ row, table }) => { cell: ({ row, table }) => {
const meta = table.options.meta as TableMeta; const meta = table.options.meta as TableMeta;
const profile = row.original; const profile = row.original;
const isCrossOs = isCrossOsProfile(profile); const isDisabled = isCrossOsProfile(profile);
const isCrossOsBlocked = isCrossOs;
const isRunning =
meta.isClient && meta.runningProfiles.has(profile.id);
const isLaunching = meta.launchingProfiles.has(profile.id);
const isStopping = meta.stoppingProfiles.has(profile.id);
const isDisabled =
isRunning || isLaunching || isStopping || isCrossOsBlocked;
return ( return (
<NoteCell <NoteCell
+4 -1
View File
@@ -490,7 +490,9 @@ export function SettingsDialog({
} }
setOriginalSettings(settingsToSave); setOriginalSettings(settingsToSave);
onClose(); if (!subPage) {
onClose();
}
} catch (error) { } catch (error) {
console.error("Failed to save settings:", error); console.error("Failed to save settings:", error);
} finally { } finally {
@@ -498,6 +500,7 @@ export function SettingsDialog({
} }
}, [ }, [
onClose, onClose,
subPage,
setTheme, setTheme,
settings, settings,
customThemeState, customThemeState,
+3 -3
View File
@@ -78,7 +78,7 @@ function AnimatedTabsList({
<TabsPrimitive.List <TabsPrimitive.List
data-slot="animated-tabs-list" data-slot="animated-tabs-list"
className={cn( className={cn(
"relative inline-flex max-w-full scrollbar-none items-center gap-1 overflow-x-auto rounded-md p-0", "relative isolate inline-flex max-w-full scrollbar-none items-center gap-1 overflow-x-auto rounded-md p-0",
className, className,
)} )}
onMouseLeave={(event) => { onMouseLeave={(event) => {
@@ -120,7 +120,7 @@ function AnimatedTabsTrigger({
onMouseEnter?.(event); onMouseEnter?.(event);
}} }}
className={cn( className={cn(
"relative isolate inline-flex h-7 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors duration-150", "relative inline-flex h-7 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors duration-150",
"text-muted-foreground hover:text-foreground", "text-muted-foreground hover:text-foreground",
isActive && "text-foreground", isActive && "text-foreground",
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none",
@@ -132,7 +132,7 @@ function AnimatedTabsTrigger({
{showIndicator && ( {showIndicator && (
<motion.span <motion.span
layoutId={`animated-tabs-indicator-${indicatorId}`} layoutId={`animated-tabs-indicator-${indicatorId}`}
className="absolute inset-0 -z-10 rounded-md bg-accent" className="pointer-events-none absolute inset-0 -z-10 rounded-md bg-accent"
transition={{ type: "spring", stiffness: 360, damping: 32 }} transition={{ type: "spring", stiffness: 360, damping: 32 }}
/> />
)} )}