@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.
@@ -152,45 +152,35 @@ var useChartAnimation = (containerRef, dataKey) => {
152
152
  prevDataKey.current = dataKey;
153
153
  if (prefersReducedMotion()) return;
154
154
  setAnimate(false);
155
- requestAnimationFrame(() => setAnimate(true));
155
+ requestAnimationFrame(() => {
156
+ requestAnimationFrame(() => setAnimate(true));
157
+ });
156
158
  }
157
159
  }, [dataKey]);
158
160
  return animate || prefersReducedMotion();
159
161
  };
162
+ var TOOLTIP_OFFSET = 12;
160
163
  var useChartTooltip = (enabled) => {
161
164
  const [tooltip, setTooltip] = import_react.default.useState({
162
165
  visible: false,
163
- x: 0,
164
- y: 0,
166
+ clientX: 0,
167
+ clientY: 0,
165
168
  content: ""
166
169
  });
167
170
  const containerRef = import_react.default.useRef(null);
168
171
  const rafRef = import_react.default.useRef(0);
169
172
  const move = import_react.default.useCallback((e) => {
170
173
  if (!enabled) return;
171
- const clientX = e.clientX;
172
- const clientY = e.clientY;
174
+ const cx = e.clientX;
175
+ const cy = e.clientY;
173
176
  cancelAnimationFrame(rafRef.current);
174
177
  rafRef.current = requestAnimationFrame(() => {
175
- const rect = containerRef.current?.getBoundingClientRect();
176
- if (!rect) return;
177
- setTooltip((prev) => ({
178
- ...prev,
179
- x: clientX - rect.left,
180
- y: clientY - rect.top - 12
181
- }));
178
+ setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
182
179
  });
183
180
  }, [enabled]);
184
181
  const show = import_react.default.useCallback((e, content) => {
185
182
  if (!enabled) return;
186
- const rect = containerRef.current?.getBoundingClientRect();
187
- if (!rect) return;
188
- setTooltip({
189
- visible: true,
190
- x: e.clientX - rect.left,
191
- y: e.clientY - rect.top - 12,
192
- content
193
- });
183
+ setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
194
184
  }, [enabled]);
195
185
  const hide = import_react.default.useCallback(() => {
196
186
  cancelAnimationFrame(rafRef.current);
@@ -224,14 +214,14 @@ var AxisLabels = import_react.default.memo(({ labels, count, chartW, height }) =
224
214
  AxisLabels.displayName = "AxisLabels";
225
215
  var useCrosshair = (seriesPoints, entries, labels, chartH) => {
226
216
  const [activeIndex, setActiveIndex] = import_react.default.useState(null);
227
- const [mouseX, setMouseX] = import_react.default.useState(null);
228
217
  const handleMouseMove = import_react.default.useCallback((e) => {
229
218
  const svg = e.currentTarget;
230
219
  const rect = svg.getBoundingClientRect();
231
220
  const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
232
- setMouseX(mx);
233
221
  if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
234
222
  const points = seriesPoints[0];
223
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
224
+ const threshold = step / 2;
235
225
  let closest = 0;
236
226
  let minDist = Math.abs(points[0].x - mx);
237
227
  for (let i = 1; i < points.length; i++) {
@@ -241,11 +231,10 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
241
231
  closest = i;
242
232
  }
243
233
  }
244
- setActiveIndex(closest);
234
+ setActiveIndex(minDist <= threshold ? closest : null);
245
235
  }, [seriesPoints]);
246
236
  const handleMouseLeave = import_react.default.useCallback(() => {
247
237
  setActiveIndex(null);
248
- setMouseX(null);
249
238
  }, []);
250
239
  const tooltipContent = import_react.default.useMemo(() => {
251
240
  if (activeIndex === null) return "";
@@ -254,7 +243,13 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
254
243
  return p ? `${key}: ${p.v}` : "";
255
244
  }).filter(Boolean).join(" / ");
256
245
  }, [activeIndex, entries, seriesPoints]);
257
- return { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent };
246
+ const getTooltipAt = import_react.default.useCallback((idx) => {
247
+ return entries.map(([key], di) => {
248
+ const p = seriesPoints[di]?.[idx];
249
+ return p ? `${key}: ${p.v}` : "";
250
+ }).filter(Boolean).join(" / ");
251
+ }, [entries, seriesPoints]);
252
+ return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
258
253
  };
259
254
  var LineChart = import_react.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
260
255
  const entries = import_react.default.useMemo(() => Object.entries(data), [data]);
@@ -275,33 +270,19 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
275
270
  ),
