@scality/core-ui 0.176.0 → 0.178.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 (43) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts +2 -2
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +16 -10
  4. package/dist/components/barchartv2/BarchartTooltip.d.ts +3 -2
  5. package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -1
  6. package/dist/components/barchartv2/BarchartTooltip.js +6 -8
  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/charttooltip/ChartTooltip.d.ts +23 -0
  11. package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -1
  12. package/dist/components/charttooltip/ChartTooltip.js +85 -3
  13. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -1
  14. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +27 -1
  15. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +1 -1
  16. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -1
  17. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +19 -59
  18. package/dist/components/globalhealthbar/components/HealthBarXAxis.js +1 -1
  19. package/dist/components/globalhealthbar/useHealthBarData.d.ts +1 -0
  20. package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -1
  21. package/dist/components/globalhealthbar/useHealthBarData.js +1 -0
  22. package/dist/components/globalhealthbar/useHealthBarData.spec.js +2 -0
  23. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  24. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +43 -47
  25. package/dist/components/linetimeseriechart/utils.js +2 -2
  26. package/dist/style/theme.js +1 -1
  27. package/package.json +1 -1
  28. package/src/lib/components/barchartv2/Barchart.component.tsx +25 -16
  29. package/src/lib/components/barchartv2/BarchartTooltip.test.tsx +30 -0
  30. package/src/lib/components/barchartv2/BarchartTooltip.tsx +21 -8
  31. package/src/lib/components/barchartv2/utils.test.ts +72 -17
  32. package/src/lib/components/barchartv2/utils.ts +39 -7
  33. package/src/lib/components/charttooltip/ChartTooltip.tsx +136 -3
  34. package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +56 -11
  35. package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +75 -117
  36. package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +1 -1
  37. package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +2 -0
  38. package/src/lib/components/globalhealthbar/useHealthBarData.ts +2 -0
  39. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +86 -82
  40. package/src/lib/components/linetimeseriechart/utils.test.ts +3 -3
  41. package/src/lib/components/linetimeseriechart/utils.ts +2 -2
  42. package/src/lib/style/theme.ts +1 -1
  43. package/stories/BarChart/barchart.stories.tsx +23 -8
@@ -1,11 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createPortal } from 'react-dom';
3
- import { useEffect, useState } from 'react';
4
2
  import styled, { css, useTheme } from 'styled-components';
5
- import { useFloating, autoUpdate, offset, flip, shift, } from '@floating-ui/react';
6
3
  import { FormattedDateTime, Stack, Text, Wrap, spacing } from '../../../index';
7
4
  import { zIndex } from '../../../style/theme';
8
5
  import { CHART_CONFIG, getTooltipPosition } from '../healthBarUtils';
