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

@sarc-mediq/react-diagrams

v1.0.1

Published

Freehand sketch & annotation canvas for React — draw, erase, add draggable text labels, and export to SVG/PNG/JPEG

Readme

@sarc-mediq/react-diagrams

Freehand sketch & annotation canvas for React — draw, erase, add draggable text labels, and export to SVG/PNG/JPEG. A modern, dependency-free drawing surface, used by the findings app.

Install

Published to the public npm registry — no authentication or .npmrc needed:

yarn add @sarc-mediq/react-diagrams
# peers
yarn add react react-dom

Usage

DiagramCanvas is the public component. Drive it through its imperative ref — set the active mode, then draw/erase/place text on the surface; read or load strokes via the export/load methods.

import { useRef } from "react"
import {
  DiagramCanvas,
  CanvasMode,
  type DiagramCanvasRef,
  type CanvasPath,
  type CanvasText,
} from "@sarc-mediq/react-diagrams"

export function Example() {
  const canvas = useRef<DiagramCanvasRef>(null)

  const handleChange = (paths: CanvasPath[], texts: CanvasText[]) => {
    // Persist or react to the latest strokes/labels.
    console.log(paths, texts)
  }

  const setMode = (mode: CanvasMode) => {
    if (canvas.current) {
      canvas.current.mode = mode
    }
  }

  return (
    <>
      <div>
        <button onClick={() => setMode(CanvasMode.pen)}>Pen</button>
        <button onClick={() => setMode(CanvasMode.eraser)}>Eraser</button>
        <button onClick={() => setMode(CanvasMode.text)}>Text</button>
        <button onClick={() => canvas.current?.undo()}>Undo</button>
        <button onClick={() => canvas.current?.clearCanvas()}>Clear</button>
      </div>

      <DiagramCanvas
        ref={canvas}
        width="600px"
        height="400px"
        strokeColor="#1a73e8"
        strokeWidth={4}
        eraserWidth={8}
        backgroundImage="/diagram-base.png"
        onChange={handleChange}
      />
    </>
  )
}

Exporting

const png = await canvas.current?.exportImage("png")
const svg = await canvas.current?.exportSvg()
const paths = canvas.current?.exportPaths()

API

DiagramCanvas props

| Prop | Type | Notes | |---|---|---| | strokeColor | string | Pen colour. | | strokeWidth | number | Pen width (default 4). | | eraserWidth | number | Eraser width (default 8). | | canvasColor | string | Background fill when no backgroundImage. | | backgroundImage | string | URL drawn behind strokes. | | backgroundImageFit | "stretch" \| "contain" | stretch (default) fills the surface, may distort; contain sizes the surface to the image's aspect ratio (centred, letterboxed, undistorted). | | exportWithBackgroundImage | boolean | Include the background in exports. | | width / height | string \| number | Surface dimensions. | | keepScale | boolean | Rescale strokes when the surface resizes. | | withTimestamp | boolean | Record per-stroke timing (getSketchingTime). | | referenceSize | Size | Reference dimensions for addPath coordinates. | | onChange | (paths, texts) => void | Fires on every mutation. | | onStroke | (path, isEraser) => void | Fires when a stroke completes. |

DiagramCanvasRef methods

mode (CanvasMode), keepScale, size, clearCanvas(), undo(), redo(), addText(text, position), addPath(points, width, color), exportImage(type), exportSvg(), exportPaths() / exportTexts(), exportPathsPromise() / exportTextsPromise(), loadPaths(paths, size?), loadTexts(texts, size?), getSketchingTime(), resetCanvas().

Modes (CanvasMode)

Set the active mode through the ref (canvas.current.mode = CanvasMode.pen):

| Mode | Behaviour | |---|---| | pen | Freehand drawing at strokeColor / strokeWidth. | | remove | Click a stroke to delete the whole stroke (a coarse "stroke eraser"). | | eraser | Drag to erase pixels through an SVG mask, at eraserWidth. | | text | Click to drop an editable, draggable text label. | | none | Inert — no drawing, no removal. |

