From 934975feaed38c1c1ed8d48bc4bc1371e3f629d4 Mon Sep 17 00:00:00 2001 From: boluwacodes <305481097+boluwacodes@users.noreply.github.com> Date: Sun, 23 Aug 2026 03:46:27 +0100 Subject: [PATCH] fix: keyboard-accessible nav dropdowns, whitespace-safe initials() - Navbar "Product"/"Resources" dropdowns now open via click/focus (not just hover) with aria-expanded/aria-haspopup and Escape-to-close, so keyboard and screen-reader users can reach the links inside them. - Consolidate the duplicated initials() helper into src/lib/utils.ts and make it whitespace-safe, so names with leading/trailing/double spaces no longer render a blank avatar badge. Closes #1 Closes #3 --- src/components/dashboard/shipment-ui.tsx | 9 +-------- src/components/layout/navbar.tsx | 23 +++++++++++++---------- src/lib/utils.ts | 11 +++++++++++ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/components/dashboard/shipment-ui.tsx b/src/components/dashboard/shipment-ui.tsx index 793df28..5ac5290 100644 --- a/src/components/dashboard/shipment-ui.tsx +++ b/src/components/dashboard/shipment-ui.tsx @@ -1,4 +1,5 @@ import type { Shipment } from '@/lib/api'; +export { initials } from '@/lib/utils'; export const TERMINAL_STATUSES: Shipment['status'][] = [ 'DELIVERED', @@ -30,11 +31,3 @@ export function formatAmount(amount: number, assetCode: Shipment['assetCode']) { return `${new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format(amount)} ${assetCode}`; } -export function initials(name: string) { - return name - .split(' ') - .map((part) => part[0]) - .slice(0, 2) - .join('') - .toUpperCase(); -} diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index 9daef14..0134de8 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -10,7 +10,7 @@ import { Button } from '@/components/ui/button'; import { Container } from '@/components/ui/container'; import { RouteTicker } from './route-ticker'; import { mainNav } from '@/lib/data'; -import { cn } from '@/lib/utils'; +import { cn, initials } from '@/lib/utils'; import { getMe, type CurrentUser } from '@/lib/api'; import { clearToken, getToken } from '@/lib/auth'; @@ -27,15 +27,6 @@ function useCurrentUser() { return [user, setUser] as const; } -function initials(name: string) { - return name - .split(' ') - .map((part) => part[0]) - .slice(0, 2) - .join('') - .toUpperCase(); -} - function AccountMenu({ user, onSignOut }: { user: CurrentUser; onSignOut: () => void }) { const [open, setOpen] = useState(false); return ( @@ -124,9 +115,21 @@ export function Navbar() { key={item.label} className="relative flex items-stretch" onMouseEnter={() => setOpenMenu(item.label)} + onBlur={(e) => { + if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { + setOpenMenu(null); + } + }} + onKeyDown={(e) => { + if (e.key === 'Escape') setOpenMenu(null); + }} >