@sybilion/uilib 1.2.26 → 1.3.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.
Files changed (111) hide show
  1. package/dist/esm/components/ui/Chart/Chart.js +1 -0
  2. package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
  3. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
  4. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
  5. package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
  6. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
  7. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
  8. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
  9. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
  10. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
  11. package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
  12. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
  13. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
  14. package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
  15. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
  16. package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
  17. package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
  18. package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
  19. package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
  20. package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
  21. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +79 -0
  22. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
  23. package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
  24. package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
  25. package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
  26. package/dist/esm/index.js +3 -2
  27. package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
  28. package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
  29. package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
  30. package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
  31. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
  32. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
  33. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
  34. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
  35. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
  36. package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
  37. package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
  38. package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
  39. package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
  40. package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
  41. package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
  42. package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
  43. package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
  44. package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
  45. package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
  46. package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
  47. package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
  48. package/dist/esm/types/src/index.d.ts +2 -0
  49. package/package.json +1 -1
  50. package/src/components/ui/Chart/Chart.tsx +5 -0
  51. package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
  52. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
  53. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
  54. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
  55. package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
  56. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
  57. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
  58. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
  59. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
  60. package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
  61. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
  62. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
  63. package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
  64. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
  65. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
  66. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
  67. package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
  68. package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
  69. package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
  70. package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
  71. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
  72. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
  73. package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
  74. package/src/components/ui/WorldMap/index.ts +2 -0
  75. package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
  76. package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
  77. package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
  78. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
  79. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
  80. package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
  81. package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
  82. package/src/components/widgets/DriverCard/index.ts +1 -0
  83. package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
  84. package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
  85. package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
  86. package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
  87. package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
  88. package/src/components/widgets/DriverMap/index.ts +0 -2
  89. package/src/docs/config/webpack.config.js +1 -1
  90. package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
  91. package/src/docs/pages/DriverMapPage.tsx +214 -60
  92. package/src/docs/pages/PageColumnsPage.tsx +92 -0
  93. package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
  94. package/src/docs/pages/WorldMapPage.styl +14 -0
  95. package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
  96. package/src/docs/pages/WorldMapPage.tsx +26 -0
  97. package/src/docs/registry.ts +13 -1
  98. package/src/index.ts +2 -0
  99. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
  100. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
  101. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
  102. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
  103. package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
  104. package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
  105. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
  106. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
  107. package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
  108. /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
  109. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
  110. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
  111. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
@@ -6,23 +6,19 @@ import 'recharts';
6
6
  import '../../../Chart/Chart.context.js';
7
7
  import '../../../Chart/Chart.styl.js';
8
8
  import { useDebounceCallback } from '../../../../../hooks/useDebounceCallback.js';
9
- import useElemDrag from '../../../../../hooks/useDragElem.js';
10
9
  import { ChevronsLeftRight } from 'lucide-react';
11
10
  import S from '../../ChartAreaInteractive.styl.js';
12
11
  import { useChartYRange } from '../useChartYRange.js';
13
12
  import S$1 from './PinOverlay.styl.js';
14
13
 
15
- function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange, className, }) {
14
+ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange: _onPreviewMonthChange, className, }) {
16
15
  const { chartData } = baseChartProps;
17
16
  const chartRef = useRef(null);
18
17
  const pinRef = useRef(null);
19
18
  const pinPlaceholderRef = useRef(null);
20
19
  const pinContainerRef = useRef(null);
21
- const [isDraggingPin, setIsDraggingPin] = useState(false);
22
- const [isPinAnimating, setIsPinAnimating] = useState(true);
23
20
  const [isPinHovered, setIsPinHovered] = useState(false);
24
21
  const pinPosRef = useRef(0);
25
- const containerRectRef = useRef(null);
26
22
  const currPinMonthRef = useRef(pinMonth);
27
23
  const debouncedOnPinMonthChange = useDebounceCallback((...args) => onPinMonthChange?.(args[0]), 500, [onPinMonthChange]);
28
24
  const { yMin, yMax } = useChartYRange({
@@ -38,7 +34,6 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
38
34
  pinPlaceholderRef.current.style.left = `${position}%`;
39
35
  }
40
36
  };
41
- // Get full month and year for pin position to send to parent component
42
37
  const getPinMonthAndYear = (position) => {
43
38
  if (!chartData.length)
44
39
  return null;
@@ -54,18 +49,15 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
54
49
  return `${month} ${year}`;
55
50
  }
56
51
  };
