@rsalianto/git-heatmap-react 0.1.0

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.
@@ -0,0 +1,33 @@
1
+ import * as react from 'react';
2
+ import { HeatmapData, LevelConfig, HeatmapTheme, HeatmapDay } from '@rsalianto/git-heatmap-core';
3
+
4
+ interface GitHeatmapProps {
5
+ data?: HeatmapData;
6
+ apiUrl?: string;
7
+ fetchData?: () => Promise<HeatmapData>;
8
+ levels?: LevelConfig[];
9
+ cellSize?: number;
10
+ cellGap?: number;
11
+ cellRadius?: number;
12
+ showTotal?: boolean;
13
+ showLegend?: boolean;
14
+ showMonthLabels?: boolean;
15
+ showDayLabels?: boolean;
16
+ theme?: Partial<HeatmapTheme>;
17
+ onDayClick?: (day: HeatmapDay) => void;
18
+ label?: string;
19
+ }
20
+ declare function GitHeatmap({ data: dataProp, apiUrl, fetchData, levels, cellSize, cellGap, cellRadius, showTotal, showLegend, showMonthLabels, showDayLabels, theme, onDayClick, label, }: GitHeatmapProps): react.JSX.Element | null;
21
+
22
+ type HeatmapStatus = "idle" | "loading" | "success" | "error";
23
+ declare function useHeatmapData(options: {
24
+ data?: HeatmapData;
25
+ apiUrl?: string;
26
+ fetchData?: () => Promise<HeatmapData>;
27
+ }): {
28
+ data: HeatmapData | null;
29
+ status: HeatmapStatus;
30
+ error: Error | null;
31
+ };
32
+
33
+ export { GitHeatmap, type GitHeatmapProps, type HeatmapStatus, useHeatmapData };
@@ -0,0 +1,33 @@
1
+ import * as react from 'react';
2
+ import { HeatmapData, LevelConfig, HeatmapTheme, HeatmapDay } from '@rsalianto/git-heatmap-core';
3
+
4
+ interface GitHeatmapProps {
5
+ data?: HeatmapData;
6
+ apiUrl?: string;
7
+ fetchData?: () => Promise<HeatmapData>;
8
+ levels?: LevelConfig[];
9
+ cellSize?: number;
10
+ cellGap?: number;
11
+ cellRadius?: number;
12
+ showTotal?: boolean;
13
+ showLegend?: boolean;
14
+ showMonthLabels?: boolean;
15
+ showDayLabels?: boolean;
16
+ theme?: Partial<HeatmapTheme>;
17
+ onDayClick?: (day: HeatmapDay) => void;
18
+ label?: string;
19
+ }
20
+ declare function GitHeatmap({ data: dataProp, apiUrl, fetchData, levels, cellSize, cellGap, cellRadius, showTotal, showLegend, showMonthLabels, showDayLabels, theme, onDayClick, label, }: GitHeatmapProps): react.JSX.Element | null;
21
+
22
+ type HeatmapStatus = "idle" | "loading" | "success" | "error";
23
+ declare function useHeatmapData(options: {
24
+ data?: HeatmapData;
25
+ apiUrl?: string;
26
+ fetchData?: () => Promise<HeatmapData>;
27
+ }): {
28
+ data: HeatmapData | null;
29
+ status: HeatmapStatus;
30
+ error: Error | null;
31
+ };
32
+
33
+ export { GitHeatmap, type GitHeatmapProps, type HeatmapStatus, useHeatmapData };
package/dist/index.js ADDED
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GitHeatmap: () => GitHeatmap,
24
+ useHeatmapData: () => useHeatmapData
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/GitHeatmap.tsx
29
+ var import_react2 = require("react");
30
+ var import_git_heatmap_core = require("@rsalianto/git-heatmap-core");
31
+
32
+ // src/useHeatmapData.ts
33
+ var import_react = require("react");
34
+ function useHeatmapData(options) {
35
+ const [data, setData] = (0, import_react.useState)(options.data ?? null);
36
+ const [status, setStatus] = (0, import_react.useState)(options.data ? "success" : "idle");
37
+ const [error, setError] = (0, import_react.useState)(null);
38
+ const apiUrl = options.apiUrl;
39
+ const fetchData = options.fetchData;
40
+ const staticData = options.data;
41
+ const didLoad = (0, import_react.useRef)(false);
42
+ (0, import_react.useEffect)(() => {
43
+ if (staticData) {
44
+ setData(staticData);
45
+ setStatus("success");
46
+ return;
47
+ }
48
+ const resolver = fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
49
+ if (!resolver) return;
50
+ let cancelled = false;
51
+ didLoad.current = false;
52
+ setStatus("loading");
53
+ resolver().then((d) => {
54
+ if (!cancelled) {
55
+ setData(d);
56
+ setStatus("success");
57
+ }
58
+ }).catch((e) => {
59
+ if (!cancelled) {
60
+ setError(e instanceof Error ? e : new Error(String(e)));
61
+ setStatus("error");
62
+ }
63
+ });
64
+ return () => {
65
+ cancelled = true;
66
+ };
67
+ }, [staticData, apiUrl, fetchData]);
68
+ return { data, status, error };
69
+ }
70
+
71
+ // src/GitHeatmap.tsx
72
+ var import_jsx_runtime = require("react/jsx-runtime");
73
+ function themeToVars(theme) {
74
+ return {
75
+ "--ghm-color-l0": theme.colorL0,
76
+ "--ghm-color-l1": theme.colorL1,
77
+ "--ghm-color-l2": theme.colorL2,
78
+ "--ghm-color-l3": theme.colorL3,
79
+ "--ghm-color-l4": theme.colorL4,
80
+ "--ghm-text": theme.textColor,
81
+ "--ghm-tooltip-bg": theme.tooltipBg,
82
+ "--ghm-tooltip-border": theme.tooltipBorderColor,
83
+ "--ghm-tooltip-text": theme.tooltipTextColor,
84
+ "--ghm-font": theme.fontFamily,
85
+ "--ghm-fs": theme.fontSize
86
+ };
87
+ }
88
+ var BASE_STYLE = `
89
+ [data-git-heatmap] {
90
+ --ghm-color-l0: rgba(255,255,255,0.08);
91
+ --ghm-color-l1: #1c3d06;
92
+ --ghm-color-l2: #3a7510;
93
+ --ghm-color-l3: #6ab81e;
94
+ --ghm-color-l4: #aafd35;
95
+ --ghm-text: rgba(255,255,255,0.5);
96
+ --ghm-tooltip-bg: #1c2128;
97
+ --ghm-tooltip-border: rgba(255,255,255,0.1);
98
+ --ghm-tooltip-text: rgba(255,255,255,0.75);
99
+ --ghm-font: inherit;
100
+ --ghm-fs: 11px;
101
+ display: block;
102
+ position: relative;
103
+ width: 100%;
104
+ font-size: var(--ghm-fs);
105
+ font-family: var(--ghm-font);
106
+ color: var(--ghm-text);
107
+ user-select: none;
108
+ box-sizing: border-box;
109
+ }
110
+ [data-git-heatmap] * { box-sizing: border-box; }
111
+ `;
112
+ var styleInjected = false;
113
+ function ensureStyle() {
114
+ if (styleInjected || typeof document === "undefined") return;
115
+ const el = document.createElement("style");
116
+ el.textContent = BASE_STYLE;
117
+ document.head.appendChild(el);
118
+ styleInjected = true;
119
+ }
120
+ function levelColor(level) {
121
+ return `var(--ghm-color-l${level})`;
122
+ }
123
+ function GitHeatmap({
124
+ data: dataProp,
125
+ apiUrl,
126
+ fetchData,
127
+ levels = import_git_heatmap_core.DEFAULT_LEVELS,
128
+ cellSize = import_git_heatmap_core.CELL,
129
+ cellGap = import_git_heatmap_core.GAP,
130
+ cellRadius = 2,
131
+ showTotal = true,
132
+ showLegend = true,
133
+ showMonthLabels = true,
134
+ showDayLabels = true,
135
+ theme,
136
+ onDayClick,
137
+ label = "Contribution heatmap"
138
+ }) {
139
+ const step = cellSize + cellGap;
140
+ const { data, status, error } = useHeatmapData({ data: dataProp, apiUrl, fetchData });
141
+ const scrollRef = (0, import_react2.useRef)(null);
142
+ const wrapperRef = (0, import_react2.useRef)(null);
143
+ const [tappedDay, setTappedDay] = (0, import_react2.useState)(null);
144
+ const [tooltip, setTooltip] = (0, import_react2.useState)(null);
145
+ (0, import_react2.useEffect)(() => {
146
+ ensureStyle();
147
+ }, []);
148
+ (0, import_react2.useEffect)(() => {
149
+ if (data && scrollRef.current) {
150
+ scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
151
+ }
152
+ }, [data]);
153
+ const mergedTheme = { ...import_git_heatmap_core.DEFAULT_THEME, ...theme };
154
+ const cssVars = themeToVars(mergedTheme);
155
+ const formatTooltip = (day) => day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
156
+ if (error) {
157
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-git-heatmap": "", style: cssVars, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "var(--ghm-text)", opacity: 0.6, fontSize: "0.9em", padding: "1rem 0" }, children: "Could not load contribution data." }) });
158
+ }
159
+ if (status === "loading" || status === "idle") {
160
+ const skeletonCols = Array.from({ length: 53 });
161
+ const skeletonRows = Array.from({ length: 7 });
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-git-heatmap": "", style: { ...cssVars, opacity: 0.4 }, children: [
163
+ showTotal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: 16, width: 160, background: "var(--ghm-text)", borderRadius: 4, marginBottom: 12, opacity: 0.2 } }),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: cellGap }, children: skeletonCols.map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: cellGap }, children: skeletonRows.map((_2, j) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: cellSize, height: cellSize, borderRadius: cellRadius, background: "var(--ghm-color-l0)" } }, j)) }, i)) })
165
+ ] });
166
+ }
167
+ if (!data) return null;
168
+ const monthLabels = showMonthLabels ? (0, import_git_heatmap_core.buildMonthLabels)(data.weeks) : [];
169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-git-heatmap": "", ref: wrapperRef, style: cssVars, "aria-label": label, children: [
170
+ showTotal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { marginBottom: 12, color: "var(--ghm-text)" }, children: [
171
+ data.totalContributions.toLocaleString(),
172
+ " contributions in the last year"
173
+ ] }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: scrollRef, style: { overflowX: "auto", overflowY: "visible", paddingBottom: 4 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "inline-flex", flexDirection: "column", minWidth: data.weeks.length * step }, children: [
175
+ showMonthLabels && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", marginBottom: cellGap, marginLeft: showDayLabels ? 32 : 0 }, children: monthLabels.map(({ label: ml, col }, idx) => {
176
+ const nextCol = monthLabels[idx + 1]?.col ?? data.weeks.length;
177
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: (nextCol - col) * step, whiteSpace: "nowrap", fontSize: "0.9em", color: "var(--ghm-text)" }, children: ml }, ml + col);
178
+ }) }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex" }, children: [
180
+ showDayLabels && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: cellGap, marginRight: 6 }, children: Array.from({ length: 7 }).map((_, dow) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: cellSize, lineHeight: `${cellSize}px`, width: 26, textAlign: "right", paddingRight: 4, fontSize: "0.9em", color: "var(--ghm-text)" }, children: import_git_heatmap_core.DAY_LABELS[dow] ?? "" }, dow)) }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: cellGap }, children: data.weeks.map((week, wi) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: cellGap }, children: Array.from({ length: 7 }).map((_, dow) => {
182
+ const day = week.days[dow];
183
+ if (!day || !day.date) {
184
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: cellSize, height: cellSize } }, dow);
185
+ }
186
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
187
+ "div",
188
+ {
189
+ style: {
190
+ width: cellSize,
191
+ height: cellSize,
192
+ borderRadius: cellRadius,
193
+ background: levelColor(day.level),
194
+ cursor: "pointer",
195
+ transition: "opacity 0.15s"
196
+ },
197
+ onMouseEnter: (e) => {
198
+ const r = e.currentTarget.getBoundingClientRect();
199
+ const wr = wrapperRef.current?.getBoundingClientRect();
200
+ if (wr) setTooltip({ day, x: r.left - wr.left + cellSize / 2, y: r.top - wr.top });
201
+ },
202
+ onMouseLeave: () => setTooltip(null),
203
+ onClick: () => {
204
+ setTappedDay((prev) => prev?.date === day.date ? null : day);
205
+ onDayClick?.(day);
206
+ },
207
+ onMouseOver: (e) => {
208
+ e.currentTarget.style.opacity = "0.7";
209
+ },
210
+ onFocus: (e) => {
211
+ e.currentTarget.style.opacity = "0.7";
212
+ },
213
+ onMouseOut: (e) => {
214
+ e.currentTarget.style.opacity = "1";
215
+ },
216
+ onBlur: (e) => {
217
+ e.currentTarget.style.opacity = "1";
218
+ },
219
+ role: "button",
220
+ tabIndex: 0,
221
+ "aria-label": formatTooltip(day)
222
+ },
223
+ dow
224
+ );
225
+ }) }, wi)) })
226
+ ] })
227
+ ] }) }),
228
+ tappedDay && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 8, fontSize: "0.9em", color: "var(--ghm-text)", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 4, padding: "6px 12px" }, children: formatTooltip(tappedDay) }),
229
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: "0.9em", color: "var(--ghm-text)" }, children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Less" }),
231
+ levels.map((lvl, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
232
+ "div",
233
+ {
234
+ style: { width: cellSize, height: cellSize, borderRadius: cellRadius, background: `var(--ghm-color-l${i})` },
235
+ title: lvl.label
236
+ },
237
+ i
238
+ )),
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "More" })
240
+ ] }) }),
241
+ tooltip && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
242
+ "div",
243
+ {
244
+ style: {
245
+ pointerEvents: "none",
246
+ position: "absolute",
247
+ zIndex: 50,
248
+ left: tooltip.x,
249
+ top: tooltip.y - 6,
250
+ transform: "translate(-50%, -100%)",
251
+ marginBottom: 4,
252
+ background: "var(--ghm-tooltip-bg)",
253
+ border: "1px solid var(--ghm-tooltip-border)",
254
+ color: "var(--ghm-tooltip-text)",
255
+ fontSize: "0.9em",
256
+ borderRadius: 4,
257
+ padding: "3px 8px",
258
+ whiteSpace: "nowrap"
259
+ },
260
+ children: [
261
+ formatTooltip(tooltip.day),
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
263
+ position: "absolute",
264
+ left: "50%",
265
+ top: "100%",
266
+ transform: "translate(-50%, -50%) rotate(45deg)",
267
+ width: 8,
268
+ height: 8,
269
+ background: "var(--ghm-tooltip-bg)",
270
+ borderRight: "1px solid var(--ghm-tooltip-border)",
271
+ borderBottom: "1px solid var(--ghm-tooltip-border)"
272
+ } })
273
+ ]
274
+ }
275
+ )
276
+ ] });
277
+ }
278
+ // Annotate the CommonJS export names for ESM import in node:
279
+ 0 && (module.exports = {
280
+ GitHeatmap,
281
+ useHeatmapData
282
+ });
283
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/GitHeatmap.tsx","../src/useHeatmapData.ts"],"sourcesContent":["export { GitHeatmap } from \"./GitHeatmap\";\nexport type { GitHeatmapProps } from \"./GitHeatmap\";\nexport { useHeatmapData } from \"./useHeatmapData\";\nexport type { HeatmapStatus } from \"./useHeatmapData\";\n","\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n buildMonthLabels,\n CELL, GAP, STEP,\n DAY_LABELS,\n DEFAULT_LEVELS,\n DEFAULT_THEME,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, HeatmapTheme, LevelConfig } from \"@rsalianto/git-heatmap-core\";\nimport { useHeatmapData } from \"./useHeatmapData\";\n\nexport interface GitHeatmapProps {\n data?: HeatmapData;\n apiUrl?: string;\n fetchData?: () => Promise<HeatmapData>;\n levels?: LevelConfig[];\n cellSize?: number;\n cellGap?: number;\n cellRadius?: number;\n showTotal?: boolean;\n showLegend?: boolean;\n showMonthLabels?: boolean;\n showDayLabels?: boolean;\n theme?: Partial<HeatmapTheme>;\n onDayClick?: (day: HeatmapDay) => void;\n label?: string;\n}\n\nfunction themeToVars(theme: HeatmapTheme): Record<string, string> {\n return {\n \"--ghm-color-l0\": theme.colorL0,\n \"--ghm-color-l1\": theme.colorL1,\n \"--ghm-color-l2\": theme.colorL2,\n \"--ghm-color-l3\": theme.colorL3,\n \"--ghm-color-l4\": theme.colorL4,\n \"--ghm-text\": theme.textColor,\n \"--ghm-tooltip-bg\": theme.tooltipBg,\n \"--ghm-tooltip-border\": theme.tooltipBorderColor,\n \"--ghm-tooltip-text\": theme.tooltipTextColor,\n \"--ghm-font\": theme.fontFamily,\n \"--ghm-fs\": theme.fontSize,\n };\n}\n\nconst BASE_STYLE = `\n[data-git-heatmap] {\n --ghm-color-l0: rgba(255,255,255,0.08);\n --ghm-color-l1: #1c3d06;\n --ghm-color-l2: #3a7510;\n --ghm-color-l3: #6ab81e;\n --ghm-color-l4: #aafd35;\n --ghm-text: rgba(255,255,255,0.5);\n --ghm-tooltip-bg: #1c2128;\n --ghm-tooltip-border: rgba(255,255,255,0.1);\n --ghm-tooltip-text: rgba(255,255,255,0.75);\n --ghm-font: inherit;\n --ghm-fs: 11px;\n display: block;\n position: relative;\n width: 100%;\n font-size: var(--ghm-fs);\n font-family: var(--ghm-font);\n color: var(--ghm-text);\n user-select: none;\n box-sizing: border-box;\n}\n[data-git-heatmap] * { box-sizing: border-box; }\n`;\n\nlet styleInjected = false;\nfunction ensureStyle() {\n if (styleInjected || typeof document === \"undefined\") return;\n const el = document.createElement(\"style\");\n el.textContent = BASE_STYLE;\n document.head.appendChild(el);\n styleInjected = true;\n}\n\nfunction levelColor(level: 0 | 1 | 2 | 3 | 4): string {\n return `var(--ghm-color-l${level})`;\n}\n\nexport function GitHeatmap({\n data: dataProp,\n apiUrl,\n fetchData,\n levels = DEFAULT_LEVELS,\n cellSize = CELL,\n cellGap = GAP,\n cellRadius = 2,\n showTotal = true,\n showLegend = true,\n showMonthLabels = true,\n showDayLabels = true,\n theme,\n onDayClick,\n label = \"Contribution heatmap\",\n}: GitHeatmapProps) {\n const step = cellSize + cellGap;\n\n const { data, status, error } = useHeatmapData({ data: dataProp, apiUrl, fetchData });\n const scrollRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const [tappedDay, setTappedDay] = useState<HeatmapDay | null>(null);\n const [tooltip, setTooltip] = useState<{ day: HeatmapDay; x: number; y: number } | null>(null);\n\n useEffect(() => { ensureStyle(); }, []);\n\n useEffect(() => {\n if (data && scrollRef.current) {\n scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;\n }\n }, [data]);\n\n const mergedTheme: HeatmapTheme = { ...DEFAULT_THEME, ...theme };\n const cssVars = themeToVars(mergedTheme) as React.CSSProperties;\n\n const formatTooltip = (day: HeatmapDay) =>\n day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\n\n if (error) {\n return (\n <div data-git-heatmap=\"\" style={cssVars}>\n <p style={{ color: \"var(--ghm-text)\", opacity: 0.6, fontSize: \"0.9em\", padding: \"1rem 0\" }}>\n Could not load contribution data.\n </p>\n </div>\n );\n }\n\n if (status === \"loading\" || status === \"idle\") {\n const skeletonCols = Array.from({ length: 53 });\n const skeletonRows = Array.from({ length: 7 });\n return (\n <div data-git-heatmap=\"\" style={{ ...cssVars, opacity: 0.4 }}>\n {showTotal && <div style={{ height: 16, width: 160, background: \"var(--ghm-text)\", borderRadius: 4, marginBottom: 12, opacity: 0.2 }} />}\n <div style={{ display: \"flex\", gap: cellGap }}>\n {skeletonCols.map((_, i) => (\n <div key={i} style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap }}>\n {skeletonRows.map((_, j) => (\n <div key={j} style={{ width: cellSize, height: cellSize, borderRadius: cellRadius, background: \"var(--ghm-color-l0)\" }} />\n ))}\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (!data) return null;\n\n const monthLabels = showMonthLabels ? buildMonthLabels(data.weeks) : [];\n\n return (\n <div data-git-heatmap=\"\" ref={wrapperRef} style={cssVars} aria-label={label}>\n\n {showTotal && (\n <p style={{ marginBottom: 12, color: \"var(--ghm-text)\" }}>\n {data.totalContributions.toLocaleString()} contributions in the last year\n </p>\n )}\n\n <div ref={scrollRef} style={{ overflowX: \"auto\", overflowY: \"visible\", paddingBottom: 4 }}>\n <div style={{ display: \"inline-flex\", flexDirection: \"column\", minWidth: data.weeks.length * step }}>\n\n {showMonthLabels && (\n <div style={{ display: \"flex\", marginBottom: cellGap, marginLeft: showDayLabels ? 32 : 0 }}>\n {monthLabels.map(({ label: ml, col }, idx) => {\n const nextCol = monthLabels[idx + 1]?.col ?? data.weeks.length;\n return (\n <div key={ml + col} style={{ width: (nextCol - col) * step, whiteSpace: \"nowrap\", fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n {ml}\n </div>\n );\n })}\n </div>\n )}\n\n <div style={{ display: \"flex\" }}>\n {showDayLabels && (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap, marginRight: 6 }}>\n {Array.from({ length: 7 }).map((_, dow) => (\n <div key={dow} style={{ height: cellSize, lineHeight: `${cellSize}px`, width: 26, textAlign: \"right\", paddingRight: 4, fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n {DAY_LABELS[dow] ?? \"\"}\n </div>\n ))}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: cellGap }}>\n {data.weeks.map((week, wi) => (\n <div key={wi} style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap }}>\n {Array.from({ length: 7 }).map((_, dow) => {\n const day = week.days[dow];\n if (!day || !day.date) {\n return <div key={dow} style={{ width: cellSize, height: cellSize }} />;\n }\n return (\n <div\n key={dow}\n style={{\n width: cellSize,\n height: cellSize,\n borderRadius: cellRadius,\n background: levelColor(day.level),\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n }}\n onMouseEnter={(e) => {\n const r = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const wr = wrapperRef.current?.getBoundingClientRect();\n if (wr) setTooltip({ day, x: r.left - wr.left + cellSize / 2, y: r.top - wr.top });\n }}\n onMouseLeave={() => setTooltip(null)}\n onClick={() => {\n setTappedDay((prev) => (prev?.date === day.date ? null : day));\n onDayClick?.(day);\n }}\n onMouseOver={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"0.7\"; }}\n onFocus={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"0.7\"; }}\n onMouseOut={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"1\"; }}\n onBlur={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"1\"; }}\n role=\"button\"\n tabIndex={0}\n aria-label={formatTooltip(day)}\n />\n );\n })}\n </div>\n ))}\n </div>\n </div>\n\n </div>\n </div>\n\n {tappedDay && (\n <div style={{ marginTop: 8, fontSize: \"0.9em\", color: \"var(--ghm-text)\", background: \"rgba(255,255,255,0.05)\", border: \"1px solid rgba(255,255,255,0.1)\", borderRadius: 4, padding: \"6px 12px\" }}>\n {formatTooltip(tappedDay)}\n </div>\n )}\n\n {showLegend && (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"flex-end\", marginTop: 8 }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 6, fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n <span>Less</span>\n {levels.map((lvl, i) => (\n <div\n key={i}\n style={{ width: cellSize, height: cellSize, borderRadius: cellRadius, background: `var(--ghm-color-l${i})` }}\n title={lvl.label}\n />\n ))}\n <span>More</span>\n </div>\n </div>\n )}\n\n {tooltip && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"absolute\",\n zIndex: 50,\n left: tooltip.x,\n top: tooltip.y - 6,\n transform: \"translate(-50%, -100%)\",\n marginBottom: 4,\n background: \"var(--ghm-tooltip-bg)\",\n border: \"1px solid var(--ghm-tooltip-border)\",\n color: \"var(--ghm-tooltip-text)\",\n fontSize: \"0.9em\",\n borderRadius: 4,\n padding: \"3px 8px\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatTooltip(tooltip.day)}\n <div style={{\n position: \"absolute\",\n left: \"50%\",\n top: \"100%\",\n transform: \"translate(-50%, -50%) rotate(45deg)\",\n width: 8,\n height: 8,\n background: \"var(--ghm-tooltip-bg)\",\n borderRight: \"1px solid var(--ghm-tooltip-border)\",\n borderBottom: \"1px solid var(--ghm-tooltip-border)\",\n }} />\n </div>\n )}\n </div>\n );\n}\n","import { useEffect, useRef, useState } from \"react\";\nimport type { HeatmapData } from \"@rsalianto/git-heatmap-core\";\n\nexport type HeatmapStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport function useHeatmapData(options: {\n data?: HeatmapData;\n apiUrl?: string;\n fetchData?: () => Promise<HeatmapData>;\n}): { data: HeatmapData | null; status: HeatmapStatus; error: Error | null } {\n const [data, setData] = useState<HeatmapData | null>(options.data ?? null);\n const [status, setStatus] = useState<HeatmapStatus>(options.data ? \"success\" : \"idle\");\n const [error, setError] = useState<Error | null>(null);\n\n const apiUrl = options.apiUrl;\n const fetchData = options.fetchData;\n const staticData = options.data;\n\n const didLoad = useRef(false);\n\n useEffect(() => {\n if (staticData) {\n setData(staticData);\n setStatus(\"success\");\n return;\n }\n\n const resolver: (() => Promise<HeatmapData>) | null =\n fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);\n\n if (!resolver) return;\n\n let cancelled = false;\n didLoad.current = false;\n setStatus(\"loading\");\n\n resolver()\n .then((d) => {\n if (!cancelled) { setData(d); setStatus(\"success\"); }\n })\n .catch((e: unknown) => {\n if (!cancelled) {\n setError(e instanceof Error ? e : new Error(String(e)));\n setStatus(\"error\");\n }\n });\n\n return () => { cancelled = true; };\n }, [staticData, apiUrl, fetchData]);\n\n return { data, status, error };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,IAAAA,gBAA4C;AAC5C,8BAMO;;;ACRP,mBAA4C;AAKrC,SAAS,eAAe,SAI8C;AAC3E,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA6B,QAAQ,QAAQ,IAAI;AACzE,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAwB,QAAQ,OAAO,YAAY,MAAM;AACrF,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAuB,IAAI;AAErD,QAAM,SAAS,QAAQ;AACvB,QAAM,YAAY,QAAQ;AAC1B,QAAM,aAAa,QAAQ;AAE3B,QAAM,cAAU,qBAAO,KAAK;AAE5B,8BAAU,MAAM;AACd,QAAI,YAAY;AACd,cAAQ,UAAU;AAClB,gBAAU,SAAS;AACnB;AAAA,IACF;AAEA,UAAM,WACJ,cAAc,SAAS,MAAM,MAAM,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI;AAErE,QAAI,CAAC,SAAU;AAEf,QAAI,YAAY;AAChB,YAAQ,UAAU;AAClB,cAAU,SAAS;AAEnB,aAAS,EACN,KAAK,CAAC,MAAM;AACX,UAAI,CAAC,WAAW;AAAE,gBAAQ,CAAC;AAAG,kBAAU,SAAS;AAAA,MAAG;AAAA,IACtD,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAI,CAAC,WAAW;AACd,iBAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AACtD,kBAAU,OAAO;AAAA,MACnB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAM;AAAA,EACnC,GAAG,CAAC,YAAY,QAAQ,SAAS,CAAC;AAElC,SAAO,EAAE,MAAM,QAAQ,MAAM;AAC/B;;;AD2EQ;AAjGR,SAAS,YAAY,OAA6C;AAChE,SAAO;AAAA,IACL,kBAAkB,MAAM;AAAA,IACxB,kBAAkB,MAAM;AAAA,IACxB,kBAAkB,MAAM;AAAA,IACxB,kBAAkB,MAAM;AAAA,IACxB,kBAAkB,MAAM;AAAA,IACxB,cAAkB,MAAM;AAAA,IACxB,oBAAwB,MAAM;AAAA,IAC9B,wBAAwB,MAAM;AAAA,IAC9B,sBAAwB,MAAM;AAAA,IAC9B,cAAkB,MAAM;AAAA,IACxB,YAAkB,MAAM;AAAA,EAC1B;AACF;AAEA,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBnB,IAAI,gBAAgB;AACpB,SAAS,cAAc;AACrB,MAAI,iBAAiB,OAAO,aAAa,YAAa;AACtD,QAAM,KAAK,SAAS,cAAc,OAAO;AACzC,KAAG,cAAc;AACjB,WAAS,KAAK,YAAY,EAAE;AAC5B,kBAAgB;AAClB;AAEA,SAAS,WAAW,OAAkC;AACpD,SAAO,oBAAoB,KAAK;AAClC;AAEO,SAAS,WAAW;AAAA,EACzB,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAoB;AAClB,QAAM,OAAO,WAAW;AAExB,QAAM,EAAE,MAAM,QAAQ,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,QAAQ,UAAU,CAAC;AACpF,QAAM,gBAAa,sBAAuB,IAAI;AAC9C,QAAM,iBAAa,sBAAuB,IAAI;AAC9C,QAAM,CAAC,WAAW,YAAY,QAAI,wBAA4B,IAAI;AAClE,QAAM,CAAC,SAAS,UAAU,QAAI,wBAA2D,IAAI;AAE7F,+BAAU,MAAM;AAAE,gBAAY;AAAA,EAAG,GAAG,CAAC,CAAC;AAEtC,+BAAU,MAAM;AACd,QAAI,QAAQ,UAAU,SAAS;AAC7B,gBAAU,QAAQ,aAAa,UAAU,QAAQ;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,cAA4B,EAAE,GAAG,uCAAe,GAAG,MAAM;AAC/D,QAAM,UAAU,YAAY,WAAW;AAEvC,QAAM,gBAAgB,CAAC,QACrB,IAAI,UAAU,IACV,uBAAuB,IAAI,IAAI,KAC/B,GAAG,IAAI,KAAK,gBAAgB,IAAI,QAAQ,IAAI,MAAM,EAAE,OAAO,IAAI,IAAI;AAEzE,MAAI,OAAO;AACT,WACE,4CAAC,SAAI,oBAAiB,IAAG,OAAO,SAC9B,sDAAC,OAAE,OAAO,EAAE,OAAO,mBAAmB,SAAS,KAAK,UAAU,SAAS,SAAS,SAAS,GAAG,+CAE5F,GACF;AAAA,EAEJ;AAEA,MAAI,WAAW,aAAa,WAAW,QAAQ;AAC7C,UAAM,eAAe,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC;AAC9C,UAAM,eAAe,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7C,WACE,6CAAC,SAAI,oBAAiB,IAAG,OAAO,EAAE,GAAG,SAAS,SAAS,IAAI,GACxD;AAAA,mBAAa,4CAAC,SAAI,OAAO,EAAE,QAAQ,IAAI,OAAO,KAAK,YAAY,mBAAmB,cAAc,GAAG,cAAc,IAAI,SAAS,IAAI,GAAG;AAAA,MACtI,4CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,QAAQ,GACzC,uBAAa,IAAI,CAAC,GAAG,MACpB,4CAAC,SAAY,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,QAAQ,GAC1E,uBAAa,IAAI,CAACC,IAAG,MACpB,4CAAC,SAAY,OAAO,EAAE,OAAO,UAAU,QAAQ,UAAU,cAAc,YAAY,YAAY,sBAAsB,KAA3G,CAA8G,CACzH,KAHO,CAIV,CACD,GACH;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,cAAc,sBAAkB,0CAAiB,KAAK,KAAK,IAAI,CAAC;AAEtE,SACE,6CAAC,SAAI,oBAAiB,IAAG,KAAK,YAAY,OAAO,SAAS,cAAY,OAEnE;AAAA,iBACC,6CAAC,OAAE,OAAO,EAAE,cAAc,IAAI,OAAO,kBAAkB,GACpD;AAAA,WAAK,mBAAmB,eAAe;AAAA,MAAE;AAAA,OAC5C;AAAA,IAGF,4CAAC,SAAI,KAAK,WAAW,OAAO,EAAE,WAAW,QAAQ,WAAW,WAAW,eAAe,EAAE,GACtF,uDAAC,SAAI,OAAO,EAAE,SAAS,eAAe,eAAe,UAAU,UAAU,KAAK,MAAM,SAAS,KAAK,GAE/F;AAAA,yBACC,4CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,cAAc,SAAS,YAAY,gBAAgB,KAAK,EAAE,GACtF,sBAAY,IAAI,CAAC,EAAE,OAAO,IAAI,IAAI,GAAG,QAAQ;AAC5C,cAAM,UAAU,YAAY,MAAM,CAAC,GAAG,OAAO,KAAK,MAAM;AACxD,eACE,4CAAC,SAAmB,OAAO,EAAE,QAAQ,UAAU,OAAO,MAAM,YAAY,UAAU,UAAU,SAAS,OAAO,kBAAkB,GAC3H,gBADO,KAAK,GAEf;AAAA,MAEJ,CAAC,GACH;AAAA,MAGF,6CAAC,SAAI,OAAO,EAAE,SAAS,OAAO,GAC3B;AAAA,yBACC,4CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,SAAS,aAAa,EAAE,GAClF,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,QACjC,4CAAC,SAAc,OAAO,EAAE,QAAQ,UAAU,YAAY,GAAG,QAAQ,MAAM,OAAO,IAAI,WAAW,SAAS,cAAc,GAAG,UAAU,SAAS,OAAO,kBAAkB,GAChK,6CAAW,GAAG,KAAK,MADZ,GAEV,CACD,GACH;AAAA,QAGF,4CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,QAAQ,GACzC,eAAK,MAAM,IAAI,CAAC,MAAM,OACrB,4CAAC,SAAa,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,QAAQ,GAC3E,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,QAAQ;AACzC,gBAAM,MAAM,KAAK,KAAK,GAAG;AACzB,cAAI,CAAC,OAAO,CAAC,IAAI,MAAM;AACrB,mBAAO,4CAAC,SAAc,OAAO,EAAE,OAAO,UAAU,QAAQ,SAAS,KAAhD,GAAmD;AAAA,UACtE;AACA,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,OAAO;AAAA,gBACL,OAAO;AAAA,gBACP,QAAQ;AAAA,gBACR,cAAc;AAAA,gBACd,YAAY,WAAW,IAAI,KAAK;AAAA,gBAChC,QAAQ;AAAA,gBACR,YAAY;AAAA,cACd;AAAA,cACA,cAAc,CAAC,MAAM;AACnB,sBAAM,IAAK,EAAE,cAA8B,sBAAsB;AACjE,sBAAM,KAAK,WAAW,SAAS,sBAAsB;AACrD,oBAAI,GAAI,YAAW,EAAE,KAAK,GAAG,EAAE,OAAO,GAAG,OAAO,WAAW,GAAG,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;AAAA,cACnF;AAAA,cACA,cAAc,MAAM,WAAW,IAAI;AAAA,cACnC,SAAS,MAAM;AACb,6BAAa,CAAC,SAAU,MAAM,SAAS,IAAI,OAAO,OAAO,GAAI;AAC7D,6BAAa,GAAG;AAAA,cAClB;AAAA,cACA,aAAa,CAAC,MAAM;AAAE,gBAAC,EAAE,cAA8B,MAAM,UAAU;AAAA,cAAO;AAAA,cAC9E,SAAS,CAAC,MAAM;AAAE,gBAAC,EAAE,cAA8B,MAAM,UAAU;AAAA,cAAO;AAAA,cAC1E,YAAY,CAAC,MAAM;AAAE,gBAAC,EAAE,cAA8B,MAAM,UAAU;AAAA,cAAK;AAAA,cAC3E,QAAQ,CAAC,MAAM;AAAE,gBAAC,EAAE,cAA8B,MAAM,UAAU;AAAA,cAAK;AAAA,cACvE,MAAK;AAAA,cACL,UAAU;AAAA,cACV,cAAY,cAAc,GAAG;AAAA;AAAA,YAzBxB;AAAA,UA0BP;AAAA,QAEJ,CAAC,KApCO,EAqCV,CACD,GACH;AAAA,SACF;AAAA,OAEF,GACF;AAAA,IAEC,aACC,4CAAC,SAAI,OAAO,EAAE,WAAW,GAAG,UAAU,SAAS,OAAO,mBAAmB,YAAY,0BAA0B,QAAQ,mCAAmC,cAAc,GAAG,SAAS,WAAW,GAC5L,wBAAc,SAAS,GAC1B;AAAA,IAGD,cACC,4CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,gBAAgB,YAAY,WAAW,EAAE,GAC5F,uDAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,UAAU,SAAS,OAAO,kBAAkB,GACvG;AAAA,kDAAC,UAAK,kBAAI;AAAA,MACT,OAAO,IAAI,CAAC,KAAK,MAChB;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,EAAE,OAAO,UAAU,QAAQ,UAAU,cAAc,YAAY,YAAY,oBAAoB,CAAC,IAAI;AAAA,UAC3G,OAAO,IAAI;AAAA;AAAA,QAFN;AAAA,MAGP,CACD;AAAA,MACD,4CAAC,UAAK,kBAAI;AAAA,OACZ,GACF;AAAA,IAGD,WACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,eAAe;AAAA,UACf,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,MAAM,QAAQ;AAAA,UACd,KAAK,QAAQ,IAAI;AAAA,UACjB,WAAW;AAAA,UACX,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc;AAAA,UACd,SAAS;AAAA,UACT,YAAY;AAAA,QACd;AAAA,QAEC;AAAA,wBAAc,QAAQ,GAAG;AAAA,UAC1B,4CAAC,SAAI,OAAO;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,KAAK;AAAA,YACL,WAAW;AAAA,YACX,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,YAAY;AAAA,YACZ,aAAa;AAAA,YACb,cAAc;AAAA,UAChB,GAAG;AAAA;AAAA;AAAA,IACL;AAAA,KAEJ;AAEJ;","names":["import_react","_"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,262 @@
1
+ // src/GitHeatmap.tsx
2
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
3
+ import {
4
+ buildMonthLabels,
5
+ CELL,
6
+ GAP,
7
+ DAY_LABELS,
8
+ DEFAULT_LEVELS,
9
+ DEFAULT_THEME
10
+ } from "@rsalianto/git-heatmap-core";
11
+
12
+ // src/useHeatmapData.ts
13
+ import { useEffect, useRef, useState } from "react";
14
+ function useHeatmapData(options) {
15
+ const [data, setData] = useState(options.data ?? null);
16
+ const [status, setStatus] = useState(options.data ? "success" : "idle");
17
+ const [error, setError] = useState(null);
18
+ const apiUrl = options.apiUrl;
19
+ const fetchData = options.fetchData;
20
+ const staticData = options.data;
21
+ const didLoad = useRef(false);
22
+ useEffect(() => {
23
+ if (staticData) {
24
+ setData(staticData);
25
+ setStatus("success");
26
+ return;
27
+ }
28
+ const resolver = fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
29
+ if (!resolver) return;
30
+ let cancelled = false;
31
+ didLoad.current = false;
32
+ setStatus("loading");
33
+ resolver().then((d) => {
34
+ if (!cancelled) {
35
+ setData(d);
36
+ setStatus("success");
37
+ }
38
+ }).catch((e) => {
39
+ if (!cancelled) {
40
+ setError(e instanceof Error ? e : new Error(String(e)));
41
+ setStatus("error");
42
+ }
43
+ });
44
+ return () => {
45
+ cancelled = true;
46
+ };
47
+ }, [staticData, apiUrl, fetchData]);
48
+ return { data, status, error };
49
+ }
50
+
51
+ // src/GitHeatmap.tsx
52
+ import { jsx, jsxs } from "react/jsx-runtime";
53
+ function themeToVars(theme) {
54
+ return {
55
+ "--ghm-color-l0": theme.colorL0,
56
+ "--ghm-color-l1": theme.colorL1,
57
+ "--ghm-color-l2": theme.colorL2,
58
+ "--ghm-color-l3": theme.colorL3,
59
+ "--ghm-color-l4": theme.colorL4,
60
+ "--ghm-text": theme.textColor,
61
+ "--ghm-tooltip-bg": theme.tooltipBg,
62
+ "--ghm-tooltip-border": theme.tooltipBorderColor,
63
+ "--ghm-tooltip-text": theme.tooltipTextColor,
64
+ "--ghm-font": theme.fontFamily,
65
+ "--ghm-fs": theme.fontSize
66
+ };
67
+ }
68
+ var BASE_STYLE = `
69
+ [data-git-heatmap] {
70
+ --ghm-color-l0: rgba(255,255,255,0.08);
71
+ --ghm-color-l1: #1c3d06;
72
+ --ghm-color-l2: #3a7510;
73
+ --ghm-color-l3: #6ab81e;
74
+ --ghm-color-l4: #aafd35;
75
+ --ghm-text: rgba(255,255,255,0.5);
76
+ --ghm-tooltip-bg: #1c2128;
77
+ --ghm-tooltip-border: rgba(255,255,255,0.1);
78
+ --ghm-tooltip-text: rgba(255,255,255,0.75);
79
+ --ghm-font: inherit;
80
+ --ghm-fs: 11px;
81
+ display: block;
82
+ position: relative;
83
+ width: 100%;
84
+ font-size: var(--ghm-fs);
85
+ font-family: var(--ghm-font);
86
+ color: var(--ghm-text);
87
+ user-select: none;
88
+ box-sizing: border-box;
89
+ }
90
+ [data-git-heatmap] * { box-sizing: border-box; }
91
+ `;
92
+ var styleInjected = false;
93
+ function ensureStyle() {
94
+ if (styleInjected || typeof document === "undefined") return;
95
+ const el = document.createElement("style");
96
+ el.textContent = BASE_STYLE;
97
+ document.head.appendChild(el);
98
+ styleInjected = true;
99
+ }
100
+ function levelColor(level) {
101
+ return `var(--ghm-color-l${level})`;
102
+ }
103
+ function GitHeatmap({
104
+ data: dataProp,
105
+ apiUrl,
106
+ fetchData,
107
+ levels = DEFAULT_LEVELS,
108
+ cellSize = CELL,
109
+ cellGap = GAP,
110
+ cellRadius = 2,
111
+ showTotal = true,
112
+ showLegend = true,
113
+ showMonthLabels = true,
114
+ showDayLabels = true,
115
+ theme,
116
+ onDayClick,
117
+ label = "Contribution heatmap"
118
+ }) {
119
+ const step = cellSize + cellGap;
120
+ const { data, status, error } = useHeatmapData({ data: dataProp, apiUrl, fetchData });
121
+ const scrollRef = useRef2(null);
122
+ const wrapperRef = useRef2(null);
123
+ const [tappedDay, setTappedDay] = useState2(null);
124
+ const [tooltip, setTooltip] = useState2(null);
125
+ useEffect2(() => {
126
+ ensureStyle();
127
+ }, []);
128
+ useEffect2(() => {
129
+ if (data && scrollRef.current) {
130
+ scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
131
+ }
132
+ }, [data]);
133
+ const mergedTheme = { ...DEFAULT_THEME, ...theme };
134
+ const cssVars = themeToVars(mergedTheme);
135
+ const formatTooltip = (day) => day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
136
+ if (error) {
137
+ return /* @__PURE__ */ jsx("div", { "data-git-heatmap": "", style: cssVars, children: /* @__PURE__ */ jsx("p", { style: { color: "var(--ghm-text)", opacity: 0.6, fontSize: "0.9em", padding: "1rem 0" }, children: "Could not load contribution data." }) });
138
+ }
139
+ if (status === "loading" || status === "idle") {
140
+ const skeletonCols = Array.from({ length: 53 });
141
+ const skeletonRows = Array.from({ length: 7 });
142
+ return /* @__PURE__ */ jsxs("div", { "data-git-heatmap": "", style: { ...cssVars, opacity: 0.4 }, children: [
143
+ showTotal && /* @__PURE__ */ jsx("div", { style: { height: 16, width: 160, background: "var(--ghm-text)", borderRadius: 4, marginBottom: 12, opacity: 0.2 } }),
144
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: cellGap }, children: skeletonCols.map((_, i) => /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: cellGap }, children: skeletonRows.map((_2, j) => /* @__PURE__ */ jsx("div", { style: { width: cellSize, height: cellSize, borderRadius: cellRadius, background: "var(--ghm-color-l0)" } }, j)) }, i)) })
145
+ ] });
146
+ }
147
+ if (!data) return null;
148
+ const monthLabels = showMonthLabels ? buildMonthLabels(data.weeks) : [];
149
+ return /* @__PURE__ */ jsxs("div", { "data-git-heatmap": "", ref: wrapperRef, style: cssVars, "aria-label": label, children: [
150
+ showTotal && /* @__PURE__ */ jsxs("p", { style: { marginBottom: 12, color: "var(--ghm-text)" }, children: [
151
+ data.totalContributions.toLocaleString(),
152
+ " contributions in the last year"
153
+ ] }),
154
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, style: { overflowX: "auto", overflowY: "visible", paddingBottom: 4 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "inline-flex", flexDirection: "column", minWidth: data.weeks.length * step }, children: [
155
+ showMonthLabels && /* @__PURE__ */ jsx("div", { style: { display: "flex", marginBottom: cellGap, marginLeft: showDayLabels ? 32 : 0 }, children: monthLabels.map(({ label: ml, col }, idx) => {
156
+ const nextCol = monthLabels[idx + 1]?.col ?? data.weeks.length;
157
+ return /* @__PURE__ */ jsx("div", { style: { width: (nextCol - col) * step, whiteSpace: "nowrap", fontSize: "0.9em", color: "var(--ghm-text)" }, children: ml }, ml + col);
158
+ }) }),
159
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex" }, children: [
160
+ showDayLabels && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: cellGap, marginRight: 6 }, children: Array.from({ length: 7 }).map((_, dow) => /* @__PURE__ */ jsx("div", { style: { height: cellSize, lineHeight: `${cellSize}px`, width: 26, textAlign: "right", paddingRight: 4, fontSize: "0.9em", color: "var(--ghm-text)" }, children: DAY_LABELS[dow] ?? "" }, dow)) }),
161
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: cellGap }, children: data.weeks.map((week, wi) => /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: cellGap }, children: Array.from({ length: 7 }).map((_, dow) => {
162
+ const day = week.days[dow];
163
+ if (!day || !day.date) {
164
+ return /* @__PURE__ */ jsx("div", { style: { width: cellSize, height: cellSize } }, dow);
165
+ }
166
+ return /* @__PURE__ */ jsx(
167
+ "div",
168
+ {
169
+ style: {
170
+ width: cellSize,
171
+ height: cellSize,
172
+ borderRadius: cellRadius,
173
+ background: levelColor(day.level),
174
+ cursor: "pointer",
175
+ transition: "opacity 0.15s"
176
+ },
177
+ onMouseEnter: (e) => {
178
+ const r = e.currentTarget.getBoundingClientRect();
179
+ const wr = wrapperRef.current?.getBoundingClientRect();
180
+ if (wr) setTooltip({ day, x: r.left - wr.left + cellSize / 2, y: r.top - wr.top });
181
+ },
182
+ onMouseLeave: () => setTooltip(null),
183
+ onClick: () => {
184
+ setTappedDay((prev) => prev?.date === day.date ? null : day);
185
+ onDayClick?.(day);
186
+ },
187
+ onMouseOver: (e) => {
188
+ e.currentTarget.style.opacity = "0.7";
189
+ },
190
+ onFocus: (e) => {
191
+ e.currentTarget.style.opacity = "0.7";
192
+ },
193
+ onMouseOut: (e) => {
194
+ e.currentTarget.style.opacity = "1";
195
+ },
196
+ onBlur: (e) => {
197
+ e.currentTarget.style.opacity = "1";
198
+ },
199
+ role: "button",
200
+ tabIndex: 0,
201
+ "aria-label": formatTooltip(day)
202
+ },
203
+ dow
204
+ );
205
+ }) }, wi)) })
206
+ ] })
207
+ ] }) }),
208
+ tappedDay && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, fontSize: "0.9em", color: "var(--ghm-text)", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 4, padding: "6px 12px" }, children: formatTooltip(tappedDay) }),
209
+ showLegend && /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", marginTop: 8 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: "0.9em", color: "var(--ghm-text)" }, children: [
210
+ /* @__PURE__ */ jsx("span", { children: "Less" }),
211
+ levels.map((lvl, i) => /* @__PURE__ */ jsx(
212
+ "div",
213
+ {
214
+ style: { width: cellSize, height: cellSize, borderRadius: cellRadius, background: `var(--ghm-color-l${i})` },
215
+ title: lvl.label
216
+ },
217
+ i
218
+ )),
219
+ /* @__PURE__ */ jsx("span", { children: "More" })
220
+ ] }) }),
221
+ tooltip && /* @__PURE__ */ jsxs(
222
+ "div",
223
+ {
224
+ style: {
225
+ pointerEvents: "none",
226
+ position: "absolute",
227
+ zIndex: 50,
228
+ left: tooltip.x,
229
+ top: tooltip.y - 6,
230
+ transform: "translate(-50%, -100%)",
231
+ marginBottom: 4,
232
+ background: "var(--ghm-tooltip-bg)",
233
+ border: "1px solid var(--ghm-tooltip-border)",
234
+ color: "var(--ghm-tooltip-text)",
235
+ fontSize: "0.9em",
236
+ borderRadius: 4,
237
+ padding: "3px 8px",
238
+ whiteSpace: "nowrap"
239
+ },
240
+ children: [
241
+ formatTooltip(tooltip.day),
242
+ /* @__PURE__ */ jsx("div", { style: {
243
+ position: "absolute",
244
+ left: "50%",
245
+ top: "100%",
246
+ transform: "translate(-50%, -50%) rotate(45deg)",
247
+ width: 8,
248
+ height: 8,
249
+ background: "var(--ghm-tooltip-bg)",
250
+ borderRight: "1px solid var(--ghm-tooltip-border)",
251
+ borderBottom: "1px solid var(--ghm-tooltip-border)"
252
+ } })
253
+ ]
254
+ }
255
+ )
256
+ ] });
257
+ }
258
+ export {
259
+ GitHeatmap,
260
+ useHeatmapData
261
+ };
262
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/GitHeatmap.tsx","../src/useHeatmapData.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n buildMonthLabels,\n CELL, GAP, STEP,\n DAY_LABELS,\n DEFAULT_LEVELS,\n DEFAULT_THEME,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, HeatmapTheme, LevelConfig } from \"@rsalianto/git-heatmap-core\";\nimport { useHeatmapData } from \"./useHeatmapData\";\n\nexport interface GitHeatmapProps {\n data?: HeatmapData;\n apiUrl?: string;\n fetchData?: () => Promise<HeatmapData>;\n levels?: LevelConfig[];\n cellSize?: number;\n cellGap?: number;\n cellRadius?: number;\n showTotal?: boolean;\n showLegend?: boolean;\n showMonthLabels?: boolean;\n showDayLabels?: boolean;\n theme?: Partial<HeatmapTheme>;\n onDayClick?: (day: HeatmapDay) => void;\n label?: string;\n}\n\nfunction themeToVars(theme: HeatmapTheme): Record<string, string> {\n return {\n \"--ghm-color-l0\": theme.colorL0,\n \"--ghm-color-l1\": theme.colorL1,\n \"--ghm-color-l2\": theme.colorL2,\n \"--ghm-color-l3\": theme.colorL3,\n \"--ghm-color-l4\": theme.colorL4,\n \"--ghm-text\": theme.textColor,\n \"--ghm-tooltip-bg\": theme.tooltipBg,\n \"--ghm-tooltip-border\": theme.tooltipBorderColor,\n \"--ghm-tooltip-text\": theme.tooltipTextColor,\n \"--ghm-font\": theme.fontFamily,\n \"--ghm-fs\": theme.fontSize,\n };\n}\n\nconst BASE_STYLE = `\n[data-git-heatmap] {\n --ghm-color-l0: rgba(255,255,255,0.08);\n --ghm-color-l1: #1c3d06;\n --ghm-color-l2: #3a7510;\n --ghm-color-l3: #6ab81e;\n --ghm-color-l4: #aafd35;\n --ghm-text: rgba(255,255,255,0.5);\n --ghm-tooltip-bg: #1c2128;\n --ghm-tooltip-border: rgba(255,255,255,0.1);\n --ghm-tooltip-text: rgba(255,255,255,0.75);\n --ghm-font: inherit;\n --ghm-fs: 11px;\n display: block;\n position: relative;\n width: 100%;\n font-size: var(--ghm-fs);\n font-family: var(--ghm-font);\n color: var(--ghm-text);\n user-select: none;\n box-sizing: border-box;\n}\n[data-git-heatmap] * { box-sizing: border-box; }\n`;\n\nlet styleInjected = false;\nfunction ensureStyle() {\n if (styleInjected || typeof document === \"undefined\") return;\n const el = document.createElement(\"style\");\n el.textContent = BASE_STYLE;\n document.head.appendChild(el);\n styleInjected = true;\n}\n\nfunction levelColor(level: 0 | 1 | 2 | 3 | 4): string {\n return `var(--ghm-color-l${level})`;\n}\n\nexport function GitHeatmap({\n data: dataProp,\n apiUrl,\n fetchData,\n levels = DEFAULT_LEVELS,\n cellSize = CELL,\n cellGap = GAP,\n cellRadius = 2,\n showTotal = true,\n showLegend = true,\n showMonthLabels = true,\n showDayLabels = true,\n theme,\n onDayClick,\n label = \"Contribution heatmap\",\n}: GitHeatmapProps) {\n const step = cellSize + cellGap;\n\n const { data, status, error } = useHeatmapData({ data: dataProp, apiUrl, fetchData });\n const scrollRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const [tappedDay, setTappedDay] = useState<HeatmapDay | null>(null);\n const [tooltip, setTooltip] = useState<{ day: HeatmapDay; x: number; y: number } | null>(null);\n\n useEffect(() => { ensureStyle(); }, []);\n\n useEffect(() => {\n if (data && scrollRef.current) {\n scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;\n }\n }, [data]);\n\n const mergedTheme: HeatmapTheme = { ...DEFAULT_THEME, ...theme };\n const cssVars = themeToVars(mergedTheme) as React.CSSProperties;\n\n const formatTooltip = (day: HeatmapDay) =>\n day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\n\n if (error) {\n return (\n <div data-git-heatmap=\"\" style={cssVars}>\n <p style={{ color: \"var(--ghm-text)\", opacity: 0.6, fontSize: \"0.9em\", padding: \"1rem 0\" }}>\n Could not load contribution data.\n </p>\n </div>\n );\n }\n\n if (status === \"loading\" || status === \"idle\") {\n const skeletonCols = Array.from({ length: 53 });\n const skeletonRows = Array.from({ length: 7 });\n return (\n <div data-git-heatmap=\"\" style={{ ...cssVars, opacity: 0.4 }}>\n {showTotal && <div style={{ height: 16, width: 160, background: \"var(--ghm-text)\", borderRadius: 4, marginBottom: 12, opacity: 0.2 }} />}\n <div style={{ display: \"flex\", gap: cellGap }}>\n {skeletonCols.map((_, i) => (\n <div key={i} style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap }}>\n {skeletonRows.map((_, j) => (\n <div key={j} style={{ width: cellSize, height: cellSize, borderRadius: cellRadius, background: \"var(--ghm-color-l0)\" }} />\n ))}\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (!data) return null;\n\n const monthLabels = showMonthLabels ? buildMonthLabels(data.weeks) : [];\n\n return (\n <div data-git-heatmap=\"\" ref={wrapperRef} style={cssVars} aria-label={label}>\n\n {showTotal && (\n <p style={{ marginBottom: 12, color: \"var(--ghm-text)\" }}>\n {data.totalContributions.toLocaleString()} contributions in the last year\n </p>\n )}\n\n <div ref={scrollRef} style={{ overflowX: \"auto\", overflowY: \"visible\", paddingBottom: 4 }}>\n <div style={{ display: \"inline-flex\", flexDirection: \"column\", minWidth: data.weeks.length * step }}>\n\n {showMonthLabels && (\n <div style={{ display: \"flex\", marginBottom: cellGap, marginLeft: showDayLabels ? 32 : 0 }}>\n {monthLabels.map(({ label: ml, col }, idx) => {\n const nextCol = monthLabels[idx + 1]?.col ?? data.weeks.length;\n return (\n <div key={ml + col} style={{ width: (nextCol - col) * step, whiteSpace: \"nowrap\", fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n {ml}\n </div>\n );\n })}\n </div>\n )}\n\n <div style={{ display: \"flex\" }}>\n {showDayLabels && (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap, marginRight: 6 }}>\n {Array.from({ length: 7 }).map((_, dow) => (\n <div key={dow} style={{ height: cellSize, lineHeight: `${cellSize}px`, width: 26, textAlign: \"right\", paddingRight: 4, fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n {DAY_LABELS[dow] ?? \"\"}\n </div>\n ))}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: cellGap }}>\n {data.weeks.map((week, wi) => (\n <div key={wi} style={{ display: \"flex\", flexDirection: \"column\", gap: cellGap }}>\n {Array.from({ length: 7 }).map((_, dow) => {\n const day = week.days[dow];\n if (!day || !day.date) {\n return <div key={dow} style={{ width: cellSize, height: cellSize }} />;\n }\n return (\n <div\n key={dow}\n style={{\n width: cellSize,\n height: cellSize,\n borderRadius: cellRadius,\n background: levelColor(day.level),\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n }}\n onMouseEnter={(e) => {\n const r = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const wr = wrapperRef.current?.getBoundingClientRect();\n if (wr) setTooltip({ day, x: r.left - wr.left + cellSize / 2, y: r.top - wr.top });\n }}\n onMouseLeave={() => setTooltip(null)}\n onClick={() => {\n setTappedDay((prev) => (prev?.date === day.date ? null : day));\n onDayClick?.(day);\n }}\n onMouseOver={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"0.7\"; }}\n onFocus={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"0.7\"; }}\n onMouseOut={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"1\"; }}\n onBlur={(e) => { (e.currentTarget as HTMLElement).style.opacity = \"1\"; }}\n role=\"button\"\n tabIndex={0}\n aria-label={formatTooltip(day)}\n />\n );\n })}\n </div>\n ))}\n </div>\n </div>\n\n </div>\n </div>\n\n {tappedDay && (\n <div style={{ marginTop: 8, fontSize: \"0.9em\", color: \"var(--ghm-text)\", background: \"rgba(255,255,255,0.05)\", border: \"1px solid rgba(255,255,255,0.1)\", borderRadius: 4, padding: \"6px 12px\" }}>\n {formatTooltip(tappedDay)}\n </div>\n )}\n\n {showLegend && (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"flex-end\", marginTop: 8 }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 6, fontSize: \"0.9em\", color: \"var(--ghm-text)\" }}>\n <span>Less</span>\n {levels.map((lvl, i) => (\n <div\n key={i}\n style={{ width: cellSize, height: cellSize, borderRadius: cellRadius, background: `var(--ghm-color-l${i})` }}\n title={lvl.label}\n />\n ))}\n <span>More</span>\n </div>\n </div>\n )}\n\n {tooltip && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"absolute\",\n zIndex: 50,\n left: tooltip.x,\n top: tooltip.y - 6,\n transform: \"translate(-50%, -100%)\",\n marginBottom: 4,\n background: \"var(--ghm-tooltip-bg)\",\n border: \"1px solid var(--ghm-tooltip-border)\",\n color: \"var(--ghm-tooltip-text)\",\n fontSize: \"0.9em\",\n borderRadius: 4,\n padding: \"3px 8px\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatTooltip(tooltip.day)}\n <div style={{\n position: \"absolute\",\n left: \"50%\",\n top: \"100%\",\n transform: \"translate(-50%, -50%) rotate(45deg)\",\n width: 8,\n height: 8,\n background: \"var(--ghm-tooltip-bg)\",\n borderRight: \"1px solid var(--ghm-tooltip-border)\",\n borderBottom: \"1px solid var(--ghm-tooltip-border)\",\n }} />\n </div>\n )}\n </div>\n );\n}\n","import { useEffect, useRef, useState } from \"react\";\nimport type { HeatmapData } from \"@rsalianto/git-heatmap-core\";\n\nexport type HeatmapStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport function useHeatmapData(options: {\n data?: HeatmapData;\n apiUrl?: string;\n fetchData?: () => Promise<HeatmapData>;\n}): { data: HeatmapData | null; status: HeatmapStatus; error: Error | null } {\n const [data, setData] = useState<HeatmapData | null>(options.data ?? null);\n const [status, setStatus] = useState<HeatmapStatus>(options.data ? \"success\" : \"idle\");\n const [error, setError] = useState<Error | null>(null);\n\n const apiUrl = options.apiUrl;\n const fetchData = options.fetchData;\n const staticData = options.data;\n\n const didLoad = useRef(false);\n\n useEffect(() => {\n if (staticData) {\n setData(staticData);\n setStatus(\"success\");\n return;\n }\n\n const resolver: (() => Promise<HeatmapData>) | null =\n fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);\n\n if (!resolver) return;\n\n let cancelled = false;\n didLoad.current = false;\n setStatus(\"loading\");\n\n resolver()\n .then((d) => {\n if (!cancelled) { setData(d); setStatus(\"success\"); }\n })\n .catch((e: unknown) => {\n if (!cancelled) {\n setError(e instanceof Error ? e : new Error(String(e)));\n setStatus(\"error\");\n }\n });\n\n return () => { cancelled = true; };\n }, [staticData, apiUrl, fetchData]);\n\n return { data, status, error };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useRef","useState","useEffect","_"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@rsalianto/git-heatmap-react",
3
+ "version": "0.1.0",
4
+ "description": "React component for git contribution heatmap",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": ["dist", "README.md"],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "test": "vitest run",
22
+ "clean": "rm -rf dist"
23
+ },
24
+ "dependencies": {
25
+ "@rsalianto/git-heatmap-core": "*"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "react-dom": ">=18.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "*",
33
+ "typescript": "*",
34
+ "vitest": "*",
35
+ "@types/react": "^18.0.0",
36
+ "@types/react-dom": "^18.0.0",
37
+ "react": "^18.0.0",
38
+ "react-dom": "^18.0.0"
39
+ }
40
+ }