@sybilion/uilib 1.2.26 → 1.3.1

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 (137) hide show
  1. package/dist/esm/components/ui/Chart/Chart.js +5 -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/lightweight/LightweightForecastChart.js +460 -0
  6. package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.styl.js +7 -0
  7. package/dist/esm/components/ui/Chart/lightweight/chartTime.js +16 -0
  8. package/dist/esm/components/ui/Chart/lightweight/lightweightForecastChart.helpers.js +114 -0
  9. package/dist/esm/components/ui/Chart/lightweight/quantileBandCustomSeries.js +147 -0
  10. package/dist/esm/components/ui/Chart/quantileBandConeChartData.js +131 -0
  11. package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
  12. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
  13. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
  14. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
  15. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
  16. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
  17. package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
  18. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
  19. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
  20. package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
  21. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
  22. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useQuantileBands.js +4 -102
  23. package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
  24. package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
  25. package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
  26. package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
  27. package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
  28. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +83 -0
  29. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
  30. package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
  31. package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
  32. package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
  33. package/dist/esm/index.js +4 -2
  34. package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +2 -0
  35. package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
  36. package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
  37. package/dist/esm/types/src/components/ui/Chart/lightweight/LightweightForecastChart.d.ts +26 -0
  38. package/dist/esm/types/src/components/ui/Chart/lightweight/chartTime.d.ts +5 -0
  39. package/dist/esm/types/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.d.ts +13 -0
  40. package/dist/esm/types/src/components/ui/Chart/lightweight/quantileBandCustomSeries.d.ts +24 -0
  41. package/dist/esm/types/src/components/ui/Chart/quantileBandConeChartData.d.ts +7 -0
  42. package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
  43. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
  44. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
  45. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
  46. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
  47. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
  48. package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
  49. package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
  50. package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
  51. package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
  52. package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
  53. package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
  54. package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
  55. package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
  56. package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
  57. package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
  58. package/dist/esm/types/src/docs/pages/LightweightChartPage.d.ts +1 -0
  59. package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
  60. package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
  61. package/dist/esm/types/src/index.d.ts +2 -0
  62. package/package.json +3 -2
  63. package/src/components/ui/Chart/Chart.tsx +9 -0
  64. package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
  65. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
  66. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
  67. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
  68. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl +25 -0
  69. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl.d.ts +11 -0
  70. package/src/components/ui/Chart/lightweight/LightweightForecastChart.tsx +721 -0
  71. package/src/components/ui/Chart/lightweight/chartTime.ts +18 -0
  72. package/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.ts +141 -0
  73. package/src/components/ui/Chart/lightweight/quantileBandCustomSeries.ts +215 -0
  74. package/src/components/ui/Chart/quantileBandConeChartData.ts +171 -0
  75. package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
  76. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
  77. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
  78. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
  79. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
  80. package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
  81. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
  82. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
  83. package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
  84. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
  85. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
  86. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
  87. package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
  88. package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
  89. package/src/components/ui/ChartAreaInteractive/overlays/useQuantileBands.ts +5 -131
  90. package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
  91. package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
  92. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
  93. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
  94. package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
  95. package/src/components/ui/WorldMap/index.ts +2 -0
  96. package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
  97. package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
  98. package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
  99. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
  100. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
  101. package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
  102. package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
  103. package/src/components/widgets/DriverCard/index.ts +1 -0
  104. package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
  105. package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
  106. package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
  107. package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
  108. package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
  109. package/src/components/widgets/DriverMap/index.ts +0 -2
  110. package/src/declarations.d.ts +2 -0
  111. package/src/docs/config/webpack.config.js +26 -3
  112. package/src/docs/index.tsx +1 -1
  113. package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
  114. package/src/docs/pages/DriverMapPage.tsx +214 -60
  115. package/src/docs/pages/LightweightChartPage.styl +18 -0
  116. package/src/docs/pages/LightweightChartPage.styl.d.ts +10 -0
  117. package/src/docs/pages/LightweightChartPage.tsx +195 -0
  118. package/src/docs/pages/PageColumnsPage.tsx +92 -0
  119. package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
  120. package/src/docs/pages/WorldMapPage.styl +14 -0
  121. package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
  122. package/src/docs/pages/WorldMapPage.tsx +26 -0
  123. package/src/docs/registry.ts +19 -1
  124. package/src/index.ts +2 -0
  125. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
  126. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
  127. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
  128. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
  129. package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
  130. package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
  131. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
  132. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
  133. package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
  134. /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
  135. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
  136. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
  137. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
