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

@affino/menu-react

v1.0.0

Published

Headless React menu components powered by @affino/menu-core

Readme

@affino/menu-react

React's most precise headless menu system — deterministic controllers, diagonal pointer intent, and zero styling opinions.

import { UiMenu, UiMenuTrigger, UiMenuContent, UiMenuItem } from "@affino/menu-react"

const actions = ["Edit", "Duplicate", "Archive"]

export function ActionsMenu() {
  return (
    <UiMenu>
      <UiMenuTrigger>Actions</UiMenuTrigger>
      <UiMenuContent>
        {actions.map((action) => (
          <UiMenuItem key={action} onSelect={() => console.log(action)}>
            {action}
          </UiMenuItem>
        ))}
      </UiMenuContent>
    </UiMenu>
  )
}
npm install @affino/menu-react

Core Features

  • Headless React components powered by @affino/menu-core
  • Smart mouse prediction keeps submenus open during diagonal travel
  • Unlimited submenu depth with shared tree state and safe focus handoffs
  • Context-menu + click menu support with a single trigger surface
  • Auto positioning and viewport collision handling without extra deps
  • Snapshot-driven controller subscriptions for zero wasted renders
  • asChild pattern so you can slot in any DOM structure or design system primitive
  • Programmatic controller API for imperative flows (open, close, highlight, select)
  • CSS variables for seamless theming (light/dark/brand palettes)
  • First-class TypeScript types for every prop, event payload, and controller method

Docs → ../../docs/index.md

Getting Started

  1. npm install @affino/menu-react
  2. Import the base styles once in your app entry so the design tokens are available everywhere:
import "@affino/menu-react/styles.css"
  1. Compose <UiMenu>, <UiMenuTrigger>, <UiMenuContent>, and <UiMenuItem> to build dropdowns, context menus, or nested navigation.
  2. Nest <UiSubMenu> components for infinite submenu depth.
  3. Use the supplied hooks (useMenu, useMenuShortcuts) to drive programmatic flows.

Controller Surface

useMenuController now builds on top of createMenuTree, so the hook exposes the same pointer + geometry adapters the core uses internally. Opt into them whenever you need custom positioning or analytics—no instanceof SubmenuCore casts required.

const controller = useMenuController({ kind: "root", options, callbacks })

useEffect(() => {
  if (!controller.state.open) return
  console.log("Active item", controller.state.activeItemId)
}, [controller.state.open, controller.state.activeItemId])

controller.recordPointer?.({ x: event.clientX, y: event.clientY })
controller.setTriggerRect?.(controller.triggerRef.current?.getBoundingClientRect() ?? null)
controller.setPanelRect?.(controller.panelRef.current?.getBoundingClientRect() ?? null)
controller.setAnchor(controller.triggerRef.current?.getBoundingClientRect() ?? null)
  • recordPointer feeds diagonal intent detection, enabling alternate input sources (stylus, remote desktop, Playwright pointer hooks).
  • setTriggerRect / setPanelRect keep submenu geometry fresh after layout transitions without poking private APIs.
  • setAnchor supports context menus, palette-style overlays, and anchored inspectors with zero custom timers.

Headless usage with createMenuTree

Rolling your own renderer? Instantiate the helper directly to get the same branch objects the React hook relies on.

import { createMenuTree } from "@affino/menu-core"

const tree = createMenuTree({ options: { openDelay: 75, closeDelay: 120 } })
tree.root.registerItem("file")

const submenu = tree.createSubmenu({
  parent: tree.root,
  parentItemId: "file",
})

const syncGeometry = () => {
  submenu.geometry?.sync({
    trigger: document.querySelector("[data-file]")?.getBoundingClientRect() ?? null,
    panel: document.querySelector("[data-file-panel]")?.getBoundingClientRect() ?? null,
  })
}

window.addEventListener("pointermove", (event) => {
  submenu.pointer?.record({ x: event.clientX, y: event.clientY })
})

syncGeometry()

// Clean up every branch when you unmount
tree.destroy()

This keeps third-party design systems, test harnesses, and custom renderers aligned with the official adapters.

Live Examples

Try it yourself in under 30 seconds:

FAQ

  • Does it work with Next.js / SSR? Yes — everything renders on the server, hydrates on the client, and pointer-only features no-op until window exists.
  • Can I open at pointer coordinates? Call controller.setAnchor({ x, y, width: 0, height: 0 }) before controller.open("pointer") or rely on the built-in context menu trigger.
  • How do I keep my own DOM? Pass asChild to any trigger or item component and Affino will clone + merge props while preserving refs/events.
  • What about 1000+ items? Pair <UiMenuContent> with your favorite virtualization library; controllers stay snapshot-driven and never depend on render count.

Browser Support

  • Evergreen Chromium, Firefox, Safari (ES2020+)
  • React 18+
  • TypeScript 5+

License

MIT © affino OSS