fix viewmode height, update other ui stuff

This commit is contained in:
Eduard Gert
2026-05-27 16:40:57 +02:00
parent ec5da43d73
commit b84c7618e7
18 changed files with 92 additions and 84 deletions
@@ -31,16 +31,12 @@ export const EmptyState = ({
<div className={cn("py-12 text-center", className)}>
<div
className={
"flex flex-col items-center justify-center max-w-sm mx-auto"
"flex flex-col items-center justify-center max-w-sm mx-auto relative top-7"
}
>
<SquareIcon icon={icon} className={"mb-3"} />
<p className={"text-base font-semibold text-nb-gray-200 mb-1"}>
{title}
</p>
{description && (
<p className={"text-sm text-nb-gray-350"}>{description}</p>
)}
<p className={"text-base font-semibold text-nb-gray-200 mb-1"}>{title}</p>
{description && <p className={"text-sm text-nb-gray-350"}>{description}</p>}
{learnMoreUrl && learnMoreTopic && (
<p className={"text-sm text-nb-gray-350"}>
{t("common.learnMoreAbout")}{" "}
@@ -57,10 +53,7 @@ export const EmptyState = ({
)}
>
{learnMoreTopic}
<ExternalLinkIcon
size={12}
className={"shrink-0"}
/>
<ExternalLinkIcon size={12} className={"shrink-0"} />
</a>
</p>
)}
@@ -1,4 +1,4 @@
import { UnplugIcon } from "lucide-react";
import { GlobeOffIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { EmptyState } from "./EmptyState";
@@ -7,11 +7,11 @@ export const NotConnectedState = () => {
return (
<div
className={
"h-full min-h-[260px] flex items-center justify-center px-6"
"h-full min-h-[260px] flex-1 flex items-center justify-center px-6 pb-20 top-1 relative"
}
>
<EmptyState
icon={UnplugIcon}
icon={GlobeOffIcon}
title={t("notConnected.title")}
description={t("notConnected.description")}
/>
@@ -319,7 +319,7 @@ export const ConnectionStatusSwitch = () => {
<div className={"flex flex-col items-center"}>
<h1
className={
"text-sm font-medium text-nb-gray-200 tracking-wide transition-colors duration-300 select-none wails-no-draggable mb-1"
"text-sm font-semibold text-nb-gray-200 tracking-wide transition-colors duration-300 select-none wails-no-draggable mb-1"
}
>
{t(STATUS_KEY[connState])}
+4 -2
View File
@@ -143,7 +143,7 @@ export const Header = () => {
<div
className={cn(
"shrink-0 cursor-default wails-draggable relative",
"flex items-center h-12 px-3 top-2",
"flex items-center h-12 px-3 top-2.5",
)}
>
<div className={"grid grid-cols-3 items-center w-[356px] shrink-0"}>
@@ -151,7 +151,9 @@ export const Header = () => {
<div className={"flex justify-center ml-4"}>{profileSlot}</div>
<div />
</div>
<div className={"absolute right-3 top-1/2 -translate-y-1/2"}>{settingsSlot}</div>
<div className={"absolute right-[0.98rem] top-1/2 -translate-y-1/2"}>
{settingsSlot}
</div>
</div>
);
};
+27 -5
View File
@@ -1,8 +1,11 @@
import { ConnectionStatusSwitch } from "@/layouts/ConnectionStatusSwitch.tsx";
import { MainRightSide } from "@/layouts/MainRightSide.tsx";
import { Navigation } from "@/layouts/Navigation.tsx";
import { cn } from "@/lib/cn";
import { useNavSection } from "@/lib/navSection";
import { useViewMode } from "@/lib/viewMode";
import { NotConnectedState } from "@/components/NotConnectedState";
import { useStatus } from "@/modules/daemon-status/StatusContext";
import { Peers } from "@/modules/peers/Peers";
import { Networks } from "@/modules/networks/Networks";
import { ExitNodes } from "@/modules/exit-nodes/ExitNodes";
@@ -28,6 +31,8 @@ const MainBody = () => {
const isAdvanced = viewMode === "advanced";
const { section } = useNavSection();
const { selected } = usePeerDetail();
const { status } = useStatus();
const isConnected = status?.status === "Connected";
return (
<div className={"wails-draggable flex flex-1 min-h-0 p-4 gap-4"}>
@@ -43,12 +48,29 @@ const MainBody = () => {
overlay={<PeerDetailPanel />}
overlayOpen={selected !== null}
>
<Navigation />
<div className={"flex-1 min-h-0 flex flex-col"}>
{section === "peers" && <Peers />}
{section === "networks" && <Networks />}
{section === "exitNode" && <ExitNodes />}
<div
className={cn(
"flex-1 min-h-0 min-w-0 flex flex-col",
!isConnected && "pointer-events-none select-none",
)}
aria-hidden={!isConnected}
>
<Navigation />
<div className={"flex-1 min-h-0 flex flex-col"}>
{section === "peers" && <Peers />}
{section === "networks" && <Networks />}
{section === "exitNode" && <ExitNodes />}
</div>
</div>
{!isConnected && (
<div
className={
"absolute inset-0 z-20 flex pointer-events-auto bg-nb-gray-940"
}
>
<NotConnectedState />
</div>
)}
</MainRightSide>
)}
</div>
@@ -51,7 +51,7 @@ export const Navigation = () => {
"group relative flex flex-1 items-center justify-center",
"gap-2.5 px-5 py-3",
"outline-none transition-all",
isActive ? "text-netbird" : "text-nb-gray-500 hover:text-nb-gray-400",
isActive ? "text-netbird" : "text-nb-gray-400 hover:text-nb-gray-300",
isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
)}
>
@@ -62,7 +62,7 @@ export const Navigation = () => {
"absolute inset-x-0 bottom-0 h-px transition-all",
isActive
? "bg-netbird"
: "bg-nb-gray-900 group-hover:bg-nb-gray-600",
: "bg-nb-gray-910 group-hover:bg-nb-gray-700",
)}
/>
</button>
@@ -72,8 +72,12 @@ export const Navigation = () => {
);
};
const ExitNodeIcon = (props: LucideProps) => (
<SquareArrowUpRight {...props} className={cn("rotate-45", props.className)} />
const ExitNodeIcon = ({ size, ...props }: LucideProps) => (
<SquareArrowUpRight
{...props}
size={typeof size === "number" ? size - 2 : size}
className={cn("rotate-45", props.className)}
/>
);
export type { NavSection } from "@/lib/navSection";
+19 -8
View File
@@ -5,12 +5,16 @@ import { ViewMode as ViewModePref } from "@bindings/preferences/models.js";
export type ViewMode = "default" | "advanced";
// Window dimensions per view. Height matches the Settings window (640) so
// the chrome height is identical across surfaces; width grows from the
// compact 380 default to 900 in advanced.
export const VIEW_SIZE: Record<ViewMode, { width: number; height: number }> = {
default: { width: 380, height: 640 },
advanced: { width: 900, height: 640 },
// Window widths per view. Height stays at whatever the window was first
// created with — we deliberately don't pass a fixed height to
// Window.SetSize because Wails' macOS implementation interprets it as the
// outer frame (windowSetSize → setFrame:), while the initial creation
// uses initWithContentRect:. The two differ by one title-bar height
// (~28px), so re-asserting 640 here would chop ~28px off the content
// area on the first switch and visually shift everything inside.
export const VIEW_WIDTH: Record<ViewMode, number> = {
default: 380,
advanced: 900,
};
type ViewModeContextValue = {
@@ -46,8 +50,15 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => {
(mode: ViewMode) => {
setMode((prev) => {
if (prev === mode) return prev;
const { width, height } = VIEW_SIZE[mode];
void Window.SetSize(width, height).catch(() => {});
void (async () => {
// Reuse the live frame height instead of asserting a
// constant — keeps content area stable across switches
// (see VIEW_WIDTH comment above).
const size = await Window.Size().catch(() => null);
const width = VIEW_WIDTH[mode];
const height = size?.height ?? 640;
await Window.SetSize(width, height).catch(() => {});
})();
void Preferences.SetViewMode(mode as unknown as ViewModePref).catch(() => {});
return mode;
});
@@ -6,7 +6,6 @@ import { cn } from "@/lib/cn";
import { SearchInput } from "@/components/SearchInput";
import { EmptyState } from "@/components/EmptyState";
import { NoResults } from "@/components/NoResults";
import { NotConnectedState } from "@/components/NotConnectedState";
import { useStatus } from "@/modules/daemon-status/StatusContext";
import {
formatShortcut,
@@ -46,19 +45,11 @@ export const ExitNodes = () => {
});
}, [exitNodes, search]);
if (!isConnected) {
return (
<div className={"flex flex-col w-full h-full min-h-0"}>
<NotConnectedState />
</div>
);
}
if (exitNodes.length === 0) {
if (isConnected && exitNodes.length === 0) {
return (
<div
className={
"flex-1 flex items-center justify-center px-6 w-full h-full min-h-0"
"flex-1 flex items-center justify-center px-6 pb-20 w-full h-full min-h-0"
}
>
<EmptyState
@@ -91,7 +82,10 @@ export const ExitNodes = () => {
{filtered.length === 0 ? (
<NoResults />
) : (
<ExitNodesList data={filtered} onToggle={toggleExitNode} />
<ExitNodesList
data={filtered}
onToggle={toggleExitNode}
/>
)}
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
@@ -6,7 +6,6 @@ import { cn } from "@/lib/cn";
import { SearchInput } from "@/components/SearchInput";
import { EmptyState } from "@/components/EmptyState";
import { NoResults } from "@/components/NoResults";
import { NotConnectedState } from "@/components/NotConnectedState";
import { useStatus } from "@/modules/daemon-status/StatusContext";
import {
formatShortcut,
@@ -97,19 +96,11 @@ export const Networks = () => {
});
}, [networkRoutes, search, filter, overlapById]);
if (!isConnected) {
return (
<div className={"flex flex-col w-full h-full min-h-0"}>
<NotConnectedState />
</div>
);
}
if (networkRoutes.length === 0) {
if (isConnected && networkRoutes.length === 0) {
return (
<div
className={
"flex-1 flex items-center justify-center px-6 w-full h-full min-h-0"
"flex-1 flex items-center justify-center px-6 pb-20 w-full h-full min-h-0"
}
>
<EmptyState
+2 -11
View File
@@ -6,7 +6,6 @@ import { cn } from "@/lib/cn";
import { SearchInput } from "@/components/SearchInput";
import { EmptyState } from "@/components/EmptyState";
import { NoResults } from "@/components/NoResults";
import { NotConnectedState } from "@/components/NotConnectedState";
import { useStatus } from "@/modules/daemon-status/StatusContext";
import {
formatShortcut,
@@ -70,19 +69,11 @@ export const Peers = () => {
});
}, [peers, search, statusFilter]);
if (!isConnected) {
return (
<div className={"flex flex-col w-full h-full min-h-0"}>
<NotConnectedState />
</div>
);
}
if (peers.length === 0) {
if (isConnected && peers.length === 0) {
return (
<div
className={
"flex-1 flex items-center justify-center px-6 w-full h-full min-h-0"
"flex-1 flex items-center justify-center px-6 pb-20 w-full h-full min-h-0"
}
>
<EmptyState
@@ -78,7 +78,7 @@ export function SettingsAbout() {
t("settings.about.client", { version: daemonVersion })
)}
</p>
<p className={"text-sm text-nb-gray-300 cursor-text select-text"}>
<p className={"text-sm text-nb-gray-250 cursor-text select-text font-medium"}>
{t("settings.about.gui", { version: guiVersion })}
</p>
</div>
@@ -28,7 +28,7 @@ export const SettingsBottomBar = ({ children }: { children: ReactNode }) => (
<div className={"absolute bottom-0 left-0 w-full"}>
<div
className={
"w-full flex justify-end gap-3 px-8 py-5 border-t border-nb-gray-900 bg-nb-gray-935"
"w-full flex justify-end gap-3 px-8 py-5 border-t border-nb-gray-920 bg-nb-gray-940"
}
>
{children}