@siyahi/ui 0.1.9-next.20 → 0.1.9-next.21
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 +30 -0
- package/README.md +3 -3
- package/agents/examples/alert.tsx +4 -6
- package/agents/examples/bar-list-sides.tsx +11 -3
- package/agents/examples/button-states.tsx +23 -0
- package/agents/examples/callout.tsx +33 -0
- 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/data-table-compact.tsx +95 -0
- package/agents/examples/heatmap-calendar.tsx +17 -7
- package/agents/examples/heatmap-status.tsx +17 -11
- package/agents/examples/heatmap-weekday.tsx +33 -11
- 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/range-position-table.tsx +101 -0
- package/agents/examples/range-position.tsx +53 -0
- package/agents/examples/section-cards-select.tsx +8 -5
- package/agents/examples/surfaces.tsx +3 -2
- package/agents/examples/target-rows.tsx +70 -0
- package/agents/examples/treemap.tsx +2 -1
- 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 +11 -4
- package/dist/components/registry/charts/line-chart-card.js +2 -2
- package/dist/components/registry/dashboardcn/bar-list.js +11 -5
- 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 +30 -15
- 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/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 +33 -25
- 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 +7 -2
- package/dist/lib/format.js +24 -5
- 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 +26 -25
- package/styles/siyahi.css +159 -43
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { ChartFrame } from "../../../lib/chart-frame.js";
|
|
2
|
+
import { ChartFrame, endDot } from "../../../lib/chart-frame.js";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { Line, LineChart } from "recharts";
|
|
5
5
|
//#region src/components/registry/charts/line-chart-card.tsx
|
|
@@ -13,7 +13,7 @@ function LineChartCard({ curve = "monotone", ...props }) {
|
|
|
13
13
|
type: curve,
|
|
14
14
|
stroke: color,
|
|
15
15
|
strokeWidth: 2,
|
|
16
|
-
dot:
|
|
16
|
+
dot: endDot(color, props.data.length)
|
|
17
17
|
}, key))
|
|
18
18
|
});
|
|
19
19
|
}
|
|
@@ -20,7 +20,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
|
|
|
20
20
|
const total = items.reduce((sum, item) => sum + item.value, 0);
|
|
21
21
|
return /* @__PURE__ */ jsx("div", {
|
|
22
22
|
"data-slot": "bar-list",
|
|
23
|
-
className: cn("@container/bar-list
|
|
23
|
+
className: cn("@container/bar-list grid grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-1.5", className),
|
|
24
24
|
...props,
|
|
25
25
|
children: items.map((item) => {
|
|
26
26
|
const width = max > 0 ? item.value / max * 100 : 0;
|
|
@@ -29,16 +29,22 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
|
|
|
29
29
|
const Label = item.href ? "a" : onItemClick ? "button" : "div";
|
|
30
30
|
return /* @__PURE__ */ jsxs("div", {
|
|
31
31
|
"data-slot": "bar-list-row",
|
|
32
|
-
className: "
|
|
32
|
+
className: "col-span-2 grid grid-cols-subgrid items-center text-sm @max-sm/bar-list:items-start",
|
|
33
33
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
34
34
|
className: "relative flex h-8 min-w-0 flex-1 items-center @max-sm/bar-list:h-auto @max-sm/bar-list:min-h-8 @max-sm/bar-list:items-start @max-sm/bar-list:pb-3",
|
|
35
35
|
children: [
|
|
36
36
|
/* @__PURE__ */ jsx("div", {
|
|
37
|
+
"data-slot": "bar-list-track",
|
|
38
|
+
"aria-hidden": "true",
|
|
39
|
+
className: "bg-muted/60 absolute inset-0 rounded-md @max-sm/bar-list:inset-y-auto @max-sm/bar-list:bottom-0 @max-sm/bar-list:h-1.5"
|
|
40
|
+
}),
|
|
41
|
+
/* @__PURE__ */ jsx("div", {
|
|
42
|
+
"data-slot": "bar-list-bar",
|
|
37
43
|
"aria-hidden": "true",
|
|
38
44
|
className: "absolute inset-y-0 left-0 rounded-md transition-[width] duration-300 @max-sm/bar-list:inset-y-auto @max-sm/bar-list:bottom-0 @max-sm/bar-list:h-1.5",
|
|
39
45
|
style: {
|
|
40
46
|
width: `${width}%`,
|
|
41
|
-
backgroundColor: `color-mix(in oklab, ${color}
|
|
47
|
+
backgroundColor: item.detail ? "color-mix(in oklab, var(--muted-foreground) 20%, transparent)" : `color-mix(in oklab, ${color} 32%, transparent)`
|
|
42
48
|
}
|
|
43
49
|
}),
|
|
44
50
|
/* @__PURE__ */ jsxs(Label, {
|
|
@@ -47,7 +53,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
|
|
|
47
53
|
type: "button",
|
|
48
54
|
onClick: () => onItemClick(item)
|
|
49
55
|
} : {},
|
|
50
|
-
className: cn("relative flex min-w-0 items-center gap-2 px-2 text-left", interactive && "hover:underline"),
|
|
56
|
+
className: cn("text-foreground relative flex min-w-0 items-center gap-2 px-2 text-left", interactive && "hover:underline"),
|
|
51
57
|
children: [item.icon ? /* @__PURE__ */ jsx("span", {
|
|
52
58
|
className: "text-muted-foreground shrink-0 [&>svg]:size-4",
|
|
53
59
|
children: item.icon
|
|
@@ -63,7 +69,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
|
|
|
63
69
|
}) : null
|
|
64
70
|
]
|
|
65
71
|
}), /* @__PURE__ */ jsxs("div", {
|
|
66
|
-
className: "flex
|
|
72
|
+
className: "flex items-center justify-end gap-2 tabular-nums",
|
|
67
73
|
children: [/* @__PURE__ */ jsx("span", {
|
|
68
74
|
className: "font-medium",
|
|
69
75
|
children: format(item.value)
|
|
@@ -39,7 +39,7 @@ function MetricStrip({ title, description, action, items, footer, className, ...
|
|
|
39
39
|
className: "-mt-px -ml-px flex flex-wrap",
|
|
40
40
|
children: items.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
41
41
|
"data-slot": "metric-strip-item",
|
|
42
|
-
className: cn("flex min-w-0 flex-col gap-1 border-t border-l px-4 py-
|
|
42
|
+
className: cn("flex min-w-0 flex-col gap-1.5 border-t border-l px-4 py-4", columns),
|
|
43
43
|
children: [/* @__PURE__ */ jsx("div", {
|
|
44
44
|
className: "truncate text-xs text-muted-foreground",
|
|
45
45
|
children: item.label
|
|
@@ -64,7 +64,7 @@ function MetricStrip({ title, description, action, items, footer, className, ...
|
|
|
64
64
|
data: item.trend,
|
|
65
65
|
variant: "line",
|
|
66
66
|
color: trendColor(item.change),
|
|
67
|
-
className: "h-
|
|
67
|
+
className: "h-9 w-20 shrink-0"
|
|
68
68
|
})]
|
|
69
69
|
})]
|
|
70
70
|
}, item.label))
|
|
@@ -5,9 +5,13 @@ export interface SparklineProps extends React$1.ComponentProps<"div"> {
|
|
|
5
5
|
/** Series to plot, oldest first. */
|
|
6
6
|
data: number[];
|
|
7
7
|
variant?: "area" | "line";
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The last step and the dot on the latest value: a chart slot, or var(--positive) or var(--negative) for a gain or
|
|
10
|
+
* a loss. Defaults to var(--chart-1). The run before it is muted.
|
|
11
|
+
*/
|
|
9
12
|
color?: ChartColor;
|
|
10
13
|
curve?: "monotone" | "linear" | "step";
|
|
14
|
+
/** The run's line; the last step is 2px. */
|
|
11
15
|
strokeWidth?: number;
|
|
12
16
|
/** Animate after hydration and when data changes. Respects reduced motion. */
|
|
13
17
|
animate?: boolean;
|
|
@@ -15,24 +15,41 @@ function getReducedMotionSnapshot() {
|
|
|
15
15
|
function getReducedMotionServerSnapshot() {
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
|
-
function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "
|
|
18
|
+
function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "linear", strokeWidth = 1.25, animate = true, animationDuration = 500, className, ...props }) {
|
|
19
19
|
const id = React$1.useId();
|
|
20
20
|
const reducedMotion = React$1.useSyncExternalStore(subscribeToReducedMotion, getReducedMotionSnapshot, getReducedMotionServerSnapshot);
|
|
21
21
|
const animationActive = animate && !reducedMotion;
|
|
22
22
|
const points = React$1.useMemo(() => data.map((y, i) => ({
|
|
23
23
|
i,
|
|
24
|
-
y
|
|
24
|
+
y,
|
|
25
|
+
now: i >= data.length - 2 ? y : void 0
|
|
25
26
|
})), [data]);
|
|
26
27
|
const margin = {
|
|
27
|
-
top:
|
|
28
|
-
right:
|
|
29
|
-
bottom:
|
|
28
|
+
top: 3,
|
|
29
|
+
right: 3,
|
|
30
|
+
bottom: 3,
|
|
30
31
|
left: 0
|
|
31
32
|
};
|
|
32
33
|
const axis = /* @__PURE__ */ jsx(YAxis, {
|
|
33
34
|
hide: true,
|
|
34
35
|
domain: ["dataMin", "dataMax"]
|
|
35
36
|
});
|
|
37
|
+
const now = /* @__PURE__ */ jsx(Line, {
|
|
38
|
+
className: "sparkline-now",
|
|
39
|
+
type: curve,
|
|
40
|
+
dataKey: "now",
|
|
41
|
+
stroke: color,
|
|
42
|
+
strokeWidth: 2,
|
|
43
|
+
dot: (dot) => dot.index === data.length - 1 ? /* @__PURE__ */ jsx("circle", {
|
|
44
|
+
cx: dot.cx,
|
|
45
|
+
cy: dot.cy,
|
|
46
|
+
r: 3,
|
|
47
|
+
fill: color
|
|
48
|
+
}, "now") : /* @__PURE__ */ jsx("g", {}, dot.index),
|
|
49
|
+
isAnimationActive: animationActive,
|
|
50
|
+
animationBegin: animationDuration,
|
|
51
|
+
animationDuration: 150
|
|
52
|
+
});
|
|
36
53
|
return /* @__PURE__ */ jsx("div", {
|
|
37
54
|
"data-slot": "sparkline",
|
|
38
55
|
"aria-hidden": "true",
|
|
@@ -44,16 +61,20 @@ function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "
|
|
|
44
61
|
children: variant === "line" ? /* @__PURE__ */ jsxs(LineChart, {
|
|
45
62
|
data: points,
|
|
46
63
|
margin,
|
|
47
|
-
children: [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
children: [
|
|
65
|
+
axis,
|
|
66
|
+
/* @__PURE__ */ jsx(Line, {
|
|
67
|
+
type: curve,
|
|
68
|
+
dataKey: "y",
|
|
69
|
+
stroke: "var(--muted-foreground)",
|
|
70
|
+
strokeWidth,
|
|
71
|
+
dot: false,
|
|
72
|
+
isAnimationActive: animationActive,
|
|
73
|
+
animationDuration,
|
|
74
|
+
animationEasing: "ease-out"
|
|
75
|
+
}),
|
|
76
|
+
now
|
|
77
|
+
]
|
|
57
78
|
}) : /* @__PURE__ */ jsxs(AreaChart, {
|
|
58
79
|
data: points,
|
|
59
80
|
margin,
|
|
@@ -68,7 +89,7 @@ function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "
|
|
|
68
89
|
children: [/* @__PURE__ */ jsx("stop", {
|
|
69
90
|
offset: "0%",
|
|
70
91
|
stopColor: color,
|
|
71
|
-
stopOpacity: .
|
|
92
|
+
stopOpacity: .18
|
|
72
93
|
}), /* @__PURE__ */ jsx("stop", {
|
|
73
94
|
offset: "100%",
|
|
74
95
|
stopColor: color,
|
|
@@ -78,14 +99,15 @@ function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "
|
|
|
78
99
|
/* @__PURE__ */ jsx(Area, {
|
|
79
100
|
type: curve,
|
|
80
101
|
dataKey: "y",
|
|
81
|
-
stroke:
|
|
102
|
+
stroke: "var(--muted-foreground)",
|
|
82
103
|
strokeWidth,
|
|
83
104
|
fill: `url(#${id})`,
|
|
84
105
|
dot: false,
|
|
85
106
|
isAnimationActive: animationActive,
|
|
86
107
|
animationDuration,
|
|
87
108
|
animationEasing: "ease-out"
|
|
88
|
-
})
|
|
109
|
+
}),
|
|
110
|
+
now
|
|
89
111
|
]
|
|
90
112
|
})
|
|
91
113
|
})
|
|
@@ -75,8 +75,10 @@ export type CalendarHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
75
75
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
76
76
|
* @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.
|
|
77
77
|
*/
|
|
78
|
-
export declare const CalendarHeatmap: ({ data, weekStart, continuousMonths, hasEmptyColumn, blockSize, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, style, className, children, ...props }: CalendarHeatmapProps) => 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;
|
|
78
|
+
export declare const CalendarHeatmap: ({ data, weekStart, continuousMonths, hasEmptyColumn, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, style, className, children, ...props }: CalendarHeatmapProps) => 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;
|
|
79
79
|
export type CalendarHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
80
|
+
/** The cell the page has picked: drawn with a ring and announced as pressed. */
|
|
81
|
+
selected?: boolean;
|
|
80
82
|
activity: ActivityWithLevel;
|
|
81
83
|
dayIndex: number;
|
|
82
84
|
weekIndex: number;
|
|
@@ -84,7 +86,7 @@ export type CalendarHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
|
84
86
|
onCellClick?: (activity: ActivityWithLevel) => void;
|
|
85
87
|
onCellHover?: (activity: ActivityWithLevel | null) => void;
|
|
86
88
|
};
|
|
87
|
-
export declare function CalendarHeatmapBlock({ ref, activity, dayIndex, weekIndex, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: CalendarHeatmapBlockProps & {
|
|
89
|
+
export declare function CalendarHeatmapBlock({ ref, activity, dayIndex, weekIndex, highlighted, selected, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: CalendarHeatmapBlockProps & {
|
|
88
90
|
ref?: React.RefObject<SVGRectElement | null>;
|
|
89
91
|
}): import("react").JSX.Element;
|
|
90
92
|
export declare namespace CalendarHeatmapBlock {
|
|
@@ -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)
|
|
@@ -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", {
|