@pearpages/heatmap 0.1.6 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { useState } from 'react';
3
2
 
4
3
  // src/shared/models.tsx
4
+ var defaultLabels = {
5
+ less: "Less",
6
+ more: "More",
7
+ level: (level) => `Level ${level}`,
8
+ noContributions: "No contributions",
9
+ contributions: (count) => `${count} contribution${count !== 1 ? "s" : ""}`
10
+ };
11
+ var DEFAULT_LOCALE = "en-US";
5
12
  var monthNames = [
6
13
  "Jan",
7
14
  "Feb",
@@ -28,18 +35,25 @@ function getLastYearPeriod() {
28
35
  const end = /* @__PURE__ */ new Date();
29
36
  return { start, end };
30
37
  }
38
+ function getLastMonthPeriod() {
39
+ const now = /* @__PURE__ */ new Date();
40
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());
41
+ const start = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate() + 1);
42
+ return { start, end };
43
+ }
31
44
  var createDateString = (date) => date.toISOString().split("T")[0];
32
45
 
33
46
  // src/ContributionHeatmap/getMonthsForHeader.ts
34
47
  function getMonthsForHeader({
35
48
  weeks,
36
- period: period2
49
+ period,
50
+ monthNames: monthNames2 = monthNames
37
51
  }) {
38
52
  const months = [];
39
- const { start, end } = period2;
53
+ const { start, end } = period;
40
54
  let currentMonth = -1;
41
55
  let currentSpan = 0;
42
- weeks.forEach((week) => {
56
+ weeks.forEach((week, weekIndex) => {
43
57
  if (week.length > 0) {
44
58
  let monthToUse = -1;
45
59
  const actualStartDate = new Date(start);
@@ -57,7 +71,7 @@ function getMonthsForHeader({
57
71
  }
58
72
  if (monthToUse !== currentMonth) {
59
73
  if (currentMonth !== -1) {
60
- months.push({ name: monthNames[currentMonth], span: currentSpan });
74
+ months.push({ name: monthNames2[currentMonth], span: currentSpan, start: weekIndex - currentSpan });
61
75
  }
62
76
  currentMonth = monthToUse;
63
77
  currentSpan = 1;
@@ -67,120 +81,226 @@ function getMonthsForHeader({
67
81
  }
68
82
  });
69
83
  if (currentMonth !== -1) {
70
- months.push({ name: monthNames[currentMonth], span: currentSpan });
71
- }
72
- if (months.length > 0 && months[months.length - 1].span === 1) {
73
- months[months.length - 1].span = 2;
84
+ months.push({ name: monthNames2[currentMonth], span: currentSpan, start: weeks.length - currentSpan });
74
85
  }
75
86
  return months;
76
87
  }
77
88
 
78
89
  // src/shared/formatTooltip.tsx
79
90
  function isInRange(date, { start, end }) {
80
- const actualStartDate = new Date(start);
81
- const actualEndDate = new Date(end);
82
- const contributionDate = new Date(date);
83
- return contributionDate > actualStartDate && contributionDate < actualEndDate;
91
+ return date >= createDateString(start) && date <= createDateString(end);
84
92
  }
85
- var formatTooltip = (contribution, { start, end }) => {
93
+ var formatTooltip = (contribution, { start, end }, { locale = DEFAULT_LOCALE, labels } = {}) => {
94
+ const text = { ...defaultLabels, ...labels };
86
95
  const date = new Date(contribution.date);
87
- const formattedDate = date.toLocaleDateString("en-US", {
96
+ const formattedDate = date.toLocaleDateString(locale, {
88
97
  weekday: "short",
89
98
  year: "numeric",
90
99
  month: "short",
91
- day: "numeric"
100
+ day: "numeric",
101
+ timeZone: "UTC"
92
102
  });
93
- if (!isInRange(new Date(contribution.date), { start, end }) || contribution.count === 0) {
94
- return `${formattedDate}: No contributions`;
103
+ if (!isInRange(contribution.date, { start, end }) || contribution.count === 0) {
104
+ return `${formattedDate}: ${text.noContributions}`;
95
105
  }
96
- return `${formattedDate}: ${contribution.count} contribution${contribution.count !== 1 ? "s" : ""}`;
106
+ return `${formattedDate}: ${text.contributions(contribution.count)}`;
97
107
  };
98
- function Legend() {
108
+
109
+ // src/shared/intl.ts
110
+ var REFERENCE_WEEK_START = Date.UTC(2024, 0, 7);
111
+ var DAYS_IN_WEEK = 7;
112
+ function getDayNames(locale, weekStartsOn = 0) {
113
+ const format = new Intl.DateTimeFormat(locale, {
114
+ weekday: "short",
115
+ timeZone: "UTC"
116
+ });
117
+ return Array.from({ length: DAYS_IN_WEEK }, (_, index) => {
118
+ const dayOfWeek = (weekStartsOn + index) % DAYS_IN_WEEK;
119
+ return format.format(new Date(REFERENCE_WEEK_START + dayOfWeek * 864e5));
120
+ });
121
+ }
122
+ function getMonthNames(locale) {
123
+ const format = new Intl.DateTimeFormat(locale, {
124
+ month: "short",
125
+ timeZone: "UTC"
126
+ });
127
+ return Array.from(
128
+ { length: 12 },
129
+ (_, month) => format.format(new Date(Date.UTC(2024, month, 15)))
130
+ );
131
+ }
132
+ function Legend({ labels }) {
133
+ const text = { ...defaultLabels, ...labels };
99
134
  return /* @__PURE__ */ jsxs("div", { className: "contribution-heatmap__legend", children: [
100
- /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: "Less" }),
135
+ /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: text.less }),
101
136
  [0, 1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx(
102
137
  "div",
103
138
  {
104
139
  className: `contribution-heatmap__legend-item contribution-heatmap__legend-item--level-${level}`,
105
- title: `Level ${level}`
140
+ title: text.level(level)
106
141
  },
107
142
  level
108
143
  )),
109
- /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: "More" })
144
+ /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: text.more })
110
145
  ] });
111
146
  }
147
+ function DayCell({ contribution, period, locale, labels, delayIndex }) {
148
+ const inRange = isInRange(contribution.date, period);
149
+ const tooltip = formatTooltip(contribution, period, { locale, labels });
150
+ return /* @__PURE__ */ jsx(
151
+ "td",
152
+ {
153
+ className: [
154
+ "contribution-heatmap__day",
155
+ `contribution-heatmap__day--level-${contribution.level}`,
156
+ inRange ? "" : "contribution-heatmap__day--outside"
157
+ ].filter(Boolean).join(" "),
158
+ title: inRange ? tooltip : void 0,
159
+ "aria-label": inRange ? tooltip : void 0,
160
+ role: inRange ? "button" : void 0,
161
+ tabIndex: inRange ? 0 : void 0,
162
+ "data-count": contribution.count,
163
+ "data-date": contribution.date,
164
+ style: { "--animation-delay": `${delayIndex * 5e-3}s` }
165
+ }
166
+ );
167
+ }
168
+ function ContributionHeatmap({
169
+ data: { period, weeks },
170
+ className = "",
171
+ isReverse = false,
172
+ locale = DEFAULT_LOCALE,
173
+ labels
174
+ }) {
175
+ const weekStartsOn = weeks.length ? new Date(weeks[0][0].date).getUTCDay() : 0;
176
+ const dayNames2 = getDayNames(locale, weekStartsOn);
177
+ const months = getMonthsForHeader({ weeks, period, monthNames: getMonthNames(locale) });
178
+ return /* @__PURE__ */ jsxs(
179
+ "div",
180
+ {
181
+ className: `contribution-heatmap${isReverse ? " contribution-heatmap--reverse" : ""} ${className}`,
182
+ children: [
183
+ /* @__PURE__ */ jsx("div", { className: "contribution-heatmap__scroll", children: /* @__PURE__ */ jsx("table", { className: "contribution-heatmap__table", children: isReverse ? /* @__PURE__ */ jsxs(Fragment, { children: [
184
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
185
+ /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__month-header" }),
186
+ dayNames2.map((dayName, dayIndex) => /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__day-header", children: dayName }, dayIndex))
187
+ ] }) }),
188
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => {
189
+ const month = months.find((m) => weekIndex === m.start);
190
+ const showMonthLabel = !!month;
191
+ return /* @__PURE__ */ jsxs("tr", { children: [
192
+ showMonthLabel && /* @__PURE__ */ jsx(
193
+ "td",
194
+ {
195
+ className: "contribution-heatmap__month-label",
196
+ rowSpan: month.span,
197
+ children: month.name
198
+ }
199
+ ),
200
+ week.map((contribution, dayIndex) => /* @__PURE__ */ jsx(
201
+ DayCell,
202
+ {
203
+ contribution,
204
+ period,
205
+ locale,
206
+ labels,
207
+ delayIndex: weekIndex * 7 + dayIndex
208
+ },
209
+ dayIndex
210
+ ))
211
+ ] }, weekIndex);
212
+ }) })
213
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
214
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
215
+ /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__day-header" }),
216
+ months.map((month, index) => /* @__PURE__ */ jsx(
217
+ "th",
218
+ {
219
+ className: `contribution-heatmap__month-header${index === months.length - 1 ? " contribution-heatmap__month-header--last" : ""}`,
220
+ colSpan: month.span,
221
+ children: /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__month-header-text", children: month.name })
222
+ },
223
+ index
224
+ ))
225
+ ] }) }),
226
+ /* @__PURE__ */ jsx("tbody", { children: dayNames2.map((dayName, dayIndex) => /* @__PURE__ */ jsxs("tr", { children: [
227
+ /* @__PURE__ */ jsx("td", { className: "contribution-heatmap__day-label", children: dayName }),
228
+ weeks.map((week, weekIndex) => {
229
+ const contribution = week[dayIndex];
230
+ return /* @__PURE__ */ jsx(
231
+ DayCell,
232
+ {
233
+ contribution,
234
+ period,
235
+ locale,
236
+ labels,
237
+ delayIndex: weekIndex * 7 + dayIndex
238
+ },
239
+ weekIndex
240
+ );
241
+ })
242
+ ] }, dayIndex)) })
243
+ ] }) }) }),
244
+ /* @__PURE__ */ jsx(Legend, { labels })
245
+ ]
246
+ }
247
+ );
248
+ }
112
249
 
