@siyahi/ui 0.0.0-stage → 0.1.0-next.4

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -2
  3. package/THIRD_PARTY_NOTICES.md +51 -0
  4. package/dist/components/registry/dashboard-01/section-cards.d.ts +17 -0
  5. package/dist/components/registry/dashboard-01/section-cards.js +39 -0
  6. package/dist/components/registry/dashboardcn/bar-list.d.ts +24 -0
  7. package/dist/components/registry/dashboardcn/bar-list.js +71 -0
  8. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +134 -0
  9. package/dist/components/registry/heatmap/calendar-heatmap.js +497 -0
  10. package/dist/components/registry/heatmap/status-heatmap.d.ts +120 -0
  11. package/dist/components/registry/heatmap/status-heatmap.js +330 -0
  12. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +145 -0
  13. package/dist/components/registry/heatmap/weekday-heatmap.js +489 -0
  14. package/dist/components/registry/niko-table/components/data-table-column-actions.d.ts +26 -0
  15. package/dist/components/registry/niko-table/components/data-table-column-actions.js +32 -0
  16. package/dist/components/registry/niko-table/components/data-table-column-filter.d.ts +13 -0
  17. package/dist/components/registry/niko-table/components/data-table-column-filter.js +17 -0
  18. package/dist/components/registry/niko-table/components/data-table-column-header.d.ts +30 -0
  19. package/dist/components/registry/niko-table/components/data-table-column-header.js +35 -0
  20. package/dist/components/registry/niko-table/components/data-table-column-sort.d.ts +21 -0
  21. package/dist/components/registry/niko-table/components/data-table-column-sort.js +36 -0
  22. package/dist/components/registry/niko-table/components/data-table-column-title.d.ts +12 -0
  23. package/dist/components/registry/niko-table/components/data-table-column-title.js +18 -0
  24. package/dist/components/registry/niko-table/components/data-table-empty-state.d.ts +108 -0
  25. package/dist/components/registry/niko-table/components/data-table-empty-state.js +148 -0
  26. package/dist/components/registry/niko-table/components/data-table-faceted-filter.d.ts +46 -0
  27. package/dist/components/registry/niko-table/components/data-table-faceted-filter.js +133 -0
  28. package/dist/components/registry/niko-table/components/data-table-grouped-rows.d.ts +111 -0
  29. package/dist/components/registry/niko-table/components/data-table-grouped-rows.js +77 -0
  30. package/dist/components/registry/niko-table/components/data-table-row-context-menu-slot.d.ts +65 -0
  31. package/dist/components/registry/niko-table/components/data-table-row-context-menu-slot.js +65 -0
  32. package/dist/components/registry/niko-table/components/data-table-row-context-menu.d.ts +50 -0
  33. package/dist/components/registry/niko-table/components/data-table-row-context-menu.js +42 -0
  34. package/dist/components/registry/niko-table/components/data-table-row-menu.d.ts +51 -0
  35. package/dist/components/registry/niko-table/components/data-table-row-menu.js +65 -0
  36. package/dist/components/registry/niko-table/components/data-table-toolbar-section.d.ts +7 -0
  37. package/dist/components/registry/niko-table/components/data-table-toolbar-section.js +63 -0
  38. package/dist/components/registry/niko-table/config/data-table.d.ts +156 -0
  39. package/dist/components/registry/niko-table/config/data-table.js +279 -0
  40. package/dist/components/registry/niko-table/config/feature-detection.d.ts +39 -0
  41. package/dist/components/registry/niko-table/config/feature-detection.js +174 -0
  42. package/dist/components/registry/niko-table/core/data-table-context.d.ts +157 -0
  43. package/dist/components/registry/niko-table/core/data-table-context.js +326 -0
  44. package/dist/components/registry/niko-table/core/data-table-error-boundary.d.ts +85 -0
  45. package/dist/components/registry/niko-table/core/data-table-error-boundary.js +116 -0
  46. package/dist/components/registry/niko-table/core/data-table-grouped-row.d.ts +49 -0
  47. package/dist/components/registry/niko-table/core/data-table-grouped-row.js +112 -0
  48. package/dist/components/registry/niko-table/core/data-table-root.d.ts +62 -0
  49. package/dist/components/registry/niko-table/core/data-table-root.js +406 -0
  50. package/dist/components/registry/niko-table/core/data-table-structure.d.ts +153 -0
  51. package/dist/components/registry/niko-table/core/data-table-structure.js +370 -0
  52. package/dist/components/registry/niko-table/core/data-table.d.ts +30 -0
  53. package/dist/components/registry/niko-table/core/data-table.js +126 -0
  54. package/dist/components/registry/niko-table/filters/table-column-actions.d.ts +49 -0
  55. package/dist/components/registry/niko-table/filters/table-column-actions.js +48 -0
  56. package/dist/components/registry/niko-table/filters/table-column-sort.d.ts +51 -0
  57. package/dist/components/registry/niko-table/filters/table-column-sort.js +184 -0
  58. package/dist/components/registry/niko-table/filters/table-column-title.d.ts +17 -0
  59. package/dist/components/registry/niko-table/filters/table-column-title.js +20 -0
  60. package/dist/components/registry/niko-table/filters/table-faceted-filter.d.ts +40 -0
  61. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +210 -0
  62. package/dist/components/registry/niko-table/hooks/use-derived-column-title.d.ts +29 -0
  63. package/dist/components/registry/niko-table/hooks/use-derived-column-title.js +40 -0
  64. package/dist/components/registry/niko-table/hooks/use-generated-options.d.ts +49 -0
  65. package/dist/components/registry/niko-table/hooks/use-generated-options.js +144 -0
  66. package/dist/components/registry/niko-table/lib/build-faceted-options.d.ts +59 -0
  67. package/dist/components/registry/niko-table/lib/build-faceted-options.js +82 -0
  68. package/dist/components/registry/niko-table/lib/column-resize-handle.d.ts +33 -0
  69. package/dist/components/registry/niko-table/lib/column-resize-handle.js +175 -0
  70. package/dist/components/registry/niko-table/lib/constants.d.ts +128 -0
  71. package/dist/components/registry/niko-table/lib/constants.js +123 -0
  72. package/dist/components/registry/niko-table/lib/create-scroll-handler.d.ts +35 -0
  73. package/dist/components/registry/niko-table/lib/create-scroll-handler.js +63 -0
  74. package/dist/components/registry/niko-table/lib/data-table-features.d.ts +218 -0
  75. package/dist/components/registry/niko-table/lib/data-table-features.js +127 -0
  76. package/dist/components/registry/niko-table/lib/filter-functions.d.ts +47 -0
  77. package/dist/components/registry/niko-table/lib/filter-functions.js +309 -0
  78. package/dist/components/registry/niko-table/lib/filter-rows.d.ts +14 -0
  79. package/dist/components/registry/niko-table/lib/filter-rows.js +28 -0
  80. package/dist/components/registry/niko-table/lib/flex-columns.d.ts +48 -0
  81. package/dist/components/registry/niko-table/lib/flex-columns.js +65 -0
  82. package/dist/components/registry/niko-table/lib/format.d.ts +33 -0
  83. package/dist/components/registry/niko-table/lib/format.js +99 -0
  84. package/dist/components/registry/niko-table/lib/render-cell-content.d.ts +17 -0
  85. package/dist/components/registry/niko-table/lib/render-cell-content.js +65 -0
  86. package/dist/components/registry/niko-table/lib/row-click.d.ts +67 -0
  87. package/dist/components/registry/niko-table/lib/row-click.js +28 -0
  88. package/dist/components/registry/niko-table/lib/styles.d.ts +6 -0
  89. package/dist/components/registry/niko-table/lib/styles.js +24 -0
  90. package/dist/components/registry/niko-table/lib/use-header-min-widths.d.ts +17 -0
  91. package/dist/components/registry/niko-table/lib/use-header-min-widths.js +82 -0
  92. package/dist/components/registry/niko-table/types/index.d.ts +141 -0
  93. package/dist/components/registry/niko-table/types/index.js +2 -0
  94. package/dist/components/registry/niko-table/ui/table.d.ts +13 -0
  95. package/dist/components/registry/niko-table/ui/table.js +74 -0
  96. package/dist/components/registry/sidebar-07/app-sidebar.d.ts +18 -0
  97. package/dist/components/registry/sidebar-07/app-sidebar.js +26 -0
  98. package/dist/components/registry/sidebar-07/nav-main.d.ts +15 -0
  99. package/dist/components/registry/sidebar-07/nav-main.js +25 -0
  100. package/dist/components/registry/sidebar-07/nav-projects.d.ts +9 -0
  101. package/dist/components/registry/sidebar-07/nav-projects.js +43 -0
  102. package/dist/components/registry/sidebar-07/nav-user.d.ts +9 -0
  103. package/dist/components/registry/sidebar-07/nav-user.js +70 -0
  104. package/dist/components/registry/sidebar-07/team-switcher.d.ts +10 -0
  105. package/dist/components/registry/sidebar-07/team-switcher.js +70 -0
  106. package/dist/components/siyahi/delta.d.ts +10 -0
  107. package/dist/components/siyahi/delta.js +27 -0
  108. package/dist/components/siyahi/local-time.d.ts +7 -0
  109. package/dist/components/siyahi/local-time.js +22 -0
  110. package/dist/components/siyahi/price-chart.d.ts +12 -0
  111. package/dist/components/siyahi/price-chart.js +80 -0
  112. package/dist/components/siyahi/siyahi-provider.d.ts +12 -0
  113. package/dist/components/siyahi/siyahi-provider.js +31 -0
  114. package/dist/components/siyahi/treemap.d.ts +14 -0
  115. package/dist/components/siyahi/treemap.js +58 -0
  116. package/dist/components/ui/alert.d.ts +12 -0
  117. package/dist/components/ui/alert.js +43 -0
  118. package/dist/components/ui/avatar.d.ts +13 -0
  119. package/dist/components/ui/avatar.js +50 -0
  120. package/dist/components/ui/badge.d.ts +9 -0
  121. package/dist/components/ui/badge.js +29 -0
  122. package/dist/components/ui/breadcrumb.d.ts +12 -0
  123. package/dist/components/ui/breadcrumb.js +72 -0
  124. package/dist/components/ui/button.d.ts +10 -0
  125. package/dist/components/ui/button.js +44 -0
  126. package/dist/components/ui/calendar.d.ts +12 -0
  127. package/dist/components/ui/calendar.js +107 -0
  128. package/dist/components/ui/card.d.ts +13 -0
  129. package/dist/components/ui/card.js +56 -0
  130. package/dist/components/ui/chart.d.ts +46 -0
  131. package/dist/components/ui/chart.js +149 -0
  132. package/dist/components/ui/collapsible.d.ts +7 -0
  133. package/dist/components/ui/collapsible.js +24 -0
  134. package/dist/components/ui/command.d.ts +21 -0
  135. package/dist/components/ui/command.js +88 -0
  136. package/dist/components/ui/context-menu.d.ts +31 -0
  137. package/dist/components/ui/context-menu.js +132 -0
  138. package/dist/components/ui/dialog.d.ts +19 -0
  139. package/dist/components/ui/dialog.js +91 -0
  140. package/dist/components/ui/dropdown-menu.d.ts +31 -0
  141. package/dist/components/ui/dropdown-menu.js +136 -0
  142. package/dist/components/ui/input-group.d.ts +20 -0
  143. package/dist/components/ui/input-group.js +79 -0
  144. package/dist/components/ui/input.d.ts +5 -0
  145. package/dist/components/ui/input.js +15 -0
  146. package/dist/components/ui/popover.d.ts +11 -0
  147. package/dist/components/ui/popover.js +55 -0
  148. package/dist/components/ui/progress.d.ts +9 -0
  149. package/dist/components/ui/progress.js +43 -0
  150. package/dist/components/ui/scroll-area.d.ts +7 -0
  151. package/dist/components/ui/scroll-area.js +36 -0
  152. package/dist/components/ui/select.d.ts +17 -0
  153. package/dist/components/ui/select.js +97 -0
  154. package/dist/components/ui/separator.d.ts +5 -0
  155. package/dist/components/ui/separator.js +15 -0
  156. package/dist/components/ui/sheet.d.ts +16 -0
  157. package/dist/components/ui/sheet.js +89 -0
  158. package/dist/components/ui/sidebar.d.ts +65 -0
  159. package/dist/components/ui/sidebar.js +385 -0
  160. package/dist/components/ui/skeleton.d.ts +4 -0
  161. package/dist/components/ui/skeleton.js +12 -0
  162. package/dist/components/ui/slider.d.ts +5 -0
  163. package/dist/components/ui/slider.js +33 -0
  164. package/dist/components/ui/table.d.ts +12 -0
  165. package/dist/components/ui/table.js +66 -0
  166. package/dist/components/ui/tabs.d.ts +12 -0
  167. package/dist/components/ui/tabs.js +45 -0
  168. package/dist/components/ui/textarea.d.ts +5 -0
  169. package/dist/components/ui/textarea.js +13 -0
  170. package/dist/components/ui/toggle-group.d.ts +13 -0
  171. package/dist/components/ui/toggle-group.js +52 -0
  172. package/dist/components/ui/toggle.d.ts +10 -0
  173. package/dist/components/ui/toggle.js +36 -0
  174. package/dist/components/ui/tooltip.d.ts +8 -0
  175. package/dist/components/ui/tooltip.js +41 -0
  176. package/dist/hooks/use-mobile.d.ts +3 -0
  177. package/dist/hooks/use-mobile.js +18 -0
  178. package/dist/index.d.ts +73 -0
  179. package/dist/index.js +71 -0
  180. package/dist/lib/format.d.ts +24 -0
  181. package/dist/lib/format.js +58 -0
  182. package/dist/lib/locale.d.ts +12 -0
  183. package/dist/lib/locale.js +8 -0
  184. package/dist/lib/recharts.d.ts +2 -0
  185. package/dist/lib/recharts.js +1 -0
  186. package/dist/lib/utils.d.ts +2 -0
  187. package/dist/lib/utils.js +1 -0
  188. package/package.json +78 -3
  189. package/registry.lock.json +413 -0
  190. package/report.css +129 -0
  191. package/styles/shadcn.css +643 -0
  192. package/styles/siyahi.css +189 -0
