mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 14:49:10 +02:00
fix viewmode height, update other ui stuff
This commit is contained in:
@@ -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])}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user