@scality/core-ui 0.175.0 → 0.177.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 (55) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts +4 -5
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +23 -27
  4. package/dist/components/barchartv2/BarchartTooltip.d.ts +4 -3
  5. package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -1
  6. package/dist/components/barchartv2/BarchartTooltip.js +10 -12
  7. package/dist/components/barchartv2/utils.d.ts +6 -1
  8. package/dist/components/barchartv2/utils.d.ts.map +1 -1
  9. package/dist/components/barchartv2/utils.js +34 -8
  10. package/dist/components/chartlegend/ChartLegendWrapper.js +1 -1
  11. package/dist/components/charttooltip/ChartTooltip.d.ts +29 -0
  12. package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -1
  13. package/dist/components/charttooltip/ChartTooltip.js +105 -1
  14. package/dist/components/date/FormattedDateTime.d.ts +23 -8
  15. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  16. package/dist/components/date/FormattedDateTime.js +51 -7
  17. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -1
  18. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +27 -1
  19. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +1 -1
  20. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -1
  21. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +19 -59
  22. package/dist/components/globalhealthbar/components/HealthBarXAxis.js +1 -1
  23. package/dist/components/globalhealthbar/useHealthBarData.d.ts +1 -0
  24. package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -1
  25. package/dist/components/globalhealthbar/useHealthBarData.js +1 -0
  26. package/dist/components/globalhealthbar/useHealthBarData.spec.js +2 -0
  27. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +2 -1
  28. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  29. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +45 -47
  30. package/dist/components/linetimeseriechart/utils.d.ts +1 -1
  31. package/dist/components/linetimeseriechart/utils.d.ts.map +1 -1
  32. package/dist/components/linetimeseriechart/utils.js +13 -13
  33. package/dist/style/theme.js +1 -1
  34. package/package.json +1 -1
  35. package/src/lib/components/barchartv2/Barchart.component.test.tsx +23 -25
  36. package/src/lib/components/barchartv2/Barchart.component.tsx +41 -39
  37. package/src/lib/components/barchartv2/BarchartTooltip.test.tsx +33 -3
  38. package/src/lib/components/barchartv2/BarchartTooltip.tsx +33 -24
  39. package/src/lib/components/barchartv2/utils.test.ts +72 -17
  40. package/src/lib/components/barchartv2/utils.ts +40 -12
  41. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +1 -1
  42. package/src/lib/components/charttooltip/ChartTooltip.tsx +174 -1
  43. package/src/lib/components/date/FormattedDateTime.tsx +73 -8
  44. package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +56 -11
  45. package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +75 -117
  46. package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +1 -1
  47. package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +2 -0
  48. package/src/lib/components/globalhealthbar/useHealthBarData.ts +2 -0
  49. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +101 -90
  50. package/src/lib/components/linetimeseriechart/utils.test.ts +30 -68
  51. package/src/lib/components/linetimeseriechart/utils.ts +13 -17
  52. package/src/lib/style/theme.ts +1 -1
  53. package/stories/BarChart/barchart.stories.tsx +23 -8
  54. package/stories/formattedate.stories.tsx +2 -0
  55. package/stories/linetimeseriechart.stories.tsx +1 -0
