@siyahi/ui 0.1.9-next.3 → 0.1.9-next.30
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 +170 -0
- package/README.md +94 -177
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/agents/examples/accordion.tsx +39 -0
- package/agents/examples/alert.tsx +24 -0
- package/agents/examples/app-sidebar.tsx +40 -0
- package/agents/examples/avatar.tsx +24 -0
- package/agents/examples/badge.tsx +26 -0
- package/agents/examples/bar-list-labels.tsx +22 -0
- package/agents/examples/bar-list-sides.tsx +40 -0
- package/agents/examples/bar-list.tsx +24 -0
- package/agents/examples/breadcrumb.tsx +28 -0
- package/agents/examples/button-states.tsx +23 -0
- package/agents/examples/button.tsx +21 -0
- package/agents/examples/calendar.tsx +11 -0
- package/agents/examples/callout.tsx +33 -0
- package/agents/examples/card.tsx +36 -0
- package/agents/examples/change-bar.tsx +104 -0
- package/agents/examples/change-grid-money.tsx +36 -0
- package/agents/examples/change-grid.tsx +42 -0
- package/agents/examples/chart-area-log.tsx +45 -0
- package/agents/examples/chart-area.tsx +39 -0
- package/agents/examples/chart-bar-flows.tsx +32 -0
- package/agents/examples/chart-bar-hourly.tsx +28 -0
- package/agents/examples/chart-bar-losses.tsx +29 -0
- package/agents/examples/chart-bar-teams.tsx +72 -0
- package/agents/examples/chart-bar-years.tsx +40 -0
- package/agents/examples/chart-bar.tsx +56 -0
- package/agents/examples/chart-category-ticks.tsx +50 -0
- package/agents/examples/chart-line-events.tsx +47 -0
- package/agents/examples/chart-line-forecast.tsx +36 -0
- package/agents/examples/chart-line-log.tsx +39 -0
- package/agents/examples/chart-line-quiet.tsx +114 -0
- package/agents/examples/chart-line-timeline.tsx +41 -0
- package/agents/examples/chart-line.tsx +57 -0
- package/agents/examples/chart-panels.tsx +94 -0
- package/agents/examples/code-block.tsx +40 -0
- package/agents/examples/collapsible.tsx +18 -0
- package/agents/examples/command.tsx +29 -0
- package/agents/examples/context-menu.tsx +17 -0
- package/agents/examples/copy-button.tsx +8 -0
- package/agents/examples/data-bar.tsx +108 -0
- package/agents/examples/data-table-compact.tsx +96 -0
- package/agents/examples/data-table-filters.tsx +103 -0
- package/agents/examples/data-table.tsx +99 -0
- package/agents/examples/delta.tsx +18 -0
- package/agents/examples/dialog.tsx +28 -0
- package/agents/examples/distribution-bar-budget.tsx +30 -0
- package/agents/examples/distribution-bar-tug.tsx +74 -0
- package/agents/examples/drawdown-chart.tsx +35 -0
- package/agents/examples/dropdown-menu.tsx +31 -0
- package/agents/examples/field.tsx +108 -0
- package/agents/examples/formatters.tsx +42 -0
- package/agents/examples/heatmap-calendar-signed.tsx +67 -0
- package/agents/examples/heatmap-calendar.tsx +62 -0
- package/agents/examples/heatmap-status.tsx +61 -0
- package/agents/examples/heatmap-weekday.tsx +72 -0
- package/agents/examples/histogram-chart.tsx +28 -0
- package/agents/examples/input-states.tsx +23 -0
- package/agents/examples/input.tsx +17 -0
- package/agents/examples/local-time.tsx +14 -0
- package/agents/examples/metric-strip.tsx +46 -0
- package/agents/examples/overview-dashboard.tsx +180 -0
- package/agents/examples/page-hero.tsx +38 -0
- package/agents/examples/page-section.tsx +52 -0
- package/agents/examples/popover.tsx +23 -0
- package/agents/examples/price-chart.tsx +32 -0
- package/agents/examples/progress-tone.tsx +29 -0
- package/agents/examples/progress.tsx +10 -0
- package/agents/examples/range-position-table.tsx +101 -0
- package/agents/examples/range-position.tsx +55 -0
- package/agents/examples/scatter-chart-quadrants.tsx +63 -0
- package/agents/examples/scatter-chart-scales.tsx +46 -0
- package/agents/examples/scatter-chart.tsx +78 -0
- package/agents/examples/scroll-area.tsx +22 -0
- package/agents/examples/section-cards-select.tsx +28 -0
- package/agents/examples/section-cards-trend.tsx +32 -0
- package/agents/examples/section-cards.tsx +48 -0
- package/agents/examples/select.tsx +27 -0
- package/agents/examples/separator.tsx +13 -0
- package/agents/examples/sheet.tsx +15 -0
- package/agents/examples/skeleton.tsx +11 -0
- package/agents/examples/slider.tsx +5 -0
- package/agents/examples/slope-chart-market.tsx +33 -0
- package/agents/examples/slope-chart.tsx +26 -0
- package/agents/examples/stale-blocks.tsx +56 -0
- package/agents/examples/surfaces.tsx +116 -0
- package/agents/examples/table.tsx +60 -0
- package/agents/examples/tabs.tsx +16 -0
- package/agents/examples/target-rows.tsx +74 -0
- package/agents/examples/toggle-group.tsx +12 -0
- package/agents/examples/toggle.tsx +11 -0
- package/agents/examples/tooltip.tsx +13 -0
- package/agents/examples/treemap.tsx +93 -0
- package/agents/examples/waterfall-chart-stores.tsx +27 -0
- package/agents/examples/waterfall-chart.tsx +37 -0
- package/agents/manifest.json +1391 -0
- package/agents/report-rules.json +7 -0
- package/dist/components/registry/charts/area-chart-card.d.ts +7 -3
- package/dist/components/registry/charts/area-chart-card.js +56 -10
- package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
- package/dist/components/registry/charts/bar-chart-card.js +20 -22
- package/dist/components/registry/charts/line-chart-card.d.ts +3 -3
- package/dist/components/registry/charts/line-chart-card.js +44 -9
- package/dist/components/registry/dashboard-01/section-cards.d.ts +6 -0
- package/dist/components/registry/dashboard-01/section-cards.js +51 -35
- package/dist/components/registry/dashboardcn/bar-list.js +17 -10
- package/dist/components/registry/dashboardcn/distribution-bar.js +6 -4
- package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
- package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
- package/dist/components/registry/dashboardcn/sparkline.d.ts +22 -0
- package/dist/components/registry/dashboardcn/sparkline.js +117 -0
- package/dist/components/registry/heatmap/calendar-heatmap.d.ts +11 -3
- package/dist/components/registry/heatmap/calendar-heatmap.js +164 -45
- package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
- package/dist/components/registry/heatmap/status-heatmap.js +115 -54
- package/dist/components/registry/heatmap/weekday-heatmap.d.ts +9 -3
- package/dist/components/registry/heatmap/weekday-heatmap.js +83 -53
- package/dist/components/registry/niko-table/core/data-table-context.d.ts +18 -1
- package/dist/components/registry/niko-table/core/data-table-context.js +7 -1
- package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
- package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
- package/dist/components/registry/niko-table/core/data-table-structure.js +17 -12
- package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
- package/dist/components/registry/niko-table/core/data-table.js +5 -1
- package/dist/components/registry/niko-table/filters/table-faceted-filter.js +5 -5
- package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
- package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
- package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
- package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
- package/dist/components/registry/niko-table/lib/styles.js +3 -3
- package/dist/components/registry/niko-table/types/index.d.ts +9 -0
- package/dist/components/registry/niko-table/ui/table.js +2 -2
- package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
- package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
- package/dist/components/registry/sidebar-07/nav-main.d.ts +2 -1
- package/dist/components/registry/sidebar-07/nav-main.js +16 -3
- package/dist/components/siyahi/callout.d.ts +17 -0
- package/dist/components/siyahi/callout.js +34 -0
- package/dist/components/siyahi/change-bar.d.ts +21 -0
- package/dist/components/siyahi/change-bar.js +35 -0
- package/dist/components/siyahi/change-grid.d.ts +31 -0
- package/dist/components/siyahi/change-grid.js +74 -0
- package/dist/components/siyahi/data-bar.d.ts +19 -0
- package/dist/components/siyahi/data-bar.js +29 -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/drawdown-chart.d.ts +34 -0
- package/dist/components/siyahi/drawdown-chart.js +102 -0
- package/dist/components/siyahi/histogram-chart.d.ts +57 -0
- package/dist/components/siyahi/histogram-chart.js +134 -0
- package/dist/components/siyahi/page-hero.d.ts +23 -0
- package/dist/components/siyahi/page-hero.js +43 -0
- package/dist/components/siyahi/page-section.d.ts +4 -1
- package/dist/components/siyahi/page-section.js +6 -3
- package/dist/components/siyahi/page-toc.d.ts +20 -0
- package/dist/components/siyahi/page-toc.js +76 -0
- package/dist/components/siyahi/panel-grid.d.ts +24 -0
- package/dist/components/siyahi/panel-grid.js +37 -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/scale-toggle.d.ts +32 -0
- package/dist/components/siyahi/scale-toggle.js +61 -0
- package/dist/components/siyahi/scatter-chart.d.ts +69 -0
- package/dist/components/siyahi/scatter-chart.js +395 -0
- package/dist/components/siyahi/siyahi-provider.d.ts +15 -2
- package/dist/components/siyahi/siyahi-provider.js +17 -4
- package/dist/components/siyahi/slope-chart.d.ts +35 -0
- package/dist/components/siyahi/slope-chart.js +159 -0
- package/dist/components/siyahi/stale-note.d.ts +9 -0
- package/dist/components/siyahi/stale-note.js +12 -0
- package/dist/components/siyahi/status-alert.d.ts +15 -0
- package/dist/components/siyahi/status-alert.js +29 -0
- package/dist/components/siyahi/status-badge.d.ts +15 -0
- package/dist/components/siyahi/status-badge.js +24 -0
- package/dist/components/siyahi/target-rows.d.ts +34 -0
- package/dist/components/siyahi/target-rows.js +97 -0
- package/dist/components/siyahi/tone-progress.d.ts +1 -1
- package/dist/components/siyahi/tone-progress.js +1 -1
- package/dist/components/siyahi/treemap.d.ts +8 -5
- package/dist/components/siyahi/treemap.js +80 -34
- package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
- package/dist/components/siyahi/waterfall-chart.js +147 -0
- package/dist/components/ui/accordion.js +5 -5
- package/dist/components/ui/alert.js +2 -2
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +2 -2
- package/dist/components/ui/button.js +10 -10
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/card.js +4 -4
- package/dist/components/ui/chart.js +1 -1
- package/dist/components/ui/code-block.js +13 -12
- package/dist/components/ui/command.js +7 -6
- package/dist/components/ui/context-menu.js +7 -7
- package/dist/components/ui/dialog.js +5 -5
- package/dist/components/ui/dropdown-menu.js +8 -8
- package/dist/components/ui/field.d.ts +26 -0
- package/dist/components/ui/field.js +109 -0
- package/dist/components/ui/input-group.js +11 -11
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.js +13 -0
- package/dist/components/ui/popover.js +3 -3
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/select.js +6 -6
- package/dist/components/ui/sheet.js +5 -5
- package/dist/components/ui/sidebar.js +17 -18
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +2 -2
- package/dist/components/ui/table.js +2 -2
- package/dist/components/ui/tabs.js +2 -2
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/toggle-group.js +2 -2
- package/dist/components/ui/toggle.js +4 -4
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/index.d.ts +35 -8
- package/dist/index.js +30 -4
- package/dist/lib/chart-frame.d.ts +51 -7
- package/dist/lib/chart-frame.js +390 -133
- package/dist/lib/chart-marks.d.ts +83 -0
- package/dist/lib/chart-marks.js +216 -0
- package/dist/lib/chart-series.d.ts +42 -10
- package/dist/lib/compact-columns.d.ts +19 -0
- package/dist/lib/compact-columns.js +55 -0
- package/dist/lib/fit-text.d.ts +6 -0
- package/dist/lib/fit-text.js +11 -0
- package/dist/lib/format.d.ts +8 -3
- package/dist/lib/format.js +55 -17
- package/dist/lib/heat.d.ts +7 -0
- package/dist/lib/heat.js +14 -0
- package/dist/lib/heatmap-navigation.d.ts +8 -0
- package/dist/lib/heatmap-navigation.js +49 -0
- package/dist/lib/scatter.d.ts +88 -0
- package/dist/lib/scatter.js +179 -0
- package/dist/lib/ticks.d.ts +27 -0
- package/dist/lib/ticks.js +67 -0
- package/dist/lib/tone.d.ts +36 -0
- package/dist/lib/tone.js +35 -0
- package/dist/lib/treemap.d.ts +0 -5
- package/dist/lib/treemap.js +1 -11
- package/dist/lib/use-element-width.d.ts +5 -0
- package/dist/lib/use-element-width.js +15 -0
- package/package.json +5 -1
- package/registry.lock.json +16 -0
- package/report.css +63 -47
- package/styles/siyahi.css +708 -55
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React$1 from "react";
|
|
4
|
+
import { Area, AreaChart, Line, LineChart, ResponsiveContainer, YAxis } from "recharts";
|
|
5
|
+
//#region src/components/registry/dashboardcn/sparkline.tsx
|
|
6
|
+
const reducedMotionQuery = "(prefers-reduced-motion: reduce)";
|
|
7
|
+
function subscribeToReducedMotion(onChange) {
|
|
8
|
+
const query = window.matchMedia(reducedMotionQuery);
|
|
9
|
+
query.addEventListener("change", onChange);
|
|
10
|
+
return () => query.removeEventListener("change", onChange);
|
|
11
|
+
}
|
|
12
|
+
function getReducedMotionSnapshot() {
|
|
13
|
+
return window.matchMedia(reducedMotionQuery).matches;
|
|
14
|
+
}
|
|
15
|
+
function getReducedMotionServerSnapshot() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "linear", strokeWidth = 1.25, animate = true, animationDuration = 500, className, ...props }) {
|
|
19
|
+
const id = React$1.useId();
|
|
20
|
+
const reducedMotion = React$1.useSyncExternalStore(subscribeToReducedMotion, getReducedMotionSnapshot, getReducedMotionServerSnapshot);
|
|
21
|
+
const animationActive = animate && !reducedMotion;
|
|
22
|
+
const points = React$1.useMemo(() => data.map((y, i) => ({
|
|
23
|
+
i,
|
|
24
|
+
y,
|
|
25
|
+
now: i >= data.length - 2 ? y : void 0
|
|
26
|
+
})), [data]);
|
|
27
|
+
const margin = {
|
|
28
|
+
top: 3,
|
|
29
|
+
right: 3,
|
|
30
|
+
bottom: 3,
|
|
31
|
+
left: 0
|
|
32
|
+
};
|
|
33
|
+
const axis = /* @__PURE__ */ jsx(YAxis, {
|
|
34
|
+
hide: true,
|
|
35
|
+
domain: ["dataMin", "dataMax"]
|
|
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
|
+
});
|
|
53
|
+
return /* @__PURE__ */ jsx("div", {
|
|
54
|
+
"data-slot": "sparkline",
|
|
55
|
+
"aria-hidden": "true",
|
|
56
|
+
className: cn("h-10 w-full", className),
|
|
57
|
+
...props,
|
|
58
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
59
|
+
width: "100%",
|
|
60
|
+
height: "100%",
|
|
61
|
+
children: variant === "line" ? /* @__PURE__ */ jsxs(LineChart, {
|
|
62
|
+
data: points,
|
|
63
|
+
margin,
|
|
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
|
+
]
|
|
78
|
+
}) : /* @__PURE__ */ jsxs(AreaChart, {
|
|
79
|
+
data: points,
|
|
80
|
+
margin,
|
|
81
|
+
children: [
|
|
82
|
+
axis,
|
|
83
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", {
|
|
84
|
+
id,
|
|
85
|
+
x1: "0",
|
|
86
|
+
y1: "0",
|
|
87
|
+
x2: "0",
|
|
88
|
+
y2: "1",
|
|
89
|
+
children: [/* @__PURE__ */ jsx("stop", {
|
|
90
|
+
offset: "0%",
|
|
91
|
+
stopColor: color,
|
|
92
|
+
stopOpacity: .18
|
|
93
|
+
}), /* @__PURE__ */ jsx("stop", {
|
|
94
|
+
offset: "100%",
|
|
95
|
+
stopColor: color,
|
|
96
|
+
stopOpacity: 0
|
|
97
|
+
})]
|
|
98
|
+
}) }),
|
|
99
|
+
/* @__PURE__ */ jsx(Area, {
|
|
100
|
+
type: curve,
|
|
101
|
+
dataKey: "y",
|
|
102
|
+
stroke: "var(--muted-foreground)",
|
|
103
|
+
strokeWidth,
|
|
104
|
+
fill: `url(#${id})`,
|
|
105
|
+
dot: false,
|
|
106
|
+
isAnimationActive: animationActive,
|
|
107
|
+
animationDuration,
|
|
108
|
+
animationEasing: "ease-out"
|
|
109
|
+
}),
|
|
110
|
+
now
|
|
111
|
+
]
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Sparkline };
|
|
@@ -32,6 +32,10 @@ export type CalendarHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
32
32
|
blockAspectRatio?: number;
|
|
33
33
|
levels?: number;
|
|
34
34
|
isNormalized?: boolean;
|
|
35
|
+
/** Colour each day green or red by its sign, deeper the larger it is, for a daily gain or loss. */
|
|
36
|
+
signed?: boolean;
|
|
37
|
+
/** With signed: the size that takes the deepest step. Defaults to the largest day's. */
|
|
38
|
+
range?: number;
|
|
35
39
|
colors?: ColorConfig;
|
|
36
40
|
locale?: Locale;
|
|
37
41
|
labels?: Labels;
|
|
@@ -75,8 +79,10 @@ export type CalendarHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
75
79
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
76
80
|
* @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
81
|
*/
|
|
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;
|
|
82
|
+
export declare const CalendarHeatmap: ({ data, weekStart, continuousMonths, hasEmptyColumn, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, signed, range, 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
83
|
export type CalendarHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
84
|
+
/** The cell the page has picked: drawn with a ring and announced as pressed. */
|
|
85
|
+
selected?: boolean;
|
|
80
86
|
activity: ActivityWithLevel;
|
|
81
87
|
dayIndex: number;
|
|
82
88
|
weekIndex: number;
|
|
@@ -84,7 +90,7 @@ export type CalendarHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
|
84
90
|
onCellClick?: (activity: ActivityWithLevel) => void;
|
|
85
91
|
onCellHover?: (activity: ActivityWithLevel | null) => void;
|
|
86
92
|
};
|
|
87
|
-
export declare function CalendarHeatmapBlock({ ref, activity, dayIndex, weekIndex, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: CalendarHeatmapBlockProps & {
|
|
93
|
+
export declare function CalendarHeatmapBlock({ ref, activity, dayIndex, weekIndex, highlighted, selected, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: CalendarHeatmapBlockProps & {
|
|
88
94
|
ref?: React.RefObject<SVGRectElement | null>;
|
|
89
95
|
}): import("react").JSX.Element;
|
|
90
96
|
export declare namespace CalendarHeatmapBlock {
|
|
@@ -125,10 +131,12 @@ export type CalendarHeatmapLegendProps = Omit<HTMLAttributes<HTMLFieldSetElement
|
|
|
125
131
|
less?: string;
|
|
126
132
|
more?: string;
|
|
127
133
|
};
|
|
134
|
+
/** A signed heatmap's figures in the legend's scale line, such as useFormat().money. */
|
|
135
|
+
valueFormatter?: (value: number) => string;
|
|
128
136
|
children?: (props: {
|
|
129
137
|
level: number;
|
|
130
138
|
}) => ReactNode;
|
|
131
139
|
};
|
|
132
|
-
export declare const CalendarHeatmapLegend: ({ labels: labelsProp, className, children, ...props }: CalendarHeatmapLegendProps) => import("react").JSX.Element;
|
|
140
|
+
export declare const CalendarHeatmapLegend: ({ labels: labelsProp, valueFormatter, className, children, ...props }: CalendarHeatmapLegendProps) => import("react").JSX.Element;
|
|
133
141
|
//#endregion
|
|
134
142
|
export {};
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { useElementWidth } from "../../../lib/use-element-width.js";
|
|
3
|
+
import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
|
|
4
|
+
import { heat, heatFill } from "../../../lib/heat.js";
|
|
2
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, createContext, use, useLayoutEffect, useMemo } from "react";
|
|
4
7
|
import { differenceInCalendarDays, eachDayOfInterval, format, formatISO, getDay, getMonth, getYear, nextDay, parseISO, subWeeks } from "date-fns";
|
|
5
8
|
//#region src/components/registry/heatmap/calendar-heatmap.tsx
|
|
6
9
|
const colorStepCount = (levels, isNormalized) => Math.max(1, isNormalized ? levels : levels - 1);
|
|
7
|
-
const getLevelFill = (level, levels, isNormalized,
|
|
10
|
+
const getLevelFill = (level, levels, isNormalized, colors) => {
|
|
8
11
|
const emptyColor = colors?.empty ?? "var(--color-secondary)";
|
|
9
12
|
const scaleColor = colors?.scale ?? "var(--color-chart-1)";
|
|
10
13
|
if (level === 0) return emptyColor;
|
|
11
14
|
const steps = colorStepCount(levels, isNormalized);
|
|
12
|
-
|
|
13
|
-
return `color-mix(in oklch, ${scaleColor} ${highlighted ? Math.round(opacity * .6) : opacity}%, transparent)`;
|
|
15
|
+
return `color-mix(in oklch, ${scaleColor} ${steps === 1 ? 100 : Math.round(20 + (level - 1) * 80 / (steps - 1))}%, transparent)`;
|
|
14
16
|
};
|
|
15
17
|
const calculateLevel = (value, minValue, maxValue, levels, isNormalized) => {
|
|
16
18
|
const steps = colorStepCount(levels, isNormalized);
|
|
@@ -38,8 +40,22 @@ const generateWeekdayLabels = (locale) => {
|
|
|
38
40
|
return date.toLocaleString("en-US", { weekday: "short" });
|
|
39
41
|
});
|
|
40
42
|
};
|
|
43
|
+
/** How a signed day is drawn: its heat tint, the edge that carries its sign's colour, and whether it is round. */
|
|
44
|
+
const signedMark = (value) => {
|
|
45
|
+
const known = Number.isFinite(value) ? value : 0;
|
|
46
|
+
return {
|
|
47
|
+
stroke: known > 0 ? "var(--positive)" : known < 0 ? "var(--negative)" : "var(--input)",
|
|
48
|
+
round: known < 0
|
|
49
|
+
};
|
|
50
|
+
};
|
|
41
51
|
const EMPTY_STYLE = {};
|
|
42
52
|
const LABEL_MARGIN = 8;
|
|
53
|
+
const WEEKDAY_LABEL_WIDTH = 40;
|
|
54
|
+
const STROKE_PADDING = 3;
|
|
55
|
+
const LEGEND_SWATCH = 12;
|
|
56
|
+
const MIN_FITTED_BLOCK = 10;
|
|
57
|
+
const MAX_FITTED_BLOCK = 20;
|
|
58
|
+
const DEFAULT_BLOCK = 12;
|
|
43
59
|
const CalendarHeatmapContext = createContext(null);
|
|
44
60
|
const useCalendarHeatmap = () => {
|
|
45
61
|
const context = use(CalendarHeatmapContext);
|
|
@@ -131,26 +147,18 @@ const groupContinuous = (activities, weekStart = 0) => {
|
|
|
131
147
|
const padded = [...new Array(leadingPad).fill(void 0), ...normalizedActivities];
|
|
132
148
|
const numberOfWeeks = Math.ceil(padded.length / 7);
|
|
133
149
|
const allWeeks = new Array(numberOfWeeks).fill(void 0).map((_, i) => padded.slice(i * 7, i * 7 + 7));
|
|
134
|
-
|
|
135
|
-
|
|
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,
|
|
150
|
+
return [{
|
|
151
|
+
year: getYear(firstDate),
|
|
145
152
|
startMonth: 0,
|
|
146
|
-
weeks
|
|
147
|
-
}
|
|
153
|
+
weeks: allWeeks
|
|
154
|
+
}];
|
|
148
155
|
};
|
|
149
|
-
const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
150
|
-
|
|
156
|
+
const getMonthLabels = (weeks, monthNames = generateMonthLabels(), yearTicks = false) => {
|
|
157
|
+
const ticks = weeks.reduce((labels, week, weekIndex) => {
|
|
151
158
|
const firstActivity = week.find((activity) => activity !== void 0);
|
|
152
159
|
if (!firstActivity) return labels;
|
|
153
|
-
const
|
|
160
|
+
const date = parseISO(firstActivity.date);
|
|
161
|
+
const month = monthNames[getMonth(date)];
|
|
154
162
|
if (!month) {
|
|
155
163
|
const monthName = new Date(firstActivity.date).toLocaleString("en-US", { month: "short" });
|
|
156
164
|
throw new Error(`Unexpected error: undefined month label for ${monthName}.`);
|
|
@@ -158,7 +166,8 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
158
166
|
const prevLabel = labels.at(-1);
|
|
159
167
|
if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) return labels.concat({
|
|
160
168
|
weekIndex,
|
|
161
|
-
label: month
|
|
169
|
+
label: month,
|
|
170
|
+
year: getMonth(date) === 0 ? getYear(date) : void 0
|
|
162
171
|
});
|
|
163
172
|
return labels;
|
|
164
173
|
}, []).filter(({ weekIndex }, index, labels) => {
|
|
@@ -167,6 +176,22 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
167
176
|
if (index === labels.length - 1) return weeks.slice(weekIndex).length >= minWeeks;
|
|
168
177
|
return true;
|
|
169
178
|
});
|
|
179
|
+
if (!yearTicks) return ticks;
|
|
180
|
+
const hasJanuary = ticks.some(({ year }) => year !== void 0);
|
|
181
|
+
return ticks.map((tick, index) => {
|
|
182
|
+
if (tick.year !== void 0) return {
|
|
183
|
+
...tick,
|
|
184
|
+
label: String(tick.year)
|
|
185
|
+
};
|
|
186
|
+
if (index === 0 && !hasJanuary) {
|
|
187
|
+
const week = weeks[tick.weekIndex]?.find((activity) => activity !== void 0);
|
|
188
|
+
return week ? {
|
|
189
|
+
...tick,
|
|
190
|
+
label: `${tick.label} ${getYear(parseISO(week.date))}`
|
|
191
|
+
} : tick;
|
|
192
|
+
}
|
|
193
|
+
return tick;
|
|
194
|
+
});
|
|
170
195
|
};
|
|
171
196
|
/**
|
|
172
197
|
* Calendar Heatmap
|
|
@@ -201,8 +226,9 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels()) => {
|
|
|
201
226
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
202
227
|
* @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
228
|
*/
|
|
204
|
-
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize
|
|
229
|
+
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize: blockSizeProp, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, signed = false, range, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, style = EMPTY_STYLE, className, children, ...props }) => {
|
|
205
230
|
const levels = Math.max(1, levelsProp);
|
|
231
|
+
const [rootRef, rootWidth] = useElementWidth();
|
|
206
232
|
const dataWithLevels = useMemo(() => {
|
|
207
233
|
if (data.length === 0) return [];
|
|
208
234
|
const maxCount = data.reduce((max, d) => Math.max(max, d.value), 1);
|
|
@@ -223,6 +249,15 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
223
249
|
continuousMonths
|
|
224
250
|
]);
|
|
225
251
|
const weeks = useMemo(() => yearRows.flatMap((r) => r.weeks), [yearRows]);
|
|
252
|
+
const signedRange = useMemo(() => {
|
|
253
|
+
if (!signed) return null;
|
|
254
|
+
if (range !== void 0 && Number.isFinite(range) && range > 0) return range;
|
|
255
|
+
return Math.max(0, ...data.map((d) => Number.isFinite(d.value) ? Math.abs(d.value) : 0));
|
|
256
|
+
}, [
|
|
257
|
+
signed,
|
|
258
|
+
range,
|
|
259
|
+
data
|
|
260
|
+
]);
|
|
226
261
|
const labels = useMemo(() => ({
|
|
227
262
|
months: generateMonthLabels(locale),
|
|
228
263
|
weekdays: generateWeekdayLabels(locale),
|
|
@@ -235,6 +270,8 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
235
270
|
const labelHeight = fontSize + LABEL_MARGIN;
|
|
236
271
|
const year = data.length > 0 ? getYear(parseISO(data[0].date)) : (/* @__PURE__ */ new Date()).getFullYear();
|
|
237
272
|
const totalCount = typeof totalCountProp === "number" ? totalCountProp : dataWithLevels.reduce((sum, activity) => sum + activity.value, 0);
|
|
273
|
+
const fitted = (rootWidth - WEEKDAY_LABEL_WIDTH - 6 + blockMargin) / Math.max(weeks.length, 1) - blockMargin;
|
|
274
|
+
const blockSize = blockSizeProp ?? (rootWidth > 0 ? Math.min(MAX_FITTED_BLOCK, Math.max(MIN_FITTED_BLOCK, fitted)) / blockAspectRatio : DEFAULT_BLOCK);
|
|
238
275
|
const blockWidth = blockSize * blockAspectRatio;
|
|
239
276
|
const width = weeks.length * (blockWidth + blockMargin) - blockMargin;
|
|
240
277
|
const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;
|
|
@@ -260,7 +297,8 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
260
297
|
hasEmptyColumn,
|
|
261
298
|
continuousMonths,
|
|
262
299
|
locale,
|
|
263
|
-
colors
|
|
300
|
+
colors,
|
|
301
|
+
signedRange
|
|
264
302
|
}), [
|
|
265
303
|
dataWithLevels,
|
|
266
304
|
weeks,
|
|
@@ -283,14 +321,16 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
283
321
|
hasEmptyColumn,
|
|
284
322
|
continuousMonths,
|
|
285
323
|
locale,
|
|
286
|
-
colors
|
|
324
|
+
colors,
|
|
325
|
+
signedRange
|
|
287
326
|
]);
|
|
288
327
|
if (data.length === 0) return emptyState ? emptyState : null;
|
|
289
328
|
return /* @__PURE__ */ jsx(CalendarHeatmapContext, {
|
|
290
329
|
value: contextValue,
|
|
291
330
|
children: /* @__PURE__ */ jsx("div", {
|
|
331
|
+
ref: rootRef,
|
|
292
332
|
"data-slot": "calendar-heatmap",
|
|
293
|
-
className: cn("flex
|
|
333
|
+
className: cn("flex flex-col gap-2", blockSizeProp === void 0 ? "w-full" : "w-max max-w-full", className),
|
|
294
334
|
style: {
|
|
295
335
|
fontSize,
|
|
296
336
|
...style
|
|
@@ -300,29 +340,34 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
300
340
|
})
|
|
301
341
|
});
|
|
302
342
|
};
|
|
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();
|
|
343
|
+
const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
344
|
+
const { blockSize, blockWidth, blockMargin, blockRadius, labelHeight, labels, levels, isNormalized, colors, signedRange } = useCalendarHeatmap();
|
|
305
345
|
const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
|
|
346
|
+
const mark = signedRange === null ? null : signedMark(activity.value);
|
|
306
347
|
const ariaLabel = (labels.cellLabel ?? "{{date}}: {{value}} contributions").replace("{{date}}", activity.date).replace("{{value}}", String(activity.value));
|
|
307
348
|
return /* @__PURE__ */ jsx("rect", {
|
|
308
349
|
ref,
|
|
309
350
|
"data-slot": "calendar-heatmap-block",
|
|
310
351
|
role: onCellClick ? "button" : "img",
|
|
352
|
+
"aria-pressed": onCellClick ? selected : void 0,
|
|
311
353
|
tabIndex: onCellClick ? 0 : -1,
|
|
312
354
|
"aria-label": ariaLabel,
|
|
313
|
-
className: cn("
|
|
355
|
+
className: cn("hover:stroke-foreground hover:stroke-1", highlighted && "stroke-foreground stroke-1", selected && "stroke-foreground stroke-2 hover:stroke-2", onCellClick && "cursor-pointer", className),
|
|
314
356
|
"data-value": activity.value,
|
|
315
357
|
"data-date": activity.date,
|
|
316
358
|
"data-level": level,
|
|
317
359
|
"data-highlighted": highlighted || void 0,
|
|
360
|
+
"data-selected": selected || void 0,
|
|
318
361
|
height: blockSize,
|
|
319
|
-
rx: blockRadius,
|
|
320
|
-
ry: blockRadius,
|
|
362
|
+
rx: mark?.round ? blockSize / 2 : blockRadius,
|
|
363
|
+
ry: mark?.round ? blockSize / 2 : blockRadius,
|
|
321
364
|
width: blockWidth,
|
|
365
|
+
stroke: mark?.stroke,
|
|
366
|
+
strokeWidth: mark ? 1 : void 0,
|
|
322
367
|
x: (blockWidth + blockMargin) * weekIndex,
|
|
323
368
|
y: labelHeight + (blockSize + blockMargin) * dayIndex,
|
|
324
369
|
style: {
|
|
325
|
-
fill: getLevelFill(level, levels, isNormalized,
|
|
370
|
+
fill: signedRange === null ? getLevelFill(level, levels, isNormalized, colors) : heat(activity.value, signedRange),
|
|
326
371
|
...styleProp
|
|
327
372
|
},
|
|
328
373
|
onClick: (event) => {
|
|
@@ -349,17 +394,18 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
349
394
|
};
|
|
350
395
|
CalendarHeatmapBlock.displayName = "CalendarHeatmapBlock";
|
|
351
396
|
const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = false, hideYearLabels = false, className, labelClassName, yearClassName, children, renderYearFooter, ...props }) => {
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
397
|
+
const navigation = useHeatmapNavigation();
|
|
398
|
+
const { yearRows, blockSize, blockWidth, blockMargin, labels, labelHeight, fontSize, weekStart, continuousMonths } = useCalendarHeatmap();
|
|
399
|
+
const weekdayLabelWidth = hideWeekdayLabels ? 0 : WEEKDAY_LABEL_WIDTH;
|
|
400
|
+
const strokePadding = STROKE_PADDING;
|
|
355
401
|
const rowData = useMemo(() => {
|
|
356
402
|
return yearRows.map((yearRow) => {
|
|
357
403
|
return {
|
|
358
404
|
yearRow,
|
|
359
405
|
width: yearRow.weeks.length * (blockWidth + blockMargin) - blockMargin,
|
|
360
406
|
height: labelHeight + (blockSize + blockMargin) * 7 - blockMargin,
|
|
361
|
-
monthLabels: getMonthLabels(yearRow.weeks, labels.months),
|
|
362
|
-
yearTotalCount:
|
|
407
|
+
monthLabels: getMonthLabels(yearRow.weeks, labels.months, continuousMonths),
|
|
408
|
+
yearTotalCount: yearRow.weeks.flat().reduce((sum, activity) => sum + (activity?.value ?? 0), 0)
|
|
363
409
|
};
|
|
364
410
|
});
|
|
365
411
|
}, [
|
|
@@ -369,15 +415,28 @@ const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = fals
|
|
|
369
415
|
blockMargin,
|
|
370
416
|
labelHeight,
|
|
371
417
|
labels.months,
|
|
372
|
-
|
|
418
|
+
continuousMonths
|
|
373
419
|
]);
|
|
374
420
|
const totalWidth = weekdayLabelWidth + Math.max(...rowData.map((r) => r.width)) + 6;
|
|
421
|
+
const scroller = navigation.ref;
|
|
422
|
+
useLayoutEffect(() => {
|
|
423
|
+
if (continuousMonths && scroller.current) scroller.current.scrollLeft = scroller.current.scrollWidth;
|
|
424
|
+
}, [
|
|
425
|
+
continuousMonths,
|
|
426
|
+
scroller,
|
|
427
|
+
totalWidth
|
|
428
|
+
]);
|
|
375
429
|
return /* @__PURE__ */ jsx("div", {
|
|
376
430
|
"data-slot": "calendar-heatmap-body",
|
|
431
|
+
ref: navigation.ref,
|
|
377
432
|
className: cn("flex max-w-full flex-col gap-6 overflow-x-auto overflow-y-hidden py-4", className),
|
|
378
433
|
...props,
|
|
434
|
+
onKeyDown: (event) => {
|
|
435
|
+
props.onKeyDown?.(event);
|
|
436
|
+
if (!event.defaultPrevented) navigation.onKeyDown(event);
|
|
437
|
+
},
|
|
379
438
|
children: rowData.map(({ yearRow, height, monthLabels, yearTotalCount }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
380
|
-
!hideYearLabels && /* @__PURE__ */ jsx("div", {
|
|
439
|
+
!hideYearLabels && !continuousMonths && /* @__PURE__ */ jsx("div", {
|
|
381
440
|
className: cn("mb-2 text-muted-foreground", yearClassName),
|
|
382
441
|
children: yearRow.year
|
|
383
442
|
}),
|
|
@@ -457,8 +516,14 @@ const CalendarHeatmapStat = ({ compute, label, className, children, ...props })
|
|
|
457
516
|
children: template.replace("{{value}}", String(value)).replace("{{year}}", String(year))
|
|
458
517
|
});
|
|
459
518
|
};
|
|
460
|
-
const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
|
|
461
|
-
const { levels, isNormalized,
|
|
519
|
+
const CalendarHeatmapLegend = ({ labels: labelsProp, valueFormatter, className, children, ...props }) => {
|
|
520
|
+
const { levels, isNormalized, blockRadius, colors, labels, signedRange } = useCalendarHeatmap();
|
|
521
|
+
if (signedRange !== null) return /* @__PURE__ */ jsx(SignedLegend, {
|
|
522
|
+
labels: labelsProp,
|
|
523
|
+
valueFormatter,
|
|
524
|
+
className,
|
|
525
|
+
...props
|
|
526
|
+
});
|
|
462
527
|
const lessLabel = labelsProp?.less ?? "Less";
|
|
463
528
|
const moreLabel = labelsProp?.more ?? "More";
|
|
464
529
|
const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
|
|
@@ -475,15 +540,15 @@ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...pro
|
|
|
475
540
|
legendLevels.map((level) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({ level }) }, `legend-level-${level}`) : /* @__PURE__ */ jsx("svg", {
|
|
476
541
|
role: "img",
|
|
477
542
|
"aria-label": (labels.legendLevelLabel ?? "{{level}} contributions").replace("{{level}}", String(level)),
|
|
478
|
-
height:
|
|
479
|
-
width:
|
|
543
|
+
height: LEGEND_SWATCH,
|
|
544
|
+
width: LEGEND_SWATCH,
|
|
480
545
|
children: /* @__PURE__ */ jsx("rect", {
|
|
481
546
|
"data-level": level,
|
|
482
|
-
height:
|
|
547
|
+
height: LEGEND_SWATCH,
|
|
483
548
|
rx: blockRadius,
|
|
484
549
|
ry: blockRadius,
|
|
485
|
-
width:
|
|
486
|
-
style: { fill: getLevelFill(level, levels, isNormalized,
|
|
550
|
+
width: LEGEND_SWATCH,
|
|
551
|
+
style: { fill: getLevelFill(level, levels, isNormalized, colors) }
|
|
487
552
|
})
|
|
488
553
|
}, `legend-level-${level}`)),
|
|
489
554
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -493,5 +558,59 @@ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...pro
|
|
|
493
558
|
]
|
|
494
559
|
});
|
|
495
560
|
};
|
|
561
|
+
/** A signed heatmap's scale: round loss steps deepest first, no change, square gain steps, and what they span. */
|
|
562
|
+
const SignedLegend = ({ labels: labelsProp, valueFormatter = String, className, ...props }) => {
|
|
563
|
+
const { blockRadius, labels, signedRange } = useCalendarHeatmap();
|
|
564
|
+
const steps = Array.from({ length: 5 }, (_, i) => i + 1);
|
|
565
|
+
const swatch = (key, fill, value) => {
|
|
566
|
+
const { stroke, round } = signedMark(value);
|
|
567
|
+
const corner = round ? 11 / 2 : blockRadius;
|
|
568
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
569
|
+
"aria-hidden": "true",
|
|
570
|
+
height: LEGEND_SWATCH,
|
|
571
|
+
width: LEGEND_SWATCH,
|
|
572
|
+
children: /* @__PURE__ */ jsx("rect", {
|
|
573
|
+
x: .5,
|
|
574
|
+
y: .5,
|
|
575
|
+
height: 11,
|
|
576
|
+
width: 11,
|
|
577
|
+
rx: corner,
|
|
578
|
+
ry: corner,
|
|
579
|
+
stroke,
|
|
580
|
+
style: { fill }
|
|
581
|
+
})
|
|
582
|
+
}, key);
|
|
583
|
+
};
|
|
584
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
585
|
+
"data-slot": "calendar-heatmap-legend",
|
|
586
|
+
"aria-label": labels.legendLabel ?? "Activity intensity legend",
|
|
587
|
+
className: cn("ml-auto flex flex-col items-end gap-1 text-muted-foreground", className),
|
|
588
|
+
...props,
|
|
589
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
590
|
+
className: "flex items-center gap-1",
|
|
591
|
+
children: [
|
|
592
|
+
/* @__PURE__ */ jsx("span", {
|
|
593
|
+
className: "mr-1 font-medium text-xs",
|
|
594
|
+
children: labelsProp?.less ?? "Loss"
|
|
595
|
+
}),
|
|
596
|
+
steps.toReversed().map((step) => swatch(`loss-${step}`, heatFill("negative", step), -1)),
|
|
597
|
+
swatch("none", heat(null), 0),
|
|
598
|
+
steps.map((step) => swatch(`gain-${step}`, heatFill("positive", step), 1)),
|
|
599
|
+
/* @__PURE__ */ jsx("span", {
|
|
600
|
+
className: "ml-1 font-medium text-xs",
|
|
601
|
+
children: labelsProp?.more ?? "Gain"
|
|
602
|
+
})
|
|
603
|
+
]
|
|
604
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
605
|
+
"data-slot": "calendar-heatmap-legend-scale",
|
|
606
|
+
className: "text-xs",
|
|
607
|
+
children: [
|
|
608
|
+
"Deepest at ±",
|
|
609
|
+
valueFormatter(signedRange ?? 0),
|
|
610
|
+
". Round days lost, square days gained; grey days had no change."
|
|
611
|
+
]
|
|
612
|
+
})]
|
|
613
|
+
});
|
|
614
|
+
};
|
|
496
615
|
//#endregion
|
|
497
616
|
export { CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBody, CalendarHeatmapFooter, CalendarHeatmapLegend, CalendarHeatmapStat };
|
|
@@ -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 {
|