@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
@@ -1,3 +1,3 @@
1
1
  import { ChartAreaInteractiveProps } from './ChartAreaInteractive.types';
2
2
  export declare const chartConfig: {};
3
- export declare function ChartAreaInteractive({ className, chartContainerClassName, legendClassName, xAxisClassName, yAxisClassName, timeRange, onTimeRangeChange, pinMonth, onPinMonthChange, onPreviewMonthChange, chartData, forecastData, error, loading, isDarkTheme, footerActions, mode, selectedForecast, upperQuantiles, lowerQuantiles, selectedLowerQuantile, selectedUpperQuantile, onLowerQuantileChange, onUpperQuantileChange, lowerThreshold, upperThreshold, onLowerThresholdChange, onUpperThresholdChange, discreteThresholdValues, headerActions, excludeLegendIds, loadingAnalyses, onLegendClick, onAnalysisSelect, disableTimeRangeSelector, forecastLineStyle, selectedAnalysisId, chartRenderId, toggleLegendSeries, ensureAnalysisSeriesVisible, overlayForecastData, hiddenSeries, disableForecastHistoricalBridge, ...restProps }: ChartAreaInteractiveProps): import("react/jsx-runtime").JSX.Element;
3
+ export declare function ChartAreaInteractive({ className, chartContainerClassName, legendClassName, xAxisClassName, yAxisClassName, timeRange, onTimeRangeChange, pinMonth, onPinMonthChange, onPreviewMonthChange, chartData, forecastData, error, loading, isDarkTheme, footerActions, mode, selectedForecast, upperQuantiles, lowerQuantiles, selectedLowerQuantile, selectedUpperQuantile, onLowerQuantileChange, onUpperQuantileChange, lowerThreshold, upperThreshold, onLowerThresholdChange, onUpperThresholdChange, discreteThresholdValues, headerActions, excludeLegendIds, loadingAnalyses, onLegendClick, onAnalysisSelect, disableTimeRangeSelector, forecastLineStyle, selectedAnalysisId, chartRenderId, toggleLegendSeries, ensureAnalysisSeriesVisible, overlayForecastData, hiddenSeries, disableForecastHistoricalBridge, overlayElements: overlayElementsProp, ...restProps }: ChartAreaInteractiveProps): import("react/jsx-runtime").JSX.Element;
@@ -8,13 +8,22 @@ declare const timeRangeToMonths: {
8
8
  readonly '5y': 60;
9
9
  readonly All: 12;
10
10
  };
11
- export type TimeRange = keyof typeof timeRangeToMonths;
11
+ export type TimeRangePreset = keyof typeof timeRangeToMonths;
12
+ /** @deprecated Use `TimeRangePreset` or `string` for brush-encoded ranges. */
13
+ export type TimeRange = TimeRangePreset;
14
+ export declare const DRAG_TIME_RANGE_PREFIX: "__drag:";
15
+ export declare function encodeDragTimeRange(a: Date, b: Date): string;
16
+ export declare function parseDragTimeRange(s: string): {
17
+ start: Date;
18
+ end: Date;
19
+ } | null;
20
+ export declare function isTimeRangePreset(s: string): s is TimeRangePreset;
12
21
  export type FilterDataForTimeRangeOptions = {
13
22
  /** When set (e.g. selected forecast on Forecast tab), the window ends at the
14
23
  * latest point that has shared historical or that analysis — not at another run. */
15
24
  endDateAnchorAnalysisId?: number | null;
16
25
  };
17
- export declare const filterDataForTimeRange: (data: ChartDataPoint[], currentTimeRange: TimeRange, options?: FilterDataForTimeRangeOptions) => ChartDataPoint[];
26
+ export declare const filterDataForTimeRange: (data: ChartDataPoint[], currentTimeRange: string, options?: FilterDataForTimeRangeOptions) => ChartDataPoint[];
18
27
  export declare const shortDateFormatter: (value: string) => string;
19
28
  export declare const longDateFormatter: (value: string) => string;
