@gnome-ui/react 1.40.0 → 1.41.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.
Files changed (90) hide show
  1. package/README.md +42 -0
  2. package/dist/components/AvatarRotator/AvatarRotator.cjs +2 -0
  3. package/dist/components/AvatarRotator/AvatarRotator.cjs.map +1 -0
  4. package/dist/components/AvatarRotator/AvatarRotator.d.ts +36 -0
  5. package/dist/components/AvatarRotator/AvatarRotator.js +84 -0
  6. package/dist/components/AvatarRotator/AvatarRotator.js.map +1 -0
  7. package/dist/components/AvatarRotator/AvatarRotator.module.css.cjs +2 -0
  8. package/dist/components/AvatarRotator/AvatarRotator.module.css.cjs.map +1 -0
  9. package/dist/components/AvatarRotator/AvatarRotator.module.css.js +13 -0
  10. package/dist/components/AvatarRotator/AvatarRotator.module.css.js.map +1 -0
  11. package/dist/components/AvatarRotator/index.d.ts +2 -0
  12. package/dist/components/AvatarRotator.cjs +1 -0
  13. package/dist/components/AvatarRotator.d.ts +2 -0
  14. package/dist/components/AvatarRotator.js +2 -0
  15. package/dist/components/Chip/Chip.cjs +1 -1
  16. package/dist/components/Chip/Chip.cjs.map +1 -1
  17. package/dist/components/Chip/Chip.js +19 -19
  18. package/dist/components/ContributionGraph/ContributionGraph.cjs +1 -1
  19. package/dist/components/ContributionGraph/ContributionGraph.cjs.map +1 -1
  20. package/dist/components/ContributionGraph/ContributionGraph.js +108 -117
  21. package/dist/components/ContributionGraph/ContributionGraph.js.map +1 -1
  22. package/dist/components/CountDownTimer/CountDownTimer.cjs +1 -1
  23. package/dist/components/CountDownTimer/CountDownTimer.cjs.map +1 -1
  24. package/dist/components/CountDownTimer/CountDownTimer.js +37 -28
  25. package/dist/components/CountDownTimer/CountDownTimer.js.map +1 -1
  26. package/dist/components/Dropdown/Dropdown.cjs +1 -1
  27. package/dist/components/Dropdown/Dropdown.cjs.map +1 -1
  28. package/dist/components/Dropdown/Dropdown.js +45 -46
  29. package/dist/components/Dropdown/Dropdown.js.map +1 -1
  30. package/dist/components/GnomeProvider/GnomeContext.cjs +2 -0
  31. package/dist/components/GnomeProvider/GnomeContext.cjs.map +1 -0
  32. package/dist/components/GnomeProvider/GnomeContext.d.ts +16 -0
  33. package/dist/components/GnomeProvider/GnomeContext.js +40 -0
  34. package/dist/components/GnomeProvider/GnomeContext.js.map +1 -0
  35. package/dist/components/GnomeProvider/GnomeProvider.cjs +2 -0
  36. package/dist/components/GnomeProvider/GnomeProvider.cjs.map +1 -0
  37. package/dist/components/GnomeProvider/GnomeProvider.d.ts +24 -0
  38. package/dist/components/GnomeProvider/GnomeProvider.js +25 -0
  39. package/dist/components/GnomeProvider/GnomeProvider.js.map +1 -0
  40. package/dist/components/PasswordEntryRow/PasswordEntryRow.cjs +1 -1
  41. package/dist/components/PasswordEntryRow/PasswordEntryRow.cjs.map +1 -1
  42. package/dist/components/PasswordEntryRow/PasswordEntryRow.js +11 -12
  43. package/dist/components/PasswordEntryRow/PasswordEntryRow.js.map +1 -1
  44. package/dist/components/SearchBar/SearchBar.cjs +1 -1
  45. package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
  46. package/dist/components/SearchBar/SearchBar.js +38 -39
  47. package/dist/components/SearchBar/SearchBar.js.map +1 -1
  48. package/dist/components/Sidebar/SidebarSection.cjs +1 -1
  49. package/dist/components/Sidebar/SidebarSection.cjs.map +1 -1
  50. package/dist/components/Sidebar/SidebarSection.js +41 -42
  51. package/dist/components/Sidebar/SidebarSection.js.map +1 -1
  52. package/dist/components/Toast/Toast.cjs +1 -1
  53. package/dist/components/Toast/Toast.cjs.map +1 -1
  54. package/dist/components/Toast/Toast.js +18 -18
  55. package/dist/hooks/index.d.ts +2 -0
  56. package/dist/hooks.cjs +1 -1
  57. package/dist/hooks.js +3 -2
  58. package/dist/index.cjs +1 -1
  59. package/dist/index.d.ts +8 -0
  60. package/dist/index.js +90 -86
  61. package/dist/style.css +1 -1
  62. package/package.json +16 -1
  63. package/dist/icons/dist/icons/Check.cjs +0 -2
  64. package/dist/icons/dist/icons/Check.cjs.map +0 -1
  65. package/dist/icons/dist/icons/Check.js +0 -12
  66. package/dist/icons/dist/icons/Check.js.map +0 -1
  67. package/dist/icons/dist/icons/Close.cjs +0 -2
  68. package/dist/icons/dist/icons/Close.cjs.map +0 -1
  69. package/dist/icons/dist/icons/Close.js +0 -12
  70. package/dist/icons/dist/icons/Close.js.map +0 -1
  71. package/dist/icons/dist/icons/PanDown.cjs +0 -2
  72. package/dist/icons/dist/icons/PanDown.cjs.map +0 -1
  73. package/dist/icons/dist/icons/PanDown.js +0 -12
  74. package/dist/icons/dist/icons/PanDown.js.map +0 -1
  75. package/dist/icons/dist/icons/PanUp.cjs +0 -2
  76. package/dist/icons/dist/icons/PanUp.cjs.map +0 -1
  77. package/dist/icons/dist/icons/PanUp.js +0 -12
  78. package/dist/icons/dist/icons/PanUp.js.map +0 -1
  79. package/dist/icons/dist/icons/Search.cjs +0 -2
  80. package/dist/icons/dist/icons/Search.cjs.map +0 -1
  81. package/dist/icons/dist/icons/Search.js +0 -12
  82. package/dist/icons/dist/icons/Search.js.map +0 -1
  83. package/dist/icons/dist/icons/ViewConceal.cjs +0 -2
  84. package/dist/icons/dist/icons/ViewConceal.cjs.map +0 -1
  85. package/dist/icons/dist/icons/ViewConceal.js +0 -12
  86. package/dist/icons/dist/icons/ViewConceal.js.map +0 -1
  87. package/dist/icons/dist/icons/ViewReveal.cjs +0 -2
  88. package/dist/icons/dist/icons/ViewReveal.cjs.map +0 -1
  89. package/dist/icons/dist/icons/ViewReveal.js +0 -12
  90. package/dist/icons/dist/icons/ViewReveal.js.map +0 -1
