@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.
@@ -186,6 +186,40 @@ var AxisLabels = React.memo(({ labels, count, chartW, height }) => {
186
186
  }) });
187
187
  });
188
188
  AxisLabels.displayName = "AxisLabels";
189
+ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
190
+ const [activeIndex, setActiveIndex] = React.useState(null);
191
+ const [mouseX, setMouseX] = React.useState(null);
192
+ const handleMouseMove = React.useCallback((e) => {
193
+ const svg = e.currentTarget;
194
+ const rect = svg.getBoundingClientRect();
195
+ const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
196
+ setMouseX(mx);
197
+ if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
198
+ const points = seriesPoints[0];
199
+ let closest = 0;
200
+ let minDist = Math.abs(points[0].x - mx);
201
+ for (let i = 1; i < points.length; i++) {
202
+ const dist = Math.abs(points[i].x - mx);
203
+ if (dist < minDist) {
204
+ minDist = dist;
205
+ closest = i;
206
+ }
207
+ }
208
+ setActiveIndex(closest);
209
+ }, [seriesPoints]);
210
+ const handleMouseLeave = React.useCallback(() => {
211
+ setActiveIndex(null);
212
+ setMouseX(null);
213
+ }, []);
214
+ const tooltipContent = React.useMemo(() => {
215
+ if (activeIndex === null) return "";
216
+ return entries.map(([key], di) => {
217
+ const p = seriesPoints[di]?.[activeIndex];
218
+ return p ? `${key}: ${p.v}` : "";
219
+ }).filter(Boolean).join(" / ");
220
+ }, [activeIndex, entries, seriesPoints]);
221
+ return { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent };
222
+ };
189
223
  var LineChart = React.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
190
224
  const entries = React.useMemo(() => Object.entries(data), [data]);
191
225
  const maxVal = React.useMemo(() => {
@@ -205,8 +239,9 @@ var LineChart = React.memo(({ data, labels, width, height, animate, onHover, onM
205
239
  ),
206
240
  [entries, count, chartW, chartH, maxVal]
207
241
  );
208
- const showPoints = count <= 100;
209
242
  const lineRefs = React.useRef([]);
243
+ const clipRef = React.useRef(null);
244
+ const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
210
245
  React.useEffect(() => {
211
246
  if (!animate) return;
212
247
  lineRefs.current.forEach((el) => {
@@ -219,61 +254,110 @@ var LineChart = React.memo(({ data, labels, width, height, animate, onHover, onM
219
254
  el.style.strokeDashoffset = "0";
220
255
  });
221
256
  });
222
- }, [animate, seriesPoints]);
223
- return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
224
- /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
225
- /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
226
- entries.map(([key], di) => {
227
- const palette = getPalette(LINE_BAR_PALETTES, di, key);
228
- const color = palette[2];
229
- const areaColor = palette[0];
230
- const points = seriesPoints[di];
231
- const gradientId = `line-gradient-${di}`;
232
- const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
233
- 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`;
234
- return /* @__PURE__ */ jsxs("g", { children: [
235
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
236
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
237
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
238
- ] }) }),
239
- /* @__PURE__ */ jsx(
240
- "path",
257
+ if (clipRef.current) {
258
+ clipRef.current.setAttribute("width", "0");
259
+ requestAnimationFrame(() => {
260
+ if (clipRef.current) {
261
+ clipRef.current.style.transition = "width 1200ms ease-out 200ms";
262
+ clipRef.current.setAttribute("width", `${width}`);
263
+ }
264
+ });
265
+ }
266
+ }, [animate, seriesPoints, width]);
267
+ const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
268
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
269
+ const lineClipId = "line-area-clip";
270
+ return /* @__PURE__ */ jsxs(
271
+ "svg",
272
+ {
273
+ viewBox: `0 0 ${width} ${height}`,
274
+ className: "chart-svg",
275
+ onMouseMove: (e) => {
276
+ handleMouseMove(e);
277
+ onMove(e);
278
+ },
279
+ onMouseLeave: () => {
280
+ handleMouseLeave();
281
+ onLeave();
282
+ },
283
+ children: [
284
+ animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
285
+ /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
286
+ /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
287
+ entries.map(([key], di) => {
288
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
289
+ const color = palette[2];
290
+ const areaColor = palette[0];
291
+ const points = seriesPoints[di];
292
+ const gradientId = `line-gradient-${di}`;
293
+ const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
294
+ 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`;
295
+ return /* @__PURE__ */ jsxs("g", { children: [
296
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
297
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
298
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
299
+ ] }) }),
300
+ /* @__PURE__ */ jsx(
301
+ "path",
302
+ {
303
+ d: areaD,
304
+ fill: `url(#${gradientId})`,
305
+ clipPath: animate ? `url(#${lineClipId})` : void 0
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx(
309
+ "polyline",
310
+ {
311
+ ref: (el) => {
312
+ lineRefs.current[di] = el;
313
+ },
314
+ points: polyPoints,
315
+ fill: "none",
316
+ stroke: color,
317
+ strokeWidth: "2"
318
+ }
319
+ ),
320
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
321
+ "circle",
322
+ {
323
+ cx: points[activeIndex].x,
324
+ cy: points[activeIndex].y,
325
+ r: "5",
326
+ fill: color,
327
+ className: "chart-point-active"
328
+ }
329
+ )
330
+ ] }, di);
331
+ }),
332
+ guideX !== null && /* @__PURE__ */ jsx(
333
+ "line",
241
334
  {
242
- d: areaD,
243
- fill: `url(#${gradientId})`,
244
- className: "chart-area",
245
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
335
+ x1: guideX,
336
+ y1: PADDING.top,
337
+ x2: guideX,
338
+ y2: PADDING.top + chartH,
339
+ className: "chart-crosshair"
246
340
  }
247
341
  ),
