@x-plat/design-system 0.5.31 → 0.5.33

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.
@@ -1999,19 +1999,43 @@
1999
1999
  .lib-xplat-chart .chart-area {
2000
2000
  opacity: 1;
2001
2001
  }
2002
+ .lib-xplat-chart .chart-crosshair {
2003
+ stroke: var(--semantic-border-strong);
2004
+ stroke-width: 1;
2005
+ stroke-dasharray: 4 3;
2006
+ pointer-events: none;
2007
+ }
2008
+ .lib-xplat-chart .chart-point-active {
2009
+ pointer-events: none;
2010
+ transition: cx 0.05s, cy 0.05s;
2011
+ }
2012
+ .lib-xplat-chart .chart-crosshair-label {
2013
+ font-size: 11px;
2014
+ font-weight: 500;
2015
+ color: var(--semantic-text-strong);
2016
+ text-align: center;
2017
+ white-space: nowrap;
2018
+ overflow: visible;
2019
+ }
2002
2020
  .lib-xplat-chart .chart-tooltip {
2003
- position: absolute;
2004
- transform: translate(-50%, -100%);
2005
- padding: var(--spacing-space-2) var(--spacing-space-3);
2021
+ padding: var(--spacing-space-3);
2006
2022
  background-color: var(--semantic-surface-neutral-strong);
2007
2023
  color: var(--semantic-text-inverse);
2008
2024
  font-size: 12px;
2025
+ line-height: 18px;
2009
2026
  font-weight: 500;
2010
2027
  border-radius: var(--spacing-radius-md);
2011
- white-space: nowrap;
2028
+ max-width: 240px;
2012
2029
  pointer-events: none;
2013
- z-index: 10;
2014
- 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;
2015
2039
  }
2016
2040
  .lib-xplat-chart .chart-bar-animate {
2017
2041
  animation: chart-bar-grow 800ms ease-out both;
@@ -2023,6 +2047,38 @@
2023
2047
  .lib-xplat-chart .chart-area[style*=animationDelay] {
2024
2048
  animation: chart-fade-in 800ms ease-out both;
2025
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
+ }
2026
2082
  @keyframes chart-bar-grow {
2027
2083
  from {
2028
2084
  transform: scaleY(0);
@@ -2042,11 +2098,17 @@
2042
2098
  @keyframes chart-tooltip-in {
2043
2099
  from {
2044
2100
  opacity: 0;
2045
- transform: translate(-50%, -90%);
2046
2101
  }
2047
2102
  to {
2048
2103
  opacity: 1;
2049
- transform: translate(-50%, -100%);
2104
+ }
2105
+ }
2106
+ @keyframes chart-tooltip-out {
2107
+ from {
2108
+ opacity: 1;
2109
+ }
2110
+ to {
2111
+ opacity: 0;
2050
2112
  }
2051
2113
  }
2052
2114
  @media (prefers-reduced-motion: reduce) {
@@ -3844,6 +3906,10 @@
3844
3906
  height: 100%;
3845
3907
  position: relative;
3846
3908
  overflow: auto;
3909
+ scrollbar-width: none;
3910
+ }
3911
+ .lib-xplat-table-wrapper::-webkit-scrollbar {
3912
+ display: none;
3847
3913
  }
3848
3914
  .lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > th,
3849
3915
  .lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > td,
@@ -2261,40 +2261,28 @@ var useChartAnimation = (containerRef, dataKey) => {
2261
2261
  }, [dataKey]);
2262
2262
  return animate || prefersReducedMotion();
2263
2263
  };
2264
+ var TOOLTIP_OFFSET = 12;
2264
2265
  var useChartTooltip = (enabled) => {
2265
2266
  const [tooltip, setTooltip] = React6.useState({
2266
2267
  visible: false,
2267
- x: 0,
2268
- y: 0,
2268
+ clientX: 0,
2269
+ clientY: 0,
2269
2270
  content: ""
2270
2271
  });
2271
2272
  const containerRef = React6.useRef(null);
2272
2273
  const rafRef = React6.useRef(0);
2273
2274
  const move = React6.useCallback((e) => {
2274
2275
  if (!enabled) return;
2275
- const clientX = e.clientX;
2276
- const clientY = e.clientY;
2276
+ const cx = e.clientX;
2277
+ const cy = e.clientY;
2277
2278
  cancelAnimationFrame(rafRef.current);
2278
2279
  rafRef.current = requestAnimationFrame(() => {
2279
- const rect = containerRef.current?.getBoundingClientRect();
2280
- if (!rect) return;
2281
- setTooltip((prev) => ({
2282
- ...prev,
2283
- x: clientX - rect.left,
2284
- y: clientY - rect.top - 12
2285
- }));
2280
+ setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
2286
2281
  });
2287
2282
  }, [enabled]);
2288
2283
  const show = React6.useCallback((e, content) => {
2289
2284
  if (!enabled) return;
2290
- const rect = containerRef.current?.getBoundingClientRect();
2291
- if (!rect) return;
2292
- setTooltip({
2293
- visible: true,
2294
- x: e.clientX - rect.left,
2295
- y: e.clientY - rect.top - 12,
2296
- content
2297
- });
2285
+ setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
2298
2286
  }, [enabled]);
2299
2287
  const hide = React6.useCallback(() => {
2300
2288
  cancelAnimationFrame(rafRef.current);
@@ -2326,6 +2314,45 @@ var AxisLabels = React6.memo(({ labels, count, chartW, height }) => {
2326
2314
  }) });
2327
2315
  });
