@scality/core-ui 0.171.0 → 0.172.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 (72) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  2. package/dist/components/barchartv2/Barchart.component.js +2 -2
  3. package/dist/components/barchartv2/BarchartTooltip.d.ts +11 -0
  4. package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -0
  5. package/dist/components/barchartv2/BarchartTooltip.js +27 -0
  6. package/dist/components/charttooltip/ChartTooltip.d.ts +13 -0
  7. package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -0
  8. package/dist/components/charttooltip/ChartTooltip.js +49 -0
  9. package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts +4 -0
  10. package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts.map +1 -1
  11. package/dist/components/globalhealthbar/GlobalHealthBar.component.js +4 -0
  12. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts +10 -0
  13. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -0
  14. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +78 -0
  15. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +18 -0
  16. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -0
  17. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +95 -0
  18. package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts +7 -0
  19. package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts.map +1 -0
  20. package/dist/components/globalhealthbar/components/HealthBarXAxis.js +25 -0
  21. package/dist/components/globalhealthbar/healthBarUtils.d.ts +77 -0
  22. package/dist/components/globalhealthbar/healthBarUtils.d.ts.map +1 -0
  23. package/dist/components/globalhealthbar/healthBarUtils.js +196 -0
  24. package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts +2 -0
  25. package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts.map +1 -0
  26. package/dist/components/globalhealthbar/healthBarUtils.spec.js +391 -0
  27. package/dist/components/globalhealthbar/useHealthBarData.d.ts +18 -0
  28. package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -0
  29. package/dist/components/globalhealthbar/useHealthBarData.js +46 -0
  30. package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts +2 -0
  31. package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts.map +1 -0
  32. package/dist/components/globalhealthbar/useHealthBarData.spec.js +207 -0
  33. package/dist/components/icon/Icon.component.js +2 -2
  34. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +0 -2
  35. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  36. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +17 -57
  37. package/dist/components/sparkline/sparkline.component.d.ts +16 -0
  38. package/dist/components/sparkline/sparkline.component.d.ts.map +1 -0
  39. package/dist/components/sparkline/sparkline.component.js +20 -0
  40. package/dist/components/text/Text.component.d.ts +1 -1
  41. package/dist/components/text/Text.component.d.ts.map +1 -1
  42. package/dist/components/text/Text.component.js +6 -1
  43. package/dist/next.d.ts +3 -1
  44. package/dist/next.d.ts.map +1 -1
  45. package/dist/next.js +3 -1
  46. package/package.json +2 -2
  47. package/src/lib/components/barchartv2/Barchart.component.tsx +3 -2
  48. package/src/lib/components/barchartv2/{ChartTooltip.test.tsx → BarchartTooltip.test.tsx} +35 -12
  49. package/src/lib/components/barchartv2/BarchartTooltip.tsx +89 -0
  50. package/src/lib/components/charttooltip/ChartTooltip.tsx +83 -0
  51. package/src/lib/components/globalhealthbar/GlobalHealthBar.component.tsx +4 -1
  52. package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +203 -0
  53. package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +173 -0
  54. package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +94 -0
  55. package/src/lib/components/globalhealthbar/healthBarUtils.spec.ts +701 -0
  56. package/src/lib/components/globalhealthbar/healthBarUtils.ts +311 -0
  57. package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +487 -0
  58. package/src/lib/components/globalhealthbar/useHealthBarData.ts +74 -0
  59. package/src/lib/components/icon/Icon.component.tsx +2 -2
  60. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +50 -77
  61. package/src/lib/components/sparkline/sparkline.component.tsx +54 -0
  62. package/src/lib/components/text/Text.component.tsx +8 -2
  63. package/src/lib/next.ts +8 -1
  64. package/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx +145 -0
  65. package/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx +5 -0
  66. package/stories/InlineInput/InlineInput.stories.tsx +7 -1
  67. package/stories/globalhealthbar.stories.tsx +25 -5
  68. package/stories/sparkline.stories.tsx +168 -0
  69. package/dist/components/barchartv2/ChartTooltip.d.ts +0 -14
  70. package/dist/components/barchartv2/ChartTooltip.d.ts.map +0 -1
  71. package/dist/components/barchartv2/ChartTooltip.js +0 -41
  72. package/src/lib/components/barchartv2/ChartTooltip.tsx +0 -106
@@ -0,0 +1,89 @@
1
+ import {
2
+ BarchartBars,
3
+ BarchartTooltipFn,
4
+ CategoryType,
5
+ TimeType,
6
+ } from './Barchart.component';
7
+ import { FormattedDateTime } from '../date/FormattedDateTime';
8
+ import { TooltipContentProps } from 'recharts';
9
+ import { getCurrentPoint } from './utils';
10
+ import {
11
+ ChartTooltipContainer,
12
+ ChartTooltipItem,
13
+ ChartTooltipHeader,
14
+ ChartTooltipItemsContainer,
15
+ } from '../charttooltip/ChartTooltip';
16
+ import { LegendShape } from '../chartlegend/ChartLegend';
17
+
18
+ export const BarchartTooltip = <T extends BarchartBars>({
19
+ type,
20
+ tooltipProps,
21
+ colorSet,
22
+ hoveredValue,
23
+ tooltip,
24
+ unitLabel,
25
+ }: {
26
+ type: TimeType | CategoryType;
27
+ tooltipProps: TooltipContentProps<number, string>;
28
+ colorSet?: Record<string, string>;
29
+ hoveredValue: string | undefined;
30
+ tooltip?: BarchartTooltipFn<T>;
31
+ unitLabel?: string;
32
+ }) => {
33
+ const { active } = tooltipProps;
34
+
35
+ if (!active) {
36
+ return null;
37
+ }
38
+
39
+ const currentPoint = getCurrentPoint(tooltipProps, hoveredValue);
40
+ if (tooltip) {
41
+ return tooltip(currentPoint);
42
+ }
43
+
44
+ return (
45
+ <ChartTooltipContainer>
46
+ <ChartTooltipHeader>
47
+ {type.type === 'time' ? (
48
+ <FormattedDateTime
49
+ format={
50
+ type.timeRange.interval < 24 * 60 * 60 * 1000
51
+ ? 'day-month-abbreviated-hour-minute-second'
52
+ : 'long-date-without-weekday'
53
+ }
54
+ value={new Date(currentPoint.category)}
55
+ />
56
+ ) : (
57
+ currentPoint.category
58
+ )}
59
+ </ChartTooltipHeader>
60
+ <ChartTooltipItemsContainer>
61
+ {currentPoint.values.map((value) => {
62
+ const legendIcon = colorSet && (
63
+ <LegendShape
64
+ color={colorSet[value.label as keyof typeof colorSet]}
65
+ shape="rectangle"
66
+ chartColors={colorSet}
67
+ />
68
+ );
69
+
70
+ const formattedValue = Number.isInteger(value.value)
71
+ ? `${value.value}`
72
+ : value.value.toFixed(2);
73
+ const valueWithUnit = unitLabel
74
+ ? `${formattedValue} ${unitLabel}`
75
+ : formattedValue;
76
+ return (
77
+ <ChartTooltipItem
78
+ key={value.label}
79
+ label={value.label}
80
+ value={valueWithUnit}
81
+ isHovered={value.isHovered}
82
+ legendIcon={legendIcon}
83
+ />
84
+ );
85
+ })}
86
+ </ChartTooltipItemsContainer>
87
+ </ChartTooltipContainer>
88
+ );
89
+ };
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { spacing } from '../../spacing';
4
+ import { fontSize, fontWeight } from '../../style/theme';
5
+
6
+ export const ChartTooltipContainer = styled.div`
7
+ border: 1px solid ${({ theme }) => theme.border};
8
+ background-color: ${({ theme }) => theme.backgroundLevel1};
9
+ color: ${({ theme }) => theme.textPrimary};
10
+ border-radius: 4px;
11
+ font-size: ${fontSize.small};
12
+ padding: ${spacing.r8};
13
+ min-width: 10rem;
14
+ max-width: 250px;
15
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
16
+ `;
17
+
18
+ const TooltipText = styled.div<{
19
+ isHovered?: boolean;
20
+ align?: 'left' | 'right';
21
+ }>`
22
+ color: ${({ theme, isHovered }) =>
23
+ isHovered ? theme.textPrimary : theme.textSecondary};
24
+ font-size: ${fontSize.smaller};
25
+ font-weight: ${({ isHovered }) =>
26
+ isHovered ? fontWeight.bold : fontWeight.base};
27
+ text-align: ${({ align }) => align || 'left'};
28
+ ${({ align }) => align === 'right' && 'flex-shrink: 0;'}
29
+ `;
30
+
31
+ const TooltipRow = styled.div`
32
+ display: flex;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ gap: ${spacing.r32};
36
+ width: 100%;
37
+ `;
38
+
39
+ const TooltipLabel = styled.div`
40
+ display: flex;
41
+ align-items: center;
42
+ gap: ${spacing.r8};
43
+ flex: 1;
44
+ min-width: 0;
45
+ `;
46
+
47
+ interface ChartTooltipItemProps {
48
+ label: React.ReactNode;
49
+ value: React.ReactNode;
50
+ isHovered?: boolean;
51
+ legendIcon?: React.ReactNode;
52
+ }
53
+
54
+ export const ChartTooltipItem: React.FC<ChartTooltipItemProps> = ({
55
+ label,
56
+ value,
57
+ isHovered = false,
58
+ legendIcon,
59
+ }) => (
60
+ <TooltipRow>
61
+ <TooltipLabel>
62
+ {legendIcon}
63
+ <TooltipText isHovered={isHovered}>{label}</TooltipText>
64
+ </TooltipLabel>
65
+ <TooltipText isHovered={isHovered} align="right">
66
+ {value}
67
+ </TooltipText>
68
+ </TooltipRow>
69
+ );
70
+
71
+ export const ChartTooltipHeader = styled.div`
72
+ color: ${({ theme }) => theme.textPrimary};
73
+ font-weight: ${fontWeight.bold};
74
+ text-align: center;
75
+ margin-bottom: ${spacing.r8};
76
+ `;
77
+
78
+ export const ChartTooltipItemsContainer = styled.div`
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: ${spacing.r8};
82
+ width: 100%;
83
+ `;
@@ -19,7 +19,10 @@ export type GlobalHealthProps = {
19
19
  height?: number;
20
20
  tooltipPosition?: Position;
21
21
  };
22
-
22
+ /**
23
+ * @deprecated Use GlobalHealthBar v2 instead
24
+ * @example import { GlobalHealthBar } from '@scality/core-ui/dist/next';
25
+ */
23
26
  function GlobalHealthBar({
24
27
  id,
25
28
  alerts,
@@ -0,0 +1,203 @@
1
+ import { useCallback, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ Bar,
4
+ BarChart,
5
+ ResponsiveContainer,
6
+ Tooltip,
7
+ TooltipContentProps,
8
+ YAxis,
9
+ } from 'recharts';
10
+ import styled, { useTheme } from 'styled-components';
11
+ import { GlobalHealthBarTooltip } from './components/GlobalHealthBarTooltip';
12
+ import { HealthBarXAxis } from './components/HealthBarXAxis';
13
+ import {
14
+ CHART_CONFIG,
15
+ getNavigationAction,
16
+ getNavigationStateUpdate,
17
+ } from './healthBarUtils';
18
+ import { Alert, useHealthBarData } from './useHealthBarData';
19
+
20
+ export interface GlobalHealthProps {
21
+ id: string;
22
+ alerts: Alert[];
23
+ start: Date;
24
+ end: Date;
25
+ }
26
+
27
+ const ChartInteractiveContainer = styled.div`
28
+ position: relative;
29
+ `;
30
+
31
+ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) {
32
+ const [tooltipData, setTooltipData] = useState<Alert | null>(null);
33
+ const [focusedAlertIndex, setFocusedAlertIndex] = useState<number>(-1);
34
+ const [keyboardActive, setKeyboardActive] = useState<boolean>(false);
35
+ const chartContainerRef = useRef<HTMLDivElement>(null);
36
+ const theme = useTheme();
37
+ const startTimestamp = new Date(start).getTime();
38
+ const endTimestamp = new Date(end).getTime();
39
+
40
+ const { chartData, alertsMap, alertKeys } = useHealthBarData(
41
+ alerts,
42
+ startTimestamp,
43
+ endTimestamp,
44
+ id,
45
+ );
46
+
47
+ const handlePointerEnter = useCallback(
48
+ (key: string) => {
49
+ setTooltipData(alertsMap[key]);
50
+ },
51
+ [alertsMap],
52
+ );
53
+
54
+ const handlePointerLeave = useCallback(() => {
55
+ if (!keyboardActive) {
56
+ setTooltipData(null);
57
+ }
58
+ }, [keyboardActive]);
59
+
60
+ const { warningKeys, criticalKeys, unavailableKeys } = alertKeys;
61
+
62
+ // Get all alert keys in order for keyboard navigation
63
+ const allAlertKeys = useMemo(() => {
64
+ return Object.values(alertsMap).sort((a, b) => {
65
+ return new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime();
66
+ });
67
+ }, [alertsMap]);
68
+
69
+ // Handle keyboard navigation
70
+ const handleKeyDown = useCallback(
71
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
72
+ const action = getNavigationAction(event.key);
73
+ if (!action || allAlertKeys.length === 0) return;
74
+
75
+ event.preventDefault();
76
+
77
+ const update = getNavigationStateUpdate(
78
+ action,
79
+ focusedAlertIndex,
80
+ allAlertKeys,
81
+ );
82
+
83
+ setFocusedAlertIndex(update.newIndex);
84
+ setTooltipData(update.selectedAlert);
85
+ setKeyboardActive(update.shouldActivateKeyboard);
86
+ },
87
+ [allAlertKeys, focusedAlertIndex],
88
+ );
89
+
90
+ // Handle focus events
91
+ const handleFocus = useCallback(() => {
92
+ if (allAlertKeys.length > 0 && focusedAlertIndex === -1) {
93
+ setFocusedAlertIndex(0);
94
+ setTooltipData(allAlertKeys[0]);
95
+ setKeyboardActive(true);
96
+ }
97
+ }, [allAlertKeys, focusedAlertIndex]);
98
+
99
+ const handleBlur = useCallback(() => {
100
+ setKeyboardActive(false);
101
+ setFocusedAlertIndex(-1);
102
+ setTooltipData(null);
103
+ }, []);
104
+
105
+ // Handle mouse enter to disable keyboard mode
106
+ const handleMouseEnter = useCallback(() => {
107
+ setKeyboardActive(false);
108
+ }, []);
109
+
110
+ const allAlertBars = useMemo(() => {
111
+ const configs = [
112
+ { keys: unavailableKeys, fill: theme.textSecondary },
113
+ { keys: warningKeys, fill: theme.statusWarning },
114
+ { keys: criticalKeys, fill: theme.statusCritical },
115
+ ];
116
+
117
+ return configs.flatMap(({ keys, fill }) =>
118
+ keys.map((key) => ({ key, fill })),
119
+ );
120
+ }, [unavailableKeys, warningKeys, criticalKeys, theme]);
121
+
122
+ return (
123
+ <ChartInteractiveContainer
124
+ ref={chartContainerRef}
125
+ tabIndex={0}
126
+ role="application"
127
+ aria-label={`Health bar chart with ${allAlertKeys.length} alerts. Use arrow keys to navigate, Escape to close tooltip.`}
128
+ onKeyDown={handleKeyDown}
129
+ onFocus={handleFocus}
130
+ onBlur={handleBlur}
131
+ onMouseEnter={handleMouseEnter}
132
+ >
133
+ <ResponsiveContainer width={'100%'} height={CHART_CONFIG.CHART_HEIGHT}>
134
+ <BarChart
135
+ data={chartData}
136
+ layout="vertical"
137
+ barSize={CHART_CONFIG.BAR_SIZE}
138
+ accessibilityLayer
139
+ margin={CHART_CONFIG.MARGINS}
140
+ >
141
+ <HealthBarXAxis
142
+ startTimestamp={startTimestamp}
143
+ endTimestamp={endTimestamp}
144
+ />
145
+
146
+ <Tooltip
147
+ allowEscapeViewBox={{ x: true, y: true }}
148
+ isAnimationActive={false}
149
+ shared={false}
150
+ wrapperStyle={{
151
+ width: '20rem',
152
+ position: 'fixed',
153
+ }}
154
+ content={(props: TooltipContentProps<number, string>) => {
155
+ return (
156
+ <GlobalHealthBarTooltip
157
+ tooltipData={tooltipData}
158
+ tooltipProps={props}
159
+ chartContainerRef={chartContainerRef}
160
+ isKeyboardActive={keyboardActive}
161
+ startTimestamp={startTimestamp}
162
+ endTimestamp={endTimestamp}
163
+ />
164
+ );
165
+ }}
166
+ />
167
+
168
+ {/* YAxis for the Background healthy bar */}
169
+ <YAxis yAxisId={'background'} type="category" hide />
170
+
171
+ {/* Generate YAxis for all alert keys */}
172
+ {allAlertBars.map(({ key }) => (
173
+ <YAxis key={`yAxis${key}`} yAxisId={key} type="category" hide />
174
+ ))}
175
+
176
+ {/* Background healthy bar */}
177
+ <Bar
178
+ dataKey="range"
179
+ fill={theme.statusHealthy}
180
+ radius={CHART_CONFIG.RADIUS_SIZE}
181
+ yAxisId="background"
182
+ isAnimationActive={false}
183
+ />
184
+
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
+ ))}
196
+ </BarChart>
197
+ </ResponsiveContainer>
198
+ </ChartInteractiveContainer>
199
+ );
200
+ }
201
+
202
+ // Re-export Alert type for external use
203
+ export type { Alert };
@@ -0,0 +1,173 @@
1
+ import React from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useEffect, useState } from 'react';
4
+ 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
+ import { FormattedDateTime, Stack, Text, Wrap, spacing } from '../../../index';
13
+ import { Alert } from '../GlobalHealthBarRecharts.component';
14
+ import { TooltipContentProps } from 'recharts';
15
+ import { zIndex } from '../../../style/theme';
16
+ import { CHART_CONFIG, getTooltipPosition } from '../healthBarUtils';
17
+
18
+ interface GlobalHealthBarTooltipProps {
19
+ tooltipData: Alert | null;
20
+ coordinate?: { x: number; y: number };
21
+ tooltipProps: TooltipContentProps<number, string>;
22
+ chartContainerRef: React.RefObject<HTMLDivElement>;
23
+ isKeyboardActive?: boolean;
24
+ startTimestamp?: number;
25
+ endTimestamp?: number;
26
+ }
27
+
28
+ const TooltipContainer = styled.div`
29
+ ${(props) => {
30
+ const theme = useTheme();
31
+
32
+ return css`
33
+ border: 1px solid ${theme.border};
34
+ width: 24rem;
35
+ z-index: ${zIndex.tooltip};
36
+ color: ${theme.textSecondary};
37
+ background-color: ${theme.backgroundLevel1};
38
+ border-radius: 4px;
39
+ padding: ${spacing.r8};
40
+ pointer-events: none;
41
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
42
+ `;
43
+ }}
44
+ `;
45
+
46
+ export const GlobalHealthBarTooltip = (props: GlobalHealthBarTooltipProps) => {
47
+ const {
48
+ tooltipData,
49
+ tooltipProps,
50
+ chartContainerRef,
51
+ isKeyboardActive = false,
52
+ startTimestamp = 0,
53
+ endTimestamp = 0,
54
+ } = props;
55
+ const { coordinate } = tooltipProps;
56
+ const [virtualElement, setVirtualElement] = useState<any>(null);
57
+
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();
78
+
79
+ let tooltipX: number;
80
+ let tooltipY: number;
81
+
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;
88
+
89
+ // Use the same positioning logic as alert bars
90
+ const alertCenterX = getTooltipPosition(
91
+ tooltipData,
92
+ startTimestamp,
93
+ endTimestamp,
94
+ chartUsableWidth,
95
+ );
96
+
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
+ }
106
+
107
+ setVirtualElement({
108
+ getBoundingClientRect() {
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,
118
+ };
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>
170
+ );
171
+
172
+ return createPortal(tooltipContent, document.body);
173
+ };
@@ -0,0 +1,94 @@
1
+ import { XAxis } from 'recharts';
2
+ import { useTheme } from 'styled-components';
3
+ import { fontSize } from '../../../style/theme';
4
+ import {
5
+ getTicks,
6
+ calculateLabelVisibility,
7
+ TIME_CONSTANTS,
8
+ getEdgeMargin,
9
+ } from '../healthBarUtils';
10
+ import { FormattedDateTime } from '../../date/FormattedDateTime';
11
+
12
+ interface HealthBarXAxisProps {
13
+ startTimestamp: number;
14
+ endTimestamp: number;
15
+ }
16
+ const CustomTick = ({
17
+ tickProps,
18
+ startTimestamp,
19
+ endTimestamp,
20
+ }: {
21
+ tickProps: any;
22
+ startTimestamp: number;
23
+ endTimestamp: number;
24
+ }) => {
25
+ const theme = useTheme();
26
+ const { y, payload, width, index, visibleTicksCount } = tickProps;
27
+ const span = endTimestamp - startTimestamp;
28
+ const is7DaySpan = span === 7 * TIME_CONSTANTS.ONE_DAY;
29
+ const isDaySpan = span === TIME_CONSTANTS.ONE_DAY;
30
+
31
+ const shouldShowLabel = calculateLabelVisibility(
32
+ width,
33
+ visibleTicksCount,
34
+ span,
35
+ index,
36
+ endTimestamp,
37
+ );
38
+ const edgeMargin = getEdgeMargin(index, visibleTicksCount, isDaySpan);
39
+ return (
40
+ // use coordinate to center the text
41
+ shouldShowLabel && (
42
+ <g transform={`translate(${payload.coordinate},${y})`}>
43
+ <text
44
+ textAnchor="middle"
45
+ dy={10}
46
+ dx={edgeMargin}
47
+ fontSize={fontSize.smaller}
48
+ fill={theme.textSecondary}
49
+ >
50
+ {is7DaySpan || isDaySpan ? (
51
+ <FormattedDateTime
52
+ format="day-month-abbreviated-hour-minute"
53
+ value={new Date(payload.value)}
54
+ />
55
+ ) : (
56
+ <FormattedDateTime format="time" value={new Date(payload.value)} />
57
+ )}
58
+ </text>
59
+ </g>
60
+ )
61
+ );
62
+ };
63
+
64
+ export const HealthBarXAxis = ({
65
+ startTimestamp,
66
+ endTimestamp,
67
+ }: HealthBarXAxisProps) => {
68
+ const theme = useTheme();
69
+ const ticks = getTicks(startTimestamp, endTimestamp);
70
+
71
+ return (
72
+ <XAxis
73
+ allowDataOverflow={true}
74
+ dataKey="start"
75
+ type="number"
76
+ domain={[startTimestamp, endTimestamp]}
77
+ tickSize={5}
78
+ minTickGap={10}
79
+ interval={0}
80
+ tick={(props: any) => {
81
+ return (
82
+ <CustomTick
83
+ tickProps={props}
84
+ startTimestamp={startTimestamp}
85
+ endTimestamp={endTimestamp}
86
+ />
87
+ );
88
+ }}
89
+ ticks={ticks}
90
+ tickLine={{ stroke: theme.textSecondary }}
91
+ axisLine={false}
92
+ />
93
+ );
94
+ };