@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.
- package/dist/esm/components/ui/Chart/Chart.js +1 -0
- package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
- package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
- package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
- package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
- package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +79 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
- package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
- package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
- package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
- package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
- package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
- package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
- package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
- package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
- package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
- package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
- package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/Chart/Chart.tsx +5 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
- package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
- package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
- package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
- package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
- package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
- package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
- package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
- package/src/components/ui/WorldMap/index.ts +2 -0
- package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
- package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
- package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
- package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
- package/src/components/widgets/DriverCard/index.ts +1 -0
- package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
- package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
- package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
- package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/index.ts +0 -2
- package/src/docs/config/webpack.config.js +1 -1
- package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
- package/src/docs/pages/DriverMapPage.tsx +214 -60
- package/src/docs/pages/PageColumnsPage.tsx +92 -0
- package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
- package/src/docs/pages/WorldMapPage.styl +14 -0
- package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
- package/src/docs/pages/WorldMapPage.tsx +26 -0
- package/src/docs/registry.ts +13 -1
- package/src/index.ts +2 -0
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
- package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
- package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
- package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
- /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
|
@@ -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,5 @@
|
|
|
1
1
|
@import '../../../lib/theme.styl'
|
|
2
|
-
@import '
|
|
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
|
|
|
@@ -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
|
-
<
|
|
173
|
+
<WorldMap className={S.mapWorld} />
|
|
176
174
|
{isLoading && <Shimmer className={S.shimmerOverlay} size="l" />}
|
|
177
175
|
{otherDrivers.map(driver => (
|
|
178
176
|
<DriverIcon
|
|
@@ -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,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<
|
|
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={
|
|
160
|
+
onTimeRangeChange={setTimeRange}
|
|
162
161
|
pinMonth={pinMonth}
|
|
163
162
|
onPinMonthChange={setPinMonth}
|
|
164
163
|
mode={mode}
|
|
@@ -1,59 +1,187 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
3
|
+
import { PageColumns, PageContentSection } from '#uilib/components/ui/Page';
|
|
4
4
|
import { Switch } from '#uilib/components/ui/Switch';
|
|
5
|
+
import { DriverCard } from '#uilib/components/widgets/DriverCard';
|
|
5
6
|
import {
|
|
6
7
|
type DriverData,
|
|
7
8
|
DriverMap,
|
|
9
|
+
getPreciseCoordinates,
|
|
8
10
|
} from '#uilib/components/widgets/DriverMap';
|
|
11
|
+
import { CaretLeft, CaretRight } from '@phosphor-icons/react';
|
|
9
12
|
|
|
10
13
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
11
14
|
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
12
15
|
|
|
16
|
+
/** ISO `YYYY-MM` keys, sorted ascending → series for DriverPerformanceChart. */
|
|
17
|
+
function buildMonthlySeries(
|
|
18
|
+
seed: number,
|
|
19
|
+
months: number,
|
|
20
|
+
drift = 0.15,
|
|
21
|
+
): NonNullable<DriverData['normalized_series']> {
|
|
22
|
+
const out: NonNullable<DriverData['normalized_series']> = {};
|
|
23
|
+
const start = new Date('2019-01-01');
|
|
24
|
+
for (let i = 0; i < months; i++) {
|
|
25
|
+
const d = new Date(start);
|
|
26
|
+
d.setMonth(d.getMonth() + i);
|
|
27
|
+
const key = d.toISOString().slice(0, 7);
|
|
28
|
+
out[key] =
|
|
29
|
+
seed + Math.sin(i * 0.35) * 14 + Math.cos(i * 0.11) * 7 + i * drift;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DriverPager({
|
|
35
|
+
drivers,
|
|
36
|
+
selectedDriver,
|
|
37
|
+
onSelect,
|
|
38
|
+
}: {
|
|
39
|
+
drivers: DriverData[];
|
|
40
|
+
selectedDriver: DriverData | null;
|
|
41
|
+
onSelect: (d: DriverData | null) => void;
|
|
42
|
+
}) {
|
|
43
|
+
const index = selectedDriver
|
|
44
|
+
? drivers.findIndex(d => d.id === selectedDriver.id)
|
|
45
|
+
: -1;
|
|
46
|
+
const total = drivers.length;
|
|
47
|
+
const safeIndex = index >= 0 ? index : 0;
|
|
48
|
+
const displayIndex = index >= 0 ? index + 1 : 0;
|
|
49
|
+
|
|
50
|
+
const go = (nextIdx: number) => {
|
|
51
|
+
if (total === 0) {
|
|
52
|
+
onSelect(null);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const wrapped = ((nextIdx % total) + total) % total;
|
|
56
|
+
onSelect(drivers[wrapped] ?? null);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
style={{
|
|
62
|
+
display: 'flex',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
gap: 6,
|
|
65
|
+
fontSize: 12,
|
|
66
|
+
color: 'var(--muted-foreground)',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
aria-label="Previous driver"
|
|
72
|
+
disabled={total === 0 || !selectedDriver}
|
|
73
|
+
onClick={() => go(safeIndex - 1)}
|
|
74
|
+
style={{
|
|
75
|
+
display: 'inline-flex',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
padding: 4,
|
|
79
|
+
borderRadius: 6,
|
|
80
|
+
border: '1px solid var(--border)',
|
|
81
|
+
background: 'var(--background)',
|
|
82
|
+
cursor: total && selectedDriver ? 'pointer' : 'default',
|
|
83
|
+
opacity: total && selectedDriver ? 1 : 0.45,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<CaretLeft size={14} />
|
|
87
|
+
</button>
|
|
88
|
+
<span
|
|
89
|
+
aria-live="polite"
|
|
90
|
+
style={{ minWidth: '2.5rem', textAlign: 'center' }}
|
|
91
|
+
>
|
|
92
|
+
{displayIndex}/{total}
|
|
93
|
+
</span>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
aria-label="Next driver"
|
|
97
|
+
disabled={total === 0 || !selectedDriver}
|
|
98
|
+
onClick={() => go(safeIndex + 1)}
|
|
99
|
+
style={{
|
|
100
|
+
display: 'inline-flex',
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
justifyContent: 'center',
|
|
103
|
+
padding: 4,
|
|
104
|
+
borderRadius: 6,
|
|
105
|
+
border: '1px solid var(--border)',
|
|
106
|
+
background: 'var(--background)',
|
|
107
|
+
cursor: total && selectedDriver ? 'pointer' : 'default',
|
|
108
|
+
opacity: total && selectedDriver ? 1 : 0.45,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<CaretRight size={14} />
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Drivers ordered for pager: long title + USA→World, EU negative + decimal %, Asia positive + large %, global negative.
|
|
13
118
|
const MOCK_DRIVERS: DriverData[] = [
|
|
14
119
|
{
|
|
15
|
-
id: '
|
|
16
|
-
name: '
|
|
17
|
-
region: ['
|
|
120
|
+
id: 'mock-us-world-long-title',
|
|
121
|
+
name: 'North American manufacturing PMI composite — orders, production, employment, supplier deliveries (SA, quarterly)',
|
|
122
|
+
region: ['North America', 'United States of America'],
|
|
18
123
|
category: 'Manufacturing',
|
|
19
124
|
isPublic: true,
|
|
20
|
-
importance:
|
|
21
|
-
direction: 0.
|
|
22
|
-
lag: '
|
|
23
|
-
coordinates:
|
|
125
|
+
importance: 84.2,
|
|
126
|
+
direction: 0.55,
|
|
127
|
+
lag: '1 quarter(s)',
|
|
128
|
+
coordinates: getPreciseCoordinates('United States of America'),
|
|
129
|
+
src_region: {
|
|
130
|
+
name: 'United States of America',
|
|
131
|
+
coordinates: { x: 0, y: 0 },
|
|
132
|
+
},
|
|
133
|
+
tgt_region: {
|
|
134
|
+
name: 'World',
|
|
135
|
+
coordinates: { x: 0, y: 0 },
|
|
136
|
+
},
|
|
137
|
+
rawImportance: { overall: { mean: 96 } },
|
|
138
|
+
summary:
|
|
139
|
+
'U.S. factory activity leads global manufacturing cycles by roughly one quarter: strength here typically coincides with wider world demand, with inventory and orders transmitting across trade links.',
|
|
140
|
+
normalized_series: buildMonthlySeries(48, 42, 0.22),
|
|
24
141
|
},
|
|
25
142
|
{
|
|
26
|
-
id: '
|
|
27
|
-
name: '
|
|
28
|
-
region: ['
|
|
29
|
-
category: '
|
|
143
|
+
id: 'mock-europe-germany-negative',
|
|
144
|
+
name: 'German industrial orders (domestic + foreign)',
|
|
145
|
+
region: ['Europe', 'Germany'],
|
|
146
|
+
category: 'Industrial',
|
|
30
147
|
isPublic: true,
|
|
31
|
-
importance:
|
|
32
|
-
direction: -0.
|
|
33
|
-
lag: '
|
|
34
|
-
coordinates:
|
|
148
|
+
importance: 81.6,
|
|
149
|
+
direction: -0.42,
|
|
150
|
+
lag: '2 month(s)',
|
|
151
|
+
coordinates: getPreciseCoordinates('Germany'),
|
|
152
|
+
summary:
|
|
153
|
+
'New orders in Germany are a timely read on euro-area capex and export pipelines; pullbacks often precede broader EU production softness before PMI surveys fully reflect the turn.',
|
|
154
|
+
normalized_series: buildMonthlySeries(62, 42, -0.12),
|
|
35
155
|
},
|
|
36
156
|
{
|
|
37
|
-
id: '
|
|
38
|
-
name: 'Japan
|
|
157
|
+
id: 'mock-asia-japan-positive',
|
|
158
|
+
name: 'Japan machinery orders (core private-sector ex. ships)',
|
|
39
159
|
region: ['Asia', 'Japan'],
|
|
40
|
-
category: '
|
|
160
|
+
category: 'market',
|
|
41
161
|
isPublic: true,
|
|
42
|
-
importance:
|
|
43
|
-
direction: 0.
|
|
44
|
-
lag: '
|
|
45
|
-
coordinates:
|
|
162
|
+
importance: 77,
|
|
163
|
+
direction: 0.28,
|
|
164
|
+
lag: '~1 month(s)',
|
|
165
|
+
coordinates: getPreciseCoordinates('Japan'),
|
|
166
|
+
rawImportance: { overall: { mean: 99 } },
|
|
167
|
+
summary:
|
|
168
|
+
'Core machinery orders capture forward-looking domestic investment and Asia supply-chain capex; spikes cluster around fiscal years and robotics/automation cycles.',
|
|
169
|
+
normalized_series: buildMonthlySeries(55, 42, 0.18),
|
|
46
170
|
},
|
|
47
171
|
{
|
|
48
|
-
id: '
|
|
49
|
-
name: 'Global risk appetite',
|
|
172
|
+
id: 'mock-global-risk-negative',
|
|
173
|
+
name: 'Global equity risk appetite (cross-asset composite)',
|
|
50
174
|
region: ['World'],
|
|
51
|
-
category: '
|
|
175
|
+
category: 'Trade',
|
|
52
176
|
isPublic: true,
|
|
53
|
-
importance:
|
|
54
|
-
direction: 0,
|
|
177
|
+
importance: 66.5,
|
|
178
|
+
direction: -0.05,
|
|
55
179
|
lag: 'Unknown',
|
|
56
|
-
coordinates:
|
|
180
|
+
coordinates: getPreciseCoordinates('World'),
|
|
181
|
+
rawImportance: { overall: { mean: 91 } },
|
|
182
|
+
summary:
|
|
183
|
+
'Combines credit spreads, vol surfaces, and cyclical equity factor tilt into a single read on whether markets are paying for growth or for safety; sharp deterioration often coincides with funding stress.',
|
|
184
|
+
normalized_series: buildMonthlySeries(40, 42, 0.08),
|
|
57
185
|
},
|
|
58
186
|
];
|
|
59
187
|
|
|
@@ -62,52 +190,78 @@ export default function DriverMapPage() {
|
|
|
62
190
|
MOCK_DRIVERS[0],
|
|
63
191
|
);
|
|
64
192
|
const [isLoading, setIsLoading] = useState(false);
|
|
193
|
+
// DriverCard checks `inQueue` before `isLoading` — toggles queue empty state independent of mocks.
|
|
194
|
+
const [inQueue, setInQueue] = useState(false);
|
|
65
195
|
|
|
66
196
|
return (
|
|
67
197
|
<>
|
|
68
198
|
<AppPageHeader
|
|
69
|
-
breadcrumbs={[{ label: '
|
|
70
|
-
title="DriverMap"
|
|
71
|
-
subheader="World map with driver badges
|
|
199
|
+
breadcrumbs={[{ label: 'Driver map' }]}
|
|
200
|
+
title="DriverMap & DriverCard"
|
|
201
|
+
subheader="World map with driver badges and the driver detail card — map clicks and the selector sync selection; arrow keys navigate the map."
|
|
72
202
|
actions={<DocsHeaderActions />}
|
|
73
203
|
/>
|
|
74
204
|
<PageContentSection>
|
|
75
205
|
<div
|
|
76
206
|
style={{
|
|
77
207
|
display: 'flex',
|
|
208
|
+
flexWrap: 'wrap',
|
|
78
209
|
alignItems: 'center',
|
|
79
|
-
gap:
|
|
210
|
+
gap: 16,
|
|
80
211
|
marginBottom: 16,
|
|
81
212
|
}}
|
|
82
213
|
>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
style={{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
drivers={MOCK_DRIVERS}
|
|
100
|
-
isLoading={isLoading}
|
|
101
|
-
selectedDriver={selectedDriver}
|
|
102
|
-
setSelectedDriver={setSelectedDriver}
|
|
103
|
-
/>
|
|
214
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
215
|
+
<Switch
|
|
216
|
+
id="loading-overlay"
|
|
217
|
+
checked={isLoading}
|
|
218
|
+
onCheckedChange={setIsLoading}
|
|
219
|
+
/>
|
|
220
|
+
<label htmlFor="loading-overlay">Loading state</label>
|
|
221
|
+
</div>
|
|
222
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
223
|
+
<Switch
|
|
224
|
+
id="in-queue"
|
|
225
|
+
checked={inQueue}
|
|
226
|
+
onCheckedChange={setInQueue}
|
|
227
|
+
/>
|
|
228
|
+
<label htmlFor="in-queue">In queue (empty state)</label>
|
|
229
|
+
</div>
|
|
104
230
|
</div>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
231
|
+
<PageColumns
|
|
232
|
+
fill="left"
|
|
233
|
+
columns={[
|
|
234
|
+
<div
|
|
235
|
+
key="map"
|
|
236
|
+
style={{
|
|
237
|
+
width: '100%',
|
|
238
|
+
maxWidth: '100%',
|
|
239
|
+
// @ts-expect-error CSS custom property on div
|
|
240
|
+
'--shimmer-color': 'var(--page-color)',
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
<DriverMap
|
|
244
|
+
drivers={MOCK_DRIVERS}
|
|
245
|
+
isLoading={isLoading}
|
|
246
|
+
selectedDriver={selectedDriver}
|
|
247
|
+
setSelectedDriver={setSelectedDriver}
|
|
248
|
+
/>
|
|
249
|
+
</div>,
|
|
250
|
+
<DriverCard
|
|
251
|
+
key="card"
|
|
252
|
+
selectedDriver={selectedDriver}
|
|
253
|
+
isLoading={isLoading}
|
|
254
|
+
inQueue={inQueue}
|
|
255
|
+
driverSelector={
|
|
256
|
+
<DriverPager
|
|
257
|
+
drivers={MOCK_DRIVERS}
|
|
258
|
+
selectedDriver={selectedDriver}
|
|
259
|
+
onSelect={setSelectedDriver}
|
|
260
|
+
/>
|
|
261
|
+
}
|
|
262
|
+
/>,
|
|
263
|
+
]}
|
|
264
|
+
/>
|
|
111
265
|
</PageContentSection>
|
|
112
266
|
</>
|
|
113
267
|
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
import { PageColumns, PageContentSection } from '#uilib/components/ui/Page';
|
|
4
|
+
|
|
5
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
6
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
7
|
+
|
|
8
|
+
const demoBlockStyle: CSSProperties = {
|
|
9
|
+
margin: 0,
|
|
10
|
+
padding: 'var(--p-4)',
|
|
11
|
+
borderRadius: 'var(--p-3)',
|
|
12
|
+
border: '1px solid var(--border)',
|
|
13
|
+
backgroundColor: 'var(--muted)',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function ColumnDemo({ label }: { label: string }) {
|
|
17
|
+
return <p style={demoBlockStyle}>{label}</p>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function PageColumnsPage() {
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<AppPageHeader
|
|
24
|
+
breadcrumbs={[{ label: 'PageColumns' }]}
|
|
25
|
+
title="PageColumns"
|
|
26
|
+
subheader="Responsive row of columns; fill prop controls which column(s) grow past the default width cap on wide viewports."
|
|
27
|
+
actions={<DocsHeaderActions />}
|
|
28
|
+
/>
|
|
29
|
+
<PageContentSection
|
|
30
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
|
|
31
|
+
>
|
|
32
|
+
<section>
|
|
33
|
+
<h3 style={{ marginBottom: '0.75rem' }}>
|
|
34
|
+
fill="all" (default)
|
|
35
|
+
</h3>
|
|
36
|
+
<p
|
|
37
|
+
style={{
|
|
38
|
+
marginBottom: '1rem',
|
|
39
|
+
color: 'var(--muted-foreground)',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Both columns grow; max-width cap is removed on stretched columns.
|
|
43
|
+
</p>
|
|
44
|
+
<PageColumns
|
|
45
|
+
columns={[
|
|
46
|
+
<ColumnDemo key="a" label="Column A" />,
|
|
47
|
+
<ColumnDemo key="b" label="Column B" />,
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
</section>
|
|
51
|
+
<section>
|
|
52
|
+
<h3 style={{ marginBottom: '0.75rem' }}>fill="left"</h3>
|
|
53
|
+
<p
|
|
54
|
+
style={{
|
|
55
|
+
marginBottom: '1rem',
|
|
56
|
+
color: 'var(--muted-foreground)',
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
First column grows; second stays at the default fixed width on wide
|
|
60
|
+
screens.
|
|
61
|
+
</p>
|
|
62
|
+
<PageColumns
|
|
63
|
+
fill="left"
|
|
64
|
+
columns={[
|
|
65
|
+
<ColumnDemo key="a" label="Growing left column" />,
|
|
66
|
+
<ColumnDemo key="b" label="Fixed sidebar" />,
|
|
67
|
+
]}
|
|
68
|
+
/>
|
|
69
|
+
</section>
|
|
70
|
+
<section>
|
|
71
|
+
<h3 style={{ marginBottom: '0.75rem' }}>fill="right"</h3>
|
|
72
|
+
<p
|
|
73
|
+
style={{
|
|
74
|
+
marginBottom: '1rem',
|
|
75
|
+
color: 'var(--muted-foreground)',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Last column grows; first stays at the default fixed width on wide
|
|
79
|
+
screens.
|
|
80
|
+
</p>
|
|
81
|
+
<PageColumns
|
|
82
|
+
fill="right"
|
|
83
|
+
columns={[
|
|
84
|
+
<ColumnDemo key="a" label="Fixed sidebar" />,
|
|
85
|
+
<ColumnDemo key="b" label="Growing right column" />,
|
|
86
|
+
]}
|
|
87
|
+
/>
|
|
88
|
+
</section>
|
|
89
|
+
</PageContentSection>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -2,13 +2,12 @@ import { useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
4
4
|
import { TimeRangeControls } from '#uilib/components/ui/TimeRangeControls';
|
|
5
|
-
import type { TimeRange } from '#uilib/components/ui/TimeRangeControls/TimeRangeControls.types';
|
|
6
5
|
|
|
7
6
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
8
7
|
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
9
8
|
|
|
10
9
|
export default function TimeRangeControlsPage() {
|
|
11
|
-
const [range, setRange] = useState<
|
|
10
|
+
const [range, setRange] = useState<string>('1y');
|
|
12
11
|
|
|
13
12
|
return (
|
|
14
13
|
<>
|
|
@@ -21,7 +20,7 @@ export default function TimeRangeControlsPage() {
|
|
|
21
20
|
<PageContentSection>
|
|
22
21
|
<TimeRangeControls
|
|
23
22
|
timeRange={range}
|
|
24
|
-
onTimeRangeChange={
|
|
23
|
+
onTimeRangeChange={setRange}
|
|
25
24
|
loading={false}
|
|
26
25
|
/>
|
|
27
26
|
</PageContentSection>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@import '../../components/ui/WorldMap/mapAspect.mixin.styl'
|
|
2
|
+
|
|
3
|
+
.demoFrame
|
|
4
|
+
position relative
|
|
5
|
+
width 100%
|
|
6
|
+
max-width 42rem
|
|
7
|
+
mapAspect()
|
|
8
|
+
border-radius var(--p-4)
|
|
9
|
+
overflow hidden
|
|
10
|
+
|
|
11
|
+
.demoFill
|
|
12
|
+
position absolute
|
|
13
|
+
inset 0
|
|
14
|
+
box-sizing border-box
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
2
|
+
import { WorldMap } from '#uilib/components/ui/WorldMap';
|
|
3
|
+
|
|
4
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
5
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
6
|
+
import S from './WorldMapPage.styl';
|
|
7
|
+
|
|
8
|
+
export default function WorldMapPage() {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<AppPageHeader
|
|
12
|
+
breadcrumbs={[{ label: 'World map' }]}
|
|
13
|
+
title="WorldMap"
|
|
14
|
+
subheader="Bundled SVG world map for layouts such as DriverMap — optional className for positioning."
|
|
15
|
+
actions={<DocsHeaderActions />}
|
|
16
|
+
/>
|
|
17
|
+
<PageContentSection>
|
|
18
|
+
<div className={S.demoFrame}>
|
|
19
|
+
<div className={S.demoFill}>
|
|
20
|
+
<WorldMap />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</PageContentSection>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}
|