@x-plat/design-system 0.5.31 → 0.5.32

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.
@@ -2326,6 +2326,40 @@ var AxisLabels = React6.memo(({ labels, count, chartW, height }) => {
2326
2326
  }) });
2327
2327
  });
2328
2328
  AxisLabels.displayName = "AxisLabels";
2329
+ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
2330
+ const [activeIndex, setActiveIndex] = React6.useState(null);
2331
+ const [mouseX, setMouseX] = React6.useState(null);
2332
+ const handleMouseMove = React6.useCallback((e) => {
2333
+ const svg = e.currentTarget;
2334
+ const rect = svg.getBoundingClientRect();
2335
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
2336
+ setMouseX(mx);
2337
+ if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
2338
+ const points = seriesPoints[0];
2339
+ let closest = 0;
2340
+ let minDist = Math.abs(points[0].x - mx);
2341
+ for (let i = 1; i < points.length; i++) {
2342
+ const dist = Math.abs(points[i].x - mx);
2343
+ if (dist < minDist) {
2344
+ minDist = dist;
2345
+ closest = i;
2346
+ }
2347
+ }
2348
+ setActiveIndex(closest);
2349
+ }, [seriesPoints]);
2350
+ const handleMouseLeave = React6.useCallback(() => {
2351
+ setActiveIndex(null);
2352
+ setMouseX(null);
2353
+ }, []);
2354
+ const tooltipContent = React6.useMemo(() => {
2355
+ if (activeIndex === null) return "";
2356
+ return entries.map(([key], di) => {
2357
+ const p = seriesPoints[di]?.[activeIndex];
2358
+ return p ? `${key}: ${p.v}` : "";
2359
+ }).filter(Boolean).join(" / ");
2360
+ }, [activeIndex, entries, seriesPoints]);
2361
+ return { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent };
2362
+ };
2329
2363
  var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
2330
2364
  const entries = React6.useMemo(() => Object.entries(data), [data]);
2331
2365
  const maxVal = React6.useMemo(() => {
@@ -2345,8 +2379,9 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
2345
2379
  ),
2346
2380
  [entries, count, chartW, chartH, maxVal]
2347
2381
  );
2348
- const showPoints = count <= 100;
2349
2382
  const lineRefs = React6.useRef([]);
2383
+ const clipRef = React6.useRef(null);
2384
+ const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
2350
2385
  React6.useEffect(() => {
2351
2386
  if (!animate) return;
2352
2387
  lineRefs.current.forEach((el) => {
@@ -2359,61 +2394,110 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
2359
2394
  el.style.strokeDashoffset = "0";
2360
2395
  });
2361
2396
  });
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",
2397
+ if (clipRef.current) {
2398
+ clipRef.current.setAttribute("width", "0");
2399
+ requestAnimationFrame(() => {
2400
+ if (clipRef.current) {
2401
+ clipRef.current.style.transition = "width 1200ms ease-out 200ms";
2402
+ clipRef.current.setAttribute("width", `${width}`);
2403
+ }
2404
+ });
2405
+ }
2406
+ }, [animate, seriesPoints, width]);
2407
+ const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
2408
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
2409
+ const lineClipId = "line-area-clip";
2410
+ return /* @__PURE__ */ jsxs197(
2411
+ "svg",
2412
+ {
2413
+ viewBox: `0 0 ${width} ${height}`,
2414
+ className: "chart-svg",
2415
+ onMouseMove: (e) => {
2416
+ handleMouseMove(e);
2417
+ onMove(e);
2418
+ },
2419
+ onMouseLeave: () => {
2420
+ handleMouseLeave();
2421
+ onLeave();
2422
+ },
2423
+ children: [
2424
+ 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 }) }) }),
2425
+ /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2426
+ /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2427
+ entries.map(([key], di) => {
2428
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
2429
+ const color = palette[2];
2430
+ const areaColor = palette[0];
2431
+ const points = seriesPoints[di];
2432
+ const gradientId = `line-gradient-${di}`;
2433
+ const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
2434
+ 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`;
2435
+ return /* @__PURE__ */ jsxs197("g", { children: [
2436
+ /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2437
+ /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
2438
+ /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
2439
+ ] }) }),
2440
+ /* @__PURE__ */ jsx307(
2441
+ "path",
2442
+ {
2443
+ d: areaD,
2444
+ fill: `url(#${gradientId})`,
2445
+ clipPath: animate ? `url(#${lineClipId})` : void 0
2446
+ }
2447
+ ),
2448
+ /* @__PURE__ */ jsx307(
2449
+ "polyline",
2450
+ {
2451
+ ref: (el) => {
2452
+ lineRefs.current[di] = el;
2453
+ },
2454
+ points: polyPoints,
2455
+ fill: "none",
2456
+ stroke: color,
2457
+ strokeWidth: "2"
2458
+ }
2459
+ ),
2460
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
2461
+ "circle",
2462
+ {
2463
+ cx: points[activeIndex].x,
2464
+ cy: points[activeIndex].y,
2465
+ r: "5",
2466
+ fill: color,
2467
+ className: "chart-point-active"
2468
+ }
2469
+ )
2470
+ ] }, di);
2471
+ }),
2472
+ guideX !== null && /* @__PURE__ */ jsx307(
2473
+ "line",
2381
2474
  {
2382
- d: areaD,
2383
- fill: `url(#${gradientId})`,
2384
- className: "chart-area",
2385
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
2475
+ x1: guideX,
2476
+ y1: PADDING.top,
2477
+ x2: guideX,
2478
+ y2: PADDING.top + chartH,
2479
+ className: "chart-crosshair"
2386
2480
  }
2387
2481
  ),