2328
2316
  AxisLabels.displayName = "AxisLabels";
2317
+ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
2318
+ const [activeIndex, setActiveIndex] = React6.useState(null);
2319
+ const handleMouseMove = React6.useCallback((e) => {
2320
+ const svg = e.currentTarget;
2321
+ const rect = svg.getBoundingClientRect();
2322
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
2323
+ if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
2324
+ const points = seriesPoints[0];
2325
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
2326
+ const threshold = step / 2;
2327
+ let closest = 0;
2328
+ let minDist = Math.abs(points[0].x - mx);
2329
+ for (let i = 1; i < points.length; i++) {
2330
+ const dist = Math.abs(points[i].x - mx);
2331
+ if (dist < minDist) {
2332
+ minDist = dist;
2333
+ closest = i;
2334
+ }
2335
+ }
2336
+ setActiveIndex(minDist <= threshold ? closest : null);
2337
+ }, [seriesPoints]);
2338
+ const handleMouseLeave = React6.useCallback(() => {
2339
+ setActiveIndex(null);
2340
+ }, []);
2341
+ const tooltipContent = React6.useMemo(() => {
2342
+ if (activeIndex === null) return "";
2343
+ return entries.map(([key], di) => {
2344
+ const p = seriesPoints[di]?.[activeIndex];
2345
+ return p ? `${key}: ${p.v}` : "";
2346
+ }).filter(Boolean).join(" / ");
2347
+ }, [activeIndex, entries, seriesPoints]);
2348
+ const getTooltipAt = React6.useCallback((idx) => {
2349
+ return entries.map(([key], di) => {
2350
+ const p = seriesPoints[di]?.[idx];
2351
+ return p ? `${key}: ${p.v}` : "";
2352
+ }).filter(Boolean).join(" / ");
2353
+ }, [entries, seriesPoints]);
2354
+ return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
2355
+ };
2329
2356
  var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
2330
2357
  const entries = React6.useMemo(() => Object.entries(data), [data]);
2331
2358
  const maxVal = React6.useMemo(() => {
@@ -2345,8 +2372,9 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
2345
2372
  ),
2346
2373
  [entries, count, chartW, chartH, maxVal]
2347
2374
  );
2348
- const showPoints = count <= 100;
2349
2375
  const lineRefs = React6.useRef([]);
2376
+ const clipRef = React6.useRef(null);
2377
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
2350
2378
  React6.useEffect(() => {
2351
2379
  if (!animate) return;
2352
2380
  lineRefs.current.forEach((el) => {
@@ -2359,61 +2387,123 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
2359
2387
  el.style.strokeDashoffset = "0";
2360
2388
  });
2361
2389
  });