20
29
  /**
@@ -1,7 +1,6 @@
1
1
  import { BaseChartWrapperProps } from '#uilib/components/ui/Chart/components/BaseChartWrapper';
2
2
  import type { ForecastData } from '#uilib/types/forecast-data';
3
3
  import { LegendPayload } from 'recharts';
4
- import { TimeRange } from './ChartAreaInteractive.helpers';
5
4
  export type OverlayMode = 'pin' | 'intervals' | 'thresholds';
6
5
  export interface ChartDataPoint {
7
6
  date: string;
@@ -22,7 +21,8 @@ export interface Analysis {
22
21
  }
23
22
  export interface ChartAreaInteractiveProps extends BaseChartWrapperProps {
24
23
  chartContainerClassName?: string;
25
- timeRange: TimeRange;
24
+ /** Preset (`6m`, `1y`, …, `All`) or `__drag:startMs,endMs` from chart brush. */
25
+ timeRange: string;
26
26
  onTimeRangeChange: (range: string) => void;
27
27
  pinMonth: string | undefined;
28
28
  onPinMonthChange: (month: string | undefined) => void;
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
3
+ export interface TimeRangeBrushHostProps {
4
+ chartData: ChartDataPoint[];
5
+ onTimeRangeChange: (range: string) => void;
6
+ enabled: boolean;
7
+ /** Bumps layout sync when chart remounts (e.g. dataset / render id). */
8
+ layoutKey?: string | number | null;
9
+ children: ReactNode;
10
+ }
11
+ /**
12
+ * Wraps chart; pointerdown on SVG starts horizontal brush. Plot box tracks the
13
+ * painted grid / Recharts plot (see TimeRangeBrushLayout.helpers).
14
+ */
15
+ export declare function TimeRangeBrushHost({ chartData, onTimeRangeChange, enabled, layoutKey, children, }: TimeRangeBrushHostProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { type ChartMargin, type PlotRect, measureHostRelativePlotRect, resolveChartMargin } from '#uilib/components/ui/Chart/tools/chartPlotGeometry';
2
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
3
+ /** Debounce for `window` resize only (RO uses rAF-coalesced sync). */
4
+ export declare const BRUSH_LAYOUT_RESIZE_DEBOUNCE_MS = 500;
5
+ export declare const BRUSH_MIN_DRAG_PX = 8;
6
+ export declare const BRUSH_DOUBLE_TAP_MS = 300;
7
+ export declare const BRUSH_DOUBLE_TAP_MAX_DIST_PX = 24;
8
+ export type { ChartMargin };
9
+ export { resolveChartMargin };
10
+ export type BrushPlotLayout = PlotRect;
11
+ /** Host-relative plot rect (grid-first, then wrapper + margins). */
12
+ export declare const measureBrushPlotLayout: typeof measureHostRelativePlotRect;
13
+ export declare function brushPlotLayoutsEqual(a: BrushPlotLayout | null, b: BrushPlotLayout | null): boolean;
14
+ export declare function brushClientXToDate(clientX: number, plotRect: DOMRect, chartData: ChartDataPoint[]): Date | null;
@@ -6,5 +6,5 @@ interface PinOverlayProps {
6
6
  onPreviewMonthChange?: (month: string | undefined) => void;
7
7
  className?: string;
8
8
  }
9
- export declare function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange, className, }: PinOverlayProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange: _onPreviewMonthChange, className, }: PinOverlayProps): import("react/jsx-runtime").JSX.Element;
10
10
  export {};
@@ -1,5 +1,5 @@
1
1
  export declare function PageColumns({ columns, fill, className, }: {
2
2
  columns: React.ReactNode[];
3
- fill: 'left' | 'right' | 'all';
3
+ fill?: 'left' | 'right' | 'all';
4
4
  className?: string;
5
5
  }): import("react/jsx-runtime").JSX.Element;
