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

@rosado-io/streakr

v0.3.0

Published

The universal Git contribution calendar. Aggregate activity from GitHub, GitLab, and more into one beautiful, framework-agnostic heatmap.

Readme

Streakr

npm version License: MIT CI Semantic Release

What is Streakr?

Streakr is a framework-agnostic, drop-in contribution-calendar component. It unifies activity from GitHub, GitLab, Bitbucket, self-hosted GitLab, or your own providers into one themeable heatmap with year tabs, provider toggles, loading/empty/ready states, stats, and an interactive tooltip.

The package is written in vanilla TypeScript and ships without React, Vue, or other runtime dependencies. Use the component directly from DOM code, wrap it in your framework of choice, or use the provider utilities independently to fetch and normalize contribution data.

Installation

Install from npm:

npm install @rosado-io/streakr
# or
pnpm add @rosado-io/streakr
# or
yarn add @rosado-io/streakr

Or load the ESM build and CSS from a CDN. Pin the version in production:

<div id="streakr"></div>

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@rosado-io/[email protected]/dist/streakr.css"
/>
<script type="module">
  import { createStreakr } from "https://esm.sh/@rosado-io/[email protected]";

  createStreakr({
    target: document.getElementById("streakr"),
    years: [2026],
    getDays: () => [],
  });
</script>

Quickstart

<div id="streakr"></div>
import { createStreakr } from "@rosado-io/streakr";
import "@rosado-io/streakr/styles.css";

const sk = createStreakr({
  target: document.getElementById("streakr")!,
  theme: "dark",
  accent: "#39d353",
  years: [2024, 2025, 2026],
  year: 2026,
  getDays: (year) => [
    {
      date: new Date(2026, 0, 12),
      total: 5,
      sources: { github: 4, gitlab: 1 },
    },
    // ...one entry per day in the requested year
  ],
});

// Update reactively
sk.update({ theme: "light", accent: "#a371f7" });

// Tear down
sk.destroy();

The CSS file ships dark/light tokens, accent tinting, modal, tooltip, skeleton, and responsive heatmap styles. You can override any token with your own CSS variables (--sk-*).

API

createStreakr(options): StreakrInstance

| Option | Type | Default | Description | | --- | --- | --- | --- | | target | HTMLElement | required | Where to mount the component. | | theme | "dark" \| "light" | "dark" | Visual theme. | | accent | string (CSS color) | "#39d353" | Drives chip/stat highlights and (optionally) the heatmap palette. | | tintHeatmap | boolean | true | When true, the heatmap palette is derived from accent. | | showProviders | boolean | true | Toggle the provider chip row. | | showStats | boolean | true | Toggle the stats grid. Historical years show Active Rate instead of Current Streak. | | state | "loading" \| "empty" \| "ready" | "ready" | Override the lifecycle state. | | years | number[] | required | Years offered in the year tabs / picker. | | year | number | last years entry | Currently selected year. | | getDays | (year) => StreakrDay[] | required | Returns the days for a given year. | | providers | StreakrProvider[] | [github, gitlab, bitbucket] | Provider configuration (see below). | | onYearChange | (year) => void | — | Fires after the user picks a different year. | | onProviderToggle | (key, enabled, all) => void | — | Fires after a provider chip is toggled. |

StreakrInstance

| Method | Description | | --- | --- | | update(patch) | Patches any StreakrOptions value and re-renders. Undefined values are ignored. | | setYear(year) | Changes the active year, fires onYearChange, and re-renders. | | setProviders(next) | Patches provider enabled/disabled state by provider key. | | destroy() | Removes DOM nodes, resize observers, tooltip, and keyboard listeners. |

StreakrDay

interface StreakrDay {
  date: Date;
  total: number;                       // sum across all providers
  sources?: Record<string, number>;    // keyed by `StreakrProvider.key`
}

The component recomputes total based on which providers are toggled on, so the value you pass in should be the "all providers" total.

StreakrProvider

interface StreakrProvider {
  key: string;     // matches a key in StreakrDay.sources
  name: string;    // display label
  color: string;   // dot/accent color in chips and tooltip
  icon?: string;   // optional inline SVG (built-in for github, gitlab, bitbucket)
}

Custom providers

Built-in icons cover GitHub, GitLab, and Bitbucket. To support arbitrary providers, pass your own providers array — anything goes:

createStreakr({
  target: el,
  years: [2026],
  providers: [
    { key: "gitea",   name: "Gitea",   color: "#609926" },
    { key: "forgejo", name: "Forgejo", color: "#d97706" },
    {
      key: "linear",
      name: "Linear",
      color: "#5e6ad2",
      icon: '<svg viewBox="0 0 24 24" width="13" height="13"><path d="…" fill="currentColor"/></svg>',
    },
  ],
  getDays: (year) => myFetchActivity(year), // each day has sources keyed by gitea/forgejo/linear
});