@@ -0,0 +1,497 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { Fragment, createContext, use, useMemo } from "react";
3
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
+ import { differenceInCalendarDays, eachDayOfInterval, format, formatISO, getDay, getMonth, getYear, nextDay, parseISO, subWeeks } from "date-fns";
5
+ //#region src/components/registry/heatmap/calendar-heatmap.tsx
6
+ const colorStepCount = (levels, isNormalized) => Math.max(1, isNormalized ? levels : levels - 1);
7
+ const getLevelFill = (level, levels, isNormalized, highlighted = false, colors) => {
8
+ const emptyColor = colors?.empty ?? "var(--color-secondary)";
9
+ const scaleColor = colors?.scale ?? "var(--color-chart-1)";
10
+ if (level === 0) return emptyColor;
11
+ const steps = colorStepCount(levels, isNormalized);
12
+ const opacity = steps === 1 ? 100 : Math.round(20 + (level - 1) * 80 / (steps - 1));
13
+ return `color-mix(in oklch, ${scaleColor} ${highlighted ? Math.round(opacity * .6) : opacity}%, transparent)`;
14
+ };
15
+ const calculateLevel = (value, minValue, maxValue, levels, isNormalized) => {
16
+ const steps = colorStepCount(levels, isNormalized);
17
+ if (!Number.isFinite(value)) return isNormalized ? 1 : 0;
18
+ if (isNormalized) {
19
+ if (maxValue <= minValue) return 1;
20
+ const percentage = (value - minValue) / (maxValue - minValue);
21
+ return Math.max(1, Math.min(steps, Math.ceil(percentage * steps)));
22
+ }
23
+ if (value <= 0 || maxValue <= 0) return 0;
24
+ const percentage = value / maxValue;
25
+ return Math.max(1, Math.min(steps, Math.ceil(percentage * steps)));
26
+ };
27
+ const generateMonthLabels = (locale) => {
28
+ return Array.from({ length: 12 }, (_, i) => {
29
+ const date = new Date(2e3, i, 1);
30
+ if (locale) return format(date, "LLL", { locale });
31
+ return date.toLocaleString("en-US", { month: "short" });
32
+ });
33
+ };
34
+ const generateWeekdayLabels = (locale) => {
35
+ return Array.from({ length: 7 }, (_, i) => {
36
+ const date = new Date(2e3, 0, 2 + i);
37
+ if (locale) return format(date, "EEE", { locale });
38
+ return date.toLocaleString("en-US", { weekday: "short" });
39
+ });
40
+ };
41
+ const EMPTY_STYLE = {};
42
+ const LABEL_MARGIN = 8;
43
+ const CalendarHeatmapContext = createContext(null);
44
+ const useCalendarHeatmap = () => {
45
+ const context = use(CalendarHeatmapContext);
46
+ if (!context) throw new Error("CalendarHeatmap components must be used within a CalendarHeatmap");
47
+ return context;
48
+ };
49
+ const fillHoles = (activities) => {
50
+ if (activities.length === 0) return [];
51
+ const sortedActivities = [...activities].sort((a, b) => a.date.localeCompare(b.date));
52
+ const calendar = new Map(activities.map((a) => [a.date, a]));
53
+ const firstActivity = sortedActivities[0];
54
+ const lastActivity = sortedActivities.at(-1);
55
+ if (!lastActivity) return [];
56
+ return eachDayOfInterval({
57
+ start: parseISO(firstActivity.date),
58
+ end: parseISO(lastActivity.date)
59
+ }).map((day) => {
60
+ const date = formatISO(day, { representation: "date" });
61
+ if (calendar.has(date)) return calendar.get(date);
62
+ return {
63
+ date,
64
+ value: 0,
65
+ level: 0
66
+ };
67
+ });
68
+ };
69
+ const groupByYearAndMonth = (activities, weekStart = 0, hasEmptyColumn = false) => {
70
+ if (activities.length === 0) return [];
71
+ const normalizedActivities = fillHoles(activities);
72
+ const activitiesByYearMonth = /* @__PURE__ */ new Map();
73
+ for (const activity of normalizedActivities) {
74
+ const date = parseISO(activity.date);
75
+ const year = getYear(date);
76
+ const month = getMonth(date);
77
+ const key = `${year}-${String(month).padStart(2, "0")}`;
78
+ if (!activitiesByYearMonth.has(key)) activitiesByYearMonth.set(key, []);
79
+ activitiesByYearMonth.get(key)?.push(activity);
80
+ }
81
+ const sortedKeys = Array.from(activitiesByYearMonth.keys()).sort();
82
+ if (sortedKeys.length === 0) return [];
83
+ const [firstYearStr, firstMonthStr] = sortedKeys[0].split("-");
84
+ const startYear = parseInt(firstYearStr, 10);
85
+ const startMonth = parseInt(firstMonthStr, 10);
86
+ const yearRows = [];
87
+ let currentYear = startYear;
88
+ let currentMonth = startMonth;
89
+ while (true) {
90
+ const rowWeeks = [];
91
+ let hasData = false;
92
+ for (let monthOffset = 0; monthOffset < 12; monthOffset++) {
93
+ const monthIndex = (currentMonth + monthOffset) % 12;
94
+ const yearOffset = Math.floor((currentMonth + monthOffset) / 12);
95
+ const key = `${currentYear + yearOffset}-${String(monthIndex).padStart(2, "0")}`;
96
+ const monthActivities = activitiesByYearMonth.get(key) || [];
97
+ if (monthActivities.length > 0) {
98
+ hasData = true;
99
+ const firstActivity = monthActivities[0];
100
+ const firstDate = parseISO(firstActivity.date);
101
+ const firstCalendarDate = getDay(firstDate) === weekStart ? firstDate : subWeeks(nextDay(firstDate, weekStart), 1);
102
+ const paddedActivities = [...new Array(differenceInCalendarDays(firstDate, firstCalendarDate)).fill(void 0), ...monthActivities];
103
+ const numberOfWeeks = Math.ceil(paddedActivities.length / 7);
104
+ const monthWeeks = new Array(numberOfWeeks).fill(void 0).map((_, weekIndex) => paddedActivities.slice(weekIndex * 7, weekIndex * 7 + 7));
105
+ if (hasEmptyColumn && rowWeeks.length > 0) rowWeeks.push(new Array(7).fill(void 0));
106
+ rowWeeks.push(...monthWeeks);
107
+ }
108
+ }
109
+ if (!hasData) break;
110
+ yearRows.push({
111
+ year: currentYear,
112
+ startMonth: currentMonth,
113
+ weeks: rowWeeks
114
+ });
115
+ currentYear++;
116
+ currentMonth = startMonth;
117
+ if (!sortedKeys.some((key) => {
118
+ const [yearStr] = key.split("-");
119
+ return parseInt(yearStr, 10) >= currentYear;
120
+ })) break;
121
+ }
122
+ return yearRows;
123
+ };
124
+ const groupContinuous = (activities, weekStart = 0) => {
125
+ if (activities.length === 0) return [];
126
+ const normalizedActivities = fillHoles(activities);
127
+ const first = normalizedActivities[0];
128
+ const firstDate = parseISO(first.date);
129
+ const firstCalendarDate = getDay(firstDate) === weekStart ? firstDate : subWeeks(nextDay(firstDate, weekStart), 1);
130
+ const leadingPad = differenceInCalendarDays(firstDate, firstCalendarDate);
131
+ const padded = [...new Array(leadingPad).fill(void 0), ...normalizedActivities];
132
+ const numberOfWeeks = Math.ceil(padded.length / 7);
133
+ const allWeeks = new Array(numberOfWeeks).fill(void 0).map((_, i) => padded.slice(i * 7, i * 7 + 7));
134
+ const yearMap = /* @__PURE__ */ new Map();
135
+ for (const week of allWeeks) {
136
+ const firstActivity = week.find((a) => a !== void 0);
137
+ const year = firstActivity ? getYear(parseISO(firstActivity.date)) : void 0;
138
+ if (year !== void 0) {
139
+ if (!yearMap.has(year)) yearMap.set(year, []);
140
+ yearMap.get(year)?.push(week);
141
+ }
142
+ }
143
+ return Array.from(yearMap.entries()).sort(([a], [b]) => a - b).map(([year, weeks]) => ({
144
+ year,
145
+ startMonth: 0,
146
+ weeks
147
+ }));
148
+ };
149
+ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
150
+ return weeks.reduce((labels, week, weekIndex) => {
151
+ const firstActivity = week.find((activity) => activity !== void 0);
152
+ if (!firstActivity) return labels;
153
+ const month = monthNames[getMonth(parseISO(firstActivity.date))];
154
+ if (!month) {
155
+ const monthName = new Date(firstActivity.date).toLocaleString("en-US", { month: "short" });
156
+ throw new Error(`Unexpected error: undefined month label for ${monthName}.`);
157
+ }
158
+ const prevLabel = labels.at(-1);
159
+ if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) return labels.concat({
160
+ weekIndex,
161
+ label: month
162
+ });
163
+ return labels;
164
+ }, []).filter(({ weekIndex }, index, labels) => {
165
+ const minWeeks = 3;
166
+ if (index === 0) return labels[1] && labels[1].weekIndex - weekIndex >= minWeeks;
167
+ if (index === labels.length - 1) return weeks.slice(weekIndex).length >= minWeeks;
168
+ return true;
169
+ });
170
+ };
171
+ /**
172
+ * Calendar Heatmap
173
+ *
174
+ * A GitHub-style contribution calendar showing daily activity over months and years.
175
+ * Each cell represents one day, arranged in weeks (rows) and months (columns).
176
+ *
177
+ * @example
178
+ * ```tsx
179
+ * <CalendarHeatmap data={data} weekStart={1} continuousMonths>
180
+ * <CalendarHeatmapBody>
181
+ * {({ activity, dayIndex, weekIndex }) => (
182
+ * <CalendarHeatmapBlock
183
+ * activity={activity}
184
+ * dayIndex={dayIndex}
185
+ * weekIndex={weekIndex}
186
+ * />
187
+ * )}
188
+ * </CalendarHeatmapBody>
189
+ * <CalendarHeatmapFooter>
190
+ * <CalendarHeatmapStat />
191
+ * <CalendarHeatmapLegend />
192
+ * </CalendarHeatmapFooter>
193
+ * </CalendarHeatmap>
194
+ * ```
195
+ *
196
+ * @param data - Array of activities with date (YYYY-MM-DD) and value
197
+ * @param weekStart - First day of week (0=Sunday, 1=Monday). Default: 0
198
+ * @param continuousMonths - Display months continuously vs. grouped by year. Default: true
199
+ * @param hasEmptyColumn - Add empty column between months. Default: false
200
+ * @param blockAspectRatio - Width/height ratio of blocks. Default: 1
201
+ * @param levels - Total number of legend cells (including empty when not normalized). Default: 5
202
+ * @param isNormalized - When true, uses min-max normalization across the dataset (suitable for signed values). When false (default), treats 0 as empty and scales from 0 to max.
203
+ */
204
+ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize = 12, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, style = EMPTY_STYLE, className, children, ...props }) => {
205
+ const levels = Math.max(1, levelsProp);
206
+ const dataWithLevels = useMemo(() => {
207
+ if (data.length === 0) return [];
208
+ const maxCount = data.reduce((max, d) => Math.max(max, d.value), 1);
209
+ const minCount = isNormalized ? data.reduce((min, d) => Math.min(min, d.value), Infinity) : 0;
210
+ return data.map((activity) => ({
211
+ ...activity,
212
+ level: calculateLevel(activity.value, minCount, maxCount, levels, isNormalized)
213
+ }));
214
+ }, [
215
+ data,
216
+ levels,
217
+ isNormalized
218
+ ]);
219
+ const yearRows = useMemo(() => continuousMonths ? groupContinuous(dataWithLevels, weekStart) : groupByYearAndMonth(dataWithLevels, weekStart, hasEmptyColumn), [
220
+ dataWithLevels,
221
+ weekStart,
222
+ hasEmptyColumn,
223
+ continuousMonths
224
+ ]);
225
+ const weeks = useMemo(() => yearRows.flatMap((r) => r.weeks), [yearRows]);
226
+ const labels = useMemo(() => ({
227
+ months: generateMonthLabels(locale),
228
+ weekdays: generateWeekdayLabels(locale),
229
+ cellLabel: "{{date}}: {{value}} contributions",
230
+ heatmapLabel: "Contribution heatmap for {{year}}",
231
+ legendLabel: "Activity intensity legend",
232
+ legendLevelLabel: "{{level}} contributions",
233
+ ...labelsProp
234
+ }), [locale, labelsProp]);
235
+ const labelHeight = fontSize + LABEL_MARGIN;
236
+ const year = data.length > 0 ? getYear(parseISO(data[0].date)) : (/* @__PURE__ */ new Date()).getFullYear();
237
+ const totalCount = typeof totalCountProp === "number" ? totalCountProp : dataWithLevels.reduce((sum, activity) => sum + activity.value, 0);
238
+ const blockWidth = blockSize * blockAspectRatio;
239
+ const width = weeks.length * (blockWidth + blockMargin) - blockMargin;
240
+ const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;
241
+ const contextValue = useMemo(() => ({
242
+ data: dataWithLevels,
243
+ weeks,
244
+ yearRows,
245
+ blockMargin,
246
+ blockRadius,
247
+ blockSize,
248
+ blockAspectRatio,
249
+ blockWidth,
250
+ fontSize,
251
+ labels,
252
+ labelHeight,
253
+ levels,
254
+ isNormalized,
255
+ totalCount,
256
+ weekStart,
257
+ year,
258
+ width,
259
+ height,
260
+ hasEmptyColumn,
261
+ continuousMonths,
262
+ locale,
263
+ colors
264
+ }), [
265
+ dataWithLevels,
266
+ weeks,
267
+ yearRows,
268
+ blockMargin,
269
+ blockRadius,
270
+ blockSize,
271
+ blockAspectRatio,
272
+ blockWidth,
273
+ fontSize,
274
+ labels,
275
+ labelHeight,
276
+ levels,
277
+ isNormalized,
278
+ totalCount,
279
+ weekStart,
280
+ year,
281
+ width,
282
+ height,
283
+ hasEmptyColumn,
284
+ continuousMonths,
285
+ locale,
286
+ colors
287
+ ]);
288
+ if (data.length === 0) return emptyState ? emptyState : null;
289
+ return /* @__PURE__ */ jsx(CalendarHeatmapContext, {
290
+ value: contextValue,
291
+ children: /* @__PURE__ */ jsx("div", {
292
+ "data-slot": "calendar-heatmap",
293
+ className: cn("flex w-max max-w-full flex-col gap-2 p-4", className),
294
+ style: {
295
+ fontSize,
296
+ ...style
297
+ },
298
+ ...props,
299
+ children
300
+ })
301
+ });
302
+ };
303
+ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
304
+ const { blockSize, blockWidth, blockMargin, blockRadius, labelHeight, labels, levels, isNormalized, colors } = useCalendarHeatmap();
305
+ const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
306
+ const ariaLabel = (labels.cellLabel ?? "{{date}}: {{value}} contributions").replace("{{date}}", activity.date).replace("{{value}}", String(activity.value));
307
+ return /* @__PURE__ */ jsx("rect", {
308
+ ref,
309
+ "data-slot": "calendar-heatmap-block",
310
+ role: onCellClick ? "button" : "img",
311
+ tabIndex: onCellClick ? 0 : -1,
312
+ "aria-label": ariaLabel,
313
+ className: cn("motion-safe:transition-opacity motion-safe:hover:opacity-70", onCellClick && "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", className),
314
+ "data-value": activity.value,
315
+ "data-date": activity.date,
316
+ "data-level": level,
317
+ "data-highlighted": highlighted || void 0,
318
+ height: blockSize,
319
+ rx: blockRadius,
320
+ ry: blockRadius,
321
+ width: blockWidth,
322
+ x: (blockWidth + blockMargin) * weekIndex,
323
+ y: labelHeight + (blockSize + blockMargin) * dayIndex,
324
+ style: {
325
+ fill: getLevelFill(level, levels, isNormalized, highlighted, colors),
326
+ ...styleProp
327
+ },
328
+ onClick: (event) => {
329
+ onCellClick?.(activity);
330
+ onClick?.(event);
331
+ },
332
+ onKeyDown: (event) => {
333
+ if (onCellClick && (event.key === "Enter" || event.key === " ")) {
334
+ event.preventDefault();
335
+ onCellClick(activity);
336
+ }
337
+ onKeyDown?.(event);
338
+ },
339
+ onMouseEnter: (event) => {
340
+ onCellHover?.(activity);
341
+ onMouseEnter?.(event);
342
+ },
343
+ onMouseLeave: (event) => {
344
+ onCellHover?.(null);
345
+ onMouseLeave?.(event);
346
+ },
347
+ ...props
348
+ });
349
+ };
350
+ CalendarHeatmapBlock.displayName = "CalendarHeatmapBlock";
351
+ const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = false, hideYearLabels = false, className, labelClassName, yearClassName, children, renderYearFooter, ...props }) => {
352
+ const { yearRows, blockSize, blockWidth, blockMargin, labels, labelHeight, fontSize, weekStart, data } = useCalendarHeatmap();
353
+ const weekdayLabelWidth = hideWeekdayLabels ? 0 : 40;
354
+ const strokePadding = 3;
355
+ const rowData = useMemo(() => {
356
+ return yearRows.map((yearRow) => {
357
+ return {
358
+ yearRow,
359
+ width: yearRow.weeks.length * (blockWidth + blockMargin) - blockMargin,
360
+ height: labelHeight + (blockSize + blockMargin) * 7 - blockMargin,
361
+ monthLabels: getMonthLabels(yearRow.weeks, labels.months),
362
+ yearTotalCount: data.filter((activity) => getYear(parseISO(activity.date)) === yearRow.year).reduce((sum, activity) => sum + activity.value, 0)
363
+ };
364
+ });
365
+ }, [
366
+ yearRows,
367
+ blockSize,
368
+ blockWidth,
369
+ blockMargin,
370
+ labelHeight,
371
+ labels.months,
372
+ data
373
+ ]);
374
+ const totalWidth = weekdayLabelWidth + Math.max(...rowData.map((r) => r.width)) + 6;
375
+ return /* @__PURE__ */ jsx("div", {
376
+ "data-slot": "calendar-heatmap-body",
377
+ className: cn("flex max-w-full flex-col gap-6 overflow-x-auto overflow-y-hidden py-4", className),
378
+ ...props,
379
+ children: rowData.map(({ yearRow, height, monthLabels, yearTotalCount }) => /* @__PURE__ */ jsxs("div", { children: [
380
+ !hideYearLabels && /* @__PURE__ */ jsx("div", {
381
+ className: cn("mb-2 text-muted-foreground", yearClassName),
382
+ children: yearRow.year
383
+ }),
384
+ /* @__PURE__ */ jsx("svg", {
385
+ role: "img",
386
+ "aria-label": (labels.heatmapLabel ?? "Contribution heatmap for {{year}}").replace("{{year}}", String(yearRow.year)),
387
+ className: "block overflow-visible rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
388
+ height: height + 6,
389
+ viewBox: `0 0 ${totalWidth} ${height + 6}`,
390
+ width: totalWidth,
391
+ children: /* @__PURE__ */ jsxs("g", {
392
+ transform: `translate(0, ${strokePadding})`,
393
+ children: [
394
+ !hideMonthLabels && /* @__PURE__ */ jsx("g", {
395
+ className: cn("fill-current font-mono", labelClassName),
396
+ children: monthLabels.map(({ label, weekIndex }) => /* @__PURE__ */ jsx("text", {
397
+ dominantBaseline: "hanging",
398
+ x: weekdayLabelWidth + strokePadding + (blockWidth + blockMargin) * weekIndex,
399
+ style: { fontSize: `${fontSize * .75}px` },
400
+ children: label
401
+ }, `${yearRow.year}-${weekIndex}`))
402
+ }),
403
+ !hideWeekdayLabels && /* @__PURE__ */ jsx("g", {
404
+ className: cn("fill-current font-mono text-xs", labelClassName),
405
+ children: labels.weekdays?.map((label, dayIndex) => {
406
+ const adjustedIndex = (dayIndex + weekStart) % 7;
407
+ const adjustedLabel = labels.weekdays?.[adjustedIndex] || label;
408
+ return /* @__PURE__ */ jsx("text", {
409
+ x: 0,
410
+ y: labelHeight + (blockSize + blockMargin) * dayIndex + blockSize / 2,
411
+ dominantBaseline: "middle",
412
+ textAnchor: "start",
413
+ style: { fontSize: `${fontSize * .75}px` },
414
+ children: adjustedLabel
415
+ }, `weekday-${yearRow.year}-${label}`);
416
+ })
417
+ }),
418
+ /* @__PURE__ */ jsx("g", {
419
+ transform: `translate(${weekdayLabelWidth + strokePadding}, 0)`,
420
+ children: yearRow.weeks.map((week, weekIndex) => week.map((activity, dayIndex) => {
421
+ if (!activity) return null;
422
+ return /* @__PURE__ */ jsx(Fragment, { children: children({
423
+ activity,
424
+ dayIndex,
425
+ weekIndex
426
+ }) }, `${yearRow.year}-${activity.date}`);
427
+ }))
428
+ })
429
+ ]
430
+ })
431
+ }),
432
+ renderYearFooter?.({
433
+ year: yearRow.year,
434
+ totalCount: yearTotalCount
435
+ })
436
+ ] }, `year-row-${yearRow.year}`))
437
+ });
438
+ };
439
+ const CalendarHeatmapFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
440
+ "data-slot": "calendar-heatmap-footer",
441
+ className: cn("flex flex-wrap gap-1 whitespace-nowrap sm:gap-x-4", className),
442
+ ...props
443
+ });
444
+ const CalendarHeatmapStat = ({ compute, label, className, children, ...props }) => {
445
+ const { data, totalCount, year, labels } = useCalendarHeatmap();
446
+ const value = compute ? compute(data) : totalCount;
447
+ if (children) return /* @__PURE__ */ jsx(Fragment$1, { children: children({
448
+ value,
449
+ data,
450
+ year
451
+ }) });
452
+ const template = label ?? labels.stat ?? "{{value}} contributions in {{year}}";
453
+ return /* @__PURE__ */ jsx("div", {
454
+ "data-slot": "calendar-heatmap-stat",
455
+ className: cn("text-muted-foreground tabular-nums", className),
456
+ ...props,
457
+ children: template.replace("{{value}}", String(value)).replace("{{year}}", String(year))
458
+ });
459
+ };
460
+ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
461
+ const { levels, isNormalized, blockSize, blockWidth, blockRadius, colors, labels } = useCalendarHeatmap();
462
+ const lessLabel = labelsProp?.less ?? "Less";
463
+ const moreLabel = labelsProp?.more ?? "More";
464
+ const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
465
+ return /* @__PURE__ */ jsxs("fieldset", {
466
+ "data-slot": "calendar-heatmap-legend",
467
+ "aria-label": labels.legendLabel ?? "Activity intensity legend",
468
+ className: cn("ml-auto flex items-center gap-1 text-muted-foreground", className),
469
+ ...props,
470
+ children: [
471
+ /* @__PURE__ */ jsx("span", {
472
+ className: "mr-1 font-medium text-xs",
473
+ children: lessLabel
474
+ }),
475
+ legendLevels.map((level) => children ? /* @__PURE__ */ jsx(Fragment, { children: children({ level }) }, `legend-level-${level}`) : /* @__PURE__ */ jsx("svg", {
476
+ role: "img",
477
+ "aria-label": (labels.legendLevelLabel ?? "{{level}} contributions").replace("{{level}}", String(level)),
478
+ height: blockSize,
479
+ width: blockWidth,
480
+ children: /* @__PURE__ */ jsx("rect", {
481
+ "data-level": level,
482
+ height: blockSize,
483
+ rx: blockRadius,
484
+ ry: blockRadius,
485
+ width: blockWidth,
486
+ style: { fill: getLevelFill(level, levels, isNormalized, false, colors) }
487
+ })
488
+ }, `legend-level-${level}`)),
489
+ /* @__PURE__ */ jsx("span", {
490
+ className: "ml-1 font-medium text-xs",
491
+ children: moreLabel
492
+ })
493
+ ]
494
+ });
495
+ };
496
+ //#endregion
497
+ export { CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBody, CalendarHeatmapFooter, CalendarHeatmapLegend, CalendarHeatmapStat };
@@ -0,0 +1,120 @@
1
+ import { CSSProperties, HTMLAttributes, ReactNode } from "react";
2
+ import { Locale } from "date-fns";
3
+ //#region src/components/registry/heatmap/status-heatmap.d.ts
4
+ export type StatusValue = number;
5
+ export type StatusActivity = {
6
+ date: string;
7
+ value: StatusValue;
8
+ };
9
+ export type StatusHeatmapLabels = {
10
+ statuses?: Record<number, string> & {
11
+ noData?: string;
12
+ critical?: string;
13
+ degraded?: string;
14
+ healthy?: string;
15
+ };
16
+ stat?: string;
17
+ cellLabel?: string;
18
+ heatmapLabel?: string;
19
+ legendLabel?: string;
20
+ };
21
+ export type StatusColorConfig = Record<number, string> & {
22
+ noData?: string;
23
+ critical?: string;
24
+ degraded?: string;
25
+ healthy?: string;
26
+ };
27
+ export type StatusHeatmapProps = HTMLAttributes<HTMLDivElement> & {
28
+ data: StatusActivity[];
29
+ dateFormat?: string;
30
+ blockSize?: number;
31
+ blockMargin?: number;
32
+ blockRadius?: number;
33
+ blockAspectRatio?: number;
34
+ colors?: StatusColorConfig;
35
+ locale?: Locale;
36
+ labels?: StatusHeatmapLabels;
37
+ fontSize?: number;
38
+ emptyState?: ReactNode;
39
+ statusValues?: StatusValue[];
40
+ healthyValue?: StatusValue;
41
+ style?: CSSProperties;
42
+ className?: string;
43
+ children: ReactNode;
44
+ };
45
+ /**
46
+ * Status Heatmap
47
+ *
48
+ * A timeline indicator showing daily status over a period (e.g., 90 days).
49
+ * Similar to Atlassian Statuspage - each day is represented by a vertical bar.
50
+ * Supports 4 status values: 0=no-data, 1=critical, 2=degraded, 3=healthy
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * <StatusHeatmap data={data} blockAspectRatio={0.2}>
55
+ * <StatusHeatmapBody>
56
+ * {({ activity, dayIndex }) => (
57
+ * <StatusHeatmapBlock
58
+ * activity={activity}
59
+ * dayIndex={dayIndex}
60
+ * />
61
+ * )}
62
+ * </StatusHeatmapBody>
63
+ * <StatusHeatmapFooter>
64
+ * <StatusHeatmapStat />
65
+ * <StatusHeatmapLegend />
66
+ * </StatusHeatmapFooter>
67
+ * </StatusHeatmap>
68
+ * ```
69
+ *
70
+ * @param data - Array of activities with date (YYYY-MM-DD) and status value (0-3)
71
+ * @param dateFormat - Date format string for tooltips. Default: "MMM d"
72
+ * @param blockAspectRatio - Width/height ratio of blocks. Default: 0.2 (narrow vertical bars)
73
+ * @param colors - Custom colors for critical, degraded, and healthy states
74
+ */
75
+ export declare const StatusHeatmap: ({ data, dateFormat, blockSize, blockMargin, blockRadius, blockAspectRatio, colors, locale, labels: labelsProp, fontSize, emptyState, statusValues, healthyValue, style, className, children, ...props }: StatusHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
76
+ export type StatusHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
77
+ activity: StatusActivity;
78
+ dayIndex: number;
79
+ highlighted?: boolean;
80
+ onCellClick?: (activity: StatusActivity) => void;
81
+ onCellHover?: (activity: StatusActivity | null) => void;
82
+ className?: string;
83
+ };
84
+ export declare function StatusHeatmapBlock({ ref, activity, dayIndex, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: StatusHeatmapBlockProps & {
85
+ ref?: React.RefObject<SVGRectElement | null>;
86
+ }): import("react").JSX.Element;
87
+ export declare namespace StatusHeatmapBlock {
88
+ var displayName: string;
89
+ }
90
+ export type StatusHeatmapBodyProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
91
+ hideDateLabels?: boolean;
92
+ labelInterval?: number;
93
+ className?: string;
94
+ labelClassName?: string;
95
+ children: (props: {
96
+ activity: StatusActivity;
97
+ dayIndex: number;
98
+ }) => ReactNode;
99
+ };
100
+ export declare const StatusHeatmapBody: ({ hideDateLabels, labelInterval, className, labelClassName, children, ...props }: StatusHeatmapBodyProps) => import("react").JSX.Element;
101
+ export type StatusHeatmapFooterProps = HTMLAttributes<HTMLDivElement>;
102
+ export declare const StatusHeatmapFooter: ({ className, ...props }: StatusHeatmapFooterProps) => import("react").JSX.Element;
103
+ export type StatusHeatmapStatProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
104
+ compute?: (data: StatusActivity[]) => number | string;
105
+ label?: string;
106
+ children?: (result: {
107
+ value: number | string;
108
+ data: StatusActivity[];
109
+ }) => ReactNode;
110
+ };
111
+ export declare const StatusHeatmapStat: ({ compute, label, className, children, ...props }: StatusHeatmapStatProps) => import("react").JSX.Element;
112
+ export type StatusHeatmapLegendProps = Omit<HTMLAttributes<HTMLFieldSetElement>, "children"> & {
113
+ labels?: StatusHeatmapLabels["statuses"];
114
+ children?: (props: {
115
+ value: StatusValue;
116
+ label: string;
117
+ }) => ReactNode;
118
+ };
119
+ export declare const StatusHeatmapLegend: ({ labels: labelsProp, className, children, ...props }: StatusHeatmapLegendProps) => import("react").JSX.Element;
120
+ //#endregion