@x-plat/design-system 0.5.32 → 0.5.34

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/dist/index.cjs CHANGED
@@ -6745,45 +6745,35 @@ var useChartAnimation = (containerRef, dataKey) => {
6745
6745
  prevDataKey.current = dataKey;
6746
6746
  if (prefersReducedMotion()) return;
6747
6747
  setAnimate(false);
6748
- requestAnimationFrame(() => setAnimate(true));
6748
+ requestAnimationFrame(() => {
6749
+ requestAnimationFrame(() => setAnimate(true));
6750
+ });
6749
6751
  }
6750
6752
  }, [dataKey]);
6751
6753
  return animate || prefersReducedMotion();
6752
6754
  };
6755
+ var TOOLTIP_OFFSET = 12;
6753
6756
  var useChartTooltip = (enabled) => {
6754
6757
  const [tooltip, setTooltip] = import_react6.default.useState({
6755
6758
  visible: false,
6756
- x: 0,
6757
- y: 0,
6759
+ clientX: 0,
6760
+ clientY: 0,
6758
6761
  content: ""
6759
6762
  });
6760
6763
  const containerRef = import_react6.default.useRef(null);
6761
6764
  const rafRef = import_react6.default.useRef(0);
6762
6765
  const move = import_react6.default.useCallback((e) => {
6763
6766
  if (!enabled) return;
6764
- const clientX = e.clientX;
6765
- const clientY = e.clientY;
6767
+ const cx = e.clientX;
6768
+ const cy = e.clientY;
6766
6769
  cancelAnimationFrame(rafRef.current);
6767
6770
  rafRef.current = requestAnimationFrame(() => {
6768
- const rect = containerRef.current?.getBoundingClientRect();
6769
- if (!rect) return;
6770
- setTooltip((prev) => ({
6771
- ...prev,
6772
- x: clientX - rect.left,
6773
- y: clientY - rect.top - 12
6774
- }));
6771
+ setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
6775
6772
  });
6776
6773
  }, [enabled]);
6777
6774
  const show = import_react6.default.useCallback((e, content) => {
6778
6775
  if (!enabled) return;
6779
- const rect = containerRef.current?.getBoundingClientRect();
6780
- if (!rect) return;
6781
- setTooltip({
6782
- visible: true,
6783
- x: e.clientX - rect.left,
6784
- y: e.clientY - rect.top - 12,
6785
- content
6786
- });
6776
+ setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
6787
6777
  }, [enabled]);
6788
6778
  const hide = import_react6.default.useCallback(() => {
6789
6779
  cancelAnimationFrame(rafRef.current);
@@ -6817,14 +6807,14 @@ var AxisLabels = import_react6.default.memo(({ labels, count, chartW, height })
6817
6807
  AxisLabels.displayName = "AxisLabels";
6818
6808
  var useCrosshair = (seriesPoints, entries, labels, chartH) => {
6819
6809
  const [activeIndex, setActiveIndex] = import_react6.default.useState(null);
6820
- const [mouseX, setMouseX] = import_react6.default.useState(null);
6821
6810
  const handleMouseMove = import_react6.default.useCallback((e) => {
6822
6811
  const svg = e.currentTarget;
6823
6812
  const rect = svg.getBoundingClientRect();
6824
6813
  const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
6825
- setMouseX(mx);
6826
6814
  if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
6827
6815
  const points = seriesPoints[0];
6816
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
6817
+ const threshold = step / 2;
6828
6818
  let closest = 0;
6829
6819
  let minDist = Math.abs(points[0].x - mx);
6830
6820
  for (let i = 1; i < points.length; i++) {
@@ -6834,11 +6824,10 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
6834
6824
  closest = i;
6835
6825
  }
6836
6826
  }
6837
- setActiveIndex(closest);
6827
+ setActiveIndex(minDist <= threshold ? closest : null);
6838
6828
  }, [seriesPoints]);
6839
6829
  const handleMouseLeave = import_react6.default.useCallback(() => {
6840
6830
  setActiveIndex(null);
6841
- setMouseX(null);
6842
6831
  }, []);
6843
6832
  const tooltipContent = import_react6.default.useMemo(() => {
6844
6833
  if (activeIndex === null) return "";
@@ -6847,7 +6836,13 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
6847
6836
  return p ? `${key}: ${p.v}` : "";
6848
6837
  }).filter(Boolean).join(" / ");
6849
6838
  }, [activeIndex, entries, seriesPoints]);
6850
- return { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent };
6839
+ const getTooltipAt = import_react6.default.useCallback((idx) => {
6840
+ return entries.map(([key], di) => {
6841
+ const p = seriesPoints[di]?.[idx];
6842
+ return p ? `${key}: ${p.v}` : "";
6843
+ }).filter(Boolean).join(" / ");
6844
+ }, [entries, seriesPoints]);
6845
+ return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
6851
6846
  };
6852
6847
  var LineChart = import_react6.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
6853
6848
  const entries = import_react6.default.useMemo(() => Object.entries(data), [data]);
@@ -6868,33 +6863,19 @@ var LineChart = import_react6.default.memo(({ data, labels, width, height, anima
6868
6863
  ),
6869
6864
  [entries, count, chartW, chartH, maxVal]
6870
6865
  );
6871
- const lineRefs = import_react6.default.useRef([]);
6872
6866
  const clipRef = import_react6.default.useRef(null);
6873
- const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
6867
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
6874
6868
  import_react6.default.useEffect(() => {
6875
- if (!animate) return;
6876
- lineRefs.current.forEach((el) => {
6877
- if (!el) return;
6878
- const len = el.getTotalLength();
6879
- el.style.strokeDasharray = `${len}`;
6880
- el.style.strokeDashoffset = `${len}`;
6881
- requestAnimationFrame(() => {
6882
- el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
6883
- el.style.strokeDashoffset = "0";
6884
- });
6869
+ if (!animate || !clipRef.current) return;
6870
+ clipRef.current.setAttribute("width", "0");
6871
+ requestAnimationFrame(() => {
6872
+ if (clipRef.current) {
6873
+ clipRef.current.style.transition = "width 1200ms ease-out 200ms";
6874
+ clipRef.current.setAttribute("width", `${width}`);
6875
+ }
6885
6876
  });
6886
- if (clipRef.current) {
6887
- clipRef.current.setAttribute("width", "0");
6888
- requestAnimationFrame(() => {
6889
- if (clipRef.current) {
6890
- clipRef.current.style.transition = "width 1200ms ease-out 200ms";
6891
- clipRef.current.setAttribute("width", `${width}`);
6892
- }
6893
- });
6894
- }
6895
- }, [animate, seriesPoints, width]);
6896
- const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
6897
- const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
6877
+ }, [animate, width]);
6878
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
6898
6879
  const lineClipId = "line-area-clip";
6899
6880
  return /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)(
6900
6881
  "svg",
@@ -6903,7 +6884,26 @@ var LineChart = import_react6.default.memo(({ data, labels, width, height, anima
6903
6884
  className: "chart-svg",
6904
6885
  onMouseMove: (e) => {
6905
6886
  handleMouseMove(e);
6906
- onMove(e);
6887
+ const svg = e.currentTarget;
6888
+ const rect = svg.getBoundingClientRect();
6889
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
6890
+ const points = seriesPoints[0];
6891
+ if (!points || points.length === 0) return;
6892
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
6893
+ let closest = 0;
6894
+ let minDist = Math.abs(points[0].x - mx);
6895
+ for (let i = 1; i < points.length; i++) {
6896
+ const dist = Math.abs(points[i].x - mx);
6897
+ if (dist < minDist) {
6898
+ minDist = dist;
6899
+ closest = i;
6900
+ }
6901
+ }
6902
+ if (minDist <= step / 2) {
6903
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
6904
+ } else {
6905
+ onLeave();
6906
+ }
6907
6907
  },
6908
6908
  onMouseLeave: () => {
6909
6909
  handleMouseLeave();
@@ -6926,26 +6926,10 @@ var LineChart = import_react6.default.memo(({ data, labels, width, height, anima
6926
6926
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
6927
6927
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
6928
6928
  ] }) }),
6929
- /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6930
- "path",
6931
- {
6932
- d: areaD,
6933
- fill: `url(#${gradientId})`,
6934
- clipPath: animate ? `url(#${lineClipId})` : void 0
6935
- }
6936
- ),
6937
- /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6938
- "polyline",
6939
- {
6940
- ref: (el) => {
6941
- lineRefs.current[di] = el;
6942
- },
6943
- points: polyPoints,
6944
- fill: "none",
6945
- stroke: color,
6946
- strokeWidth: "2"
6947
- }
6948
- ),
6929
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
6930
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("path", { d: areaD, fill: `url(#${gradientId})` }),
6931
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
6932
+ ] }),
6949
6933
  activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6950
