npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dloizides/ui-nav

v1.26.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

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-web

Peer 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
  overflowLabel={FM('menu.more')}             // "More ▾" priority+ trigger label
  overflowHint={FM('menu.moreHint')}
/>

Priority+ overflow ("…" menu). Above collapseBelow the links sit on ONE non-wrapping row. When they don't all fit, a measure pass keeps the leading items that fit inline and collapses the rest behind a "More ▾" trigger — the shared @dloizides/ui-layout ModalDropdown — so no item is ever cut off; the surplus moves into a portalled, keyboard-navigable dropdown. If the active route lives in the overflow set, the trigger wears the accent pill so it stays reachable. Localize the trigger with the optional overflowLabel (visible + accessible, default "More") and overflowHint. Below collapseBelow the whole set still collapses into the responsive hamburger drawer, so the two mechanisms never overlap.

One component, two orientations (Nav)

Nav renders EITHER the horizontal NavBar or the vertical Sidebar from the same NavItem[], chosen by a single orientation prop — flip between a top bar and a side rail without swapping components or re-plumbing props. orientation defaults to "horizontal"; the remaining props are a discriminated union of the delegate's props (TypeScript enforces the right set per orientation — e.g. title only on vertical, collapseBelow / overflowLabel only on horizontal).

import { Nav, type NavItem } from '@dloizides/ui-nav';

<Nav orientation="horizontal" items={items} pathname={p} onNavigate={go} regionLabel={r} />  // NavBar
<Nav orientation="vertical"   items={items} pathname={p} onNavigate={go} title={t} regionLabel={r} />  // Sidebar

Nav is purely additive: Sidebar / Topbar / NavBar stay exported and unchanged.

One shell, three layouts — top | side | both (NavShell)

Where Nav answers the single-axis question (a NavBar or a Sidebar), NavShell owns the whole page composition and renders a top bar, a side rail, or BOTH together, chosen by a single layout prop. It wires a top bar into AppShell's header slot and a side rail into its sidebar slot, and inherits every AppShell capability (auth gate, loading/error/forbidden state cards, width policy, banner, the responsive hamburger + overlay drawer).

import { NavShell, type NavItem } from '@dloizides/ui-nav';

// layout="both" — a top bar (brand / global links + overflow "…" / user) AND a
// side rail (sections) together (the AML-console shape), now first-class.
<NavShell
  layout="both"
  pathname={usePathname()}
  onNavigate={(route) => router.push(route)}
  regionLabel={FM('a11y.nav')}
  topBar={{
    items: globalItems,                 // top links (overflow-capable)
    brand: <BrandLogo />,
    right: <><LanguageSwitcher /><UserChip /></>,
    overflowLabel: FM('menu.more'),
    barTheme: darkChromeTheme,          // optional: dark bar over a light page
    contentMaxWidth: 1180,              // optional: full-bleed bar, inner capped + centred
  }}
  sideRail={{
    items: sectionItems,                // side sections (leaf + expandable)
    title: FM('menu.title'),
    footer: <><DarkModeControl … /><LogoutButton /></>,
    collapsed: { range: { min: 768, max: 1024 } },   // optional 3-tier icon rail
  }}
  gate={{ pending, authenticated, onRedirect: () => router.push('/login') }}
  state={{ loading, error, forbidden }}
  width={{ max: 1180, wideMax: 1400, wideMinViewport: 1600 }}
  testID="app-shell"
>
  <PageContent />
</NavShell>
  • layout="top" → a top bar only (topBar); no rail.
  • layout="side" → a header + a persistent left rail of sections (sideRail); pass a structured <Topbar> as header for the language/notification/user slots.
  • layout="both" → both, together.

NavShell composes existing components, so it stays purely additive — AppShell, NavBar, and Sidebar are unchanged.

Three-tier responsive rail (AppShell.collapsedSidebar / CollapsedRail)

Supply a collapsedSidebar (or NavShell's sideRail.collapsed) to opt the rail into the erevna/katalogos scheme: the full rail on desktop, an intermediate persistent collapsed (icon-only) rail on a tablet band [min, max) (default { min: 768, max: 1024 }), and the overlay drawer on a phone. Omit it and the layout stays the unchanged two-tier scheme (full rail ≥768px, drawer below). CollapsedRail renders the icon rail from the same NavItem[] (each item's renderIcon, or a first-letter glyph fallback), with header/footer slots. The pure resolveRailMode decides the tier and is directly unit-testable.

Dark bar over a light page (NavBar.barTheme)

Pass barTheme (a UiTheme) to re-provision the theme for the bar only — a dark appbar above a light workspace (the AML v1 console). The live t / navigate are re-passed so labels keep localizing; every colour still flows through the theme (style-only). Custom appbar backgrounds (e.g. a radial-glow gradient) stay expressible via containerStyle.

Full-bleed bar with a centred content column (NavBar.contentMaxWidth)

Pass contentMaxWidth (px) — via NavBar.contentMaxWidth or NavShell.topBar.contentMaxWidth — to make the bar full-bleed while its content stays in a centred column. The OUTER container (which carries the bar background + bottom border from barTheme / theme surface + any containerStyle, e.g. AML's radial-glow appbar) spans the full viewport width; the INNER row (brand + links + right) is capped to contentMaxWidth and centred — the v1-console .ui-appbar (full-bleed) + .ui-appbar-inner { max-width; margin: 0 auto } shape. Omit it and the inner spans the full container (unchanged). The priority+ overflow "…" fit measures the capped inner's rendered width, so it keeps working against the content column, not the full-bleed container.

Cross-navigation pills (PillNav)

A row of fully-rounded pills from a NavItem[] — kefi's coral dashboard switcher. The active pill wears the accent (theme palette.primary). Folds in kefi's rule: it hides (renders null) unless there are at least minItems entries (default 1; kefi passes 2). Drop it into AppShell/NavShell's nav slot.

Colour-scheme control (DarkModeControl)

A controlled Light / Dark / System control (erevna/katalogos), in two variants: "segmented" (a highlighted segment row) and "cycle" (an icon+label button that advances to the next option per press). Options are pre-localized and value-agnostic (strings), so map your own preference enum without this package importing it. Put it in a Sidebar/CollapsedRail footer or a NavBar right slot.

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.

Search — desktop inline field · mobile ⌘K palette

The rail can be searched two ways, chosen by viewport. resolveSearchAffordance(viewport) is the pure rule: 'inline' at/above DEFAULT_SEARCH_BREAKPOINT (768px), 'palette' below.

  • Desktop (wide): opt the Sidebar into a persistent inline field that narrows the nav list live as you type. It is OFF by default — pass enableInlineSearch + pre-localized search labels:

    <Sidebar
      items={items} pathname={p} onNavigate={go} title={t} regionLabel={r}
      enableInlineSearch
      search={{
        placeholder: FM('nav.search'), hint: FM('nav.searchHint'),
        clearLabel: FM('nav.clear'), clearHint: FM('nav.clearHint'),
        emptyText: FM('nav.noMatches'),
      }}
    />

    The sidebar owns the query (uncontrolled) unless you pass searchQuery + onSearchChange. Filtering is the pure filterNavItems(items, query) (all query tokens must match a label; a matched group keeps its children, a group with a matched descendant is kept pruned to it).

  • Mobile (narrow): keep the ⌘K CommandPalette (a persistent field costs too much vertical space). The useCommandPaletteHotkey shortcut stays bound at every width.

So a shell renders resolveSearchAffordance(width) === 'inline' ? <Sidebar enableInlineSearch …/> : <CommandPalette …/> (or simply passes enableInlineSearch only on the full-rail branch).

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