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

heysnap-web-viewers

v0.4.1

Published

Client-side React components for rendering PDF, DOCX, PPT, XLSX, images, video, audio, code, markdown, and HTML in the browser.

Readme

heysnap-web-viewers

Client-side React components for previewing documents and media in the browser. One toolbar vocabulary across the family — filename, format-specific controls, zoom, download — and aggressive optional-peer hygiene so a consumer only installs the engines for the viewers they actually use.

Ten viewers in the box:

| Format | Component | Subpath | | ----------- | ----------------------- | ------------------------------ | | PDF | HeySnapPdfViewer | heysnap-web-viewers/pdf | | DOCX | HeySnapDocxViewer | heysnap-web-viewers/docx | | PPTX | HeySnapPPTViewer | heysnap-web-viewers/ppt | | XLSX | HeySnapXlsxViewer | heysnap-web-viewers/xlsx | | Image | HeySnapImageViewer | heysnap-web-viewers/image | | Video | HeySnapVideoViewer | heysnap-web-viewers/video | | Audio | HeySnapAudioPlayer | heysnap-web-viewers/audio | | Source code | HeySnapCodeViewer | heysnap-web-viewers/code | | Markdown | HeySnapMarkdownViewer | heysnap-web-viewers/markdown | | HTML | HeySnapHtmlViewer | heysnap-web-viewers/html |

Install

pnpm add heysnap-web-viewers react react-dom @hugeicons/react @hugeicons/core-free-icons

That's the floor — React plus the icon set every toolbar uses. Each viewer's heavy engine deps are declared as optional peer dependencies: install only what you need.

| Viewer | Additional peers | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | pdf | @embedpdf/core, @embedpdf/engines, @embedpdf/plugin-{document-manager,interaction-manager,pan,render,scroll,thumbnail,viewport,zoom} | | docx | docx-preview, jszip | | ppt | — (renders converted slide images; consumer supplies the manifest) | | xlsx | @glideapps/glide-data-grid, @fontsource/geist-sans | | image | — (uses the platform <img>) | | video | — (uses the platform <video>) | | audio | — (uses the platform <audio>) | | code | monaco-editor, @monaco-editor/react | | markdown | react-markdown, remark-gfm | | html | — (renders into a sandboxed <iframe>) |

For example, a project using only PDF and code installs:

pnpm add heysnap-web-viewers react react-dom \
  @hugeicons/react @hugeicons/core-free-icons \
  @embedpdf/core @embedpdf/engines \
  @embedpdf/plugin-document-manager @embedpdf/plugin-interaction-manager \
  @embedpdf/plugin-pan @embedpdf/plugin-render @embedpdf/plugin-scroll \
  @embedpdf/plugin-thumbnail @embedpdf/plugin-viewport @embedpdf/plugin-zoom \
  monaco-editor @monaco-editor/react

peerDependenciesMeta marks every engine as "optional": true, so npm/pnpm/yarn won't shout about the ones you skipped.

Stylesheet

The XLSX viewer ships a small stylesheet for its grid chrome. Import it once at the top level of your app if you use the XLSX viewer:

import "heysnap-web-viewers/style.css";

