@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.mjs CHANGED
@@ -3182,6 +3182,7 @@ var RichTextEditor = ({
3182
3182
  const [showImageModal, setShowImageModal] = useState12(false);
3183
3183
  const [imageUrl, setImageUrl] = useState12("");
3184
3184
  const [imageAlt, setImageAlt] = useState12("");
3185
+ const savedSelection = useRef7(null);
3185
3186
  useLayoutEffect(() => {
3186
3187
  const styleId = "rich-text-editor-styles";
3187
3188
  if (!document.getElementById(styleId)) {
@@ -3208,6 +3209,15 @@ var RichTextEditor = ({
3208
3209
  font-weight: bold;
3209
3210
  margin: 0.83em 0;
3210
3211
  }
3212
+ [contenteditable] p {
3213
+ margin: 0.5em 0;
3214
+ }
3215
+ [contenteditable] p:first-child {
3216
+ margin-top: 0;
3217
+ }
3218
+ [contenteditable] p:last-child {
3219
+ margin-bottom: 0;
3220
+ }
3211
3221
  [contenteditable] ul, [contenteditable] ol {
3212
3222
  margin: 1em 0;
3213
3223
  padding-left: 2em;
@@ -3236,11 +3246,60 @@ var RichTextEditor = ({
3236
3246
  }
3237
3247
  }, []);
3238
3248
  const isInitialRender = useRef7(true);
3249
+ const isInternalUpdate = useRef7(false);
3239
3250
  useEffect8(() => {
3240
- if (!isInitialRender.current && editorRef.current && editorRef.current.innerHTML !== value) {
3251
+ if (isInitialRender.current && editorRef.current) {
3241
3252
  editorRef.current.innerHTML = value;
3253
+ isInitialRender.current = false;
3254
+ try {
3255
+ document.execCommand("defaultParagraphSeparator", false, "p");
3256
+ } catch (e) {
3257
+ console.warn("Could not set defaultParagraphSeparator", e);
3258
+ }
3242
3259
  }
3243
- isInitialRender.current = false;
3260
+ }, []);
3261
+ useEffect8(() => {
3262
+ if (!isInitialRender.current && !isInternalUpdate.current && editorRef.current) {
3263
+ const currentHtml = editorRef.current.innerHTML;
3264
+ if (currentHtml !== value) {
3265
+ const selection = window.getSelection();
3266
+ const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
3267
+ const preSelectionRange = range ? range.cloneRange() : null;
3268
+ if (preSelectionRange && editorRef.current.contains(preSelectionRange.startContainer)) {
3269
+ preSelectionRange.selectNodeContents(editorRef.current);
3270
+ preSelectionRange.setEnd(range.startContainer, range.startOffset);
3271
+ const start = preSelectionRange.toString().length;
3272
+ editorRef.current.innerHTML = value;
3273
+ const textNodes = [];
3274
+ const getTextNodes = (node) => {
3275
+ if (node.nodeType === Node.TEXT_NODE) {
3276
+ textNodes.push(node);
3277
+ } else {
3278
+ node.childNodes.forEach(getTextNodes);
3279
+ }
3280
+ };
3281
+ getTextNodes(editorRef.current);
3282
+ let charCount = 0;
3283
+ let foundStart = false;
3284
+ for (const textNode of textNodes) {
3285
+ const textLength = textNode.textContent?.length || 0;
3286
+ if (!foundStart && charCount + textLength >= start) {
3287
+ const newRange = document.createRange();
3288
+ newRange.setStart(textNode, start - charCount);
3289
+ newRange.collapse(true);
3290
+ selection?.removeAllRanges();
3291
+ selection?.addRange(newRange);
3292
+ foundStart = true;
3293
+ break;
3294
+ }
3295
+ charCount += textLength;
3296
+ }
3297
+ } else {
3298
+ editorRef.current.innerHTML = value;
3299
+ }
3300
+ }
3301
+ }
3302
+ isInternalUpdate.current = false;
3244
3303
  }, [value]);
3245
3304
  const updateActiveFormats = useCallback(() => {
3246
3305
  const formats = /* @__PURE__ */ new Set();
@@ -3261,10 +3320,25 @@ var RichTextEditor = ({
3261
3320
  }, []);
3262
3321
  const handleInput = useCallback(() => {
3263
3322
  if (editorRef.current && onChange) {
3323
+ isInternalUpdate.current = true;
3264
3324
  onChange(editorRef.current.innerHTML);
3265
3325
  }
3266
3326
  updateActiveFormats();
3267
3327
  }, [onChange, updateActiveFormats]);
3328
+ const handleFocus = useCallback(() => {
3329
+ setIsFocused(true);
3330
+ if (editorRef.current && (!editorRef.current.innerHTML || editorRef.current.innerHTML === "")) {
3331
+ editorRef.current.innerHTML = "<p><br></p>";
3332
+ const selection = window.getSelection();
3333
+ const range = document.createRange();
3334
+ if (editorRef.current.firstChild) {
3335
+ range.setStart(editorRef.current.firstChild, 0);
3336
+ range.collapse(true);
3337
+ selection?.removeAllRanges();
3338
+ selection?.addRange(range);
3339
+ }
3340
+ }
3341
+ }, []);
3268
3342
  const handleFormat = useCallback((command) => {
3269
3343
  if (disabled) return;
3270
3344
  document.execCommand(command, false);
@@ -3288,16 +3362,29 @@ var RichTextEditor = ({
3288
3362
  }, [disabled, updateActiveFormats, handleInput]);
3289
3363
  const handleLink = useCallback(() => {
3290
3364
  if (disabled) return;
3365
+ const selection = window.getSelection();
3366
+ if (selection && selection.rangeCount > 0) {
3367
+ savedSelection.current = selection.getRangeAt(0).cloneRange();
3368
+ }
3291
3369
  setShowLinkModal(true);
3292
3370
  }, [disabled]);
3293
3371
  const insertLink = useCallback(() => {
3294
- if (linkUrl) {
3295
- document.execCommand("createLink", false, linkUrl);
3296
- setShowLinkModal(false);
3297
- setLinkUrl("");
3298
- editorRef.current?.focus();
3299
- handleInput();
3372
+ if (!linkUrl || !editorRef.current) return;
3373
+ const selection = window.getSelection();
3374
+ if (savedSelection.current && selection) {
3375
+ try {
3376
+ selection.removeAllRanges();
3377
+ selection.addRange(savedSelection.current);
3378
+ document.execCommand("createLink", false, linkUrl);
3379
+ savedSelection.current = null;
3380
+ } catch (e) {
3381
+ console.warn("Could not insert link at saved position", e);
3382
+ }
3300
3383
  }
3384
+ setShowLinkModal(false);
3385
+ setLinkUrl("");
3386
+ editorRef.current?.focus();
3387
+ handleInput();
3301
3388
  }, [linkUrl, handleInput]);
3302
3389
  const handleCode = useCallback(() => {
3303
3390
  if (disabled) return;
@@ -3317,33 +3404,82 @@ var RichTextEditor = ({
3317
3404
  }, [disabled, handleInput]);
3318
3405
  const handleImage = useCallback(() => {
3319
3406
  if (disabled) return;
3407
+ const selection = window.getSelection();
3408
+ if (selection && selection.rangeCount > 0) {
3409
+ savedSelection.current = selection.getRangeAt(0).cloneRange();
3410
+ }
3320
3411
  setShowImageModal(true);
3321
3412
  }, [disabled]);
3322
3413
  const insertImage = useCallback(() => {
3323
- if (!imageUrl) return;
3414
+ if (!imageUrl || !editorRef.current) return;
3415
+ editorRef.current.focus();
3324
3416
  const img = document.createElement("img");
3325
3417
  img.src = imageUrl;
3326
3418
  img.alt = imageAlt || "";
3327
3419
  img.style.maxWidth = "100%";
3328
3420
  img.style.height = "auto";
3329
3421
  const selection = window.getSelection();
3330
- if (selection && selection.rangeCount > 0) {
3331
- const range = selection.getRangeAt(0);
3332
- range.deleteContents();
3333
- range.insertNode(img);
3334
- range.setStartAfter(img);
3335
- range.setEndAfter(img);
3336
- selection.removeAllRanges();
3337
- selection.addRange(range);
3338
- } else if (editorRef.current) {
3339
- editorRef.current.appendChild(img);
3422
+ if (savedSelection.current && selection && editorRef.current.contains(savedSelection.current.commonAncestorContainer)) {
3423
+ try {
3424
+ selection.removeAllRanges();
3425
+ selection.addRange(savedSelection.current);
3426
+ savedSelection.current.deleteContents();
3427
+ savedSelection.current.insertNode(img);
3428
+ const br = document.createElement("br");
3429
+ savedSelection.current.setStartAfter(img);
3430
+ savedSelection.current.insertNode(br);
3431
+ savedSelection.current.setStartAfter(br);
3432
+ savedSelection.current.collapse(true);
3433
+ selection.removeAllRanges();
3434
+ selection.addRange(savedSelection.current);
3435
+ savedSelection.current = null;
3436
+ } catch (e) {
3437
+ console.warn("Could not insert at saved position", e);
3438
+ if (selection.rangeCount > 0) {
3439
+ const range = selection.getRangeAt(0);
3440
+ if (editorRef.current.contains(range.commonAncestorContainer)) {
3441
+ range.insertNode(img);
3442
+ const br = document.createElement("br");
3443
+ range.setStartAfter(img);
3444
+ range.insertNode(br);
3445
+ range.setStartAfter(br);
3446
+ range.collapse(true);
3447
+ } else {
3448
+ editorRef.current.appendChild(img);
3449
+ editorRef.current.appendChild(document.createElement("br"));
3450
+ }
3451
+ } else {
3452
+ editorRef.current.appendChild(img);
3453
+ editorRef.current.appendChild(document.createElement("br"));
3454
+ }
3455
+ }
3456
+ } else {
3457
+ if (selection && selection.rangeCount > 0) {
3458
+ const range = selection.getRangeAt(0);
3459
+ if (editorRef.current.contains(range.commonAncestorContainer)) {
3460
+ range.deleteContents();
3461
+ range.insertNode(img);
3462
+ const br = document.createElement("br");
3463
+ range.setStartAfter(img);
3464
+ range.insertNode(br);
3465
+ range.setStartAfter(br);
3466
+ range.collapse(true);
3467
+ selection.removeAllRanges();
3468
+ selection.addRange(range);
3469
+ } else {
3470
+ editorRef.current.appendChild(img);
3471
+ editorRef.current.appendChild(document.createElement("br"));
3472
+ }
3473
+ } else {
3474
+ editorRef.current.appendChild(img);
3475
+ editorRef.current.appendChild(document.createElement("br"));
3476
+ }
3340
3477
  }
3341
3478
  setShowImageModal(false);
3342
3479
  setImageUrl("");
3343
3480
  setImageAlt("");
3344
- editorRef.current?.focus();
3345
3481
  handleInput();
3346
- }, [imageUrl, imageAlt, handleInput, disabled]);
3482
+ }, [imageUrl, imageAlt, handleInput]);
3347
3483
  const getButtonClass = (isActive) => {
3348
3484
  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";
3349
3485
  const activeClass = themeName === "minimalistic" ? "bg-white text-black" : "bg-blue-100 dark:bg-blue-900 border-blue-500 dark:border-blue-400";
@@ -3507,11 +3643,10 @@ var RichTextEditor = ({
3507
3643
  ref: editorRef,
3508
3644
  contentEditable: !disabled,
3509
3645
  onInput: handleInput,
3510
- onFocus: () => setIsFocused(true),
3646
+ onFocus: handleFocus,
3511
3647
  onBlur: () => setIsFocused(false),
3512
3648
  onMouseUp: updateActiveFormats,
3513
3649
  onKeyUp: updateActiveFormats,
3514
- dangerouslySetInnerHTML: { __html: value },
3515
3650
  className: `
3516
3651
  w-full px-4 py-3 rounded-b-lg outline-none overflow-y-auto
3517
3652
  ${editorBaseClass} ${focusClass} ${errorClass}
@@ -5000,8 +5135,10 @@ var defaultColors = [
5000
5135
  ];
5001
5136
  var LineChart = ({
5002
5137
  data,
5003
- width = 600,
5004
- height = 400,
5138
+ width: providedWidth,
5139
+ height: providedHeight,
5140
+ aspectRatio = 16 / 9,
5141
+ responsive = true,
5005
5142
  showGrid = true,
5006
5143
  showXAxis = true,
5007
5144
  showYAxis = true,
@@ -5012,12 +5149,32 @@ var LineChart = ({
5012
5149
  strokeWidth = 2,
5013
5150
  className = "",
5014
5151
  xAxisLabel,
5015
- yAxisLabel
5152
+ yAxisLabel,
5153
+ baseFontSize = 14
5016
5154
  }) => {
5155
+ const viewBoxWidth = 1e3;
5156
+ const viewBoxHeight = 600;
5157
+ const containerRef = React26.useRef(null);
5158
+ const svgRef = React26.useRef(null);
5159
+ const [tooltipPosition, setTooltipPosition] = React26.useState(null);
5017
5160
  const [hoveredPoint, setHoveredPoint] = React26.useState(null);
5018
- const padding = { top: 20, right: 20, bottom: showXAxis ? 60 : 20, left: showYAxis ? 60 : 20 };
5019
- const chartWidth = width - padding.left - padding.right;
5020
- const chartHeight = height - padding.top - padding.bottom;
5161
+ const chartId = React26.useId();
5162
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5163
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5164
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5165
+ const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
5166
+ const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
5167
+ const padding = {
5168
+ top: 50,
5169
+ right: 50,
5170
+ bottom: showXAxis ? 120 : 50,
5171
+ left: showYAxis ? 130 : 50
5172
+ };
5173
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
5174
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5175
+ const gridLabelFontSize = viewBoxWidth * 0.045;
5176
+ const axisLabelFontSize = viewBoxWidth * 0.05;
5177
+ const titleFontSize = viewBoxWidth * 0.055;
5021
5178
  const allPoints = data.flatMap((series) => series.data);
5022
5179
  const allYValues = allPoints.map((p) => p.y);
5023
5180
  const firstXValue = data[0]?.data[0]?.x;
@@ -5030,7 +5187,7 @@ var LineChart = ({
5030
5187
  const minY = Math.min(0, ...allYValues);
5031
5188
  const maxY = Math.max(...allYValues);
5032
5189
  const yRange = maxY - minY;
5033
- const yMin = minY - yRange * 0.1;
5190
+ const yMin = Math.max(0, minY - yRange * 0.1);
5034
5191
  const yMax = maxY + yRange * 0.1;
5035
5192
  const scaleX = (x, index) => {
5036
5193
  const numX = isStringX ? index : x;
@@ -5077,28 +5234,32 @@ var LineChart = ({
5077
5234
  x2: chartWidth,
5078
5235
  y2: y,
5079
5236
  stroke: "currentColor",
5080
- strokeWidth: "1",
5237
+ strokeWidth: "0.5",
5081
5238
  className: "text-gray-200 dark:text-gray-700",
5082
- strokeDasharray: "2,2"
5239
+ strokeDasharray: "4,4"
5083
5240
  }
5084
5241
  ),
5085
5242
  showYAxis && /* @__PURE__ */ jsx108(
5086
5243
  "text",
5087
5244
  {
5088
- x: -10,
5089
- y: y + 4,
5245
+ x: -25,
5246
+ y,
5090
5247
  textAnchor: "end",
5091
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5248
+ dominantBaseline: "middle",
5249
+ fontSize: gridLabelFontSize,
5250
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5092
5251
  children: yValue.toFixed(1)
5093
5252
  }
5094
5253
  )
5095
5254
  ] }, `grid-h-${i}`)
5096
5255
  );
5097
5256
  }
5098
- const numXGridLines = Math.min(allPoints.length, 6);
5257
+ const numXGridLines = Math.max(2, Math.min(allPoints.length, 6));
5258
+ const xGridStep = numXGridLines > 1 ? 1 / (numXGridLines - 1) : 0;
5099
5259
  for (let i = 0; i < numXGridLines; i++) {
5100
- const x = chartWidth / (numXGridLines - 1) * i;
5101
- const xValue = data[0]?.data[Math.floor((data[0].data.length - 1) * (i / (numXGridLines - 1)))]?.x || "";
5260
+ const x = chartWidth * (i * xGridStep);
5261
+ const dataIndex = Math.floor((data[0]?.data.length - 1) * (i * xGridStep));
5262
+ const xValue = data[0]?.data[Math.max(0, Math.min(dataIndex, data[0].data.length - 1))]?.x || "";
5102
5263
  gridLines.push(
5103
5264
  /* @__PURE__ */ jsxs35("g", { children: [
5104
5265
  /* @__PURE__ */ jsx108(
@@ -5109,18 +5270,20 @@ var LineChart = ({
5109
5270
  x2: x,
5110
5271
  y2: chartHeight,
5111
5272
  stroke: "currentColor",
5112
- strokeWidth: "1",
5273
+ strokeWidth: "0.5",
5113
5274
  className: "text-gray-200 dark:text-gray-700",
5114
- strokeDasharray: "2,2"
5275
+ strokeDasharray: "4,4"
5115
5276
  }
5116
5277
  ),
5117
5278
  showXAxis && /* @__PURE__ */ jsx108(
5118
5279
  "text",
5119
5280
  {
5120
5281
  x,
5121
- y: chartHeight + 20,
5282
+ y: chartHeight + 35,
5122
5283
  textAnchor: "middle",
5123
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5284
+ dominantBaseline: "hanging",
5285
+ fontSize: gridLabelFontSize,
5286
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5124
5287
  children: xValue
5125
5288
  }
5126
5289
  )
@@ -5128,108 +5291,197 @@ var LineChart = ({
5128
5291
  );
5129
5292
  }
5130
5293
  }
5131
- return /* @__PURE__ */ jsxs35("div", { className: `relative inline-block ${className}`, children: [
5132
- /* @__PURE__ */ jsx108(
5133
- "svg",
5134
- {
5135
- width,
5136
- height,
5137
- className: "bg-white dark:bg-gray-800",
5138
- style: { overflow: "visible" },
5139
- children: /* @__PURE__ */ jsxs35("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5140
- gridLines,
5141
- data.map((series, seriesIndex) => /* @__PURE__ */ jsx108(
5142
- "path",
5143
- {
5144
- d: generatePath(series),
5145
- fill: "none",
5146
- stroke: series.color || defaultColors[seriesIndex % defaultColors.length],
5147
- strokeWidth,
5148
- strokeLinecap: "round",
5149
- strokeLinejoin: "round"
5150
- },
5151
- `line-${seriesIndex}`
5152
- )),
5153
- showDots && data.map(
5154
- (series, seriesIndex) => series.data.map((point, pointIndex) => {
5155
- const x = scaleX(point.x, pointIndex);
5156
- const y = scaleY(point.y);
5157
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5158
- return /* @__PURE__ */ jsx108(
5159
- "circle",
5294
+ const getTooltipPosition = React26.useCallback((x, y) => {
5295
+ if (!containerRef.current) return { x, y };
5296
+ const rect = containerRef.current.getBoundingClientRect();
5297
+ const tooltipWidth = 120;
5298
+ const tooltipHeight = 80;
5299
+ let tooltipX = x + 10;
5300
+ let tooltipY = y - 30;
5301
+ if (tooltipX + tooltipWidth > rect.width) {
5302
+ tooltipX = x - tooltipWidth - 10;
5303
+ }
5304
+ if (tooltipY + tooltipHeight > rect.height) {
5305
+ tooltipY = y + 30;
5306
+ }
5307
+ return { x: tooltipX, y: tooltipY };
5308
+ }, []);
5309
+ const containerStyle = {
5310
+ "--font-size-base": `${baseFontSize}px`,
5311
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
5312
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
5313
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
5314
+ };
5315
+ return /* @__PURE__ */ jsxs35(
5316
+ "div",
5317
+ {
5318
+ ref: containerRef,
5319
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
5320
+ style: {
5321
+ ...containerStyle
5322
+ },
5323
+ children: [
5324
+ /* @__PURE__ */ jsx108("style", { children: `
5325
+ /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
5326
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
5327
+ .${axisLabelClass} { display: none; }
5328
+ .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
5329
+ .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
5330
+
5331
+ /* Tablet: Medium fonts, show axis titles, medium lines */
5332
+ @media (min-width: 640px) {
5333
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
5334
+ .${axisLabelClass} {
5335
+ font-size: ${axisLabelFontSize * 0.7}px !important;
5336
+ display: block;
5337
+ }
5338
+ .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
5339
+ .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
5340
+ }
5341
+
5342
+ /* Desktop: Small fonts, show axis titles, normal lines */
5343
+ @media (min-width: 1024px) {
5344
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
5345
+ .${axisLabelClass} {
5346
+ font-size: ${axisLabelFontSize * 0.4}px !important;
5347
+ display: block;
5348
+ }
5349
+ .${lineClass} { stroke-width: ${strokeWidth} !important; }
5350
+ .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
5351
+ }
5352
+ ` }),
5353
+ /* @__PURE__ */ jsx108(
5354
+ "svg",
5355
+ {
5356
+ ref: svgRef,
5357
+ width: "100%",
5358
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
5359
+ preserveAspectRatio: "xMidYMid meet",
5360
+ className: "bg-white dark:bg-gray-800 block w-full",
5361
+ style: { height: "auto", overflow: "visible" },
5362
+ children: /* @__PURE__ */ jsxs35("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5363
+ gridLines,
5364
+ data.map((series, seriesIndex) => /* @__PURE__ */ jsx108(
5365
+ "path",
5160
5366
  {
5161
- cx: x,
5162
- cy: y,
5163
- r: isHovered ? 6 : 4,
5164
- fill: series.color || defaultColors[seriesIndex % defaultColors.length],
5165
- stroke: "white",
5166
- strokeWidth: "2",
5167
- className: "cursor-pointer transition-all",
5168
- onMouseEnter: () => showTooltip && setHoveredPoint({ seriesIndex, pointIndex, x, y }),
5169
- onMouseLeave: () => setHoveredPoint(null)
5367
+ d: generatePath(series),
5368
+ fill: "none",
5369
+ stroke: series.color || defaultColors[seriesIndex % defaultColors.length],
5370
+ strokeWidth,
5371
+ strokeLinecap: "round",
5372
+ strokeLinejoin: "round",
5373
+ className: lineClass
5170
5374
  },
5171
- `point-${seriesIndex}-${pointIndex}`
5172
- );
5173
- })
5174
- ),
5175
- xAxisLabel && showXAxis && /* @__PURE__ */ jsx108(
5176
- "text",
5375
+ `line-${seriesIndex}`
5376
+ )),
5377
+ showDots && data.map(
5378
+ (series, seriesIndex) => series.data.map((point, pointIndex) => {
5379
+ const x = scaleX(point.x, pointIndex);
5380
+ const y = scaleY(point.y);
5381
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5382
+ return /* @__PURE__ */ jsx108(
5383
+ "circle",
5384
+ {
5385
+ cx: x,
5386
+ cy: y,
5387
+ r: isHovered ? 5 : 3,
5388
+ fill: series.color || defaultColors[seriesIndex % defaultColors.length],
5389
+ stroke: "white",
5390
+ strokeWidth: "1.5",
5391
+ className: `cursor-pointer transition-all ${pointClass}`,
5392
+ onMouseEnter: (e) => {
5393
+ if (showTooltip && containerRef.current) {
5394
+ setHoveredPoint({ seriesIndex, pointIndex, x, y });
5395
+ const containerRect = containerRef.current.getBoundingClientRect();
5396
+ const mouseX = e.clientX - containerRect.left;
5397
+ const mouseY = e.clientY - containerRect.top;
5398
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
5399
+ }
5400
+ },
5401
+ onMouseMove: (e) => {
5402
+ if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
5403
+ const containerRect = containerRef.current.getBoundingClientRect();
5404
+ const mouseX = e.clientX - containerRect.left;
5405
+ const mouseY = e.clientY - containerRect.top;
5406
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
5407
+ }
5408
+ },
5409
+ onMouseLeave: () => {
5410
+ setHoveredPoint(null);
5411
+ setTooltipPosition(null);
5412
+ }
5413
+ },
5414
+ `point-${seriesIndex}-${pointIndex}`
5415
+ );
5416
+ })
5417
+ ),
5418
+ xAxisLabel && showXAxis && /* @__PURE__ */ jsx108(
5419
+ "text",
5420
+ {
5421
+ x: chartWidth / 2,
5422
+ y: chartHeight + 80,
5423
+ textAnchor: "middle",
5424
+ dominantBaseline: "hanging",
5425
+ fontSize: axisLabelFontSize,
5426
+ fontWeight: "600",
5427
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5428
+ children: xAxisLabel
5429
+ }
5430
+ ),
5431
+ yAxisLabel && showYAxis && /* @__PURE__ */ jsx108(
5432
+ "text",
5433
+ {
5434
+ x: -chartHeight / 2,
5435
+ y: -100,
5436
+ textAnchor: "middle",
5437
+ dominantBaseline: "middle",
5438
+ fontSize: axisLabelFontSize,
5439
+ fontWeight: "600",
5440
+ transform: "rotate(-90)",
5441
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5442
+ children: yAxisLabel
5443
+ }
5444
+ )
5445
+ ] })
5446
+ }
5447
+ ),
5448
+ showLegend && /* @__PURE__ */ jsx108("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2 text-sm", children: [
5449
+ /* @__PURE__ */ jsx108(
5450
+ "div",
5177
5451
  {
5178
- x: chartWidth / 2,
5179
- y: chartHeight + 50,
5180
- textAnchor: "middle",
5181
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5182
- children: xAxisLabel
5452
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
5453
+ style: {
5454
+ backgroundColor: series.color || defaultColors[index % defaultColors.length]
5455
+ }
5183
5456
  }
5184
5457
  ),
5185
- yAxisLabel && showYAxis && /* @__PURE__ */ jsx108(
5186
- "text",
5187
- {
5188
- x: -chartHeight / 2,
5189
- y: -45,
5190
- textAnchor: "middle",
5191
- transform: `rotate(-90, 0, 0)`,
5192
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5193
- children: yAxisLabel
5194
- }
5195
- )
5196
- ] })
5197
- }
5198
- ),
5199
- showLegend && /* @__PURE__ */ jsx108("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
5200
- /* @__PURE__ */ jsx108(
5201
- "div",
5202
- {
5203
- className: "w-4 h-4 rounded-sm",
5204
- style: {
5205
- backgroundColor: series.color || defaultColors[index % defaultColors.length]
5458
+ /* @__PURE__ */ jsx108("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
5459
+ ] }, `legend-${index}`)) }),
5460
+ showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ jsxs35(
5461
+ "div",
5462
+ {
5463
+ 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",
5464
+ style: {
5465
+ left: `${tooltipPosition.x}px`,
5466
+ top: `${tooltipPosition.y}px`,
5467
+ transform: "translateZ(0)"
5468
+ },
5469
+ children: [
5470
+ /* @__PURE__ */ jsx108("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5471
+ /* @__PURE__ */ jsxs35("div", { className: "text-xs opacity-90", children: [
5472
+ "x: ",
5473
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5474
+ ] }),
5475
+ /* @__PURE__ */ jsxs35("div", { className: "text-xs opacity-90", children: [
5476
+ "y: ",
5477
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5478
+ ] })
5479
+ ]
5206
5480
  }
5207
- }
5208
- ),
5209
- /* @__PURE__ */ jsx108("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5210
- ] }, `legend-${index}`)) }),
5211
- showTooltip && hoveredPoint && /* @__PURE__ */ jsxs35(
5212
- "div",
5213
- {
5214
- 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",
5215
- style: {
5216
- left: `${padding.left + hoveredPoint.x + 10}px`,
5217
- top: `${padding.top + hoveredPoint.y - 30}px`
5218
- },
5219
- children: [
5220
- /* @__PURE__ */ jsx108("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5221
- /* @__PURE__ */ jsxs35("div", { children: [
5222
- "x: ",
5223
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5224
- ] }),
5225
- /* @__PURE__ */ jsxs35("div", { children: [
5226
- "y: ",
5227
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5228
- ] })
5229
- ]
5230
- }
5231
- )
5232
- ] });
5481
+ )
5482
+ ]
5483
+ }
5484
+ );
5233
5485
  };
5234
5486
 
5235
5487
  // src/components/BarChart.tsx
@@ -5251,8 +5503,10 @@ var defaultColors2 = [
5251
5503
  ];
5252
5504
  var BarChart = ({
5253
5505
  data,
5254
- width = 600,
5255
- height = 400,
5506
+ width: providedWidth,
5507
+ height: providedHeight,
5508
+ aspectRatio = 16 / 9,
5509
+ responsive = true,
5256
5510
  showGrid = true,
5257
5511
  showXAxis = true,
5258
5512
  showYAxis = true,
@@ -5264,24 +5518,38 @@ var BarChart = ({
5264
5518
  barWidth = 0.8,
5265
5519
  className = "",
5266
5520
  xAxisLabel,
5267
- yAxisLabel
5521
+ yAxisLabel,
5522
+ baseFontSize = 14,
5523
+ colors = defaultColors2
5268
5524
  }) => {
5525
+ const viewBoxWidth = 1e3;
5526
+ const viewBoxHeight = 600;
5527
+ const containerRef = React27.useRef(null);
5528
+ const [tooltipPosition, setTooltipPosition] = React27.useState(null);
5269
5529
  const [hoveredBar, setHoveredBar] = React27.useState(null);
5530
+ const chartId = React27.useId();
5531
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5532
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5533
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5534
+ const barClass = `chart-bar-${chartId.replace(/:/g, "-")}`;
5270
5535
  const padding = {
5271
- top: 20,
5272
- right: 20,
5273
- bottom: showXAxis ? horizontal ? 60 : 80 : 20,
5274
- left: showYAxis ? horizontal ? 80 : 60 : 20
5536
+ top: 50,
5537
+ right: 50,
5538
+ bottom: showXAxis ? horizontal ? 120 : 140 : 50,
5539
+ left: showYAxis ? horizontal ? 140 : 130 : 50
5275
5540
  };
5276
- const chartWidth = width - padding.left - padding.right;
5277
- const chartHeight = height - padding.top - padding.bottom;
5541
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
5542
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5543
+ const gridLabelFontSize = viewBoxWidth * 0.045;
5544
+ const axisLabelFontSize = viewBoxWidth * 0.05;
5545
+ const titleFontSize = viewBoxWidth * 0.055;
5278
5546
  const allYValues = stacked ? data[0]?.data.map(
5279
5547
  (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
5280
5548
  ) || [] : data.flatMap((series) => series.data.map((p) => p.y));
5281
5549
  const minY = Math.min(0, ...allYValues);
5282
5550
  const maxY = Math.max(...allYValues);
5283
5551
  const yRange = maxY - minY;
5284
- const yMin = minY - yRange * 0.1;
5552
+ const yMin = Math.max(0, minY - yRange * 0.1);
5285
5553
  const yMax = maxY + yRange * 0.1;
5286
5554
  const numBars = data[0]?.data.length || 0;
5287
5555
  const numSeries = data.length;
@@ -5308,18 +5576,20 @@ var BarChart = ({
5308
5576
  x2: chartWidth,
5309
5577
  y2: y,
5310
5578
  stroke: "currentColor",
5311
- strokeWidth: "1",
5579
+ strokeWidth: "0.5",
5312
5580
  className: "text-gray-200 dark:text-gray-700",
5313
- strokeDasharray: "2,2"
5581
+ strokeDasharray: "4,4"
5314
5582
  }
5315
5583
  ),
5316
5584
  showYAxis && !horizontal && /* @__PURE__ */ jsx109(
5317
5585
  "text",
5318
5586
  {
5319
- x: -10,
5320
- y: y + 4,
5587
+ x: -25,
5588
+ y,
5321
5589
  textAnchor: "end",
5322
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5590
+ dominantBaseline: "middle",
5591
+ fontSize: gridLabelFontSize,
5592
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5323
5593
  children: yValue.toFixed(0)
5324
5594
  }
5325
5595
  )
@@ -5368,12 +5638,20 @@ var BarChart = ({
5368
5638
  x: xPos,
5369
5639
  y: yPos,
5370
5640
  width: barW,
5371
- height: barHeight,
5372
- fill: data[seriesIndex].color || defaultColors2[seriesIndex % defaultColors2.length],
5373
- className: "cursor-pointer transition-opacity",
5641
+ height: Math.abs(scaleY(0) - yPos),
5642
+ fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5643
+ className: `cursor-pointer transition-opacity ${barClass}`,
5374
5644
  opacity: isHovered ? 0.8 : 1,
5375
- onMouseEnter: () => showTooltip && setHoveredBar({ seriesIndex, barIndex, x: xPos + barW / 2, y: yPos }),
5376
- onMouseLeave: () => setHoveredBar(null)
5645
+ onMouseEnter: () => {
5646
+ if (showTooltip) {
5647
+ setHoveredBar({ seriesIndex, barIndex, x: xPos + barW / 2, y: yPos });
5648
+ setTooltipPosition(getTooltipPosition(padding.left + xPos + barW / 2, padding.top + yPos));
5649
+ }
5650
+ },
5651
+ onMouseLeave: () => {
5652
+ setHoveredBar(null);
5653
+ setTooltipPosition(null);
5654
+ }
5377
5655
  },
5378
5656
  `bar-${seriesIndex}-${barIndex}`
5379
5657
  )
@@ -5398,8 +5676,8 @@ var BarChart = ({
5398
5676
  y: yPos,
5399
5677
  width: actualBarWidth,
5400
5678
  height: barHeight,
5401
- fill: data[seriesIndex].color || defaultColors2[seriesIndex % defaultColors2.length],
5402
- className: "cursor-pointer transition-opacity",
5679
+ fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5680
+ className: `cursor-pointer transition-opacity ${barClass}`,
5403
5681
  opacity: isHovered ? 0.8 : 1,
5404
5682
  onMouseEnter: () => showTooltip && setHoveredBar({ seriesIndex, barIndex, x: xPos + actualBarWidth / 2, y: yPos }),
5405
5683
  onMouseLeave: () => setHoveredBar(null)
@@ -5413,9 +5691,12 @@ var BarChart = ({
5413
5691
  "text",
5414
5692
  {
5415
5693
  x: xPos + actualBarWidth / 2,
5416
- y: yPos - 5,
5694
+ y: yPos - 10,
5417
5695
  textAnchor: "middle",
5418
- className: "text-xs fill-gray-700 dark:fill-gray-300 font-medium",
5696
+ dominantBaseline: "middle",
5697
+ fontSize: "11",
5698
+ fontWeight: "600",
5699
+ className: "fill-gray-700 dark:fill-gray-300",
5419
5700
  children: point.y
5420
5701
  },
5421
5702
  `value-${seriesIndex}-${barIndex}`
@@ -5433,81 +5714,145 @@ var BarChart = ({
5433
5714
  "text",
5434
5715
  {
5435
5716
  x: xPos,
5436
- y: chartHeight + 20,
5717
+ y: chartHeight + 35,
5437
5718
  textAnchor: "middle",
5438
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5719
+ dominantBaseline: "hanging",
5720
+ fontSize: gridLabelFontSize,
5721
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5439
5722
  children: point.x
5440
5723
  },
5441
5724
  `x-label-${i}`
5442
5725
  );
5443
5726
  });
5444
- return /* @__PURE__ */ jsxs36("div", { className: `relative inline-block ${className}`, children: [
5445
- /* @__PURE__ */ jsx109(
5446
- "svg",
5447
- {
5448
- width,
5449
- height,
5450
- className: "bg-white dark:bg-gray-800",
5451
- style: { overflow: "visible" },
5452
- children: /* @__PURE__ */ jsxs36("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5453
- gridLines,
5454
- renderBars(),
5455
- showXAxis && xAxisLabels,
5456
- xAxisLabel && showXAxis && /* @__PURE__ */ jsx109(
5457
- "text",
5727
+ const getTooltipPosition = React27.useCallback((x, y) => {
5728
+ if (!containerRef.current) return { x, y };
5729
+ const rect = containerRef.current.getBoundingClientRect();
5730
+ const tooltipWidth = 140;
5731
+ const tooltipHeight = 80;
5732
+ let tooltipX = x + 10;
5733
+ let tooltipY = y - 30;
5734
+ if (tooltipX + tooltipWidth > rect.width) {
5735
+ tooltipX = x - tooltipWidth - 10;
5736
+ }
5737
+ if (tooltipY + tooltipHeight > rect.height) {
5738
+ tooltipY = y + 30;
5739
+ }
5740
+ return { x: tooltipX, y: tooltipY };
5741
+ }, []);
5742
+ const containerStyle = {
5743
+ "--font-size-base": `${baseFontSize}px`,
5744
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
5745
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
5746
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
5747
+ };
5748
+ return /* @__PURE__ */ jsxs36(
5749
+ "div",
5750
+ {
5751
+ ref: containerRef,
5752
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
5753
+ style: {
5754
+ ...containerStyle
5755
+ },
5756
+ children: [
5757
+ /* @__PURE__ */ jsx109("style", { children: `
5758
+ /* Mobile: Large fonts, hidden axis titles, thicker bars (default) */
5759
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
5760
+ .${axisLabelClass} { display: none; }
5761
+
5762
+ /* Tablet: Medium fonts, show axis titles */
5763
+ @media (min-width: 640px) {
5764
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
5765
+ .${axisLabelClass} {
5766
+ font-size: ${axisLabelFontSize * 0.7}px !important;
5767
+ display: block;
5768
+ }
5769
+ }
5770
+
5771
+ /* Desktop: Small fonts, show axis titles */
5772
+ @media (min-width: 1024px) {
5773
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
5774
+ .${axisLabelClass} {
5775
+ font-size: ${axisLabelFontSize * 0.4}px !important;
5776
+ display: block;
5777
+ }
5778
+ }
5779
+ ` }),
5780
+ /* @__PURE__ */ jsx109(
5781
+ "svg",
5782
+ {
5783
+ width: "100%",
5784
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
5785
+ preserveAspectRatio: "xMidYMid meet",
5786
+ className: "bg-white dark:bg-gray-800 block w-full",
5787
+ style: { height: "auto", overflow: "visible" },
5788
+ children: /* @__PURE__ */ jsxs36("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5789
+ gridLines,
5790
+ renderBars(),
5791
+ showXAxis && xAxisLabels,
5792
+ xAxisLabel && showXAxis && /* @__PURE__ */ jsx109(
5793
+ "text",
5794
+ {
5795
+ x: chartWidth / 2,
5796
+ y: chartHeight + 80,
5797
+ textAnchor: "middle",
5798
+ dominantBaseline: "hanging",
5799
+ fontSize: axisLabelFontSize,
5800
+ fontWeight: "600",
5801
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5802
+ children: xAxisLabel
5803
+ }
5804
+ ),
5805
+ yAxisLabel && showYAxis && /* @__PURE__ */ jsx109(
5806
+ "text",
5807
+ {
5808
+ x: -chartHeight / 2,
5809
+ y: -100,
5810
+ textAnchor: "middle",
5811
+ dominantBaseline: "middle",
5812
+ fontSize: axisLabelFontSize,
5813
+ fontWeight: "600",
5814
+ transform: "rotate(-90)",
5815
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5816
+ children: yAxisLabel
5817
+ }
5818
+ )
5819
+ ] })
5820
+ }
5821
+ ),
5822
+ showLegend && /* @__PURE__ */ jsx109("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2 text-sm", children: [
5823
+ /* @__PURE__ */ jsx109(
5824
+ "div",
5458
5825
  {
5459
- x: chartWidth / 2,
5460
- y: chartHeight + 50,
5461
- textAnchor: "middle",
5462
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5463
- children: xAxisLabel
5826
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
5827
+ style: {
5828
+ backgroundColor: series.color || colors[index % colors.length]
5829
+ }
5464
5830
  }
5465
5831
  ),
5466
- yAxisLabel && showYAxis && /* @__PURE__ */ jsx109(
5467
- "text",
5468
- {
5469
- x: -chartHeight / 2,
5470
- y: -45,
5471
- textAnchor: "middle",
5472
- transform: `rotate(-90, 0, 0)`,
5473
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5474
- children: yAxisLabel
5475
- }
5476
- )
5477
- ] })
5478
- }
5479
- ),
5480
- showLegend && /* @__PURE__ */ jsx109("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
5481
- /* @__PURE__ */ jsx109(
5482
- "div",
5483
- {
5484
- className: "w-4 h-4 rounded-sm",
5485
- style: {
5486
- backgroundColor: series.color || defaultColors2[index % defaultColors2.length]
5832
+ /* @__PURE__ */ jsx109("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
5833
+ ] }, `legend-${index}`)) }),
5834
+ showTooltip && hoveredBar && tooltipPosition && /* @__PURE__ */ jsxs36(
5835
+ "div",
5836
+ {
5837
+ 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",
5838
+ style: {
5839
+ left: `${tooltipPosition.x}px`,
5840
+ top: `${tooltipPosition.y}px`,
5841
+ transform: "translateZ(0)"
5842
+ },
5843
+ children: [
5844
+ /* @__PURE__ */ jsx109("div", { className: "font-semibold", children: data[hoveredBar.seriesIndex].name }),
5845
+ /* @__PURE__ */ jsxs36("div", { className: "text-xs opacity-90", children: [
5846
+ data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].x,
5847
+ ": ",
5848
+ data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].y
5849
+ ] })
5850
+ ]
5487
5851
  }
5488
- }
5489
- ),
5490
- /* @__PURE__ */ jsx109("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5491
- ] }, `legend-${index}`)) }),
5492
- showTooltip && hoveredBar && /* @__PURE__ */ jsxs36(
5493
- "div",
5494
- {
5495
- 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",
5496
- style: {
5497
- left: `${padding.left + hoveredBar.x + 10}px`,
5498
- top: `${padding.top + hoveredBar.y - 30}px`
5499
- },
5500
- children: [
5501
- /* @__PURE__ */ jsx109("div", { className: "font-semibold", children: data[hoveredBar.seriesIndex].name }),
5502
- /* @__PURE__ */ jsxs36("div", { children: [
5503
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].x,
5504
- ": ",
5505
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].y
5506
- ] })
5507
- ]
5508
- }
5509
- )
5510
- ] });
5852
+ )
5853
+ ]
5854
+ }
5855
+ );
5511
5856
  };
5512
5857
 
5513
5858
  // src/components/AreaChart.tsx
@@ -5529,8 +5874,10 @@ var defaultColors3 = [
5529
5874
  ];
5530
5875
  var AreaChart = ({
5531
5876
  data,
5532
- width = 600,
5533
- height = 400,
5877
+ width: providedWidth,
5878
+ height: providedHeight,
5879
+ aspectRatio = 16 / 9,
5880
+ responsive = true,
5534
5881
  showGrid = true,
5535
5882
  showXAxis = true,
5536
5883
  showYAxis = true,
@@ -5543,12 +5890,32 @@ var AreaChart = ({
5543
5890
  strokeWidth = 2,
5544
5891
  className = "",
5545
5892
  xAxisLabel,
5546
- yAxisLabel
5893
+ yAxisLabel,
5894
+ baseFontSize = 14
5547
5895
  }) => {
5896
+ const viewBoxWidth = 1e3;
5897
+ const viewBoxHeight = 600;
5898
+ const containerRef = React28.useRef(null);
5899
+ const svgRef = React28.useRef(null);
5900
+ const [tooltipPosition, setTooltipPosition] = React28.useState(null);
5548
5901
  const [hoveredPoint, setHoveredPoint] = React28.useState(null);
5549
- const padding = { top: 20, right: 20, bottom: showXAxis ? 60 : 20, left: showYAxis ? 60 : 20 };
5550
- const chartWidth = width - padding.left - padding.right;
5551
- const chartHeight = height - padding.top - padding.bottom;
5902
+ const chartId = React28.useId();
5903
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5904
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5905
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5906
+ const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
5907
+ const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
5908
+ const padding = {
5909
+ top: 50,
5910
+ right: 50,
5911
+ bottom: showXAxis ? 120 : 50,
5912
+ left: showYAxis ? 130 : 50
5913
+ };
5914
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
5915
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5916
+ const gridLabelFontSize = viewBoxWidth * 0.045;
5917
+ const axisLabelFontSize = viewBoxWidth * 0.05;
5918
+ const titleFontSize = viewBoxWidth * 0.055;
5552
5919
  const allPoints = data.flatMap((series) => series.data);
5553
5920
  const allYValues = stacked ? data[0]?.data.map(
5554
5921
  (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
@@ -5562,7 +5929,7 @@ var AreaChart = ({
5562
5929
  const minY = Math.min(0, ...allYValues);
5563
5930
  const maxY = Math.max(...allYValues);
5564
5931
  const yRange = maxY - minY;
5565
- const yMin = minY - yRange * 0.1;
5932
+ const yMin = Math.max(0, minY - yRange * 0.1);
5566
5933
  const yMax = maxY + yRange * 0.1;
5567
5934
  const scaleX = (x, index) => {
5568
5935
  const numX = isStringX ? index : x;
@@ -5643,19 +6010,21 @@ var AreaChart = ({
5643
6010
  x2: chartWidth,
5644
6011
  y2: y,
5645
6012
  stroke: "currentColor",
5646
- strokeWidth: "1",
6013
+ strokeWidth: "0.5",
5647
6014
  className: "text-gray-200 dark:text-gray-700",
5648
- strokeDasharray: "2,2"
6015
+ strokeDasharray: "4,4"
5649
6016
  }
5650
6017
  ),
5651
6018
  showYAxis && /* @__PURE__ */ jsx110(
5652
6019
  "text",
5653
6020
  {
5654
- x: -10,
5655
- y: y + 4,
6021
+ x: -25,
6022
+ y,
5656
6023
  textAnchor: "end",
5657
- className: "text-xs fill-gray-600 dark:fill-gray-400",
5658
- children: yValue.toFixed(0)
6024
+ dominantBaseline: "middle",
6025
+ fontSize: gridLabelFontSize,
6026
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6027
+ children: yValue.toFixed(1)
5659
6028
  }
5660
6029
  )
5661
6030
  ] }, `grid-h-${i}`)
@@ -5675,18 +6044,20 @@ var AreaChart = ({
5675
6044
  x2: x,
5676
6045
  y2: chartHeight,
5677
6046
  stroke: "currentColor",
5678
- strokeWidth: "1",
6047
+ strokeWidth: "0.5",
5679
6048
  className: "text-gray-200 dark:text-gray-700",
5680
- strokeDasharray: "2,2"
6049
+ strokeDasharray: "4,4"
5681
6050
  }
5682
6051
  ),
5683
6052
  showXAxis && /* @__PURE__ */ jsx110(
5684
6053
  "text",
5685
6054
  {
5686
6055
  x,
5687
- y: chartHeight + 20,
6056
+ y: chartHeight + 35,
5688
6057
  textAnchor: "middle",
5689
- className: "text-xs fill-gray-600 dark:fill-gray-400",
6058
+ dominantBaseline: "hanging",
6059
+ fontSize: gridLabelFontSize,
6060
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5690
6061
  children: xValue
5691
6062
  }
5692
6063
  )
@@ -5698,130 +6069,217 @@ var AreaChart = ({
5698
6069
  if (stacked) {
5699
6070
  cumulativeValues = Array(data[0]?.data.length || 0).fill(0);
5700
6071
  }
5701
- return /* @__PURE__ */ jsxs37("div", { className: `relative inline-block ${className}`, children: [
5702
- /* @__PURE__ */ jsx110(
5703
- "svg",
5704
- {
5705
- width,
5706
- height,
5707
- className: "bg-white dark:bg-gray-800",
5708
- style: { overflow: "visible" },
5709
- children: /* @__PURE__ */ jsxs37("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5710
- gridLines,
5711
- data.map((series, seriesIndex) => {
5712
- const baselineYValues = stacked ? [...cumulativeValues] : void 0;
5713
- const areaPath = generateAreaPath(series, baselineYValues);
5714
- const linePath = generateLinePath(series, baselineYValues);
5715
- if (stacked) {
5716
- series.data.forEach((point, i) => {
5717
- cumulativeValues[i] += point.y;
5718
- });
5719
- }
5720
- return /* @__PURE__ */ jsxs37("g", { children: [
5721
- /* @__PURE__ */ jsx110(
5722
- "path",
6072
+ const getTooltipPosition = React28.useCallback((x, y) => {
6073
+ if (!containerRef.current) return { x, y };
6074
+ const rect = containerRef.current.getBoundingClientRect();
6075
+ const tooltipWidth = 120;
6076
+ const tooltipHeight = 80;
6077
+ let tooltipX = x + 10;
6078
+ let tooltipY = y - 30;
6079
+ if (tooltipX + tooltipWidth > rect.width) {
6080
+ tooltipX = x - tooltipWidth - 10;
6081
+ }
6082
+ if (tooltipY + tooltipHeight > rect.height) {
6083
+ tooltipY = y + 30;
6084
+ }
6085
+ return { x: tooltipX, y: tooltipY };
6086
+ }, []);
6087
+ const containerStyle = {
6088
+ "--font-size-base": `${baseFontSize}px`,
6089
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6090
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6091
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
6092
+ };
6093
+ return /* @__PURE__ */ jsxs37(
6094
+ "div",
6095
+ {
6096
+ ref: containerRef,
6097
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6098
+ style: containerStyle,
6099
+ children: [
6100
+ /* @__PURE__ */ jsx110("style", { children: `
6101
+ /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
6102
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6103
+ .${axisLabelClass} { display: none; }
6104
+ .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
6105
+ .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
6106
+
6107
+ /* Tablet: Medium fonts, show axis titles, medium lines */
6108
+ @media (min-width: 640px) {
6109
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6110
+ .${axisLabelClass} {
6111
+ font-size: ${axisLabelFontSize * 0.7}px !important;
6112
+ display: block;
6113
+ }
6114
+ .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
6115
+ .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
6116
+ }
6117
+
6118
+ /* Desktop: Small fonts, show axis titles, normal lines */
6119
+ @media (min-width: 1024px) {
6120
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6121
+ .${axisLabelClass} {
6122
+ font-size: ${axisLabelFontSize * 0.4}px !important;
6123
+ display: block;
6124
+ }
6125
+ .${lineClass} { stroke-width: ${strokeWidth} !important; }
6126
+ .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
6127
+ }
6128
+ ` }),
6129
+ /* @__PURE__ */ jsx110(
6130
+ "svg",
6131
+ {
6132
+ ref: svgRef,
6133
+ width: "100%",
6134
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6135
+ preserveAspectRatio: "xMidYMid meet",
6136
+ className: "bg-white dark:bg-gray-800 block w-full",
6137
+ style: { height: "auto", overflow: "visible" },
6138
+ children: /* @__PURE__ */ jsxs37("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
6139
+ gridLines,
6140
+ data.map((series, seriesIndex) => {
6141
+ const baselineYValues = stacked ? [...cumulativeValues] : void 0;
6142
+ const areaPath = generateAreaPath(series, baselineYValues);
6143
+ const linePath = generateLinePath(series, baselineYValues);
6144
+ if (stacked) {
6145
+ series.data.forEach((point, i) => {
6146
+ cumulativeValues[i] += point.y;
6147
+ });
6148
+ }
6149
+ return /* @__PURE__ */ jsxs37("g", { children: [
6150
+ /* @__PURE__ */ jsx110(
6151
+ "path",
6152
+ {
6153
+ d: areaPath,
6154
+ fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6155
+ opacity: fillOpacity
6156
+ }
6157
+ ),
6158
+ /* @__PURE__ */ jsx110(
6159
+ "path",
6160
+ {
6161
+ d: linePath,
6162
+ fill: "none",
6163
+ stroke: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6164
+ strokeWidth,
6165
+ strokeLinecap: "round",
6166
+ strokeLinejoin: "round",
6167
+ className: lineClass
6168
+ }
6169
+ )
6170
+ ] }, `area-${seriesIndex}`);
6171
+ }),
6172
+ showDots && data.map((series, seriesIndex) => {
6173
+ const baselineYValues = stacked ? data.slice(0, seriesIndex).reduce((acc, s) => {
6174
+ return acc.map((val, i) => val + (s.data[i]?.y || 0));
6175
+ }, Array(series.data.length).fill(0)) : void 0;
6176
+ return series.data.map((point, pointIndex) => {
6177
+ const x = scaleX(point.x, pointIndex);
6178
+ const y = baselineYValues ? scaleY(baselineYValues[pointIndex] + point.y) : scaleY(point.y);
6179
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
6180
+ return /* @__PURE__ */ jsx110(
6181
+ "circle",
6182
+ {
6183
+ cx: x,
6184
+ cy: y,
6185
+ r: isHovered ? 6 : 4,
6186
+ fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6187
+ stroke: "white",
6188
+ strokeWidth: "2",
6189
+ className: `cursor-pointer transition-all ${pointClass}`,
6190
+ onMouseEnter: (e) => {
6191
+ if (showTooltip && containerRef.current) {
6192
+ setHoveredPoint({ seriesIndex, pointIndex, x, y });
6193
+ const containerRect = containerRef.current.getBoundingClientRect();
6194
+ const mouseX = e.clientX - containerRect.left;
6195
+ const mouseY = e.clientY - containerRect.top;
6196
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6197
+ }
6198
+ },
6199
+ onMouseMove: (e) => {
6200
+ if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
6201
+ const containerRect = containerRef.current.getBoundingClientRect();
6202
+ const mouseX = e.clientX - containerRect.left;
6203
+ const mouseY = e.clientY - containerRect.top;
6204
+ setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6205
+ }
6206
+ },
6207
+ onMouseLeave: () => {
6208
+ setHoveredPoint(null);
6209
+ setTooltipPosition(null);
6210
+ }
6211
+ },
6212
+ `point-${seriesIndex}-${pointIndex}`
6213
+ );
6214
+ });
6215
+ }),
6216
+ xAxisLabel && showXAxis && /* @__PURE__ */ jsx110(
6217
+ "text",
5723
6218
  {
5724
- d: areaPath,
5725
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5726
- opacity: fillOpacity
6219
+ x: chartWidth / 2,
6220
+ y: chartHeight + 80,
6221
+ textAnchor: "middle",
6222
+ dominantBaseline: "hanging",
6223
+ fontSize: axisLabelFontSize,
6224
+ fontWeight: "600",
6225
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6226
+ children: xAxisLabel
5727
6227
  }
5728
6228
  ),
5729
- /* @__PURE__ */ jsx110(
5730
- "path",
6229
+ yAxisLabel && showYAxis && /* @__PURE__ */ jsx110(
6230
+ "text",
5731
6231
  {
5732
- d: linePath,
5733
- fill: "none",
5734
- stroke: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5735
- strokeWidth,
5736
- strokeLinecap: "round",
5737
- strokeLinejoin: "round"
6232
+ x: -chartHeight / 2,
6233
+ y: -100,
6234
+ textAnchor: "middle",
6235
+ dominantBaseline: "middle",
6236
+ fontSize: axisLabelFontSize,
6237
+ fontWeight: "600",
6238
+ transform: "rotate(-90)",
6239
+ className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6240
+ children: yAxisLabel
5738
6241
  }
5739
6242
  )
5740
- ] }, `area-${seriesIndex}`);
5741
- }),
5742
- showDots && data.map((series, seriesIndex) => {
5743
- const baselineYValues = stacked ? data.slice(0, seriesIndex).reduce((acc, s) => {
5744
- return acc.map((val, i) => val + (s.data[i]?.y || 0));
5745
- }, Array(series.data.length).fill(0)) : void 0;
5746
- return series.data.map((point, pointIndex) => {
5747
- const x = scaleX(point.x, pointIndex);
5748
- const y = baselineYValues ? scaleY(baselineYValues[pointIndex] + point.y) : scaleY(point.y);
5749
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5750
- return /* @__PURE__ */ jsx110(
5751
- "circle",
5752
- {
5753
- cx: x,
5754
- cy: y,
5755
- r: isHovered ? 6 : 4,
5756
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
5757
- stroke: "white",
5758
- strokeWidth: "2",
5759
- className: "cursor-pointer transition-all",
5760
- onMouseEnter: () => showTooltip && setHoveredPoint({ seriesIndex, pointIndex, x, y }),
5761
- onMouseLeave: () => setHoveredPoint(null)
5762
- },
5763
- `point-${seriesIndex}-${pointIndex}`
5764
- );
5765
- });
5766
- }),
5767
- xAxisLabel && showXAxis && /* @__PURE__ */ jsx110(
5768
- "text",
6243
+ ] })
6244
+ }
6245
+ ),
6246
+ showLegend && /* @__PURE__ */ jsx110("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ jsxs37("div", { className: "flex items-center gap-2 text-sm", children: [
6247
+ /* @__PURE__ */ jsx110(
6248
+ "div",
5769
6249
  {
5770
- x: chartWidth / 2,
5771
- y: chartHeight + 50,
5772
- textAnchor: "middle",
5773
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5774
- children: xAxisLabel
6250
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
6251
+ style: {
6252
+ backgroundColor: series.color || defaultColors3[index % defaultColors3.length]
6253
+ }
5775
6254
  }
5776
6255
  ),
5777
- yAxisLabel && showYAxis && /* @__PURE__ */ jsx110(
5778
- "text",
5779
- {
5780
- x: -chartHeight / 2,
5781
- y: -45,
5782
- textAnchor: "middle",
5783
- transform: `rotate(-90, 0, 0)`,
5784
- className: "text-sm font-medium fill-gray-700 dark:fill-gray-300",
5785
- children: yAxisLabel
5786
- }
5787
- )
5788
- ] })
5789
- }
5790
- ),
5791
- showLegend && /* @__PURE__ */ jsx110("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((series, index) => /* @__PURE__ */ jsxs37("div", { className: "flex items-center gap-2", children: [
5792
- /* @__PURE__ */ jsx110(
5793
- "div",
5794
- {
5795
- className: "w-4 h-4 rounded-sm",
5796
- style: {
5797
- backgroundColor: series.color || defaultColors3[index % defaultColors3.length]
6256
+ /* @__PURE__ */ jsx110("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
6257
+ ] }, `legend-${index}`)) }),
6258
+ showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ jsxs37(
6259
+ "div",
6260
+ {
6261
+ 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",
6262
+ style: {
6263
+ left: `${tooltipPosition.x}px`,
6264
+ top: `${tooltipPosition.y}px`,
6265
+ transform: "translateZ(0)"
6266
+ },
6267
+ children: [
6268
+ /* @__PURE__ */ jsx110("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
6269
+ /* @__PURE__ */ jsxs37("div", { className: "text-xs opacity-90", children: [
6270
+ "x: ",
6271
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
6272
+ ] }),
6273
+ /* @__PURE__ */ jsxs37("div", { className: "text-xs opacity-90", children: [
6274
+ "y: ",
6275
+ data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
6276
+ ] })
6277
+ ]
5798
6278
  }
5799
- }
5800
- ),
5801
- /* @__PURE__ */ jsx110("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: series.name })
5802
- ] }, `legend-${index}`)) }),
5803
- showTooltip && hoveredPoint && /* @__PURE__ */ jsxs37(
5804
- "div",
5805
- {
5806
- 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",
5807
- style: {
5808
- left: `${padding.left + hoveredPoint.x + 10}px`,
5809
- top: `${padding.top + hoveredPoint.y - 30}px`
5810
- },
5811
- children: [
5812
- /* @__PURE__ */ jsx110("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5813
- /* @__PURE__ */ jsxs37("div", { children: [
5814
- "x: ",
5815
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5816
- ] }),
5817
- /* @__PURE__ */ jsxs37("div", { children: [
5818
- "y: ",
5819
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5820
- ] })
5821
- ]
5822
- }
5823
- )
5824
- ] });
6279
+ )
6280
+ ]
6281
+ }
6282
+ );
5825
6283
  };
5826
6284
 
5827
6285
  // src/components/PieChart.tsx
@@ -5847,22 +6305,37 @@ var defaultColors4 = [
5847
6305
  ];
5848
6306
  var PieChart = ({
5849
6307
  data,
5850
- width = 400,
5851
- height = 400,
6308
+ width: providedWidth,
6309
+ height: providedHeight,
6310
+ aspectRatio = 1,
6311
+ responsive = true,
5852
6312
  showLegend = true,
5853
6313
  showLabels = true,
5854
6314
  showValues = false,
5855
6315
  showPercentages = false,
5856
6316
  donut = false,
5857
6317
  donutWidth = 60,
5858
- className = ""
6318
+ className = "",
6319
+ baseFontSize = 14
5859
6320
  }) => {
6321
+ const viewBoxWidth = 600;
6322
+ const viewBoxHeight = 600;
6323
+ const containerRef = React29.useRef(null);
6324
+ const [tooltipPosition, setTooltipPosition] = React29.useState(null);
5860
6325
  const [hoveredSlice, setHoveredSlice] = React29.useState(null);
6326
+ const chartId = React29.useId();
6327
+ const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
6328
+ const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
6329
+ const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
6330
+ const sliceClass = `chart-slice-${chartId.replace(/:/g, "-")}`;
5861
6331
  const total = data.reduce((sum, item) => sum + item.value, 0);
5862
- const centerX = width / 2;
5863
- const centerY = height / 2;
5864
- const radius = Math.min(width, height) / 2 - 40;
6332
+ const centerX = viewBoxWidth / 2;
6333
+ const centerY = viewBoxHeight / 2;
6334
+ const radius = Math.min(viewBoxWidth, viewBoxHeight) / 2 - 40;
5865
6335
  const innerRadius = donut ? radius - donutWidth : 0;
6336
+ const gridLabelFontSize = viewBoxWidth * 0.045;
6337
+ const axisLabelFontSize = viewBoxWidth * 0.05;
6338
+ const titleFontSize = viewBoxWidth * 0.055;
5866
6339
  let currentAngle = -90;
5867
6340
  const slices = data.map((item, index) => {
5868
6341
  const percentage = item.value / total * 100;
@@ -5914,112 +6387,194 @@ var PieChart = ({
5914
6387
  index
5915
6388
  };
5916
6389
  });
5917
- return /* @__PURE__ */ jsxs38("div", { className: `relative inline-block ${className}`, children: [
5918
- /* @__PURE__ */ jsxs38(
5919
- "svg",
5920
- {
5921
- width,
5922
- height,
5923
- className: "bg-white dark:bg-gray-800",
5924
- children: [
5925
- slices.map((slice) => {
5926
- const isHovered = hoveredSlice === slice.index;
5927
- return /* @__PURE__ */ jsxs38("g", { children: [
6390
+ const getTooltipPosition = React29.useCallback((x, y) => {
6391
+ if (!containerRef.current) return { x, y };
6392
+ const rect = containerRef.current.getBoundingClientRect();
6393
+ const tooltipWidth = 120;
6394
+ const tooltipHeight = 80;
6395
+ let tooltipX = x + 10;
6396
+ let tooltipY = y - 30;
6397
+ if (tooltipX + tooltipWidth > rect.width) {
6398
+ tooltipX = x - tooltipWidth - 10;
6399
+ }
6400
+ if (tooltipY + tooltipHeight > rect.height) {
6401
+ tooltipY = y + 30;
6402
+ }
6403
+ return { x: tooltipX, y: tooltipY };
6404
+ }, []);
6405
+ const containerStyle = {
6406
+ "--font-size-base": `${baseFontSize}px`,
6407
+ "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6408
+ "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6409
+ "--font-size-xs": `calc(var(--font-size-base) * 0.75)`,
6410
+ "--font-size-xl": `calc(var(--font-size-base) * 1.5)`,
6411
+ "--font-size-2xl": `calc(var(--font-size-base) * 2)`
6412
+ };
6413
+ if (responsive) {
6414
+ containerStyle["--font-size-base"] = `clamp(${baseFontSize * 0.8}px, 4vw, ${baseFontSize}px)`;
6415
+ }
6416
+ return /* @__PURE__ */ jsxs38(
6417
+ "div",
6418
+ {
6419
+ ref: containerRef,
6420
+ className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6421
+ style: containerStyle,
6422
+ children: [
6423
+ /* @__PURE__ */ jsx111("style", { children: `
6424
+ /* Mobile: Large fonts (default) */
6425
+ .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6426
+ .${axisLabelClass} { font-size: ${titleFontSize}px !important; }
6427
+
6428
+ /* Tablet: Medium fonts */
6429
+ @media (min-width: 640px) {
6430
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6431
+ .${axisLabelClass} { font-size: ${titleFontSize * 0.7}px !important; }
6432
+ }
6433
+
6434
+ /* Desktop: Small fonts */
6435
+ @media (min-width: 1024px) {
6436
+ .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6437
+ .${axisLabelClass} { font-size: ${titleFontSize * 0.4}px !important; }
6438
+ }
6439
+ ` }),
6440
+ /* @__PURE__ */ jsxs38(
6441
+ "svg",
6442
+ {
6443
+ width: "100%",
6444
+ viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6445
+ preserveAspectRatio: "xMidYMid meet",
6446
+ className: "bg-white dark:bg-gray-800 block w-full",
6447
+ style: { height: "auto", overflow: "visible" },
6448
+ children: [
6449
+ slices.map((slice) => {
6450
+ const isHovered = hoveredSlice === slice.index;
6451
+ return /* @__PURE__ */ jsxs38("g", { children: [
6452
+ /* @__PURE__ */ jsx111(
6453
+ "path",
6454
+ {
6455
+ d: slice.path,
6456
+ fill: slice.color,
6457
+ stroke: "white",
6458
+ strokeWidth: "2",
6459
+ className: "cursor-pointer transition-opacity",
6460
+ opacity: isHovered ? 0.8 : 1,
6461
+ onMouseEnter: (e) => {
6462
+ setHoveredSlice(slice.index);
6463
+ if (containerRef.current) {
6464
+ const rect = containerRef.current.getBoundingClientRect();
6465
+ const svgX = e.clientX - rect.left;
6466
+ const svgY = e.clientY - rect.top;
6467
+ setTooltipPosition(getTooltipPosition(svgX, svgY));
6468
+ }
6469
+ },
6470
+ onMouseLeave: () => {
6471
+ setHoveredSlice(null);
6472
+ setTooltipPosition(null);
6473
+ }
6474
+ }
6475
+ ),
6476
+ showLabels && slice.percentage > 5 && /* @__PURE__ */ jsxs38(
6477
+ "text",
6478
+ {
6479
+ x: slice.labelX,
6480
+ y: slice.labelY,
6481
+ textAnchor: "middle",
6482
+ dominantBaseline: "middle",
6483
+ fontSize: gridLabelFontSize,
6484
+ fontWeight: "600",
6485
+ className: `fill-gray-700 dark:fill-gray-200 pointer-events-none ${gridLabelClass}`,
6486
+ children: [
6487
+ showPercentages && `${slice.percentage.toFixed(1)}%`,
6488
+ showPercentages && showValues && " ",
6489
+ showValues && `(${slice.value})`,
6490
+ !showPercentages && !showValues && slice.label
6491
+ ]
6492
+ }
6493
+ )
6494
+ ] }, slice.index);
6495
+ }),
6496
+ donut && /* @__PURE__ */ jsxs38("g", { children: [
6497
+ /* @__PURE__ */ jsx111(
6498
+ "text",
6499
+ {
6500
+ x: centerX,
6501
+ y: centerY - 10,
6502
+ textAnchor: "middle",
6503
+ dominantBaseline: "middle",
6504
+ fontSize: titleFontSize,
6505
+ fontWeight: "700",
6506
+ className: `fill-gray-900 dark:fill-gray-100 ${axisLabelClass}`,
6507
+ children: total
6508
+ }
6509
+ ),
6510
+ /* @__PURE__ */ jsx111(
6511
+ "text",
6512
+ {
6513
+ x: centerX,
6514
+ y: centerY + 15,
6515
+ textAnchor: "middle",
6516
+ dominantBaseline: "middle",
6517
+ fontSize: axisLabelFontSize,
6518
+ className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6519
+ children: "Total"
6520
+ }
6521
+ )
6522
+ ] })
6523
+ ]
6524
+ }
6525
+ ),
6526
+ showLegend && /* @__PURE__ */ jsx111("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((item, index) => /* @__PURE__ */ jsxs38(
6527
+ "div",
6528
+ {
6529
+ className: "flex items-center gap-2 text-sm cursor-pointer",
6530
+ onMouseEnter: () => setHoveredSlice(index),
6531
+ onMouseLeave: () => setHoveredSlice(null),
6532
+ children: [
5928
6533
  /* @__PURE__ */ jsx111(
5929
- "path",
6534
+ "div",
5930
6535
  {
5931
- d: slice.path,
5932
- fill: slice.color,
5933
- stroke: "white",
5934
- strokeWidth: "2",
5935
- className: "cursor-pointer transition-opacity",
5936
- opacity: isHovered ? 0.8 : 1,
5937
- onMouseEnter: () => setHoveredSlice(slice.index),
5938
- onMouseLeave: () => setHoveredSlice(null)
6536
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
6537
+ style: {
6538
+ backgroundColor: item.color || defaultColors4[index % defaultColors4.length]
6539
+ }
5939
6540
  }
5940
6541
  ),
5941
- showLabels && slice.percentage > 5 && /* @__PURE__ */ jsxs38(
5942
- "text",
5943
- {
5944
- x: slice.labelX,
5945
- y: slice.labelY,
5946
- textAnchor: "middle",
5947
- dominantBaseline: "middle",
5948
- className: "text-xs font-semibold fill-gray-700 dark:fill-gray-200 pointer-events-none",
5949
- children: [
5950
- showPercentages && `${slice.percentage.toFixed(1)}%`,
5951
- showPercentages && showValues && " ",
5952
- showValues && `(${slice.value})`,
5953
- !showPercentages && !showValues && slice.label
5954
- ]
5955
- }
5956
- )
5957
- ] }, slice.index);
5958
- }),
5959
- donut && /* @__PURE__ */ jsxs38("g", { children: [
5960
- /* @__PURE__ */ jsx111(
5961
- "text",
5962
- {
5963
- x: centerX,
5964
- y: centerY - 10,
5965
- textAnchor: "middle",
5966
- className: "text-2xl font-bold fill-gray-900 dark:fill-gray-100",
5967
- children: total
5968
- }
5969
- ),
5970
- /* @__PURE__ */ jsx111(
5971
- "text",
5972
- {
5973
- x: centerX,
5974
- y: centerY + 15,
5975
- textAnchor: "middle",
5976
- className: "text-sm fill-gray-600 dark:fill-gray-400",
5977
- children: "Total"
5978
- }
5979
- )
5980
- ] })
5981
- ]
5982
- }
5983
- ),
5984
- showLegend && /* @__PURE__ */ jsx111("div", { className: "flex flex-wrap gap-4 mt-4 justify-center", children: data.map((item, index) => /* @__PURE__ */ jsxs38(
5985
- "div",
5986
- {
5987
- className: "flex items-center gap-2 cursor-pointer",
5988
- onMouseEnter: () => setHoveredSlice(index),
5989
- onMouseLeave: () => setHoveredSlice(null),
5990
- children: [
5991
- /* @__PURE__ */ jsx111(
5992
- "div",
5993
- {
5994
- className: "w-4 h-4 rounded-sm",
5995
- style: {
5996
- backgroundColor: item.color || defaultColors4[index % defaultColors4.length]
5997
- }
5998
- }
5999
- ),
6000
- /* @__PURE__ */ jsxs38("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: [
6001
- item.label,
6002
- ": ",
6003
- item.value,
6004
- showPercentages && ` (${(item.value / total * 100).toFixed(1)}%)`
6005
- ] })
6006
- ]
6007
- },
6008
- `legend-${index}`
6009
- )) }),
6010
- hoveredSlice !== null && /* @__PURE__ */ jsxs38("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: [
6011
- /* @__PURE__ */ jsx111("div", { className: "font-semibold", children: data[hoveredSlice].label }),
6012
- /* @__PURE__ */ jsxs38("div", { children: [
6013
- "Value: ",
6014
- data[hoveredSlice].value
6015
- ] }),
6016
- /* @__PURE__ */ jsxs38("div", { children: [
6017
- "Percentage: ",
6018
- (data[hoveredSlice].value / total * 100).toFixed(1),
6019
- "%"
6020
- ] })
6021
- ] })
6022
- ] });
6542
+ /* @__PURE__ */ jsxs38("span", { className: "text-gray-700 dark:text-gray-300", children: [
6543
+ item.label,
6544
+ ": ",
6545
+ item.value,
6546
+ showPercentages && ` (${(item.value / total * 100).toFixed(1)}%)`
6547
+ ] })
6548
+ ]
6549
+ },
6550
+ `legend-${index}`
6551
+ )) }),
6552
+ hoveredSlice !== null && tooltipPosition && /* @__PURE__ */ jsxs38(
6553
+ "div",
6554
+ {
6555
+ 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",
6556
+ style: {
6557
+ left: `${tooltipPosition.x}px`,
6558
+ top: `${tooltipPosition.y}px`,
6559
+ transform: "translateZ(0)"
6560
+ },
6561
+ children: [
6562
+ /* @__PURE__ */ jsx111("div", { className: "font-semibold", children: data[hoveredSlice].label }),
6563
+ /* @__PURE__ */ jsxs38("div", { className: "text-xs opacity-90", children: [
6564
+ "Value: ",
6565
+ data[hoveredSlice].value
6566
+ ] }),
6567
+ /* @__PURE__ */ jsxs38("div", { className: "text-xs opacity-90", children: [
6568
+ "Percentage: ",
6569
+ (data[hoveredSlice].value / total * 100).toFixed(1),
6570
+ "%"
6571
+ ] })
6572
+ ]
6573
+ }
6574
+ )
6575
+ ]
6576
+ }
6577
+ );
6023
6578
  };
6024
6579
 
6025
6580
  // src/utils/theme-script.ts