6934
  "circle",
6951
6935
  {
@@ -6958,21 +6942,16 @@ var LineChart = import_react6.default.memo(({ data, labels, width, height, anima
6958
6942
  )
6959
6943
  ] }, di);
6960
6944
  }),
6961
- guideX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6945
+ activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6962
6946
  "line",
6963
6947
  {
6964
- x1: guideX,
6948
+ x1: activeX,
6965
6949
  y1: PADDING.top,
6966
- x2: guideX,
6950
+ x2: activeX,
6967
6951
  y2: PADDING.top + chartH,
6968
6952
  className: "chart-crosshair"
6969
6953
  }
6970
6954
  ),
6971
- activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-crosshair-label", children: [
6972
- labels[activeIndex],
6973
- " \u2014 ",
6974
- tooltipContent
6975
- ] }) }),
6976
6955
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
6977
6956
  "rect",
6978
6957
  {
@@ -7008,33 +6987,19 @@ var CurveChart = import_react6.default.memo(({ data, labels, width, height, anim
7008
6987
  ),
7009
6988
  [entries, count, chartW, chartH, maxVal]
7010
6989
  );
7011
- const lineRefs = import_react6.default.useRef([]);
7012
6990
  const curveClipRef = import_react6.default.useRef(null);
7013
- const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
6991
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
7014
6992
  import_react6.default.useEffect(() => {
7015
- if (!animate) return;
7016
- lineRefs.current.forEach((el) => {
7017
- if (!el) return;
7018
- const len = el.getTotalLength();
7019
- el.style.strokeDasharray = `${len}`;
7020
- el.style.strokeDashoffset = `${len}`;
7021
- requestAnimationFrame(() => {
7022
- el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
7023
- el.style.strokeDashoffset = "0";
7024
- });
6993
+ if (!animate || !curveClipRef.current) return;
6994
+ curveClipRef.current.setAttribute("width", "0");
6995
+ requestAnimationFrame(() => {
6996
+ if (curveClipRef.current) {
6997
+ curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
6998
+ curveClipRef.current.setAttribute("width", `${width}`);
6999
+ }
7025
7000
  });
7026
- if (curveClipRef.current) {
7027
- curveClipRef.current.setAttribute("width", "0");
7028
- requestAnimationFrame(() => {
7029
- if (curveClipRef.current) {
7030
- curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
7031
- curveClipRef.current.setAttribute("width", `${width}`);
7032
- }
7033
- });
7034
- }
7035
- }, [animate, seriesPoints, width]);
7036
- const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
7037
- const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
7001
+ }, [animate, width]);
7002
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
7038
7003
  const curveClipId = "curve-area-clip";
7039
7004
  return /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)(
7040
7005
  "svg",
@@ -7043,7 +7008,26 @@ var CurveChart = import_react6.default.memo(({ data, labels, width, height, anim
7043
7008
  className: "chart-svg",
7044
7009
  onMouseMove: (e) => {
7045
7010
  handleMouseMove(e);
7046
- onMove(e);
7011
+ const svg = e.currentTarget;
7012
+ const rect = svg.getBoundingClientRect();
7013
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
7014
+ const points = seriesPoints[0];
7015
+ if (!points || points.length === 0) return;
7016
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
7017
+ let closest = 0;
7018
+ let minDist = Math.abs(points[0].x - mx);
7019
+ for (let i = 1; i < points.length; i++) {
7020
+ const dist = Math.abs(points[i].x - mx);
7021
+ if (dist < minDist) {
7022
+ minDist = dist;
7023
+ closest = i;
7024
+ }
7025
+ }
7026
+ if (minDist <= step / 2) {
7027
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
7028
+ } else {
7029
+ onLeave();
7030
+ }
7047
7031
  },
7048
7032
  onMouseLeave: () => {
7049
7033
  handleMouseLeave();
@@ -7066,26 +7050,10 @@ var CurveChart = import_react6.default.memo(({ data, labels, width, height, anim
7066
7050
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
7067
7051
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
7068
7052
  ] }) }),
7069
- /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7070
- "path",
7071
- {
7072
- d: areaPath,
7073
- fill: `url(#${gradientId})`,
7074
- clipPath: animate ? `url(#${curveClipId})` : void 0
7075
- }
7076
- ),
7077
- /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7078
- "path",
7079
- {
7080
- ref: (el) => {
7081
- lineRefs.current[di] = el;
7082
- },
7083
- d: linePath,
7084
- fill: "none",
7085
- stroke: color,
7086
- strokeWidth: "2"
7087
- }
7088
- ),
7053
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
7054
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("path", { d: areaPath, fill: `url(#${gradientId})` }),
7055
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
7056
+ ] }),
7089
7057
  activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7090