342
+ activeIndex !== null && activeX !== null && /* @__PURE__ */ jsx("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ jsxs("div", { className: "chart-crosshair-label", children: [
343
+ labels[activeIndex],
344
+ " \u2014 ",
345
+ tooltipContent
346
+ ] }) }),
248
347
  /* @__PURE__ */ jsx(
249
- "polyline",
348
+ "rect",
250
349
  {
251
- ref: (el) => {
252
- lineRefs.current[di] = el;
253
- },
254
- points: polyPoints,
255
- fill: "none",
256
- stroke: color,
257
- strokeWidth: "2"
350
+ x: PADDING.left,
351
+ y: PADDING.top,
352
+ width: chartW,
353
+ height: chartH,
354
+ fill: "transparent",
355
+ style: { cursor: "crosshair" }
258
356
  }
259
- ),
260
- showPoints && points.map((p, i) => /* @__PURE__ */ jsx(
261
- "circle",
262
- {
263
- cx: p.x,
264
- cy: p.y,
265
- r: "4",
266
- fill: color,
267
- className: "chart-point",
268
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
269
- onMouseMove: onMove,
270
- onMouseLeave: onLeave
271
- },
272
- i
273
- ))
274
- ] }, di);
275
- })
276
- ] });
357
+ )
358
+ ]
359
+ }
360
+ );
277
361
  });
278
362
  LineChart.displayName = "LineChart";
279
363
  var CurveChart = React.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