@@ -0,0 +1,219 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { CoffeeIcon, TrendDownIcon, TrendUpIcon } from '@phosphor-icons/react';
4
+
5
+ import { Badge } from '../../ui/Badge';
6
+ import { Card, CardContent } from '../../ui/Card';
7
+ import { LabelWithId } from '../../ui/LabelWithId';
8
+ import { Skeleton } from '../../ui/Skeleton';
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/Tooltip';
10
+ import { getCategoryIcon } from '../DriverMap/driverCategoryIcon';
11
+ import type { DriverData } from '../DriverMap/driverMapGeography';
12
+ import S from './DriverCard.styl';
13
+ import { DriverPerformanceChart } from './DriverPerformanceChart';
14
+
15
+ export interface DriverCardProps {
16
+ selectedDriver: DriverData | null;
17
+ isLoading: boolean;
18
+ inQueue?: boolean;
19
+ driverSelector?: React.ReactNode;
20
+ }
21
+
22
+ export function DriverCard({
23
+ selectedDriver,
24
+ isLoading,
25
+ inQueue = false,
26
+ driverSelector,
27
+ }: DriverCardProps) {
28
+ const importanceDisplay = useMemo(() => {
29
+ if (!selectedDriver) return '';
30
+
31
+ let importance = 0;
32
+ if (
33
+ selectedDriver.rawImportance &&
34
+ typeof selectedDriver.rawImportance === 'object'
35
+ ) {
36
+ const importanceObj = selectedDriver.rawImportance as any;
37
+ if (importanceObj.overall && typeof importanceObj.overall === 'object') {
38
+ const overall = importanceObj.overall as any;
39
+ if (typeof overall.mean === 'number') {
40
+ importance = Math.round(overall.mean);
41
+ }
42
+ }
43
+ }
44
+
45
+ return importance > 0
46
+ ? `${importance}%`
47
+ : `${selectedDriver.importance.toFixed(1)}%`;
48
+ }, [selectedDriver]);
49
+
50
+ const regionDisplay = React.useMemo(() => {
51
+ if (!selectedDriver) return '';
52
+
53
+ // Check for src_region and tgt_region properties first
54
+ const driver = selectedDriver as any;
55
+ const srcRegion = driver.src_region;
56
+ const tgtRegion = driver.tgt_region;
57
+ const srcName = srcRegion?.name;
58
+ const tgtName = tgtRegion?.name;
59
+
60
+ // If both src_region and tgt_region have valid names
61
+ if (srcName && tgtName) {
62
+ // If source and target are the same, just show one
63
+ if (srcName === tgtName) {
64
+ return srcName;
65
+ }
66
+ return `${srcName} → ${tgtName}`;
67
+ }
68
+
69
+ // If only src_region has a valid name
70
+ if (srcName) {
71
+ if (srcName === 'World') {
72
+ return 'World';
73
+ }
74
+ return `${srcName} → World`;
75
+ }
76
+
77
+ // If only tgt_region has a valid name
78
+ if (tgtName) {
79
+ if (tgtName === 'World') {
80
+ return 'World';
81
+ }
82
+ return `World → ${tgtName}`;
83
+ }
84
+
85
+ if (selectedDriver.region.length === 1) return selectedDriver.region[0];
86
+
87
+ if (selectedDriver.region.length > 1) {
88
+ return (
89
+ selectedDriver.region
90
+ // .filter(region => region !== 'World')
91
+ .join(' → ')
92
+ );
93
+ }
94
+
95
+ // Fallback to "World" when no region information is available
96
+ return 'World';
97
+ }, [selectedDriver]);
98
+
99
+ if (inQueue) {
100
+ return (
101
+ <Card className={S.root}>
102
+ <CardContent centered fullHeight noScroll>
103
+ <div className={S.queueMessage}>
104
+ <CoffeeIcon size={24} weight="regular" />
105
+ <div>
106
+ <p>Retrieving drivers data.</p>
107
+ <p>This may take a while.</p>
108
+ </div>
109
+ </div>
110
+ </CardContent>
111
+ </Card>
112
+ );
113
+ }
114
+
115
+ if (isLoading) {
116
+ return (
117
+ <Card className={S.root}>
118
+ <CardContent noScroll>
119
+ <div className={S.skeletonContainer}>
120
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
121
+ <Skeleton className={`${S.skeletonItem} ${S.hm} ${S.w50}`} />
122
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w33}`} />
123
+ <div className={S.skeletonGroup}>
124
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.wFull}`} />
125
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
126
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w50}`} />
127
+ </div>
128
+ <Skeleton className={`${S.skeletonItem} ${S.hl} ${S.wFull}`} />
129
+ <div className={S.skeletonGroup}>
130
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.wFull}`} />
131
+ <Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
132
+ </div>
133
+ </div>
134
+ </CardContent>
135
+ </Card>
136
+ );
137
+ }
138
+
139
+ if (!selectedDriver) {
140
+ return (
141
+ <Card className={S.root}>
142
+ <CardContent centered fullHeight noScroll>
143
+ <span className={S.noDriverSelected}>No driver selected</span>
144
+ </CardContent>
145
+ </Card>
146
+ );
147
+ }
148
+
149
+ const { id, name, category, direction, lag, summary } = selectedDriver;
150
+ const directionText = direction > 0 ? 'Positive' : 'Negative';
151
+ const DirectionIcon = direction > 0 ? TrendUpIcon : TrendDownIcon;
152
+
153
+ const nameElem = (
154
+ <h4 className={`${S.driverTitle} ${S.truncated}`}>{name}</h4>
155
+ );
156
+
157
+ return (
158
+ <Card className={S.root} paddingSize="l">
159
+ <CardContent noScroll>
160
+ <div className={S.cardContent}>
161
+ {/* Driver Header */}
162
+ <div className={S.driverHeader}>
163
+ <div className={S.headerContent}>
164
+ <div className={S.topHeader}>
165
+ <p className={S.categoryInfo}>
166
+ <span className={S.categoryIcon}>
167
+ {getCategoryIcon(category)}
168
+ </span>
169
+ <span className={S.categoryText}>{category}</span>
170
+ </p>
171
+
172
+ {driverSelector}
173
+ </div>
174
+ {name.length > 60 ? (
175
+ <Tooltip>
176
+ <LabelWithId
177
+ id={id}
178
+ label={<TooltipTrigger asChild>{nameElem}</TooltipTrigger>}
179
+ />
180
+ <TooltipContent side="left" className={S.tooltipContent}>
181
+ <div className={S.tooltipTitle}>{name}</div>
182
+ </TooltipContent>
183
+ </Tooltip>
184
+ ) : (
185
+ <LabelWithId id={id} label={nameElem} />
186
+ )}
187
+
188
+ <p className={S.regionDisplay}>{regionDisplay}</p>
189
+ </div>
190
+ </div>
191
+
192
+ {/* Key Metrics */}
193
+ <div className={S.metricsSection}>
194
+ <div className={S.importanceScore}>{importanceDisplay}</div>
195
+ </div>
196
+
197
+ {/* Direction and Lag Information */}
198
+ <div className={S.directionLagSection}>
199
+ <Badge
200
+ variant={direction > 0 ? 'green' : 'red'}
201
+ className={S.directionBadge}
202
+ >
203
+ <DirectionIcon className={S.trendIcon} />
204
+ {directionText} correlation
205
+ </Badge>
206
+
207
+ <span className={S.lagInfo}>Lag: {lag}</span>
208
+ </div>
209
+
210
+ {/* Driver Performance Chart */}
211
+ <DriverPerformanceChart driver={selectedDriver} />
212
+
213
+ {/* Description */}
214
+ <p className={S.description}>{summary ?? ''}</p>
215
+ </div>
216
+ </CardContent>
217
+ </Card>
218
+ );
219
+ }
@@ -0,0 +1,43 @@
1
+ // Driver performance chart (normalized series)
2
+ .driverChartContainer
3
+ width 100%
4
+ position relative
5
+
6
+ svg
7
+ overflow visible
8
+
9
+ .chartContainer
10
+ height 214px
11
+ width 100%
12
+ margin-left -0.5rem
13
+ padding var(--p-4) 0
14
+
15
+ .chartContainerDisabled
16
+ opacity 0.1
17
+
18
+ .noDataMessage
19
+ position absolute
20
+ top 50%
21
+ left 50%
22
+ transform translate(-50%, -50%)
23
+ font-size 0.875rem
24
+ font-weight 500
25
+ color var(--foreground)
26
+ pointer-events none
27
+ z-index 10
28
+
29
+ .chartTooltip
30
+ background-color var(--popover)
31
+ border 1px solid var(--border)
32
+ border-radius 0.5rem
33
+ padding 0.75rem
34
+ box-shadow 0 10px 15px -3px rgba(0, 0, 0, 0.1)
35
+
36
+ .tooltipDate
37
+ font-size 0.875rem
38
+ font-weight 500
39
+ color var(--popover-foreground)
40
+
41
+ .tooltipPerformance
42
+ font-size 0.875rem
43
+ color var(--muted-foreground)
@@ -0,0 +1,13 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'chartContainer': string;
5
+ 'chartContainerDisabled': string;
6
+ 'chartTooltip': string;
7
+ 'driverChartContainer': string;
8
+ 'noDataMessage': string;
9
+ 'tooltipDate': string;
10
+ 'tooltipPerformance': string;
11
+ }
12
+ export const cssExports: CssExports;
13
+ export default cssExports;
@@ -0,0 +1,150 @@
1
+ import cn from 'classnames';
2
+ import { useMemo } from 'react';
3
+
4
+ import {
5
+ CartesianGrid,
6
+ Line,
7
+ LineChart,
8
+ Tooltip as RechartsTooltip,
9
+ type TooltipContentProps,
10
+ XAxis,
11
+ YAxis,
12
+ } from 'recharts';
13
+
14
+ import {
15
+ type ChartConfig,
16
+ ChartContainer,
17
+ ChartEmptyState,
18
+ } from '../../ui/Chart/Chart';
19
+ import type { DriverData } from '../DriverMap/driverMapGeography';
20
+ import S from './DriverPerformanceChart.styl';
21
+ import { generateDriverChartData } from './driverPerformanceChartData';
22
+
23
+ const driverChartConfig = {
24
+ value: {
25
+ label: 'Performance',
26
+ color: 'var(--primary)',
27
+ },
28
+ } satisfies ChartConfig;
29
+
30
+ export interface DriverPerformanceChartProps {
31
+ driver: DriverData;
32
+ }
33
+
34
+ export function DriverPerformanceChart({
35
+ driver,
36
+ }: DriverPerformanceChartProps) {
37
+ const precision = useMemo(() => {
38
+ if (!driver.normalized_series) return 7;
39
+
40
+ const values = Object.values(driver.normalized_series).filter(
41
+ (v): v is number => typeof v === 'number' && v !== null,
42
+ );
43
+
44
+ if (values.length === 0) return 7;
45
+
46
+ const absoluteValues = values.map(Math.abs).filter(v => v > 0);
47
+ if (absoluteValues.length === 0) return 7;
48
+
49
+ const minAbs = Math.min(...absoluteValues);
50
+
51
+ if (minAbs < 0.0001) return 7;
52
+ if (minAbs < 0.001) return 6;
53
+ if (minAbs < 0.01) return 5;
54
+ if (minAbs < 0.1) return 4;
55
+ if (minAbs < 1) return 3;
56
+ return 2;
57
+ }, [driver.normalized_series]);
58
+
59
+ const chartData = useMemo(
60
+ () => generateDriverChartData(driver, precision),
61
+ [driver, precision],
62
+ );
63
+
64
+ const noSeriesData = useMemo(() => {
65
+ if (!driver.normalized_series) return false;
66
+ return Object.values(driver.normalized_series).every(
67
+ value => value === null,
68
+ );
69
+ }, [driver.normalized_series]);
70
+
71
+ const renderTooltipContent = ({
72
+ active,
73
+ payload,
74
+ }: TooltipContentProps<number, string>) => {
75
+ if (active && payload?.length) {
76
+ const dataPoint = payload[0];
77
+ const row = dataPoint?.payload as
78
+ | { date?: Date; value?: number }
79
+ | undefined;
80
+ const date = row?.date;
81
+ const value = dataPoint?.value;
82
+
83
+ return (
84
+ <div className={S.chartTooltip}>
85
+ <div className={S.tooltipDate}>
86
+ {date instanceof Date
87
+ ? date.toLocaleDateString('en-US', {
88
+ month: 'long',
89
+ year: 'numeric',
90
+ })
91
+ : 'Unknown Date'}
92
+ </div>
93
+ <div className={S.tooltipPerformance}>
94
+ Performance:{' '}
95
+ {typeof value === 'number'
96
+ ? value.toFixed(precision)
97
+ : String(value ?? '')}
98
+ </div>
99
+ </div>
100
+ );
101
+ }
102
+ return null;
103
+ };
104
+
105
+ return (
106
+ <div className={S.driverChartContainer}>
107
+ <ChartContainer
108
+ config={driverChartConfig}
109
+ className={cn(
110
+ S.chartContainer,
111
+ noSeriesData && S.chartContainerDisabled,
112
+ )}
113
+ >
114
+ <LineChart
115
+ data={chartData}
116
+ margin={{ left: 8, right: 8, top: 8, bottom: -20 }}
117
+ >
118
+ <CartesianGrid vertical={false} />
119
+ <XAxis
120
+ dataKey="date"
121
+ tickLine={false}
122
+ axisLine={false}
123
+ tick={false}
124
+ tickMargin={0}
125
+ minTickGap={0}
126
+ />
127
+ <YAxis
128
+ tickLine={false}
129
+ axisLine={false}
130
+ tick={false}
131
+ tickMargin={0}
132
+ minTickGap={0}
133
+ width={0}
134
+ domain={['dataMin', 'dataMax']}
135
+ />
136
+ <RechartsTooltip cursor={false} content={renderTooltipContent} />
137
+ <Line dataKey="value" type="natural" stroke="var(--color-value)" />
138
+ </LineChart>
139
+ </ChartContainer>
140
+ {noSeriesData && (
141
+ <ChartEmptyState
142
+ variant="inline"
143
+ align="center"
144
+ status="No series data"
145
+ className={S.noDataMessage}
146
+ />
147
+ )}
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,64 @@
1
+ import type { DriverData } from '../DriverMap/driverMapGeography';
2
+
3
+ export type DriverPerformanceChartPoint = {
4
+ date: Date;
5
+ value: number;
6
+ };
7
+
8
+ /** Last non-null points from `normalized_series`, or deterministic fallback sample. */
9
+ export function generateDriverChartData(
10
+ driver: DriverData,
11
+ precision: number = 7,
12
+ ): DriverPerformanceChartPoint[] {
13
+ if (driver.normalized_series) {
14
+ const entries = Object.entries(driver.normalized_series);
15
+
16
+ const seriesData = entries
17
+ .filter(([, value]) => value !== null)
18
+ .map(([date, value]) => {
19
+ let parsedDate: Date;
20
+ try {
21
+ const dateStr = date.toString();
22
+
23
+ if (dateStr.includes('-')) {
24
+ parsedDate = new Date(`${dateStr}T00:00:00`);
25
+ } else {
26
+ parsedDate = new Date(dateStr);
27
+ }
28
+
29
+ if (Number.isNaN(parsedDate.getTime())) {
30
+ return null;
31
+ }
32
+ } catch {
33
+ return null;
34
+ }
35
+
36
+ const numericValue = parseFloat((value as number).toFixed(precision));
37
+
38
+ return {
39
+ date: parsedDate,
40
+ value: numericValue,
41
+ };
42
+ })
43
+ .filter((item): item is DriverPerformanceChartPoint => item !== null)
44
+ .sort((a, b) => a.date.getTime() - b.date.getTime())
45
+ .slice(-24);
46
+
47
+ if (seriesData.length > 0) {
48
+ return seriesData;
49
+ }
50
+ }
51
+
52
+ const data: DriverPerformanceChartPoint[] = [];
53
+ const baseValue = driver.importance / 100;
54
+
55
+ for (let i = 0; i < 12; i++) {
56
+ const randomVariation = (Math.random() - 0.5) * 0.2;
57
+ const value = Math.max(0, Math.min(1, baseValue + randomVariation));
58
+ data.push({
59
+ date: new Date(2023, i, 1),
60
+ value: parseFloat(value.toFixed(precision)),
61
+ });
62
+ }
63
+ return data;
64
+ }
@@ -0,0 +1 @@
1
+ export { DriverCard, type DriverCardProps } from './DriverCard';
@@ -1,5 +1,3 @@
1
- 'use client';
2
-
3
1
  import cn from 'classnames';
4
2
 
5
3
  import { Badge } from '#uilib/components/ui/Badge';
@@ -1,5 +1,5 @@
1
1
  @import '../../../lib/theme.styl'
2
- @import './MapBackground/mapAspect.mixin.styl'
2
+ @import '../../ui/WorldMap/mapAspect.mixin.styl'
3
3
 
4
4
 
5
5
  .root
@@ -22,6 +22,11 @@
22
22
  border-radius var(--p-4)
23
23
  overflow hidden
24
24
 
25
+ .mapWorld
26
+ position absolute
27
+ inset 0
28
+ box-sizing border-box
29
+
25
30
  .shimmerOverlay
26
31
  z-index 20
27
32
 
@@ -3,6 +3,7 @@
3
3
  interface CssExports {
4
4
  'inTransition': string;
5
5
  'mapInner': string;
6
+ 'mapWorld': string;
6
7
  'root': string;
7
8
  'shimmerOverlay': string;
8
9
  'worldDrivers': string;
@@ -1,8 +1,7 @@
1
- 'use client';
2
-
3
1
  import cn from 'classnames';
4
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
3
 
4
+ import { WorldMap } from '#uilib/components/ui/WorldMap';
6
5
  import useEvent from '#uilib/hooks/useEvent';
7
6
  import { Shimmer } from '@homecode/ui';
8
7
 
@@ -15,7 +14,6 @@ import {
15
14
  hasValidCoords,
16
15
  } from './DriverMap.helpers';
17
16
  import S from './DriverMap.styl';
18
- import { MapBackground } from './MapBackground/MapBackground';
19
17
  import type { DriverData } from './driverMapGeography';
20
18
  import { getHighestImportanceDriver } from './driverMapSelection';
21
19
 
@@ -172,7 +170,7 @@ export function DriverMap({
172
170
  return (
173
171
  <div className={cn(S.root, isTransitioning && S.inTransition)}>
174
172
  <div className={S.mapInner}>
175
- <MapBackground />
173
+ <WorldMap className={S.mapWorld} />
176
174
  {isLoading && <Shimmer className={S.shimmerOverlay} size="l" />}
177
175
  {otherDrivers.map(driver => (
178
176
  <DriverIcon
@@ -1,5 +1,3 @@
1
- 'use client';
2
-
3
1
  import {
4
2
  ChartBarIcon,
5
3
  ChartLineIcon,
@@ -13,6 +13,4 @@ export {
13
13
  getResponsiveCoordinates,
14
14
  svgToPercentage,
15
15
  } from './driverMapGeography';
16
- export { MapBackground } from './MapBackground/MapBackground';
17
- export { LoadingSpinner } from './LoadingSpinner/LoadingSpinner';
18
16
  export type { BadgeSize } from './DriverIcon/DriverIcon';
@@ -1,4 +1,6 @@
1
1
  declare const VERSION: string;
2
+ /** Base URL path for docs Router (empty at site root; e.g. `/uilib` on GitHub Pages project site). */
3
+ declare const DOCS_ROUTER_BASENAME: string;
2
4
 
3
5
  declare module '*.png';
4
6
  declare module '*.json';
@@ -22,9 +22,27 @@ const pkg = require('../../../package.json');
22
22
  const themeStyl = pathResolve(paths.src, 'theme.styl');
23
23
  const logoSvgPath = pathResolve(paths.assets, 'logo.svg');
24
24
 
25
+ /** GitHub Pages project sites live at /<repo>/; set PUBLIC_PATH=/repo-name/ for production deploy. */
26
+ function normalizePublicPath(raw) {
27
+ const v = (raw && String(raw).trim()) || '/';
28
+ if (v === '/') {
29
+ return '/';
30
+ }
31
+ const withLeading = v.startsWith('/') ? v : `/${v}`;
32
+ return withLeading.endsWith('/') ? withLeading : `${withLeading}/`;
33
+ }
34
+
35
+ function routerBasenameFromPublicPath(publicPath) {
36
+ if (publicPath === '/') {
37
+ return '';
38
+ }
39
+ return publicPath.replace(/\/$/, '');
40
+ }
41
+
25
42
  export default (env, argv) => {
26
43
  const isDev = argv.mode === 'development';
27
- const analyticsPath = `${paths.assets}/analytics.html`;
44
+ const publicPath = normalizePublicPath(process.env.PUBLIC_PATH);
45
+ const docsRouterBasename = routerBasenameFromPublicPath(publicPath);
28
46
  const alias = {
29
47
  '#uilib': paths.src,
30
48
  uilib: paths.src,
@@ -38,6 +56,7 @@ export default (env, argv) => {
38
56
  entry: [paths.docs],
39
57
  output: {
40
58
  path: paths.build,
59
+ publicPath,
41
60
  },
42
61
 
43
62
  resolve: {
@@ -104,7 +123,7 @@ export default (env, argv) => {
104
123
  type: 'asset/resource',
105
124
  },
106
125
  {
107
- include: /DriverMap[/\\]MapBackground[/\\]map\.svg$/,
126
+ include: /components[/\\]ui[/\\]WorldMap[/\\]map\.svg$/,
108
127
  type: 'asset/resource',
109
128
  },
110
129
  {
@@ -152,6 +171,7 @@ export default (env, argv) => {
152
171
  new webpack.DefinePlugin({
153
172
  isDEV: JSON.stringify(isDev),
154
173
  VERSION: JSON.stringify(pkg.version),
174
+ DOCS_ROUTER_BASENAME: JSON.stringify(docsRouterBasename),
155
175
  }),
156
176
  new CopyWebpackPlugin({
157
177
  patterns: [
@@ -174,7 +194,7 @@ export default (env, argv) => {
174
194
 
175
195
  new HtmlWebpackPlugin({
176
196
  lang: 'en',
177
- baseUrl: '/',
197
+ baseUrl: publicPath,
178
198
  filename: 'index.html',
179
199
  template: `${paths.assets}/index.html`,
180
200
  minify: isDev
@@ -221,6 +241,9 @@ export default (env, argv) => {
221
241
  hot: true,
222
242
  port: process.env.PORT || 8181,
223
243
  historyApiFallback: true,
244
+ ...(publicPath !== '/' && {
245
+ devMiddleware: { publicPath },
246
+ }),
224
247
  },
225
248
  });
226
249
  }
@@ -10,7 +10,7 @@ const elem = document.getElementById('app-root') as HTMLElement;
10
10
  const root = createRoot(elem);
11
11
 
12
12
  root.render(
13
- <BrowserRouter>
13
+ <BrowserRouter basename={DOCS_ROUTER_BASENAME || undefined}>
14
14
  <ThemeProvider activeColor={DEFAULT_THEME_ACTIVE_COLOR}>
15
15
  <App />
16
16
  </ThemeProvider>
@@ -1,7 +1,6 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
2
 
3
3
  import { ChartAreaInteractive } from '#uilib/components/ui/ChartAreaInteractive';
4
- import type { TimeRange } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers';
5
4
  import type {
6
5
  ChartDataPoint,
7
6
  OverlayMode,
@@ -91,7 +90,7 @@ type DemoMode = 'none' | OverlayMode;
91
90
 
92
91
  export default function ChartAreaInteractivePage() {
93
92
  const { isDarkMode } = useTheme();
94
- const [timeRange, setTimeRange] = useState<TimeRange>('1y');
93
+ const [timeRange, setTimeRange] = useState<string>('1y');
95
94
  const [pinMonth, setPinMonth] = useState<string | undefined>(undefined);
96
95
  const [demoMode, setDemoMode] = useState<DemoMode>('none');
97
96
  const [chartData] = useState<ChartDataPoint[]>(INITIAL_CHART);
@@ -158,7 +157,7 @@ export default function ChartAreaInteractivePage() {
158
157
  </Tabs>
159
158
  <ChartAreaInteractive
160
159
  timeRange={timeRange}
161
- onTimeRangeChange={v => setTimeRange(v as TimeRange)}
160
+ onTimeRangeChange={setTimeRange}
162
161
  pinMonth={pinMonth}
163
162
  onPinMonthChange={setPinMonth}
164
163
  mode={mode}