2482
+ activeIndex !== null && activeX !== null && /* @__PURE__ */ jsx307("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ jsxs197("div", { className: "chart-crosshair-label", children: [
2483
+ labels[activeIndex],
2484
+ " \u2014 ",
2485
+ tooltipContent
2486
+ ] }) }),
2388
2487
  /* @__PURE__ */ jsx307(
2389
- "polyline",
2488
+ "rect",
2390
2489
  {
2391
- ref: (el) => {
2392
- lineRefs.current[di] = el;
2393
- },
2394
- points: polyPoints,
2395
- fill: "none",
2396
- stroke: color,
2397
- strokeWidth: "2"
2490
+ x: PADDING.left,
2491
+ y: PADDING.top,
2492
+ width: chartW,
2493
+ height: chartH,
2494
+ fill: "transparent",
2495
+ style: { cursor: "crosshair" }
2398
2496
  }
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
- ] });
2497
+ )
2498
+ ]
2499
+ }
2500
+ );
2417
2501
  });
2418
2502
  LineChart.displayName = "LineChart";
2419
2503
  var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
@@ -2435,8 +2519,9 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
2435
2519
  ),
2436
2520
  [entries, count, chartW, chartH, maxVal]
2437
2521
  );
2438
- const showPoints = count <= 100;
2439
2522
  const lineRefs = React6.useRef([]);
2523
+ const curveClipRef = React6.useRef(null);
2524
+ const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
2440
2525
  React6.useEffect(() => {
2441
2526
  if (!animate) return;
2442
2527
  lineRefs.current.forEach((el) => {
@@ -2449,61 +2534,110 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
2449
2534
  el.style.strokeDashoffset = "0";
2450
2535
  });
2451
2536
  });
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",
2537
+ if (curveClipRef.current) {
2538
+ curveClipRef.current.setAttribute("width", "0");
2539
+ requestAnimationFrame(() => {
2540
+ if (curveClipRef.current) {
2541
+ curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
2542
+ curveClipRef.current.setAttribute("width", `${width}`);
2543
+ }
2544
+ });
2545
+ }
2546
+ }, [animate, seriesPoints, width]);
2547
+ const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
2548
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
2549
+ const curveClipId = "curve-area-clip";
2550
+ return /* @__PURE__ */ jsxs197(
2551
+ "svg",
2552
+ {
2553
+ viewBox: `0 0 ${width} ${height}`,
2554
+ className: "chart-svg",
2555
+ onMouseMove: (e) => {
2556
+ handleMouseMove(e);
2557
+ onMove(e);
2558
+ },
2559
+ onMouseLeave: () => {
2560
+ handleMouseLeave();
2561
+ onLeave();
2562
+ },
2563
+ children: [
2564
+ 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 }) }) }),
2565
+ /* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
2566
+ /* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
2567
+ entries.map(([key], di) => {
2568
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
2569
+ const color = palette[2];
2570
+ const areaColor = palette[0];
2571
+ const points = seriesPoints[di];
2572
+ const gradientId = `curve-gradient-${di}`;
2573
+ const linePath = toSmoothPath(points);
2574
+ const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
2575
+ return /* @__PURE__ */ jsxs197("g", { children: [
2576
+ /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
2577
+ /* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
2578
+ /* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
2579
+ ] }) }),
2580
+ /* @__PURE__ */ jsx307(
2581
+ "path",
2582
+ {
2583
+ d: areaPath,
2584
+ fill: `url(#${gradientId})`,
2585
+ clipPath: animate ? `url(#${curveClipId})` : void 0
2586
+ }
2587
+ ),
2588
+ /* @__PURE__ */ jsx307(
2589
+ "path",
2590
+ {
2591
+ ref: (el) => {
2592
+ lineRefs.current[di] = el;
2593
+ },
2594
+ d: linePath,
2595
+ fill: "none",
2596
+ stroke: color,
2597
+ strokeWidth: "2"
2598
+ }
2599
+ ),
2600
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
2601
+ "circle",
2602
+ {
2603
+ cx: points[activeIndex].x,
2604
+ cy: points[activeIndex].y,
2605
+ r: "5",
2606
+ fill: color,
2607
+ className: "chart-point-active"
2608
+ }
2609
+ )
2610
+ ] }, di);
2611
+ }),
2612
+ guideX !== null && /* @__PURE__ */ jsx307(
2613
+ "line",
2471
2614
  {
2472
- d: areaPath,
2473
- fill: `url(#${gradientId})`,
2474
- className: "chart-area",
2475
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
2615
+ x1: guideX,
2616
+ y1: PADDING.top,
2617
+ x2: guideX,
2618
+ y2: PADDING.top + chartH,
2619
+ className: "chart-crosshair"
2476
2620
  }
2477
2621
  ),
2622
+ activeIndex !== null && activeX !== null && /* @__PURE__ */ jsx307("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ jsxs197("div", { className: "chart-crosshair-label", children: [
2623
+ labels[activeIndex],
2624
+ " \u2014 ",
2625
+ tooltipContent
2626
+ ] }) }),
2478
2627
  /* @__PURE__ */ jsx307(
2479
- "path",
2628
+ "rect",
2480
2629
  {
2481
- ref: (el) => {
2482
- lineRefs.current[di] = el;
2483
- },
2484
- d: linePath,
2485
- fill: "none",
2486
- stroke: color,
2487
- strokeWidth: "2"
2630
+ x: PADDING.left,
2631
+ y: PADDING.top,
2632
+ width: chartW,
2633
+ height: chartH,
2634
+ fill: "transparent",
2635
+ style: { cursor: "crosshair" }
2488
2636
  }
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
- ] });
2637
+ )
2638
+ ]
2639
+ }
2640
+ );
2507
2641
  });
2508
2642
  CurveChart.displayName = "CurveChart";
2509
2643
  var BarChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {