@siyahi/ui 0.1.9-next.20 → 0.1.9-next.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +42 -0
- package/README.md +3 -3
- package/agents/examples/alert.tsx +4 -6
- package/agents/examples/app-sidebar.tsx +3 -1
- package/agents/examples/bar-list-sides.tsx +13 -3
- package/agents/examples/button-states.tsx +23 -0
- package/agents/examples/calendar.tsx +3 -7
- package/agents/examples/callout.tsx +33 -0
- package/agents/examples/card.tsx +3 -1
- package/agents/examples/change-bar.tsx +104 -0
- package/agents/examples/chart-area.tsx +5 -4
- package/agents/examples/chart-bar-flows.tsx +3 -2
- package/agents/examples/chart-bar-sectors.tsx +2 -0
- package/agents/examples/collapsible.tsx +2 -2
- package/agents/examples/command.tsx +22 -20
- package/agents/examples/context-menu.tsx +4 -2
- package/agents/examples/data-table-compact.tsx +95 -0
- package/agents/examples/distribution-bar-tug.tsx +24 -22
- package/agents/examples/heatmap-calendar.tsx +19 -7
- package/agents/examples/heatmap-status.tsx +19 -11
- package/agents/examples/heatmap-weekday.tsx +34 -15
- package/agents/examples/input-states.tsx +33 -0
- package/agents/examples/metric-strip.tsx +9 -3
- package/agents/examples/overview-dashboard.tsx +18 -0
- package/agents/examples/page-hero.tsx +38 -0
- package/agents/examples/price-chart.tsx +2 -2
- package/agents/examples/range-position-table.tsx +101 -0
- package/agents/examples/range-position.tsx +55 -0
- package/agents/examples/scroll-area.tsx +12 -10
- package/agents/examples/section-cards-select.tsx +8 -5
- package/agents/examples/surfaces.tsx +7 -4
- package/agents/examples/target-rows.tsx +72 -0
- package/agents/examples/treemap.tsx +13 -10
- package/agents/manifest.json +153 -3
- package/dist/components/registry/charts/area-chart-card.js +25 -7
- package/dist/components/registry/charts/bar-chart-card.d.ts +0 -1
- package/dist/components/registry/charts/bar-chart-card.js +15 -4
- package/dist/components/registry/charts/line-chart-card.js +2 -2
- package/dist/components/registry/dashboard-01/section-cards.js +1 -1
- package/dist/components/registry/dashboardcn/bar-list.js +11 -5
- package/dist/components/registry/dashboardcn/distribution-bar.js +5 -3
- package/dist/components/registry/dashboardcn/kpi-row-card.js +2 -2
- package/dist/components/registry/dashboardcn/sparkline.d.ts +5 -1
- package/dist/components/registry/dashboardcn/sparkline.js +40 -18
- package/dist/components/registry/heatmap/calendar-heatmap.d.ts +4 -2
- package/dist/components/registry/heatmap/calendar-heatmap.js +67 -33
- package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
- package/dist/components/registry/heatmap/status-heatmap.js +31 -16
- package/dist/components/registry/heatmap/weekday-heatmap.d.ts +4 -2
- package/dist/components/registry/heatmap/weekday-heatmap.js +28 -12
- package/dist/components/registry/niko-table/core/data-table-context.d.ts +16 -1
- package/dist/components/registry/niko-table/core/data-table-context.js +5 -20
- package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
- package/dist/components/registry/niko-table/core/data-table-structure.js +2 -0
- package/dist/components/registry/niko-table/lib/styles.js +1 -1
- package/dist/components/registry/niko-table/types/index.d.ts +9 -0
- package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -0
- package/dist/components/registry/sidebar-07/nav-main.js +9 -2
- package/dist/components/siyahi/callout.d.ts +24 -0
- package/dist/components/siyahi/callout.js +40 -0
- package/dist/components/siyahi/change-bar.d.ts +21 -0
- package/dist/components/siyahi/change-bar.js +35 -0
- package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
- package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
- package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
- package/dist/components/siyahi/data-table-footnote.js +35 -0
- package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
- package/dist/components/siyahi/default-sidebar-open.js +13 -0
- package/dist/components/siyahi/delta.d.ts +5 -1
- package/dist/components/siyahi/delta.js +14 -9
- package/dist/components/siyahi/page-hero.d.ts +25 -0
- package/dist/components/siyahi/page-hero.js +41 -0
- package/dist/components/siyahi/page-toc.d.ts +20 -0
- package/dist/components/siyahi/page-toc.js +76 -0
- package/dist/components/siyahi/quote.d.ts +12 -0
- package/dist/components/siyahi/quote.js +23 -0
- package/dist/components/siyahi/range-position.d.ts +24 -0
- package/dist/components/siyahi/range-position.js +105 -0
- package/dist/components/siyahi/siyahi-provider.d.ts +1 -1
- package/dist/components/siyahi/siyahi-provider.js +1 -1
- package/dist/components/siyahi/target-rows.d.ts +34 -0
- package/dist/components/siyahi/target-rows.js +97 -0
- package/dist/components/siyahi/treemap.d.ts +4 -1
- package/dist/components/siyahi/treemap.js +36 -28
- package/dist/components/siyahi/warning-alert.d.ts +10 -0
- package/dist/components/siyahi/warning-alert.js +20 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +12 -1
- package/dist/lib/chart-frame.d.ts +12 -2
- package/dist/lib/chart-frame.js +51 -7
- package/dist/lib/chart-series.d.ts +5 -2
- package/dist/lib/compact-columns.d.ts +19 -0
- package/dist/lib/compact-columns.js +55 -0
- package/dist/lib/format.d.ts +8 -3
- package/dist/lib/format.js +25 -6
- package/dist/lib/treemap.js +6 -1
- package/dist/lib/use-element-width.d.ts +5 -0
- package/dist/lib/use-element-width.js +15 -0
- package/package.json +2 -1
- package/report.css +37 -36
- package/styles/siyahi.css +243 -55
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
|
|
3
|
+
import { useElementWidth } from "../../../lib/use-element-width.js";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, createContext, use, useLayoutEffect, useMemo } from "react";
|
|
5
6
|
import { differenceInCalendarDays, eachDayOfInterval, format, formatISO, getDay, getMonth, getYear, nextDay, parseISO, subWeeks } from "date-fns";
|
|
6
7
|
//#region src/components/registry/heatmap/calendar-heatmap.tsx
|
|
7
8
|
const colorStepCount = (levels, isNormalized) => Math.max(1, isNormalized ? levels : levels - 1);
|
|
@@ -41,6 +42,12 @@ const generateWeekdayLabels = (locale) => {
|
|
|
41
42
|
};
|
|
42
43
|
const EMPTY_STYLE = {};
|
|
43
44
|
const LABEL_MARGIN = 8;
|
|
45
|
+
const WEEKDAY_LABEL_WIDTH = 40;
|
|
46
|
+
const STROKE_PADDING = 3;
|
|
47
|
+
const LEGEND_SWATCH = 12;
|
|
48
|
+
const MIN_FITTED_BLOCK = 10;
|
|
49
|
+
const MAX_FITTED_BLOCK = 20;
|
|
50
|
+
const DEFAULT_BLOCK = 12;
|
|
44
51
|
const CalendarHeatmapContext = createContext(null);
|
|
45
52
|
const useCalendarHeatmap = () => {
|
|
46
53
|
const context = use(CalendarHeatmapContext);
|
|
@@ -132,26 +139,18 @@ const groupContinuous = (activities, weekStart = 0) => {
|
|
|
132
139
|
const padded = [...new Array(leadingPad).fill(void 0), ...normalizedActivities];
|
|
133
140
|
const numberOfWeeks = Math.ceil(padded.length / 7);
|
|
134
141
|
const allWeeks = new Array(numberOfWeeks).fill(void 0).map((_, i) => padded.slice(i * 7, i * 7 + 7));
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const firstActivity = week.find((a) => a !== void 0);
|
|
138
|
-
const year = firstActivity ? getYear(parseISO(firstActivity.date)) : void 0;
|
|
139
|
-
if (year !== void 0) {
|
|
140
|
-
if (!yearMap.has(year)) yearMap.set(year, []);
|
|
141
|
-
yearMap.get(year)?.push(week);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return Array.from(yearMap.entries()).sort(([a], [b]) => a - b).map(([year, weeks]) => ({
|
|
145
|
-
year,
|
|
142
|
+
return [{
|
|
143
|
+
year: getYear(firstDate),
|
|
146
144
|
startMonth: 0,
|
|
147
|
-
weeks
|
|
148
|
-
}
|
|
145
|
+
weeks: allWeeks
|
|
146
|
+
}];
|
|
149
147
|
};
|
|
150
|
-
const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
151
|
-
|
|
148
|
+
const getMonthLabels = (weeks, monthNames = generateMonthLabels(), yearTicks = false) => {
|
|
149
|
+
const ticks = weeks.reduce((labels, week, weekIndex) => {
|
|
152
150
|
const firstActivity = week.find((activity) => activity !== void 0);
|
|
153
151
|
if (!firstActivity) return labels;
|
|
154
|
-
const
|
|
152
|
+
const date = parseISO(firstActivity.date);
|
|
153
|
+
const month = monthNames[getMonth(date)];
|
|
155
154
|
if (!month) {
|
|
156
155
|
const monthName = new Date(firstActivity.date).toLocaleString("en-US", { month: "short" });
|
|
157
156
|
throw new Error(`Unexpected error: undefined month label for ${monthName}.`);
|
|
@@ -159,7 +158,8 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
159
158
|
const prevLabel = labels.at(-1);
|
|
160
159
|
if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) return labels.concat({
|
|
161
160
|
weekIndex,
|
|
162
|
-
label: month
|
|
161
|
+
label: month,
|
|
162
|
+
year: getMonth(date) === 0 ? getYear(date) : void 0
|
|
163
163
|
});
|
|
164
164
|
return labels;
|
|
165
165
|
}, []).filter(({ weekIndex }, index, labels) => {
|
|
@@ -168,6 +168,22 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
168
168
|
if (index === labels.length - 1) return weeks.slice(weekIndex).length >= minWeeks;
|
|
169
169
|
return true;
|
|
170
170
|
});
|
|
171
|
+
if (!yearTicks) return ticks;
|
|
172
|
+
const hasJanuary = ticks.some(({ year }) => year !== void 0);
|
|
173
|
+
return ticks.map((tick, index) => {
|
|
174
|
+
if (tick.year !== void 0) return {
|
|
175
|
+
...tick,
|
|
176
|
+
label: String(tick.year)
|
|
177
|
+
};
|
|
178
|
+
if (index === 0 && !hasJanuary) {
|
|
179
|
+
const week = weeks[tick.weekIndex]?.find((activity) => activity !== void 0);
|
|
180
|
+
return week ? {
|
|
181
|
+
...tick,
|
|
182
|
+
label: `${tick.label} ${getYear(parseISO(week.date))}`
|
|
183
|
+
} : tick;
|
|
184
|
+
}
|
|
185
|
+
return tick;
|
|
186
|
+
});
|
|
171
187
|
};
|
|
172
188
|
/**
|
|
173
189
|
* Calendar Heatmap
|
|
@@ -202,8 +218,9 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
202
218
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
203
219
|
* @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.
|
|
204
220
|
*/
|
|
205
|
-
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize
|
|
221
|
+
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize: blockSizeProp, 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 }) => {
|
|
206
222
|
const levels = Math.max(1, levelsProp);
|
|
223
|
+
const [rootRef, rootWidth] = useElementWidth();
|
|
207
224
|
const dataWithLevels = useMemo(() => {
|
|
208
225
|
if (data.length === 0) return [];
|
|
209
226
|
const maxCount = data.reduce((max, d) => Math.max(max, d.value), 1);
|
|
@@ -236,6 +253,8 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
236
253
|
const labelHeight = fontSize + LABEL_MARGIN;
|
|
237
254
|
const year = data.length > 0 ? getYear(parseISO(data[0].date)) : (/* @__PURE__ */ new Date()).getFullYear();
|
|
238
255
|
const totalCount = typeof totalCountProp === "number" ? totalCountProp : dataWithLevels.reduce((sum, activity) => sum + activity.value, 0);
|
|
256
|
+
const fitted = (rootWidth - WEEKDAY_LABEL_WIDTH - 6 + blockMargin) / Math.max(weeks.length, 1) - blockMargin;
|
|
257
|
+
const blockSize = blockSizeProp ?? (rootWidth > 0 ? Math.min(MAX_FITTED_BLOCK, Math.max(MIN_FITTED_BLOCK, fitted)) / blockAspectRatio : DEFAULT_BLOCK);
|
|
239
258
|
const blockWidth = blockSize * blockAspectRatio;
|
|
240
259
|
const width = weeks.length * (blockWidth + blockMargin) - blockMargin;
|
|
241
260
|
const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;
|
|
@@ -290,8 +309,9 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
290
309
|
return /* @__PURE__ */ jsx(CalendarHeatmapContext, {
|
|
291
310
|
value: contextValue,
|
|
292
311
|
children: /* @__PURE__ */ jsx("div", {
|
|
312
|
+
ref: rootRef,
|
|
293
313
|
"data-slot": "calendar-heatmap",
|
|
294
|
-
className: cn("flex
|
|
314
|
+
className: cn("flex flex-col gap-2", blockSizeProp === void 0 ? "w-full" : "w-max max-w-full", className),
|
|
295
315
|
style: {
|
|
296
316
|
fontSize,
|
|
297
317
|
...style
|
|
@@ -301,7 +321,7 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
301
321
|
})
|
|
302
322
|
});
|
|
303
323
|
};
|
|
304
|
-
const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
324
|
+
const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
305
325
|
const { blockSize, blockWidth, blockMargin, blockRadius, labelHeight, labels, levels, isNormalized, colors } = useCalendarHeatmap();
|
|
306
326
|
const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
|
|
307
327
|
const ariaLabel = (labels.cellLabel ?? "{{date}}: {{value}} contributions").replace("{{date}}", activity.date).replace("{{value}}", String(activity.value));
|
|
@@ -309,6 +329,7 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
309
329
|
ref,
|
|
310
330
|
"data-slot": "calendar-heatmap-block",
|
|
311
331
|
role: onCellClick ? "button" : "img",
|
|
332
|
+
"aria-pressed": onCellClick ? selected : void 0,
|
|
312
333
|
tabIndex: onCellClick ? 0 : -1,
|
|
313
334
|
"aria-label": ariaLabel,
|
|
314
335
|
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),
|
|
@@ -316,6 +337,7 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
316
337
|
"data-date": activity.date,
|
|
317
338
|
"data-level": level,
|
|
318
339
|
"data-highlighted": highlighted || void 0,
|
|
340
|
+
"data-selected": selected || void 0,
|
|
319
341
|
height: blockSize,
|
|
320
342
|
rx: blockRadius,
|
|
321
343
|
ry: blockRadius,
|
|
@@ -324,6 +346,10 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
324
346
|
y: labelHeight + (blockSize + blockMargin) * dayIndex,
|
|
325
347
|
style: {
|
|
326
348
|
fill: getLevelFill(level, levels, isNormalized, highlighted, colors),
|
|
349
|
+
...selected ? {
|
|
350
|
+
stroke: "var(--foreground)",
|
|
351
|
+
strokeWidth: 2
|
|
352
|
+
} : {},
|
|
327
353
|
...styleProp
|
|
328
354
|
},
|
|
329
355
|
onClick: (event) => {
|
|
@@ -351,17 +377,17 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
351
377
|
CalendarHeatmapBlock.displayName = "CalendarHeatmapBlock";
|
|
352
378
|
const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = false, hideYearLabels = false, className, labelClassName, yearClassName, children, renderYearFooter, ...props }) => {
|
|
353
379
|
const navigation = useHeatmapNavigation();
|
|
354
|
-
const { yearRows, blockSize, blockWidth, blockMargin, labels, labelHeight, fontSize, weekStart,
|
|
355
|
-
const weekdayLabelWidth = hideWeekdayLabels ? 0 :
|
|
356
|
-
const strokePadding =
|
|
380
|
+
const { yearRows, blockSize, blockWidth, blockMargin, labels, labelHeight, fontSize, weekStart, continuousMonths } = useCalendarHeatmap();
|
|
381
|
+
const weekdayLabelWidth = hideWeekdayLabels ? 0 : WEEKDAY_LABEL_WIDTH;
|
|
382
|
+
const strokePadding = STROKE_PADDING;
|
|
357
383
|
const rowData = useMemo(() => {
|
|
358
384
|
return yearRows.map((yearRow) => {
|
|
359
385
|
return {
|
|
360
386
|
yearRow,
|
|
361
387
|
width: yearRow.weeks.length * (blockWidth + blockMargin) - blockMargin,
|
|
362
388
|
height: labelHeight + (blockSize + blockMargin) * 7 - blockMargin,
|
|
363
|
-
monthLabels: getMonthLabels(yearRow.weeks, labels.months),
|
|
364
|
-
yearTotalCount:
|
|
389
|
+
monthLabels: getMonthLabels(yearRow.weeks, labels.months, continuousMonths),
|
|
390
|
+
yearTotalCount: yearRow.weeks.flat().reduce((sum, activity) => sum + (activity?.value ?? 0), 0)
|
|
365
391
|
};
|
|
366
392
|
});
|
|
367
393
|
}, [
|
|
@@ -371,9 +397,17 @@ const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = fals
|
|
|
371
397
|
blockMargin,
|
|
372
398
|
labelHeight,
|
|
373
399
|
labels.months,
|
|
374
|
-
|
|
400
|
+
continuousMonths
|
|
375
401
|
]);
|
|
376
402
|
const totalWidth = weekdayLabelWidth + Math.max(...rowData.map((r) => r.width)) + 6;
|
|
403
|
+
const scroller = navigation.ref;
|
|
404
|
+
useLayoutEffect(() => {
|
|
405
|
+
if (continuousMonths && scroller.current) scroller.current.scrollLeft = scroller.current.scrollWidth;
|
|
406
|
+
}, [
|
|
407
|
+
continuousMonths,
|
|
408
|
+
scroller,
|
|
409
|
+
totalWidth
|
|
410
|
+
]);
|
|
377
411
|
return /* @__PURE__ */ jsx("div", {
|
|
378
412
|
"data-slot": "calendar-heatmap-body",
|
|
379
413
|
ref: navigation.ref,
|
|
@@ -384,7 +418,7 @@ const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = fals
|
|
|
384
418
|
if (!event.defaultPrevented) navigation.onKeyDown(event);
|
|
385
419
|
},
|
|
386
420
|
children: rowData.map(({ yearRow, height, monthLabels, yearTotalCount }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
387
|
-
!hideYearLabels && /* @__PURE__ */ jsx("div", {
|
|
421
|
+
!hideYearLabels && !continuousMonths && /* @__PURE__ */ jsx("div", {
|
|
388
422
|
className: cn("mb-2 text-muted-foreground", yearClassName),
|
|
389
423
|
children: yearRow.year
|
|
390
424
|
}),
|
|
@@ -465,7 +499,7 @@ const CalendarHeatmapStat = ({ compute, label, className, children, ...props })
|
|
|
465
499
|
});
|
|
466
500
|
};
|
|
467
501
|
const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
|
|
468
|
-
const { levels, isNormalized,
|
|
502
|
+
const { levels, isNormalized, blockRadius, colors, labels } = useCalendarHeatmap();
|
|
469
503
|
const lessLabel = labelsProp?.less ?? "Less";
|
|
470
504
|
const moreLabel = labelsProp?.more ?? "More";
|
|
471
505
|
const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
|
|
@@ -482,14 +516,14 @@ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...pro
|
|
|
482
516
|
legendLevels.map((level) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({ level }) }, `legend-level-${level}`) : /* @__PURE__ */ jsx("svg", {
|
|
483
517
|
role: "img",
|
|
484
518
|
"aria-label": (labels.legendLevelLabel ?? "{{level}} contributions").replace("{{level}}", String(level)),
|
|
485
|
-
height:
|
|
486
|
-
width:
|
|
519
|
+
height: LEGEND_SWATCH,
|
|
520
|
+
width: LEGEND_SWATCH,
|
|
487
521
|
children: /* @__PURE__ */ jsx("rect", {
|
|
488
522
|
"data-level": level,
|
|
489
|
-
height:
|
|
523
|
+
height: LEGEND_SWATCH,
|
|
490
524
|
rx: blockRadius,
|
|
491
525
|
ry: blockRadius,
|
|
492
|
-
width:
|
|
526
|
+
width: LEGEND_SWATCH,
|
|
493
527
|
style: { fill: getLevelFill(level, levels, isNormalized, false, colors) }
|
|
494
528
|
})
|
|
495
529
|
}, `legend-level-${level}`)),
|
|
@@ -69,11 +69,13 @@ export type StatusHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
69
69
|
*
|
|
70
70
|
* @param data - Array of activities with date (YYYY-MM-DD) and status value (0-3)
|
|
71
71
|
* @param dateFormat - Date format string for tooltips. Default: "MMM d"
|
|
72
|
-
* @param blockAspectRatio - Width/height ratio of blocks. Default:
|
|
72
|
+
* @param blockAspectRatio - Width/height ratio of blocks. Default: bars share the full width
|
|
73
73
|
* @param colors - Custom colors for critical, degraded, and healthy states
|
|
74
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;
|
|
75
|
+
export declare const StatusHeatmap: ({ data, dateFormat, blockSize, blockMargin, blockRadius, blockAspectRatio: blockAspectRatioProp, 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
76
|
export type StatusHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
77
|
+
/** The cell the page has picked: drawn with a ring and announced as pressed. */
|
|
78
|
+
selected?: boolean;
|
|
77
79
|
activity: StatusActivity;
|
|
78
80
|
dayIndex: number;
|
|
79
81
|
highlighted?: boolean;
|
|
@@ -81,7 +83,7 @@ export type StatusHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
|
81
83
|
onCellHover?: (activity: StatusActivity | null) => void;
|
|
82
84
|
className?: string;
|
|
83
85
|
};
|
|
84
|
-
export declare function StatusHeatmapBlock({ ref, activity, dayIndex, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: StatusHeatmapBlockProps & {
|
|
86
|
+
export declare function StatusHeatmapBlock({ ref, activity, dayIndex, highlighted, selected, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: StatusHeatmapBlockProps & {
|
|
85
87
|
ref?: React.RefObject<SVGRectElement | null>;
|
|
86
88
|
}): import("react").JSX.Element;
|
|
87
89
|
export declare namespace StatusHeatmapBlock {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
|
|
3
|
+
import { useElementWidth } from "../../../lib/use-element-width.js";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
|
|
5
6
|
import { format } from "date-fns";
|
|
6
7
|
//#region src/components/registry/heatmap/status-heatmap.tsx
|
|
7
8
|
const DEFAULT_COLORS = {
|
|
8
9
|
0: "var(--color-secondary)",
|
|
9
|
-
1: "var(--
|
|
10
|
+
1: "var(--destructive)",
|
|
10
11
|
2: "var(--warning)",
|
|
11
|
-
3: "var(--
|
|
12
|
+
3: "color-mix(in oklab, var(--muted-foreground) 40%, transparent)"
|
|
12
13
|
};
|
|
13
14
|
const SEMANTIC_KEYS = {
|
|
14
15
|
0: "noData",
|
|
@@ -52,6 +53,9 @@ const getStatusFill = (value, colors, highlighted = false) => {
|
|
|
52
53
|
return `color-mix(in oklch, ${base} 60%, transparent)`;
|
|
53
54
|
};
|
|
54
55
|
const EMPTY_STYLE = {};
|
|
56
|
+
const LEGEND_SWATCH = 10;
|
|
57
|
+
const MIN_FITTED_BAR = 2;
|
|
58
|
+
const DEFAULT_ASPECT_RATIO = .2;
|
|
55
59
|
const StatusHeatmapContext = createContext(null);
|
|
56
60
|
const useStatusHeatmap = () => {
|
|
57
61
|
const context = use(StatusHeatmapContext);
|
|
@@ -85,15 +89,16 @@ const useStatusHeatmap = () => {
|
|
|
85
89
|
*
|
|
86
90
|
* @param data - Array of activities with date (YYYY-MM-DD) and status value (0-3)
|
|
87
91
|
* @param dateFormat - Date format string for tooltips. Default: "MMM d"
|
|
88
|
-
* @param blockAspectRatio - Width/height ratio of blocks. Default:
|
|
92
|
+
* @param blockAspectRatio - Width/height ratio of blocks. Default: bars share the full width
|
|
89
93
|
* @param colors - Custom colors for critical, degraded, and healthy states
|
|
90
94
|
*/
|
|
91
|
-
const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin = 2, blockRadius = 2, blockAspectRatio
|
|
95
|
+
const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin = 2, blockRadius = 2, blockAspectRatio: blockAspectRatioProp, colors, locale, labels: labelsProp, fontSize = 14, emptyState, statusValues = [
|
|
92
96
|
0,
|
|
93
97
|
1,
|
|
94
98
|
2,
|
|
95
99
|
3
|
|
96
100
|
], healthyValue = 3, style = EMPTY_STYLE, className, children, ...props }) => {
|
|
101
|
+
const [rootRef, rootWidth] = useElementWidth();
|
|
97
102
|
const labels = useMemo(() => ({
|
|
98
103
|
statuses: {
|
|
99
104
|
noData: "No Data",
|
|
@@ -113,7 +118,10 @@ const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin
|
|
|
113
118
|
});
|
|
114
119
|
return Array.from(uniqueDates).sort();
|
|
115
120
|
}, [data]);
|
|
116
|
-
const
|
|
121
|
+
const fits = blockAspectRatioProp === void 0;
|
|
122
|
+
const fitted = (rootWidth - 6 + blockMargin) / Math.max(dates.length, 1) - blockMargin;
|
|
123
|
+
const blockWidth = fits && rootWidth > 0 ? Math.max(MIN_FITTED_BAR, fitted) : blockSize * (blockAspectRatioProp ?? DEFAULT_ASPECT_RATIO);
|
|
124
|
+
const blockAspectRatio = blockWidth / blockSize;
|
|
117
125
|
const width = dates.length * (blockWidth + blockMargin) - blockMargin;
|
|
118
126
|
const height = blockSize;
|
|
119
127
|
const contextValue = useMemo(() => ({
|
|
@@ -155,8 +163,9 @@ const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin
|
|
|
155
163
|
return /* @__PURE__ */ jsx(StatusHeatmapContext, {
|
|
156
164
|
value: contextValue,
|
|
157
165
|
children: /* @__PURE__ */ jsx("div", {
|
|
166
|
+
ref: rootRef,
|
|
158
167
|
"data-slot": "status-heatmap",
|
|
159
|
-
className: cn("flex
|
|
168
|
+
className: cn("flex flex-col gap-2", fits ? "w-full" : "w-max max-w-full", className),
|
|
160
169
|
style: {
|
|
161
170
|
fontSize,
|
|
162
171
|
...style
|
|
@@ -166,7 +175,7 @@ const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin
|
|
|
166
175
|
})
|
|
167
176
|
});
|
|
168
177
|
};
|
|
169
|
-
const StatusHeatmapBlock = ({ ref, activity, dayIndex, highlighted = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
178
|
+
const StatusHeatmapBlock = ({ ref, activity, dayIndex, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
170
179
|
const { blockSize, blockWidth, blockMargin, blockRadius, labels, colors } = useStatusHeatmap();
|
|
171
180
|
const statusText = resolveStatusLabel(activity.value, labels.statuses);
|
|
172
181
|
const ariaLabel = (labels.cellLabel ?? "{{date}}: {{status}}").replace("{{date}}", activity.date).replace("{{status}}", statusText);
|
|
@@ -175,12 +184,14 @@ const StatusHeatmapBlock = ({ ref, activity, dayIndex, highlighted = false, onCe
|
|
|
175
184
|
ref,
|
|
176
185
|
"data-slot": "status-heatmap-block",
|
|
177
186
|
role: onCellClick ? "button" : "img",
|
|
187
|
+
"aria-pressed": onCellClick ? selected : void 0,
|
|
178
188
|
tabIndex: onCellClick ? 0 : -1,
|
|
179
189
|
"aria-label": ariaLabel,
|
|
180
190
|
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),
|
|
181
191
|
"data-value": activity.value,
|
|
182
192
|
"data-date": activity.date,
|
|
183
193
|
"data-highlighted": highlighted || void 0,
|
|
194
|
+
"data-selected": selected || void 0,
|
|
184
195
|
height: blockSize,
|
|
185
196
|
rx: blockRadius,
|
|
186
197
|
ry: blockRadius,
|
|
@@ -189,6 +200,10 @@ const StatusHeatmapBlock = ({ ref, activity, dayIndex, highlighted = false, onCe
|
|
|
189
200
|
y: 0,
|
|
190
201
|
style: {
|
|
191
202
|
fill: getStatusFill(activity.value, colors, highlighted),
|
|
203
|
+
...selected ? {
|
|
204
|
+
stroke: "var(--foreground)",
|
|
205
|
+
strokeWidth: 2
|
|
206
|
+
} : {},
|
|
192
207
|
...styleProp
|
|
193
208
|
},
|
|
194
209
|
onClick: (event) => {
|
|
@@ -247,9 +262,9 @@ const StatusHeatmapBody = ({ hideDateLabels = false, labelInterval = 30, classNa
|
|
|
247
262
|
role: "img",
|
|
248
263
|
"aria-label": labels.heatmapLabel ?? "Status heatmap",
|
|
249
264
|
className: "block overflow-visible rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
250
|
-
height: height + labelHeight +
|
|
251
|
-
viewBox: `-
|
|
252
|
-
width: width +
|
|
265
|
+
height: height + labelHeight + 6,
|
|
266
|
+
viewBox: `-3 -3 ${width + 6} ${height + labelHeight + 6}`,
|
|
267
|
+
width: width + 6,
|
|
253
268
|
children: [!hideDateLabels && /* @__PURE__ */ jsx("g", {
|
|
254
269
|
className: cn("fill-current font-mono", labelClassName),
|
|
255
270
|
children: dates.map((date, dayIndex) => {
|
|
@@ -299,7 +314,7 @@ const StatusHeatmapStat = ({ compute, label, className, children, ...props }) =>
|
|
|
299
314
|
});
|
|
300
315
|
};
|
|
301
316
|
const StatusHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
|
|
302
|
-
const { labels, statusValues,
|
|
317
|
+
const { labels, statusValues, blockRadius, colors } = useStatusHeatmap();
|
|
303
318
|
const statuses = statusValues.map((value) => ({
|
|
304
319
|
value,
|
|
305
320
|
label: resolveStatusLabel(value, labelsProp ?? labels.statuses)
|
|
@@ -307,7 +322,7 @@ const StatusHeatmapLegend = ({ labels: labelsProp, className, children, ...props
|
|
|
307
322
|
return /* @__PURE__ */ jsx("fieldset", {
|
|
308
323
|
"data-slot": "status-heatmap-legend",
|
|
309
324
|
"aria-label": labels.legendLabel ?? "Status legend",
|
|
310
|
-
className: cn("ml-auto flex items-center gap-
|
|
325
|
+
className: cn("ml-auto flex items-center gap-3", className),
|
|
311
326
|
...props,
|
|
312
327
|
children: statuses.map((status) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({
|
|
313
328
|
value: status.value,
|
|
@@ -316,14 +331,14 @@ const StatusHeatmapLegend = ({ labels: labelsProp, className, children, ...props
|
|
|
316
331
|
className: "flex items-center gap-1",
|
|
317
332
|
children: [/* @__PURE__ */ jsx("svg", {
|
|
318
333
|
"aria-hidden": "true",
|
|
319
|
-
height:
|
|
320
|
-
width:
|
|
334
|
+
height: LEGEND_SWATCH,
|
|
335
|
+
width: LEGEND_SWATCH,
|
|
321
336
|
children: /* @__PURE__ */ jsx("rect", {
|
|
322
337
|
"data-value": status.value,
|
|
323
|
-
height:
|
|
338
|
+
height: LEGEND_SWATCH,
|
|
324
339
|
rx: blockRadius,
|
|
325
340
|
ry: blockRadius,
|
|
326
|
-
width:
|
|
341
|
+
width: LEGEND_SWATCH,
|
|
327
342
|
style: { fill: getStatusFill(status.value, colors) }
|
|
328
343
|
})
|
|
329
344
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -90,8 +90,10 @@ export type WeekdayHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
90
90
|
* @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.
|
|
91
91
|
* @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.
|
|
92
92
|
*/
|
|
93
|
-
export declare const WeekdayHeatmap: ({ data, weekStart, hours, weekdays: displayedWeekdays, use12Hour, blockSize, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => 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;
|
|
93
|
+
export declare const WeekdayHeatmap: ({ data, weekStart, hours, weekdays: displayedWeekdays, use12Hour, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => 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;
|
|
94
94
|
export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
95
|
+
/** The cell the page has picked: drawn with a ring and announced as pressed. */
|
|
96
|
+
selected?: boolean;
|
|
95
97
|
activity: WeekdayHourlyActivityWithLevel;
|
|
96
98
|
extra?: "row" | "column";
|
|
97
99
|
highlighted?: boolean;
|
|
@@ -99,7 +101,7 @@ export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
|
99
101
|
onCellHover?: (activity: WeekdayHourlyActivityWithLevel | null) => void;
|
|
100
102
|
className?: string;
|
|
101
103
|
};
|
|
102
|
-
export declare function WeekdayHeatmapBlock({ ref, activity, extra, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: WeekdayHeatmapBlockProps & {
|
|
104
|
+
export declare function WeekdayHeatmapBlock({ ref, activity, extra, highlighted, selected, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: WeekdayHeatmapBlockProps & {
|
|
103
105
|
ref?: React.RefObject<SVGRectElement | null>;
|
|
104
106
|
}): import("react").JSX.Element | null;
|
|
105
107
|
export declare namespace WeekdayHeatmapBlock {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
|
|
3
|
+
import { useElementWidth } from "../../../lib/use-element-width.js";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
|
|
5
6
|
import { format } from "date-fns";
|
|
@@ -48,6 +49,10 @@ const generateWeekdayLabels = (locale) => Array.from({ length: 7 }, (_, i) => {
|
|
|
48
49
|
});
|
|
49
50
|
const EMPTY_STYLE = {};
|
|
50
51
|
const LABEL_MARGIN = 8;
|
|
52
|
+
const LEGEND_SWATCH = 12;
|
|
53
|
+
const MIN_FITTED_BLOCK = 14;
|
|
54
|
+
const MAX_FITTED_BLOCK = 44;
|
|
55
|
+
const DEFAULT_BLOCK = 24;
|
|
51
56
|
const WeekdayHeatmapContext = createContext(null);
|
|
52
57
|
const useWeekdayHeatmap = () => {
|
|
53
58
|
const context = use(WeekdayHeatmapContext);
|
|
@@ -85,8 +90,9 @@ const useWeekdayHeatmap = () => {
|
|
|
85
90
|
* @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.
|
|
86
91
|
* @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.
|
|
87
92
|
*/
|
|
88
|
-
const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displayedWeekdays, use12Hour = false, blockSize
|
|
93
|
+
const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displayedWeekdays, use12Hour = false, blockSize: blockSizeProp, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style = EMPTY_STYLE, className, children, ...props }) => {
|
|
89
94
|
const levels = Math.max(1, levelsProp);
|
|
95
|
+
const [rootRef, rootWidth] = useElementWidth();
|
|
90
96
|
const weekdays = useMemo(() => displayedWeekdays ?? Array.from({ length: 7 }, (_, i) => (weekStart + i) % 7), [displayedWeekdays, weekStart]);
|
|
91
97
|
const dataWithLevels = useMemo(() => {
|
|
92
98
|
if (data.length === 0) return [];
|
|
@@ -159,9 +165,12 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
|
|
|
159
165
|
]);
|
|
160
166
|
const labelWidth = fontSize * 3.5 + LABEL_MARGIN;
|
|
161
167
|
const totalCount = typeof totalCountProp === "number" ? totalCountProp : dataWithLevels.reduce((sum, a) => sum + a.value, 0);
|
|
168
|
+
const hasExtraColumn = resolvedExtraColumn !== null;
|
|
169
|
+
const columns = hours.length + (hasExtraColumn ? 2 : 0);
|
|
170
|
+
const fitted = (rootWidth - 24 - labelWidth - columns * blockMargin + (hasExtraColumn ? 0 : blockMargin)) / (columns + (hasExtraColumn ? 1 / (2 * blockAspectRatio) : 0));
|
|
171
|
+
const blockSize = blockSizeProp ?? (rootWidth > 0 ? Math.min(MAX_FITTED_BLOCK, Math.max(MIN_FITTED_BLOCK, fitted)) / blockAspectRatio : DEFAULT_BLOCK);
|
|
162
172
|
const blockWidth = blockSize * blockAspectRatio;
|
|
163
173
|
const labelHeight = fontSize + LABEL_MARGIN;
|
|
164
|
-
const hasExtraColumn = resolvedExtraColumn !== null;
|
|
165
174
|
const hasExtraRow = resolvedExtraRow !== null;
|
|
166
175
|
const width = hours.length * (blockWidth + blockMargin) + (hasExtraColumn ? blockSize / 2 + (blockWidth + blockMargin) * 2 : 0) - blockMargin + labelWidth;
|
|
167
176
|
const rowCount = weekdays.length + (hasExtraRow ? 1 : 0);
|
|
@@ -216,8 +225,9 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
|
|
|
216
225
|
return /* @__PURE__ */ jsx(WeekdayHeatmapContext, {
|
|
217
226
|
value: contextValue,
|
|
218
227
|
children: /* @__PURE__ */ jsx("div", {
|
|
228
|
+
ref: rootRef,
|
|
219
229
|
"data-slot": "weekday-heatmap",
|
|
220
|
-
className: cn("flex
|
|
230
|
+
className: cn("flex flex-col gap-2", blockSizeProp === void 0 ? "w-full" : "w-max max-w-full", className),
|
|
221
231
|
style: {
|
|
222
232
|
fontSize,
|
|
223
233
|
...style
|
|
@@ -227,7 +237,7 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
|
|
|
227
237
|
})
|
|
228
238
|
});
|
|
229
239
|
};
|
|
230
|
-
const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
240
|
+
const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
231
241
|
const { blockSize, blockWidth, blockMargin, blockRadius, labelWidth, labelHeight, levels, isNormalized, hours, weekdays, labels, colors } = useWeekdayHeatmap();
|
|
232
242
|
const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
|
|
233
243
|
const isExtraRow = extra === "row";
|
|
@@ -246,6 +256,7 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
|
|
|
246
256
|
ref,
|
|
247
257
|
"data-slot": "weekday-heatmap-block",
|
|
248
258
|
role: onCellClick ? "button" : "img",
|
|
259
|
+
"aria-pressed": onCellClick ? selected : void 0,
|
|
249
260
|
tabIndex: onCellClick ? 0 : -1,
|
|
250
261
|
"aria-label": ariaLabel,
|
|
251
262
|
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),
|
|
@@ -254,6 +265,7 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
|
|
|
254
265
|
"data-hour": isExtraColumn ? "extra" : activity.hour,
|
|
255
266
|
"data-level": level,
|
|
256
267
|
"data-highlighted": highlighted || void 0,
|
|
268
|
+
"data-selected": selected || void 0,
|
|
257
269
|
height: blockSize,
|
|
258
270
|
rx: blockRadius,
|
|
259
271
|
ry: blockRadius,
|
|
@@ -262,6 +274,10 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
|
|
|
262
274
|
y: yPosition,
|
|
263
275
|
style: {
|
|
264
276
|
fill: getLevelFill(level, levels, isNormalized, highlighted, colors),
|
|
277
|
+
...selected ? {
|
|
278
|
+
stroke: "var(--foreground)",
|
|
279
|
+
strokeWidth: 2
|
|
280
|
+
} : {},
|
|
265
281
|
...styleProp
|
|
266
282
|
},
|
|
267
283
|
onClick: (event) => {
|
|
@@ -374,9 +390,9 @@ const WeekdayHeatmapBody = ({ hideHourLabels = false, hideWeekdayLabels = false,
|
|
|
374
390
|
role: "img",
|
|
375
391
|
"aria-label": labels.heatmapLabel ?? "Activity heatmap by weekday and hour",
|
|
376
392
|
className: "block overflow-visible rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
377
|
-
height: svgHeight +
|
|
378
|
-
viewBox: `-
|
|
379
|
-
width: svgWidth +
|
|
393
|
+
height: svgHeight + 24,
|
|
394
|
+
viewBox: `-12 -12 ${svgWidth + 24} ${svgHeight + 24}`,
|
|
395
|
+
width: svgWidth + 24,
|
|
380
396
|
children: [
|
|
381
397
|
!hideHourLabels && /* @__PURE__ */ jsxs("g", {
|
|
382
398
|
className: cn("fill-current font-mono", labelClassName),
|
|
@@ -469,7 +485,7 @@ const WeekdayHeatmapStat = ({ compute, label, className, children, ...props }) =
|
|
|
469
485
|
});
|
|
470
486
|
};
|
|
471
487
|
const WeekdayHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
|
|
472
|
-
const { levels, isNormalized,
|
|
488
|
+
const { levels, isNormalized, blockRadius, colors, labels } = useWeekdayHeatmap();
|
|
473
489
|
const lessLabel = labelsProp?.less ?? "Less";
|
|
474
490
|
const moreLabel = labelsProp?.more ?? "More";
|
|
475
491
|
const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
|
|
@@ -486,14 +502,14 @@ const WeekdayHeatmapLegend = ({ labels: labelsProp, className, children, ...prop
|
|
|
486
502
|
legendLevels.map((level) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({ level }) }, `legend-level-${level}`) : /* @__PURE__ */ jsx("svg", {
|
|
487
503
|
role: "img",
|
|
488
504
|
"aria-label": (labels.legendLevelLabel ?? "{{level}} contributions").replace("{{level}}", String(level)),
|
|
489
|
-
height:
|
|
490
|
-
width:
|
|
505
|
+
height: LEGEND_SWATCH,
|
|
506
|
+
width: LEGEND_SWATCH,
|
|
491
507
|
children: /* @__PURE__ */ jsx("rect", {
|
|
492
508
|
"data-level": level,
|
|
493
|
-
height:
|
|
509
|
+
height: LEGEND_SWATCH,
|
|
494
510
|
rx: blockRadius,
|
|
495
511
|
ry: blockRadius,
|
|
496
|
-
width:
|
|
512
|
+
width: LEGEND_SWATCH,
|
|
497
513
|
style: { fill: getLevelFill(level, levels, isNormalized, false, colors) }
|
|
498
514
|
})
|
|
499
515
|
}, `legend-level-${level}`)),
|
|
@@ -93,6 +93,11 @@ type DataTableContextProps<TData extends RowData> = DataTableContextState & {
|
|
|
93
93
|
scrollContainer: HTMLElement | null;
|
|
94
94
|
/** True below 40rem of table space; use for compact toolbar and record layouts. */
|
|
95
95
|
isCompact: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Set while some columns do not fit and the table has `meta.compact`
|
|
98
|
+
* columns: they hide, rightmost first, until the reader asks for all.
|
|
99
|
+
*/
|
|
100
|
+
compactColumns: CompactColumns | null;
|
|
96
101
|
/** Internal: `<DataTable>` registers its scroll container element here. */
|
|
97
102
|
registerScrollContainer: (el: HTMLElement | null) => void;
|
|
98
103
|
/**
|
|
@@ -148,12 +153,22 @@ export declare enum DataTableActions {
|
|
|
148
153
|
SET = 0,
|
|
149
154
|
SET_IS_LOADING = 1
|
|
150
155
|
}
|
|
156
|
+
export interface CompactColumns {
|
|
157
|
+
/** True while columns are hidden to fit; false once the reader shows them all. */
|
|
158
|
+
collapsed: boolean;
|
|
159
|
+
/** The columns hidden to fit right now, for a cell to fold one under a kept figure. */
|
|
160
|
+
hidden: ReadonlySet<string>;
|
|
161
|
+
setCollapsed: (collapsed: boolean) => void;
|
|
162
|
+
}
|
|
151
163
|
interface DataTableProviderProps<TData extends RowData> {
|
|
152
164
|
children: React.ReactNode;
|
|
153
165
|
table: DataTableInstance<TData>;
|
|
154
166
|
columns?: DataTableColumnDef<TData>[];
|
|
155
167
|
isLoading?: boolean;
|
|
168
|
+
isCompact: boolean;
|
|
169
|
+
onWidthChange: (width: number) => void;
|
|
170
|
+
compactColumns: CompactColumns | null;
|
|
156
171
|
}
|
|
157
|
-
export declare function DataTableProvider<TData extends RowData>({ children, table, columns, isLoading: externalIsLoading }: DataTableProviderProps<TData>): React.JSX.Element;
|
|
172
|
+
export declare function DataTableProvider<TData extends RowData>({ children, table, columns, isLoading: externalIsLoading, isCompact, onWidthChange, compactColumns }: DataTableProviderProps<TData>): React.JSX.Element;
|
|
158
173
|
//#endregion
|
|
159
174
|
export { DataTableContext };
|