276
271
  [entries, count, chartW, chartH, maxVal]
277
272
  );
278
- const lineRefs = import_react.default.useRef([]);
279
273
  const clipRef = import_react.default.useRef(null);
280
- const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
274
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
281
275
  import_react.default.useEffect(() => {
282
- if (!animate) return;
283
- lineRefs.current.forEach((el) => {
284
- if (!el) return;
285
- const len = el.getTotalLength();
286
- el.style.strokeDasharray = `${len}`;
287
- el.style.strokeDashoffset = `${len}`;
288
- requestAnimationFrame(() => {
289
- el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
290
- el.style.strokeDashoffset = "0";
291
- });
276
+ if (!animate || !clipRef.current) return;
277
+ clipRef.current.setAttribute("width", "0");
278
+ requestAnimationFrame(() => {
279
+ if (clipRef.current) {
280
+ clipRef.current.style.transition = "width 1200ms ease-out 200ms";
281
+ clipRef.current.setAttribute("width", `${width}`);
282
+ }
292
283
  });
293
- if (clipRef.current) {
294
- clipRef.current.setAttribute("width", "0");
295
- requestAnimationFrame(() => {
296
- if (clipRef.current) {
297
- clipRef.current.style.transition = "width 1200ms ease-out 200ms";
298
- clipRef.current.setAttribute("width", `${width}`);
299
- }
300
- });
301
- }
302
- }, [animate, seriesPoints, width]);
303
- const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
304
- const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
284
+ }, [animate, width]);
285
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
305
286
  const lineClipId = "line-area-clip";
306
287
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
307
288
  "svg",
@@ -310,7 +291,26 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
310
291
  className: "chart-svg",
311
292
  onMouseMove: (e) => {
312
293
  handleMouseMove(e);
313
- onMove(e);
294
+ const svg = e.currentTarget;
295
+ const rect = svg.getBoundingClientRect();
296
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
297
+ const points = seriesPoints[0];
298
+ if (!points || points.length === 0) return;
299
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
300
+ let closest = 0;
301
+ let minDist = Math.abs(points[0].x - mx);
302
+ for (let i = 1; i < points.length; i++) {
303
+ const dist = Math.abs(points[i].x - mx);
304
+ if (dist < minDist) {
305
+ minDist = dist;
306
+ closest = i;
307
+ }
308
+ }
309
+ if (minDist <= step / 2) {
310
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
311
+ } else {
312
+ onLeave();
313
+ }
314
314
  },
315
315
  onMouseLeave: () => {
316
316
  handleMouseLeave();
@@ -333,26 +333,10 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
333
333
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
334
334
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
335
335
  ] }) }),
336
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
337
- "path",
338
- {
339
- d: areaD,
340
- fill: `url(#${gradientId})`,
341
- clipPath: animate ? `url(#${lineClipId})` : void 0
342
- }
343
- ),
344
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
345
- "polyline",
346
- {
347
- ref: (el) => {
348
- lineRefs.current[di] = el;
349
- },
350
- points: polyPoints,
351
- fill: "none",
352
- stroke: color,
353
- strokeWidth: "2"
354
- }
355
- ),
336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: areaD, fill: `url(#${gradientId})` }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
339
+ ] }),
356
340
  activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
357
341
  "circle",
358
342
  {
@@ -365,21 +349,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
365
349
  )
366
350
  ] }, di);
367
351
  }),
368
- guideX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
352
+ activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
369
353
  "line",
