@marcoschwartz/lite-ui 0.11.0 → 0.16.0

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.js CHANGED
@@ -3333,6 +3333,7 @@ var RichTextEditor = ({
3333
3333
  const [showImageModal, setShowImageModal] = (0, import_react20.useState)(false);
3334
3334
  const [imageUrl, setImageUrl] = (0, import_react20.useState)("");
3335
3335
  const [imageAlt, setImageAlt] = (0, import_react20.useState)("");
3336
+ const savedSelection = (0, import_react20.useRef)(null);
3336
3337
  (0, import_react20.useLayoutEffect)(() => {
3337
3338
  const styleId = "rich-text-editor-styles";
3338
3339
  if (!document.getElementById(styleId)) {
@@ -3359,6 +3360,15 @@ var RichTextEditor = ({
3359
3360
  font-weight: bold;
3360
3361
  margin: 0.83em 0;
3361
3362
  }
3363
+ [contenteditable] p {
3364
+ margin: 0.5em 0;
3365
+ }
3366
+ [contenteditable] p:first-child {
3367
+ margin-top: 0;
3368
+ }
3369
+ [contenteditable] p:last-child {
3370
+ margin-bottom: 0;
3371
+ }
3362
3372
  [contenteditable] ul, [contenteditable] ol {
3363
3373
  margin: 1em 0;
3364
3374
  padding-left: 2em;
@@ -3387,11 +3397,60 @@ var RichTextEditor = ({
3387
3397
  }
3388
3398
  }, []);
3389
3399
  const isInitialRender = (0, import_react20.useRef)(true);
3400
+ const isInternalUpdate = (0, import_react20.useRef)(false);
3390
3401
  (0, import_react20.useEffect)(() => {
3391
- if (!isInitialRender.current && editorRef.current && editorRef.current.innerHTML !== value) {
3402
+ if (isInitialRender.current && editorRef.current) {
3392
3403
  editorRef.current.innerHTML = value;
3404
+ isInitialRender.current = false;
3405
+ try {
3406
+ document.execCommand("defaultParagraphSeparator", false, "p");
3407
+ } catch (e) {
3408
+ console.warn("Could not set defaultParagraphSeparator", e);
3409
+ }
3393
3410
  }
3394
- isInitialRender.current = false;
3411
+ }, []);
3412
+ (0, import_react20.useEffect)(() => {
3413
+ if (!isInitialRender.current && !isInternalUpdate.current && editorRef.current) {
3414
+ const currentHtml = editorRef.current.innerHTML;
3415
+ if (currentHtml !== value) {
3416
+ const selection = window.getSelection();
3417
+ const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
3418
+ const preSelectionRange = range ? range.cloneRange() : null;
3419
+ if (preSelectionRange && editorRef.current.contains(preSelectionRange.startContainer)) {
3420
+ preSelectionRange.selectNodeContents(editorRef.current);
3421
+ preSelectionRange.setEnd(range.startContainer, range.startOffset);
3422
+ const start = preSelectionRange.toString().length;
3423
+ editorRef.current.innerHTML = value;
3424
+ const textNodes = [];
3425
+ const getTextNodes = (node) => {
3426
+ if (node.nodeType === Node.TEXT_NODE) {
3427
+ textNodes.push(node);
3428
+ } else {
3429
+ node.childNodes.forEach(getTextNodes);
3430
+ }
3431
+ };
3432
+ getTextNodes(editorRef.current);
3433
+ let charCount = 0;
3434
+ let foundStart = false;
3435
+ for (const textNode of textNodes) {
3436
+ const textLength = textNode.textContent?.length || 0;
3437
+ if (!foundStart && charCount + textLength >= start) {
3438
+ const newRange = document.createRange();
3439
+ newRange.setStart(textNode, start - charCount);
3440
+ newRange.collapse(true);
3441
+ selection?.removeAllRanges();
3442
+ selection?.addRange(newRange);
3443
+ foundStart = true;
3444
+ break;
3445
+ }
3446
+ charCount += textLength;
3447
+ }
3448
+ } else {
3449
+ editorRef.current.innerHTML = value;
3450
+ }
3451
+ }
3452
+ }
3453
+ isInternalUpdate.current = false;
3395
3454
  }, [value]);
3396
3455
  const updateActiveFormats = (0, import_react20.useCallback)(() => {
3397
3456
  const formats = /* @__PURE__ */ new Set();
@@ -3412,10 +3471,25 @@ var RichTextEditor = ({
3412
3471
  }, []);
3413
3472
  const handleInput = (0, import_react20.useCallback)(() => {
3414
3473
  if (editorRef.current && onChange) {
3474
+ isInternalUpdate.current = true;
3415
3475
  onChange(editorRef.current.innerHTML);
3416
3476
  }
3417
3477
  updateActiveFormats();
3418
3478
  }, [onChange, updateActiveFormats]);
3479
+ const handleFocus = (0, import_react20.useCallback)(() => {
3480
+ setIsFocused(true);
3481
+ if (editorRef.current && (!editorRef.current.innerHTML || editorRef.current.innerHTML === "")) {
3482
+ editorRef.current.innerHTML = "<p><br></p>";
3483
+ const selection = window.getSelection();
3484
+ const range = document.createRange();
3485
+ if (editorRef.current.firstChild) {
3486
+ range.setStart(editorRef.current.firstChild, 0);
3487
+ range.collapse(true);
3488
+ selection?.removeAllRanges();
3489
+ selection?.addRange(range);
3490
+ }
3491
+ }
3492
+ }, []);
3419
3493
  const handleFormat = (0, import_react20.useCallback)((command) => {
3420
3494
  if (disabled) return;
3421
3495
  document.execCommand(command, false);
@@ -3439,16 +3513,29 @@ var RichTextEditor = ({
3439
3513
  }, [disabled, updateActiveFormats, handleInput]);
3440
3514
  const handleLink = (0, import_react20.useCallback)(() => {
3441
3515
  if (disabled) return;
3516
+ const selection = window.getSelection();
3517
+ if (selection && selection.rangeCount > 0) {
3518
+ savedSelection.current = selection.getRangeAt(0).cloneRange();
3519
+ }
3442
3520
  setShowLinkModal(true);
3443
3521
  }, [disabled]);
3444
3522
  const insertLink = (0, import_react20.useCallback)(() => {
3445
- if (linkUrl) {
3446
- document.execCommand("createLink", false, linkUrl);
3447
- setShowLinkModal(false);
3448
- setLinkUrl("");
3449
- editorRef.current?.focus();
3450
- handleInput();
3523
+ if (!linkUrl || !editorRef.current) return;
3524
+ const selection = window.getSelection();
3525
+ if (savedSelection.current && selection) {
3526
+ try {
3527
+ selection.removeAllRanges();
3528
+ selection.addRange(savedSelection.current);
3529
+ document.execCommand("createLink", false, linkUrl);
3530
+ savedSelection.current = null;
3531
+ } catch (e) {
3532
+ console.warn("Could not insert link at saved position", e);
3533
+ }
3451
3534
  }
3535
+ setShowLinkModal(false);
3536
+ setLinkUrl("");
3537
+ editorRef.current?.focus();
3538
+ handleInput();
3452
3539
  }, [linkUrl, handleInput]);
3453
3540
  const handleCode = (0, import_react20.useCallback)(() => {
3454
3541
  if (disabled) return;
@@ -3468,33 +3555,82 @@ var RichTextEditor = ({
3468
3555
  }, [disabled, handleInput]);
3469
3556
  const handleImage = (0, import_react20.useCallback)(() => {
3470
3557
  if (disabled) return;
3558
+ const selection = window.getSelection();
3559
+ if (selection && selection.rangeCount > 0) {
3560
+ savedSelection.current = selection.getRangeAt(0).cloneRange();
3561
+ }
3471
3562
  setShowImageModal(true);
3472
3563
  }, [disabled]);
3473
3564
  const insertImage = (0, import_react20.useCallback)(() => {
3474
- if (!imageUrl) return;
3565
+ if (!imageUrl || !editorRef.current) return;
3566
+ editorRef.current.focus();
3475
3567
  const img = document.createElement("img");
3476
3568
  img.src = imageUrl;
3477
3569
  img.alt = imageAlt || "";
3478
3570
  img.style.maxWidth = "100%";
3479
3571
  img.style.height = "auto";
3480
3572
  const selection = window.getSelection();
3481
- if (selection && selection.rangeCount > 0) {
3482
- const range = selection.getRangeAt(0);
3483
- range.deleteContents();
3484
- range.insertNode(img);
3485
- range.setStartAfter(img);
3486
- range.setEndAfter(img);
3487
- selection.removeAllRanges();
3488
- selection.addRange(range);
3489
- } else if (editorRef.current) {
3490
- editorRef.current.appendChild(img);
3573
+ if (savedSelection.current && selection && editorRef.current.contains(savedSelection.current.commonAncestorContainer)) {
3574
+ try {
3575
+ selection.removeAllRanges();
3576
+ selection.addRange(savedSelection.current);
3577
+ savedSelection.current.deleteContents();
3578
+ savedSelection.current.insertNode(img);
3579
+ const br = document.createElement("br");
3580
+ savedSelection.current.setStartAfter(img);
3581
+ savedSelection.current.insertNode(br);
3582
+ savedSelection.current.setStartAfter(br);
3583
+ savedSelection.current.collapse(true);
3584
+ selection.removeAllRanges();
3585
+ selection.addRange(savedSelection.current);
3586
+ savedSelection.current = null;
3587
+ } catch (e) {
3588
+ console.warn("Could not insert at saved position", e);
3589
+ if (selection.rangeCount > 0) {
3590
+ const range = selection.getRangeAt(0);
3591
+ if (editorRef.current.contains(range.commonAncestorContainer)) {
3592
+ range.insertNode(img);
3593
+ const br = document.createElement("br");
3594
+ range.setStartAfter(img);
3595
+ range.insertNode(br);
3596
+ range.setStartAfter(br);
3597
+ range.collapse(true);
3598
+ } else {
3599
+ editorRef.current.appendChild(img);
3600
+ editorRef.current.appendChild(document.createElement("br"));
3601
+ }
3602
+ } else {
3603
+ editorRef.current.appendChild(img);
3604
+ editorRef.current.appendChild(document.createElement("br"));
3605
+ }
3606
+ }
3607
+ } else {
3608
+ if (selection && selection.rangeCount > 0) {
3609
+ const range = selection.getRangeAt(0);
3610
+ if (editorRef.current.contains(range.commonAncestorContainer)) {
3611
+ range.deleteContents();
3612
+ range.insertNode(img);
3613
+ const br = document.createElement("br");
3614
+ range.setStartAfter(img);
3615
+ range.insertNode(br);
3616
+ range.setStartAfter(br);
3617
+ range.collapse(true);
3618
+ selection.removeAllRanges();
3619
+ selection.addRange(range);
3620
+ } else {
3621
+ editorRef.current.appendChild(img);
3622
+ editorRef.current.appendChild(document.createElement("br"));
3623
+ }
3624
+ } else {
3625
+ editorRef.current.appendChild(img);
3626
+ editorRef.current.appendChild(document.createElement("br"));
3627
+ }
3491
3628
  }
3492
3629
  setShowImageModal(false);
3493
3630
  setImageUrl("");
3494
3631
  setImageAlt("");
3495
- editorRef.current?.focus();
3496
3632
  handleInput();
3497
- }, [imageUrl, imageAlt, handleInput, disabled]);
3633
+ }, [imageUrl, imageAlt, handleInput]);
3498
3634
  const getButtonClass = (isActive) => {
3499
3635
  const baseClass = themeName === "minimalistic" ? "border border-white text-white transition-colors" : "border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 transition-colors";
3500
3636
  const activeClass = themeName === "minimalistic" ? "bg-white text-black" : "bg-blue-100 dark:bg-blue-900 border-blue-500 dark:border-blue-400";
@@ -3658,11 +3794,10 @@ var RichTextEditor = ({
3658
3794
  ref: editorRef,
3659
3795
  contentEditable: !disabled,
3660
3796
  onInput: handleInput,
3661
- onFocus: () => setIsFocused(true),
3797
+ onFocus: handleFocus,
3662
3798
  onBlur: () => setIsFocused(false),
3663
3799
  onMouseUp: updateActiveFormats,
3664
3800
  onKeyUp: updateActiveFormats,
3665
- dangerouslySetInnerHTML: { __html: value },
3666
3801
  className: `
3667
3802
  w-full px-4 py-3 rounded-b-lg outline-none overflow-y-auto
3668
3803
  ${editorBaseClass} ${focusClass} ${errorClass}
@@ -5151,8 +5286,10 @@ var defaultColors = [
5151
5286
  ];
5152
5287
  var LineChart = ({
5153
5288
  data,
5154
- width = 600,
5155
- height = 400,
5289
+ width: providedWidth,
5290
+ height: providedHeight,
5291
+ aspectRatio = 16 / 9,
5292
+ responsive = true,
5156
5293
  showGrid = true,
5157
5294
  showXAxis = true,
5158
5295
  showYAxis = true,
@@ -5163,12 +5300,32 @@ var LineChart = ({
5163
5300
  strokeWidth = 2,
5164
5301
  className = "",
5165
5302
  xAxisLabel,
5166
- yAxisLabel
5303
+ yAxisLabel,
5304
+ baseFontSize = 14
5167
5305
  }) => {
5306
+ const viewBoxWidth = 1e3;
5307
+ const viewBoxHeight = 600;
5308
+ const containerRef = import_react26.default.useRef(null);
5309
+ const svgRef = import_react26.default.useRef(null);
5310
+ const [tooltipPosition, setTooltipPosition] = import_react26.default.useState(null);
5168
5311
  const [hoveredPoint, setHoveredPoint] = import_react26.default.useState(null);
5169
- const padding = { top: 20, right: 20, bottom: showXAxis ? 60 : 20, left: showYAxis ? 60 : 20 };
5170
- const chartWidth = width - padding.left - padding.right;
5171
- const chartHeight = height - padding.top - padding.bottom;
5312
+ const chartId = import_react26.default.useId();
5313
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5314
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5315
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5316
+ const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
5317
+ const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
5318
+ const padding = {
5319
+ top: 50,
5320
+ right: 50,
5321
+ bottom: showXAxis ? 120 : 50,
5322
+ left: showYAxis ? 130 : 50
5323
+ };
5324
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
5325
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5326
+ const gridLabelFontSize = viewBoxWidth * 0.045;
5327
+ const axisLabelFontSize = viewBoxWidth * 0.05;
5328
+ const titleFontSize = viewBoxWidth * 0.055;
5172
5329
  const allPoints = data.flatMap((series) => series.data);
5173
5330
  const allYValues = allPoints.map((p) => p.y);
5174
5331
  const firstXValue = data[0]?.data[0]?.x;
@@ -5181,7 +5338,7 @@ var LineChart = ({
5181
5338
  const minY = Math.min(0, ...allYValues);
5182
5339
  const maxY = Math.max(...allYValues);
5183
5340
  const yRange = maxY - minY;
5184
- const yMin = minY - yRange * 0.1;
5341
+ const yMin = Math.max(0, minY - yRange * 0.1);
5185
5342
  const yMax = maxY + yRange * 0.1;
5186
5343
  const scaleX = (x, index) => {
5187
5344
  const numX = isStringX ? index : x;
@@ -5228,28 +5385,32 @@ var LineChart = ({
5228
5385
  x2: chartWidth,
5229
5386
  y2: y,
5230
5387
  stroke: "currentColor",
5231
- strokeWidth: "1",
5388
+ strokeWidth: "0.5",
5232
5389
  className: "text-gray-200 dark:text-gray-700",
5233
- strokeDasharray: "2,2"
5390
+ strokeDasharray: "4,4"
5234
5391
  }
5235
5392
  ),
5236
5393
  showYAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5237
5394
  "text",
5238
5395
  {
5239
- x: -10,
5240
- y: y + 4,
5396
+ x: -25,
5397
+ y,
5241
5398
  textAnchor: "end",
5242
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5399
+ dominantBaseline: "middle",
5400
+ fontSize: gridLabelFontSize,
5401
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5243
5402
  children: yValue.toFixed(1)
5244
5403
  }
5245
5404
  )
5246
5405
  ] }, `grid-h-${i}`)
5247
5406
  );
5248
5407
  }
5249
- const numXGridLines = Math.min(allPoints.length, 6);
5408
+ const numXGridLines = Math.max(2, Math.min(allPoints.length, 6));
5409
+ const xGridStep = numXGridLines > 1 ? 1 / (numXGridLines - 1) : 0;
5250
5410
  for (let i = 0; i < numXGridLines; i++) {
5251
- const x = chartWidth / (numXGridLines - 1) * i;
5252
- const xValue = data[0]?.data[Math.floor((data[0].data.length - 1) * (i / (numXGridLines - 1)))]?.x || "";
5411
+ const x = chartWidth * (i * xGridStep);
5412
+ const dataIndex = Math.floor((data[0]?.data.length - 1) * (i * xGridStep));
5413
+ const xValue = data[0]?.data[Math.max(0, Math.min(dataIndex, data[0].data.length - 1))]?.x || "";
5253
5414
  gridLines.push(
5254
5415
  /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { children: [
5255
5416
  /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
@@ -5260,18 +5421,20 @@ var LineChart = ({
5260
5421
  x2: x,
5261
5422
  y2: chartHeight,
5262
5423
  stroke: "currentColor",
5263
- strokeWidth: "1",
5424
+ strokeWidth: "0.5",
5264
5425
  className: "text-gray-200 dark:text-gray-700",
5265
- strokeDasharray: "2,2"
5426
+ strokeDasharray: "4,4"
5266
5427
  }
5267
5428
  ),
5268
5429
  showXAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5269
5430
  "text",
5270
5431
  {
5271
5432
  x,
5272
- y: chartHeight + 20,
5433
+ y: chartHeight + 35,
5273
5434
  textAnchor: "middle",
5274
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5435
+ dominantBaseline: "hanging",
5436
+ fontSize: gridLabelFontSize,
5437
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5275
5438
  children: xValue
5276
5439
  }
5277
5440
  )
@@ -5279,108 +5442,197 @@ var LineChart = ({
5279
5442
  );
5280
5443
  }
5281
5444
  }
5282
- return /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: `relative inline-block ${className}`, children: [
5283
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5284
- "svg",
5285
- {
5286
- width,
5287
- height,
5288
- className: "bg-white dark:bg-gray-800",
5289
- style: { overflow: "visible" },
5290
- children: /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5291
- gridLines,
5292
- data.map((series, seriesIndex) => /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5293
- "path",
5294
- {
5295
- d: generatePath(series),
5296
- fill: "none",
5297
- stroke: series.color || defaultColors[seriesIndex % defaultColors.length],
5298
- strokeWidth,
5299
- strokeLinecap: "round",
5300
- strokeLinejoin: "round"
5301
- },
5302
- `line-${seriesIndex}`
5303
- )),
5304
- showDots && data.map(
5305
- (series, seriesIndex) => series.data.map((point, pointIndex) => {
5306
- const x = scaleX(point.x, pointIndex);
5307
- const y = scaleY(point.y);
5308
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5309
- return /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5310
- "circle",
5445
+ const getTooltipPosition = import_react26.default.useCallback((x, y) => {
5446
+ if (!containerRef.current) return { x, y };
5447
+ const rect = containerRef.current.getBoundingClientRect();
5448
+ const tooltipWidth = 120;
5449
+ const tooltipHeight = 80;
5450
+ let tooltipX = x + 10;
5451
+ let tooltipY = y - 30;
5452
+ if (tooltipX + tooltipWidth > rect.width) {
5453
+ tooltipX = x - tooltipWidth - 10;
5454
+ }
5455
+ if (tooltipY + tooltipHeight > rect.height) {
5456
+ tooltipY = y + 30;
5457
+ }
5458
+ return { x: tooltipX, y: tooltipY };
5459
+ }, []);
5460
+ const containerStyle = {
5461
+ "--font-size-base": `${baseFontSize}px`,
5462
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
5463
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
5464
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
5465
+ };
5466
+ return /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)(
5467
+ "div",
5468
+ {
5469
+ ref: containerRef,
5470
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
5471
+ style: {
5472
+ ...containerStyle
5473
+ },
5474
+ children: [
5475
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("style", { children: `
5476
+ /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
5477
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
5478
+ .${axisLabelClass} { display: none; }
5479
+ .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
5480
+ .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
5481
+
5482
+ /* Tablet: Medium fonts, show axis titles, medium lines */
5483
+ @media (min-width: 640px) {
5484
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
5485
+ .${axisLabelClass} {
5486
+ font-size: ${axisLabelFontSize * 0.7}px !important;
5487
+ display: block;
5488
+ }
5489
+ .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
5490
+ .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
5491
+ }
5492
+
5493
+ /* Desktop: Small fonts, show axis titles, normal lines */
5494
+ @media (min-width: 1024px) {
5495
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
5496
+ .${axisLabelClass} {
5497
+ font-size: ${axisLabelFontSize * 0.4}px !important;
5498
+ display: block;
5499
+ }
5500
+ .${lineClass} { stroke-width: ${strokeWidth} !important; }
5501
+ .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
5502
+ }
5503
+ ` }),
5504
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5505
+ "svg",
5506
+ {
5507
+ ref: svgRef,
5508
+ width: "100%",
5509
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
5510
+ preserveAspectRatio: "xMidYMid meet",
5511
+ className: "bg-white dark:bg-gray-800 block w-full",
5512
+ style: { height: "auto", overflow: "visible" },
5513
+ children: /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5514
+ gridLines,
5515
+ data.map((series, seriesIndex) => /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5516
+ "path",
5311
5517
  {
5312
- cx: x,
5313
- cy: y,
5314
- r: isHovered ? 6 : 4,
5315
- fill: series.color || defaultColors[seriesIndex % defaultColors.length],
5316
- stroke: "white",
5317
- strokeWidth: "2",
5318
- className: "cursor-pointer transition-all",
5319
- onMouseEnter: () => showTooltip && setHoveredPoint({ seriesIndex, pointIndex, x, y }),
5320
- onMouseLeave: () => setHoveredPoint(null)
5518
+ d: generatePath(series),
5519
+ fill: "none",
5520
+ stroke: series.color || defaultColors[seriesIndex % defaultColors.length],
5521
+ strokeWidth,
5522
+ strokeLinecap: "round",
5523
+ strokeLinejoin: "round",
5524
+ className: lineClass
5321
5525
  },
5322
- `point-${seriesIndex}-${pointIndex}`
5323
- );
5324
- })
5325
- ),
5326
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5327
- "text",
5526
+ `line-${seriesIndex}`
5527
+ )),
5528
+ showDots && data.map(
5529
+ (series, seriesIndex) => series.data.map((point, pointIndex) => {
5530
+ const x = scaleX(point.x, pointIndex);
5531
+ const y = scaleY(point.y);
5532
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5533
+ return /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5534
+ "circle",
5535
+ {
5536
+ cx: x,
5537
+ cy: y,
5538
+ r: isHovered ? 5 : 3,
5539
+ fill: series.color || defaultColors[seriesIndex % defaultColors.length],
5540
+ stroke: "white",
5541
+ strokeWidth: "1.5",
5542
+ className: `cursor-pointer transition-all ${pointClass}`,
5543
+ onMouseEnter: (e) => {
5544
+ if (showTooltip && containerRef.current) {
5545
+ setHoveredPoint({ seriesIndex, pointIndex, x, y });
5546
+ const containerRect = containerRef.current.getBoundingClientRect();
5547
+ const mouseX = e.clientX - containerRect.left;
5548
+ const mouseY = e.clientY - containerRect.top;
5549
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
5550
+ }
5551
+ },
5552
+ onMouseMove: (e) => {
5553
+ if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
5554
+ const containerRect = containerRef.current.getBoundingClientRect();
5555
+ const mouseX = e.clientX - containerRect.left;
5556
+ const mouseY = e.clientY - containerRect.top;
5557
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
5558
+ }
5559
+ },
5560
+ onMouseLeave: () => {
5561
+ setHoveredPoint(null);
5562
+ setTooltipPosition(null);
5563
+ }
5564
+ },
5565
+ `point-${seriesIndex}-${pointIndex}`
5566
+ );
5567
+ })
5568
+ ),
5569
+ xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5570
+ "text",
5571
+ {
5572
+ x: chartWidth / 2,
5573
+ y: chartHeight + 80,
5574
+ textAnchor: "middle",
5575
+ dominantBaseline: "hanging",
5576
+ fontSize: axisLabelFontSize,
5577
+ fontWeight: "600",
5578
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5579
+ children: xAxisLabel
5580
+ }
5581
+ ),
5582
+ yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5583
+ "text",
5584
+ {
5585
+ x: -chartHeight / 2,
5586
+ y: -100,
5587
+ textAnchor: "middle",
5588
+ dominantBaseline: "middle",
5589
+ fontSize: axisLabelFontSize,
5590
+ fontWeight: "600",
5591
+ transform: "rotate(-90)",
5592
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5593
+ children: yAxisLabel
5594
+ }
5595
+ )
5596
+ ] })
5597
+ }
5598
+ ),
5599
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
5600
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5601
+ "div",
5328
5602
  {
5329
- x: chartWidth / 2,
5330
- y: chartHeight + 50,
5331
- textAnchor: "middle",
5332
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5333
- children: xAxisLabel
5603
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
5604
+ style: {
5605
+ backgroundColor: series.color || defaultColors[index % defaultColors.length]
5606
+ }
5334
5607
  }
5335
5608
  ),
5336
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5337
- "text",
5338
- {
5339
- x: -chartHeight / 2,
5340
- y: -45,
5341
- textAnchor: "middle",
5342
- transform: `rotate(-90, 0, 0)`,
5343
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5344
- children: yAxisLabel
5345
- }
5346
- )
5347
- ] })
5348
- }
5349
- ),
5350
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "flex items-center gap-2", children: [
5351
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5352
- "div",
5353
- {
5354
- className: "w-4 h-4 rounded-sm",
5355
- style: {
5356
- backgroundColor: series.color || defaultColors[index % defaultColors.length]
5609
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
5610
+ ] }, `legend-${index}`)) }),
5611
+ showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)(
5612
+ "div",
5613
+ {
5614
+ className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
5615
+ style: {
5616
+ left: `${tooltipPosition.x}px`,
5617
+ top: `${tooltipPosition.y}px`,
5618
+ transform: "translateZ(0)"
5619
+ },
5620
+ children: [
5621
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5622
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "text-xs opacity-90", children: [
5623
+ "x: ",
5624
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5625
+ ] }),
5626
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "text-xs opacity-90", children: [
5627
+ "y: ",
5628
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5629
+ ] })
5630
+ ]
5357
5631
  }
5358
- }
5359
- ),
5360
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5361
- ] }, `legend-${index}`)) }),
5362
- showTooltip && hoveredPoint && /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)(
5363
- "div",
5364
- {
5365
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg text-sm pointer-events-none z-50",
5366
- style: {
5367
- left: `${padding.left + hoveredPoint.x + 10}px`,
5368
- top: `${padding.top + hoveredPoint.y - 30}px`
5369
- },
5370
- children: [
5371
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5372
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { children: [
5373
- "x: ",
5374
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5375
- ] }),
5376
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { children: [
5377
- "y: ",
5378
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5379
- ] })
5380
- ]
5381
- }
5382
- )
5383
- ] });
5632
+ )
5633
+ ]
5634
+ }
5635
+ );
5384
5636
  };
5385
5637
 
5386
5638
  // src/components/BarChart.tsx
@@ -5402,8 +5654,10 @@ var defaultColors2 = [
5402
5654
  ];
5403
5655
  var BarChart = ({
5404
5656
  data,
5405
- width = 600,
5406
- height = 400,
5657
+ width: providedWidth,
5658
+ height: providedHeight,
5659
+ aspectRatio = 16 / 9,
5660
+ responsive = true,
5407
5661
  showGrid = true,
5408
5662
  showXAxis = true,
5409
5663
  showYAxis = true,
@@ -5415,24 +5669,38 @@ var BarChart = ({
5415
5669
  barWidth = 0.8,
5416
5670
  className = "",
5417
5671
  xAxisLabel,
5418
- yAxisLabel
5672
+ yAxisLabel,
5673
+ baseFontSize = 14,
5674
+ colors = defaultColors2
5419
5675
  }) => {
5676
+ const viewBoxWidth = 1e3;
5677
+ const viewBoxHeight = 600;
5678
+ const containerRef = import_react27.default.useRef(null);
5679
+ const [tooltipPosition, setTooltipPosition] = import_react27.default.useState(null);
5420
5680
  const [hoveredBar, setHoveredBar] = import_react27.default.useState(null);
5681
+ const chartId = import_react27.default.useId();
5682
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5683
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5684
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5685
+ const barClass = `chart-bar-${chartId.replace(/:/g, "-")}`;
5421
5686
  const padding = {
5422
- top: 20,
5423
- right: 20,
5424
- bottom: showXAxis ? horizontal ? 60 : 80 : 20,
5425
- left: showYAxis ? horizontal ? 80 : 60 : 20
5687
+ top: 50,
5688
+ right: 50,
5689
+ bottom: showXAxis ? horizontal ? 120 : 140 : 50,
5690
+ left: showYAxis ? horizontal ? 140 : 130 : 50
5426
5691
  };
5427
- const chartWidth = width - padding.left - padding.right;
5428
- const chartHeight = height - padding.top - padding.bottom;
5692
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
5693
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5694
+ const gridLabelFontSize = viewBoxWidth * 0.045;
5695
+ const axisLabelFontSize = viewBoxWidth * 0.05;
5696
+ const titleFontSize = viewBoxWidth * 0.055;
5429
5697
  const allYValues = stacked ? data[0]?.data.map(
5430
5698
  (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
5431
5699
  ) || [] : data.flatMap((series) => series.data.map((p) => p.y));
5432
5700
  const minY = Math.min(0, ...allYValues);
5433
5701
  const maxY = Math.max(...allYValues);
5434
5702
  const yRange = maxY - minY;
5435
- const yMin = minY - yRange * 0.1;
5703
+ const yMin = Math.max(0, minY - yRange * 0.1);
5436
5704
  const yMax = maxY + yRange * 0.1;
5437
5705
  const numBars = data[0]?.data.length || 0;
5438
5706
  const numSeries = data.length;
@@ -5459,18 +5727,20 @@ var BarChart = ({
5459
5727
  x2: chartWidth,
5460
5728
  y2: y,
5461
5729
  stroke: "currentColor",
5462
- strokeWidth: "1",
5730
+ strokeWidth: "0.5",
5463
5731
  className: "text-gray-200 dark:text-gray-700",
5464
- strokeDasharray: "2,2"
5732
+ strokeDasharray: "4,4"
5465
5733
  }
5466
5734
  ),
5467
5735
  showYAxis && !horizontal && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5468
5736
  "text",
5469
5737
  {
5470
- x: -10,
5471
- y: y + 4,
5738
+ x: -25,
5739
+ y,
5472
5740
  textAnchor: "end",
5473
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5741
+ dominantBaseline: "middle",
5742
+ fontSize: gridLabelFontSize,
5743
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5474
5744
  children: yValue.toFixed(0)
5475
5745
  }
5476
5746
  )
@@ -5519,12 +5789,20 @@ var BarChart = ({
5519
5789
  x: xPos,
5520
5790
  y: yPos,
5521
5791
  width: barW,
5522
- height: barHeight,
5523
- fill: data[seriesIndex].color || defaultColors2[seriesIndex % defaultColors2.length],
5524
- className: "cursor-pointer transition-opacity",
5792
+ height: Math.abs(scaleY(0) - yPos),
5793
+ fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5794
+ className: `cursor-pointer transition-opacity ${barClass}`,
5525
5795
  opacity: isHovered ? 0.8 : 1,
5526
- onMouseEnter: () => showTooltip && setHoveredBar({ seriesIndex, barIndex, x: xPos + barW / 2, y: yPos }),
5527
- onMouseLeave: () => setHoveredBar(null)
5796
+ onMouseEnter: () => {
5797
+ if (showTooltip) {
5798
+ setHoveredBar({ seriesIndex, barIndex, x: xPos + barW / 2, y: yPos });
5799
+ setTooltipPosition(getTooltipPosition(padding.left + xPos + barW / 2, padding.top + yPos));
5800
+ }
5801
+ },
5802
+ onMouseLeave: () => {
5803
+ setHoveredBar(null);
5804
+ setTooltipPosition(null);
5805
+ }
5528
5806
  },
5529
5807
  `bar-${seriesIndex}-${barIndex}`
5530
5808
  )
@@ -5549,8 +5827,8 @@ var BarChart = ({
5549
5827
  y: yPos,
5550
5828
  width: actualBarWidth,
5551
5829
  height: barHeight,
5552
- fill: data[seriesIndex].color || defaultColors2[seriesIndex % defaultColors2.length],
5553
- className: "cursor-pointer transition-opacity",
5830
+ fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5831
+ className: `cursor-pointer transition-opacity ${barClass}`,
5554
5832
  opacity: isHovered ? 0.8 : 1,
5555
5833
  onMouseEnter: () => showTooltip && setHoveredBar({ seriesIndex, barIndex, x: xPos + actualBarWidth / 2, y: yPos }),
5556
5834
  onMouseLeave: () => setHoveredBar(null)
@@ -5564,9 +5842,12 @@ var BarChart = ({
5564
5842
  "text",
5565
5843
  {
5566
5844
  x: xPos + actualBarWidth / 2,
5567
- y: yPos - 5,
5845
+ y: yPos - 10,
5568
5846
  textAnchor: "middle",
5569
- className: "text-xs fill-gray-700 dark:fill-gray-300 font-medium",
5847
+ dominantBaseline: "middle",
5848
+ fontSize: "11",
5849
+ fontWeight: "600",
5850
+ className: "fill-gray-700 dark:fill-gray-300",
5570
5851
  children: point.y
5571
5852
  },
5572
5853
  `value-${seriesIndex}-${barIndex}`
@@ -5584,81 +5865,145 @@ var BarChart = ({
5584
5865
  "text",
5585
5866
  {
5586
5867
  x: xPos,
5587
- y: chartHeight + 20,
5868
+ y: chartHeight + 35,
5588
5869
  textAnchor: "middle",
5589
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5870
+ dominantBaseline: "hanging",
5871
+ fontSize: gridLabelFontSize,
5872
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5590
5873
  children: point.x
5591
5874
  },
5592
5875
  `x-label-${i}`
5593
5876
  );
5594
5877
  });
5595
- return /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: `relative inline-block ${className}`, children: [
5596
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5597
- "svg",
5598
- {
5599
- width,
5600
- height,
5601
- className: "bg-white dark:bg-gray-800",
5602
- style: { overflow: "visible" },
5603
- children: /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5604
- gridLines,
5605
- renderBars(),
5606
- showXAxis && xAxisLabels,
5607
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5608
- "text",
5878
+ const getTooltipPosition = import_react27.default.useCallback((x, y) => {
5879
+ if (!containerRef.current) return { x, y };
5880
+ const rect = containerRef.current.getBoundingClientRect();
5881
+ const tooltipWidth = 140;
5882
+ const tooltipHeight = 80;
5883
+ let tooltipX = x + 10;
5884
+ let tooltipY = y - 30;
5885
+ if (tooltipX + tooltipWidth > rect.width) {
5886
+ tooltipX = x - tooltipWidth - 10;
5887
+ }
5888
+ if (tooltipY + tooltipHeight > rect.height) {
5889
+ tooltipY = y + 30;
5890
+ }
5891
+ return { x: tooltipX, y: tooltipY };
5892
+ }, []);
5893
+ const containerStyle = {
5894
+ "--font-size-base": `${baseFontSize}px`,
5895
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
5896
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
5897
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
5898
+ };
5899
+ return /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
5900
+ "div",
5901
+ {
5902
+ ref: containerRef,
5903
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
5904
+ style: {
5905
+ ...containerStyle
5906
+ },
5907
+ children: [
5908
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("style", { children: `
5909
+ /* Mobile: Large fonts, hidden axis titles, thicker bars (default) */
5910
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
5911
+ .${axisLabelClass} { display: none; }
5912
+
5913
+ /* Tablet: Medium fonts, show axis titles */
5914
+ @media (min-width: 640px) {
5915
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
5916
+ .${axisLabelClass} {
5917
+ font-size: ${axisLabelFontSize * 0.7}px !important;
5918
+ display: block;
5919
+ }
5920
+ }
5921
+
5922
+ /* Desktop: Small fonts, show axis titles */
5923
+ @media (min-width: 1024px) {
5924
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
5925
+ .${axisLabelClass} {
5926
+ font-size: ${axisLabelFontSize * 0.4}px !important;
5927
+ display: block;
5928
+ }
5929
+ }
5930
+ ` }),
5931
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5932
+ "svg",
5933
+ {
5934
+ width: "100%",
5935
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
5936
+ preserveAspectRatio: "xMidYMid meet",
5937
+ className: "bg-white dark:bg-gray-800 block w-full",
5938
+ style: { height: "auto", overflow: "visible" },
5939
+ children: /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5940
+ gridLines,
5941
+ renderBars(),
5942
+ showXAxis && xAxisLabels,
5943
+ xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5944
+ "text",
5945
+ {
5946
+ x: chartWidth / 2,
5947
+ y: chartHeight + 80,
5948
+ textAnchor: "middle",
5949
+ dominantBaseline: "hanging",
5950
+ fontSize: axisLabelFontSize,
5951
+ fontWeight: "600",
5952
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5953
+ children: xAxisLabel
5954
+ }
5955
+ ),
5956
+ yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5957
+ "text",
5958
+ {
5959
+ x: -chartHeight / 2,
5960
+ y: -100,
5961
+ textAnchor: "middle",
5962
+ dominantBaseline: "middle",
5963
+ fontSize: axisLabelFontSize,
5964
+ fontWeight: "600",
5965
+ transform: "rotate(-90)",
5966
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5967
+ children: yAxisLabel
5968
+ }
5969
+ )
5970
+ ] })
5971
+ }
5972
+ ),
5973
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
5974
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5975
+ "div",
5609
5976
  {
5610
- x: chartWidth / 2,
5611
- y: chartHeight + 50,
5612
- textAnchor: "middle",
5613
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5614
- children: xAxisLabel
5977
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
5978
+ style: {
5979
+ backgroundColor: series.color || colors[index % colors.length]
5980
+ }
5615
5981
  }
5616
5982
  ),
5617
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5618
- "text",
5619
- {
5620
- x: -chartHeight / 2,
5621
- y: -45,
5622
- textAnchor: "middle",
5623
- transform: `rotate(-90, 0, 0)`,
5624
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5625
- children: yAxisLabel
5626
- }
5627
- )
5628
- ] })
5629
- }
5630
- ),
5631
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: "flex items-center gap-2", children: [
5632
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5633
- "div",
5634
- {
5635
- className: "w-4 h-4 rounded-sm",
5636
- style: {
5637
- backgroundColor: series.color || defaultColors2[index % defaultColors2.length]
5983
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
5984
+ ] }, `legend-${index}`)) }),
5985
+ showTooltip && hoveredBar && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
5986
+ "div",
5987
+ {
5988
+ className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
5989
+ style: {
5990
+ left: `${tooltipPosition.x}px`,
5991
+ top: `${tooltipPosition.y}px`,
5992
+ transform: "translateZ(0)"
5993
+ },
5994
+ children: [
5995
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "font-semibold", children: data[hoveredBar.seriesIndex].name }),
5996
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: "text-xs opacity-90", children: [
5997
+ data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].x,
5998
+ ": ",
5999
+ data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].y
6000
+ ] })
6001
+ ]
5638
6002
  }
5639
- }
5640
- ),
5641
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5642
- ] }, `legend-${index}`)) }),
5643
- showTooltip && hoveredBar && /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
5644
- "div",
5645
- {
5646
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg text-sm pointer-events-none z-50",
5647
- style: {
5648
- left: `${padding.left + hoveredBar.x + 10}px`,
5649
- top: `${padding.top + hoveredBar.y - 30}px`
5650
- },
5651
- children: [
5652
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "font-semibold", children: data[hoveredBar.seriesIndex].name }),
5653
- /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { children: [
5654
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].x,
5655
- ": ",
5656
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].y
5657
- ] })
5658
- ]
5659
- }
5660
- )
5661
- ] });
6003
+ )
6004
+ ]
6005
+ }
6006
+ );
5662
6007
  };
5663
6008
 
5664
6009
  // src/components/AreaChart.tsx
@@ -5680,8 +6025,10 @@ var defaultColors3 = [
5680
6025
  ];
5681
6026
  var AreaChart = ({
5682
6027
  data,
5683
- width = 600,
5684
- height = 400,
6028
+ width: providedWidth,
6029
+ height: providedHeight,
6030
+ aspectRatio = 16 / 9,
6031
+ responsive = true,
5685
6032
  showGrid = true,
5686
6033
  showXAxis = true,
5687
6034
  showYAxis = true,
@@ -5694,12 +6041,32 @@ var AreaChart = ({
5694
6041
  strokeWidth = 2,
5695
6042
  className = "",
5696
6043
  xAxisLabel,
5697
- yAxisLabel
6044
+ yAxisLabel,
6045
+ baseFontSize = 14
5698
6046
  }) => {
6047
+ const viewBoxWidth = 1e3;
6048
+ const viewBoxHeight = 600;
6049
+ const containerRef = import_react28.default.useRef(null);
6050
+ const svgRef = import_react28.default.useRef(null);
6051
+ const [tooltipPosition, setTooltipPosition] = import_react28.default.useState(null);
5699
6052
  const [hoveredPoint, setHoveredPoint] = import_react28.default.useState(null);
5700
- const padding = { top: 20, right: 20, bottom: showXAxis ? 60 : 20, left: showYAxis ? 60 : 20 };
5701
- const chartWidth = width - padding.left - padding.right;
5702
- const chartHeight = height - padding.top - padding.bottom;
6053
+ const chartId = import_react28.default.useId();
6054
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
6055
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
6056
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
6057
+ const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
6058
+ const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
6059
+ const padding = {
6060
+ top: 50,
6061
+ right: 50,
6062
+ bottom: showXAxis ? 120 : 50,
6063
+ left: showYAxis ? 130 : 50
6064
+ };
6065
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
6066
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
6067
+ const gridLabelFontSize = viewBoxWidth * 0.045;
6068
+ const axisLabelFontSize = viewBoxWidth * 0.05;
6069
+ const titleFontSize = viewBoxWidth * 0.055;
5703
6070
  const allPoints = data.flatMap((series) => series.data);
5704
6071
  const allYValues = stacked ? data[0]?.data.map(
5705
6072
  (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
@@ -5713,7 +6080,7 @@ var AreaChart = ({
5713
6080
  const minY = Math.min(0, ...allYValues);
5714
6081
  const maxY = Math.max(...allYValues);
5715
6082
  const yRange = maxY - minY;
5716
- const yMin = minY - yRange * 0.1;
6083
+ const yMin = Math.max(0, minY - yRange * 0.1);
5717
6084
  const yMax = maxY + yRange * 0.1;
5718
6085
  const scaleX = (x, index) => {
5719
6086
  const numX = isStringX ? index : x;
@@ -5794,19 +6161,21 @@ var AreaChart = ({
5794
6161
  x2: chartWidth,
5795
6162
  y2: y,
5796
6163
  stroke: "currentColor",
5797
- strokeWidth: "1",
6164
+ strokeWidth: "0.5",
5798
6165
  className: "text-gray-200 dark:text-gray-700",
5799
- strokeDasharray: "2,2"
6166
+ strokeDasharray: "4,4"
5800
6167
  }
5801
6168
  ),
5802
6169
  showYAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5803
6170
  "text",
5804
6171
  {
5805
- x: -10,
5806
- y: y + 4,
6172
+ x: -25,
6173
+ y,
5807
6174
  textAnchor: "end",
5808
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5809
- children: yValue.toFixed(0)
6175
+ dominantBaseline: "middle",
6176
+ fontSize: gridLabelFontSize,
6177
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6178
+ children: yValue.toFixed(1)
5810
6179
  }
5811
6180
  )
5812
6181
  ] }, `grid-h-${i}`)
@@ -5826,18 +6195,20 @@ var AreaChart = ({
5826
6195
  x2: x,
5827
6196
  y2: chartHeight,
5828
6197
  stroke: "currentColor",
5829
- strokeWidth: "1",
6198
+ strokeWidth: "0.5",
5830
6199
  className: "text-gray-200 dark:text-gray-700",
5831
- strokeDasharray: "2,2"
6200
+ strokeDasharray: "4,4"
5832
6201
  }
5833
6202
  ),
5834
6203
  showXAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5835
6204
  "text",
5836
6205
  {
5837
6206
  x,
5838
- y: chartHeight + 20,
6207
+ y: chartHeight + 35,
5839
6208
  textAnchor: "middle",
5840
- className: "text-xs fill-gray-600 dark:fill-gray-400",
6209
+ dominantBaseline: "hanging",
6210
+ fontSize: gridLabelFontSize,
6211
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5841
6212
  children: xValue
5842
6213
  }
5843
6214
  )
@@ -5849,130 +6220,217 @@ var AreaChart = ({
5849
6220
  if (stacked) {
5850
6221
  cumulativeValues = Array(data[0]?.data.length || 0).fill(0);
5851
6222
  }
5852
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: `relative inline-block ${className}`, children: [
5853
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5854
- "svg",
5855
- {
5856
- width,
5857
- height,
5858
- className: "bg-white dark:bg-gray-800",
5859
- style: { overflow: "visible" },
5860
- children: /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5861
- gridLines,
5862
- data.map((series, seriesIndex) => {
5863
- const baselineYValues = stacked ? [...cumulativeValues] : void 0;
5864
- const areaPath = generateAreaPath(series, baselineYValues);
5865
- const linePath = generateLinePath(series, baselineYValues);
5866
- if (stacked) {
5867
- series.data.forEach((point, i) => {
5868
- cumulativeValues[i] += point.y;
5869
- });
5870
- }
5871
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { children: [
5872
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5873
- "path",
6223
+ const getTooltipPosition = import_react28.default.useCallback((x, y) => {
6224
+ if (!containerRef.current) return { x, y };
6225
+ const rect = containerRef.current.getBoundingClientRect();
6226
+ const tooltipWidth = 120;
6227
+ const tooltipHeight = 80;
6228
+ let tooltipX = x + 10;
6229
+ let tooltipY = y - 30;
6230
+ if (tooltipX + tooltipWidth > rect.width) {
6231
+ tooltipX = x - tooltipWidth - 10;
6232
+ }
6233
+ if (tooltipY + tooltipHeight > rect.height) {
6234
+ tooltipY = y + 30;
6235
+ }
6236
+ return { x: tooltipX, y: tooltipY };
6237
+ }, []);
6238
+ const containerStyle = {
6239
+ "--font-size-base": `${baseFontSize}px`,
6240
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6241
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6242
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
6243
+ };
6244
+ return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)(
6245
+ "div",
6246
+ {
6247
+ ref: containerRef,
6248
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6249
+ style: containerStyle,
6250
+ children: [
6251
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("style", { children: `
6252
+ /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
6253
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6254
+ .${axisLabelClass} { display: none; }
6255
+ .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
6256
+ .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
6257
+
6258
+ /* Tablet: Medium fonts, show axis titles, medium lines */
6259
+ @media (min-width: 640px) {
6260
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6261
+ .${axisLabelClass} {
6262
+ font-size: ${axisLabelFontSize * 0.7}px !important;
6263
+ display: block;
6264
+ }
6265
+ .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
6266
+ .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
6267
+ }
6268
+
6269
+ /* Desktop: Small fonts, show axis titles, normal lines */
6270
+ @media (min-width: 1024px) {
6271
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6272
+ .${axisLabelClass} {
6273
+ font-size: ${axisLabelFontSize * 0.4}px !important;
6274
+ display: block;
6275
+ }
6276
+ .${lineClass} { stroke-width: ${strokeWidth} !important; }
6277
+ .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
6278
+ }
6279
+ ` }),
6280
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6281
+ "svg",
6282
+ {
6283
+ ref: svgRef,
6284
+ width: "100%",
6285
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6286
+ preserveAspectRatio: "xMidYMid meet",
6287
+ className: "bg-white dark:bg-gray-800 block w-full",
6288
+ style: { height: "auto", overflow: "visible" },
6289
+ children: /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
6290
+ gridLines,
6291
+ data.map((series, seriesIndex) => {
6292
+ const baselineYValues = stacked ? [...cumulativeValues] : void 0;
6293
+ const areaPath = generateAreaPath(series, baselineYValues);
6294
+ const linePath = generateLinePath(series, baselineYValues);
6295
+ if (stacked) {
6296
+ series.data.forEach((point, i) => {
6297
+ cumulativeValues[i] += point.y;
6298
+ });
6299
+ }
6300
+ return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { children: [
6301
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6302
+ "path",
6303
+ {
6304
+ d: areaPath,
6305
+ fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6306
+ opacity: fillOpacity
6307
+ }
6308
+ ),
6309
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6310
+ "path",
6311
+ {
6312
+ d: linePath,
6313
+ fill: "none",
6314
+ stroke: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6315
+ strokeWidth,
6316
+ strokeLinecap: "round",
6317
+ strokeLinejoin: "round",
6318
+ className: lineClass
6319
+ }
6320
+ )
6321
+ ] }, `area-${seriesIndex}`);
6322
+ }),
6323
+ showDots && data.map((series, seriesIndex) => {
6324
+ const baselineYValues = stacked ? data.slice(0, seriesIndex).reduce((acc, s) => {
6325
+ return acc.map((val, i) => val + (s.data[i]?.y || 0));
6326
+ }, Array(series.data.length).fill(0)) : void 0;
6327
+ return series.data.map((point, pointIndex) => {
6328
+ const x = scaleX(point.x, pointIndex);
6329
+ const y = baselineYValues ? scaleY(baselineYValues[pointIndex] + point.y) : scaleY(point.y);
6330
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
6331
+ return /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6332
+ "circle",
6333
+ {
6334
+ cx: x,
6335
+ cy: y,
6336
+ r: isHovered ? 6 : 4,
6337
+ fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6338
+ stroke: "white",
6339
+ strokeWidth: "2",
6340
+ className: `cursor-pointer transition-all ${pointClass}`,
6341
+ onMouseEnter: (e) => {
6342
+ if (showTooltip && containerRef.current) {
6343
+ setHoveredPoint({ seriesIndex, pointIndex, x, y });
6344
+ const containerRect = containerRef.current.getBoundingClientRect();
6345
+ const mouseX = e.clientX - containerRect.left;
6346
+ const mouseY = e.clientY - containerRect.top;
6347
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6348
+ }
6349
+ },
6350
+ onMouseMove: (e) => {
6351
+ if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
6352
+ const containerRect = containerRef.current.getBoundingClientRect();
6353
+ const mouseX = e.clientX - containerRect.left;
6354
+ const mouseY = e.clientY - containerRect.top;
6355
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6356
+ }
6357
+ },
6358
+ onMouseLeave: () => {
6359
+ setHoveredPoint(null);
6360
+ setTooltipPosition(null);
6361
+ }
6362
+ },
6363
+ `point-${seriesIndex}-${pointIndex}`
6364
+ );
6365
+ });
6366
+ }),
6367
+ xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6368
+ "text",
5874
6369
  {
5875
- d: areaPath,
5876
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5877
- opacity: fillOpacity
6370
+ x: chartWidth / 2,
6371
+ y: chartHeight + 80,
6372
+ textAnchor: "middle",
6373
+ dominantBaseline: "hanging",
6374
+ fontSize: axisLabelFontSize,
6375
+ fontWeight: "600",
6376
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6377
+ children: xAxisLabel
5878
6378
  }
5879
6379
  ),
5880
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5881
- "path",
6380
+ yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6381
+ "text",
5882
6382
  {
5883
- d: linePath,
5884
- fill: "none",
5885
- stroke: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5886
- strokeWidth,
5887
- strokeLinecap: "round",
5888
- strokeLinejoin: "round"
6383
+ x: -chartHeight / 2,
6384
+ y: -100,
6385
+ textAnchor: "middle",
6386
+ dominantBaseline: "middle",
6387
+ fontSize: axisLabelFontSize,
6388
+ fontWeight: "600",
6389
+ transform: "rotate(-90)",
6390
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6391
+ children: yAxisLabel
5889
6392
  }
5890
6393
  )
5891
- ] }, `area-${seriesIndex}`);
5892
- }),
5893
- showDots && data.map((series, seriesIndex) => {
5894
- const baselineYValues = stacked ? data.slice(0, seriesIndex).reduce((acc, s) => {
5895
- return acc.map((val, i) => val + (s.data[i]?.y || 0));
5896
- }, Array(series.data.length).fill(0)) : void 0;
5897
- return series.data.map((point, pointIndex) => {
5898
- const x = scaleX(point.x, pointIndex);
5899
- const y = baselineYValues ? scaleY(baselineYValues[pointIndex] + point.y) : scaleY(point.y);
5900
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5901
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5902
- "circle",
5903
- {
5904
- cx: x,
5905
- cy: y,
5906
- r: isHovered ? 6 : 4,
5907
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5908
- stroke: "white",
5909
- strokeWidth: "2",
5910
- className: "cursor-pointer transition-all",
5911
- onMouseEnter: () => showTooltip && setHoveredPoint({ seriesIndex, pointIndex, x, y }),
5912
- onMouseLeave: () => setHoveredPoint(null)
5913
- },
5914
- `point-${seriesIndex}-${pointIndex}`
5915
- );
5916
- });
5917
- }),
5918
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5919
- "text",
6394
+ ] })
6395
+ }
6396
+ ),
6397
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
6398
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6399
+ "div",
5920
6400
  {
5921
- x: chartWidth / 2,
5922
- y: chartHeight + 50,
5923
- textAnchor: "middle",
5924
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5925
- children: xAxisLabel
6401
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
6402
+ style: {
6403
+ backgroundColor: series.color || defaultColors3[index % defaultColors3.length]
6404
+ }
5926
6405
  }
5927
6406
  ),
5928
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5929
- "text",
5930
- {
5931
- x: -chartHeight / 2,
5932
- y: -45,
5933
- textAnchor: "middle",
5934
- transform: `rotate(-90, 0, 0)`,
5935
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5936
- children: yAxisLabel
5937
- }
5938
- )
5939
- ] })
5940
- }
5941
- ),
5942
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "flex items-center gap-2", children: [
5943
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
5944
- "div",
5945
- {
5946
- className: "w-4 h-4 rounded-sm",
5947
- style: {
5948
- backgroundColor: series.color || defaultColors3[index % defaultColors3.length]
6407
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
6408
+ ] }, `legend-${index}`)) }),
6409
+ showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)(
6410
+ "div",
6411
+ {
6412
+ className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
6413
+ style: {
6414
+ left: `${tooltipPosition.x}px`,
6415
+ top: `${tooltipPosition.y}px`,
6416
+ transform: "translateZ(0)"
6417
+ },
6418
+ children: [
6419
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
6420
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "text-xs opacity-90", children: [
6421
+ "x: ",
6422
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
6423
+ ] }),
6424
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "text-xs opacity-90", children: [
6425
+ "y: ",
6426
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
6427
+ ] })
6428
+ ]
5949
6429
  }
5950
- }
5951
- ),
5952
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5953
- ] }, `legend-${index}`)) }),
5954
- showTooltip && hoveredPoint && /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)(
5955
- "div",
5956
- {
5957
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg text-sm pointer-events-none z-50",
5958
- style: {
5959
- left: `${padding.left + hoveredPoint.x + 10}px`,
5960
- top: `${padding.top + hoveredPoint.y - 30}px`
5961
- },
5962
- children: [
5963
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5964
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { children: [
5965
- "x: ",
5966
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5967
- ] }),
5968
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { children: [
5969
- "y: ",
5970
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5971
- ] })
5972
- ]
5973
- }
5974
- )
5975
- ] });
6430
+ )
6431
+ ]
6432
+ }
6433
+ );
5976
6434
  };
5977
6435
 
5978
6436
  // src/components/PieChart.tsx
@@ -5998,22 +6456,37 @@ var defaultColors4 = [
5998
6456
  ];
5999
6457
  var PieChart = ({
6000
6458
  data,
6001
- width = 400,
6002
- height = 400,
6459
+ width: providedWidth,
6460
+ height: providedHeight,
6461
+ aspectRatio = 1,
6462
+ responsive = true,
6003
6463
  showLegend = true,
6004
6464
  showLabels = true,
6005
6465
  showValues = false,
6006
6466
  showPercentages = false,
6007
6467
  donut = false,
6008
6468
  donutWidth = 60,
6009
- className = ""
6469
+ className = "",
6470
+ baseFontSize = 14
6010
6471
  }) => {
6472
+ const viewBoxWidth = 600;
6473
+ const viewBoxHeight = 600;
6474
+ const containerRef = import_react29.default.useRef(null);
6475
+ const [tooltipPosition, setTooltipPosition] = import_react29.default.useState(null);
6011
6476
  const [hoveredSlice, setHoveredSlice] = import_react29.default.useState(null);
6477
+ const chartId = import_react29.default.useId();
6478
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
6479
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
6480
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
6481
+ const sliceClass = `chart-slice-${chartId.replace(/:/g, "-")}`;
6012
6482
  const total = data.reduce((sum, item) => sum + item.value, 0);
6013
- const centerX = width / 2;
6014
- const centerY = height / 2;
6015
- const radius = Math.min(width, height) / 2 - 40;
6483
+ const centerX = viewBoxWidth / 2;
6484
+ const centerY = viewBoxHeight / 2;
6485
+ const radius = Math.min(viewBoxWidth, viewBoxHeight) / 2 - 40;
6016
6486
  const innerRadius = donut ? radius - donutWidth : 0;
6487
+ const gridLabelFontSize = viewBoxWidth * 0.045;
6488
+ const axisLabelFontSize = viewBoxWidth * 0.05;
6489
+ const titleFontSize = viewBoxWidth * 0.055;
6017
6490
  let currentAngle = -90;
6018
6491
  const slices = data.map((item, index) => {
6019
6492
  const percentage = item.value / total * 100;
@@ -6065,112 +6538,194 @@ var PieChart = ({
6065
6538
  index
6066
6539
  };
6067
6540
  });
6068
- return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: `relative inline-block ${className}`, children: [
6069
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6070
- "svg",
6071
- {
6072
- width,
6073
- height,
6074
- className: "bg-white dark:bg-gray-800",
6075
- children: [
6076
- slices.map((slice) => {
6077
- const isHovered = hoveredSlice === slice.index;
6078
- return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6541
+ const getTooltipPosition = import_react29.default.useCallback((x, y) => {
6542
+ if (!containerRef.current) return { x, y };
6543
+ const rect = containerRef.current.getBoundingClientRect();
6544
+ const tooltipWidth = 120;
6545
+ const tooltipHeight = 80;
6546
+ let tooltipX = x + 10;
6547
+ let tooltipY = y - 30;
6548
+ if (tooltipX + tooltipWidth > rect.width) {
6549
+ tooltipX = x - tooltipWidth - 10;
6550
+ }
6551
+ if (tooltipY + tooltipHeight > rect.height) {
6552
+ tooltipY = y + 30;
6553
+ }
6554
+ return { x: tooltipX, y: tooltipY };
6555
+ }, []);
6556
+ const containerStyle = {
6557
+ "--font-size-base": `${baseFontSize}px`,
6558
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6559
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6560
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`,
6561
+ "--font-size-xl": `calc(var(--font-size-base) * 1.5)`,
6562
+ "--font-size-2xl": `calc(var(--font-size-base) * 2)`
6563
+ };
6564
+ if (responsive) {
6565
+ containerStyle["--font-size-base"] = `clamp(${baseFontSize * 0.8}px, 4vw, ${baseFontSize}px)`;
6566
+ }
6567
+ return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6568
+ "div",
6569
+ {
6570
+ ref: containerRef,
6571
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6572
+ style: containerStyle,
6573
+ children: [
6574
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("style", { children: `
6575
+ /* Mobile: Large fonts (default) */
6576
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6577
+ .${axisLabelClass} { font-size: ${titleFontSize}px !important; }
6578
+
6579
+ /* Tablet: Medium fonts */
6580
+ @media (min-width: 640px) {
6581
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6582
+ .${axisLabelClass} { font-size: ${titleFontSize * 0.7}px !important; }
6583
+ }
6584
+
6585
+ /* Desktop: Small fonts */
6586
+ @media (min-width: 1024px) {
6587
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6588
+ .${axisLabelClass} { font-size: ${titleFontSize * 0.4}px !important; }
6589
+ }
6590
+ ` }),
6591
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6592
+ "svg",
6593
+ {
6594
+ width: "100%",
6595
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6596
+ preserveAspectRatio: "xMidYMid meet",
6597
+ className: "bg-white dark:bg-gray-800 block w-full",
6598
+ style: { height: "auto", overflow: "visible" },
6599
+ children: [
6600
+ slices.map((slice) => {
6601
+ const isHovered = hoveredSlice === slice.index;
6602
+ return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6603
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6604
+ "path",
6605
+ {
6606
+ d: slice.path,
6607
+ fill: slice.color,
6608
+ stroke: "white",
6609
+ strokeWidth: "2",
6610
+ className: "cursor-pointer transition-opacity",
6611
+ opacity: isHovered ? 0.8 : 1,
6612
+ onMouseEnter: (e) => {
6613
+ setHoveredSlice(slice.index);
6614
+ if (containerRef.current) {
6615
+ const rect = containerRef.current.getBoundingClientRect();
6616
+ const svgX = e.clientX - rect.left;
6617
+ const svgY = e.clientY - rect.top;
6618
+ setTooltipPosition(getTooltipPosition(svgX, svgY));
6619
+ }
6620
+ },
6621
+ onMouseLeave: () => {
6622
+ setHoveredSlice(null);
6623
+ setTooltipPosition(null);
6624
+ }
6625
+ }
6626
+ ),
6627
+ showLabels && slice.percentage > 5 && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6628
+ "text",
6629
+ {
6630
+ x: slice.labelX,
6631
+ y: slice.labelY,
6632
+ textAnchor: "middle",
6633
+ dominantBaseline: "middle",
6634
+ fontSize: gridLabelFontSize,
6635
+ fontWeight: "600",
6636
+ className: `fill-gray-700 dark:fill-gray-200 pointer-events-none ${gridLabelClass}`,
6637
+ children: [
6638
+ showPercentages && `${slice.percentage.toFixed(1)}%`,
6639
+ showPercentages && showValues && " ",
6640
+ showValues && `(${slice.value})`,
6641
+ !showPercentages && !showValues && slice.label
6642
+ ]
6643
+ }
6644
+ )
6645
+ ] }, slice.index);
6646
+ }),
6647
+ donut && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6648
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6649
+ "text",
6650
+ {
6651
+ x: centerX,
6652
+ y: centerY - 10,
6653
+ textAnchor: "middle",
6654
+ dominantBaseline: "middle",
6655
+ fontSize: titleFontSize,
6656
+ fontWeight: "700",
6657
+ className: `fill-gray-900 dark:fill-gray-100 ${axisLabelClass}`,
6658
+ children: total
6659
+ }
6660
+ ),
6661
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6662
+ "text",
6663
+ {
6664
+ x: centerX,
6665
+ y: centerY + 15,
6666
+ textAnchor: "middle",
6667
+ dominantBaseline: "middle",
6668
+ fontSize: axisLabelFontSize,
6669
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6670
+ children: "Total"
6671
+ }
6672
+ )
6673
+ ] })
6674
+ ]
6675
+ }
6676
+ ),
6677
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6678
+ "div",
6679
+ {
6680
+ className: "flex items-center gap-2 text-sm cursor-pointer",
6681
+ onMouseEnter: () => setHoveredSlice(index),
6682
+ onMouseLeave: () => setHoveredSlice(null),
6683
+ children: [
6079
6684
  /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6080
- "path",
6685
+ "div",
6081
6686
  {
6082
- d: slice.path,
6083
- fill: slice.color,
6084
- stroke: "white",
6085
- strokeWidth: "2",
6086
- className: "cursor-pointer transition-opacity",
6087
- opacity: isHovered ? 0.8 : 1,
6088
- onMouseEnter: () => setHoveredSlice(slice.index),
6089
- onMouseLeave: () => setHoveredSlice(null)
6687
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
6688
+ style: {
6689
+ backgroundColor: item.color || defaultColors4[index % defaultColors4.length]
6690
+ }
6090
6691
  }
6091
6692
  ),
6092
- showLabels && slice.percentage > 5 && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6093
- "text",
6094
- {
6095
- x: slice.labelX,
6096
- y: slice.labelY,
6097
- textAnchor: "middle",
6098
- dominantBaseline: "middle",
6099
- className: "text-xs font-semibold fill-gray-700 dark:fill-gray-200 pointer-events-none",
6100
- children: [
6101
- showPercentages && `${slice.percentage.toFixed(1)}%`,
6102
- showPercentages && showValues && " ",
6103
- showValues && `(${slice.value})`,
6104
- !showPercentages && !showValues && slice.label
6105
- ]
6106
- }
6107
- )
6108
- ] }, slice.index);
6109
- }),
6110
- donut && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6111
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6112
- "text",
6113
- {
6114
- x: centerX,
6115
- y: centerY - 10,
6116
- textAnchor: "middle",
6117
- className: "text-2xl font-bold fill-gray-900 dark:fill-gray-100",
6118
- children: total
6119
- }
6120
- ),
6121
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6122
- "text",
6123
- {
6124
- x: centerX,
6125
- y: centerY + 15,
6126
- textAnchor: "middle",
6127
- className: "text-sm fill-gray-600 dark:fill-gray-400",
6128
- children: "Total"
6129
- }
6130
- )
6131
- ] })
6132
- ]
6133
- }
6134
- ),
6135
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6136
- "div",
6137
- {
6138
- className: "flex items-center gap-2 cursor-pointer",
6139
- onMouseEnter: () => setHoveredSlice(index),
6140
- onMouseLeave: () => setHoveredSlice(null),
6141
- children: [
6142
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6143
- "div",
6144
- {
6145
- className: "w-4 h-4 rounded-sm",
6146
- style: {
6147
- backgroundColor: item.color || defaultColors4[index % defaultColors4.length]
6148
- }
6149
- }
6150
- ),
6151
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: [
6152
- item.label,
6153
- ": ",
6154
- item.value,
6155
- showPercentages && ` (${(item.value / total * 100).toFixed(1)}%)`
6156
- ] })
6157
- ]
6158
- },
6159
- `legend-${index}`
6160
- )) }),
6161
- hoveredSlice !== null && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: "absolute top-2 right-2 bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg text-sm pointer-events-none z-50", children: [
6162
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "font-semibold", children: data[hoveredSlice].label }),
6163
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { children: [
6164
- "Value: ",
6165
- data[hoveredSlice].value
6166
- ] }),
6167
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { children: [
6168
- "Percentage: ",
6169
- (data[hoveredSlice].value / total * 100).toFixed(1),
6170
- "%"
6171
- ] })
6172
- ] })
6173
- ] });
6693
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("span", { className: "text-gray-700 dark:text-gray-300", children: [
6694
+ item.label,
6695
+ ": ",
6696
+ item.value,
6697
+ showPercentages && ` (${(item.value / total * 100).toFixed(1)}%)`
6698
+ ] })
6699
+ ]
6700
+ },
6701
+ `legend-${index}`
6702
+ )) }),
6703
+ hoveredSlice !== null && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6704
+ "div",
6705
+ {
6706
+ className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
6707
+ style: {
6708
+ left: `${tooltipPosition.x}px`,
6709
+ top: `${tooltipPosition.y}px`,
6710
+ transform: "translateZ(0)"
6711
+ },
6712
+ children: [
6713
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "font-semibold", children: data[hoveredSlice].label }),
6714
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: "text-xs opacity-90", children: [
6715
+ "Value: ",
6716
+ data[hoveredSlice].value
6717
+ ] }),
6718
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: "text-xs opacity-90", children: [
6719
+ "Percentage: ",
6720
+ (data[hoveredSlice].value / total * 100).toFixed(1),
6721
+ "%"
6722
+ ] })
6723
+ ]
6724
+ }
6725
+ )
6726
+ ]
6727
+ }
6728
+ );
6174
6729
  };
6175
6730
 
6176
6731
  // src/utils/theme-script.ts