@pearpages/heatmap 0.2.0 → 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",
@@ -39,7 +46,8 @@ var createDateString = (date) => date.toISOString().split("T")[0];
39
46
  // src/ContributionHeatmap/getMonthsForHeader.ts
40
47
  function getMonthsForHeader({
41
48
  weeks,
42
- period
49
+ period,
50
+ monthNames: monthNames2 = monthNames
43
51
  }) {
44
52
  const months = [];
45
53
  const { start, end } = period;
@@ -63,7 +71,7 @@ function getMonthsForHeader({
63
71
  }
64
72
  if (monthToUse !== currentMonth) {
65
73
  if (currentMonth !== -1) {
66
- months.push({ name: monthNames[currentMonth], span: currentSpan, start: weekIndex - currentSpan });
74
+ months.push({ name: monthNames2[currentMonth], span: currentSpan, start: weekIndex - currentSpan });
67
75
  }
68
76
  currentMonth = monthToUse;
69
77
  currentSpan = 1;
@@ -73,130 +81,227 @@ function getMonthsForHeader({
73
81
  }
74
82
  });
75
83
  if (currentMonth !== -1) {
76
- months.push({ name: monthNames[currentMonth], span: currentSpan, start: weeks.length - currentSpan });
77
- }
78
- if (months.length > 0 && months[months.length - 1].span === 1) {
79
- months[months.length - 1].span = 2;
84
+ months.push({ name: monthNames2[currentMonth], span: currentSpan, start: weeks.length - currentSpan });
80
85
  }
81
86
  return months;
82
87
  }
83
88
 
84
89
  // src/shared/formatTooltip.tsx
85
90
  function isInRange(date, { start, end }) {
86
- const actualStartDate = new Date(start);
87
- const actualEndDate = new Date(end);
88
- const contributionDate = new Date(date);
89
- return contributionDate > actualStartDate && contributionDate < actualEndDate;
91
+ return date >= createDateString(start) && date <= createDateString(end);
90
92
  }
91
- var formatTooltip = (contribution, { start, end }) => {
93
+ var formatTooltip = (contribution, { start, end }, { locale = DEFAULT_LOCALE, labels } = {}) => {
94
+ const text = { ...defaultLabels, ...labels };
92
95
  const date = new Date(contribution.date);
93
- const formattedDate = date.toLocaleDateString("en-US", {
96
+ const formattedDate = date.toLocaleDateString(locale, {
94
97
  weekday: "short",
95
98
  year: "numeric",
96
99
  month: "short",
97
- day: "numeric"
100
+ day: "numeric",
101
+ timeZone: "UTC"
98
102
  });
99
- if (!isInRange(new Date(contribution.date), { start, end }) || contribution.count === 0) {
100
- return `${formattedDate}: No contributions`;
103
+ if (!isInRange(contribution.date, { start, end }) || contribution.count === 0) {
104
+ return `${formattedDate}: ${text.noContributions}`;
101
105
  }
102
- return `${formattedDate}: ${contribution.count} contribution${contribution.count !== 1 ? "s" : ""}`;
106
+ return `${formattedDate}: ${text.contributions(contribution.count)}`;
103
107
  };
104
- 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 };
105
134
  return /* @__PURE__ */ jsxs("div", { className: "contribution-heatmap__legend", children: [
106
- /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: "Less" }),
135
+ /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: text.less }),
107
136
  [0, 1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx(
108
137
  "div",
109
138
  {
110
139
  className: `contribution-heatmap__legend-item contribution-heatmap__legend-item--level-${level}`,
111
- title: `Level ${level}`
140
+ title: text.level(level)
112
141
  },
113
142
  level
114
143
  )),
115
- /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: "More" })
144
+ /* @__PURE__ */ jsx("span", { className: "contribution-heatmap__legend-text", children: text.more })
116
145
  ] });
