@dloizides/ui-nav
v1.4.0
Published
Config-driven, brand-agnostic React Native (RN-web) navigation shell — Sidebar + Topbar promoted from the byte-identical erevna-web/katalogos-web nav twins. Renders a caller-supplied NavItem[] (pre-localized labels, icon render slots), routes colours thro
Maintainers
Readme
@dloizides/ui-nav
Config-driven, brand-agnostic React Native (RN-web) navigation shell for the
dloizides.com portfolio: Sidebar + Topbar, promoted from the byte-identical
erevna-web / katalogos-web nav twins. The chrome is shared; the item data stays with
your app — the components render a caller-supplied NavItem[] (labels already localized,
icons as render slots) and route every colour through the shared @dloizides/ui-feedback
UiProvider theme.
Install
npm install @dloizides/ui-nav @dloizides/ui-feedback @dloizides/auth-webPeer deps: @dloizides/ui-feedback >= 1.2.0, @dloizides/auth-web >= 1.5.0, react >= 18,
react-native >= 0.74 (use react-native-web on web).
Usage
import { Sidebar, Topbar, accessibleNavItems, type NavItem } from '@dloizides/ui-nav';
const items: NavItem[] = buildItems(); // your role-filtered / grouped items
<Sidebar
items={items}
pathname={usePathname()}
onNavigate={(route) => router.push(route)}
title={FM('menu.title')}
regionLabel={FM('accessibility.navigationRegion')}
navigateHint={(label) => FM('menu.navigateToHint', label)}
expandHint={FM('menu.expandSection')}
collapseHint={FM('menu.collapseSection')}
renderChevron={(open, color, size) => <SvgIcon name={open ? 'chevronUp' : 'chevronDown'} color={color} size={size} />}
header={<HomeShortcut />}
footer={<><DarkModeToggle /><LogoutButton /></>}
/>
<Topbar
left={<TenantLogo />}
language={{ label: FM(`topbar.lang.${locale}`), hint: FM('topbar.langHint'), onPress: toggleLocale }}
notificationSlot={<NotificationBell />}
user={{ name: displayName, email }}
logout={{ label: FM('topbar.logout'), hint: FM('topbar.logoutHint'), onPress: logout, testID: 'logout-button' }}
/>Horizontal top bar (NavBar)
Prefer the nav links on top (a horizontal menu bar) instead of the left rail?
NavBar renders the SAME NavItem[] model horizontally — brand slot on the left,
inline links, a free-form right slot (auth / user / logout) — and collapses the
links behind a hamburger below a configurable breakpoint (default 760, matching
the v1 AML console .global-nav). It is purely additive; Sidebar is unchanged.
import { NavBar, type NavItem } from '@dloizides/ui-nav';
<NavBar
items={items} // same role-filtered NavItem[] as Sidebar
pathname={usePathname()}
onNavigate={(route) => router.push(route)}
regionLabel={FM('accessibility.navigationRegion')}
navigateHint={(label) => FM('menu.navigateToHint', label)}
brand={<BrandLogo />} // left slot
right={<AuthLink />} // right slot (auth / user / logout)
menuLabel={FM('menu.toggle')} // a11y label for the hamburger
menuHint={FM('menu.toggleHint')}
collapseBelow={760} // hamburger under this viewport width
/>An app that renders a Sidebar today switches to a top bar by swapping the
Sidebar element for NavBar (same items / pathname / onNavigate) — e.g.
the AML v2 app drops NavBar into the AppShell header (or nav) slot so its
links sit across the top like the rest of the console.
Each link is a rounded-pill target with the v1 .gn-links affordance: muted at
rest, brightening its text + showing a subtle pill background on hover, and a
solid accent pill when active. All colours are theme-driven (rest =
textSecondary, hover text = text, hover bg = surfaceElevated, active/ring =
palette.primary); hover/focus are web-only and the hover cross-fade honours
prefers-reduced-motion.
Accessibility. The container is a role="navigation" landmark named by
regionLabel; active links expose aria-current="page" + role="link"; the
responsive hamburger carries role="button", aria-expanded, and aria-controls
pointing at the collapsible links region; every link/toggle is keyboard-operable
(Enter/Space) with a themed focus ring and a ≥44×44 touch target. Sidebar leaves
carry aria-current="page" and Topbar buttons a focus ring under the same bar.
Role gating
accessibleNavItems(user, roleRouteTable, translate) builds the NavItem[] a user's roles
unlock, reusing resolveAccessibleRoutes from @dloizides/auth-web (most privileged first,
empty when none) — no duplicated role logic.
License
MIT