113
- // src/ContributionHeatmap/addExtraWeekIfNeeded.ts
114
- var createEmptyContribution = (date) => ({
250
+ // src/shared/groupByWeeks.ts
251
+ var createEmpyContribution = (date) => ({
115
252
  date,
116
253
  count: 0,
117
254
  level: 0
118
255
  });
119
- var createEmptyWeek = (startDate) => {
256
+ var createContributionMap = (contributions) => {
257
+ const contributionMap = /* @__PURE__ */ new Map();
258
+ contributions.forEach((contribution) => {
259
+ contributionMap.set(contribution.date, contribution);
260
+ });
261
+ return contributionMap;
262
+ };
263
+ var daysIntoWeek = (date, weekStartsOn) => (date.getDay() - weekStartsOn + 7) % 7;
264
+ var getFirstDayOfWeek = (firstDate, weekStartsOn) => {
265
+ const startOfWeek = new Date(firstDate);
266
+ startOfWeek.setDate(firstDate.getDate() - daysIntoWeek(firstDate, weekStartsOn));
267
+ return startOfWeek;
268
+ };
269
+ var getLastDayOfWeek = (lastDate, weekStartsOn) => {
270
+ const endOfWeek = new Date(lastDate);
271
+ endOfWeek.setDate(lastDate.getDate() + (6 - daysIntoWeek(lastDate, weekStartsOn)));
272
+ return endOfWeek;
273
+ };
274
+ var createWeek = (startDate, contributionMap) => {
120
275
  const week = [];
121
276
  const currentDate = new Date(startDate);
122
277
  for (let day = 0; day < 7; day++) {
123
278
  const dateString = createDateString(currentDate);
124
- week.push(createEmptyContribution(dateString));
279
+ const contribution = contributionMap.get(dateString);
280
+ if (contribution) {
281
+ week.push(contribution);
282
+ } else {
283
+ week.push(createEmpyContribution(dateString));
284
+ }
125
285
  currentDate.setDate(currentDate.getDate() + 1);
126
286
  }
127
287
  return week;
128
288
  };
129
- function addExtraWeekIfNeeded(weeks) {
130
- if (weeks.length === 0) return weeks;
131
- const lastWeek = weeks[weeks.length - 1];
132
- const lastDate = new Date(lastWeek[6].date);
133
- const nextWeekStart = new Date(lastDate);
134
- nextWeekStart.setDate(lastDate.getDate() + 1);
135
- const extraWeek = createEmptyWeek(nextWeekStart);
136
- return [...weeks, extraWeek];
137
- }
138
- function ContributionHeatmap({
139
- data: { period: period2, weeks },
140
- className = ""
141
- }) {
142
- const adjustedWeeks = addExtraWeekIfNeeded(weeks);
143
- const months = getMonthsForHeader({ weeks: adjustedWeeks, period: period2 });
144
- return /* @__PURE__ */ jsxs("div", { className: `contribution-heatmap ${className}`, children: [
145
- /* @__PURE__ */ jsxs("table", { className: "contribution-heatmap__table", children: [
146
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
147
- /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__day-header" }),
148
- months.map((month, index) => /* @__PURE__ */ jsx(
149
- "th",
150
- {
151
- className: "contribution-heatmap__month-header",
152
- colSpan: month.span,
153
- children: month.name
154
- },
155
- index
156
- ))
157
- ] }) }),
158
- /* @__PURE__ */ jsx("tbody", { children: dayNames.map((dayName, dayIndex) => /* @__PURE__ */ jsxs("tr", { children: [
159
- /* @__PURE__ */ jsx("td", { className: "contribution-heatmap__day-label", children: dayName }),
160
- adjustedWeeks.map((week, weekIndex) => {
161
- const contribution = week[dayIndex];
162
- return /* @__PURE__ */ jsx(
163
- "td",
164
- {
165
- className: `contribution-heatmap__day contribution-heatmap__day--level-${contribution.level}`,
166
- title: formatTooltip(contribution, period2),
167
- "aria-label": formatTooltip(contribution, period2),
168
- role: "button",
169
- tabIndex: 0,
170
- "data-count": contribution.count,
171
- "data-date": contribution.date,
172
- style: {
173
- "--animation-delay": `${(weekIndex * 7 + dayIndex) * 5e-3}s`
174
- }
175
- },
176
- weekIndex
177
- );
178
- })
179
- ] }, dayIndex)) })
180
- ] }),
181
- /* @__PURE__ */ jsx(Legend, {})
182
- ] });
183
- }
289
+ var groupByWeeks = (contributions, { weekStartsOn = 0 } = {}) => {
290
+ const weeks = [];
291
+ const contributionMap = createContributionMap(contributions);
292
+ const firstDay = getFirstDayOfWeek(new Date(contributions[0].date), weekStartsOn);
293
+ const lastDate = getLastDayOfWeek(
294
+ new Date(contributions[contributions.length - 1].date),
295
+ weekStartsOn
296
+ );
297
+ const currentDate = new Date(firstDay);
298
+ while (currentDate <= lastDate) {
299
+ weeks.push(createWeek(currentDate, contributionMap));
300
+ currentDate.setDate(currentDate.getDate() + 7);
301
+ }
302
+ return weeks;
303
+ };
184
304
 
