commit d0e6c55fdfd82cfd7cb1e1fcd8fd076b47c51055
parent 3259d51444c68c1b23402521f4269ed57b1c79f1
Author: Florian Dold <dold@taler.net>
Date: Wed, 4 Nov 2026 00:54:56 +0100
merchant web UI: keep active menu item visible
Diffstat:
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/packages/taler-merchant-webui/src/ui/Menu.tsx b/packages/taler-merchant-webui/src/ui/Menu.tsx
@@ -15,6 +15,7 @@
*/
import type { VNode } from "preact";
+import { useLayoutEffect, useRef } from "preact/hooks";
import { useLocation } from "wouter-preact";
import { Link } from "../routing/Link.js";
import { TalerLogo } from "./TalerLogo.js";
@@ -109,6 +110,21 @@ export function Menu({
const [location] = useLocation();
const { t, lang, setLanguage, languages } = useTranslation();
const showAdvanced = preferences.value.advancedTools;
+ const desktopSidebar = useRef<HTMLElement>(null);
+ const mobileSidebar = useRef<HTMLElement>(null);
+
+ useLayoutEffect(() => {
+ // Reveal the current route after mounting or opening the drawer, without
+ // moving keyboard focus or scrolling an already visible link.
+ for (const sidebar of [desktopSidebar.current, mobileSidebar.current]) {
+ if (!sidebar?.getClientRects().length) continue;
+ sidebar.querySelector('[aria-current="page"]')?.scrollIntoView({
+ block: "nearest",
+ inline: "nearest",
+ behavior: "instant",
+ });
+ }
+ }, [location, isMobileOpen, showAdvanced, showAdministration, lang]);
const isActivePath = (path: string) => {
if (path === "/orders")
@@ -172,6 +188,7 @@ export function Menu({
<li key={entry.href}>
<Link
href={entry.href}
+ aria-current={isActive(entry) ? "page" : undefined}
onClick={onCloseMobile}
class={`flex items-center justify-between px-3 py-2 rounded-md text-sm font-medium transition-colors ${
isActive(entry)
@@ -231,14 +248,20 @@ export function Menu({
return (
<>
{/* Desktop Sidebar (visible on md screens and up) */}
- <aside class="hidden md:flex w-64 h-screen sticky top-0 bg-gray-900 text-gray-200 flex-col p-4 border-r border-gray-800 shrink-0 overflow-hidden">
+ <aside
+ ref={desktopSidebar}
+ class="hidden md:flex w-64 h-screen sticky top-0 bg-gray-900 text-gray-200 flex-col p-4 border-r border-gray-800 shrink-0 overflow-hidden"
+ >
{renderNavContent()}
</aside>
{/* Mobile Drawer (visible on small screens when open) */}
{isMobileOpen && (
<div class="fixed inset-0 z-50 flex md:hidden bg-gray-900/80 backdrop-blur-xs">
- <aside class="w-72 h-full bg-gray-900 text-gray-200 flex flex-col p-4 border-r border-gray-800 shadow-2xl overflow-hidden">
+ <aside
+ ref={mobileSidebar}
+ class="w-72 h-full bg-gray-900 text-gray-200 flex flex-col p-4 border-r border-gray-800 shadow-2xl overflow-hidden"
+ >
{renderNavContent()}
</aside>
<button