@@ -295,8 +379,9 @@ var CurveChart = React.memo(({ data, labels, width, height, animate, onHover, on
295
379
  ),
296
380
  [entries, count, chartW, chartH, maxVal]
297
381
  );
298
- const showPoints = count <= 100;
299
382
  const lineRefs = React.useRef([]);
383
+ const curveClipRef = React.useRef(null);
384
+ const { activeIndex, mouseX, handleMouseMove, handleMouseLeave, tooltipContent } = useCrosshair(seriesPoints, entries, labels, chartH);
300
385
  React.useEffect(() => {
301
386
  if (!animate) return;
302
387
  lineRefs.current.forEach((el) => {
@@ -309,61 +394,110 @@ var CurveChart = React.memo(({ data, labels, width, height, animate, onHover, on
309
394
  el.style.strokeDashoffset = "0";
310
395
  });
311
396
  });
312
- }, [animate, seriesPoints]);
313
- return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
314
- /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
315
- /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
316
- entries.map(([key], di) => {
317
- const palette = getPalette(LINE_BAR_PALETTES, di, key);
318
- const color = palette[2];
319
- const areaColor = palette[0];
320
- const points = seriesPoints[di];
321
- const gradientId = `curve-gradient-${di}`;
322
- const linePath = toSmoothPath(points);
323
- const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
324
- return /* @__PURE__ */ jsxs("g", { children: [
325
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
326
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
327
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
328
- ] }) }),
329
- /* @__PURE__ */ jsx(
330
- "path",
397
+ if (curveClipRef.current) {
398
+ curveClipRef.current.setAttribute("width", "0");
399
+ requestAnimationFrame(() => {
400
+ if (curveClipRef.current) {
401
+ curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
402
+ curveClipRef.current.setAttribute("width", `${width}`);
403
+ }
404
+ });
405
+ }
406
+ }, [animate, seriesPoints, width]);
407
+ const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
408
+ const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
409
+ const curveClipId = "curve-area-clip";
410
+ return /* @__PURE__ */ jsxs(
411
+ "svg",
412
+ {
413
+ viewBox: `0 0 ${width} ${height}`,
414
+ className: "chart-svg",
415
+ onMouseMove: (e) => {
416
+ handleMouseMove(e);
417
+ onMove(e);
418
+ },
419
+ onMouseLeave: () => {
420
+ handleMouseLeave();
421
+ onLeave();
422
+ },
423
+ children: [
424
+ animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
425
+ /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
426
+ /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
427
+ entries.map(([key], di) => {
428
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
429
+ const color = palette[2];
430
+ const areaColor = palette[0];
431
+ const points = seriesPoints[di];
432
+ const gradientId = `curve-gradient-${di}`;
433
+ const linePath = toSmoothPath(points);
434
+ const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
435
+ return /* @__PURE__ */ jsxs("g", { children: [
436
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
437
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
438
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
439
+ ] }) }),
440
+ /* @__PURE__ */ jsx(
441
+ "path",
442
+ {
443
+ d: areaPath,
444
+ fill: `url(#${gradientId})`,
445
+ clipPath: animate ? `url(#${curveClipId})` : void 0
446
+ }
447
+ ),
448
+ /* @__PURE__ */ jsx(
449
+ "path",
450
+ {
451
+ ref: (el) => {
452
+ lineRefs.current[di] = el;
453
+ },
454
+ d: linePath,
455
+ fill: "none",
456
+ stroke: color,
457
+ strokeWidth: "2"
458
+ }
459
+ ),
460
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
461
+ "circle",
462
+ {
463
+ cx: points[activeIndex].x,
464
+ cy: points[activeIndex].y,
465
+ r: "5",
466
+ fill: color,
467
+ className: "chart-point-active"
468
+ }
469
+ )
470
+ ] }, di);
471
+ }),
472
+ guideX !== null && /* @__PURE__ */ jsx(
473
+ "line",
331
474
  {
332
- d: areaPath,
333
- fill: `url(#${gradientId})`,
334
- className: "chart-area",
335
- style: animate ? { animationDelay: "600ms" } : { opacity: 1 }
475
+ x1: guideX,
476
+ y1: PADDING.top,
477
+ x2: guideX,
478
+ y2: PADDING.top + chartH,
479
+ className: "chart-crosshair"
336
480
  }
337
481
  ),
482
+ activeIndex !== null && activeX !== null && /* @__PURE__ */ jsx("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ jsxs("div", { className: "chart-crosshair-label", children: [
483
+ labels[activeIndex],
484
+ " \u2014 ",
485
+ tooltipContent
486
+ ] }) }),
338
487
  /* @__PURE__ */ jsx(
339
- "path",
488
+ "rect",
340
489
  {
341
- ref: (el) => {
342
- lineRefs.current[di] = el;
343
- },
344
- d: linePath,
345
- fill: "none",
346
- stroke: color,
347
- strokeWidth: "2"
490
+ x: PADDING.left,
491
+ y: PADDING.top,
492
+ width: chartW,
493
+ height: chartH,
494
+ fill: "transparent",
495
+ style: { cursor: "crosshair" }
348
496
  }
349
- ),
350
- showPoints && points.map((p, i) => /* @__PURE__ */ jsx(
351
- "circle",
352
- {
353
- cx: p.x,
354
- cy: p.y,
355
- r: "4",
356
- fill: color,
357
- className: "chart-point",
358
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
359
- onMouseMove: onMove,
360
- onMouseLeave: onLeave
361
- },
362
- i
363
- ))
364
- ] }, di);
365
- })
366
- ] });
497
+ )
498
+ ]
499
+ }
500
+ );
367
501
  });
368
502
  CurveChart.displayName = "CurveChart";
369
503
  var BarChart = React.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
@@ -5,6 +5,10 @@
5
5
  height: 100%;
6
6
  position: relative;
7
7
  overflow: auto;
8
+ scrollbar-width: none;
9
+ }
10
+ .lib-xplat-table-wrapper::-webkit-scrollbar {
11
+ display: none;
8
12
  }
9
13
  .lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > th,
10
14
  .lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > td,