Other viewers inject their styles themselves (markdown's preview rules; the rest use inline styles), so this import is a no-op for them.

Import

The package ships per-viewer subpath entries so bundlers tree-shake unused viewers to zero:

import { HeySnapPdfViewer } from "heysnap-web-viewers/pdf";
import { HeySnapCodeViewer } from "heysnap-web-viewers/code";

// or, for convenience, from the aggregate entry:
import { HeySnapPdfViewer, HeySnapCodeViewer } from "heysnap-web-viewers";

Subpath imports are preferred — they let your bundler skip viewer chunks (and their optional peers) that aren't actually used.

Every viewer mounts at width: 100% / height: 100% and expects its parent to constrain its size.

HeySnapPdfViewer

A self-contained PDF viewer with a customizable toolbar (filename, cursor / hand-pan tools, zoom controls, download), an animated thumbnail sidebar, and full keyboard / screen-reader support.

import { useState } from "react";
import { HeySnapPdfViewer } from "heysnap-web-viewers/pdf";

export function App() {
  const [file, setFile] = useState<File | null>(null);
  return (
    <>
      <input
        type="file"
        accept="application/pdf"
        onChange={(e) => setFile(e.target.files?.[0] ?? null)}
      />
      {file && (
        <div style={{ height: 600 }}>
          <HeySnapPdfViewer src={file} />
        </div>
      )}
    </>
  );
}

src accepts

  • A URL string
  • A File from an <input type="file">
  • A Blob
  • An ArrayBuffer or Uint8Array

Notable props

| Prop | Type | Default | Notes | | -------------------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | showHeader | boolean | true | Render the toolbar | | headerBackground | string | "#fafafa" | Toolbar background | | headerForeground | string | "#1f1f1f" | Toolbar foreground; drives every hover/active tint via currentColor + color-mix, so flipping this one prop swaps the whole chrome between light and dark | | bodyBackground | string | "#e9eaed" | Gutter color around the rendered pages | | defaultSidebarOpen | boolean | false | Whether the thumbnail sidebar starts open | | sidebarBackground | string | "#f5f5f7" | Sidebar background | | sidebarWidth | number | 180 | Sidebar width in px |

HeySnapDocxViewer

Read-only DOCX viewer with the same toolbar vocabulary as the PDF viewer. Built on docx-preview, with a compact custom header layered on top.

import { HeySnapDocxViewer } from "heysnap-web-viewers/docx";

<div style={{ height: 600 }}>
  <HeySnapDocxViewer src={file} />
</div>;

src accepts

URL string · File · Blob · ArrayBuffer · Uint8Array.

Notable props

| Prop | Type | Default | Notes | | ------------------ | ---------------- | ----------- | --------------------------------------------------------------------------------------------------------------------- | | headerForeground | string | "#1f1f1f" | Drives toolbar tints and the editor's internal --doc-text* tokens, so inner UI text stays readable in dark mode | | bodyBackground | string | "#e9eaed" | Gutter color and the editor's --doc-bg token | | showRuler | boolean | false | Horizontal page ruler | | rulerUnit | "inch" \| "cm" | "cm" | Units when ruler is shown | | showMarginGuides | boolean | false | Dotted page-margin guides | | initialZoom | number | 1.0 | Startup zoom |

Set the three color props together — everything else cascades:

<HeySnapDocxViewer
  src={file}
  headerBackground="#1f242c"
  headerForeground="#e6e8eb"
  bodyBackground="#0b0e13"
/>

HeySnapPPTViewer

Slide viewer that renders pre-converted slide images plus an optional thumbnail sidebar. The viewer doesn't convert .pptx itself — your backend (or HeySnapPPTConversion hooks) produces a SlideManifest, and the viewer renders it.

import { HeySnapPPTViewer, type SlideManifest } from "heysnap-web-viewers/ppt";

const manifest: SlideManifest = {
  slides: [
    { src: "/slides/1.png", width: 1280, height: 720 },
    { src: "/slides/2.png", width: 1280, height: 720 },
  ],
  name: "Deck.pptx",
};

<HeySnapPPTViewer src={manifest} />;

src accepts

A SlideManifest object, or a URL string pointing at a JSON manifest of the same shape.

HeySnapXlsxViewer

Spreadsheet viewer built on @glideapps/glide-data-grid. Supports sheet tabs, formula bar, frozen panes, and the same zoom / theme vocabulary as the rest of the family.

Remember to import the stylesheet:

import "heysnap-web-viewers/style.css";
import { HeySnapXlsxViewer } from "heysnap-web-viewers/xlsx";
<div style={{ height: 600 }}>
  <HeySnapXlsxViewer src={workbook} />
</div>

src accepts

A WorkbookJSON produced by your XLSX conversion pipeline. (The library doesn't parse .xlsx directly — pair this with a server-side converter or the heysnap-xlsxl CLI.)

HeySnapImageViewer

Lightweight image viewer with filename · zoom · download. Renders via the native <img>, so format support tracks whatever the browser supports (PNG / JPEG / GIF / WebP / AVIF / SVG / …) and there are no engine peers to install.

<HeySnapImageViewer src={file} />

src accepts

URL string · data: URL · blob: URL · File · Blob · ArrayBuffer · Uint8Array. Object URLs are revoked automatically on src change and on unmount.

Notable props

| Prop | Type | Default | Notes | | ---------------- | ---------------------- | ----------------- | ---------------------------------- | | bodyBackground | string | "#e9eaed" | Gutter color around the image | | imageStyle | CSSProperties | — | Merged onto the rendered <img> | | alt | string | resolved filename | Image alt text | | onError | (err: Error) => void | — | Called on resolve / decode failure |

Zoom is fit-to-window × user zoom: at 100 % the image fills the viewport without up-scaling past native pixels; + walks the preset ladder 25 / 50 / 75 / 100 / 125 / 150 / 200 / 300 / 400 / 600 / 800 %. The badge reads user zoom (not effective pixel scale).

HeySnapVideoViewer

Native <video> player with the family's title + download chrome. Codec/container support tracks the browser (MP4 H.264, WebM, Ogg, …).

<HeySnapVideoViewer src={file} controls autoPlay={false} muted={false} />

src accepts

URL string · File · Blob · ArrayBuffer · Uint8Array.

Notable props

| Prop | Type | Default | Notes | | ---------------- | -------------------------------- | ------------ | --------------------------------------------------- | | controls | boolean | true | Native player controls | | autoPlay | boolean | false | Start on load (most browsers require muted: true) | | muted | boolean | false | Start muted | | loop | boolean | false | Loop when finished | | preload | "none" \| "metadata" \| "auto" | "metadata" | Preload behavior | | poster | string | — | Poster image URL | | bodyBackground | string | "#000000" | Letterbox color |

HeySnapAudioPlayer

Native <audio> player with the same chrome. Codec support tracks the browser (MP3, AAC, Opus, FLAC, WAV, …).

<HeySnapAudioPlayer src={file} />

Same src shape and controls / autoPlay / muted / loop / preload props as the video viewer. The native pill is capped at 560 px wide so it doesn't sprawl in wide containers.

HeySnapCodeViewer

Read-only Monaco editor with filename · font-size · word-wrap · download in the header. Languages are inferred from the filename extension; consumers can override via the language prop.

import { HeySnapCodeViewer } from "heysnap-web-viewers/code";

<HeySnapCodeViewer src="/snippets/example.ts" />;

src accepts

URL string · File · Blob · ArrayBuffer · Uint8Array. Buffers decode as UTF-8.

Notable props

| Prop | Type | Default | Notes | | ------------------ | --------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------ | | language | string | inferred from filename | Monaco language id ("typescript", "python", "plaintext", …) | | theme | string | "heysnap-light" | Monaco theme id | | initialFontSize | number | 12 | Font size in px (persisted to localStorage) | | defaultWordWrap | "on" \| "off" | "off" | Soft-wrap initial state | | wordWrap | "on" \| "off" | — | Controlled wrap state | | onWordWrapChange | (next) => void | — | Wrap-toggle callback | | options | editor.IStandaloneEditorConstructionOptions | — | Forwarded to Monaco; readOnly, fontSize, and wordWrap are locked by the viewer |

heysnap Monaco themes

The viewer ships two tight three-color themes that match the rest of the family in light and dark modes. They register automatically the first time the viewer mounts.

import { HEYSNAP_LIGHT_ID, HEYSNAP_DARK_ID } from "heysnap-web-viewers/code";

<HeySnapCodeViewer src={file} theme={darkMode ? HEYSNAP_DARK_ID : HEYSNAP_LIGHT_ID} />

You can also register them on a Monaco instance that lives outside the viewer:

import { defineHeysnapThemes } from "heysnap-web-viewers/code";

defineHeysnapThemes(monaco); // pass your @monaco-editor/react `Monaco` instance

HeySnapMarkdownViewer

Renders Markdown via react-markdown + remark-gfm. The header carries a Preview / Code toggle next to the filename — Preview shows the rendered markdown, Code swaps in HeySnapCodeViewer on the same surface so the raw source reads as native chrome rather than a separate viewer. The header also surfaces zoom (preview) and word-wrap (code).

import { HeySnapMarkdownViewer } from "heysnap-web-viewers/markdown";

<HeySnapMarkdownViewer src="/docs/README.md" />
// or in-memory:
<HeySnapMarkdownViewer src={{ text: "# Hello", name: "hello.md" }} />

src accepts

URL string · File · Blob · ArrayBuffer · Uint8Array · { text, name } for in-memory content.

Notable props

| Prop | Type | Default | Notes | | ----------------------- | ------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | defaultMode | "preview" \| "code" | "preview" | Initial view mode | | mode / onModeChange | — | — | Controlled mode pair | | codeTheme | string | — | Monaco theme id forwarded to the embedded code view | | initialZoom | number | 1.0 | Preview zoom (0.5–3× ladder) | | bodyBackground | string | "#ffffff" | Background for both modes; match this to the heysnap Monaco bg (#FFFFFF light / #0F0F11 dark) so the toggle reads as a content swap | | components | import("react-markdown").Components | — | Shallow-merged on top of the defaults; plug in syntax-highlighted code blocks, custom link wrappers, etc. |

GFM features (tables, task lists, strikethrough, autolinks) are on by default. The preview stylesheet is injected into document.head once on first mount — no CSS plumbing on the consumer side. All accent colors derive from currentColor + color-mix, so dark mode "just works" when the surrounding foreground is light.

HeySnapHtmlViewer

Renders HTML markup inside a sandboxed <iframe> (sandbox="allow-same-origin" by default — same-origin lets the viewer apply live zoom; no allow-scripts token means embedded JS is inert, which is the safe default for previewing untrusted markup). Same Preview / Code toggle and zoom / word-wrap chrome as the markdown viewer.

import { HeySnapHtmlViewer } from "heysnap-web-viewers/html";

<HeySnapHtmlViewer src={{ text: "<h1>Hi</h1>", name: "hi.html" }} />;

src accepts

URL string · File · Blob · ArrayBuffer · Uint8Array · { text, name }.

Notable props

| Prop | Type | Default | Notes | | --------------------------------------- | -------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------- | | defaultMode / mode / onModeChange | — | — | Preview/Code mode (controlled or uncontrolled) | | codeTheme | string | — | Monaco theme forwarded to the code view | | initialZoom | number | 1.0 | Applied to the iframe's documentElement.style.zoom (no srcdoc re-render, scroll preserved) | | sandbox | string | "allow-same-origin" | Override only if you trust the content. Adding allow-scripts enables JS inside the iframe — treat it as a trust decision | | bodyBackground | string | "#ffffff" | Painted around the iframe; the iframe itself is transparent so this shows through |

Tree-shaking & bundle hygiene

  • Subpath imports (heysnap-web-viewers/pdf, …) are the canonical way to consume the package. Bundlers drop chunks for unused viewers entirely.
  • sideEffects is set to ["**/*.css"] so CSS imports aren't shaken away. JS modules are pure.
  • Every viewer's heavy engine is an optional peer: install only what you use.
  • The aggregate entry (heysnap-web-viewers) is provided for convenience and will still tree-shake when your bundler does scope-hoisting; subpaths are the safer bet for minimum bundle size.

License

MIT © Ananya