add default and advanced view items into dropdown

This commit is contained in:
Eduard Gert
2026-05-20 09:39:35 +02:00
parent bec4eb326a
commit b79b62bee4
4 changed files with 85 additions and 14 deletions

View File

@@ -1,9 +1,8 @@
import { ComponentType, forwardRef } from "react"; import { ButtonHTMLAttributes, ComponentType, forwardRef } from "react";
import { motion, HTMLMotionProps } from "framer-motion";
import { LucideProps } from "lucide-react"; import { LucideProps } from "lucide-react";
import { cn } from "@/lib/cn"; import { cn } from "@/lib/cn";
type Props = HTMLMotionProps<"button"> & { type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
icon: ComponentType<LucideProps>; icon: ComponentType<LucideProps>;
iconSize?: number; iconSize?: number;
iconClassName?: string; iconClassName?: string;
@@ -14,10 +13,9 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
ref, ref,
) { ) {
return ( return (
<motion.button <button
ref={ref} ref={ref}
type={type} type={type}
whileTap={{ scale: 0.95 }}
className={cn( className={cn(
"h-10 w-10 flex items-center justify-center rounded-lg cursor-default outline-none", "h-10 w-10 flex items-center justify-center rounded-lg cursor-default outline-none",
"text-nb-gray-400 hover:text-nb-gray-300 hover:bg-nb-gray-900", "text-nb-gray-400 hover:text-nb-gray-300 hover:bg-nb-gray-900",
@@ -27,6 +25,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
{...props} {...props}
> >
<Icon size={iconSize} className={iconClassName} /> <Icon size={iconSize} className={iconClassName} />
</motion.button> </button>
); );
}); });

View File

@@ -82,6 +82,10 @@
"profile.dialog.submit": "Add Profile", "profile.dialog.submit": "Add Profile",
"profile.dialog.required": "Please enter a profile name, e.g. Work, Home", "profile.dialog.required": "Please enter a profile name, e.g. Work, Home",
"header.menu.settings": "Settings",
"header.menu.defaultView": "Default View",
"header.menu.advancedView": "Advanced View",
"profile.deregister.title": "Deregister Profile", "profile.deregister.title": "Deregister Profile",
"profile.deregister.message": "Are you sure you want to deregister \"{name}\"? You will need to log in again to use it.", "profile.deregister.message": "Are you sure you want to deregister \"{name}\"? You will need to log in again to use it.",
"profile.deregister.confirm": "Deregister", "profile.deregister.confirm": "Deregister",

View File

@@ -1,14 +1,42 @@
import { SettingsIcon } from "lucide-react"; import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
Check,
MoreVertical,
PanelTop,
PanelsRightBottom,
Settings,
type LucideIcon,
} from "lucide-react";
import { WindowManager } from "@bindings/services"; import { WindowManager } from "@bindings/services";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/DropdownMenu";
import { IconButton } from "@/components/IconButton"; import { IconButton } from "@/components/IconButton";
import { ProfileDropdown } from "@/components/ProfileDropdown"; import { ProfileDropdown } from "@/components/ProfileDropdown";
import { cn } from "@/lib/cn"; import { cn } from "@/lib/cn";
type ViewMode = "default" | "advanced";
export const Header = () => { export const Header = () => {
const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>("default");
const openSettings = () => { const openSettings = () => {
setMenuOpen(false);
void WindowManager.OpenSettings().catch(() => {}); void WindowManager.OpenSettings().catch(() => {});
}; };
const selectMode = (mode: ViewMode) => {
setMenuOpen(false);
setViewMode(mode);
};
return ( return (
<div <div
className={cn( className={cn(
@@ -23,12 +51,53 @@ export const Header = () => {
<ProfileDropdown /> <ProfileDropdown />
</div> </div>
<div className={"flex justify-end"}> <div className={"flex justify-end"}>
<DropdownMenu modal={false} open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild>
<IconButton <IconButton
icon={SettingsIcon} icon={MoreVertical}
iconClassName={"text-nb-gray-200"} iconClassName={"text-nb-gray-200"}
onClick={openSettings}
/> />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={8} className="min-w-52">
<DropdownMenuItem onClick={openSettings}>
<div className="flex items-center gap-2">
<Settings size={14} />
{t("header.menu.settings")}
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<ViewModeItem
icon={PanelTop}
label={t("header.menu.defaultView")}
selected={viewMode === "default"}
onSelect={() => selectMode("default")}
/>
<ViewModeItem
icon={PanelsRightBottom}
label={t("header.menu.advancedView")}
selected={viewMode === "advanced"}
onSelect={() => selectMode("advanced")}
/>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
</div> </div>
); );
}; };
type ViewModeItemProps = {
icon: LucideIcon;
label: string;
selected: boolean;
onSelect: () => void;
};
const ViewModeItem = ({ icon: Icon, label, selected, onSelect }: ViewModeItemProps) => (
<DropdownMenuItem onClick={onSelect}>
<div className="flex items-center gap-2 w-full">
<Icon size={14} />
<span className="flex-1">{label}</span>
{selected && <Check size={14} className="text-netbird" />}
</div>
</DropdownMenuItem>
);

View File

@@ -169,8 +169,8 @@ func main() {
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "NetBird", Title: "NetBird",
Width: 310, Width: 380,
Height: 420, Height: 590,
Hidden: true, Hidden: true,
BackgroundColour: application.NewRGB(24, 26, 29), BackgroundColour: application.NewRGB(24, 26, 29),
URL: "/", URL: "/",