@danielwh2/contribution-graph 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Belyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,327 @@
1
+ # contribution-graph
2
+
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/contribution-graph"><img src="https://img.shields.io/npm/v/contribution-graph?color=cb3837&label=npm" alt="npm version"></a>
5
+ <img src="https://img.shields.io/badge/framework-agnostic-22c55e" alt="framework agnostic">
6
+ <img src="https://img.shields.io/badge/bundle-7kB-8b5cf6" alt="bundle size">
7
+ <img src="https://img.shields.io/badge/react-✓-61dafb" alt="react">
8
+ <img src="https://img.shields.io/badge/vue-✓-42b883" alt="vue">
9
+ <img src="https://img.shields.io/badge/solid-✓-2c4f7c" alt="solid">
10
+ <img src="https://img.shields.io/badge/svelte-✓-ff3e00" alt="svelte">
11
+ <img src="https://img.shields.io/badge/typescript-✓-3178c6" alt="typescript">
12
+ </p>
13
+
14
+ Framework-agnostic GitHub-style contribution heatmap for any app.
15
+
16
+ Render a year of activity into any element as a crisp SVG or HTML grid — week
17
+ columns, day rows, month labels, weekday labels, intensity colors, smooth
18
+ slot-text tooltips, custom cell colors, and click handlers.
19
+
20
+ ## 📦 Install
21
+
22
+ ```bash
23
+ npm install contribution-graph
24
+ ```
25
+
26
+ ```bash
27
+ pnpm add contribution-graph
28
+ ```
29
+
30
+ ```bash
31
+ bun add contribution-graph
32
+ ```
33
+
34
+ ```bash
35
+ yarn add contribution-graph
36
+ ```
37
+
38
+ ## 🚀 Quick start
39
+
40
+ ```ts
41
+ import "contribution-graph/style.css";
42
+ import { contributionGraph } from "contribution-graph";
43
+
44
+ const data = [
45
+ { date: "2026-06-01", count: 3 },
46
+ { date: "2026-06-02", count: 7 },
47
+ // ...one entry per day with contributions
48
+ ];
49
+
50
+ const graph = contributionGraph(document.querySelector("#graph")!, data, {
51
+ until: "2026-06-22",
52
+ onCellClick: (day) => console.log(day.date, day.count),
53
+ });
54
+
55
+ graph.set(newData); // update
56
+ graph.destroy(); // clean up
57
+ ```
58
+
59
+ Import the CSS once, pass your data, get a heatmap. That's it.
60
+
61
+ ## 🧩 API
62
+
63
+ ```ts
64
+ interface ContributionDay {
65
+ date: string; // YYYY-MM-DD
66
+ count: number;
67
+ color?: string; // optional per-cell color override
68
+ }
69
+
70
+ interface ContributionGraphController {
71
+ readonly element: HTMLElement;
72
+ set(data: ContributionDay[], options?: ContributionGraphOptions): void;
73
+ destroy(): void;
74
+ }
75
+
76
+ function contributionGraph(
77
+ element: HTMLElement,
78
+ data: ContributionDay[],
79
+ options?: ContributionGraphOptions,
80
+ ): ContributionGraphController;
81
+ ```
82
+
83
+ | Method | What it does |
84
+ | --- | --- |
85
+ | `set(data, options?)` | Re-render with new data and/or options |
86
+ | `destroy()` | Remove the graph and clean up listeners |
87
+
88
+ ## ⚛️ React
89
+
90
+ ```tsx
91
+ import "contribution-graph/style.css";
92
+ import { ContributionGraph } from "contribution-graph/react";
93
+
94
+ <ContributionGraph
95
+ data={data}
96
+ options={{ until: "2026-06-22", onCellClick: (day) => alert(day.date) }}
97
+ />
98
+ ```
99
+
100
+ ## 💚 Vue
101
+
102
+ ```vue
103
+ <script setup lang="ts">
104
+ import "contribution-graph/style.css";
105
+ import { ContributionGraph } from "contribution-graph/vue";
106
+ </script>
107
+
108
+ <template>
109
+ <ContributionGraph :data="data" :options="{ until: '2026-06-22' }" />
110
+ </template>
111
+ ```
112
+
113
+ ## 🔷 Solid
114
+
115
+ ```tsx
116
+ import "contribution-graph/style.css";
117
+ import { contributionGraph } from "contribution-graph/solid";
118
+
119
+ <div use:contributionGraph={{ data, options: { until: "2026-06-22" } }} />
120
+ ```
121
+
122
+ ## 🧡 Svelte
123
+
124
+ ```svelte
125
+ <script lang="ts">
126
+ import "contribution-graph/style.css";
127
+ import { contributionGraph } from "contribution-graph/svelte";
128
+ </script>
129
+
130
+ <div use:contributionGraph={{ data, options: { until: "2026-06-22" } }} />
131
+ ```
132
+
133
+ ## ⚙️ Options
134
+
135
+ | Option | Default | Description |
136
+ | --- | --- | --- |
137
+ | `weeks` | `53` | Number of week columns |
138
+ | `cellSize` | `11` | Cell size in px |
139
+ | `cellGap` | `3` | Gap between cells in px |
140
+ | `cellRadius` | `3` | Cell corner radius in px |
141
+ | `colors` | GitHub greens | Colors from least to most active (length = `levels`) |
142
+ | `levels` | `5` | Intensity levels including the empty level |
143
+ | `showLabels` | `true` | Show month and weekday labels |
144
+ | `labelColor` | `#8b949e` | Label text color |
145
+ | `labelFontSize` | `11` | Label font size in px |
146
+ | `until` | today (UTC) | End date of the range, `Date` or `YYYY-MM-DD` |
147
+ | `tooltip` | — | `(day) => string \| null` for smooth popup tooltips |
148
+ | `onCellClick` | — | `(day) => void` — `day` has `date`, `count`, `level` |
149
+ | `showCounts` | `false` | Use HTML cells. Pair with `countFontSize` to show in-cell counts |
150
+ | `countColors` | auto-contrasted | Text colors per level for in-cell count labels |
151
+ | `countFontSize` | `0` | Count label font size in px. `0` hides in-cell counts |
152
+ | `monthLabels` | `["Jan", "Feb", ...]` | Array of 12 month labels. Use `""` to hide a month |
153
+ | `weekdayLabels` | `["Mon", "", "Wed", ...]` | Array of 7 weekday labels (Mon..Sun). Use `""` to hide a row |
154
+ | `weekStart` | `1` | First day of each column: `0` = Sunday, `1` = Monday |
155
+ | `hud` | `false` | Wrap the graph in the cinematic "GIT COMMITS" HUD (see below) |
156
+ | `profile` | `false` | Wrap the graph in a sleek luxury GitHub profile widget (see below) |
157
+
158
+ ### 💎 Profile mode (Luxury)
159
+
160
+ Set `profile: { username: "yourname" }` to wrap the component in a stunning dark-mode GitHub profile card. It mimics the classic GitHub UI but elevates it with a deep `#0d1117` background, floating drop shadows, a smooth glowing effect on the activity cells, and a bold activity count.
161
+
162
+ ```ts
163
+ import "contribution-graph/style.css";
164
+
165
+ contributionGraph(el, data, {
166
+ profile: { username: "zzzzshawn" },
167
+ until: "2026-06-22"
168
+ });
169
+ ```
170
+
171
+ ### 🛰️ HUD mode
172
+
173
+ Set `hud: true` to render the graph inside a cinematic heads-up display: a
174
+ corner-bracketed glass panel over an animated teal→ember glitch backdrop, with
175
+ auto-computed week stats and commit streak rolling in via
176
+ [slot-text](https://www.npmjs.com/package/slot-text).
177
+
178
+ ```ts
179
+ import "contribution-graph/style.css";
180
+ import "slot-text/style.css"; // required — powers the rolling numbers
181
+
182
+ contributionGraph(el, data, { hud: true, until: "2026-06-22" });
183
+ ```
184
+
185
+ The HUD derives everything it shows straight from your data — **this week** and
186
+ **last week** totals, the delta %, and the current **day streak** (with delta
187
+ coloring: green when up, amber when down). It applies its own monochrome,
188
+ Sunday-first, single-letter-weekday defaults; any explicit option above still
189
+ overrides them (e.g. pass `colors` for a different ramp, or `cellSize`). The
190
+ whole HUD is scoped inside the host element, so size and place it like any
191
+ other block.
192
+
193
+ ### 🎨 Custom colors
194
+
195
+ ```ts
196
+ contributionGraph(el, data, {
197
+ colors: ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"],
198
+ levels: 5,
199
+ });
200
+ ```
201
+
202
+ Any palette works — pass 5 colors for 5 levels, 4 for 4, etc. The first color
203
+ is always the "no contributions" color.
204
+
205
+ To override the color of **individual specific cells**, pass a `color` string in your data:
206
+
207
+ ```ts
208
+ const data = [
209
+ { date: "2026-06-21", count: 12 },
210
+ { date: "2026-06-22", count: 50, color: "#ff0000" }, // specific override
211
+ ];
212
+ ```
213
+
214
+ ### GitHub contribution data
215
+
216
+ GitHub's contribution calendar is available through the official GraphQL API. Use the exported helper to fetch and normalize it into `ContributionDay[]`:
217
+
218
+ ```ts
219
+ import { contributionGraph, fetchGitHubContributions } from "contribution-graph";
220
+
221
+ const result = await fetchGitHubContributions({
222
+ username: "octocat",
223
+ token: process.env.GITHUB_TOKEN,
224
+ from: "2026-01-01",
225
+ to: "2026-12-31",
226
+ });
227
+
228
+ contributionGraph(el, result.data, {
229
+ until: result.to,
230
+ });
231
+ ```
232
+
233
+ For browser apps, do not ship a GitHub token to users. Call `fetchGitHubContributions()` from your server with `token`, or call a safe server proxy from the browser with `endpoint` and no token. The helper only sends `token` to GitHub's official GraphQL endpoint:
234
+
235
+ ```ts
236
+ const result = await fetchGitHubContributions({
237
+ username: "octocat",
238
+ endpoint: "/api/github-contributions",
239
+ });
240
+ ```
241
+
242
+ If you want to preserve GitHub's per-day colors instead of using your palette, set `includeGitHubColors: true`.
243
+
244
+ ### 💬 Tooltips
245
+
246
+ ```ts
247
+ contributionGraph(el, data, {
248
+ tooltip: (day) =>
249
+ day.count === 0
250
+ ? `No contributions on ${day.dateLabel}`
251
+ : `${day.count} contributions on ${day.dateLabel}`,
252
+ });
253
+ ```
254
+
255
+ Each `day` carries `date` (raw `YYYY-MM-DD`), `count`, `level`, and
256
+ `dateLabel` — a human-readable UTC date produced with
257
+ [dayjs](https://dayjs.dev) (e.g. `Jun 22, 2026`). Return `null` to disable
258
+ the tooltip for a given day.
259
+
260
+ ### 🖱️ Click handling
261
+
262
+ ```ts
263
+ contributionGraph(el, data, {
264
+ onCellClick: (day) => {
265
+ console.log(day.date, day.dateLabel, day.count, day.level);
266
+ },
267
+ });
268
+ ```
269
+
270
+ ### 🔢 HTML mode and optional count labels
271
+
272
+ Set `showCounts: true` to render cells as HTML elements instead of SVG. Counts
273
+ stay hidden by default for compact graphs. Add `countFontSize` to show animated
274
+ in-cell count labels powered by [slot-text](https://www.npmjs.com/package/slot-text).
275
+
276
+ ```ts
277
+ import "contribution-graph/style.css";
278
+ import "slot-text/style.css";
279
+ import { contributionGraph } from "contribution-graph";
280
+
281
+ const graph = contributionGraph(el, data, {
282
+ showCounts: true,
283
+ countFontSize: 9,
284
+ until: "2026-06-22",
285
+ });
286
+
287
+ graph.set(newData);
288
+ ```
289
+
290
+ Text colors auto-contrast with the cell background. Override with `countColors`:
291
+
292
+ ```ts
293
+ contributionGraph(el, data, {
294
+ showCounts: true,
295
+ countFontSize: 9,
296
+ colors: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
297
+ countColors: ["#57606a", "#1f2328", "#1f2328", "#ffffff", "#ffffff"],
298
+ });
299
+ ```
300
+
301
+ ## 📅 Date handling
302
+
303
+ All date math uses **UTC** so graphs render identically in every timezone.
304
+ Weeks start on **Sunday** (GitHub convention). The range ends at `until` and is
305
+ snapped back so the first column begins on a Sunday. Days beyond `until` in the
306
+ final week are omitted.
307
+
308
+ Data entries are matched by `YYYY-MM-DD`; days with no entry default to count 0.
309
+ Human-readable labels (tooltip/click `day.dateLabel`) are formatted with dayjs
310
+ in UTC mode via the exported `formatHumanDate` helper.
311
+
312
+ ## 📝 Notes
313
+
314
+ - Browser-only DOM utility with no framework runtime dependency.
315
+ - Smooth tooltips and optional in-cell count labels are powered by `slot-text`.
316
+ - React, Vue, Solid, and Svelte are optional peer dependencies — plain JS users
317
+ don't need them.
318
+ - Import `contribution-graph/style.css` once before rendering. Import
319
+ `slot-text/style.css` when you use the animated tooltip or count label styles.
320
+ - Low-level helpers also exported: `buildGraph`, `clearGraph`, `buildHtmlGraph`,
321
+ `clearHtmlGraph`, `buildHudGraph`, `clearHudGraph`, `computeGrid`,
322
+ `levelForCount`, `parseDate`, `formatDate`, `formatHumanDate`,
323
+ `resolveOptions`, `fetchGitHubContributions`.
324
+
325
+ ## License
326
+
327
+ MIT
@@ -0,0 +1,150 @@
1
+ /**
2
+ * contribution-graph core — GitHub-style heatmap renderer.
3
+ *
4
+ * Renders an SVG contribution graph into a host element. All date math uses
5
+ * UTC to stay timezone-stable; weeks start on Sunday (GitHub convention).
6
+ * Human-readable date labels are produced with dayjs (UTC mode).
7
+ */
8
+ export interface ContributionDay {
9
+ /** ISO date: YYYY-MM-DD. */
10
+ date: string;
11
+ /** Number of contributions that day. */
12
+ count: number;
13
+ /** Optional custom color for this specific cell. Overrides the level-based color. */
14
+ color?: string;
15
+ }
16
+ export interface ContributionGraphOptions {
17
+ /** Number of week columns (default 53). */
18
+ weeks?: number;
19
+ /** First day of each week column: 0 = Sunday, 1 = Monday (default 1). */
20
+ weekStart?: 0 | 1;
21
+ /** Cell size in px (default 11). */
22
+ cellSize?: number;
23
+ /** Gap between cells in px (default 3). */
24
+ cellGap?: number;
25
+ /** Cell corner radius in px (default 2). */
26
+ cellRadius?: number;
27
+ /** Colors from least to most active, length = levels (default GitHub greens). */
28
+ colors?: string[];
29
+ /** Number of intensity levels including the empty level (default 5). */
30
+ levels?: number;
31
+ /** Show month and weekday labels (default true). */
32
+ showLabels?: boolean;
33
+ /** Show month labels along the top (default follows showLabels). */
34
+ showMonthLabels?: boolean;
35
+ /** Show weekday labels along the left (default follows showLabels). */
36
+ showWeekdayLabels?: boolean;
37
+ /** Label text color (default "#8b949e"). */
38
+ labelColor?: string;
39
+ /** Label font size in px (default 10). */
40
+ labelFontSize?: number;
41
+ /** End date of the range, as Date or ISO string (default today, UTC). */
42
+ until?: Date | string;
43
+ /** Custom tooltip text. Return null to disable the tooltip. */
44
+ tooltip?: (day: ContributionDayContext) => string | null;
45
+ /** Called when a cell is clicked. */
46
+ onCellClick?: (day: ContributionDayContext) => void;
47
+ /** Render HTML cells instead of SVG cells (default false). */
48
+ showCounts?: boolean;
49
+ /** Text colors per level for optional count labels (default: auto-contrasted). */
50
+ countColors?: string[];
51
+ /** Count label font size in px. Set 0 to hide labels (default 0). */
52
+ countFontSize?: number;
53
+ /** Array of 12 month labels. Empty strings hide the month. */
54
+ monthLabels?: string[];
55
+ /**
56
+ * Array of 7 weekday labels, row order Mon..Sun. Empty strings hide that row's
57
+ * label (default ["Mon", "", "Wed", "", "Fri", "", ""]).
58
+ */
59
+ weekdayLabels?: string[];
60
+ /**
61
+ * Wrap the graph in the cinematic "GIT COMMITS" HUD: corner-bracketed glass
62
+ * panel, animated glitch backdrop, auto-computed week stats + streak, and
63
+ * rolling numbers. Applies its own monochrome, Sunday-first defaults which
64
+ * any explicit option above still overrides (default false).
65
+ */
66
+ hud?: boolean;
67
+ /**
68
+ * Wrap the graph in a luxury GitHub profile widget.
69
+ */
70
+ profile?: boolean | {
71
+ username?: string;
72
+ };
73
+ }
74
+ /** Parse a YYYY-MM-DD string into a UTC midnight Date. */
75
+ export declare function parseDate(value: string): Date;
76
+ /** Format a UTC Date as YYYY-MM-DD. */
77
+ export declare function formatDate(date: Date): string;
78
+ /**
79
+ * Format a YYYY-MM-DD date as a human-readable label in UTC, e.g.
80
+ * "Jun 22, 2026". Used for tooltip/click-handler context so consumers don't
81
+ * have to format ISO strings themselves.
82
+ */
83
+ export declare function formatHumanDate(date: string): string;
84
+ /**
85
+ * A contribution day augmented with the context callbacks receive: the
86
+ * intensity level and a human-readable date label.
87
+ */
88
+ export interface ContributionDayContext extends ContributionDay {
89
+ /** Intensity level 0..levels-1. */
90
+ level: number;
91
+ /** Human-readable date label, e.g. "Jun 22, 2026" (UTC). */
92
+ dateLabel: string;
93
+ }
94
+ export interface ResolvedOptions {
95
+ weeks: number;
96
+ weekStart: 0 | 1;
97
+ cellSize: number;
98
+ cellGap: number;
99
+ cellRadius: number;
100
+ colors: string[];
101
+ levels: number;
102
+ showLabels: boolean;
103
+ showMonthLabels: boolean;
104
+ showWeekdayLabels: boolean;
105
+ labelColor: string;
106
+ labelFontSize: number;
107
+ until: Date;
108
+ showCounts: boolean;
109
+ countColors?: string[];
110
+ countFontSize: number;
111
+ monthLabels: string[];
112
+ weekdayLabels: string[];
113
+ tooltip?: (day: ContributionDayContext) => string | null;
114
+ onCellClick?: (day: ContributionDayContext) => void;
115
+ }
116
+ export declare function resolveOptions(options?: ContributionGraphOptions): ResolvedOptions;
117
+ export interface GridCell extends ContributionDay {
118
+ /** Intensity level 0..levels-1. */
119
+ level: number;
120
+ /** Column index (week). */
121
+ week: number;
122
+ /** Row index (day of week, 0 = Sunday). */
123
+ day: number;
124
+ }
125
+ export interface Grid {
126
+ cells: GridCell[];
127
+ weeks: number;
128
+ /** Month label entries: { label, xOffset } relative to the grid start. */
129
+ months: {
130
+ label: string;
131
+ col: number;
132
+ }[];
133
+ max: number;
134
+ totalCols: number;
135
+ }
136
+ /**
137
+ * Build the grid for a date range ending at `opts.until`, spanning `opts.weeks`
138
+ * week columns. The range is snapped back so the first column starts on Monday.
139
+ */
140
+ export declare function computeGrid(data: ContributionDay[], opts: ResolvedOptions): Grid;
141
+ /** Map a count to an intensity level 0..levels-1. */
142
+ export declare function levelForCount(count: number, max: number, levels: number): number;
143
+ export declare function normalizeCssColor(value: string | undefined, fallback: string): string;
144
+ /**
145
+ * Render the graph SVG into `element`. Replaces prior content.
146
+ * Returns the SVG element for callers that need to attach listeners.
147
+ */
148
+ export declare function buildGraph(element: HTMLElement, data: ContributionDay[], options?: ContributionGraphOptions): SVGElement;
149
+ /** Remove the rendered graph and root class. */
150
+ export declare function clearGraph(element: HTMLElement): void;