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

@alphinex/hooks

v1.0.2

Published

Cross-cutting React hooks shared across the platform (useDisclosure, useMediaQuery, ...).

Readme

@alphinex/hooks

Small, cross-cutting React hooks shared across the platform — no UI, no styling, just reusable stateful logic that @alphinex/ui components and app code both build on.

useDisclosure

Shared open/close boolean state for Dialog, Drawer, Menu, Popover, Tooltip, and anything else with an open/closed concept. Takes an optional defaultOpen, onOpen, and onClose:

import { useDisclosure } from "@alphinex/hooks";

function DeleteConfirmDialog() {
  const { isOpen, open, close, toggle } = useDisclosure({
    onClose: () => console.log("dialog closed"),
  });

  return (
    <>
      <button onClick={open}>Delete</button>
      {isOpen && <Dialog onDismiss={close}>Are you sure?</Dialog>}
    </>
  );
}

useControllableState

Backs every stateful @alphinex/ui component: uncontrolled by default, but transparently switches to controlled mode when value/onChange are supplied — one implementation, no behavioral divergence between the two modes. Pass value as anything other than undefined to switch it to controlled:

import { useControllableState } from "@alphinex/hooks";

interface TabsProps {
  value?: string;
  defaultValue?: string;
  onChange?: (value: string) => void;
}

function Tabs({ value, defaultValue = "overview", onChange }: TabsProps) {
  const [activeTab, setActiveTab] = useControllableState({
    value,
    defaultValue,
    onChange,
  });

  return (
    <div role="tablist">
      <button onClick={() => setActiveTab("overview")} aria-selected={activeTab === "overview"}>
        Overview
      </button>
      {/* ... */}
    </div>
  );
}

// Uncontrolled: <Tabs defaultValue="settings" />
// Controlled:   <Tabs value={tab} onChange={setTab} />

In uncontrolled mode, setValue accepts a plain value or a functional updater, same as useState. In controlled mode, calling setValue never touches internal state — it just resolves the next value (applying your updater against the current controlled value if you passed a function) and calls onChange with it; the parent owns re-rendering with the new value.

useMediaQuery

Reactively tracks a CSS media query via useSyncExternalStore — SSR-safe, returns false until the client can evaluate window.matchMedia:

import { useMediaQuery } from "@alphinex/hooks";

function ResponsiveNav() {
  const isDesktop = useMediaQuery("(min-width: 48rem)");
  return isDesktop ? <DesktopNav /> : <MobileNav />;
}

useDebouncedValue

Returns value, updated only after it has been stable for delayMs — the standard pattern for debouncing a search input before firing a request:

import { useState } from "react";
import { useDebouncedValue } from "@alphinex/hooks";

function SearchBox() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebouncedValue(query, 300);

  // effect/query hook keyed on debouncedQuery, not query
  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}

useClickOutside

Fires handler on any pointer event (mouse or touch) outside the returned ref's element — used for dismissing menus, popovers, and dropdowns. Pass enabled = false to disable the listener without unmounting (e.g. while the target is already closed):

import { useClickOutside } from "@alphinex/hooks";

function DropdownMenu({ onClose }: { onClose: () => void }) {
  const ref = useClickOutside<HTMLDivElement>(onClose);
  return <div ref={ref}>{/* menu items */}</div>;
}

useLocalStorage

Persists a piece of state to localStorage, JSON-serialized, with the same tuple shape as useState. SSR-safe — falls back to defaultValue when window isn't available, and silently keeps working in-memory if storage is unavailable (private browsing, quota exceeded):

import { useLocalStorage } from "@alphinex/hooks";

function SidebarCollapseToggle() {
  const [collapsed, setCollapsed] = useLocalStorage("sidebar-collapsed", false);
  return (
    <button onClick={() => setCollapsed((prev) => !prev)}>
      {collapsed ? "Expand" : "Collapse"}
    </button>
  );
}

usePrevious

Returns the value from the previous render — undefined on the first render. Useful for comparing against a prop/state change (e.g. to animate only on transition):

import { usePrevious } from "@alphinex/hooks";

function Counter({ count }: { count: number }) {
  const previousCount = usePrevious(count);
  const direction = previousCount === undefined ? null : count > previousCount ? "up" : "down";
  return <span data-direction={direction}>{count}</span>;
}

See documentation/ARCHITECTURE.md for the full package contract, dependency rules, and roadmap placement.