6
+ import { ChartTooltipPortal } from '../../charttooltip/ChartTooltip';
9
7
  const TooltipContainer = styled.div `
10
8
  ${(props) => {
11
9
  const theme = useTheme();
@@ -25,28 +23,11 @@ const TooltipContainer = styled.div `
25
23
  export const GlobalHealthBarTooltip = (props) => {
26
24
  const { tooltipData, tooltipProps, chartContainerRef, isKeyboardActive = false, startTimestamp = 0, endTimestamp = 0, } = props;
27
25
  const { coordinate } = tooltipProps;
28
- const [virtualElement, setVirtualElement] = useState(null);
29
- const { refs, floatingStyles } = useFloating({
30
- elements: {
31
- reference: virtualElement,
32
- },
33
- middleware: [
34
- offset(({ placement }) => {
35
- // Use larger offset when tooltip is on top
36
- // to avoid tooltip over bar
37
- return placement.includes('top') ? 20 : 30;
38
- }),
39
- flip(),
40
- shift({ padding: 10 }),
41
- ],
42
- whileElementsMounted: autoUpdate,
43
- });
44
- // Create virtual element from coordinate
45
- useEffect(() => {
46
- if (chartContainerRef.current) {
47
- const chartRect = chartContainerRef.current.getBoundingClientRect();
48
- let tooltipX;
49
- let tooltipY;
26
+ if (!tooltipData)
27
+ return null;
28
+ const { description, startsAt, endsAt, severity } = tooltipData;
29
+ const tooltipContent = (_jsxs(Stack, { direction: "vertical", gap: "r8", children: [_jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Severity" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: severity })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Start" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(startsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "End" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(endsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", style: { paddingRight: spacing.r32 }, children: "Description" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", style: { whiteSpace: 'wrap', textAlign: 'justify' }, children: description })] })] }));
30
+ return (_jsx(ChartTooltipPortal, { coordinate: coordinate, chartContainerRef: chartContainerRef, isVisible: !!tooltipData, customPosition: (chartRect, coordinate) => {
50
31
  if (isKeyboardActive && tooltipData && startTimestamp && endTimestamp) {
51
32
  // Calculate the chart's usable width (excluding margins)
52
33
  const chartUsableWidth = chartRect.width -
@@ -54,42 +35,21 @@ export const GlobalHealthBarTooltip = (props) => {
54
35
  CHART_CONFIG.MARGINS.right;
55
36
  // Use the same positioning logic as alert bars
56
37
  const alertCenterX = getTooltipPosition(tooltipData, startTimestamp, endTimestamp, chartUsableWidth);
57
- // Position tooltip at the center of the alert's time span
58
- // alertCenterX already includes the margin offset, so just add chartRect.left
59
- tooltipX = chartRect.left + alertCenterX;
60
- tooltipY = chartRect.top + CHART_CONFIG.BAR_SIZE;
38
+ return {
39
+ x: chartRect.left + alertCenterX,
40
+ y: chartRect.top + CHART_CONFIG.BAR_SIZE,
41
+ };
61
42
  }
62
43
  else {
63
44
  // For mouse navigation, use the provided coordinate
64
- tooltipX = chartRect.left + (coordinate === null || coordinate === void 0 ? void 0 : coordinate.x);
65
- tooltipY = chartRect.top + (coordinate === null || coordinate === void 0 ? void 0 : coordinate.y);
45
+ return {
46
+ x: chartRect.left + ((coordinate === null || coordinate === void 0 ? void 0 : coordinate.x) || 0),
47
+ y: chartRect.top + ((coordinate === null || coordinate === void 0 ? void 0 : coordinate.y) || 0),
48
+ };
66
49
  }
67
- setVirtualElement({
68
- getBoundingClientRect() {
69
- return {
70
- width: 0,
71
- height: 0,
72
- x: tooltipX,
73
- y: tooltipY,
74
- left: tooltipX,
75
- top: tooltipY,
76
- right: tooltipX,
77
- bottom: tooltipY,
78
- };
79
- },
80
- });
81
- }
82
- }, [
83
- coordinate,
84
- chartContainerRef,
85
- isKeyboardActive,
86
- tooltipData,
87
- startTimestamp,
88
- endTimestamp,
89
- ]);
90
- if (!tooltipData)
91
- return null;
92
- const { description, startsAt, endsAt, severity } = tooltipData;
93
- const tooltipContent = (_jsx(TooltipContainer, { ref: refs.setFloating, style: floatingStyles, children: _jsxs(Stack, { direction: "vertical", gap: "r8", children: [_jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Severity" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: severity })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Start" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(startsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "End" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(endsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", style: { paddingRight: spacing.r32 }, children: "Description" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", style: { whiteSpace: 'wrap', textAlign: 'justify' }, children: description })] })] }) }));
94
- return createPortal(tooltipContent, document.body);
50
+ }, containerComponent: TooltipContainer, offset: ({ placement }) => {
51
+ // Use larger offset when tooltip is on top
52
+ // to avoid tooltip over bar
53
+ return placement.includes('top') ? 20 : 30;
54
+ }, children: tooltipContent }));
95
55
  };
@@ -14,7 +14,7 @@ const CustomTick = ({ tickProps, startTimestamp, endTimestamp, }) => {
14
14
  const edgeMargin = getEdgeMargin(index, visibleTicksCount, isDaySpan);
15
15
  return (
16
16
  // use coordinate to center the text
17
- shouldShowLabel && (_jsx("g", { transform: `translate(${payload.coordinate},${y})`, children: _jsx("text", { textAnchor: "middle", dy: 10, dx: edgeMargin, fontSize: fontSize.smaller, fill: theme.textSecondary, children: is7DaySpan || isDaySpan ? (_jsx(FormattedDateTime, { format: "day-month-abbreviated-hour-minute", value: new Date(payload.value) })) : (_jsx(FormattedDateTime, { format: "time", value: new Date(payload.value) })) }) })));
17
+ shouldShowLabel && (_jsx("g", { transform: `translate(${payload.coordinate},${y})`, children: _jsx("text", { textAnchor: "middle", dy: 10, dx: edgeMargin, fontSize: fontSize.smaller, fill: theme.textSecondary, children: is7DaySpan ? (_jsx(FormattedDateTime, { format: "day-month-abbreviated-hour-minute", value: new Date(payload.value) })) : (_jsx(FormattedDateTime, { format: "time", value: new Date(payload.value) })) }) })));
18
18
  };