370
354
  {
371
- x1: guideX,
355
+ x1: activeX,
372
356
  y1: PADDING.top,
373
- x2: guideX,
357
+ x2: activeX,
374
358
  y2: PADDING.top + chartH,
375
359
  className: "chart-crosshair"
376
360
  }
377
361
  ),
378
- activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-crosshair-label", children: [
379
- labels[activeIndex],
380
- " \u2014 ",
381
- tooltipContent
382
- ] }) }),
383
362
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
384
363
  "rect",
385
364
  {
@@ -415,33 +394,19 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
415
394
  ),
416
395
  [entries, count, chartW, chartH, maxVal]
417
396
  );
418
- const lineRefs = import_react.default.useRef([]);
419
397
  const curveClipRef = import_react.default.useRef(null);
420
- const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
398
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
421
399
  import_react.default.useEffect(() => {
422
- if (!animate) return;
423
- lineRefs.current.forEach((el) => {
424
- if (!el) return;
425
- const len = el.getTotalLength();
426
- el.style.strokeDasharray = `${len}`;
427
- el.style.strokeDashoffset = `${len}`;
428
- requestAnimationFrame(() => {
429
- el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
430
- el.style.strokeDashoffset = "0";
431
- });
400
+ if (!animate || !curveClipRef.current) return;
401
+ curveClipRef.current.setAttribute("width", "0");
402
+ requestAnimationFrame(() => {
403
+ if (curveClipRef.current) {
404
+ curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
405
+ curveClipRef.current.setAttribute("width", `${width}`);
406
+ }
432
407
  });
433
- if (curveClipRef.current) {
434
- curveClipRef.current.setAttribute("width", "0");
435
- requestAnimationFrame(() => {
436
- if (curveClipRef.current) {
437
- curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
438
- curveClipRef.current.setAttribute("width", `${width}`);
439
- }
440
- });
441
- }
442
- }, [animate, seriesPoints, width]);
443
- const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
444
- const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
408
+ }, [animate, width]);
409
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
445
410
  const curveClipId = "curve-area-clip";
446
411
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
447
412
  "svg",
@@ -450,7 +415,26 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
450
415
  className: "chart-svg",
451
416
  onMouseMove: (e) => {
452
417
  handleMouseMove(e);
453
- onMove(e);
418
+ const svg = e.currentTarget;
419
+ const rect = svg.getBoundingClientRect();
420
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
421
+ const points = seriesPoints[0];
422
+ if (!points || points.length === 0) return;
423
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
424
+ let closest = 0;
425
+ let minDist = Math.abs(points[0].x - mx);
426
+ for (let i = 1; i < points.length; i++) {
427
+ const dist = Math.abs(points[i].x - mx);
428
+ if (dist < minDist) {
429
+ minDist = dist;
430
+ closest = i;
431
+ }
432
+ }
433
+ if (minDist <= step / 2) {
434
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
435
+ } else {
436
+ onLeave();
437
+ }
454
438
  },
455
439
  onMouseLeave: () => {
456
440
  handleMouseLeave();
@@ -473,26 +457,10 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
473
457
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
474
458
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
475
459
  ] }) }),
476
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
477
- "path",
478
- {
479
- d: areaPath,
480
- fill: `url(#${gradientId})`,
481
- clipPath: animate ? `url(#${curveClipId})` : void 0
482
- }
483
- ),
484
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
485
- "path",
486
- {
487
- ref: (el) => {
488
- lineRefs.current[di] = el;
489
- },
490
- d: linePath,
491
- fill: "none",
492
- stroke: color,
493
- strokeWidth: "2"
494
- }
495
- ),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: areaPath, fill: `url(#${gradientId})` }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
463
+ ] }),
496
464
  activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
497
465
  "circle",
498
466
  {
@@ -505,21 +473,16 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
505
473
  )
506
474
  ] }, di);
507
475
  }),
508
- guideX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
476
+ activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
509
477
  "line",