7058
  "circle",
7091
7059
  {
@@ -7098,21 +7066,16 @@ var CurveChart = import_react6.default.memo(({ data, labels, width, height, anim
7098
7066
  )
7099
7067
  ] }, di);
7100
7068
  }),
7101
- guideX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7069
+ activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7102
7070
  "line",
7103
7071
  {
7104
- x1: guideX,
7072
+ x1: activeX,
7105
7073
  y1: PADDING.top,
7106
- x2: guideX,
7074
+ x2: activeX,
7107
7075
  y2: PADDING.top + chartH,
7108
7076
  className: "chart-crosshair"
7109
7077
  }
7110
7078
  ),
7111
- activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-crosshair-label", children: [
7112
- labels[activeIndex],
7113
- " \u2014 ",
7114
- tooltipContent
7115
- ] }) }),
7116
7079
  /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7117
7080
  "rect",
7118
7081
  {
@@ -7291,30 +7254,70 @@ var PieDonutChart = import_react6.default.memo(
7291
7254
  }
7292
7255
  );
7293
7256
  PieDonutChart.displayName = "PieDonutChart";
7294
- var TooltipBubble = ({ x, y, containerWidth, children }) => {
7257
+ var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
7295
7258
  const ref = import_react6.default.useRef(null);
7296
- const [adjustedX, setAdjustedX] = import_react6.default.useState(x);
7297
- import_react6.default.useEffect(() => {
7259
+ const [pos, setPos] = import_react6.default.useState({ left: 0, top: 0 });
7260
+ import_react6.default.useLayoutEffect(() => {
7298
7261
  const el = ref.current;
7299
7262
  if (!el) return;
7300
7263
  const w = el.offsetWidth;
7301
- const half = w / 2;
7302
- const margin = 8;
7303
- let nx = x;
7304
- if (x - half < margin) nx = half + margin;
7305
- else if (x + half > containerWidth - margin) nx = containerWidth - half - margin;
7306
- setAdjustedX(nx);
7307
- }, [x, containerWidth]);
7264
+ const h = el.offsetHeight;
7265
+ const vw = window.innerWidth;
7266
+ let left = clientX + TOOLTIP_OFFSET;
7267
+ let top = clientY - h - TOOLTIP_OFFSET;
7268
+ if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
7269
+ if (top < 8) top = clientY + TOOLTIP_OFFSET;
7270
+ if (left < 8) left = 8;
7271
+ setPos({ left, top });
7272
+ }, [clientX, clientY]);
7308
7273
  return /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(
7309
7274
  "div",
7310
7275
  {
7311
7276
  ref,
7312
- className: "chart-tooltip",
7313
- style: { left: adjustedX, top: y },
7277
+ className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
7278
+ style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
7314
7279
  children
7315
7280
  }
7316
7281
  );
7317
7282
  };