19
19
  export const HealthBarXAxis = ({ startTimestamp, endTimestamp, }) => {
20
20
  const theme = useTheme();
@@ -3,6 +3,7 @@ export interface Alert {
3
3
  startsAt: string;
4
4
  endsAt: string;
5
5
  severity: 'warning' | 'critical' | 'unavailable';
6
+ key: string;
6
7
  }
7
8
  export declare const useHealthBarData: (alerts: Alert[], startTimestamp: number, endTimestamp: number, id: string) => {
8
9
  chartData: {
@@ -1 +1 @@
1
- {"version":3,"file":"useHealthBarData.d.ts","sourceRoot":"","sources":["../../../src/lib/components/globalhealthbar/useHealthBarData.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;CAClD;AAED,eAAO,MAAM,gBAAgB,WACnB,KAAK,EAAE,kBACC,MAAM,gBACR,MAAM,MAChB,MAAM;;;;;;;;;;CA4DX,CAAC"}
1
+ {"version":3,"file":"useHealthBarData.d.ts","sourceRoot":"","sources":["../../../src/lib/components/globalhealthbar/useHealthBarData.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IACjD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,gBAAgB,WACnB,KAAK,EAAE,kBACC,MAAM,gBACR,MAAM,MAChB,MAAM;;;;;;;;;;CA6DX,CAAC"}
@@ -20,6 +20,7 @@ export const useHealthBarData = (alerts, startTimestamp, endTimestamp, id) => {
20
20
  // Store alert data separately for tooltip access
21
21
  alertsMapData[uniqueKey] = {
22
22
  ...alert,
23
+ key: uniqueKey, // Add the consistent key to the alert object
23
24
  };
24
25
  });
25
26
  // Chart data - ready for BarChart (as array)
@@ -10,6 +10,7 @@ describe('useHealthBarData', () => {
10
10
  severity,
11
11
  startsAt,
12
12
  endsAt,
13
+ key: `${severity}_${startsAt}`,
13
14
  });
14
15
  describe('Alert Filtering', () => {
15
16
  it('should include alerts that are completely within the time range', () => {
@@ -93,6 +94,7 @@ describe('useHealthBarData', () => {
93
94
  severity: 'warning',
94
95
  startsAt: '2023-12-01T02:00:00Z',
95
96
  endsAt: '2023-12-01T04:00:00Z',
97
+ key: 'warning_0',
96
98
  });
97
99
  });
98
100
  it('should handle multiple alerts with correct indexing', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"linetimeseriechart.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,mBAAmB,EAGpB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAiD,MAAM,OAAO,CAAC;AAqCtE,MAAM,MAAM,KAAK,GAAG;IAElB,QAAQ,EAAE,MAAM,CAAC;IAEjB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAEzC,eAAe,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtE,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,SAAS,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACrC,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;CAC7B,CAAC;AAGF,KAAK,qBAAqB,GAAG;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EACF;QACE,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;QAC3B,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;KAC5B,GACD,SAAS,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CACzB,wBAAwB,GACxB,qBAAqB,CACxB,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CACd,YAAY,EAAE,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,EACjD,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,KACd,KAAK,CAAC,SAAS,CAAC;CACtB,CAAC;AAkGF,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,KAAK,EACL,MAAM,EACN,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAiB,EACjB,UAAwB,EACxB,SAAqB,EACrB,UAAU,EACV,QAAQ,EACR,MAAM,EACN,aAAa,EACb,GAAG,IAAI,EACR,EAAE,cAAc,2CA4WhB"}
1
+ {"version":3,"file":"linetimeseriechart.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,mBAAmB,EAGpB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAiD,MAAM,OAAO,CAAC;AAiCtE,MAAM,MAAM,KAAK,GAAG;IAElB,QAAQ,EAAE,MAAM,CAAC;IAEjB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAEzC,eAAe,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtE,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,SAAS,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACrC,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;CAC7B,CAAC;AAGF,KAAK,qBAAqB,GAAG;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EACF;QACE,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;QAC3B,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;KAC5B,GACD,SAAS,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CACzB,wBAAwB,GACxB,qBAAqB,CACxB,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CACd,YAAY,EAAE,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,EACjD,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,KACd,KAAK,CAAC,SAAS,CAAC;CACtB,CAAC;AAiHF,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,KAAK,EACL,MAAM,EACN,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAiB,EACjB,UAAwB,EACxB,SAAqB,EACrB,UAAU,EACV,QAAQ,EACR,MAAM,EACN,aAAa,EACb,GAAG,IAAI,EACR,EAAE,cAAc,2CAqWhB"}
@@ -1,62 +1,58 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { CartesianGrid, Line, LineChart, ReferenceLine, Tooltip, XAxis, YAxis, } from 'recharts';
3
3
  import React, { useCallback, useMemo, useRef, useState } from 'react';
4
4
  import styled, { useTheme } from 'styled-components';
5
- import { spacing } from '../../spacing';
5
+ import { Stack } from '../../spacing';
6
6
  import { fontSize } from '../../style/theme';
7
- import { Box } from '../box/Box';
8
7
  import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
9
- import { Icon } from '../icon/Icon.component';
10
8
  import { addMissingDataPoint, getUnitLabel, } from '../linetemporalchart/ChartUtil';
11
9
  import { Loader } from '../loader/Loader.component';
12
- import { ChartTitleText, Text } from '../text/Text.component';
13
- import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
10
+ import { ChartTitleText } from '../text/Text.component';
14
11
  import { formatXAxisLabel } from './utils';
15
- import { ChartTooltipContainer, ChartTooltipItem, ChartTooltipHeader, ChartTooltipItemsContainer, ChartTooltipSeparator, TooltipHeader, } from '../charttooltip/ChartTooltip';
12
+ import { ChartTooltipPortal, ChartTooltipItem, ChartTooltipHeader, ChartTooltipItemsContainer, ChartTooltipSeparator, TooltipHeader, } from '../charttooltip/ChartTooltip';
16
13
  import { LegendShape } from '../chartlegend/ChartLegend';
17
14
  import { StyledResponsiveContainer } from '../barchartv2/Barchart.component';
15
+ import { getRoundReferenceValue, getTicks } from '../barchartv2/utils';
16
+ import { IconHelp } from '../iconhelper/IconHelper';
17
+ const maxWidthTooltip = { maxWidth: '20rem' };
18
18
  const LineTemporalChartWrapper = styled.div `
19
19
  display: flex;
20
20
  flex-direction: column;
21
21
  justify-content: flex-start;
22
22
  `;
23
- const ChartHeader = styled.div `
24
- display: flex;
25
- align-items: center;
26
- `;
27
- const LineTimeSerieChartTooltip = ({ unitLabel, duration, isChartActive, tooltipProps, renderTooltip, hoveredValue, isSymmetrical, }) => {
28
- const { active, payload, label } = tooltipProps;
23
+ const LineTimeSerieChartTooltip = ({ unitLabel, duration, isChartActive, tooltipProps, renderTooltip, hoveredValue, isSymmetrical, chartContainerRef, }) => {
24
+ const { active, payload, label, coordinate } = tooltipProps;
29
25
  if (!active || !payload || !payload.length || !label || !isChartActive)
30
26
  return null;
31
- if (renderTooltip) {
32
- return renderTooltip(tooltipProps, unitLabel, duration);
33
- }
34
- // We can't use the default itemSorter method because it's a custom tooltip.
35
- // Sort the payload here instead
36
- const sortedPayload = [...payload].sort((a, b) => {
37
- const aValue = a.value;
38
- const bValue = b.value;
39
- if (aValue >= 0 && bValue >= 0) {
40
- return bValue - aValue; // Higher positive values first
41
- }
42
- if (aValue < 0 && bValue < 0) {
43
- return bValue - aValue; // Lower negative values first
44
- }
45
- return bValue - aValue; // Positives before negatives
46
- });
47
- // Find the transition point between positive and negative values
48
- const separatorIndex = sortedPayload.findIndex((entry) => entry.value < 0);
49
- const hasBothPositiveAndNegative = separatorIndex > 0 && separatorIndex < sortedPayload.length;
50
- return (_jsxs(ChartTooltipContainer, { children: [_jsx(ChartTooltipHeader, { children: _jsx(TooltipHeader, { duration: duration, value: label }) }), _jsx(ChartTooltipItemsContainer, { children: sortedPayload.map((entry, index) => {
51
- const legendIcon = (_jsx(LegendShape, { color: entry.color, shape: "line", chartColors: { [entry.color]: entry.color } }));
52
- const isHovered = entry.name === hoveredValue;
53
- const formattedValue = !Number.isFinite(entry.value)
54
- ? '-'
55
- : `${entry.value.toFixed(2)} ${unitLabel}`;
56
- return (_jsxs(React.Fragment, { children: [isSymmetrical &&
57
- hasBothPositiveAndNegative &&
58
- index === separatorIndex && _jsx(ChartTooltipSeparator, {}), _jsx(ChartTooltipItem, { label: entry.name, value: formattedValue, legendIcon: legendIcon, isHovered: isHovered })] }, index));
59
- }) })] }));
27
+ const tooltipContent = renderTooltip ? (renderTooltip(tooltipProps, unitLabel, duration)) : (_jsxs(_Fragment, { children: [_jsx(ChartTooltipHeader, { children: _jsx(TooltipHeader, { duration: duration, value: label }) }), _jsx(ChartTooltipItemsContainer, { children: (() => {
28
+ // We can't use the default itemSorter method because it's a custom tooltip.
29
+ // Sort the payload here instead
30
+ const sortedPayload = [...payload].sort((a, b) => {
31
+ const aValue = a.value;
32
+ const bValue = b.value;
33
+ if (aValue >= 0 && bValue >= 0) {
34
+ return bValue - aValue; // Higher positive values first
35
+ }
36
+ if (aValue < 0 && bValue < 0) {
37
+ return bValue - aValue; // Lower negative values first
38
+ }
39
+ return bValue - aValue; // Positives before negatives
40
+ });
41
+ // Find the transition point between positive and negative values
42
+ const separatorIndex = sortedPayload.findIndex((entry) => entry.value < 0);
43
+ const hasBothPositiveAndNegative = separatorIndex > 0 && separatorIndex < sortedPayload.length;
44
+ return sortedPayload.map((entry, index) => {
45
+ const legendIcon = (_jsx(LegendShape, { color: entry.color, shape: "line", chartColors: { [entry.color]: entry.color } }));
46
+ const isHovered = entry.name === hoveredValue;
47
+ const formattedValue = !Number.isFinite(entry.value)
48
+ ? '-'
49
+ : `${entry.value.toFixed(2)} ${unitLabel}`;
50
+ return (_jsxs(React.Fragment, { children: [isSymmetrical &&
51
+ hasBothPositiveAndNegative &&
52
+ index === separatorIndex && _jsx(ChartTooltipSeparator, {}), _jsx(ChartTooltipItem, { label: entry.name, value: formattedValue, legendIcon: legendIcon, isHovered: isHovered })] }, index));
53
+ });
54
+ })() })] }));
55
+ return (_jsx(ChartTooltipPortal, { coordinate: coordinate, chartContainerRef: chartContainerRef, isVisible: active && isChartActive, children: tooltipContent }));
60
56
  };
61
57
  const isSymmetricalSeries = (series) => {
62
58
  return 'above' in series && 'below' in series;
@@ -172,7 +168,8 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
172
168
  const bottom = Math.abs(Math.min(...values));
173
169
  const maxValue = Math.max(top, bottom);
174
170
  const { valueBase, unitLabel } = getUnitLabel(unitRange !== null && unitRange !== void 0 ? unitRange : [], maxValue);
175
- const topValue = Math.ceil(maxValue / valueBase / 10) * 10;
171
+ // Use round reference value to add extra padding to the top value
172
+ const topValue = getRoundReferenceValue(maxValue / valueBase);
176
173
  const rechartsData = chartData.map((dataPoint) => {
177
174
  const normalizedDataPoint = { ...dataPoint };
178
175
  Object.entries(dataPoint).forEach(([key, value]) => {
@@ -222,15 +219,14 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
222
219
  }, [series, getColor, selectedResources]);
223
220
  // Format time for display the tick in the x axis
224
221
  const formatXAxisLabelCallback = useCallback((timestamp) => formatXAxisLabel(timestamp, duration), [duration]);
225
- return (_jsxs(LineTemporalChartWrapper, { children: [_jsxs(ChartHeader, { children: [_jsxs(ChartTitleText, { children: [title, " ", unitLabel && `(${unitLabel})`] }), helpText && (_jsx(Box, { ml: spacing.r4, children: _jsx(TooltipComponent, { placement: 'right', overlay: _jsx(Text, { children: helpText }), children: _jsx(Icon, { name: "Info", color: theme.buttonSecondary }) }) })), isLoading && _jsx(Loader, {})] }), _jsx("div", { onFocus: () => setIsChartActive(true), onBlur: () => setIsChartActive(false), onFocusCapture: () => setIsChartActive(true), onBlurCapture: () => setIsChartActive(false), children: _jsx(StyledResponsiveContainer, { width: "100%", height: height, children: _jsxs(LineChart, { data: rechartsData, ref: chartRef, margin: { top: 0, right: 0, bottom: 0, left: 0 }, "aria-label": `Time series chart for ${title}`, syncId: syncId, onMouseEnter: () => setIsChartActive(true), onMouseLeave: () => setIsChartActive(false), accessibilityLayer: true, children: [_jsx(CartesianGrid, { vertical: true, horizontal: true, verticalPoints: [0], horizontalPoints: [0], stroke: theme.border, fill: theme.backgroundLevel4, strokeWidth: 1 }), _jsx(XAxis, { dataKey: "timestamp", type: "number", domain: ['dataMin', 'dataMax'], ticks: xAxisTicks, tickFormatter: formatXAxisLabelCallback, tickCount: 5, tick: {
222
+ return (_jsxs(LineTemporalChartWrapper, { children: [_jsxs(Stack, { gap: "r4", children: [_jsxs(ChartTitleText, { children: [title, " ", unitLabel && `(${unitLabel})`] }), helpText && (_jsx(IconHelp, { tooltipMessage: helpText, overlayStyle: maxWidthTooltip })), isLoading && _jsx(Loader, {})] }), _jsx("div", { onFocus: () => setIsChartActive(true), onBlur: () => setIsChartActive(false), onFocusCapture: () => setIsChartActive(true), onBlurCapture: () => setIsChartActive(false), children: _jsx(StyledResponsiveContainer, { width: "100%", height: height, children: _jsxs(LineChart, { data: rechartsData, ref: chartRef, margin: { top: 0, right: 0, bottom: 0, left: 0 }, "aria-label": `Time series chart for ${title}`, syncId: syncId, onMouseEnter: () => setIsChartActive(true), onMouseLeave: () => setIsChartActive(false), accessibilityLayer: true, children: [_jsx(CartesianGrid, { vertical: true, horizontal: true, verticalPoints: [0], horizontalPoints: [0], stroke: theme.border, fill: theme.backgroundLevel4, strokeWidth: 1 }), _jsx(XAxis, { dataKey: "timestamp", type: "number", domain: ['dataMin', 'dataMax'], ticks: xAxisTicks, tickFormatter: formatXAxisLabelCallback, tickCount: 5, tick: {
226
223
  fill: theme.textSecondary,
227
224
  fontSize: fontSize.smaller,
228
225
  }, axisLine: { stroke: theme.border } }), _jsx(YAxis, { orientation: "right", label: {
229
226
  value: yAxisTitle,
230
227
  angle: 90,
231
- position: 'insideRight',
228
+ dx: 20,
232
229
  style: {
233
- textAnchor: 'middle',
234
230
  fill: theme.textSecondary,
235
231
  fontSize: fontSize.smaller,
236
232
  },
@@ -241,7 +237,7 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
241
237
  : [0, topValue], axisLine: { stroke: theme.border }, tick: {
242
238
  fill: theme.textSecondary,
243
239
  fontSize: fontSize.smaller,
244
- }, tickFormatter: (value) => new Intl.NumberFormat('fr-FR').format(value.toFixed(0)), tickCount: 5, interval: 0 }), _jsx(Tooltip, { content: (props) => (_jsx(LineTimeSerieChartTooltip, { unitLabel: unitLabel, duration: duration, renderTooltip: renderTooltip, isSymmetrical: yAxisType === 'symmetrical', tooltipProps: props, isChartActive: isChartActive, hoveredValue: hoveredValue })) }), yAxisType === 'symmetrical' && (_jsx(ReferenceLine, { y: 0, stroke: theme.border })), Object.entries(groupedSeries).map(([resource, resourceSeries]) => resourceSeries.map((serie, serieIndex) => {
240
+ }, tickFormatter: (value) => new Intl.NumberFormat('fr-FR').format(value), ticks: getTicks(topValue, yAxisType === 'symmetrical'), interval: 0 }), _jsx(Tooltip, { content: (props) => (_jsx(LineTimeSerieChartTooltip, { unitLabel: unitLabel, duration: duration, renderTooltip: renderTooltip, isSymmetrical: yAxisType === 'symmetrical', tooltipProps: props, isChartActive: isChartActive, hoveredValue: hoveredValue, chartContainerRef: chartRef })) }), yAxisType === 'symmetrical' && (_jsx(ReferenceLine, { y: 0, stroke: theme.border })), Object.entries(groupedSeries).map(([resource, resourceSeries]) => resourceSeries.map((serie, serieIndex) => {
245
241
  const label = serie.getTooltipLabel(serie.metricPrefix, serie.resource);
246
242
  return (_jsx(Line, { type: "monotone", dataKey: label, stroke: colorMapping[resource], dot: false, isAnimationActive: false, strokeDasharray: serie.isLineDashed ? '4 4' : undefined, onMouseEnter: () => setHoveredValue(label), onMouseLeave: () => setHoveredValue(undefined) }, `${title}-${resource}-${serieIndex}`));
247
243
  }))] }) }) })] }));
@@ -1,4 +1,4 @@
1
- import { TIME_FORMATER, DAY_MONTH_ABBREVIATED, DAY_MONTH_ABBREVIATED_YEAR, } from '../date/FormattedDateTime';
1
+ import { TIME_FORMATER, DAY_MONTH_ABBREVIATED_YEAR, DAY_MONTH_ABBREVIATED_HOUR_MINUTE, } from '../date/FormattedDateTime';
2
2
  export const ONE_YEAR_MILLISECONDS = 366 * 24 * 60 * 60 * 1000;
3
3
  /**
4
4
  * Formats timestamp for X-axis labels based on time format and data range:
@@ -16,7 +16,7 @@ export const formatXAxisLabel = (timestamp, duration) => {
16
16
  return TIME_FORMATER.format(date);
17
17
  }
18
18
  else if (duration <= 7 * 24 * 60 * 60) {
19
- return DAY_MONTH_ABBREVIATED.format(date)
19
+ return DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date)
20
20
  .replace(',', '')
21
21
  .replace(/Sept/g, 'Sep');
22
22
  }
@@ -33,7 +33,7 @@ export const coreUIAvailableThemesNames = [
33
33
  'darkRebrand',
34
34
  'artescaLight',
35
35
  'ring9dark',
36
- 'G-Dark'
36
+ 'G-Dark',
37
37
  ];
38
38
  export const coreUIAvailableThemes = {
39
39
  darkRebrand: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/core-ui",
3
- "version": "0.176.0",
3
+ "version": "0.178.0",
4
4
  "description": "Scality common React component library",
5
5
  "author": "Scality Engineering",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useState, useRef } from 'react';
2
2
  import {
3
3
  Bar,
4
4
  BarChart,
@@ -20,7 +20,7 @@ import { IconHelp } from '../iconhelper/IconHelper';
20
20
  import { Loader } from '../loader/Loader.component';
21
21
  import { Text } from '../text/Text.component';
22
22
  import { BarchartTooltip } from './BarchartTooltip';
23
- import { UnitRange, useChartData } from './utils';
23
+ import { getTicks, UnitRange, useChartData } from './utils';
24
24
 
25
25
  const CHART_CONSTANTS = {
26
26
  TICK_WIDTH_OFFSET: 4,
@@ -34,6 +34,7 @@ const CHART_CONSTANTS = {
34
34
  bottom: 0,
35
35
  },
36
36
  };
37
+ const maxWidthTooltip = { maxWidth: '20rem' };
37
38
 
38
39
  /* ---------------------------------- TYPE ---------------------------------- */
39
40
 
@@ -76,6 +77,7 @@ export type BarchartSortFn<T extends BarchartBars> = (
76
77
 
77
78
  export type BarchartProps<T extends BarchartBars> = {
78
79
  type: CategoryType | TimeType;
80
+ title: string;
79
81
  bars?: T;
80
82
  tooltip?: BarchartTooltipFn<T>;
81
83
  defaultSort?: BarchartSortFn<T>;
@@ -89,7 +91,6 @@ export type BarchartProps<T extends BarchartBars> = {
89
91
  * @default 'default'
90
92
  */
91
93
  stackedBarSort?: 'default' | 'legend';
92
- title?: string;
93
94
  secondaryTitle?: string;
94
95
  rightTitle?: React.ReactNode;
95
96
  height?: number;
@@ -112,15 +113,15 @@ interface CustomTickProps {
112
113
 
113
114
  /**
114
115
  * Get the format of the date based on the duration
115
- * @param duration - Duration in milliseconds
116
+ * @param duration - Duration in seconds
116
117
  * @returns Formatted string
117
118
  */
118
119
  export const formatDate = (
119
120
  duration: number,
120
121
  ): 'time' | 'day-month-abbreviated' | 'chart-long-term-date' => {
121
- if (duration <= 24 * 60 * 60 * 1000) {
122
+ if (duration <= 24 * 60 * 60) {
122
123
  return 'time';
123
- } else if (duration <= 7 * 24 * 60 * 60 * 1000) {
124
+ } else if (duration <= 7 * 24 * 60 * 60) {
124
125
  return 'day-month-abbreviated';
125
126
  } else {
126
127
  return 'chart-long-term-date';
@@ -142,7 +143,9 @@ export const CustomTick = ({
142
143
 
143
144
  const duration =
144
145
  type.type === 'time'
145
- ? type.timeRange.endDate.getTime() - type.timeRange.startDate.getTime()
146
+ ? (type.timeRange.endDate.getTime() -
147
+ type.timeRange.startDate.getTime()) /
148
+ 1000
146
149
  : 0;
147
150
 
148
151
  return (
@@ -183,6 +186,7 @@ export const CustomTick = ({
183
186
  export const StyledResponsiveContainer = styled(ResponsiveContainer)`
184
187
  // Avoid tooltip over constrained text to be cut off
185
188
  & .recharts-surface {
189
+ outline: none;
186
190
  overflow: visible;
187
191
  }
188
192
  `;
@@ -202,7 +206,12 @@ const ChartHeader = ({
202
206
  <Wrap>
203
207
  <Stack gap="r4">
204
208
  <Text variant="ChartTitle">{title}</Text>
205
- {helpTooltip && <IconHelp tooltipMessage={helpTooltip} />}
209
+ {helpTooltip && (
210
+ <IconHelp
211
+ tooltipMessage={helpTooltip}
212
+ overlayStyle={maxWidthTooltip}
213
+ />
214
+ )}
206
215
 
207
216
  {secondaryTitle && (
208
217
  <Text
@@ -257,6 +266,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
257
266
  const theme = useTheme();
258
267
  const { getColor } = useChartLegend();
259
268
  const [hoveredValue, setHoveredValue] = useState<string | undefined>();
269
+ const chartRef = useRef<HTMLDivElement>(null);
260
270
 
261
271
  const {
262
272
  height = CHART_CONSTANTS.DEFAULT_HEIGHT,
@@ -297,11 +307,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
297
307
  unitRange,
298
308
  stackedBarSort,
299
309
  );
300
-
310
+ const titleWithUnit = unitLabel ? `${title} (${unitLabel})` : title;
301
311
  return (
302
- <Stack direction="vertical">
312
+ <Stack direction="vertical" style={{ gap: '0' }}>
303
313
  <ChartHeader
304
- title={title}
314
+ title={titleWithUnit}
305
315
  secondaryTitle={secondaryTitle}
306
316
  helpTooltip={helpTooltip}
307
317
  rightTitle={rightTitle}
@@ -311,7 +321,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
311
321
  ) : isLoading ? (
312
322
  <Loading height={height} />
313
323
  ) : (
314
- <StyledResponsiveContainer width="100%" height={height}>
324
+ <StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
315
325
  <BarChart
316
326
  data={rechartsData}
317
327
  accessibilityLayer
@@ -352,13 +362,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
352
362
  })}
353
363
 
354
364
  <YAxis
355
- tickCount={1}
356
365
  interval={0}
357
- unit={` ${unitLabel}`}
358
366
  domain={[0, roundReferenceValue]}
367
+ ticks={getTicks(roundReferenceValue, false)}
359
368
  tickFormatter={
360
- (value) =>
361
- new Intl.NumberFormat('fr-FR').format(value.toFixed(0)) // Add a space as thousand separator
369
+ (value) => new Intl.NumberFormat('fr-FR').format(value) // Add a space as thousand separator
362
370
  }
363
371
  axisLine={{ stroke: theme.border }}
364
372
  tick={{
@@ -391,6 +399,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
391
399
  hoveredValue={hoveredValue}
392
400
  tooltip={tooltip}
393
401
  unitLabel={unitLabel}
402
+ chartContainerRef={chartRef}
394
403
  />
395
404
  )}
396
405
  cursor={false}
@@ -19,6 +19,30 @@ const testTooltipProps = {
19
19
  const testTooltip = () => <div>Test Tooltip</div>;
20
20
  const date = new Date('2024-07-01T00:00:00').getTime();
21
21
 
22
+ // Create a mock DOM element for the chart container
23
+ const mockChartContainer = document.createElement('div');
24
+ mockChartContainer.getBoundingClientRect = jest.fn(() => ({
25
+ width: 800,
26
+ height: 400,
27
+ top: 0,
28
+ left: 0,
29
+ right: 800,
30
+ bottom: 400,
31
+ x: 0,
32
+ y: 0,
33
+ toJSON: () => ({
34
+ width: 800,
35
+ height: 400,
36
+ top: 0,
37
+ left: 0,
38
+ right: 800,
39
+ bottom: 400,
40
+ x: 0,
41
+ y: 0,
42
+ }),
43
+ }));
44
+ const mockChartContainerRef = { current: mockChartContainer };
45
+
22
46
  describe('ChartTooltip', () => {
23
47
  const selectors = {
24
48
  tooltip: () => screen.queryByText(/Test Tooltip/),
@@ -39,6 +63,7 @@ describe('ChartTooltip', () => {
39
63
  tooltipProps={testTooltipProps}
40
64
  hoveredValue="Success"
41
65
  tooltip={undefined}
66
+ chartContainerRef={mockChartContainerRef}
42
67
  />,
43
68
  );
44
69
  expect(selectors.category()).toBeInTheDocument();
@@ -54,6 +79,7 @@ describe('ChartTooltip', () => {
54
79
  tooltipProps={testTooltipProps}
55
80
  hoveredValue="Success"
56
81
  tooltip={testTooltip}
82
+ chartContainerRef={mockChartContainerRef}
57
83
  />,
58
84
  );
59
85
  expect(selectors.tooltip()).toBeInTheDocument();
@@ -65,6 +91,7 @@ describe('ChartTooltip', () => {
65
91
  tooltipProps={{ ...testTooltipProps, active: false }}
66
92
  hoveredValue="Success"
67
93
  tooltip={testTooltip}
94
+ chartContainerRef={mockChartContainerRef}
68
95
  />,
69
96
  );
70
97
  expect(selectors.tooltip()).not.toBeInTheDocument();
@@ -84,6 +111,7 @@ describe('ChartTooltip', () => {
84
111
  }}
85
112
  tooltipProps={{ ...testTooltipProps, label }}
86
113
  hoveredValue="Success"
114
+ chartContainerRef={mockChartContainerRef}
87
115
  />,
88
116
  );
89
117
  expect(selectors.success()).toBeInTheDocument();
@@ -107,6 +135,7 @@ describe('ChartTooltip', () => {
107
135
  }}
108
136
  tooltipProps={{ ...testTooltipProps, label }}
109
137
  hoveredValue="Success"
138
+ chartContainerRef={mockChartContainerRef}
110
139
  />,
111
140
  );
112
141
  expect(selectors.success()).toBeInTheDocument();
@@ -133,6 +162,7 @@ describe('ChartTooltip', () => {
133
162
  tooltipProps={tooltipProps}
134
163
  hoveredValue="Success"
135
164
  unitLabel="kB"
165
+ chartContainerRef={mockChartContainerRef}
136
166
  />,
137
167
  );
138
168
 
@@ -1,7 +1,7 @@
1
1
  import { TooltipContentProps } from 'recharts';
2
2
  import { LegendShape } from '../chartlegend/ChartLegend';
3
3
  import {
4
- ChartTooltipContainer,
4
+ ChartTooltipPortal,
5
5
  ChartTooltipHeader,
6
6
  ChartTooltipItem,
7
7
  ChartTooltipItemsContainer,
@@ -22,6 +22,7 @@ export const BarchartTooltip = <T extends BarchartBars>({
22
22
  hoveredValue,
23
23
  tooltip,
24
24
  unitLabel,
25
+ chartContainerRef,
25
26
  }: {
26
27
  type: TimeType | CategoryType;
27
28
  tooltipProps: TooltipContentProps<number, string>;
@@ -29,23 +30,25 @@ export const BarchartTooltip = <T extends BarchartBars>({
29
30
  hoveredValue: string | undefined;
30
31
  tooltip?: BarchartTooltipFn<T>;
31
32
  unitLabel?: string;
33
+ chartContainerRef: React.RefObject<HTMLDivElement>;
32
34
  }) => {
33
- const { active } = tooltipProps;
35
+ const { active, coordinate } = tooltipProps;
34
36
 
35
37
  if (!active) {
36
38
  return null;
37
39
  }
38
40
 
39
41
  const currentPoint = getCurrentPoint(tooltipProps, hoveredValue);
40
- if (tooltip) {
41
- return tooltip(currentPoint);
42
- }
42
+
43
43
  const duration =
44
44
  type.type === 'time'
45
45
  ? type.timeRange.startDate.getTime() - type.timeRange.endDate.getTime()
46
46
  : 0;
47
- return (
48
- <ChartTooltipContainer>
47
+
48
+ const tooltipContent = tooltip ? (
49
+ tooltip(currentPoint)
50
+ ) : (
51
+ <>
49
52
  <ChartTooltipHeader>
50
53
  {type.type === 'time' ? (
51
54
  <TooltipHeader duration={duration} value={currentPoint.category} />
@@ -80,6 +83,16 @@ export const BarchartTooltip = <T extends BarchartBars>({
80
83
  );
81
84
  })}
82
85
  </ChartTooltipItemsContainer>
83
- </ChartTooltipContainer>
86
+ </>
87
+ );
88
+
89
+ return (
90
+ <ChartTooltipPortal
91
+ coordinate={coordinate}
92
+ chartContainerRef={chartContainerRef}
93
+ isVisible={active}
94
+ >
95
+ {tooltipContent}
96
+ </ChartTooltipPortal>
84
97
  );
85
98
  };