react-horizontal-heatmap
Version:
A lightweight React component for rendering a horizontal heatmap. Perfect for timelines, activity charts, or health status indicators. Fully customizable colors, box size, and spacing.
132 lines • 4.2 kB
JavaScript
// src/HorizontalHeatmap.tsx
import { useState, useRef } from "react";
import { Popover, ArrowContainer } from "react-tiny-popover";
import { jsx, jsxs } from "react/jsx-runtime";
var defaultColors = [
"rgb(36, 193, 154)",
// greenish
"rgb(251, 191, 36)",
// yellowish
"rgb(248, 113, 113)"
// reddish
];
var HorizontalHeatmap = ({
data,
boxSize = 40,
gap = 5,
colors = defaultColors,
emptyMessage = "No items available"
}) => {
const [openIndex, setOpenIndex] = useState(null);
const hideTimeoutRef = useRef(null);
const getColor = (value) => {
const max = Math.max(...data.map((d) => d.value), 1);
const index = Math.floor(value / max * (colors.length - 1));
return colors[index];
};
const clearHideTimeout = () => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
};
const startHideTimeout = (callback) => {
clearHideTimeout();
hideTimeoutRef.current = setTimeout(callback, 150);
};
return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap }, children: data.map((d, i) => /* @__PURE__ */ jsx(
Popover,
{
isOpen: openIndex === i,
positions: ["bottom"],
padding: 10,
reposition: true,
onClickOutside: () => setOpenIndex(null),
content: ({
position,
childRect,
popoverRect
}) => /* @__PURE__ */ jsx(
ArrowContainer,
{
position,
childRect,
popoverRect,
arrowColor: "#f5f5f5",
arrowSize: 10,
arrowStyle: { opacity: 1 },
className: "popover-arrow-container",
children: /* @__PURE__ */ jsxs(
"div",
{
style: {
backgroundColor: "#f5f5f5",
color: "#333",
padding: 12,
borderRadius: 6,
minWidth: 180,
fontFamily: "Arial, sans-serif",
boxShadow: "0 4px 12px rgba(0,0,0,0.1)"
},
onMouseEnter: clearHideTimeout,
onMouseLeave: () => startHideTimeout(() => setOpenIndex(null)),
children: [
/* @__PURE__ */ jsx("div", { style: { fontWeight: "bold", marginBottom: 6 }, children: d.time }),
d.items.length > 0 ? /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: d.items.map((item, idx) => /* @__PURE__ */ jsxs(
"li",
{
style: {
display: "flex",
alignItems: "center",
marginBottom: 4
},
children: [
/* @__PURE__ */ jsx("span", { style: { marginRight: 6 }, children: item.icon }),
/* @__PURE__ */ jsx(
"a",
{
href: item.link,
target: "_blank",
rel: "noopener noreferrer",
style: {
color: "#1976d2",
textDecoration: "none"
},
children: item.text
}
)
]
},
idx
)) }) : /* @__PURE__ */ jsx("div", { style: { fontStyle: "italic", opacity: 0.6 }, children: emptyMessage })
]
}
)
}
),
children: /* @__PURE__ */ jsx(
"div",
{
style: {
width: boxSize,
height: boxSize * 16 / 9,
backgroundColor: getColor(d.value),
borderRadius: 4,
cursor: "pointer",
transition: "transform 0.15s ease-in-out"
},
onMouseEnter: () => {
clearHideTimeout();
setOpenIndex(i);
},
onMouseLeave: () => startHideTimeout(() => setOpenIndex(null))
}
)
},
i
)) });
};
export {
HorizontalHeatmap
};
//# sourceMappingURL=index.mjs.map