510
478
  {
511
- x1: guideX,
479
+ x1: activeX,
512
480
  y1: PADDING.top,
513
- x2: guideX,
481
+ x2: activeX,
514
482
  y2: PADDING.top + chartH,
515
483
  className: "chart-crosshair"
516
484
  }
517
485
  ),
518
- activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-crosshair-label", children: [
519
- labels[activeIndex],
520
- " \u2014 ",
521
- tooltipContent
522
- ] }) }),
523
486
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
524
487
  "rect",
525
488
  {
@@ -698,30 +661,70 @@ var PieDonutChart = import_react.default.memo(
698
661
  }
699
662
  );
700
663
  PieDonutChart.displayName = "PieDonutChart";
701
- var TooltipBubble = ({ x, y, containerWidth, children }) => {
664
+ var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
702
665
  const ref = import_react.default.useRef(null);
703
- const [adjustedX, setAdjustedX] = import_react.default.useState(x);
704
- import_react.default.useEffect(() => {
666
+ const [pos, setPos] = import_react.default.useState({ left: 0, top: 0 });
667
+ import_react.default.useLayoutEffect(() => {
705
668
  const el = ref.current;
706
669
  if (!el) return;
707
670
  const w = el.offsetWidth;
708
- const half = w / 2;
709
- const margin = 8;
710
- let nx = x;
711
- if (x - half < margin) nx = half + margin;
712
- else if (x + half > containerWidth - margin) nx = containerWidth - half - margin;
713
- setAdjustedX(nx);
714
- }, [x, containerWidth]);
671
+ const h = el.offsetHeight;
672
+ const vw = window.innerWidth;
673
+ let left = clientX + TOOLTIP_OFFSET;
674
+ let top = clientY - h - TOOLTIP_OFFSET;
675
+ if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
676
+ if (top < 8) top = clientY + TOOLTIP_OFFSET;
677
+ if (left < 8) left = 8;
678
+ setPos({ left, top });
679
+ }, [clientX, clientY]);
715
680
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
716
681
  "div",
717
682
  {
718
683
  ref,
719
- className: "chart-tooltip",
720
- style: { left: adjustedX, top: y },
684
+ className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
685
+ style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
721
686
  children
722
687
  }
723
688
  );
724
689
  };
690
+ var ChartLegend = ({ data, labels, type }) => {
691
+ const entries = Object.entries(data);
692
+ if (type === "pie" || type === "doughnut") {
693
+ const values = entries.flatMap(([, v]) => v);
694
+ const total = values.reduce((a, b) => a + b, 0) || 1;
695
+ const firstKey = entries[0]?.[0] ?? "";
696
+ const colorOffset = hashString(firstKey);
697
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "chart-legend", children: values.map((v, i) => {
698
+ const pct = Math.round(v / total * 100);
699
+ const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
700
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-item", children: [
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-text", children: [
703
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "chart-legend-value", children: [
705
+ v.toLocaleString(),
706
+ "(",
707
+ pct,
708
+ "%)"
709
+ ] })
710
+ ] })
711
+ ] }, i);
712
+ }) });
713
+ }
714
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "chart-legend", children: entries.map(([key], di) => {
715
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
716
+ const color = palette[2];
717
+ const values = entries[di][1];
718
+ const sum = values.reduce((a, b) => a + b, 0);
719
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-item", children: [
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-text", children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-label", children: key }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-value", children: sum.toLocaleString() })
724
+ ] })
725
+ ] }, di);
726
+ }) });
727
+ };
725
728
  var Chart = import_react.default.memo((props) => {
726
729
  const { type, data, labels, tooltip: showTooltip = true } = props;
727
730
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
@@ -737,7 +740,8 @@ var Chart = import_react.default.memo((props) => {
737
740
  ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
738
741
  ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
739
742
  ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
740
- tooltip.visible && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
743
+ ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartLegend, { data: stableData, labels: stableLabels, type }),
744
+ tooltip.content && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content })
741
745
  ] });
742
746
  });