2362
- }, [animate, seriesPoints]);
2363
- return /* @__PURE__ */ jsxs197("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
2364
- /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2365
- /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2366
- entries.map(([key], di) => {
2367
- const palette = getPalette(LINE_BAR_PALETTES, di, key);
2368
- const color = palette[2];
2369
- const areaColor = palette[0];
2370
- const points = seriesPoints[di];
2371
- const gradientId = `line-gradient-${di}`;
2372
- const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
2373
- const areaD = `M ${points[0].x},${points[0].y} ${points.map((p) => `L ${p.x},${p.y}`).join(" ")} L ${points[points.length - 1].x},${PADDING.top + chartH} L ${points[0].x},${PADDING.top + chartH} Z`;
2374
- return /* @__PURE__ */ jsxs197("g", { children: [
2375
- /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2376
- /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
2377
- /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
2378
- ] }) }),
2379
- /* @__PURE__ */ jsx307(
2380
- "path",
2390
+ if (clipRef.current) {
2391
+ clipRef.current.setAttribute("width", "0");
2392
+ requestAnimationFrame(() => {
2393
+ if (clipRef.current) {
2394
+ clipRef.current.style.transition = "width 1200ms ease-out 200ms";
2395
+ clipRef.current.setAttribute("width", `${width}`);
2396
+ }
2397
+ });
2398
+ }
2399
+ }, [animate, seriesPoints, width]);
2400
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
2401
+ const lineClipId = "line-area-clip";
2402
+ return /* @__PURE__ */ jsxs197(
2403
+ "svg",
2404
+ {
2405
+ viewBox: `0 0 ${width} ${height}`,
2406
+ className: "chart-svg",
2407
+ onMouseMove: (e) => {
2408
+ handleMouseMove(e);
2409
+ const svg = e.currentTarget;
2410
+ const rect = svg.getBoundingClientRect();
2411
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
2412
+ const points = seriesPoints[0];
2413
+ if (!points || points.length === 0) return;
2414
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
2415
+ let closest = 0;
2416
+ let minDist = Math.abs(points[0].x - mx);
2417
+ for (let i = 1; i < points.length; i++) {
2418
+ const dist = Math.abs(points[i].x - mx);
2419
+ if (dist < minDist) {
2420
+ minDist = dist;
2421
+ closest = i;
2422
+ }
2423
+ }
2424
+ if (minDist <= step / 2) {
2425
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
2426
+ } else {
2427
+ onLeave();
2428
+ }
2429
+ },
2430
+ onMouseLeave: () => {
2431
+ handleMouseLeave();
2432
+ onLeave();
2433
+ },
2434
+ children: [
2435
+ animate && /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsx307("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx307("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
2436
+ /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2437
+ /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2438
+ entries.map(([key], di) => {
2439
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
2440
+ const color = palette[2];
2441
+ const areaColor = palette[0];
2442
+ const points = seriesPoints[di];
2443
+ const gradientId = `line-gradient-${di}`;
2444
+ const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
2445
+ const areaD = `M ${points[0].x},${points[0].y} ${points.map((p) => `L ${p.x},${p.y}`).join(" ")} L ${points[points.length - 1].x},${PADDING.top + chartH} L ${points[0].x},${PADDING.top + chartH} Z`;
2446
+ return /* @__PURE__ */ jsxs197("g", { children: [
2447
+ /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2448
+ /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
2449
+ /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
2450
+ ] }) }),
2451
+ /* @__PURE__ */ jsx307(
2452
+ "path",
2453
+ {
2454
+ d: areaD,
2455
+ fill: `url(#${gradientId})`,
2456
+ clipPath: animate ? `url(#${lineClipId})` : void 0
2457
+ }
2458
+ ),
2459
+ /* @__PURE__ */ jsx307(
2460
+ "polyline",
2461
+ {
2462
+ ref: (el) => {
2463
+ lineRefs.current[di] = el;
2464
+ },
2465
+ points: polyPoints,
2466
+ fill: "none",
2467
+ stroke: color,
2468
+ strokeWidth: "2"
2469
+ }
2470
+ ),
2471
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
2472
+ "circle",
2473
+ {
2474
+ cx: points[activeIndex].x,
2475
+ cy: points[activeIndex].y,
2476
+ r: "5",
2477
+ fill: color,
2478
+ className: "chart-point-active"
2479
+ }
2480
+ )
2481
+ ] }, di);
2482
+ }),
2483
+ activeX !== null && /* @__PURE__ */ jsx307(
2484
+ "line",
2381
2485
  {
2382
- d: areaD,
2383
- fill: `url(#${gradientId})`,
2384
- className: "chart-area",
2385
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
2486
+ x1: activeX,
2487
+ y1: PADDING.top,
2488
+ x2: activeX,
2489
+ y2: PADDING.top + chartH,
2490
+ className: "chart-crosshair"
2386
2491
  }
2387
2492
  ),