The surface sets a matching cursor for each mode (crosshair for pen, an eraser cursor for both erase modes, a text caret for text), so consumers ship no cursor CSS of their own.

Undo/redo is operation-based: undo() reverses the last action and redo() reapplies it — including stroke removals (a stroke removed in remove mode is restored with undo()). The pixel eraser only affects strokes drawn before it; strokes drawn afterwards are not erased.

DiagramSurface (the lower-level SVG surface) and the data types (CanvasPath, CanvasText, CanvasMode, Point, Size, ExportImageType) are also exported.

Development

| Script | Action | |---|---| | yarn build | Bundle ESM + CJS + type declarations to dist/ (tsup). | | yarn dev | Rebuild on change. | | yarn ladle | Run the component preview (Ladle dev server). | | yarn ladle:build | Build the static Ladle site. | | yarn test | Unit tests once (vitest + jsdom). | | yarn test:watch | Watch-mode unit tests. | | yarn test:coverage | Unit tests with V8 coverage. | | yarn e2e:install | Install the Playwright Chromium browser (run once before e2e). | | yarn e2e | End-to-end tests (Playwright; auto-starts Ladle). | | yarn typecheck | tsc --noEmit. | | yarn lint | ESLint over src. | | yarn format | Prettier write over src. | | yarn format:check | Prettier check (no writes) over src. |

Git hooks

Git hooks live in .githooks/ and are wired automatically on yarn install (via a prepare script that sets core.hooksPath) — no Husky, no extra deps. They mirror CI so pushes don't fail it:

  • pre-commit — fast gate: format:check + lint + typecheck + test.
  • pre-push — full CI mirror: the above plus build + e2e.

e2e needs the Playwright browser once (yarn e2e:install). Escape hatches: SKIP_E2E=1 git push skips just e2e; git commit/push --no-verify skips the whole gate.

Toolchain

  • Build: tsup (esbuild) → ESM + CJS + .d.ts
  • Preview: Ladle (Vite) — src/**/*.stories.tsx
  • Test: Vitest + Testing Library (jsdom) for units; Playwright over Ladle for end-to-end
  • Lint: ESLint flat config + typescript-eslint, mirroring portal-ui's type-aware strict ruleset
  • Types: TypeScript strict, React 18 peer
  • Versioning: the version in package.json is the source of truth; a push to the release branch publishes it (see below)

Releasing

Publishing to the public npm registry is publish-on-version-change, driven by the release branch. Everyday commits on master are free-form — no prefixes, no changeset files. Cutting a release is a deliberate act:

# 1. Bump the version on master (edits package.json only — no commit/tag)
yarn version minor          # or: patch | major   (yarn release:minor, …)
git commit -am "1.3.0"      # commit the bump

# 2. Merge master into release and push
git checkout release
git merge master
git push                    # triggers .github/workflows/release.yml

The push to release runs .github/workflows/release.yml, which compares package.json's version against the registry and, only if it isn't published yet, runs npm publish (gated by prepublishOnly: lint + typecheck + test + build) and then gh release create vX.Y.Z --generate-notes to tag it and cut a GitHub Release with notes built from the merged history.

| Bump | Command | Example | |---|---|---| | patch | yarn version patch | 1.2.0 → 1.2.1 | | minor | yarn version minor | 1.2.0 → 1.3.0 | | major | yarn version major | 1.2.0 → 2.0.0 |

Re-pushing release without a version change is a no-op (the version is already on the registry), so there's nothing to guard against.

One-time setup: create the branch with git switch -c release master && git push -u origin release, and configure npm trusted publishing (OIDC) for the package on npmjs (org SARC-MedIQ, repo react-diagrams, workflow release.yml). The workflow then publishes tokenless via OIDC — no stored secret. (Build provenance is intentionally off: it requires a public source repo, and this one is private.) Consumers need no token either.

Architecture decisions

The initial, hard-to-reverse decisions (standalone library, the findings data contract, build/preview/test tooling, the erase modes, operation-based undo/redo, backgroundImageFit, versioning) are recorded in docs/adr/.

License

MIT © SARC-MedIQ