7283
+ var ChartLegend = ({ data, labels, type }) => {
7284
+ const entries = Object.entries(data);
7285
+ if (type === "pie" || type === "doughnut") {
7286
+ const values = entries.flatMap(([, v]) => v);
7287
+ const total = values.reduce((a, b) => a + b, 0) || 1;
7288
+ const firstKey = entries[0]?.[0] ?? "";
7289
+ const colorOffset = hashString(firstKey);
7290
+ return /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("div", { className: "chart-legend", children: values.map((v, i) => {
7291
+ const pct = Math.round(v / total * 100);
7292
+ const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
7293
+ return /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-legend-item", children: [
7294
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
7295
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-legend-text", children: [
7296
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
7297
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("span", { className: "chart-legend-value", children: [
7298
+ v.toLocaleString(),
7299
+ "(",
7300
+ pct,
7301
+ "%)"
7302
+ ] })
7303
+ ] })
7304
+ ] }, i);
7305
+ }) });
7306
+ }
7307
+ return /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("div", { className: "chart-legend", children: entries.map(([key], di) => {
7308
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
7309
+ const color = palette[2];
7310
+ const values = entries[di][1];
7311
+ const sum = values.reduce((a, b) => a + b, 0);
7312
+ return /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-legend-item", children: [
7313
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
7314
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsxs)("div", { className: "chart-legend-text", children: [
7315
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("span", { className: "chart-legend-label", children: key }),
7316
+ /* @__PURE__ */ (0, import_jsx_runtime307.jsx)("span", { className: "chart-legend-value", children: sum.toLocaleString() })
7317
+ ] })
7318
+ ] }, di);
7319
+ }) });
7320
+ };
7318
7321
  var Chart = import_react6.default.memo((props) => {
7319
7322
  const { type, data, labels, tooltip: showTooltip = true } = props;
7320
7323
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
@@ -7330,7 +7333,8 @@ var Chart = import_react6.default.memo((props) => {
7330
7333
  ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
7331
7334
  ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
7332
7335
  ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
7333
- tooltip.visible && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
7336
+ ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(ChartLegend, { data: stableData, labels: stableLabels, type }),
7337
+ tooltip.content && /* @__PURE__ */ (0, import_jsx_runtime307.jsx)(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content })
7334
7338
  ] });