@@ -1,11 +1,9 @@
1
- import { TimeRange } from './TimeRangeControls.types';
2
- export declare const TimeRangeControls: import("react").MemoExoticComponent<({ timeRange, onTimeRangeChange, loading, }: {
3
- timeRange: TimeRange;
1
+ export type TimeRangeControlsProps = {
2
+ timeRange: string;
4
3
  onTimeRangeChange: (range: string) => void;
5
4
  loading?: boolean;
6
- }) => import("react/jsx-runtime").JSX.Element>;
7
- export declare const TimeRangeSelect: import("react").MemoExoticComponent<({ timeRange, onTimeRangeChange, loading, }: {
8
- timeRange: TimeRange;
9
- onTimeRangeChange: (range: string) => void;
5
+ };
6
+ export declare const TimeRangeControls: import("react").MemoExoticComponent<({ timeRange, onTimeRangeChange, loading }: TimeRangeControlsProps) => import("react/jsx-runtime").JSX.Element>;
7
+ export declare const TimeRangeSelect: import("react").MemoExoticComponent<({ timeRange, onTimeRangeChange, loading, }: TimeRangeControlsProps & {
10
8
  loading: boolean;
11
9
  }) => import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1,4 @@
1
+ export type WorldMapProps = {
2
+ className?: string;
3
+ };
4
+ export declare function WorldMap({ className }: WorldMapProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export { WorldMap } from './WorldMap';
2
+ export type { WorldMapProps } from './WorldMap';
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { DriverData } from '../DriverMap/driverMapGeography';
3
+ export interface DriverCardProps {
4
+ selectedDriver: DriverData | null;
5
+ isLoading: boolean;
6
+ inQueue?: boolean;
7
+ driverSelector?: React.ReactNode;
8
+ }
9
+ export declare function DriverCard({ selectedDriver, isLoading, inQueue, driverSelector, }: DriverCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { DriverData } from '../DriverMap/driverMapGeography';
2
+ export interface DriverPerformanceChartProps {
3
+ driver: DriverData;
4
+ }
5
+ export declare function DriverPerformanceChart({ driver, }: DriverPerformanceChartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { DriverData } from '../DriverMap/driverMapGeography';
2
+ export type DriverPerformanceChartPoint = {
3
+ date: Date;
4
+ value: number;
5
+ };
6
+ /** Last non-null points from `normalized_series`, or deterministic fallback sample. */
7
+ export declare function generateDriverChartData(driver: DriverData, precision?: number): DriverPerformanceChartPoint[];
@@ -0,0 +1 @@
1
+ export { DriverCard, type DriverCardProps } from './DriverCard';
@@ -3,6 +3,4 @@ export type { DriverData } from './driverMapGeography';
3
3
  export { getCategoryIcon } from './driverCategoryIcon';
4
4
  export { getDriverImportance, getHighestImportanceDriver, } from './driverMapSelection';
5
5
  export { geographicCoordinates, geographicToSVG, getContinentFromRegion, getPreciseCoordinates, getResponsiveCoordinates, svgToPercentage, } from './driverMapGeography';
6
- export { MapBackground } from './MapBackground/MapBackground';
7
- export { LoadingSpinner } from './LoadingSpinner/LoadingSpinner';
8
6
  export type { BadgeSize } from './DriverIcon/DriverIcon';
@@ -0,0 +1 @@
1
+ export default function PageColumnsPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export default function WorldMapPage(): import("react/jsx-runtime").JSX.Element;
@@ -58,8 +58,10 @@ export * from './components/ui/Toggle';
58
58
  export * from './components/ui/ToggleGroup';
59
59
  export * from './components/ui/Tooltip';
60
60
  export * from './components/ui/VimeoEmbed';
61
+ export * from './components/ui/WorldMap';
61
62
  export * from './components/ui/WorkspaceAppSwitcher';
62
63
  export * from './components/widgets/SidebarDatasetsItemsGrouped';
64
+ export * from './components/widgets/DriverCard';
63
65
  export * from './components/widgets/DriverMap';
64
66
  export * from './components/widgets/SybilionAppHeader';
65
67
  export * from './components/widgets/SybilionAuthLayout';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.2.26",
3
+ "version": "1.3.0",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -21,3 +21,8 @@ export {
21
21
  ChartStyle,
22
22
  BaseChartWrapper,
23
23
  };
24
+ export {
25
+ ChartEmptyState,
26
+ type ChartEmptyStateProps,
27
+ type ChartEmptyStatusTone,
28
+ } from './components/ChartEmptyState/ChartEmptyState';
@@ -9,15 +9,7 @@ import {
9
9
  useState,
10
10
  } from 'react';
11
11
 
12
- import {
13
- ChartConfig,
14
- ChartContainer,
15
- ChartTooltip,
16
- ChartTooltipContent,
17
- CustomChartLegend,
18
- } from '#uilib/components/ui/Chart/Chart';
19
12
  import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
20
- import { QuantileBands } from '#uilib/components/ui/Chart/components/QuantileBands';
21
13
  import { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
22
14
  import {
23
15
  ChartLines,
@@ -26,47 +18,22 @@ import {
26
18
  } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
27
19
  import { Skeleton } from '#uilib/components/ui/Skeleton';
28
20
  import { chartRenderQueue } from '#uilib/utils/chartRenderQueue';
29
- import { ComposedChart, LineChart } from 'recharts';
21
+ import { ComposedChart, LineChart, Tooltip as ChartTooltip } from 'recharts';
22
+
23
+ import type { ChartConfig } from '../Chart.types';
24
+ import { ChartContainer } from './ChartContainer';
25
+ import { ChartTooltipContent } from './ChartTooltipContent';
26
+ import { CustomChartLegend } from './CustomChartLegend/CustomChartLegend';
27
+ import { QuantileBands } from './QuantileBands';
30
28
  import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
31
29
 
30
+ import { getPlotViewBox, resolveChartMargin } from '../tools/chartPlotGeometry';
32
31
  import { formatDate } from '../tools/formatters';
33
32
  import S from './BaseChartWrapper.styl';
34
33
  import { ChartAxes } from './ChartAxes';
35
34
  import { ChartGrid } from './ChartGrid';
36
35
  import { LegendSvg } from './LegendSvg/LegendSvg';
37
36
 
38
- type ChartMargin = { top: number; right: number; bottom: number; left: number };
39
-
40
- const DEFAULT_CHART_MARGIN: ChartMargin = {
41
- top: 5,
42
- right: 5,
43
- bottom: 5,
44
- left: 5,
45
- };
46
-
47
- function resolveChartMargin(
48
- margin: Partial<ChartMargin> | undefined,
49
- ): ChartMargin {
50
- return {
51
- top: margin?.top ?? DEFAULT_CHART_MARGIN.top,
52
- right: margin?.right ?? DEFAULT_CHART_MARGIN.right,
53
- bottom: margin?.bottom ?? DEFAULT_CHART_MARGIN.bottom,
54
- left: margin?.left ?? DEFAULT_CHART_MARGIN.left,
55
- };
56
- }
57
-
58
- /** Plot box inside `.recharts-wrapper`, same convention as Recharts cartesian viewBox. */
59
- function getPlotViewBox(wrapper: HTMLElement, m: ChartMargin) {
60
- const w = wrapper.clientWidth;
61
- const h = wrapper.clientHeight;
62
- return {
63
- x: m.left,
64
- y: m.top,
65
- width: Math.max(0, w - m.left - m.right),
66
- height: Math.max(0, h - m.top - m.bottom),
67
- };
68
- }
69
-
70
37
  function clampTooltipTranslate(args: {
71
38
  coordinate: { x: number; y: number };
72
39
  viewBox: { x: number; y: number; width: number; height: number };
@@ -0,0 +1,60 @@
1
+ @import '../../../../../lib/theme.styl'
2
+
3
+ .root
4
+ display flex
5
+ flex-direction column
6
+ gap var(--p-1)
7
+ max-width 42rem
8
+ padding 0 var(--p-3)
9
+
10
+ border-radius var(--p-4)
11
+ backdrop-filter blur(4px)
12
+ box-shadow 0 0 0 2px var(--page-color)
13
+ pointer-events auto
14
+
15
+ .rootPanel
16
+ width 100%
17
+ min-height var(--chart-height)
18
+ justify-content center
19
+ padding var(--p-6)
20
+ border-radius var(--radius-md)
21
+ background var(--muted) / 0.35
22
+
23
+ .rootInline
24
+ width fit-content
25
+ max-width 100%
26
+
27
+ .rootAlignCenter
28
+ align-items center
29
+ text-align center
30
+
31
+ .rootAlignStart
32
+ align-items flex-start
33
+ text-align left
34
+
35
+ .hint
36
+ font-size 14px
37
+ line-height 1.5
38
+ font-weight 400
39
+ color var(--muted-foreground)
40
+ margin 0
41
+
42
+ a
43
+ color var(--sb-cyan-400)
44
+ text-decoration underline
45
+ font-weight 500
46
+
47
+ &:hover
48
+ opacity 0.9
49
+
50
+ .status
51
+ font-size 14px
52
+ line-height 1.5
53
+ font-weight 400
54
+ margin 0
55
+
56
+ .statusMuted
57
+ color var(--muted-foreground)
58
+
59
+ .statusDestructive
60
+ color var(--destructive)
@@ -0,0 +1,15 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'hint': string;
5
+ 'root': string;
6
+ 'rootAlignCenter': string;
7
+ 'rootAlignStart': string;
8
+ 'rootInline': string;
9
+ 'rootPanel': string;
10
+ 'status': string;
11
+ 'statusDestructive': string;
12
+ 'statusMuted': string;
13
+ }
14
+ export const cssExports: CssExports;
15
+ export default cssExports;
@@ -0,0 +1,66 @@
1
+ import cn from 'classnames';
2
+ import { ReactNode } from 'react';
3
+
4
+ import S from './ChartEmptyState.styl';
5
+
6
+ export type ChartEmptyStatusTone = 'muted' | 'destructive';
7
+
8
+ export interface ChartEmptyStateProps {
9
+ className?: string;
10
+ /** Primary guidance (muted). */
11
+ hint?: ReactNode;
12
+ /** Status / technical detail. */
13
+ status?: ReactNode;
14
+ statusTone?: ChartEmptyStatusTone;
15
+ /** `panel`: chart-sized block with light fill. `inline`: text only (e.g. above chart). */
16
+ variant?: 'panel' | 'inline';
17
+ align?: 'center' | 'start';
18
+ }
19
+
20
+ function isNonEmpty(node: ReactNode): boolean {
21
+ if (node == null || node === false) return false;
22
+ if (typeof node === 'string') return node.trim().length > 0;
23
+ return true;
24
+ }
25
+
26
+ export function ChartEmptyState({
27
+ className,
28
+ hint,
29
+ status,
30
+ statusTone = 'muted',
31
+ variant = 'panel',
32
+ align = 'center',
33
+ }: ChartEmptyStateProps) {
34
+ const hasHint = isNonEmpty(hint);
35
+ const hasStatus = isNonEmpty(status);
36
+
37
+ if (!hasHint && !hasStatus) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <div
43
+ className={cn(
44
+ S.root,
45
+ variant === 'panel' && S.rootPanel,
46
+ variant === 'inline' && S.rootInline,
47
+ align === 'start' && S.rootAlignStart,
48
+ align === 'center' && S.rootAlignCenter,
49
+ className,
50
+ )}
51
+ >
52
+ {hasHint && <div className={S.hint}>{hint}</div>}
53
+ {hasStatus && (
54
+ <div
55
+ className={cn(
56
+ S.status,
57
+ statusTone === 'destructive' && S.statusDestructive,
58
+ statusTone === 'muted' && S.statusMuted,
59
+ )}
60
+ >
61
+ {status}
62
+ </div>
63
+ )}
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,89 @@
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
+
6
+ export type ChartMargin = {
7
+ top: number;
8
+ right: number;
9
+ bottom: number;
10
+ left: number;
11
+ };
12
+
13
+ export const DEFAULT_CHART_MARGIN: ChartMargin = {
14
+ top: 5,
15
+ right: 5,
16
+ bottom: 5,
17
+ left: 5,
18
+ };
19
+
20
+ export function resolveChartMargin(
21
+ margin: Partial<ChartMargin> | undefined,
22
+ ): ChartMargin {
23
+ return {
24
+ top: margin?.top ?? DEFAULT_CHART_MARGIN.top,
25
+ right: margin?.right ?? DEFAULT_CHART_MARGIN.right,
26
+ bottom: margin?.bottom ?? DEFAULT_CHART_MARGIN.bottom,
27
+ left: margin?.left ?? DEFAULT_CHART_MARGIN.left,
28
+ };
29
+ }
30
+
31
+ /** Plot box inside `.recharts-wrapper` (Recharts cartesian convention). */
32
+ export function getPlotViewBox(wrapper: HTMLElement, m: ChartMargin) {
33
+ const w = wrapper.clientWidth;
34
+ const h = wrapper.clientHeight;
35
+ return {
36
+ x: m.left,
37
+ y: m.top,
38
+ width: Math.max(0, w - m.left - m.right),
39
+ height: Math.max(0, h - m.top - m.bottom),
40
+ };
41
+ }
42
+
43
+ export type PlotRect = {
44
+ left: number;
45
+ top: number;
46
+ width: number;
47
+ height: number;
48
+ };
49
+
50
+ const GRID_BOUNDS_MIN_PX = 2;
51
+
52
+ /**
53
+ * Plot area in `host` local px: prefer painted `.recharts-cartesian-grid`, else
54
+ * last `.recharts-wrapper` + margins. One `hostRect` read; grid/wrapper rects as needed.
55
+ */
56
+ export function measureHostRelativePlotRect(
57
+ host: HTMLElement,
58
+ margin: ChartMargin,
59
+ ): PlotRect | null {
60
+ const hostRect = host.getBoundingClientRect();
61
+
62
+ const grid = host.querySelector<SVGGElement>('.recharts-cartesian-grid');
63
+ if (grid) {
64
+ const gr = grid.getBoundingClientRect();
65
+ if (gr.width > GRID_BOUNDS_MIN_PX && gr.height > GRID_BOUNDS_MIN_PX) {
66
+ return {
67
+ left: gr.left - hostRect.left,
68
+ top: gr.top - hostRect.top,
69
+ width: gr.width,
70
+ height: gr.height,
71
+ };
72
+ }
73
+ }
74
+
75
+ const wrappers = host.querySelectorAll('.recharts-wrapper');
76
+ const wrapper = wrappers[wrappers.length - 1];
77
+ if (!(wrapper instanceof HTMLElement)) return null;
78
+
79
+ const vb = getPlotViewBox(wrapper, margin);
80
+ if (vb.width <= 0 || vb.height <= 0) return null;
81
+
82
+ const wrapRect = wrapper.getBoundingClientRect();
83
+ return {
84
+ left: wrapRect.left - hostRect.left + vb.x,
85
+ top: wrapRect.top - hostRect.top + vb.y,
86
+ width: vb.width,
87
+ height: vb.height,
88
+ };
89
+ }
@@ -65,7 +65,37 @@ const timeRangeToMonths = {
65
65
  All: 12,
66
66
  } as const;
67
67
 
68
- export type TimeRange = keyof typeof timeRangeToMonths;
68
+ export type TimeRangePreset = keyof typeof timeRangeToMonths;
69
+
70
+ /** @deprecated Use `TimeRangePreset` or `string` for brush-encoded ranges. */
71
+ export type TimeRange = TimeRangePreset;
72
+
73
+ export const DRAG_TIME_RANGE_PREFIX = '__drag:' as const;
74
+
75
+ export function encodeDragTimeRange(a: Date, b: Date): string {
76
+ const t0 = Math.min(a.getTime(), b.getTime());
77
+ const t1 = Math.max(a.getTime(), b.getTime());
78
+ return `${DRAG_TIME_RANGE_PREFIX}${t0},${t1}`;
79
+ }
80
+
81
+ export function parseDragTimeRange(
82
+ s: string,
83
+ ): { start: Date; end: Date } | null {
84
+ if (!s.startsWith(DRAG_TIME_RANGE_PREFIX)) return null;
85
+ const body = s.slice(DRAG_TIME_RANGE_PREFIX.length);
86
+ const comma = body.indexOf(',');
87
+ if (comma === -1) return null;
88
+ const a = Number(body.slice(0, comma));
89
+ const b = Number(body.slice(comma + 1));
90
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
91
+ const t0 = Math.min(a, b);
92
+ const t1 = Math.max(a, b);
93
+ return { start: new Date(t0), end: new Date(t1) };
94
+ }
95
+
96
+ export function isTimeRangePreset(s: string): s is TimeRangePreset {
97
+ return Object.prototype.hasOwnProperty.call(timeRangeToMonths, s);
98
+ }
69
99
 
70
100
  function isPlottableNumber(value: unknown): value is number {
71
101
  return typeof value === 'number' && Number.isFinite(value);
@@ -154,11 +184,23 @@ export type FilterDataForTimeRangeOptions = {
154
184
 
155
185
  export const filterDataForTimeRange = (
156
186
  data: ChartDataPoint[],
157
- currentTimeRange: TimeRange,
187
+ currentTimeRange: string,
158
188
  options?: FilterDataForTimeRangeOptions,
159
189
  ) => {
190
+ const dragRange = parseDragTimeRange(currentTimeRange);
191
+ if (dragRange) {
192
+ const { start, end } = dragRange;
193
+ return data.filter(item => {
194
+ if (!item.date) return false;
195
+ const d = new Date(item.date);
196
+ return d >= start && d <= end;
197
+ });
198
+ }
199
+
160
200
  if (currentTimeRange === 'All') return data;
161
201
 
202
+ if (!isTimeRangePreset(currentTimeRange)) return data;
203
+
162
204
  const latestDate = computeLatestPlottableDate(data, options);
163
205
 
164
206
  // Pre-compute start date based on latest date in data
@@ -18,6 +18,7 @@ import {
18
18
  } from './ChartAreaInteractive.helpers';
19
19
  import S from './ChartAreaInteractive.styl';
20
20
  import { ChartAreaInteractiveProps } from './ChartAreaInteractive.types';
21
+ import { TimeRangeBrushHost } from './TimeRangeBrushLayer';
21
22
  import { IntervalsOverlay, PinOverlay, ThresholdsOverlay } from './overlays';
22
23
 
23
24
  export const chartConfig = {
@@ -68,6 +69,7 @@ export function ChartAreaInteractive({
68
69
  overlayForecastData = {},
69
70
  hiddenSeries,
70
71
  disableForecastHistoricalBridge,
72
+ overlayElements: overlayElementsProp,
71
73
  ...restProps
72
74
  }: ChartAreaInteractiveProps) {
73
75
  const seriesHidden = hiddenSeries ?? new Set<string>();
@@ -161,10 +163,14 @@ export function ChartAreaInteractive({
161
163
  loadingMessage,
162
164
  excludeLegendIds,
163
165
  forecastLineStyle,
166
+ overlayElements: overlayElementsProp,
164
167
  // loadingAnalyses,
165
168
  ...restProps,
166
169
  };
167
170
 
171
+ const brushEnabled =
172
+ !disableTimeRangeSelector && !loading && bridgedChartData.length > 1;
173
+
168
174
  const renderChart = () => {
169
175
  const overlayClassName = cn(chartContainerClassName);
170
176
 
@@ -237,7 +243,14 @@ export function ChartAreaInteractive({
237
243
  </div>
238
244
  )}
239
245
 
240
- {renderChart()}
246
+ <TimeRangeBrushHost
247
+ chartData={bridgedChartData}
248
+ onTimeRangeChange={onTimeRangeChange}
249
+ enabled={brushEnabled}
250
+ layoutKey={chartRenderId ?? null}
251
+ >
252
+ {renderChart()}
253
+ </TimeRangeBrushHost>
241
254
  </InteractionOverlay>
242
255
  );
243
256
  }
@@ -2,8 +2,6 @@ import { BaseChartWrapperProps } from '#uilib/components/ui/Chart/components/Bas
2
2
  import type { ForecastData } from '#uilib/types/forecast-data';
3
3
  import { LegendPayload } from 'recharts';
4
4
 
5
- import { TimeRange } from './ChartAreaInteractive.helpers';
6
-
7
5
  export type OverlayMode = 'pin' | 'intervals' | 'thresholds';
8
6
 
9
7
  export interface ChartDataPoint {
@@ -28,7 +26,8 @@ export interface Analysis {
28
26
 
29
27
  export interface ChartAreaInteractiveProps extends BaseChartWrapperProps {
30
28
  chartContainerClassName?: string;
31
- timeRange: TimeRange;
29
+ /** Preset (`6m`, `1y`, …, `All`) or `__drag:startMs,endMs` from chart brush. */
30
+ timeRange: string;
32
31
  onTimeRangeChange: (range: string) => void;
33
32
  pinMonth: string | undefined;
34
33
  onPinMonthChange: (month: string | undefined) => void;
@@ -0,0 +1,21 @@
1
+ .selection
2
+ position absolute
3
+ top 0
4
+ bottom 0
5
+ background-color var(--brand-color-500)
6
+ opacity 0.1
7
+ border 1px solid var(--ring)
8
+ pointer-events none
9
+ box-sizing border-box
10
+
11
+ .host
12
+ position relative
13
+ width 100%
14
+ overflow visible
15
+ touch-action pan-y
16
+
17
+ .plotBox
18
+ position absolute
19
+ z-index 8
20
+ pointer-events none
21
+ box-sizing border-box