2388
2493
  /* @__PURE__ */ jsx307(
2389
- "polyline",
2494
+ "rect",
2390
2495
  {
2391
- ref: (el) => {
2392
- lineRefs.current[di] = el;
2393
- },
2394
- points: polyPoints,
2395
- fill: "none",
2396
- stroke: color,
2397
- strokeWidth: "2"
2496
+ x: PADDING.left,
2497
+ y: PADDING.top,
2498
+ width: chartW,
2499
+ height: chartH,
2500
+ fill: "transparent",
2501
+ style: { cursor: "crosshair" }
2398
2502
  }
2399
- ),
2400
- showPoints && points.map((p, i) => /* @__PURE__ */ jsx307(
2401
- "circle",
2402
- {
2403
- cx: p.x,
2404
- cy: p.y,
2405
- r: "4",
2406
- fill: color,
2407
- className: "chart-point",
2408
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
2409
- onMouseMove: onMove,
2410
- onMouseLeave: onLeave
2411
- },
2412
- i
2413
- ))
2414
- ] }, di);
2415
- })
2416
- ] });
2503
+ )
2504
+ ]
2505
+ }
2506
+ );
2417
2507
  });
2418
2508
  LineChart.displayName = "LineChart";
2419
2509
  var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
@@ -2435,8 +2525,9 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
2435
2525
  ),
2436
2526
  [entries, count, chartW, chartH, maxVal]
2437
2527
  );
2438
- const showPoints = count <= 100;
2439
2528
  const lineRefs = React6.useRef([]);
2529
+ const curveClipRef = React6.useRef(null);
2530
+ const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
2440
2531
  React6.useEffect(() => {
2441
2532
  if (!animate) return;
2442
2533
  lineRefs.current.forEach((el) => {
@@ -2449,61 +2540,123 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
2449
2540
  el.style.strokeDashoffset = "0";
2450
2541
  });
2451
2542
  });
