fix sidebar loading issues and navbar on mobile (#7991)

* Refactor MobileSidebar to manage script and category selection based on current pathname. Introduced temporary state for non-scripts pages and updated logic for last viewed script handling. Improved accessibility by ensuring proper aria attributes and class management.

* Update API endpoint paths in data.ts to include ProxmoxVE prefix for category and version fetching functions.

* Refactor Navbar component layout for improved structure and responsiveness. Adjusted flex properties to ensure proper alignment of elements, enhancing the mobile and desktop user experience. Updated accessibility features and ensured consistent use of TailwindCSS classes.
This commit is contained in:
Bram Suurd
2025-09-29 21:56:07 +02:00
committed by GitHub
parent be5ac7153e
commit b52c252553
3 changed files with 52 additions and 33 deletions

View File

@@ -42,32 +42,34 @@ function Navbar() {
<Image height={18} unoptimized width={18} alt="logo" src="/ProxmoxVE/logo.png" className="" /> <Image height={18} unoptimized width={18} alt="logo" src="/ProxmoxVE/logo.png" className="" />
<span className="">Proxmox VE Helper-Scripts</span> <span className="">Proxmox VE Helper-Scripts</span>
</Link> </Link>
<div className="flex items-center gap-2"> <div className="flex items-center justify-between gap-2 w-full">
<div className="flex sm:hidden"> <div className="flex sm:hidden">
<Suspense> <Suspense>
<MobileSidebar /> <MobileSidebar />
</Suspense> </Suspense>
</div> </div>
<CommandMenu /> <div className="flex sm:gap-2">
<GitHubStarsButton username="community-scripts" repo="ProxmoxVE" className="hidden md:flex" /> <CommandMenu />
{navbarLinks.map(({ href, event, icon, text, mobileHidden }) => ( <GitHubStarsButton username="community-scripts" repo="ProxmoxVE" className="hidden md:flex" />
<TooltipProvider key={event}> {navbarLinks.map(({ href, event, icon, text, mobileHidden }) => (
<Tooltip delayDuration={100}> <TooltipProvider key={event}>
<TooltipTrigger className={mobileHidden ? "hidden lg:block" : ""}> <Tooltip delayDuration={100}>
<Button variant="ghost" size="icon" asChild> <TooltipTrigger className={mobileHidden ? "hidden lg:block" : ""}>
<Link target="_blank" href={href} data-umami-event={event}> <Button variant="ghost" size="icon" asChild>
{icon} <Link target="_blank" href={href} data-umami-event={event}>
<span className="sr-only">{text}</span> {icon}
</Link> <span className="sr-only">{text}</span>
</Button> </Link>
</TooltipTrigger> </Button>
<TooltipContent side="bottom" className="text-xs"> </TooltipTrigger>
{text} <TooltipContent side="bottom" className="text-xs">
</TooltipContent> {text}
</Tooltip> </TooltipContent>
</TooltipProvider> </Tooltip>
))} </TooltipProvider>
<ThemeToggle /> ))}
<ThemeToggle />
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { useQueryState } from "nuqs"; import { useQueryState } from "nuqs";
import { Menu } from "lucide-react"; import { Menu } from "lucide-react";
@@ -18,9 +19,20 @@ function MobileSidebar() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [lastViewedScript, setLastViewedScript] = useState<Script | undefined>(undefined); const [lastViewedScript, setLastViewedScript] = useState<Script | undefined>(undefined);
const pathname = usePathname();
// Always call the hooks (React hooks can't be conditional)
const [selectedScript, setSelectedScript] = useQueryState("id"); const [selectedScript, setSelectedScript] = useQueryState("id");
const [selectedCategory, setSelectedCategory] = useQueryState("category"); const [selectedCategory, setSelectedCategory] = useQueryState("category");
// For non-scripts pages, we'll manage state locally
const [tempSelectedScript, setTempSelectedScript] = useState<string | null>(null);
const [tempSelectedCategory, setTempSelectedCategory] = useState<string | null>(null);
const isOnScriptsPage = pathname === "/scripts";
const currentSelectedScript = isOnScriptsPage ? selectedScript : tempSelectedScript;
const currentSelectedCategory = isOnScriptsPage ? selectedCategory : tempSelectedCategory;
const loadCategories = useCallback(async () => { const loadCategories = useCallback(async () => {
setIsLoading(true); setIsLoading(true);
try { try {
@@ -40,16 +52,16 @@ function MobileSidebar() {
}, [loadCategories]); }, [loadCategories]);
useEffect(() => { useEffect(() => {
if (!selectedScript || categories.length === 0) { if (!currentSelectedScript || categories.length === 0) {
return; return;
} }
const scriptMatch = categories const scriptMatch = categories
.flatMap(category => category.scripts) .flatMap(category => category.scripts)
.find(script => script.slug === selectedScript); .find(script => script.slug === currentSelectedScript);
setLastViewedScript(scriptMatch); setLastViewedScript(scriptMatch);
}, [selectedScript, categories]); }, [currentSelectedScript, categories]);
const handleOpenChange = (openState: boolean) => { const handleOpenChange = (openState: boolean) => {
setIsOpen(openState); setIsOpen(openState);
@@ -78,7 +90,9 @@ function MobileSidebar() {
<Menu className="size-5" aria-hidden="true" /> <Menu className="size-5" aria-hidden="true" />
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetHeader className="border-b border-border px-6 pb-4 pt-2"><SheetTitle className="sr-only">Categories</SheetTitle></SheetHeader> <SheetHeader className="border-b border-border px-6 pb-4 pt-2 sr-only">
<SheetTitle className="sr-only">Categories</SheetTitle>
</SheetHeader>
<SheetContent side="left" className="flex w-full max-w-xs flex-col gap-4 overflow-hidden px-0 pb-6"> <SheetContent side="left" className="flex w-full max-w-xs flex-col gap-4 overflow-hidden px-0 pb-6">
<div className="flex h-full flex-col gap-4 overflow-y-auto"> <div className="flex h-full flex-col gap-4 overflow-y-auto">
{isLoading && !hasLinks {isLoading && !hasLinks
@@ -91,19 +105,22 @@ function MobileSidebar() {
<div className="flex flex-col gap-4 px-4"> <div className="flex flex-col gap-4 px-4">
<Sidebar <Sidebar
items={categories} items={categories}
selectedScript={selectedScript} selectedScript={currentSelectedScript}
setSelectedScript={setSelectedScript} setSelectedScript={isOnScriptsPage ? setSelectedScript : setTempSelectedScript}
selectedCategory={selectedCategory} selectedCategory={currentSelectedCategory}
setSelectedCategory={setSelectedCategory} setSelectedCategory={isOnScriptsPage ? setSelectedCategory : setTempSelectedCategory}
onItemSelect={handleItemSelect} onItemSelect={handleItemSelect}
/> />
</div> </div>
)} )}
{selectedScript && lastViewedScript {currentSelectedScript && lastViewedScript
? ( ? (
<div className="flex flex-col gap-3 px-4"> <div className="flex flex-col gap-3 px-4">
<p className="text-sm font-medium">Last Viewed</p> <p className="text-sm font-medium">Last Viewed</p>
<ScriptItem item={lastViewedScript} setSelectedScript={setSelectedScript} /> <ScriptItem
item={lastViewedScript}
setSelectedScript={isOnScriptsPage ? setSelectedScript : setTempSelectedScript}
/>
</div> </div>
) )
: null} : null}

View File

@@ -1,7 +1,7 @@
import type { Category } from "./types"; import type { Category } from "./types";
export async function fetchCategories() { export async function fetchCategories() {
const response = await fetch("api/categories"); const response = await fetch(`/ProxmoxVE/api/categories`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch categories: ${response.statusText}`); throw new Error(`Failed to fetch categories: ${response.statusText}`);
} }
@@ -10,7 +10,7 @@ export async function fetchCategories() {
} }
export async function fetchVersions() { export async function fetchVersions() {
const response = await fetch(`api/versions`); const response = await fetch(`/ProxmoxVE/api/versions`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch versions: ${response.statusText}`); throw new Error(`Failed to fetch versions: ${response.statusText}`);
} }