185
305
  // src/mocks/createRealisticMockedCount.ts
186
306
  var isSummerMonth = (month) => {
@@ -205,7 +325,7 @@ function createRealisticMockedCount(date) {
205
325
  return count;
206
326
  }
207
327
 
208
- // src/mocks/generateMockData.tsx
328
+ // src/mocks/index.tsx
209
329
  var getRandomCount = () => Math.floor(Math.random() * 15);
210
330
  var getLevel = (count) => {
211
331
  let level = 0;
@@ -216,9 +336,9 @@ var getLevel = (count) => {
216
336
  else level = 4;
217
337
  return level;
218
338
  };
219
- function getRange(period2) {
220
- if (period2 !== "lastYear") {
221
- return period2;
339
+ function getRange(period) {
340
+ if (period !== "lastYear") {
341
+ return period;
222
342
  }
223
343
  const end = /* @__PURE__ */ new Date();
224
344
  const start = /* @__PURE__ */ new Date();
@@ -226,11 +346,11 @@ function getRange(period2) {
226
346
  return { start, end };
227
347
  }
228
348
  function generateMockData({
229
- period: period2,
349
+ period,
230
350
  isRealistic
231
351
  }) {
232
352
  const data = [];
233
- const { start, end } = getRange(period2);
353
+ const { start, end } = getRange(period);
234
354
  for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
235
355
  const count = isRealistic ? createRealisticMockedCount(date) : getRandomCount();
236
356
  data.push({
@@ -242,142 +362,6 @@ function generateMockData({
242
362
  return data;
243
363
  }
244
364
 
245
- // src/shared/groupByWeeks.ts
246
- var createEmpyContribution = (date) => ({
247
- date,
248
- count: 0,
249
- level: 0
250
- });
251
- var createContributionMap = (contributions) => {
252
- const contributionMap = /* @__PURE__ */ new Map();
253
- contributions.forEach((contribution) => {
254
- contributionMap.set(contribution.date, contribution);
255
- });
256
- return contributionMap;
257
- };
258
- var getFirstSunday = (firstDate) => {
259
- const startOfWeek = new Date(firstDate);
260
- startOfWeek.setDate(firstDate.getDate() - firstDate.getDay());
261
- return startOfWeek;
262
- };
263
- var getLastSaturday = (lastDate) => {
264
- const endOfWeek = new Date(lastDate);
265
- endOfWeek.setDate(lastDate.getDate() + (6 - lastDate.getDay()));
266
- return endOfWeek;
267
- };
268
- var createWeek = (startDate, contributionMap) => {
269
- const week = [];
270
- const currentDate = new Date(startDate);
271
- for (let day = 0; day < 7; day++) {
272
- const dateString = createDateString(currentDate);
273
- const contribution = contributionMap.get(dateString);
274
- if (contribution) {
275
- week.push(contribution);
276
- } else {
277
- week.push(createEmpyContribution(dateString));
278
- }
279
- currentDate.setDate(currentDate.getDate() + 1);
280
- }
281
- return week;
282
- };
283
- var groupByWeeks = (contributions) => {
284
- const weeks = [];
285
- const contributionMap = createContributionMap(contributions);
286
- const firstDay = getFirstSunday(new Date(contributions[0].date));
287
- const lastDate = getLastSaturday(
288
- new Date(contributions[contributions.length - 1].date)
289
- );
290
- const currentDate = new Date(firstDay);
291
- while (currentDate <= lastDate) {
292
- weeks.push(createWeek(currentDate, contributionMap));
293
- currentDate.setDate(currentDate.getDate() + 7);
294
- }
295
- return weeks;
296
- };
297
- function ControlGroups({
298
- actions: { theme, setTheme, hasRealisticData, setHasRealisticData }
299
- }) {
300
- return /* @__PURE__ */ jsxs(Fragment, { children: [
301
- /* @__PURE__ */ jsxs("div", { className: "control-group", children: [
302
- /* @__PURE__ */ jsx("label", { className: "control-group__label", children: "Theme:" }),
303
- /* @__PURE__ */ jsxs("div", { className: "control-group__buttons", children: [
304
- /* @__PURE__ */ jsx(
305
- "button",
306
- {
307
- className: `theme-button ${theme === "" ? "theme-button--active" : ""}`,
308
- onClick: () => setTheme(""),
309
- children: "GitHub"
310
- }
311
- ),
312
- /* @__PURE__ */ jsx(
313
- "button",
314
- {
315
- className: `theme-button ${theme === "ocean" ? "theme-button--active" : ""}`,
316
- onClick: () => setTheme("ocean"),
317
- children: "Ocean"
318
- }
319
- ),
320
- /* @__PURE__ */ jsx(
321
- "button",
322
- {
323
- className: `theme-button ${theme === "sunset" ? "theme-button--active" : ""}`,
324
- onClick: () => setTheme("sunset"),
325
- children: "Sunset"
326
- }
327
- ),
328
- /* @__PURE__ */ jsx(
329
- "button",
330
- {
331
- className: `theme-button ${theme === "purple" ? "theme-button--active" : ""}`,
332
- onClick: () => setTheme("purple"),
333
- children: "Purple"
334
- }
335
- )
336
- ] })
337
- ] }),
338
- /* @__PURE__ */ jsx("div", { className: "control-group", children: /* @__PURE__ */ jsxs("label", { className: "control-group__checkbox", children: [
339
- /* @__PURE__ */ jsx(
340
- "input",
341
- {
342
- type: "checkbox",
343
- checked: hasRealisticData,
344
- onChange: (e) => setHasRealisticData(e.target.checked)
345
- }
346
- ),
347
- "Use realistic sample data"
348
- ] }) })
349
- ] });
350
- }
351
- var sampleData = generateMockData({ period: "lastYear", isRealistic: true });
352
- var period = getLastYearPeriod();
353
- function ContributionHeatmapExample() {
354
- const [theme, setTheme] = useState("");
355
- const [hasRealisticData, setHasRealisticData] = useState(false);
356
- const contribution = hasRealisticData ? sampleData : generateMockData({
357
- period,
358
- isRealistic: false
359
- });
360
- return /* @__PURE__ */ jsxs(Fragment, { children: [
361
- /* @__PURE__ */ jsx("div", { className: "example__controls", children: /* @__PURE__ */ jsx(
362
- ControlGroups,
363
- {
364
- actions: { theme, setTheme, hasRealisticData, setHasRealisticData }
365
- }
366
- ) }),
367
- /* @__PURE__ */ jsx("div", { className: "example__heatmap", children: /* @__PURE__ */ jsx(
368
- ContributionHeatmap,
369
- {
370
- className: theme ? `contribution-heatmap--${theme}` : "",
371
- data: {
372
- weeks: groupByWeeks(contribution),
373
- contribution,
374
- period
375
- }
376
- }
377
- ) })
378
- ] });
379
- }
380
-
381
- export { ContributionHeatmap, ContributionHeatmapExample };
365
+ export { ContributionHeatmap, createDateString, dayNames, generateMockData, getLastMonthPeriod, getLastYearPeriod, groupByWeeks, monthNames };
382
366
  //# sourceMappingURL=index.js.map
383
367
  //# sourceMappingURL=index.js.map