2452
- }, [animate, seriesPoints]);
2453
- return /* @__PURE__ */ jsxs197("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
2454
- /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2455
- /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2456
- entries.map(([key], di) => {
2457
- const palette = getPalette(LINE_BAR_PALETTES, di, key);
2458
- const color = palette[2];
2459
- const areaColor = palette[0];
2460
- const points = seriesPoints[di];
2461
- const gradientId = `curve-gradient-${di}`;
2462
- const linePath = toSmoothPath(points);
2463
- const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
2464
- return /* @__PURE__ */ jsxs197("g", { children: [
2465
- /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2466
- /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
2467
- /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
2468
- ] }) }),
2469
- /* @__PURE__ */ jsx307(
2470
- "path",
2543
+ if (curveClipRef.current) {
2544
+ curveClipRef.current.setAttribute("width", "0");
2545
+ requestAnimationFrame(() => {
2546
+ if (curveClipRef.current) {
2547
+ curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
2548
+ curveClipRef.current.setAttribute("width", `${width}`);
2549
+ }
2550
+ });
2551
+ }
2552
+ }, [animate, seriesPoints, width]);
2553
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
2554
+ const curveClipId = "curve-area-clip";
2555
+ return /* @__PURE__ */ jsxs197(
2556
+ "svg",
2557
+ {
2558
+ viewBox: `0 0 ${width} ${height}`,
2559
+ className: "chart-svg",
2560
+ onMouseMove: (e) => {
2561
+ handleMouseMove(e);
2562
+ const svg = e.currentTarget;
2563
+ const rect = svg.getBoundingClientRect();
2564
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
2565
+ const points = seriesPoints[0];
2566
+ if (!points || points.length === 0) return;
2567
+ const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
2568
+ let closest = 0;
2569
+ let minDist = Math.abs(points[0].x - mx);
2570
+ for (let i = 1; i < points.length; i++) {
2571
+ const dist = Math.abs(points[i].x - mx);
2572
+ if (dist < minDist) {
2573
+ minDist = dist;
2574
+ closest = i;
2575
+ }
2576
+ }
2577
+ if (minDist <= step / 2) {
2578
+ onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
2579
+ } else {
2580
+ onLeave();
2581
+ }
2582
+ },
2583
+ onMouseLeave: () => {
2584
+ handleMouseLeave();
2585
+ onLeave();
2586
+ },
2587
+ children: [
2588
+ animate && /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsx307("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx307("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
2589
+ /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2590
+ /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2591
+ entries.map(([key], di) => {
2592
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
2593
+ const color = palette[2];
2594
+ const areaColor = palette[0];
2595
+ const points = seriesPoints[di];
2596
+ const gradientId = `curve-gradient-${di}`;
2597
+ const linePath = toSmoothPath(points);
2598
+ const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
2599
+ return /* @__PURE__ */ jsxs197("g", { children: [
2600
+ /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2601
+ /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
2602
+ /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
2603
+ ] }) }),
2604
+ /* @__PURE__ */ jsx307(
2605
+ "path",
2606
+ {
2607
+ d: areaPath,
2608
+ fill: `url(#${gradientId})`,
2609
+ clipPath: animate ? `url(#${curveClipId})` : void 0
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx307(
2613
+ "path",
2614
+ {
2615
+ ref: (el) => {
2616
+ lineRefs.current[di] = el;
2617
+ },
2618
+ d: linePath,
2619
+ fill: "none",
2620
+ stroke: color,
2621
+ strokeWidth: "2"
2622
+ }
2623
+ ),
2624
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
2625
+ "circle",
2626
+ {
2627
+ cx: points[activeIndex].x,
2628
+ cy: points[activeIndex].y,
2629
+ r: "5",
2630
+ fill: color,
2631
+ className: "chart-point-active"
2632
+ }
2633
+ )
2634
+ ] }, di);
2635
+ }),
2636
+ activeX !== null && /* @__PURE__ */ jsx307(
2637
+ "line",
2471
2638
  {
2472
- d: areaPath,
2473
- fill: `url(#${gradientId})`,
2474
- className: "chart-area",
2475
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
2639
+ x1: activeX,
2640
+ y1: PADDING.top,
2641
+ x2: activeX,
2642
+ y2: PADDING.top + chartH,
2643
+ className: "chart-crosshair"
2476
2644
  }
2477
2645
  ),
2478
2646
  /* @__PURE__ */ jsx307(
2479
- "path",
2647
+ "rect",
2480
2648
  {
2481
- ref: (el) => {
2482
- lineRefs.current[di] = el;
2483
- },
2484
- d: linePath,
2485
- fill: "none",
2486
- stroke: color,
2487
- strokeWidth: "2"
2649
+ x: PADDING.left,
2650
+ y: PADDING.top,
2651
+ width: chartW,
2652
+ height: chartH,
2653
+ fill: "transparent",
2654
+ style: { cursor: "crosshair" }
2488
2655
  }
2489
- ),
2490
- showPoints && points.map((p, i) => /* @__PURE__ */ jsx307(
2491
- "circle",
2492
- {
2493
- cx: p.x,
2494
- cy: p.y,
2495
- r: "4",
2496
- fill: color,
2497
- className: "chart-point",
2498
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
2499
- onMouseMove: onMove,
2500
- onMouseLeave: onLeave
2501
- },
2502
- i
2503
- ))
2504
- ] }, di);
2505
- })
2506
- ] });
2656
+ )
2657
+ ]
2658
+ }
2659
+ );
2507
2660
  });
2508
2661
  CurveChart.displayName = "CurveChart";
2509
2662
  var BarChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
@@ -2668,30 +2821,70 @@ var PieDonutChart = React6.memo(
2668
2821
  }
2669
2822
  );
2670
2823
  PieDonutChart.displayName = "PieDonutChart";