@@ -1,239 +1,230 @@
1
1
  import e from "./ContributionGraph.module.css.js";
2
- import { useMemo as t, useRef as n, useState as r } from "react";
3
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
2
+ import { useDateTimeFormatter as t } from "../GnomeProvider/GnomeContext.js";
3
+ import { useMemo as n, useRef as r, useState as i } from "react";
4
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
5
  //#region src/components/ContributionGraph/ContributionGraph.tsx
5
- var o = [
6
- "Jan",
7
- "Feb",
8
- "Mar",
9
- "Apr",
10
- "May",
11
- "Jun",
12
- "Jul",
13
- "Aug",
14
- "Sep",
15
- "Oct",
16
- "Nov",
17
- "Dec"
18
- ], s = [
19
- "Sun",
20
- "Mon",
21
- "Tue",
22
- "Wed",
23
- "Thu",
24
- "Fri",
25
- "Sat"
26
- ], c = 28, l = 20, u = [
6
+ var s = new Date(2e3, 0, 2);
7
+ function c(e, t) {
8
+ return t.format(new Date(2e3, e));
9
+ }
10
+ function l(e, t) {
11
+ let n = new Date(s);
12
+ return n.setDate(s.getDate() + e), t.format(n);
13
+ }
14
+ var u = 28, d = 20, f = [
27
15
  "var(--gnome-card-shade-color, rgba(0,0,0,0.07))",
28
16
  "var(--gnome-green-1, #8ff0a4)",
29
17
  "var(--gnome-green-2, #57e389)",
30
18
  "var(--gnome-green-4, #2ec27e)",
31
19
  "var(--gnome-green-5, #26a269)"
32
20
  ];
33
- function d(e) {
21
+ function p(e) {
34
22
  return `${e.getFullYear()}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}`;
35
23
  }
36
- function f(e) {
24
+ function m(e) {
37
25
  let [t, n, r] = e.split("-").map(Number);
38
26
  return new Date(t, n - 1, r);
39
27
  }
40
- function p(e) {
41
- return f(e).toLocaleDateString("en-US", {
28
+ function h(e, t) {
29
+ return t.format(m(e));
30
+ }
31
+ function g({ data: s, maxLevel: m = 4, colorScale: g, cellSize: _ = 12, cellGap: v = 3, weekStartDay: y = 1, showMonthLabels: b = !0, showDayLabels: x = !0, showLegend: S = !0, weeks: C = 52, ariaLabel: w = "Contribution graph", onDayClick: T, tooltipContent: E, className: D }) {
32
+ let O = t({ month: "short" }), k = t({ weekday: "short" }), A = t({
42
33
  weekday: "long",
43
34
  year: "numeric",
44
35
  month: "long",
45
36
  day: "numeric"
46
- });
47
- }
48
- function m({ data: f, maxLevel: m = 4, colorScale: h, cellSize: g = 12, cellGap: _ = 3, weekStartDay: v = 1, showMonthLabels: y = !0, showDayLabels: b = !0, showLegend: x = !0, weeks: S = 52, ariaLabel: C = "Contribution graph", onDayClick: w, tooltipContent: T, className: E }) {
49
- let D = g + _, O = n(null), k = n(null), A = h ?? u, [j, M] = r({
37
+ }), j = _ + v, M = r(null), N = r(null), P = g ?? f, [F, I] = i({
50
38
  col: 0,
51
39
  row: 0
52
- }), [N, P] = r(null), F = t(() => {
40
+ }), [L, R] = i(null), z = n(() => {
53
41
  let e = /* @__PURE__ */ new Map();
54
- for (let t of f) e.set(t.date, t.count);
42
+ for (let t of s) e.set(t.date, t.count);
55
43
  return e;
56
- }, [f]), I = t(() => Math.max(1, ...f.map((e) => e.count)), [f]), { grid: L, monthLabels: R } = t(() => {
44
+ }, [s]), B = n(() => Math.max(1, ...s.map((e) => e.count)), [s]), { grid: V, monthLabels: H } = n(() => {
57
45
  let e = /* @__PURE__ */ new Date();
58
46
  e.setHours(0, 0, 0, 0);
59
- let t = (e.getDay() - v + 7) % 7, n = new Date(e);
47
+ let t = (e.getDay() - y + 7) % 7, n = new Date(e);
60
48
  n.setDate(e.getDate() - t);
61
49
  let r = new Date(n);
62
- r.setDate(n.getDate() - (S - 1) * 7);
63
- let i = [], a = [], s = -1;
64
- for (let t = 0; t < S; t++) {
50
+ r.setDate(n.getDate() - (C - 1) * 7);
51
+ let i = [], a = [], o = -1;
52
+ for (let t = 0; t < C; t++) {
65
53
  let n = new Date(r);
66
- n.setDate(r.getDate() + t * 7), n.getMonth() !== s && (a.push({
54
+ n.setDate(r.getDate() + t * 7), n.getMonth() !== o && (a.push({
67
55
  col: t,
68
- month: o[n.getMonth()]
69
- }), s = n.getMonth());
70
- let c = [];
56
+ month: c(n.getMonth(), O)
57
+ }), o = n.getMonth());
58
+ let s = [];
71
59
  for (let n = 0; n < 7; n++) {
72
60
  let i = new Date(r);
73
61
  i.setDate(r.getDate() + t * 7 + n);
74
- let a = i > e, o = d(i), s = a ? 0 : F.get(o) ?? 0, l = s === 0 ? 0 : Math.min(m, Math.ceil(s / I * m));
75
- c.push({
62
+ let a = i > e, o = p(i), c = a ? 0 : z.get(o) ?? 0, l = c === 0 ? 0 : Math.min(m, Math.ceil(c / B * m));
63
+ s.push({
76
64
  iso: o,
77
- count: s,
65
+ count: c,
78
66
  level: l,
79
67
  future: a
80
68
  });
81
69
  }
82
- i.push(c);
70
+ i.push(s);
83
71
  }
84
72
  return {
85
73
  grid: i,
86
74
  monthLabels: a
87
75
  };
88
76
  }, [
89
- F,
90
- I,
77
+ z,
78
+ B,
91
79
  m,
92
- S,
93
- v
94
- ]), z = t(() => [
80
+ C,
81
+ y,
82
+ O
83
+ ]), U = n(() => [
95
84
  1,
96
85
  3,
97
86
  5
98
87
  ].map((e) => ({
99
- row: (e - v + 7) % 7,
100
- label: s[e]
101
- })), [v]), B = b ? c : 0, V = y ? l : 0, H = B + S * D - _, U = V + 7 * D - _, W = Math.min(4, Math.floor(g / 3));
102
- function G(e, t) {
103
- let n = Math.max(0, Math.min(S - 1, e)), r = Math.max(0, Math.min(6, t));
104
- M({
88
+ row: (e - y + 7) % 7,
89
+ label: l(e, k)
90
+ })), [y, k]), W = x ? u : 0, G = b ? d : 0, K = W + C * j - v, q = G + 7 * j - v, J = Math.min(4, Math.floor(_ / 3));
91
+ function Y(e, t) {
92
+ let n = Math.max(0, Math.min(C - 1, e)), r = Math.max(0, Math.min(6, t));
93
+ I({
105
94
  col: n,
106
95
  row: r
107
- }), O.current?.querySelector(`[data-col="${n}"][data-row="${r}"]`)?.focus();
96
+ }), M.current?.querySelector(`[data-col="${n}"][data-row="${r}"]`)?.focus();
108
97
  }
109
- function K(e, t, n) {
98
+ function X(e, t, n) {
110
99
  let r = {
111
100
  ArrowRight: [t + 1, n],
112
101
  ArrowLeft: [t - 1, n],
113
102
  ArrowDown: [t, n + 1],
114
103
  ArrowUp: [t, n - 1]
115
104
  };
116
- if (r[e.key]) e.preventDefault(), G(...r[e.key]);
105
+ if (r[e.key]) e.preventDefault(), Y(...r[e.key]);
117
106
  else if (e.key === "Enter" || e.key === " ") {
118
107
  e.preventDefault();
119
- let r = L[t]?.[n];
120
- r && !r.future && w?.({
108
+ let r = V[t]?.[n];
109
+ r && !r.future && T?.({
121
110
  date: r.iso,
122
111
  count: r.count
123
112
  });
124
113
  }
125
114
  }
126
- function q(e, t) {
127
- let n = k.current;
115
+ function Z(e, t) {
116
+ let n = N.current;
128
117
  if (!n) return;
129
118
  let r = n.getBoundingClientRect(), i = e.currentTarget.getBoundingClientRect();
130
- P({
131
- x: i.left - r.left + g / 2,
119
+ R({
120
+ x: i.left - r.left + _ / 2,
132
121
  y: i.top - r.top,
133
122
  text: t
134
123
  });
135
124
  }
136
- function J(e) {
137
- return e.future ? A[0] : A[Math.min(e.level, A.length - 1)] ?? A[A.length - 1];
125
+ function Q(e) {
126
+ return e.future ? P[0] : P[Math.min(e.level, P.length - 1)] ?? P[P.length - 1];
138
127
  }
139
- function Y(e) {
140
- return e.future ? p(e.iso) : T?.({
128
+ function $(e) {
129
+ if (e.future) return h(e.iso, A);
130
+ let t = h(e.iso, A);
131
+ return E?.({
141
132
  date: e.iso,
142
133
  count: e.count
143
- }) ?? `${e.count} contribution${e.count === 1 ? "" : "s"} on ${p(e.iso)}`;
134
+ }) ?? `${e.count} contribution${e.count === 1 ? "" : "s"} on ${t}`;
144
135
  }
145
- return /* @__PURE__ */ a("div", {
146
- ref: k,
147
- className: [e.wrapper, E].filter(Boolean).join(" "),
136
+ return /* @__PURE__ */ o("div", {
137
+ ref: N,
138
+ className: [e.wrapper, D].filter(Boolean).join(" "),
148
139
  children: [
149
- /* @__PURE__ */ a("svg", {
150
- ref: O,
151
- width: H,
152
- height: U,
140
+ /* @__PURE__ */ o("svg", {
141
+ ref: M,
142
+ width: K,
143
+ height: q,
153
144
  className: e.svg,
154
- "aria-label": C,
145
+ "aria-label": w,
155
146
  role: "img",
156
147
  children: [
157
- y && R.map(({ col: t, month: n }) => /* @__PURE__ */ i("text", {
158
- x: B + t * D,
148
+ b && H.map(({ col: t, month: n }) => /* @__PURE__ */ a("text", {
149
+ x: W + t * j,
159
150
  y: 12,
160
151
  className: e.label,
161
152
  children: n
162
153
  }, `m-${t}`)),
163
- b && z.map(({ row: t, label: n }) => /* @__PURE__ */ i("text", {
154
+ x && U.map(({ row: t, label: n }) => /* @__PURE__ */ a("text", {
164
155
  x: 0,
165
- y: V + t * D + g - 1,
156
+ y: G + t * j + _ - 1,
166
157
  className: e.label,
167
158
  children: n
168
159
  }, `d-${t}`)),
169
- /* @__PURE__ */ i("g", {
160
+ /* @__PURE__ */ a("g", {
170
161
  role: "grid",
171
- "aria-label": C,
172
- children: L.map((t, n) => /* @__PURE__ */ i("g", {
162
+ "aria-label": w,
163
+ children: V.map((t, n) => /* @__PURE__ */ a("g", {
173
164
  role: "row",
174
165
  children: t.map((t, r) => {
175
- let a = Y(t), o = j.col === n && j.row === r;
176
- return /* @__PURE__ */ i("rect", {
166
+ let i = $(t), o = F.col === n && F.row === r;
167
+ return /* @__PURE__ */ a("rect", {
177
168
  "data-col": n,
178
169
  "data-row": r,
179
- x: B + n * D,
180
- y: V + r * D,
181
- width: g,
182
- height: g,
183
- rx: W,
184
- fill: J(t),
170
+ x: W + n * j,
171
+ y: G + r * j,
172
+ width: _,
173
+ height: _,
174
+ rx: J,
175
+ fill: Q(t),
185
176
  opacity: t.future ? .35 : 1,
186
177
  className: e.cell,
187
178
  role: "gridcell",
188
- "aria-label": a,
179
+ "aria-label": i,
189
180
  "aria-disabled": t.future || void 0,
190
181
  tabIndex: o ? 0 : -1,
191
- onClick: () => !t.future && w?.({
182
+ onClick: () => !t.future && T?.({
192
183
  date: t.iso,
193
184
  count: t.count
194
185
  }),
195
- onKeyDown: (e) => K(e, n, r),
196
- onFocus: () => M({
186
+ onKeyDown: (e) => X(e, n, r),
187
+ onFocus: () => I({
197
188
  col: n,
198
189
  row: r
199
190
  }),
200
- onMouseEnter: (e) => q(e, a),
201
- onMouseLeave: () => P(null)
191
+ onMouseEnter: (e) => Z(e, i),
192
+ onMouseLeave: () => R(null)
202
193
  }, `${n}-${r}`);
203
194
  })
204
195
  }, n))
205
196
  })
206
197
  ]
207
198
  }),
208
- N && /* @__PURE__ */ i("div", {
199
+ L && /* @__PURE__ */ a("div", {
209
200
  className: e.tooltip,
210
201
  style: {
211
- left: N.x,
212
- top: N.y
202
+ left: L.x,
203
+ top: L.y
213
204
  },
214
205
  role: "tooltip",
215
- children: N.text
206
+ children: L.text
216
207
  }),
217
- x && /* @__PURE__ */ a("div", {
208
+ S && /* @__PURE__ */ o("div", {
218
209
  className: e.legend,
219
210
  children: [
220
- /* @__PURE__ */ i("span", {
211
+ /* @__PURE__ */ a("span", {
221
212
  className: e.legendLabel,
222
213
  children: "Less"
223
214
  }),
224
- Array.from({ length: m + 1 }, (t, n) => /* @__PURE__ */ i("svg", {
225
- width: g,
226
- height: g,
215
+ Array.from({ length: m + 1 }, (t, n) => /* @__PURE__ */ a("svg", {
216
+ width: _,
217
+ height: _,
227
218
  "aria-hidden": "true",
228
219
  className: e.legendCell,
229
- children: /* @__PURE__ */ i("rect", {
230
- width: g,
231
- height: g,
232
- rx: W,
233
- fill: A[Math.min(n, A.length - 1)]
220
+ children: /* @__PURE__ */ a("rect", {
221
+ width: _,
222
+ height: _,
223
+ rx: J,
224
+ fill: P[Math.min(n, P.length - 1)]
234
225
  })
235
226
  }, n)),
236
- /* @__PURE__ */ i("span", {
227
+ /* @__PURE__ */ a("span", {
237
228
  className: e.legendLabel,
238
229
  children: "More"
239
230
  })
@@ -243,6 +234,6 @@ function m({ data: f, maxLevel: m = 4, colorScale: h, cellSize: g = 12, cellGap:
243
234
  });
244
235
  }
245
236
  //#endregion
246
- export { m as ContributionGraph };
237
+ export { g as ContributionGraph };
247
238
 
248
239
  //# sourceMappingURL=ContributionGraph.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContributionGraph.js","names":[],"sources":["../../../src/components/ContributionGraph/ContributionGraph.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport styles from \"./ContributionGraph.module.css\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /**\n * Colour scale — length must be maxLevel + 1 (index 0 = empty).\n * Defaults to the Adwaita green palette.\n */\n colorScale?: string[];\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\nconst SHORT_MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n// 0=Sun … 6=Sat\nconst SHORT_DAYS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst DEFAULT_COLORS = [\n \"var(--gnome-card-shade-color, rgba(0,0,0,0.07))\",\n \"var(--gnome-green-1, #8ff0a4)\",\n \"var(--gnome-green-2, #57e389)\",\n \"var(--gnome-green-4, #2ec27e)\",\n \"var(--gnome-green-5, #26a269)\",\n];\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string): string {\n return isoToLocal(iso).toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n colorScale,\n cellSize = 12,\n cellGap = 3,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const stride = cellSize + cellGap;\n const svgRef = useRef<SVGSVGElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const colors = colorScale ?? DEFAULT_COLORS;\n\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n x: number;\n y: number;\n text: string;\n } | null>(null);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (weeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < weeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({ col, month: SHORT_MONTHS[colStart.getMonth()] });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, weeks, weekStartDay]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: SHORT_DAYS[dayIndex],\n })),\n [weekStartDay],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const svgWidth = dayLabelW + weeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(cellSize / 3));\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(weeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const wRect = wrapper.getBoundingClientRect();\n const rRect = (e.currentTarget as Element).getBoundingClientRect();\n setTooltip({\n x: rRect.left - wRect.left + cellSize / 2,\n y: rRect.top - wRect.top,\n text,\n });\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${fullDateLabel(cell.iso)}`\n );\n }\n\n return (\n <div\n ref={wrapperRef}\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + cellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={() => setTooltip(null)}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n\n {tooltip && (\n <div\n className={styles.tooltip}\n style={{ left: tooltip.x, top: tooltip.y }}\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={cellSize}\n height={cellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ContributionGraph.js","names":[],"sources":["../../../src/components/ContributionGraph/ContributionGraph.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport styles from \"./ContributionGraph.module.css\";\nimport { useDateTimeFormatter } from \"../GnomeProvider/GnomeContext\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /**\n * Colour scale — length must be maxLevel + 1 (index 0 = empty).\n * Defaults to the Adwaita green palette.\n */\n colorScale?: string[];\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\n// Reference Sunday: 2000-01-02 is a Sunday (day index 0)\nconst REF_SUNDAY = new Date(2000, 0, 2);\n\nfunction getShortMonth(\n monthIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n return formatter.format(new Date(2000, monthIndex));\n}\n\nfunction getShortDay(\n dayIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n const date = new Date(REF_SUNDAY);\n date.setDate(REF_SUNDAY.getDate() + dayIndex);\n return formatter.format(date);\n}\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst DEFAULT_COLORS = [\n \"var(--gnome-card-shade-color, rgba(0,0,0,0.07))\",\n \"var(--gnome-green-1, #8ff0a4)\",\n \"var(--gnome-green-2, #57e389)\",\n \"var(--gnome-green-4, #2ec27e)\",\n \"var(--gnome-green-5, #26a269)\",\n];\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string, formatter: Intl.DateTimeFormat): string {\n return formatter.format(isoToLocal(iso));\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n colorScale,\n cellSize = 12,\n cellGap = 3,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const monthFormatter = useDateTimeFormatter({ month: \"short\" });\n const weekdayFormatter = useDateTimeFormatter({ weekday: \"short\" });\n const fullDateFormatter = useDateTimeFormatter({\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n const stride = cellSize + cellGap;\n const svgRef = useRef<SVGSVGElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const colors = colorScale ?? DEFAULT_COLORS;\n\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n x: number;\n y: number;\n text: string;\n } | null>(null);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (weeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < weeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({\n col,\n month: getShortMonth(colStart.getMonth(), monthFormatter),\n });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, weeks, weekStartDay, monthFormatter]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: getShortDay(dayIndex, weekdayFormatter),\n })),\n [weekStartDay, weekdayFormatter],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const svgWidth = dayLabelW + weeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(cellSize / 3));\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(weeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const wRect = wrapper.getBoundingClientRect();\n const rRect = (e.currentTarget as Element).getBoundingClientRect();\n setTooltip({\n x: rRect.left - wRect.left + cellSize / 2,\n y: rRect.top - wRect.top,\n text,\n });\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso, fullDateFormatter);\n const formattedDate = fullDateLabel(cell.iso, fullDateFormatter);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${formattedDate}`\n );\n }\n\n return (\n <div\n ref={wrapperRef}\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + cellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={() => setTooltip(null)}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n\n {tooltip && (\n <div\n className={styles.tooltip}\n style={{ left: tooltip.x, top: tooltip.y }}\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={cellSize}\n height={cellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./CountDownTimer.module.css.cjs`);let t=require(`react`),n=require(`react/jsx-runtime`);function r({start:r,end:i,format:a=`time`,variant:o=`accent`,action:s,className:c,...l}){let[u,d]=(0,t.useState)(null),[f,p]=(0,t.useState)(!1);if((0,t.useEffect)(()=>{let e=()=>{let e=new Date,t=new Date(i).getTime()-e.getTime();return t<=0?{days:0,hours:0,minutes:0,seconds:0,isFinished:!0}:{days:Math.floor(t/(1e3*60*60*24)),hours:Math.floor(t/(1e3*60*60)%24),minutes:Math.floor(t/1e3/60%60),seconds:Math.floor(t/1e3%60),isFinished:!1}};d(e());let t=setInterval(()=>{let t=e();d(t),t.isFinished&&!f&&(s?.(),p(!0))},1e3);return()=>clearInterval(t)},[i,s,f]),!u)return null;let m=()=>{if(a===`date`)return new Date(i).toLocaleDateString();let{days:e,hours:t,minutes:n,seconds:r,isFinished:o}=u;return o?`00:00:00`:a===`datetime`?`${new Date(i).toLocaleDateString()} ${`${String(t).padStart(2,`0`)}:${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}`:e>0?`${e}d ${String(t).padStart(2,`0`)}h ${String(n).padStart(2,`0`)}m`:`${String(t).padStart(2,`0`)}:${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`},h=u.isFinished?`destructive`:o;return(0,n.jsx)(`div`,{className:[e.default.countdownTimer,e.default[h],u.isFinished?e.default.finished:null,c].filter(Boolean).join(` `),...l,children:(0,n.jsx)(`span`,{className:e.default.value,children:m()})})}exports.CountDownTimer=r;
1
+ const e=require(`../GnomeProvider/GnomeContext.cjs`),t=require(`./CountDownTimer.module.css.cjs`);let n=require(`react`),r=require(`react/jsx-runtime`);function i({start:i,end:a,format:o=`time`,variant:s=`accent`,action:c,className:l,...u}){let d=e.useDateTimeFormatter(),[f,p]=(0,n.useState)(null),[m,h]=(0,n.useState)(!1);if((0,n.useEffect)(()=>{let e=()=>{let e=new Date,t=new Date(a).getTime()-e.getTime();return t<=0?{days:0,hours:0,minutes:0,seconds:0,isFinished:!0}:{days:Math.floor(t/(1e3*60*60*24)),hours:Math.floor(t/(1e3*60*60)%24),minutes:Math.floor(t/1e3/60%60),seconds:Math.floor(t/1e3%60),isFinished:!1}};p(e());let t=setInterval(()=>{let t=e();p(t),t.isFinished&&!m&&(c?.(),h(!0))},1e3);return()=>clearInterval(t)},[a,c,m]),!f)return null;let g=()=>{if(o===`date`){let e=new Date(a);return d.format(e)}let{days:e,hours:t,minutes:n,seconds:r,isFinished:i}=f;if(i)return`00:00:00`;if(o===`datetime`){let e=new Date(a);return`${d.format(e)} ${`${String(t).padStart(2,`0`)}:${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}`}return e>0?`${e}d ${String(t).padStart(2,`0`)}h ${String(n).padStart(2,`0`)}m`:`${String(t).padStart(2,`0`)}:${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`},_=f.isFinished?`destructive`:s;return(0,r.jsx)(`div`,{className:[t.default.countdownTimer,t.default[_],f.isFinished?t.default.finished:null,l].filter(Boolean).join(` `),...u,children:(0,r.jsx)(`span`,{className:t.default.value,children:g()})})}exports.CountDownTimer=i;
2
2
  //# sourceMappingURL=CountDownTimer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CountDownTimer.cjs","names":[],"sources":["../../../src/components/CountDownTimer/CountDownTimer.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return endDate.toLocaleDateString();\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = endDate.toLocaleDateString();\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\n}\n"],"mappings":"yGAgCA,SAAgB,EAAe,CAC3B,QACA,MACA,SAAS,OACT,UAAU,SACV,SACA,YACA,GAAG,GACiB,CACpB,GAAM,CAAC,EAAe,IAAA,EAAA,EAAA,UAAmD,KAAK,CACxE,CAAC,EAAmB,IAAA,EAAA,EAAA,UAAiC,GAAM,CAmDjE,IAjDA,EAAA,EAAA,eAAgB,CACZ,IAAM,MAA8C,CAChD,IAAM,EAAM,IAAI,KAGV,EAFU,IAAI,KAAK,EAAI,CAAC,SAAS,CACvB,EAAI,SAAS,CAkB7B,OAfI,GAAc,EACP,CACH,KAAM,EACN,MAAO,EACP,QAAS,EACT,QAAS,EACT,WAAY,GACf,CAQE,CACH,KANS,KAAK,MAAM,GAAc,IAAO,GAAK,GAAK,IAAI,CAOvD,MANU,KAAK,MAAO,GAAc,IAAO,GAAK,IAAO,GAAG,CAO1D,QANY,KAAK,MAAO,EAAa,IAAO,GAAM,GAAG,CAOrD,QANY,KAAK,MAAO,EAAa,IAAQ,GAAG,CAOhD,WAAY,GACf,EAIL,EAAiB,GAAwB,CAAC,CAG1C,IAAM,EAAW,gBAAkB,CAC/B,IAAM,EAAY,GAAwB,CAC1C,EAAiB,EAAU,CAGvB,EAAU,YAAc,CAAC,IACzB,KAAU,CACV,EAAqB,GAAK,GAE/B,IAAK,CAER,UAAa,cAAc,EAAS,EACrC,CAAC,EAAK,EAAQ,EAAkB,CAAC,CAEhC,CAAC,EACD,OAAO,KAGX,IAAM,MAA2B,CAC7B,GAAI,IAAW,OAEX,OADgB,IAAI,KAAK,EAAI,CACd,oBAAoB,CAGvC,GAAM,CAAE,OAAM,QAAO,UAAS,UAAS,cAAe,EAiBtD,OAfI,EACO,WAGP,IAAW,WAIJ,GAHS,IAAI,KAAK,EAAI,CACF,oBAAoB,CAE1B,GADF,GAAG,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,KAI5H,EAAO,EACA,GAAG,EAAK,IAAI,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,IAAI,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAGpF,GAAG,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,IAG9G,EAAiB,EAAc,WAAa,cAAgB,EAElE,OACI,EAAA,EAAA,KAAC,MAAD,CACI,UAAW,CACP,EAAA,QAAO,eACP,EAAA,QAAO,GACP,EAAc,WAAa,EAAA,QAAO,SAAW,KAC7C,EACH,CACI,OAAO,QAAQ,CACf,KAAK,IAAI,CACd,GAAI,YAEJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAW,EAAA,QAAO,eAAQ,GAAY,CAAQ,CAAA,CAClD,CAAA"}
1
+ {"version":3,"file":"CountDownTimer.cjs","names":[],"sources":["../../../src/components/CountDownTimer/CountDownTimer.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\nimport { useDateTimeFormatter } from \"../GnomeProvider/GnomeContext\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const dateFormatter = useDateTimeFormatter();\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return dateFormatter.format(endDate);\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = dateFormatter.format(endDate);\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\n}\n"],"mappings":"wJAiCA,SAAgB,EAAe,CAC3B,QACA,MACA,SAAS,OACT,UAAU,SACV,SACA,YACA,GAAG,GACiB,CACpB,IAAM,EAAgB,EAAA,sBAAsB,CACtC,CAAC,EAAe,IAAA,EAAA,EAAA,UAAmD,KAAK,CACxE,CAAC,EAAmB,IAAA,EAAA,EAAA,UAAiC,GAAM,CAmDjE,IAjDA,EAAA,EAAA,eAAgB,CACZ,IAAM,MAA8C,CAChD,IAAM,EAAM,IAAI,KAGV,EAFU,IAAI,KAAK,EAAI,CAAC,SAAS,CACvB,EAAI,SAAS,CAkB7B,OAfI,GAAc,EACP,CACH,KAAM,EACN,MAAO,EACP,QAAS,EACT,QAAS,EACT,WAAY,GACf,CAQE,CACH,KANS,KAAK,MAAM,GAAc,IAAO,GAAK,GAAK,IAAI,CAOvD,MANU,KAAK,MAAO,GAAc,IAAO,GAAK,IAAO,GAAG,CAO1D,QANY,KAAK,MAAO,EAAa,IAAO,GAAM,GAAG,CAOrD,QANY,KAAK,MAAO,EAAa,IAAQ,GAAG,CAOhD,WAAY,GACf,EAIL,EAAiB,GAAwB,CAAC,CAG1C,IAAM,EAAW,gBAAkB,CAC/B,IAAM,EAAY,GAAwB,CAC1C,EAAiB,EAAU,CAGvB,EAAU,YAAc,CAAC,IACzB,KAAU,CACV,EAAqB,GAAK,GAE/B,IAAK,CAER,UAAa,cAAc,EAAS,EACrC,CAAC,EAAK,EAAQ,EAAkB,CAAC,CAEhC,CAAC,EACD,OAAO,KAGX,IAAM,MAA2B,CAC7B,GAAI,IAAW,OAAQ,CACnB,IAAM,EAAU,IAAI,KAAK,EAAI,CAC7B,OAAO,EAAc,OAAO,EAAQ,CAGxC,GAAM,CAAE,OAAM,QAAO,UAAS,UAAS,cAAe,EAEtD,GAAI,EACA,MAAO,WAGX,GAAI,IAAW,WAAY,CACvB,IAAM,EAAU,IAAI,KAAK,EAAI,CAG7B,MAAO,GAFY,EAAc,OAAO,EAAQ,CAE3B,GADF,GAAG,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,KAQhI,OAJI,EAAO,EACA,GAAG,EAAK,IAAI,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,IAAI,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAGpF,GAAG,OAAO,EAAM,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,EAAG,IAAI,IAG9G,EAAiB,EAAc,WAAa,cAAgB,EAElE,OACI,EAAA,EAAA,KAAC,MAAD,CACI,UAAW,CACP,EAAA,QAAO,eACP,EAAA,QAAO,GACP,EAAc,WAAa,EAAA,QAAO,SAAW,KAC7C,EACH,CACI,OAAO,QAAQ,CACf,KAAK,IAAI,CACd,GAAI,YAEJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAW,EAAA,QAAO,eAAQ,GAAY,CAAQ,CAAA,CAClD,CAAA"}
@@ -1,12 +1,13 @@
1
- import e from "./CountDownTimer.module.css.js";
2
- import { useEffect as t, useState as n } from "react";
3
- import { jsx as r } from "react/jsx-runtime";
1
+ import { useDateTimeFormatter as e } from "../GnomeProvider/GnomeContext.js";
2
+ import t from "./CountDownTimer.module.css.js";
3
+ import { useEffect as n, useState as r } from "react";
4
+ import { jsx as i } from "react/jsx-runtime";
4
5
  //#region src/components/CountDownTimer/CountDownTimer.tsx
5
- function i({ start: i, end: a, format: o = "time", variant: s = "accent", action: c, className: l, ...u }) {
6
- let [d, f] = n(null), [p, m] = n(!1);
7
- if (t(() => {
6
+ function a({ start: a, end: o, format: s = "time", variant: c = "accent", action: l, className: u, ...d }) {
7
+ let f = e(), [p, m] = r(null), [h, g] = r(!1);
8
+ if (n(() => {
8
9
  let e = () => {
9
- let e = /* @__PURE__ */ new Date(), t = new Date(a).getTime() - e.getTime();
10
+ let e = /* @__PURE__ */ new Date(), t = new Date(o).getTime() - e.getTime();
10
11
  return t <= 0 ? {
11
12
  days: 0,
12
13
  hours: 0,
@@ -21,37 +22,45 @@ function i({ start: i, end: a, format: o = "time", variant: s = "accent", action
21
22
  isFinished: !1
22
23
  };
23
24
  };
24
- f(e());
25
+ m(e());
25
26
  let t = setInterval(() => {
26
27
  let t = e();
27
- f(t), t.isFinished && !p && (c?.(), m(!0));
28
+ m(t), t.isFinished && !h && (l?.(), g(!0));
28
29
  }, 1e3);
29
30
  return () => clearInterval(t);
30
31
  }, [
31
- a,
32
- c,
33
- p
34
- ]), !d) return null;
35
- let h = () => {
36
- if (o === "date") return new Date(a).toLocaleDateString();
37
- let { days: e, hours: t, minutes: n, seconds: r, isFinished: i } = d;
38
- return i ? "00:00:00" : o === "datetime" ? `${new Date(a).toLocaleDateString()} ${`${String(t).padStart(2, "0")}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}`}` : e > 0 ? `${e}d ${String(t).padStart(2, "0")}h ${String(n).padStart(2, "0")}m` : `${String(t).padStart(2, "0")}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}`;
39
- }, g = d.isFinished ? "destructive" : s;
40
- return /* @__PURE__ */ r("div", {
32
+ o,
33
+ l,
34
+ h
35
+ ]), !p) return null;
36
+ let _ = () => {
37
+ if (s === "date") {
38
+ let e = new Date(o);
39
+ return f.format(e);
40
+ }
41
+ let { days: e, hours: t, minutes: n, seconds: r, isFinished: i } = p;
42
+ if (i) return "00:00:00";
43
+ if (s === "datetime") {
44
+ let e = new Date(o);
45
+ return `${f.format(e)} ${`${String(t).padStart(2, "0")}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}`}`;
46
+ }
47
+ return e > 0 ? `${e}d ${String(t).padStart(2, "0")}h ${String(n).padStart(2, "0")}m` : `${String(t).padStart(2, "0")}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}`;
48
+ }, v = p.isFinished ? "destructive" : c;
49
+ return /* @__PURE__ */ i("div", {
41
50
  className: [
42
- e.countdownTimer,
43
- e[g],
44
- d.isFinished ? e.finished : null,
45
- l
51
+ t.countdownTimer,
52
+ t[v],
53
+ p.isFinished ? t.finished : null,
54
+ u
46
55
  ].filter(Boolean).join(" "),
47
- ...u,
48
- children: /* @__PURE__ */ r("span", {
49
- className: e.value,
50
- children: h()
56
+ ...d,
57
+ children: /* @__PURE__ */ i("span", {
58
+ className: t.value,
59
+ children: _()
51
60
  })
52
61
  });
53
62
  }
54
63
  //#endregion
55
- export { i as CountDownTimer };
64
+ export { a as CountDownTimer };
56
65
 
57
66
  //# sourceMappingURL=CountDownTimer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CountDownTimer.js","names":[],"sources":["../../../src/components/CountDownTimer/CountDownTimer.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return endDate.toLocaleDateString();\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = endDate.toLocaleDateString();\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\n}\n"],"mappings":";;;;AAgCA,SAAgB,EAAe,EAC3B,UACA,QACA,YAAS,QACT,aAAU,UACV,WACA,cACA,GAAG,KACiB;CACpB,IAAM,CAAC,GAAe,KAAoB,EAA+B,KAAK,EACxE,CAAC,GAAmB,KAAwB,EAAS,GAAM;AAmDjE,KAjDA,QAAgB;EACZ,IAAM,UAA8C;GAChD,IAAM,oBAAM,IAAI,MAAM,EAGhB,IAFU,IAAI,KAAK,EAAI,CAAC,SAAS,GACvB,EAAI,SAAS;AAkB7B,UAfI,KAAc,IACP;IACH,MAAM;IACN,OAAO;IACP,SAAS;IACT,SAAS;IACT,YAAY;IACf,GAQE;IACH,MANS,KAAK,MAAM,KAAc,MAAO,KAAK,KAAK,IAAI;IAOvD,OANU,KAAK,MAAO,KAAc,MAAO,KAAK,MAAO,GAAG;IAO1D,SANY,KAAK,MAAO,IAAa,MAAO,KAAM,GAAG;IAOrD,SANY,KAAK,MAAO,IAAa,MAAQ,GAAG;IAOhD,YAAY;IACf;;AAIL,IAAiB,GAAwB,CAAC;EAG1C,IAAM,IAAW,kBAAkB;GAC/B,IAAM,IAAY,GAAwB;AAI1C,GAHA,EAAiB,EAAU,EAGvB,EAAU,cAAc,CAAC,MACzB,KAAU,EACV,EAAqB,GAAK;KAE/B,IAAK;AAER,eAAa,cAAc,EAAS;IACrC;EAAC;EAAK;EAAQ;EAAkB,CAAC,EAEhC,CAAC,EACD,QAAO;CAGX,IAAM,UAA2B;AAC7B,MAAI,MAAW,OAEX,QADgB,IAAI,KAAK,EAAI,CACd,oBAAoB;EAGvC,IAAM,EAAE,SAAM,UAAO,YAAS,YAAS,kBAAe;AAiBtD,SAfI,IACO,aAGP,MAAW,aAIJ,GAHS,IAAI,KAAK,EAAI,CACF,oBAAoB,CAE1B,GADF,GAAG,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,OAI5H,IAAO,IACA,GAAG,EAAK,IAAI,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,KAGpF,GAAG,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI;IAG9G,IAAiB,EAAc,aAAa,gBAAgB;AAElE,QACI,kBAAC,OAAD;EACI,WAAW;GACP,EAAO;GACP,EAAO;GACP,EAAc,aAAa,EAAO,WAAW;GAC7C;GACH,CACI,OAAO,QAAQ,CACf,KAAK,IAAI;EACd,GAAI;YAEJ,kBAAC,QAAD;GAAM,WAAW,EAAO;aAAQ,GAAY;GAAQ,CAAA;EAClD,CAAA"}
1
+ {"version":3,"file":"CountDownTimer.js","names":[],"sources":["../../../src/components/CountDownTimer/CountDownTimer.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\nimport { useDateTimeFormatter } from \"../GnomeProvider/GnomeContext\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const dateFormatter = useDateTimeFormatter();\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return dateFormatter.format(endDate);\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = dateFormatter.format(endDate);\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\n}\n"],"mappings":";;;;;AAiCA,SAAgB,EAAe,EAC3B,UACA,QACA,YAAS,QACT,aAAU,UACV,WACA,cACA,GAAG,KACiB;CACpB,IAAM,IAAgB,GAAsB,EACtC,CAAC,GAAe,KAAoB,EAA+B,KAAK,EACxE,CAAC,GAAmB,KAAwB,EAAS,GAAM;AAmDjE,KAjDA,QAAgB;EACZ,IAAM,UAA8C;GAChD,IAAM,oBAAM,IAAI,MAAM,EAGhB,IAFU,IAAI,KAAK,EAAI,CAAC,SAAS,GACvB,EAAI,SAAS;AAkB7B,UAfI,KAAc,IACP;IACH,MAAM;IACN,OAAO;IACP,SAAS;IACT,SAAS;IACT,YAAY;IACf,GAQE;IACH,MANS,KAAK,MAAM,KAAc,MAAO,KAAK,KAAK,IAAI;IAOvD,OANU,KAAK,MAAO,KAAc,MAAO,KAAK,MAAO,GAAG;IAO1D,SANY,KAAK,MAAO,IAAa,MAAO,KAAM,GAAG;IAOrD,SANY,KAAK,MAAO,IAAa,MAAQ,GAAG;IAOhD,YAAY;IACf;;AAIL,IAAiB,GAAwB,CAAC;EAG1C,IAAM,IAAW,kBAAkB;GAC/B,IAAM,IAAY,GAAwB;AAI1C,GAHA,EAAiB,EAAU,EAGvB,EAAU,cAAc,CAAC,MACzB,KAAU,EACV,EAAqB,GAAK;KAE/B,IAAK;AAER,eAAa,cAAc,EAAS;IACrC;EAAC;EAAK;EAAQ;EAAkB,CAAC,EAEhC,CAAC,EACD,QAAO;CAGX,IAAM,UAA2B;AAC7B,MAAI,MAAW,QAAQ;GACnB,IAAM,IAAU,IAAI,KAAK,EAAI;AAC7B,UAAO,EAAc,OAAO,EAAQ;;EAGxC,IAAM,EAAE,SAAM,UAAO,YAAS,YAAS,kBAAe;AAEtD,MAAI,EACA,QAAO;AAGX,MAAI,MAAW,YAAY;GACvB,IAAM,IAAU,IAAI,KAAK,EAAI;AAG7B,UAAO,GAFY,EAAc,OAAO,EAAQ,CAE3B,GADF,GAAG,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI;;AAQhI,SAJI,IAAO,IACA,GAAG,EAAK,IAAI,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,KAGpF,GAAG,OAAO,EAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAQ,CAAC,SAAS,GAAG,IAAI;IAG9G,IAAiB,EAAc,aAAa,gBAAgB;AAElE,QACI,kBAAC,OAAD;EACI,WAAW;GACP,EAAO;GACP,EAAO;GACP,EAAc,aAAa,EAAO,WAAW;GAC7C;GACH,CACI,OAAO,QAAQ,CACf,KAAK,IAAI;EACd,GAAI;YAEJ,kBAAC,QAAD;GAAM,WAAW,EAAO;aAAQ,GAAY;GAAQ,CAAA;EAClD,CAAA"}