7335
7339
  });
7336
7340
  Chart.displayName = "Chart";
package/dist/index.css CHANGED
@@ -2000,9 +2000,9 @@
2000
2000
  opacity: 1;
2001
2001
  }
2002
2002
  .lib-xplat-chart .chart-crosshair {
2003
- stroke: var(--semantic-border-subtle);
2003
+ stroke: var(--semantic-border-strong);
2004
2004
  stroke-width: 1;
2005
- stroke-dasharray: 4 2;
2005
+ stroke-dasharray: 4 3;
2006
2006
  pointer-events: none;
2007
2007
  }
2008
2008
  .lib-xplat-chart .chart-point-active {
@@ -2018,18 +2018,24 @@
2018
2018
  overflow: visible;
2019
2019
  }
2020
2020
  .lib-xplat-chart .chart-tooltip {
2021
- position: absolute;
2022
- transform: translate(-50%, -100%);
2023
- padding: var(--spacing-space-2) var(--spacing-space-3);
2021
+ padding: var(--spacing-space-3);
2024
2022
  background-color: var(--semantic-surface-neutral-strong);
2025
2023
  color: var(--semantic-text-inverse);
2026
2024
  font-size: 12px;
2025
+ line-height: 18px;
2027
2026
  font-weight: 500;
2028
2027
  border-radius: var(--spacing-radius-md);
2029
- white-space: nowrap;
2028
+ max-width: 240px;
2030
2029
  pointer-events: none;
2031
- z-index: 10;
2032
- animation: chart-tooltip-in 150ms ease-out;
2030
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
2031
+ }
2032
+ .lib-xplat-chart .chart-tooltip.chart-tooltip-show {
2033
+ opacity: 1;
2034
+ animation: chart-tooltip-in 120ms ease-out;
2035
+ }
2036
+ .lib-xplat-chart .chart-tooltip.chart-tooltip-hide {
2037
+ opacity: 0;
2038
+ animation: chart-tooltip-out 80ms ease-in;
2033
2039
  }
2034
2040
  .lib-xplat-chart .chart-bar-animate {
2035
2041
  animation: chart-bar-grow 800ms ease-out both;
@@ -2041,6 +2047,38 @@
2041
2047
  .lib-xplat-chart .chart-area[style*=animationDelay] {
2042
2048
  animation: chart-fade-in 800ms ease-out both;
2043
2049
  }
2050
+ .lib-xplat-chart .chart-legend {
2051
+ display: flex;
2052
+ flex-wrap: wrap;
2053
+ justify-content: center;
2054
+ gap: var(--spacing-space-4);
2055
+ padding: var(--spacing-space-3) 0;
2056
+ }
2057
+ .lib-xplat-chart .chart-legend-item {
2058
+ display: flex;
2059
+ align-items: flex-start;
2060
+ gap: var(--spacing-space-2);
2061
+ }
2062
+ .lib-xplat-chart .chart-legend-dot {
2063
+ flex-shrink: 0;
2064
+ width: 10px;
2065
+ height: 10px;
2066
+ border-radius: 50%;
2067
+ margin-top: 3px;
2068
+ }
2069
+ .lib-xplat-chart .chart-legend-text {
2070
+ display: flex;
2071
+ flex-direction: column;
2072
+ }
2073
+ .lib-xplat-chart .chart-legend-label {
2074
+ font-size: 12px;
2075
+ color: var(--semantic-text-muted);
2076
+ }
2077
+ .lib-xplat-chart .chart-legend-value {
2078
+ font-size: 14px;
2079
+ font-weight: 600;
2080
+ color: var(--semantic-text-strong);
2081
+ }
2044
2082
  @keyframes chart-bar-grow {
2045
2083
  from {
2046
2084
  transform: scaleY(0);
@@ -2060,11 +2098,17 @@
2060
2098
  @keyframes chart-tooltip-in {
2061
2099
  from {
2062
2100
  opacity: 0;
2063
- transform: translate(-50%, -90%);
2064
2101
  }
2065
2102
  to {
2066
2103
  opacity: 1;
2067
- transform: translate(-50%, -100%);
2104
+ }
2105
+ }
2106
+ @keyframes chart-tooltip-out {
2107
+ from {
2108
+ opacity: 1;
2109
+ }
2110
+ to {
2111
+ opacity: 0;
2068
2112
  }
2069
2113
  }
2070
2114
  @media (prefers-reduced-motion: reduce) {