117
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
+ }
118
168
  function ContributionHeatmap({
119
169
  data: { period, weeks },
120
170
  className = "",
121
- isReverse = false
171
+ isReverse = false,
172
+ locale = DEFAULT_LOCALE,
173
+ labels
122
174
  }) {
123
- const months = getMonthsForHeader({ weeks, period });
124
- return /* @__PURE__ */ jsxs("div", { className: `contribution-heatmap ${className}`, children: [
125
- /* @__PURE__ */ jsx("table", { className: "contribution-heatmap__table", children: isReverse ? /* @__PURE__ */ jsxs(Fragment, { children: [
126
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
127
- /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__month-header" }),
128
- dayNames.map((dayName, dayIndex) => /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__day-header", children: dayName }, dayIndex))
129
- ] }) }),
130
- /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => {
131
- const month = months.find((m) => weekIndex === m.start);
132
- const showMonthLabel = !!month;
133
- return /* @__PURE__ */ jsxs("tr", { children: [
134
- showMonthLabel && /* @__PURE__ */ jsx(
135
- "td",
136
- {
137
- className: "contribution-heatmap__month-label",
138
- rowSpan: month.span,
139
- children: month.name
140
- }
141
- ),
142
- week.map((contribution, dayIndex) => /* @__PURE__ */ jsx(
143
- "td",
144
- {
145
- className: `contribution-heatmap__day contribution-heatmap__day--level-${contribution.level}`,
146
- title: formatTooltip(contribution, period),
147
- "aria-label": formatTooltip(contribution, period),
148
- role: "button",
149
- tabIndex: 0,
150
- "data-count": contribution.count,
151
- "data-date": contribution.date,
152
- style: {
153
- "--animation-delay": `${(weekIndex * 7 + dayIndex) * 5e-3}s`
154
- }
155
- },
156
- dayIndex
157
- ))
158
- ] }, weekIndex);
159
- }) })
160
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
161
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
162
- /* @__PURE__ */ jsx("th", { className: "contribution-heatmap__day-header" }),
163
- months.map((month, index) => /* @__PURE__ */ jsx(
164
- "th",
165
- {
166
- className: "contribution-heatmap__month-header",
167
- colSpan: month.span,
168
- children: month.name
169
- },
170
- index
171
- ))
172
- ] }) }),
173
- /* @__PURE__ */ jsx("tbody", { children: dayNames.map((dayName, dayIndex) => /* @__PURE__ */ jsxs("tr", { children: [
174
- /* @__PURE__ */ jsx("td", { className: "contribution-heatmap__day-label", children: dayName }),
175
- weeks.map((week, weekIndex) => {
176
- const contribution = week[dayIndex];
177
- return /* @__PURE__ */ jsx(
178
- "td",
179
- {
180
- className: `contribution-heatmap__day contribution-heatmap__day--level-${contribution.level}`,
181
- title: formatTooltip(contribution, period),
182
- "aria-label": formatTooltip(contribution, period),
183
- role: "button",
184
- tabIndex: 0,
185
- "data-count": contribution.count,
186
- "data-date": contribution.date,
187
- style: {
188
- "--animation-delay": `${(weekIndex * 7 + dayIndex) * 5e-3}s`
189
- }
190
- },
191
- weekIndex
192
- );
193
- })
194
- ] }, dayIndex)) })
195
- ] }) }),
196
- /* @__PURE__ */ jsx(Legend, {})
197
- ] });
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
+ );
198
248
  }
199
249
 
250
+ // src/shared/groupByWeeks.ts
251
+ var createEmpyContribution = (date) => ({
252
+ date,
253
+ count: 0,
254
+ level: 0
255
+ });
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) => {
275
+ const week = [];
276
+ const currentDate = new Date(startDate);
277
+ for (let day = 0; day < 7; day++) {
278
+ const dateString = createDateString(currentDate);
279
+ const contribution = contributionMap.get(dateString);
280
+ if (contribution) {
281
+ week.push(contribution);
282
+ } else {
283
+ week.push(createEmpyContribution(dateString));
284
+ }
285
+ currentDate.setDate(currentDate.getDate() + 1);
286
+ }
287
+ return week;
288
+ };
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
+ };
304
+
200
305
  // src/mocks/createRealisticMockedCount.ts