743
747
  Chart.displayName = "Chart";
@@ -75,9 +75,9 @@
75
75
  opacity: 1;
76
76
  }
77
77
  .lib-xplat-chart .chart-crosshair {
78
- stroke: var(--semantic-border-subtle);
78
+ stroke: var(--semantic-border-strong);
79
79
  stroke-width: 1;
80
- stroke-dasharray: 4 2;
80
+ stroke-dasharray: 4 3;
81
81
  pointer-events: none;
82
82
  }
83
83
  .lib-xplat-chart .chart-point-active {
@@ -93,18 +93,24 @@
93
93
  overflow: visible;
94
94
  }
95
95
  .lib-xplat-chart .chart-tooltip {
96
- position: absolute;
97
- transform: translate(-50%, -100%);
98
- padding: var(--spacing-space-2) var(--spacing-space-3);
96
+ padding: var(--spacing-space-3);
99
97
  background-color: var(--semantic-surface-neutral-strong);
100
98
  color: var(--semantic-text-inverse);
101
99
  font-size: 12px;
100
+ line-height: 18px;
102
101
  font-weight: 500;
103
102
  border-radius: var(--spacing-radius-md);
104
- white-space: nowrap;
103
+ max-width: 240px;
105
104
  pointer-events: none;
106
- z-index: 10;
107
- animation: chart-tooltip-in 150ms ease-out;
105
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
106
+ }
107
+ .lib-xplat-chart .chart-tooltip.chart-tooltip-show {
108
+ opacity: 1;
109
+ animation: chart-tooltip-in 120ms ease-out;
110
+ }
111
+ .lib-xplat-chart .chart-tooltip.chart-tooltip-hide {
112
+ opacity: 0;
113
+ animation: chart-tooltip-out 80ms ease-in;
108
114
  }
109
115
  .lib-xplat-chart .chart-bar-animate {
110
116
  animation: chart-bar-grow 800ms ease-out both;
@@ -116,6 +122,38 @@
116
122
  .lib-xplat-chart .chart-area[style*=animationDelay] {
117
123
  animation: chart-fade-in 800ms ease-out both;
118
124
  }
125
+ .lib-xplat-chart .chart-legend {
126
+ display: flex;
127
+ flex-wrap: wrap;
128
+ justify-content: center;
129
+ gap: var(--spacing-space-4);
130
+ padding: var(--spacing-space-3) 0;
131
+ }
132
+ .lib-xplat-chart .chart-legend-item {
133
+ display: flex;
134
+ align-items: flex-start;
135
+ gap: var(--spacing-space-2);
136
+ }
137
+ .lib-xplat-chart .chart-legend-dot {
138
+ flex-shrink: 0;
139
+ width: 10px;
140
+ height: 10px;
141
+ border-radius: 50%;
142
+ margin-top: 3px;
143
+ }
144
+ .lib-xplat-chart .chart-legend-text {
145
+ display: flex;
146
+ flex-direction: column;
147
+ }
148
+ .lib-xplat-chart .chart-legend-label {
149
+ font-size: 12px;
150
+ color: var(--semantic-text-muted);
151
+ }
152
+ .lib-xplat-chart .chart-legend-value {
153
+ font-size: 14px;
154
+ font-weight: 600;
155
+ color: var(--semantic-text-strong);
156
+ }
119
157
  @keyframes chart-bar-grow {
120
158
  from {
121
159
  transform: scaleY(0);
@@ -135,11 +173,17 @@
135
173
  @keyframes chart-tooltip-in {
136
174
  from {
137
175
  opacity: 0;
138
- transform: translate(-50%, -90%);
139
176
  }
140
177
  to {
141
178
  opacity: 1;
142
- transform: translate(-50%, -100%);
179
+ }
180
+ }
181
+ @keyframes chart-tooltip-out {
182
+ from {
183
+ opacity: 1;
184
+ }
185
+ to {
186
+ opacity: 0;
143
187
  }
144
188
  }
145
189
  @media (prefers-reduced-motion: reduce) {