57
- // Handle chart click to move pin to specific position
58
52
  const snapPinToPosition = (eventX, needMonthUpdate = true, immediateMonthUpdate = false) => {
59
53
  if (!pinContainerRef.current || !chartData.length)
60
54
  return;
61
55
  const pinContainerRect = pinContainerRef.current.getBoundingClientRect();
62
56
  const effectiveLeft = pinContainerRect.left;
63
57
  const effectiveWidth = pinContainerRect.width;
64
- // Calculate relative position within plotted area
65
58
  const relativeX = eventX - effectiveLeft;
66
59
  const rawPercentage = effectiveWidth > 0 ? (relativeX / effectiveWidth) * 100 : 0;
67
60
  const percentage = Math.max(0, Math.min(100, isNaN(rawPercentage) ? 0 : rawPercentage));
68
- // Snap to nearest data point
69
61
  const totalPoints = chartData.length;
70
62
  if (totalPoints > 1) {
71
63
  const nearestIndex = Math.round((percentage / 100) * (totalPoints - 1));
@@ -73,13 +65,11 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
73
65
  const snappedPercentage = (clampedIndex / (totalPoints - 1)) * 100;
74
66
  setPinPosition(isNaN(snappedPercentage) ? 0 : snappedPercentage);
75
67
  if (needMonthUpdate) {
76
- // Update news for all months (historical and forecast)
77
68
  const monthAndYear = getPinMonthAndYear();
78
69
  if (monthAndYear) {
79
70
  const isNewMonth = monthAndYear !== currPinMonthRef.current;
80
71
  if (isNewMonth)
81
72
  currPinMonthRef.current = monthAndYear;
82
- // When immediate (e.g. dragEnd), always notify parent so showFutureOutlook is correct
83
73
  if (immediateMonthUpdate) {
84
74
  onPinMonthChange?.(monthAndYear);
85
75
  }
@@ -90,11 +80,9 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
90
80
  }
91
81
  }
92
82
  };
93
- // Update pin position when pinMonth prop changes
94
83
  useEffect(() => {
95
84
  if (!pinMonth || !chartData.length)
96
85
  return;
97
- // Find the data point index for the given month
98
86
  const dataPointIndex = chartData.findIndex(point => {
99
87
  const date = new Date(point.date);
100
88
  const month = date.toLocaleDateString('en-US', { month: 'short' });
@@ -109,57 +97,16 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
109
97
  currPinMonthRef.current = pinMonth;
110
98
  }
111
99
  }, [pinMonth, chartData]);
112
- useElemDrag({
113
- elem: [chartRef],
114
- onDragStart: () => {
115
- setIsDraggingPin(true);
116
- containerRectRef.current =
117
- pinContainerRef.current?.getBoundingClientRect() || null;
118
- },
119
- onDrag: (delta) => {
120
- if (pinRef.current) {
121
- const containerRect = containerRectRef.current;
122
- if (!containerRect)
123
- return;
124
- const pinCurrentLeft = (pinPosRef.current / 100) * containerRect.width;
125
- const leftLimit = -pinCurrentLeft;
126
- const rightLimit = containerRect.width - pinCurrentLeft;
127
- const clampedDeltaX = Math.max(leftLimit, Math.min(rightLimit, delta.x));
128
- setIsPinAnimating(false);
129
- pinRef.current.style.transform = `translateX(${clampedDeltaX}px)`;
130
- // Calculate preview month based on current drag position
131
- if (onPreviewMonthChange && chartData.length > 0) {
132
- const newPositionPixels = pinCurrentLeft + clampedDeltaX;
133
- const newPercentage = (newPositionPixels / containerRect.width) * 100;
134
- const clampedPercentage = Math.max(0, Math.min(100, newPercentage));
135
- const previewMonth = getPinMonthAndYear(clampedPercentage);
136
- if (previewMonth) {
137
- onPreviewMonthChange(previewMonth);
138
- }
139
- }
140
- }
141
- },
142
- onDragEnd: (e) => {
143
- setIsDraggingPin(false);
144
- setTimeout(() => setIsPinAnimating(true), 200);
145
- if (pinRef.current)
146
- pinRef.current.style.transform = '';
147
- // Snap first so onPinMonthChange runs before we clear preview (avoids showFutureOutlook fallback)
148
- snapPinToPosition(e.clientX, true, true);
149
- if (onPreviewMonthChange) {
150
- onPreviewMonthChange(undefined);
151
- }
152
- },
153
- });
154
- const onPointerDown = (e) => {
155
- if (e.target.tagName !== 'svg')
100
+ const onChartClick = (e) => {
101
+ if (e.button !== 0)
102
+ return;
103
+ const target = e.target;
104
+ if (!target.closest?.('svg'))
156
105
  return;
157
106
  snapPinToPosition(e.clientX, true, false);
158
107
  };
159
108
  const overlay = (jsxs(Fragment, { children: [jsx("div", { className: cn(S.overlay, S$1.pinContainer), ref: pinContainerRef, children: jsx("div", { className: S$1.pinLineBase, style: { left: `${pinPosRef.current}%` }, ref: pinRef, children: jsx("div", { className: S$1.pinButton, "aria-label": "News pin", title: `Current month: ${getPinMonthAndYear() || 'Loading...'}`, children: jsx(ChevronsLeftRight, { className: S$1.pinIcon, "aria-hidden": "true" }) }) }) }), jsx("div", { className: S$1.pinPlaceholder, style: { left: `${pinPosRef.current}%` }, ref: pinPlaceholderRef, onPointerEnter: () => setIsPinHovered(true), onPointerLeave: () => setIsPinHovered(false) })] }));
160
- return (jsx("div", { className: cn(className, S$1.root, isDraggingPin && S$1.pinDragging, isPinAnimating && S$1.pinAnimating, isPinHovered && S$1.pinHovered), onPointerDown: onPointerDown,
161
- // onClick={e => snapPinToPosition(e.clientX)}
162
- ref: chartRef, children: jsx(BaseChartWrapper, { ...baseChartProps, yMin: yMin, yMax: yMax, autoScaleYAxis: false, overlayElements: overlay }) }));
109
+ return (jsx("div", { className: cn(className, S$1.root, S$1.pinAnimating, isPinHovered && S$1.pinHovered), onClick: onChartClick, ref: chartRef, children: jsx(BaseChartWrapper, { ...baseChartProps, yMin: yMin, yMax: yMax, autoScaleYAxis: false, overlayElements: overlay }) }));
163
110
  }
164
111
 
165
112
  export { PinOverlay };
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".PinOverlay_root__RUZxO{touch-action:pan-y}.PinOverlay_pinContainer__6sc1J{height:calc(var(--chart-height) - 70px);inset:0;margin:var(--p-5) var(--p-8) var(--p-24) var(--p-12);pointer-events:none;position:absolute;touch-action:pan-y;transition:transform .2s ease-out,z-index 0ms;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:2}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinContainer__6sc1J{z-index:10}.PinOverlay_pinLineBase__0BWsD{height:100%;pointer-events:none;position:absolute;width:3px}.PinOverlay_pinLineBase__0BWsD:before{border-radius:50%;box-shadow:0 4px 6px -1px var(--shadow-color);content:\"\";height:36px;left:-16px;position:absolute;top:-8px;width:36px;z-index:-1}.PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background);border-radius:12px;bottom:0;content:\"\";left:0;position:absolute;top:0;transform:translate3d(.5px,0,0);transition:background-color .3s ease-out;width:100%;z-index:1}.PinOverlay_pinDragging__gu3xp .PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background-alpha-700)}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinLineBase__0BWsD{transition:left .3s ease-out}.PinOverlay_pinButton__cnV1K{position:absolute;--offset:-8px;align-items:center;background-color:var(--background);border-radius:9999px;cursor:grab;display:flex;justify-content:center;left:-8px;left:var(--offset);margin-left:-8px;margin-left:var(--offset);padding:10px;pointer-events:auto;top:-18px;touch-action:none;transition:transform .2s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none}.PinOverlay_pinDragging__gu3xp .PinOverlay_pinButton__cnV1K{transition:none}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinButton__cnV1K{transform:scale(1.1)}.PinOverlay_pinButton__cnV1K:active{cursor:grabbing}.PinOverlay_pinIcon__s7Ze0{color:var(--foreground);height:16px;width:16px}.PinOverlay_pinPlaceholder__JhKcQ{cursor:grab;height:72px;margin-left:-40px;margin-top:-40px;pointer-events:auto;position:absolute;top:var(--p-5);touch-action:none;width:72px;z-index:20}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinPlaceholder__JhKcQ{transition:left .3s ease-out}.PinOverlay_pinPlaceholder__JhKcQ:active{cursor:grabbing}";
4
- var S = {"root":"PinOverlay_root__RUZxO","pinContainer":"PinOverlay_pinContainer__6sc1J","pinHovered":"PinOverlay_pinHovered__cGPjN","pinLineBase":"PinOverlay_pinLineBase__0BWsD","pinDragging":"PinOverlay_pinDragging__gu3xp","pinAnimating":"PinOverlay_pinAnimating__5XMJG","pinButton":"PinOverlay_pinButton__cnV1K","pinIcon":"PinOverlay_pinIcon__s7Ze0","pinPlaceholder":"PinOverlay_pinPlaceholder__JhKcQ"};
3
+ var css_248z = ".PinOverlay_root__RUZxO{touch-action:pan-y}.PinOverlay_pinContainer__6sc1J{height:calc(var(--chart-height) - 70px);inset:0;margin:var(--p-5) var(--p-8) var(--p-24) var(--p-12);pointer-events:none;position:absolute;touch-action:pan-y;transition:transform .2s ease-out,z-index 0ms;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:2}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinContainer__6sc1J{z-index:10}.PinOverlay_pinLineBase__0BWsD{height:100%;pointer-events:none;position:absolute;width:3px}.PinOverlay_pinLineBase__0BWsD:before{border-radius:50%;box-shadow:0 4px 6px -1px var(--shadow-color);content:\"\";height:36px;left:-16px;position:absolute;top:-8px;width:36px;z-index:-1}.PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background);border-radius:12px;bottom:0;content:\"\";left:0;position:absolute;top:0;transform:translate3d(.5px,0,0);transition:background-color .3s ease-out;width:100%;z-index:1}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinLineBase__0BWsD{transition:left .3s ease-out}.PinOverlay_pinButton__cnV1K{position:absolute;--offset:-8px;align-items:center;background-color:var(--background);border-radius:9999px;cursor:pointer;display:flex;justify-content:center;left:-8px;left:var(--offset);margin-left:-8px;margin-left:var(--offset);padding:10px;pointer-events:auto;top:-18px;touch-action:none;transition:transform .2s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinButton__cnV1K{transform:scale(1.1)}.PinOverlay_pinButton__cnV1K:active{cursor:grabbing}.PinOverlay_pinIcon__s7Ze0{color:var(--foreground);height:16px;width:16px}.PinOverlay_pinPlaceholder__JhKcQ{cursor:pointer;height:72px;margin-left:-40px;margin-top:-40px;pointer-events:auto;position:absolute;top:var(--p-5);touch-action:none;width:72px;z-index:20}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinPlaceholder__JhKcQ{transition:left .3s ease-out}.PinOverlay_pinPlaceholder__JhKcQ:active{cursor:grabbing}";
4
+ var S = {"root":"PinOverlay_root__RUZxO","pinContainer":"PinOverlay_pinContainer__6sc1J","pinHovered":"PinOverlay_pinHovered__cGPjN","pinLineBase":"PinOverlay_pinLineBase__0BWsD","pinAnimating":"PinOverlay_pinAnimating__5XMJG","pinButton":"PinOverlay_pinButton__cnV1K","pinIcon":"PinOverlay_pinIcon__s7Ze0","pinPlaceholder":"PinOverlay_pinPlaceholder__JhKcQ"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -70,6 +70,7 @@ function useThresholdButton({ lineRef, overlayContainerRef, initialValue, minVal
70
70
  onValueChangeThrottled(clampedValue);
71
71
  }, [clientYToValue, valueToPercent, minValue, maxValue]);
72
72
  const handleDragStart = useCallback((e) => {
73
+ e.stopPropagation();
73
74
  initialValueRef.current = initialValue;
74
75
  startYRef.current = e.clientY;
75
76
  setIsDragging(true);
@@ -15,11 +15,9 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
15
15
  Object.entries(point).forEach(([key, value]) => {
16
16
  if (key === 'date')
17
17
  return;
18
- // When selectedForecastId is provided, scale only from selected forecast + its quantile band
19
- // (exclude historical, other forecasts, and other forecasts' quantile values)
18
+ // When selectedForecastId is provided, scale from historical + selected forecast + its
19
+ // quantile band (exclude other forecasts and their quantile values only).
20
20
  if (forecastId !== undefined) {
21
- if (key === 'historical')
22
- return;
23
21
  if (key.startsWith('forecast_') && key !== `forecast_${forecastId}`)
24
22
  return;
25
23
  // Exclude q{quantile}_{otherAnalysisId} - only include selected forecast's quantiles
@@ -5,9 +5,14 @@ import { ToggleGroup, ToggleGroupItem } from '../ToggleGroup/ToggleGroup.js';
5
5
  import { TIME_RANGES } from './TimeRangeControls.constants.js';
6
6
  import S from './TimeRangeControls.styl.js';
7
7
 
8
- const TimeRangeControls = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsx("div", { className: S.timeRangeContainer, children: jsx(ToggleGroup, { type: "single", value: timeRange, onValueChange: onTimeRangeChange, variant: "outline", disabled: loading, className: S.timeRangeToggleGroup, children: TIME_RANGES.map(range => (jsx(ToggleGroupItem, { value: range, className: S.timeRangeToggleItem, children: range }, range))) }) })));
8
+ function toggleValueForTimeRange(timeRange) {
9
+ return TIME_RANGES.includes(timeRange)
10
+ ? timeRange
11
+ : '';
12
+ }
13
+ const TimeRangeControls = memo(({ timeRange, onTimeRangeChange, loading }) => (jsx("div", { className: S.timeRangeContainer, children: jsx(ToggleGroup, { type: "single", value: toggleValueForTimeRange(timeRange), onValueChange: onTimeRangeChange, variant: "outline", disabled: loading, className: S.timeRangeToggleGroup, children: TIME_RANGES.map(range => (jsx(ToggleGroupItem, { value: range, className: S.timeRangeToggleItem, children: range }, range))) }) })));
9
14
  TimeRangeControls.displayName = 'TimeRangeControls';
10
- const TimeRangeSelect = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsxs(Select, { value: timeRange, onValueChange: onTimeRangeChange, disabled: loading, children: [jsx(SelectTrigger, { className: S.timeRangeSelectTrigger, size: "sm", "aria-label": "Select a value", children: jsx(SelectValue, { placeholder: "1 year" }) }), jsx(SelectContent, { className: S.timeRangeSelectContent, children: TIME_RANGES.map(range => (jsx(SelectItem, { value: range, className: S.timeRangeSelectItem, children: range }, range))) })] })));
15
+ const TimeRangeSelect = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsxs(Select, { value: toggleValueForTimeRange(timeRange) || undefined, onValueChange: onTimeRangeChange, disabled: loading, children: [jsx(SelectTrigger, { className: S.timeRangeSelectTrigger, size: "sm", "aria-label": "Select a value", children: jsx(SelectValue, { placeholder: "1 year" }) }), jsx(SelectContent, { className: S.timeRangeSelectContent, children: TIME_RANGES.map(range => (jsx(SelectItem, { value: range, className: S.timeRangeSelectItem, children: range }, range))) })] })));
11
16
  TimeRangeSelect.displayName = 'TimeRangeSelect';
12
17
 
13
18
  export { TimeRangeControls, TimeRangeSelect };
@@ -0,0 +1,11 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import cn from 'classnames';
3
+ import S from './WorldMap.styl.js';
4
+ import mapBgUrl from './map.svg.js';
5
+
6
+ function WorldMap({ className }) {
7
+ const src = mapBgUrl;
8
+ return (jsx("img", { alt: "", className: cn(S.worldMap, className), decoding: "async", draggable: false, src: src }));
9
+ }
10
+
11
+ export { WorldMap };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = ".WorldMap_worldMap__XTiex{aspect-ratio:623.2/341.276;display:block;height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}";
4
+ var S = {"worldMap":"WorldMap_worldMap__XTiex"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };
@@ -0,0 +1,89 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import React__default, { useMemo } from 'react';
3
+ import { CoffeeIcon, TrendUpIcon, TrendDownIcon } from '@phosphor-icons/react';
4
+ import { Badge } from '../../ui/Badge/Badge.js';
5
+ import { Card, CardContent } from '../../ui/Card/Card.js';
6
+ import { LabelWithId } from '../../ui/LabelWithId/LabelWithId.js';
7
+ import { Skeleton } from '../../ui/Skeleton/Skeleton.js';
8
+ import { Tooltip, TooltipTrigger, TooltipContent } from '../../ui/Tooltip/Tooltip.js';
9
+ import { getCategoryIcon } from '../DriverMap/driverCategoryIcon.js';
10
+ import S from './DriverCard.styl.js';
11
+ import { DriverPerformanceChart } from './DriverPerformanceChart.js';
12
+
13
+ function DriverCard({ selectedDriver, isLoading, inQueue = false, driverSelector, }) {
14
+ const importanceDisplay = useMemo(() => {
15
+ if (!selectedDriver)
16
+ return '';
17
+ let importance = 0;
18
+ if (selectedDriver.rawImportance &&
19
+ typeof selectedDriver.rawImportance === 'object') {
20
+ const importanceObj = selectedDriver.rawImportance;
21
+ if (importanceObj.overall && typeof importanceObj.overall === 'object') {
22
+ const overall = importanceObj.overall;
23
+ if (typeof overall.mean === 'number') {
24
+ importance = Math.round(overall.mean);
25
+ }
26
+ }
27
+ }
28
+ return importance > 0
29
+ ? `${importance}%`
30
+ : `${selectedDriver.importance.toFixed(1)}%`;
31
+ }, [selectedDriver]);
32
+ const regionDisplay = React__default.useMemo(() => {
33
+ if (!selectedDriver)
34
+ return '';
35
+ // Check for src_region and tgt_region properties first
36
+ const driver = selectedDriver;
37
+ const srcRegion = driver.src_region;
38
+ const tgtRegion = driver.tgt_region;
39
+ const srcName = srcRegion?.name;
40
+ const tgtName = tgtRegion?.name;
41
+ // If both src_region and tgt_region have valid names
42
+ if (srcName && tgtName) {
43
+ // If source and target are the same, just show one
44
+ if (srcName === tgtName) {
45
+ return srcName;
46
+ }
47
+ return `${srcName} → ${tgtName}`;
48
+ }
49
+ // If only src_region has a valid name
50
+ if (srcName) {
51
+ if (srcName === 'World') {
52
+ return 'World';
53
+ }
54
+ return `${srcName} → World`;
55
+ }
56
+ // If only tgt_region has a valid name
57
+ if (tgtName) {
58
+ if (tgtName === 'World') {
59
+ return 'World';
60
+ }
61
+ return `World → ${tgtName}`;
62
+ }
63
+ if (selectedDriver.region.length === 1)
64
+ return selectedDriver.region[0];
65
+ if (selectedDriver.region.length > 1) {
66
+ return (selectedDriver.region
67
+ // .filter(region => region !== 'World')
68
+ .join(' → '));
69
+ }
70
+ // Fallback to "World" when no region information is available
71
+ return 'World';
72
+ }, [selectedDriver]);
73
+ if (inQueue) {
74
+ return (jsx(Card, { className: S.root, children: jsx(CardContent, { centered: true, fullHeight: true, noScroll: true, children: jsxs("div", { className: S.queueMessage, children: [jsx(CoffeeIcon, { size: 24, weight: "regular" }), jsxs("div", { children: [jsx("p", { children: "Retrieving drivers data." }), jsx("p", { children: "This may take a while." })] })] }) }) }));
75
+ }
76
+ if (isLoading) {
77
+ return (jsx(Card, { className: S.root, children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.skeletonContainer, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hm} ${S.w50}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w33}` }), jsxs("div", { className: S.skeletonGroup, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.wFull}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w50}` })] }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hl} ${S.wFull}` }), jsxs("div", { className: S.skeletonGroup, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.wFull}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` })] })] }) }) }));
78
+ }
79
+ if (!selectedDriver) {
80
+ return (jsx(Card, { className: S.root, children: jsx(CardContent, { centered: true, fullHeight: true, noScroll: true, children: jsx("span", { className: S.noDriverSelected, children: "No driver selected" }) }) }));
81
+ }
82
+ const { id, name, category, direction, lag, summary } = selectedDriver;
83
+ const directionText = direction > 0 ? 'Positive' : 'Negative';
84
+ const DirectionIcon = direction > 0 ? TrendUpIcon : TrendDownIcon;
85
+ const nameElem = (jsx("h4", { className: `${S.driverTitle} ${S.truncated}`, children: name }));
86
+ return (jsx(Card, { className: S.root, paddingSize: "l", children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.cardContent, children: [jsx("div", { className: S.driverHeader, children: jsxs("div", { className: S.headerContent, children: [jsxs("div", { className: S.topHeader, children: [jsxs("p", { className: S.categoryInfo, children: [jsx("span", { className: S.categoryIcon, children: getCategoryIcon(category) }), jsx("span", { className: S.categoryText, children: category })] }), driverSelector] }), name.length > 60 ? (jsxs(Tooltip, { children: [jsx(LabelWithId, { id: id, label: jsx(TooltipTrigger, { asChild: true, children: nameElem }) }), jsx(TooltipContent, { side: "left", className: S.tooltipContent, children: jsx("div", { className: S.tooltipTitle, children: name }) })] })) : (jsx(LabelWithId, { id: id, label: nameElem })), jsx("p", { className: S.regionDisplay, children: regionDisplay })] }) }), jsx("div", { className: S.metricsSection, children: jsx("div", { className: S.importanceScore, children: importanceDisplay }) }), jsxs("div", { className: S.directionLagSection, children: [jsxs(Badge, { variant: direction > 0 ? 'green' : 'red', className: S.directionBadge, children: [jsx(DirectionIcon, { className: S.trendIcon }), directionText, " correlation"] }), jsxs("span", { className: S.lagInfo, children: ["Lag: ", lag] })] }), jsx(DriverPerformanceChart, { driver: selectedDriver }), jsx("p", { className: S.description, children: summary ?? '' })] }) }) }));
87
+ }
88
+
89
+ export { DriverCard };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverCard_root__Kr5Xz{height:auto;min-height:500px;width:100%}@media (min-width:640px){.DriverCard_root__Kr5Xz{min-height:25rem}}@media (min-width:768px){.DriverCard_root__Kr5Xz{min-height:31.25rem}}.DriverCard_cardContent__zB8UV{display:flex;flex-direction:column;gap:.5rem}.DriverCard_noDriverSelected__-mVm-{color:var(--muted-foreground)}.DriverCard_driverHeader__0aeJ-{align-items:flex-start;display:flex;flex-direction:column;gap:.75rem;padding-bottom:.25rem}.DriverCard_headerContent__YNqtK{display:flex;flex-direction:column;flex-grow:1;gap:.5rem;width:100%}.DriverCard_topHeader__sUDUc{align-items:center;display:flex;justify-content:space-between;margin-top:-12px;min-height:36px}.DriverCard_categoryInfo__8Dhqn{align-items:center;color:var(--muted-foreground);display:flex;font-size:.75rem;gap:.25rem}.DriverCard_categoryIcon__lSIM8{color:var(--muted-foreground);height:.75rem;width:.75rem}.DriverCard_categoryText__DuifF{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;text-transform:lowercase}.DriverCard_categoryText__DuifF:first-letter{text-transform:uppercase}.DriverCard_driverTitle__tYQO1{color:var(--foreground);font-size:1.1rem;font-weight:500;line-height:1.4;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_driverTitle__tYQO1.DriverCard_truncated__RIn6f{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;cursor:help;overflow:hidden}.DriverCard_regionDisplay__QYVWx{color:var(--muted-foreground);font-size:.75rem}.DriverCard_metricsSection__uZViQ{margin-bottom:.5rem;text-align:left}.DriverCard_importanceScore__RGh5Y{align-items:center;color:var(--foreground);display:flex;font-size:2.25rem;font-weight:400;height:auto;line-height:1;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_directionLagSection__Mh-ep{align-items:center;display:flex;justify-content:space-between}.DriverCard_directionBadge__7EAHe{align-items:center;display:flex;gap:.5rem}.DriverCard_trendIcon__GtkDe{height:.75rem;width:.75rem}.DriverCard_lagInfo__enbxl{color:var(--muted-foreground);font-size:.75rem}.DriverCard_description__fNR4e{font-size:.875rem;line-height:1.625;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_skeletonContainer__AE9MP{display:flex;flex-direction:column;gap:1rem}.DriverCard_skeletonGroup__lQXls{display:flex;flex-direction:column;gap:.5rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hxs__taJow{height:.75rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hs__PlDPB{height:1rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hm__oFwUK{height:2rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hl__McKZn{height:8rem}.DriverCard_skeletonItem__pmx4S.DriverCard_w33__cI-vq{width:33.333333%}.DriverCard_skeletonItem__pmx4S.DriverCard_w50__DcUSo{width:50%}.DriverCard_skeletonItem__pmx4S.DriverCard_w75__To5oR{width:75%}.DriverCard_skeletonItem__pmx4S.DriverCard_w80__y8wQl{width:80%}.DriverCard_skeletonItem__pmx4S.DriverCard_wFull__TnaxF{width:100%}.DriverCard_tooltipContent__F6DUH{font-size:.75rem;max-width:20rem}.DriverCard_tooltipTitle__NieWB{font-family:var(--font-family-body);font-weight:400}.DriverCard_queueMessage__4hQxI{align-items:center;color:var(--muted-foreground);display:flex;flex-direction:column;font-size:.875rem;gap:var(--p-4);line-height:1.5;padding:var(--p-8) var(--p-4);text-align:center}.DriverCard_queueMessage__4hQxI svg{color:var(--muted-foreground);flex-shrink:0;opacity:.8}";
4
+ var S = {"root":"DriverCard_root__Kr5Xz","cardContent":"DriverCard_cardContent__zB8UV","noDriverSelected":"DriverCard_noDriverSelected__-mVm-","driverHeader":"DriverCard_driverHeader__0aeJ-","headerContent":"DriverCard_headerContent__YNqtK","topHeader":"DriverCard_topHeader__sUDUc","categoryInfo":"DriverCard_categoryInfo__8Dhqn","categoryIcon":"DriverCard_categoryIcon__lSIM8","categoryText":"DriverCard_categoryText__DuifF","driverTitle":"DriverCard_driverTitle__tYQO1","truncated":"DriverCard_truncated__RIn6f","regionDisplay":"DriverCard_regionDisplay__QYVWx","metricsSection":"DriverCard_metricsSection__uZViQ","importanceScore":"DriverCard_importanceScore__RGh5Y","directionLagSection":"DriverCard_directionLagSection__Mh-ep","directionBadge":"DriverCard_directionBadge__7EAHe","trendIcon":"DriverCard_trendIcon__GtkDe","lagInfo":"DriverCard_lagInfo__enbxl","description":"DriverCard_description__fNR4e","skeletonContainer":"DriverCard_skeletonContainer__AE9MP","skeletonGroup":"DriverCard_skeletonGroup__lQXls","skeletonItem":"DriverCard_skeletonItem__pmx4S","hxs":"DriverCard_hxs__taJow","hs":"DriverCard_hs__PlDPB","hm":"DriverCard_hm__oFwUK","hl":"DriverCard_hl__McKZn","w33":"DriverCard_w33__cI-vq","w50":"DriverCard_w50__DcUSo","w75":"DriverCard_w75__To5oR","w80":"DriverCard_w80__y8wQl","wFull":"DriverCard_wFull__TnaxF","tooltipContent":"DriverCard_tooltipContent__F6DUH","tooltipTitle":"DriverCard_tooltipTitle__NieWB","queueMessage":"DriverCard_queueMessage__4hQxI"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };
@@ -0,0 +1,79 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import cn from 'classnames';
3
+ import { useMemo } from 'react';
4
+ import { LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Line } from 'recharts';
5
+ import '../../ui/Chart/components/BaseChartWrapper.js';
6
+ import { ChartContainer } from '../../ui/Chart/components/ChartContainer.js';
7
+ import '../../ui/Chart/Chart.context.js';
8
+ import '../../ui/Chart/Chart.styl.js';
9
+ import '../../ui/AnalysisLineIcon/AnalysisLineIcon.styl.js';
10
+ import '@radix-ui/react-slot';
11
+ import '../../ui/Badge/Badge.styl.js';
12
+ import '../../ui/LabelWithId/LabelWithId.styl.js';
13
+ import '@radix-ui/react-select';
14
+ import 'lucide-react';
15
+ import '../../ui/Select/Select.styl.js';
16
+ import '../../ui/TextShimmer/TextShimmer.js';
17
+ import '@phosphor-icons/react';
18
+ import '../../ui/AnalysesSelector/AnalysesSelector.styl.js';
19
+ import '../../ui/Chart/components/CustomChartLegend/CustomChartLegend.styl.js';
20
+ import { ChartEmptyState } from '../../ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
21
+ import S from './DriverPerformanceChart.styl.js';
22
+ import { generateDriverChartData } from './driverPerformanceChartData.js';
23
+
24
+ const driverChartConfig = {
25
+ value: {
26
+ label: 'Performance',
27
+ color: 'var(--primary)',
28
+ },
29
+ };
30
+ function DriverPerformanceChart({ driver, }) {
31
+ const precision = useMemo(() => {
32
+ if (!driver.normalized_series)
33
+ return 7;
34
+ const values = Object.values(driver.normalized_series).filter((v) => typeof v === 'number' && v !== null);
35
+ if (values.length === 0)
36
+ return 7;
37
+ const absoluteValues = values.map(Math.abs).filter(v => v > 0);
38
+ if (absoluteValues.length === 0)
39
+ return 7;
40
+ const minAbs = Math.min(...absoluteValues);
41
+ if (minAbs < 0.0001)
42
+ return 7;
43
+ if (minAbs < 0.001)
44
+ return 6;
45
+ if (minAbs < 0.01)
46
+ return 5;
47
+ if (minAbs < 0.1)
48
+ return 4;
49
+ if (minAbs < 1)
50
+ return 3;
51
+ return 2;
52
+ }, [driver.normalized_series]);
53
+ const chartData = useMemo(() => generateDriverChartData(driver, precision), [driver, precision]);
54
+ const noSeriesData = useMemo(() => {
55
+ if (!driver.normalized_series)
56
+ return false;
57
+ return Object.values(driver.normalized_series).every(value => value === null);
58
+ }, [driver.normalized_series]);
59
+ const renderTooltipContent = ({ active, payload, }) => {
60
+ if (active && payload?.length) {
61
+ const dataPoint = payload[0];
62
+ const row = dataPoint?.payload;
63
+ const date = row?.date;
64
+ const value = dataPoint?.value;
65
+ return (jsxs("div", { className: S.chartTooltip, children: [jsx("div", { className: S.tooltipDate, children: date instanceof Date
66
+ ? date.toLocaleDateString('en-US', {
67
+ month: 'long',
68
+ year: 'numeric',
69
+ })
70
+ : 'Unknown Date' }), jsxs("div", { className: S.tooltipPerformance, children: ["Performance:", ' ', typeof value === 'number'
71
+ ? value.toFixed(precision)
72
+ : String(value ?? '')] })] }));
73
+ }
74
+ return null;
75
+ };
76
+ return (jsxs("div", { className: S.driverChartContainer, children: [jsx(ChartContainer, { config: driverChartConfig, className: cn(S.chartContainer, noSeriesData && S.chartContainerDisabled), children: jsxs(LineChart, { data: chartData, margin: { left: 8, right: 8, top: 8, bottom: -20 }, children: [jsx(CartesianGrid, { vertical: false }), jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tick: false, tickMargin: 0, minTickGap: 0 }), jsx(YAxis, { tickLine: false, axisLine: false, tick: false, tickMargin: 0, minTickGap: 0, width: 0, domain: ['dataMin', 'dataMax'] }), jsx(Tooltip, { cursor: false, content: renderTooltipContent }), jsx(Line, { dataKey: "value", type: "natural", stroke: "var(--color-value)" })] }) }), noSeriesData && (jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series data", className: S.noDataMessage }))] }));
77
+ }
78
+
79
+ export { DriverPerformanceChart };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = ".DriverPerformanceChart_driverChartContainer__27xZ-{position:relative;width:100%}.DriverPerformanceChart_driverChartContainer__27xZ- svg{overflow:visible}.DriverPerformanceChart_chartContainer__--rGL{height:214px;margin-left:-.5rem;padding:var(--p-4) 0;width:100%}.DriverPerformanceChart_chartContainerDisabled__ndtPZ{opacity:.1}.DriverPerformanceChart_noDataMessage__RO3u1{color:var(--foreground);font-size:.875rem;font-weight:500;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:10}.DriverPerformanceChart_chartTooltip__cpaQw{background-color:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);padding:.75rem}.DriverPerformanceChart_tooltipDate__B2aeP{color:var(--popover-foreground);font-size:.875rem;font-weight:500}.DriverPerformanceChart_tooltipPerformance__3hfgE{color:var(--muted-foreground);font-size:.875rem}";
4
+ var S = {"driverChartContainer":"DriverPerformanceChart_driverChartContainer__27xZ-","chartContainer":"DriverPerformanceChart_chartContainer__--rGL","chartContainerDisabled":"DriverPerformanceChart_chartContainerDisabled__ndtPZ","noDataMessage":"DriverPerformanceChart_noDataMessage__RO3u1","chartTooltip":"DriverPerformanceChart_chartTooltip__cpaQw","tooltipDate":"DriverPerformanceChart_tooltipDate__B2aeP","tooltipPerformance":"DriverPerformanceChart_tooltipPerformance__3hfgE"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };
@@ -0,0 +1,50 @@
1
+ /** Last non-null points from `normalized_series`, or deterministic fallback sample. */
2
+ function generateDriverChartData(driver, precision = 7) {
3
+ if (driver.normalized_series) {
4
+ const entries = Object.entries(driver.normalized_series);
5
+ const seriesData = entries
6
+ .filter(([, value]) => value !== null)
7
+ .map(([date, value]) => {
8
+ let parsedDate;
9
+ try {
10
+ const dateStr = date.toString();
11
+ if (dateStr.includes('-')) {
12
+ parsedDate = new Date(`${dateStr}T00:00:00`);
13
+ }
14
+ else {
15
+ parsedDate = new Date(dateStr);
16
+ }
17
+ if (Number.isNaN(parsedDate.getTime())) {
18
+ return null;
19
+ }
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ const numericValue = parseFloat(value.toFixed(precision));
25
+ return {
26
+ date: parsedDate,
27
+ value: numericValue,
28
+ };
29
+ })
30
+ .filter((item) => item !== null)
31
+ .sort((a, b) => a.date.getTime() - b.date.getTime())
32
+ .slice(-24);
33
+ if (seriesData.length > 0) {
34
+ return seriesData;
35
+ }
36
+ }
37
+ const data = [];
38
+ const baseValue = driver.importance / 100;
39
+ for (let i = 0; i < 12; i++) {
40
+ const randomVariation = (Math.random() - 0.5) * 0.2;
41
+ const value = Math.max(0, Math.min(1, baseValue + randomVariation));
42
+ data.push({
43
+ date: new Date(2023, i, 1),
44
+ value: parseFloat(value.toFixed(precision)),
45
+ });
46
+ }
47
+ return data;
48
+ }
49
+
50
+ export { generateDriverChartData };
@@ -1,13 +1,13 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
3
  import { useRef, useState, useMemo, useEffect, useCallback } from 'react';
4
+ import { WorldMap } from '../../ui/WorldMap/WorldMap.js';
4
5
  import useEvent from '../../../hooks/useEvent.js';
5
6
  import { Shimmer } from '@homecode/ui';
6
7
  import { DriverIcon } from './DriverIcon/DriverIcon.js';
7
8
  import constants from './DriverIcon/DriverIcon.constants.json.js';
8
9
  import { findMostSpecificRegion, getDriverCoordinates, hasValidCoords, calculateBadgeSizes } from './DriverMap.helpers.js';
9
10
  import S from './DriverMap.styl.js';
10
- import { MapBackground } from './MapBackground/MapBackground.js';
11
11
  import { getHighestImportanceDriver } from './driverMapSelection.js';
12
12
 
13
13
  const delay = (ms) => new Promise(r => setTimeout(r, ms));
@@ -113,7 +113,7 @@ function DriverMap({ drivers, isLoading, setSelectedDriver, selectedDriver, }) {
113
113
  callback: handleKeyDown,
114
114
  isCapture: true,
115
115
  });
116
- return (jsxs("div", { className: cn(S.root, isTransitioning && S.inTransition), children: [jsxs("div", { className: S.mapInner, children: [jsx(MapBackground, {}), isLoading && jsx(Shimmer, { className: S.shimmerOverlay, size: "l" }), otherDrivers.map(driver => (jsx(DriverIcon, { isLoading: isLoading, isVisible: isVisible, driver: driver, size: badgeSizes[driver.id] || 's', isSelected: selectedDriver?.id === driver.id, onClick: () => setSelectedDriver(driver) }, driver.id)))] }), jsx("div", { className: S.worldDrivers, children: worldDrivers.map(driver => {
116
+ return (jsxs("div", { className: cn(S.root, isTransitioning && S.inTransition), children: [jsxs("div", { className: S.mapInner, children: [jsx(WorldMap, { className: S.mapWorld }), isLoading && jsx(Shimmer, { className: S.shimmerOverlay, size: "l" }), otherDrivers.map(driver => (jsx(DriverIcon, { isLoading: isLoading, isVisible: isVisible, driver: driver, size: badgeSizes[driver.id] || 's', isSelected: selectedDriver?.id === driver.id, onClick: () => setSelectedDriver(driver) }, driver.id)))] }), jsx("div", { className: S.worldDrivers, children: worldDrivers.map(driver => {
117
117
  const driverWithCoords = {
118
118
  ...driver,
119
119
  coordinates: driver.coordinates || {
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverMap_root__JszhG{aspect-ratio:623.2/341.276;max-width:100%;overflow:hidden;padding:0;position:relative;width:100%}@media (min-width:768px){.DriverMap_root__JszhG{flex:1;height:35rem;width:-moz-fit-content;width:fit-content}}.DriverMap_mapInner__E7rZR{aspect-ratio:623.2/341.276;border-radius:var(--p-4);margin:0 auto;max-height:100%;max-width:100%;overflow:hidden;position:relative}.DriverMap_shimmerOverlay__UH2qz{z-index:20}.DriverMap_inTransition__lvYwJ{pointer-events:none}.DriverMap_worldDrivers__sZOIW{align-items:center;bottom:0;display:flex;gap:.5rem;padding:.5rem;position:absolute}.DriverMap_worldDrivers__sZOIW button{position:static!important;transform:none!important}.DriverMap_worldDrivers__sZOIW button>span{opacity:1!important;transform:none!important}";
4
- var S = {"root":"DriverMap_root__JszhG","mapInner":"DriverMap_mapInner__E7rZR","shimmerOverlay":"DriverMap_shimmerOverlay__UH2qz","inTransition":"DriverMap_inTransition__lvYwJ","worldDrivers":"DriverMap_worldDrivers__sZOIW"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverMap_root__JszhG{aspect-ratio:623.2/341.276;max-width:100%;overflow:hidden;padding:0;position:relative;width:100%}@media (min-width:768px){.DriverMap_root__JszhG{flex:1;height:35rem;width:-moz-fit-content;width:fit-content}}.DriverMap_mapInner__E7rZR{aspect-ratio:623.2/341.276;border-radius:var(--p-4);margin:0 auto;max-height:100%;max-width:100%;overflow:hidden;position:relative}.DriverMap_mapWorld__vmPp-{box-sizing:border-box;inset:0;position:absolute}.DriverMap_shimmerOverlay__UH2qz{z-index:20}.DriverMap_inTransition__lvYwJ{pointer-events:none}.DriverMap_worldDrivers__sZOIW{align-items:center;bottom:0;display:flex;gap:.5rem;padding:.5rem;position:absolute}.DriverMap_worldDrivers__sZOIW button{position:static!important;transform:none!important}.DriverMap_worldDrivers__sZOIW button>span{opacity:1!important;transform:none!important}";
4
+ var S = {"root":"DriverMap_root__JszhG","mapInner":"DriverMap_mapInner__E7rZR","mapWorld":"DriverMap_mapWorld__vmPp-","shimmerOverlay":"DriverMap_shimmerOverlay__UH2qz","inTransition":"DriverMap_inTransition__lvYwJ","worldDrivers":"DriverMap_worldDrivers__sZOIW"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
package/dist/esm/index.js CHANGED
@@ -81,6 +81,7 @@ export { Toggle } from './components/ui/Toggle/Toggle.js';
81
81
  export { ToggleGroup, ToggleGroupItem } from './components/ui/ToggleGroup/ToggleGroup.js';
82
82
  export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './components/ui/Tooltip/Tooltip.js';
83
83
  export { VimeoEmbed } from './components/ui/VimeoEmbed/VimeoEmbed.js';
84
+ export { WorldMap } from './components/ui/WorldMap/WorldMap.js';
84
85
  export { WorkspaceAppSwitcher } from './components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.js';
85
86
  export { WORKSPACE_APP_SLUG_BASE_PATH } from './components/ui/WorkspaceAppSwitcher/workspaceApp.types.js';
86
87
  export { WORKSPACE_APPS_LS_KEY } from './components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.js';
@@ -89,12 +90,11 @@ export { WORKSPACE_APP_ICONS, isWorkspaceAppIconKey } from './components/ui/Work
89
90
  export { findWorkspaceAppByPathname, workspaceAppSlugPath } from './components/ui/WorkspaceAppSwitcher/workspaceAppPaths.js';
90
91
  export { SidebarDatasetsItemsGrouped } from './components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js';
91
92
  export { groupSidebarDatasets } from './components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.js';
93
+ export { DriverCard } from './components/widgets/DriverCard/DriverCard.js';
92
94
  export { DriverMap } from './components/widgets/DriverMap/DriverMap.js';
93
95
  export { getCategoryIcon } from './components/widgets/DriverMap/driverCategoryIcon.js';
94
96
  export { getDriverImportance, getHighestImportanceDriver } from './components/widgets/DriverMap/driverMapSelection.js';
95
97
  export { geographicCoordinates, geographicToSVG, getContinentFromRegion, getPreciseCoordinates, getResponsiveCoordinates, svgToPercentage } from './components/widgets/DriverMap/driverMapGeography.js';
96
- export { MapBackground } from './components/widgets/DriverMap/MapBackground/MapBackground.js';
97
- export { LoadingSpinner } from './components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js';
98
98
  export { SybilionAppHeader } from './components/widgets/SybilionAppHeader/SybilionAppHeader.js';
99
99
  export { SybilionAuthLayout } from './components/widgets/SybilionAuthLayout/SybilionAuthLayout.js';
100
100
  export { SybilionAuthHeadline } from './components/widgets/SybilionAuthLayout/SybilionAuthHeadline.js';
@@ -103,6 +103,7 @@ export { SignInPage } from './components/widgets/SignInPage/SignInPage.js';
103
103
  export { ChartTooltipItem } from './components/ui/Chart/components/ChartTooltipItem.js';
104
104
  export { ChartLegendItem } from './components/ui/Chart/components/ChartLegendItem.js';
105
105
  export { CustomChartLegend } from './components/ui/Chart/components/CustomChartLegend/CustomChartLegend.js';
106
+ export { ChartEmptyState } from './components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
106
107
  export { ChartContainer, ChartStyle } from './components/ui/Chart/components/ChartContainer.js';
107
108
  export { ChartTooltipContent } from './components/ui/Chart/components/ChartTooltipContent.js';
108
109
  export { ChartLegendContent } from './components/ui/Chart/components/ChartLegendContent.js';
@@ -10,3 +10,4 @@ export { ChartTooltipItem } from './components/ChartTooltipItem';
10
10
  export { ChartLegendItem } from './components/ChartLegendItem';
11
11
  export { CustomChartLegend } from './components/CustomChartLegend/CustomChartLegend';
12
12
  export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, BaseChartWrapper, };
13
+ export { ChartEmptyState, type ChartEmptyStateProps, type ChartEmptyStatusTone, } from './components/ChartEmptyState/ChartEmptyState';
@@ -1,8 +1,9 @@
1
1
  import { ComponentProps, ReactNode } from 'react';
2
- import { ChartConfig, ChartContainer } from '#uilib/components/ui/Chart/Chart';
3
2
  import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
4
3
  import { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
5
4
  import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
5
+ import type { ChartConfig } from '../Chart.types';
6
+ import { ChartContainer } from './ChartContainer';
6
7
  import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
7
8
  export interface BaseChartWrapperProps {
8
9
  renderId?: string;
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from 'react';
2
+ export type ChartEmptyStatusTone = 'muted' | 'destructive';
3
+ export interface ChartEmptyStateProps {
4
+ className?: string;
5
+ /** Primary guidance (muted). */
6
+ hint?: ReactNode;
7
+ /** Status / technical detail. */
8
+ status?: ReactNode;
9
+ statusTone?: ChartEmptyStatusTone;
10
+ /** `panel`: chart-sized block with light fill. `inline`: text only (e.g. above chart). */
11
+ variant?: 'panel' | 'inline';
12
+ align?: 'center' | 'start';
13
+ }
14
+ export declare function ChartEmptyState({ className, hint, status, statusTone, variant, align, }: ChartEmptyStateProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared Recharts plot / margin math (DOM-agnostic except measurement entry points).
3
+ * Keeps BaseChartWrapper tooltip clamp and ChartAreaInteractive brush in sync.
4
+ */
5
+ export type ChartMargin = {
6
+ top: number;
7
+ right: number;
8
+ bottom: number;
9
+ left: number;
10
+ };
11
+ export declare const DEFAULT_CHART_MARGIN: ChartMargin;
12
+ export declare function resolveChartMargin(margin: Partial<ChartMargin> | undefined): ChartMargin;
13
+ /** Plot box inside `.recharts-wrapper` (Recharts cartesian convention). */
14
+ export declare function getPlotViewBox(wrapper: HTMLElement, m: ChartMargin): {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ };
20
+ export type PlotRect = {
21
+ left: number;
22
+ top: number;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ /**
27
+ * Plot area in `host` local px: prefer painted `.recharts-cartesian-grid`, else
28
+ * last `.recharts-wrapper` + margins. One `hostRect` read; grid/wrapper rects as needed.
29
+ */
30
+ export declare function measureHostRelativePlotRect(host: HTMLElement, margin: ChartMargin): PlotRect | null;