201
306
  var isSummerMonth = (month) => {
202
307
  return month >= 5 && month <= 7;
@@ -257,158 +362,6 @@ function generateMockData({
257
362
  return data;
258
363
  }
259
364
 
260
- // src/shared/groupByWeeks.ts
261
- var createEmpyContribution = (date) => ({
262
- date,
263
- count: 0,
264
- level: 0
265
- });
266
- var createContributionMap = (contributions) => {
267
- const contributionMap = /* @__PURE__ */ new Map();
268
- contributions.forEach((contribution) => {
269
- contributionMap.set(contribution.date, contribution);
270
- });
271
- return contributionMap;
272
- };
273
- var getFirstSunday = (firstDate) => {
274
- const startOfWeek = new Date(firstDate);
275
- startOfWeek.setDate(firstDate.getDate() - firstDate.getDay());
276
- return startOfWeek;
277
- };
278
- var getLastSaturday = (lastDate) => {
279
- const endOfWeek = new Date(lastDate);
280
- endOfWeek.setDate(lastDate.getDate() + (6 - lastDate.getDay()));
281
- return endOfWeek;
282
- };
283
- var createWeek = (startDate, contributionMap) => {
284
- const week = [];
285
- const currentDate = new Date(startDate);
286
- for (let day = 0; day < 7; day++) {
287
- const dateString = createDateString(currentDate);
288
- const contribution = contributionMap.get(dateString);
289
- if (contribution) {
290
- week.push(contribution);
291
- } else {
292
- week.push(createEmpyContribution(dateString));
293
- }
294
- currentDate.setDate(currentDate.getDate() + 1);
295
- }
296
- return week;
297
- };
298
- var groupByWeeks = (contributions) => {
299
- const weeks = [];
300
- const contributionMap = createContributionMap(contributions);
301
- const firstDay = getFirstSunday(new Date(contributions[0].date));
302
- const lastDate = getLastSaturday(
303
- new Date(contributions[contributions.length - 1].date)
304
- );
305
- const currentDate = new Date(firstDay);
306
- while (currentDate <= lastDate) {
307
- weeks.push(createWeek(currentDate, contributionMap));
308
- currentDate.setDate(currentDate.getDate() + 7);
309
- }
310
- return weeks;
311
- };
312
- function ControlGroups({
313
- actions: { theme, setTheme, hasRealisticData, setHasRealisticData }
314
- }) {
315
- return /* @__PURE__ */ jsxs(Fragment, { children: [
316
- /* @__PURE__ */ jsxs("div", { className: "control-group", children: [
317
- /* @__PURE__ */ jsx("label", { className: "control-group__label", children: "Theme:" }),
318
- /* @__PURE__ */ jsxs("div", { className: "control-group__buttons", children: [
319
- /* @__PURE__ */ jsx(
320
- "button",
321
- {
322
- className: `theme-button ${theme === "" ? "theme-button--active" : ""}`,
323
- onClick: () => setTheme(""),
324
- children: "GitHub"
325
- }
326
- ),
327
- /* @__PURE__ */ jsx(
328
- "button",
329
- {
330
- className: `theme-button ${theme === "ocean" ? "theme-button--active" : ""}`,
331
- onClick: () => setTheme("ocean"),
332
- children: "Ocean"
333
- }
334
- ),
335
- /* @__PURE__ */ jsx(
336
- "button",
337
- {
338
- className: `theme-button ${theme === "sunset" ? "theme-button--active" : ""}`,
339
- onClick: () => setTheme("sunset"),
340
- children: "Sunset"
341
- }
342
- ),
343
- /* @__PURE__ */ jsx(
344
- "button",
345
- {
346
- className: `theme-button ${theme === "purple" ? "theme-button--active" : ""}`,
347
- onClick: () => setTheme("purple"),
348
- children: "Purple"
349
- }
350
- )
351
- ] })
352
- ] }),
353
- /* @__PURE__ */ jsx("div", { className: "control-group", children: /* @__PURE__ */ jsxs("label", { className: "control-group__checkbox", children: [
354
- /* @__PURE__ */ jsx(
355
- "input",
356
- {
357
- type: "checkbox",
358
- checked: hasRealisticData,
359
- onChange: (e) => setHasRealisticData(e.target.checked)
360
- }
361
- ),
362
- "Use realistic sample data"
363
- ] }) })
364
- ] });
365
- }
366
- var lastYearPeriod = getLastYearPeriod();
367
- var lastMonthPeriod = getLastMonthPeriod();
368
- function ContributionHeatmapExample() {
369
- const [theme, setTheme] = useState("");
370
- const [hasRealisticData, setHasRealisticData] = useState(false);
371
- const lastYearContribution = hasRealisticData ? generateMockData({ period: lastYearPeriod, isRealistic: true }) : generateMockData({
372
- period: lastYearPeriod,
373
- isRealistic: false
374
- });
375
- const lastMonthContribution = hasRealisticData ? generateMockData({ period: lastMonthPeriod, isRealistic: true }) : generateMockData({
376
- period: lastMonthPeriod,
377
- isRealistic: false
378
- });
379
- return /* @__PURE__ */ jsxs(Fragment, { children: [
380
- /* @__PURE__ */ jsx("div", { className: "example__controls", children: /* @__PURE__ */ jsx(
381
- ControlGroups,
382
- {
383
- actions: { theme, setTheme, hasRealisticData, setHasRealisticData }
384
- }
385
- ) }),
386
- /* @__PURE__ */ jsx("div", { className: "example__heatmap", children: /* @__PURE__ */ jsx(
387
- ContributionHeatmap,
388
- {
389
- className: theme ? `contribution-heatmap--${theme}` : "",
390
- data: {
391
- weeks: groupByWeeks(lastYearContribution),
392
- contribution: lastYearContribution,
393
- period: lastYearPeriod
394
- }
395
- }
396
- ) }),
397
- /* @__PURE__ */ jsx("div", { className: "example__heatmap", children: /* @__PURE__ */ jsx(
398
- ContributionHeatmap,
399
- {
400
- isReverse: true,
401
- className: theme ? `contribution-heatmap--${theme}` : "",
402
- data: {
403
- weeks: groupByWeeks(lastMonthContribution),
404
- contribution: lastMonthContribution,
405
- period: lastMonthPeriod
406
- }
407
- }
408
- ) })
409
- ] });
410
- }
411
-
412
- export { ContributionHeatmap, ContributionHeatmapExample };
365
+ export { ContributionHeatmap, createDateString, dayNames, generateMockData, getLastMonthPeriod, getLastYearPeriod, groupByWeeks, monthNames };
413
366
  //# sourceMappingURL=index.js.map
414
367
  //# sourceMappingURL=index.js.map