⚠️ Security note on icon. The string is inserted into the DOM as raw HTML. Only pass trusted, statically-defined SVG markup. Never forward user-supplied or remotely-fetched SVG without sanitizing it first (e.g. with DOMPurify) — SVG can contain inline scripts and lead to XSS.

Lifecycle states

sk.update({ state: "loading" });   // shimmer skeleton
sk.update({ state: "empty" });     // empty illustration with the active year
sk.update({ state: "ready" });     // normal render

"ready" with a year that has zero contributions automatically falls back to the empty illustration.

Framework snippets

The component is vanilla DOM. These snippets show the lifecycle wrapper only.

Vanilla

import { createStreakr } from "@rosado-io/streakr";
import "@rosado-io/streakr/styles.css";

const el = document.querySelector<HTMLElement>("#streakr");
if (!el) throw new Error("Missing #streakr");

const sk = createStreakr({
  target: el,
  years: [2026],
  getDays: (year) => loadDays(year),
});

window.addEventListener("beforeunload", () => sk.destroy(), { once: true });

React

import { useEffect, useRef } from "react";
import { createStreakr, type StreakrDay } from "@rosado-io/streakr";
import "@rosado-io/streakr/styles.css";

export function StreakrWidget({
  years,
  getDays,
}: {
  years: number[];
  getDays: (year: number) => StreakrDay[];
}) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!ref.current) return;
    const sk = createStreakr({ target: ref.current, years, getDays });
    return () => sk.destroy();
  }, [years, getDays]);

  return <div ref={ref} />;
}

Vue

<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from "vue";
import { createStreakr, type StreakrDay, type StreakrInstance } from "@rosado-io/streakr";
import "@rosado-io/streakr/styles.css";

const props = defineProps<{ years: number[]; getDays: (y: number) => StreakrDay[] }>();
const el = ref<HTMLElement | null>(null);
let sk: StreakrInstance | null = null;

onMounted(() => {
  if (el.value) sk = createStreakr({ target: el.value, years: props.years, getDays: props.getDays });
});
onBeforeUnmount(() => sk?.destroy());
</script>

<template>
  <div ref="el" />
</template>

Svelte

<script lang="ts">
  import { onMount, onDestroy } from "svelte";
  import { createStreakr, type StreakrDay, type StreakrInstance } from "@rosado-io/streakr";
  import "@rosado-io/streakr/styles.css";

  export let years: number[];
  export let getDays: (year: number) => StreakrDay[];

  let el: HTMLElement;
  let sk: StreakrInstance | null = null;

  onMount(() => { sk = createStreakr({ target: el, years, getDays }); });
  onDestroy(() => sk?.destroy());
</script>

<div bind:this={el}></div>

Bringing in real data

Streakr ships data utilities for fetching from Git hosts and shaping the result for the component.

Providers

import {
  GitHubProvider,
  GitLabProvider,
  aggregate,
  normalizeEventsToDaily,
} from "@rosado-io/streakr";

const events = await aggregate(
  [
    new GitHubProvider({ token: process.env.GITHUB_TOKEN! }),
    new GitLabProvider({ token: process.env.GITLAB_TOKEN! }),
  ],
  { user: "octocat", start: "2026-01-01", end: "2026-12-31" },
);

const days = normalizeEventsToDaily(events).map((day) => ({
  date: new Date(`${day.date}T00:00:00`),
  total: day.count,
  sources: day.sources,
}));

aggregate(providers, params) returns a merged array; failed providers are skipped. See docs/providers.md for authentication, self-hosted GitLab, rate-limit notes, and writing your own provider.

Utilities

  • normalizeEventsToDaily(events) — merge duplicates, fill gaps, sort.
  • computeStreaks(days) — total, best streak, current streak.
  • buildCalendarGrid(days, options?) — week-by-day grid with intensity levels.

These are independent helpers — useful if you want to plug custom data into createStreakr or build something different on top.

Package exports

import {
  createStreakr,
  GitHubProvider,
  GitLabProvider,
  aggregate,
  normalizeEventsToDaily,
  computeStreaks,
  buildCalendarGrid,
} from "@rosado-io/streakr";

Privacy

Streakr does not send data to any Streakr-owned service and does not include analytics, cookies, or background network calls. The component renders whatever getDays returns into the DOM. Provider classes call the configured Git host directly from the environment where your code runs.

Treat tokens as secrets:

  • Don't expose GitHub or GitLab PATs in public browser code.
  • Prefer server-side fetching or an authenticated backend proxy for real data.
  • Cache provider responses to reduce API calls and rate-limit pressure.
  • Contribution counts can reveal work patterns. Aggregate, redact, or limit ranges before showing private activity on public pages.
  • Custom provider icons are inserted as raw SVG HTML; only use trusted static markup or sanitize it first.

Versioning

Streakr follows semver, with one caveat: while the package is pre-1.0, minor releases (0.x.0) may include breaking changes.

If you want to pin against breakage, use a tilde range (~0.2.0) instead of a caret (^0.2.0) until 1.0.

Development

pnpm install
pnpm dev      # runs the demo / landing
pnpm test
pnpm build

License

MIT