2671
- var TooltipBubble = ({ x, y, containerWidth, children }) => {
2824
+ var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
2672
2825
  const ref = React6.useRef(null);
2673
- const [adjustedX, setAdjustedX] = React6.useState(x);
2674
- React6.useEffect(() => {
2826
+ const [pos, setPos] = React6.useState({ left: 0, top: 0 });
2827
+ React6.useLayoutEffect(() => {
2675
2828
  const el = ref.current;
2676
2829
  if (!el) return;
2677
2830
  const w = el.offsetWidth;
2678
- const half = w / 2;
2679
- const margin = 8;
2680
- let nx = x;
2681
- if (x - half < margin) nx = half + margin;
2682
- else if (x + half > containerWidth - margin) nx = containerWidth - half - margin;
2683
- setAdjustedX(nx);
2684
- }, [x, containerWidth]);
2831
+ const h = el.offsetHeight;
2832
+ const vw = window.innerWidth;
2833
+ let left = clientX + TOOLTIP_OFFSET;
2834
+ let top = clientY - h - TOOLTIP_OFFSET;
2835
+ if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
2836
+ if (top < 8) top = clientY + TOOLTIP_OFFSET;
2837
+ if (left < 8) left = 8;
2838
+ setPos({ left, top });
2839
+ }, [clientX, clientY]);
2685
2840
  return /* @__PURE__ */ jsx307(
2686
2841
  "div",
2687
2842
  {
2688
2843
  ref,
2689
- className: "chart-tooltip",
2690
- style: { left: adjustedX, top: y },
2844
+ className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
2845
+ style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
2691
2846
  children
2692
2847
  }
2693
2848
  );
2694
2849
  };
2850
+ var ChartLegend = ({ data, labels, type }) => {
2851
+ const entries = Object.entries(data);
2852
+ if (type === "pie" || type === "doughnut") {
2853
+ const values = entries.flatMap(([, v]) => v);
2854
+ const total = values.reduce((a, b) => a + b, 0) || 1;
2855
+ const firstKey = entries[0]?.[0] ?? "";
2856
+ const colorOffset = hashString(firstKey);
2857
+ return /* @__PURE__ */ jsx307("div", { className: "chart-legend", children: values.map((v, i) => {
2858
+ const pct = Math.round(v / total * 100);
2859
+ const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
2860
+ return /* @__PURE__ */ jsxs197("div", { className: "chart-legend-item", children: [
2861
+ /* @__PURE__ */ jsx307("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
2862
+ /* @__PURE__ */ jsxs197("div", { className: "chart-legend-text", children: [
2863
+ /* @__PURE__ */ jsx307("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
2864
+ /* @__PURE__ */ jsxs197("span", { className: "chart-legend-value", children: [
2865
+ v.toLocaleString(),
2866
+ "(",
2867
+ pct,
2868
+ "%)"
2869
+ ] })
2870
+ ] })
2871
+ ] }, i);
2872
+ }) });
2873
+ }
2874
+ return /* @__PURE__ */ jsx307("div", { className: "chart-legend", children: entries.map(([key], di) => {
2875
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
2876
+ const color = palette[2];
2877
+ const values = entries[di][1];
2878
+ const sum = values.reduce((a, b) => a + b, 0);
2879
+ return /* @__PURE__ */ jsxs197("div", { className: "chart-legend-item", children: [
2880
+ /* @__PURE__ */ jsx307("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
2881
+ /* @__PURE__ */ jsxs197("div", { className: "chart-legend-text", children: [
2882
+ /* @__PURE__ */ jsx307("span", { className: "chart-legend-label", children: key }),
2883
+ /* @__PURE__ */ jsx307("span", { className: "chart-legend-value", children: sum.toLocaleString() })
2884
+ ] })
2885
+ ] }, di);
2886
+ }) });
2887
+ };
2695
2888
  var Chart = React6.memo((props) => {
2696
2889
  const { type, data, labels, tooltip: showTooltip = true } = props;
2697
2890
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
@@ -2707,7 +2900,8 @@ var Chart = React6.memo((props) => {
2707
2900
  ready && type === "bar" && /* @__PURE__ */ jsx307(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
2708
2901
  ready && type === "pie" && /* @__PURE__ */ jsx307(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
2709
2902
  ready && type === "doughnut" && /* @__PURE__ */ jsx307(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
2710
- tooltip.visible && /* @__PURE__ */ jsx307(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
2903
+ ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ jsx307(ChartLegend, { data: stableData, labels: stableLabels, type }),
2904
+ tooltip.content && /* @__PURE__ */ jsx307(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content })
2711
2905
  ] });
2712
2906
  });
2713
2907
  Chart.displayName = "Chart";