@@ -133,7 +133,7 @@ export const ChartLegendWrapper = ({
133
133
  );
134
134
 
135
135
  const listResources = useCallback(() => {
136
- return Object.keys(internalColorSet);
136
+ return Object.keys(internalColorSet).sort();
137
137
  }, [internalColorSet]);
138
138
 
139
139
  const chartLegendState = useMemo(
@@ -1,7 +1,18 @@
1
1
  import React from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useEffect, useState, useRef } from 'react';
4
+ import {
5
+ useFloating,
6
+ autoUpdate,
7
+ offset,
8
+ flip,
9
+ shift,
10
+ Middleware,
11
+ } from '@floating-ui/react';
2
12
  import styled from 'styled-components';
3
13
  import { spacing } from '../../spacing';
4
14
  import { fontSize, fontWeight } from '../../style/theme';
15
+ import { FormattedDateTime } from '../date/FormattedDateTime';
5
16
 
6
17
  export const ChartTooltipContainer = styled.div`
7
18
  border: 1px solid ${({ theme }) => theme.border};
@@ -11,7 +22,8 @@ export const ChartTooltipContainer = styled.div`
11
22
  font-size: ${fontSize.small};
12
23
  padding: ${spacing.r8};
13
24
  min-width: 10rem;
14
- max-width: 250px;
25
+ max-width: 40rem;
26
+
15
27
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
16
28
  `;
17
29
 
@@ -81,3 +93,164 @@ export const ChartTooltipItemsContainer = styled.div`
81
93
  gap: ${spacing.r8};
82
94
  width: 100%;
83
95
  `;
96
+
97
+ export const ChartTooltipSeparator = styled.div`
98
+ height: 1px;
99
+ background-color: ${({ theme }) => theme.border};
100
+ margin: ${spacing.r4} 0;
101
+ width: 100%;
102
+ `;
103
+
104
+ export type TooltipDateFormat =
105
+ | 'day-month-abbreviated-year-hour-minute'
106
+ | 'day-month-abbreviated-hour-minute-second'
107
+ | 'day-month-abbreviated-hour-minute';
108
+
109
+ const getTooltipDateFormat: (duration: number) => TooltipDateFormat = (
110
+ duration: number,
111
+ ) => {
112
+ if (duration <= 60 * 60 * 1000) {
113
+ return 'day-month-abbreviated-hour-minute-second';
114
+ } else if (duration <= 7 * 24 * 60 * 60 * 1000) {
115
+ return 'day-month-abbreviated-hour-minute';
116
+ } else {
117
+ return 'day-month-abbreviated-year-hour-minute';
118
+ }
119
+ };
120
+
121
+ export const TooltipHeader = ({
122
+ duration,
123
+ value,
124
+ }: {
125
+ duration: number;
126
+ value: string | number;
127
+ }) => {
128
+ const timeFormat = getTooltipDateFormat(duration);
129
+ return (
130
+ <ChartTooltipHeader>
131
+ <FormattedDateTime format={timeFormat} value={new Date(value)} />
132
+ </ChartTooltipHeader>
133
+ );
134
+ };
135
+
136
+ export interface ChartTooltipPortalProps {
137
+ children: React.ReactNode;
138
+ coordinate?: { x: number; y: number };
139
+ chartContainerRef: React.RefObject<HTMLDivElement>;
140
+ isVisible?: boolean;
141
+ middleware?: Middleware[];
142
+ offset?: number | (({ placement }: { placement: string }) => number);
143
+ customPosition?: (
144
+ chartRect: DOMRect,
145
+ coordinate?: { x: number; y: number },
146
+ ) => { x: number; y: number };
147
+ containerComponent?: React.ComponentType<any>;
148
+ }
149
+
150
+ export const ChartTooltipPortal: React.FC<ChartTooltipPortalProps> = ({
151
+ children,
152
+ coordinate,
153
+ chartContainerRef,
154
+ isVisible = true,
155
+ middleware,
156
+ offset: customOffset,
157
+ customPosition,
158
+ containerComponent: ContainerComponent = ChartTooltipContainer,
159
+ }) => {
160
+ const [virtualElement, setVirtualElement] = useState<any>(null);
161
+ const previousPositionRef = useRef<{ x: number; y: number } | null>(null);
162
+ const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
163
+ null,
164
+ );
165
+
166
+ // Default middleware configuration
167
+ const defaultMiddleware = [
168
+ offset(customOffset || 20),
169
+ flip(),
170
+ shift({ padding: 10 }),
171
+ ];
172
+
173
+ const { refs, floatingStyles } = useFloating({
174
+ elements: {
175
+ reference: virtualElement,
176
+ },
177
+ placement: 'top',
178
+ middleware: middleware || defaultMiddleware,
179
+ whileElementsMounted: autoUpdate,
180
+ });
181
+
182
+ // Create portal container once
183
+ useEffect(() => {
184
+ const container = document.createElement('div');
185
+ document.body.appendChild(container);
186
+ setPortalContainer(container);
187
+
188
+ return () => {
189
+ document.body.removeChild(container);
190
+ };
191
+ }, []);
192
+
193
+ // Create virtual element from coordinate or custom position
194
+ useEffect(() => {
195
+ if (chartContainerRef.current) {
196
+ const chartRect = chartContainerRef.current.getBoundingClientRect();
197
+
198
+ let tooltipX: number;
199
+ let tooltipY: number;
200
+
201
+ if (customPosition) {
202
+ // Use custom positioning function
203
+ const position = customPosition(chartRect, coordinate);
204
+ tooltipX = position.x;
205
+ tooltipY = position.y;
206
+ } else if (coordinate) {
207
+ // Use default coordinate-based positioning
208
+ tooltipX = chartRect.left + coordinate.x;
209
+ tooltipY = chartRect.top + coordinate.y;
210
+ } else {
211
+ return; // No positioning method available
212
+ }
213
+
214
+ // Check if position has changed significantly
215
+ const hasPositionChanged =
216
+ !previousPositionRef.current ||
217
+ Math.abs(previousPositionRef.current.x - tooltipX) > 5 ||
218
+ Math.abs(previousPositionRef.current.y - tooltipY) > 5;
219
+
220
+ if (hasPositionChanged) {
221
+ previousPositionRef.current = { x: tooltipX, y: tooltipY };
222
+ }
223
+
224
+ setVirtualElement({
225
+ getBoundingClientRect() {
226
+ return {
227
+ width: 0,
228
+ height: 0,
229
+ x: tooltipX,
230
+ y: tooltipY,
231
+ left: tooltipX,
232
+ top: tooltipY,
233
+ right: tooltipX,
234
+ bottom: tooltipY,
235
+ };
236
+ },
237
+ });
238
+ }
239
+ }, [coordinate, chartContainerRef, customPosition]);
240
+
241
+ if (!isVisible || !virtualElement || !portalContainer) return null;
242
+
243
+ const tooltipContent = (
244
+ <ContainerComponent
245
+ ref={refs.setFloating}
246
+ style={{
247
+ ...floatingStyles,
248
+ opacity: isVisible ? 1 : 0,
249
+ }}
250
+ >
251
+ {children}
252
+ </ContainerComponent>
253
+ );
254
+
255
+ return createPortal(tooltipContent, portalContainer);
256
+ };
@@ -3,7 +3,7 @@ import { Tooltip } from '../tooltip/Tooltip.component';
3
3
 
4
4
  /**
5
5
  * @description Long date formatter, with weekday, year, month and day. Used for describing long term date.
6
- * @example Wednesday 6 October 2025
6
+ * @example Wednesday 06 October 2025
7
7
  */
8
8
  export const LONG_DATE_FORMATER = Intl.DateTimeFormat('en-GB', {
9
9
  weekday: 'long',
@@ -35,7 +35,7 @@ export const DATE_FORMATER = Intl.DateTimeFormat('fr-CA', {
35
35
 
36
36
  /**
37
37
  * @description Day month formatter, with weekday, day and month. Used for describing long term date.
38
- * @example Wed 6 Oct
38
+ * @example Wed 06 Oct
39
39
  */
40
40
  export const DAY_MONTH_FORMATER = Intl.DateTimeFormat('en-GB', {
41
41
  weekday: 'short',
@@ -64,9 +64,30 @@ export const TIME_FORMATER = Intl.DateTimeFormat('en-GB', {
64
64
  minute: '2-digit',
65
65
  });
66
66
 
67
+ /**
68
+ * @description Day month abbreviated formatter. Used for describing long term date.
69
+ * @example 06 Oct
70
+ */
71
+ export const DAY_MONTH_ABBREVIATED = Intl.DateTimeFormat('en-GB', {
72
+ day: '2-digit',
73
+ month: 'short',
74
+ hour12: false,
75
+ });
76
+
77
+ /**
78
+ * @description Day month abbreviated formatter. Used for describing long term date.
79
+ * @example 06 Oct 25
80
+ */
81
+ export const DAY_MONTH_ABBREVIATED_YEAR = Intl.DateTimeFormat('en-GB', {
82
+ day: '2-digit',
83
+ month: 'short',
84
+ year: '2-digit',
85
+ hour12: false,
86
+ });
87
+
67
88
  /**
68
89
  * @description Day month abbreviated hour minute second formatter. Used for describing long term date.
69
- * @example 6 Oct 18:33:00
90
+ * @example 06 Oct 18:33:00
70
91
  */
71
92
  export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
72
93
  'en-GB',
@@ -82,7 +103,7 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
82
103
 
83
104
  /**
84
105
  * @description Day month abbreviated hour minute formatter. Used for describing long term date.
85
- * @example 6 Oct 18:33
106
+ * @example 06 Oct 18:33
86
107
  */
87
108
  export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
88
109
  day: '2-digit',
@@ -92,6 +113,22 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
92
113
  hour12: false,
93
114
  });
94
115
 
116
+ /**
117
+ * @description Day month abbreviated year hour minute formatter. Used for describing long term date.
118
+ * @example 06 Oct 2025 18:33
119
+ */
120
+ export const DAY_MONTH_ABBREVIATED_YEAR_HOUR_MINUTE = Intl.DateTimeFormat(
121
+ 'en-GB',
122
+ {
123
+ day: '2-digit',
124
+ month: 'short',
125
+ year: 'numeric',
126
+ hour: '2-digit',
127
+ minute: '2-digit',
128
+ hour12: false,
129
+ },
130
+ );
131
+
95
132
  /**
96
133
  * @description Year month day formatter, without time. Used for describing long term date.
97
134
  * @example 2025-01-01
@@ -121,11 +158,14 @@ type FormattedDateTimeProps = {
121
158
  | 'relative'
122
159
  | 'day-month-abbreviated-hour-minute'
123
160
  | 'day-month-abbreviated-hour-minute-second'
161
+ | 'day-month-abbreviated-year-hour-minute'
124
162
  | 'long-date'
125
163
  | 'long-date-without-weekday'
126
164
  | 'chart-date'
127
165
  | 'year-month-day'
128
- | 'month-day';
166
+ | 'month-day'
167
+ | 'day-month-abbreviated'
168
+ | 'chart-long-term-date';
129
169
 
130
170
  value: Date;
131
171
  };
@@ -150,10 +190,10 @@ const isItFutureOrIsItPast = (
150
190
  * time: '00:00'
151
191
  * 'time-second': '00:00:00'
152
192
  * relative: '1 month ago'
153
- * 'day-month-abbreviated-hour-minute': '6 Oct 18:33'
154
- * 'day-month-abbreviated-hour-minute-second': '6 Oct 18:33:00'
193
+ * 'day-month-abbreviated-hour-minute': '06 Oct 18:33'
194
+ * 'day-month-abbreviated-hour-minute-second': '06 Oct 18:33:00'
155
195
  * 'long-date': 'Wednesday 6 October 2025'
156
- * 'chart-date': '6 Oct'
196
+ * 'chart-date': '06 Oct'
157
197
  * 'year-month-day': '2025-10-06'
158
198
  */
159
199
  export const FormattedDateTime = ({
@@ -290,6 +330,31 @@ export const FormattedDateTime = ({
290
330
  return <>{YEAR_MONTH_DAY_FORMATTER.format(value)}</>;
291
331
  case 'month-day':
292
332
  return <>{MONTH_DAY_FORMATTER.format(value)}</>;
333
+ case 'day-month-abbreviated-year-hour-minute':
334
+ return (
335
+ <>
336
+ {DAY_MONTH_ABBREVIATED_YEAR_HOUR_MINUTE.format(value)
337
+ .replace(',', '')
338
+ .replace(/Sept/g, 'Sep')}
339
+ </>
340
+ );
341
+ case 'day-month-abbreviated':
342
+ return (
343
+ <>
344
+ {DAY_MONTH_ABBREVIATED.format(value)
345
+ .replace(',', '')
346
+ .replace(/Sept/g, 'Sep')}
347
+ </>
348
+ );
349
+ case 'chart-long-term-date':
350
+ return (
351
+ <>
352
+ {DAY_MONTH_ABBREVIATED_YEAR.format(value)
353
+ .replace(/[ ,]/g, '')
354
+ // replace Sept with Sep to keep 3 letter month
355
+ .replace(/Sept/g, 'Sep')}
356
+ </>
357
+ );
293
358
  default:
294
359
  return <></>;
295
360
  }
@@ -26,12 +26,17 @@ export interface GlobalHealthProps {
26
26
 
27
27
  const ChartInteractiveContainer = styled.div`
28
28
  position: relative;
29
+ outline: none;
30
+ .recharts-surface {
31
+ outline: none;
32
+ }
29
33
  `;
30
34
 
31
35
  export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
32
36
  const [tooltipData, setTooltipData] = useState<Alert | null>(null);
33
37
  const [focusedAlertIndex, setFocusedAlertIndex] = useState<number>(-1);
34
38
  const [keyboardActive, setKeyboardActive] = useState<boolean>(false);
39
+ const [activeBarKey, setActiveBarKey] = useState<string | null>(null);
35
40
  const chartContainerRef = useRef<HTMLDivElement>(null);
36
41
  const theme = useTheme();
37
42
  const startTimestamp = new Date(start).getTime();
@@ -47,6 +52,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
47
52
  const handlePointerEnter = useCallback(
48
53
  (key: string) => {
49
54
  setTooltipData(alertsMap[key]);
55
+ setActiveBarKey(key);
50
56
  },
51
57
  [alertsMap],
52
58
  );
@@ -54,6 +60,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
54
60
  const handlePointerLeave = useCallback(() => {
55
61
  if (!keyboardActive) {
56
62
  setTooltipData(null);
63
+ setActiveBarKey(null);
57
64
  }
58
65
  }, [keyboardActive]);
59
66
 
@@ -83,6 +90,11 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
83
90
  setFocusedAlertIndex(update.newIndex);
84
91
  setTooltipData(update.selectedAlert);
85
92
  setKeyboardActive(update.shouldActivateKeyboard);
93
+
94
+ // Set active bar key for keyboard navigation
95
+ if (update.selectedAlert) {
96
+ setActiveBarKey(update.selectedAlert.key);
97
+ }
86
98
  },
87
99
  [allAlertKeys, focusedAlertIndex],
88
100
  );
@@ -93,6 +105,9 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
93
105
  setFocusedAlertIndex(0);
94
106
  setTooltipData(allAlertKeys[0]);
95
107
  setKeyboardActive(true);
108
+
109
+ // Set active bar key for initial focus
110
+ setActiveBarKey(allAlertKeys[0].key);
96
111
  }
97
112
  }, [allAlertKeys, focusedAlertIndex]);
98
113
 
@@ -100,6 +115,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
100
115
  setKeyboardActive(false);
101
116
  setFocusedAlertIndex(-1);
102
117
  setTooltipData(null);
118
+ setActiveBarKey(null);
103
119
  }, []);
104
120
 
105
121
  // Handle mouse enter to disable keyboard mode
@@ -182,17 +198,46 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
182
198
  isAnimationActive={false}
183
199
  />
184
200
 
185
- {/* Alert bars */}
186
- {allAlertBars.map(({ key, fill }) => (
187
- <Bar
188
- dataKey={key}
189
- yAxisId={key}
190
- fill={fill}
191
- onPointerEnter={() => handlePointerEnter(key)}
192
- onPointerLeave={() => handlePointerLeave()}
193
- isAnimationActive={false}
194
- />
195
- ))}
201
+ {/* Alert bars - render non-active bars first */}
202
+ {allAlertBars.map(({ key, fill }) => {
203
+ const isActive = key === activeBarKey;
204
+ // Skip active bar here - it will be rendered last
205
+ if (isActive) return null;
206
+
207
+ return (
208
+ <Bar
209
+ key={key}
210
+ dataKey={key}
211
+ yAxisId={key}
212
+ fill={fill}
213
+ onPointerEnter={() => handlePointerEnter(key)}
214
+ onPointerLeave={() => handlePointerLeave()}
215
+ isAnimationActive={false}
216
+ />
217
+ );
218
+ })}
219
+
220
+ {/* Render active bar last to ensure it's on top */}
221
+ {activeBarKey &&
222
+ (() => {
223
+ const activeBar = allAlertBars.find(
224
+ (bar) => bar.key === activeBarKey,
225
+ );
226
+ if (!activeBar) return null;
227
+
228
+ return (
229
+ <Bar
230
+ key={`${activeBar.key}-active`}
231
+ dataKey={activeBar.key}
232
+ yAxisId={activeBar.key}
233
+ fill={activeBar.fill}
234
+ stroke={theme.selectedActive}
235
+ onPointerEnter={() => handlePointerEnter(activeBar.key)}
236
+ onPointerLeave={() => handlePointerLeave()}
237
+ isAnimationActive={false}
238
+ />
239
+ );
240
+ })()}
196
241
  </BarChart>
197
242
  </ResponsiveContainer>
198
243
  </ChartInteractiveContainer>
@@ -1,19 +1,11 @@
1
1
  import React from 'react';
2
- import { createPortal } from 'react-dom';
3
- import { useEffect, useState } from 'react';
4
2
  import styled, { css, useTheme } from 'styled-components';
5
- import {
6
- useFloating,
7
- autoUpdate,
8
- offset,
9
- flip,
10
- shift,
11
- } from '@floating-ui/react';
12
3
  import { FormattedDateTime, Stack, Text, Wrap, spacing } from '../../../index';
13
4
  import { Alert } from '../GlobalHealthBarRecharts.component';
14
5
  import { TooltipContentProps } from 'recharts';
15
6
  import { zIndex } from '../../../style/theme';
16
7
  import { CHART_CONFIG, getTooltipPosition } from '../healthBarUtils';
8
+ import { ChartTooltipPortal } from '../../charttooltip/ChartTooltip';
17
9
 
18
10
  interface GlobalHealthBarTooltipProps {
19
11
  tooltipData: Alert | null;
@@ -53,121 +45,87 @@ export const GlobalHealthBarTooltip = (props: GlobalHealthBarTooltipProps) => {
53
45
  endTimestamp = 0,
54
46
  } = props;
55
47
  const { coordinate } = tooltipProps;
56
- const [virtualElement, setVirtualElement] = useState<any>(null);
57
48
 
58
- const { refs, floatingStyles } = useFloating({
59
- elements: {
60
- reference: virtualElement,
61
- },
62
- middleware: [
63
- offset(({ placement }) => {
64
- // Use larger offset when tooltip is on top
65
- // to avoid tooltip over bar
66
- return placement.includes('top') ? 20 : 30;
67
- }),
68
- flip(),
69
- shift({ padding: 10 }),
70
- ],
71
- whileElementsMounted: autoUpdate,
72
- });
73
-
74
- // Create virtual element from coordinate
75
- useEffect(() => {
76
- if (chartContainerRef.current) {
77
- const chartRect = chartContainerRef.current.getBoundingClientRect();
49
+ if (!tooltipData) return null;
78
50
 
79
- let tooltipX: number;
80
- let tooltipY: number;
51
+ const { description, startsAt, endsAt, severity } = tooltipData;
81
52
 
82
- if (isKeyboardActive && tooltipData && startTimestamp && endTimestamp) {
83
- // Calculate the chart's usable width (excluding margins)
84
- const chartUsableWidth =
85
- chartRect.width -
86
- CHART_CONFIG.MARGINS.left -
87
- CHART_CONFIG.MARGINS.right;
53
+ const tooltipContent = (
54
+ <Stack direction="vertical" gap="r8">
55
+ <Wrap>
56
+ <Text variant="Smaller">Severity</Text>
57
+ <Text color="textPrimary" variant="Smaller">
58
+ {severity}
59
+ </Text>
60
+ </Wrap>
61
+ <Wrap>
62
+ <Text variant="Smaller">Start</Text>
63
+ <Text color="textPrimary" variant="Smaller">
64
+ <FormattedDateTime format="date-time" value={new Date(startsAt)} />
65
+ </Text>
66
+ </Wrap>
67
+ <Wrap>
68
+ <Text variant="Smaller">End</Text>
69
+ <Text color="textPrimary" variant="Smaller">
70
+ <FormattedDateTime format="date-time" value={new Date(endsAt)} />
71
+ </Text>
72
+ </Wrap>
73
+ <Wrap>
74
+ <Text variant="Smaller" style={{ paddingRight: spacing.r32 }}>
75
+ Description
76
+ </Text>
77
+ <Text
78
+ color="textPrimary"
79
+ variant="Smaller"
80
+ style={{ whiteSpace: 'wrap', textAlign: 'justify' }}
81
+ >
82
+ {description}
83
+ </Text>
84
+ </Wrap>
85
+ </Stack>
86
+ );
88
87
 
89
- // Use the same positioning logic as alert bars
90
- const alertCenterX = getTooltipPosition(
91
- tooltipData,
92
- startTimestamp,
93
- endTimestamp,
94
- chartUsableWidth,
95
- );
88
+ return (
89
+ <ChartTooltipPortal
90
+ coordinate={coordinate}
91
+ chartContainerRef={chartContainerRef}
92
+ isVisible={!!tooltipData}
93
+ customPosition={(chartRect, coordinate) => {
94
+ if (isKeyboardActive && tooltipData && startTimestamp && endTimestamp) {
95
+ // Calculate the chart's usable width (excluding margins)
96
+ const chartUsableWidth =
97
+ chartRect.width -
98
+ CHART_CONFIG.MARGINS.left -
99
+ CHART_CONFIG.MARGINS.right;
96
100
 
97
- // Position tooltip at the center of the alert's time span
98
- // alertCenterX already includes the margin offset, so just add chartRect.left
99
- tooltipX = chartRect.left + alertCenterX;
100
- tooltipY = chartRect.top + CHART_CONFIG.BAR_SIZE;
101
- } else {
102
- // For mouse navigation, use the provided coordinate
103
- tooltipX = chartRect.left + coordinate?.x;
104
- tooltipY = chartRect.top + coordinate?.y;
105
- }
101
+ // Use the same positioning logic as alert bars
102
+ const alertCenterX = getTooltipPosition(
103
+ tooltipData,
104
+ startTimestamp,
105
+ endTimestamp,
106
+ chartUsableWidth,
107
+ );
106
108
 
107
- setVirtualElement({
108
- getBoundingClientRect() {
109
109
  return {
110
- width: 0,
111
- height: 0,
112
- x: tooltipX,
113
- y: tooltipY,
114
- left: tooltipX,
115
- top: tooltipY,
116
- right: tooltipX,
117
- bottom: tooltipY,
110
+ x: chartRect.left + alertCenterX,
111
+ y: chartRect.top + CHART_CONFIG.BAR_SIZE,
118
112
  };
119
- },
120
- });
121
- }
122
- }, [
123
- coordinate,
124
- chartContainerRef,
125
- isKeyboardActive,
126
- tooltipData,
127
- startTimestamp,
128
- endTimestamp,
129
- ]);
130
-
131
- if (!tooltipData) return null;
132
-
133
- const { description, startsAt, endsAt, severity } = tooltipData;
134
-
135
- const tooltipContent = (
136
- <TooltipContainer ref={refs.setFloating} style={floatingStyles}>
137
- <Stack direction="vertical" gap="r8">
138
- <Wrap>
139
- <Text variant="Smaller">Severity</Text>
140
- <Text color="textPrimary" variant="Smaller">
141
- {severity}
142
- </Text>
143
- </Wrap>
144
- <Wrap>
145
- <Text variant="Smaller">Start</Text>
146
- <Text color="textPrimary" variant="Smaller">
147
- <FormattedDateTime format="date-time" value={new Date(startsAt)} />
148
- </Text>
149
- </Wrap>
150
- <Wrap>
151
- <Text variant="Smaller">End</Text>
152
- <Text color="textPrimary" variant="Smaller">
153
- <FormattedDateTime format="date-time" value={new Date(endsAt)} />
154
- </Text>
155
- </Wrap>
156
- <Wrap>
157
- <Text variant="Smaller" style={{ paddingRight: spacing.r32 }}>
158
- Description
159
- </Text>
160
- <Text
161
- color="textPrimary"
162
- variant="Smaller"
163
- style={{ whiteSpace: 'wrap', textAlign: 'justify' }}
164
- >
165
- {description}
166
- </Text>
167
- </Wrap>
168
- </Stack>
169
- </TooltipContainer>
113
+ } else {
114
+ // For mouse navigation, use the provided coordinate
115
+ return {
116
+ x: chartRect.left + (coordinate?.x || 0),
117
+ y: chartRect.top + (coordinate?.y || 0),
118
+ };
119
+ }
120
+ }}
121
+ containerComponent={TooltipContainer}
122
+ offset={({ placement }) => {
123
+ // Use larger offset when tooltip is on top
124
+ // to avoid tooltip over bar
125
+ return placement.includes('top') ? 20 : 30;
126
+ }}
127
+ >
128
+ {tooltipContent}
129
+ </ChartTooltipPortal>
170
130
  );
171
-
172
- return createPortal(tooltipContent, document.body);
173
131
  };
@@ -47,7 +47,7 @@ const CustomTick = ({
47
47
  fontSize={fontSize.smaller}
48
48
  fill={theme.textSecondary}
49
49
  >
50
- {is7DaySpan || isDaySpan ? (
50
+ {is7DaySpan ? (
51
51
  <FormattedDateTime
52
52
  format="day-month-abbreviated-hour-minute"
53
53
  value={new Date(payload.value)}