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

rvlib-mantine

v0.2.0

Published

Mantine app baseline for Bun+React projects: X/H/I globals (core, hooks, Material Symbols icon proxy), a house theme factory, an AppRoot wrapper (MantineProvider + modals + notifications + css), chart export (svg to PNG with styles inlined, a copy/downloa

Readme

rvlib-mantine

Mantine app baseline for Bun+React projects: install once, build screens immediately.

// main.tsx
import "rvlib-mantine/setup" // X/H/I globals — FIRST import
import { AppRoot, makeHouseTheme } from "rvlib-mantine"
import { createRoot } from "react-dom/client"

const theme = makeHouseTheme({ colors: { brand: myBrandRamp }, primaryColor: "brand" })
createRoot(document.getElementById("root")!).render(
    <AppRoot theme={theme}>{/* router */}</AppRoot>,
)
  • Globals: after the setup import, every screen reads X.Button (@mantine/core), H.useDisclosure (@mantine/hooks), I.settings (Material Symbols proxy — components materialize on demand, cached), and the Maybe<T> type — zero per-file imports.
  • Icons need the Material Symbols font in index.html (link tag is in src/icons.tsx's header comment).
  • Theme: makeHouseTheme = house defaults (radius md, Inter, striped tables), project brings its brand ramps. Anything is overridable.
  • AppRoot: MantineProvider + modals + notifications + css imports.
  • Chart data: groupSum / groupCount (rows to [label, value] pairs, sorted desc), toChart (pairs to chart data with the cycling CHART_PALETTE).

Chart export

A chart the user can take away: copied, downloaded as PNG, or handed to the app's PDF writer. Wrap each card:

import { ChartContextMenu, configureMantineI18n } from "rvlib-mantine"

configureMantineI18n({ lang: "fr" }) // once, in the app entry; English by default

<ChartContextMenu title="Revenue per site" empty={data.length === 0} onPdf={savePdf}>
    <DonutChart data={data} />
</ChartContextMenu>

Right click opens the menu where the pointer is, the kebab at the top right opens it for a finger. The PDF entry shows only when onPdf(card, title) is given: this package never imports a PDF writer, the app builds its document from cardToPngBlob(card, { type: "image/jpeg" }) and pngDataUrl.

  • svgToPngBlob(svg, { scale?, background?, type? }) and cardToPngBlob(card, opts): the svg with every computed style inlined on a clone (Mantine paints through CSS variables and currentColor, which a standalone svg cannot resolve), drawn on a canvas at scale 2. The card variant draws the title above and redraws the legend below the chart.
  • ImageType: "image/png" for a file the user keeps, "image/jpeg" for an image inside a PDF (jsPDF stores PNG pixels raw, and a report of twenty charts weighs tens of MB that way).
  • legendEntries(card): the { label, color } rows of a card's legend, from the ColorSwatch rows Mantine renders.
  • copyPng(blob | Promise<Blob>): takes the PENDING blob, since Safari only honours a clipboard write started inside the click. downloadBlob(blob, filename), pngDataUrl(blob), slug(title, fallback?) for the filename.
  • Every failure is a ChartImageError with a code, the offscreen timeout included. chartErrorText(e, mantineI18n.t) words it in the active locale.
  • Strings: configureMantineI18n({ lang }) picks the locale for everything this package renders (EN and FR ship, the choice persists under rv-lang like every other holder), configureMantineI18n({ dict: { fr: { copy: "Copier" } } }) overlays single strings and merges across calls, and a labels prop overrides one component instance. The lib never reads your app's i18n singleton.
  • The wrapper carries data-chart-title and data-chart-empty (CHART_TITLE_ATTR, CHART_EMPTY_ATTR). chartCardsIn(root), cardTitle(card), isSettled(card) and chartsSettled(root) read them: they are how a page export finds every chart.

For a document, render the FULL view off screen at one width, whatever tab or phone the export was started from:

import { renderOffscreen, useStaticCharts } from "rvlib-mantine"

// every chart reads the flag: no animation under an export, or the first frame is empty
const isStatic = useStaticCharts()
<PieChart data={data} pieProps={{ isAnimationActive: !isStatic }} />

const { cards, dispose } = await renderOffscreen({ width: 1100, render: <ExportView data={data} />, theme })
try {
    for (const card of cards) doc.addImage(await pngDataUrl(await cardToPngBlob(card, { type: "image/jpeg" })), ...)
} finally {
    dispose()
}

renderOffscreen({ width, render, theme?, settled?, timeoutMs? }) mounts the view under StaticCharts in a laid out host placed where no reader looks (a display: none node has no size and a chart in it never draws), waits until every card is painted or marked empty (settled defaults to chartsSettled), and returns { host, cards, dispose }. A render that does not settle in timeoutMs (8 s) is disposed and thrown. A chart must read THIS package's useStaticCharts: an app local context is invisible to the offscreen render, the chart animates, isSettled sees the svg on its first empty frame, and the document carries blank charts with no error.

Page state in the URL (rvlib-mantine/router)

The URL is the persisted state: a refresh, a bookmark and a pasted link show the same view. The tab is a route segment, the filters are the search string.

import { useRouteTab, useSearchState, field, text, oneOf, list } from "rvlib-mantine/router"

// /settings/:tab, an absent or unknown segment shows "sites", a change keeps the search string
const [tab, setTab] = useRouteTab({ tabs: ["sites", "users"] as const, fallback: "sites", base: "/settings" })

const spec = {
    period: oneOf(["7 d", "30 d", "1 year"] as const, "30 d", { slug: (p) => p.replace(/\s+/g, "") }), // ?period=7d
    from: field("", (raw) => (/^\d{4}-\d{2}-\d{2}$/.test(raw) ? raw : undefined), { key: "du" }),      // ?du=…, garbage falls back
    site: list((raw) => raw || undefined),                                                           // ?site=a&site=b, [] when absent
    q: text(),
}
const [state, patch] = useSearchState(spec) // state.period is typed "7 d" | "30 d" | "1 year"
patch({ site: ["north"] })                   // a replace navigation: back walks pages, never filter clicks
  • A value at its fallback is absent from the URL, so a bare path is the default view and a shared link carries only what the user changed.
  • key in a helper's options names the query key when it differs from the state key. Keys the spec does not name survive a write.
  • encodeSearch(spec, state, base?) and decodeSearch(spec, params) are the codec under the hook, for a link builder or a test.

Peer deps: @mantine/* v8, react and react-dom ≥18, mobx ^7 and rvlib-mobx ^1 (the locale is observable), react-router ^7 for the router entry only. Ships as TypeScript source (Bun, Vite, and modern bundlers consume it directly).