@scality/core-ui 0.176.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 (43) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts +1 -1
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +10 -6
  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 +83 -1
  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 +19 -12
  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 +134 -1
  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
@@ -10,21 +10,18 @@ import {
10
10
  } from 'recharts';
11
11
  import React, { useCallback, useMemo, useRef, useState } from 'react';
12
12
  import styled, { useTheme } from 'styled-components';
13
- import { spacing } from '../../spacing';
13
+ import { Stack } from '../../spacing';
14
14
  import { fontSize } from '../../style/theme';
15
- import { Box } from '../box/Box';
16
15
  import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
17
- import { Icon } from '../icon/Icon.component';
18
16
  import {
19
17
  addMissingDataPoint,
20
18
  getUnitLabel,
21
19
  } from '../linetemporalchart/ChartUtil';
22
20
  import { Loader } from '../loader/Loader.component';
23
- import { ChartTitleText, Text } from '../text/Text.component';
24
- import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
21
+ import { ChartTitleText } from '../text/Text.component';
25
22
  import { formatXAxisLabel } from './utils';
26
23
  import {
27
- ChartTooltipContainer,
24
+ ChartTooltipPortal,
28
25
  ChartTooltipItem,
29
26
  ChartTooltipHeader,
30
27
  ChartTooltipItemsContainer,
@@ -33,6 +30,10 @@ import {
33
30
  } from '../charttooltip/ChartTooltip';
34
31
  import { LegendShape } from '../chartlegend/ChartLegend';
35
32
  import { StyledResponsiveContainer } from '../barchartv2/Barchart.component';
33
+ import { getRoundReferenceValue, getTicks } from '../barchartv2/utils';
34
+ import { IconHelp } from '../iconhelper/IconHelper';
35
+
36
+ const maxWidthTooltip = { maxWidth: '20rem' };
36
37
 
37
38
  const LineTemporalChartWrapper = styled.div`
38
39
  display: flex;
@@ -40,11 +41,6 @@ const LineTemporalChartWrapper = styled.div`
40
41
  justify-content: flex-start;
41
42
  `;
42
43
 
43
- const ChartHeader = styled.div`
44
- display: flex;
45
- align-items: center;
46
- `;
47
-
48
44
  export type Serie = {
49
45
  // the name of the resource
50
46
  resource: string;
@@ -112,6 +108,7 @@ const LineTimeSerieChartTooltip = ({
112
108
  renderTooltip,
113
109
  hoveredValue,
114
110
  isSymmetrical,
111
+ chartContainerRef,
115
112
  }: {
116
113
  tooltipProps: TooltipContentProps<number, string>;
117
114
  unitLabel?: string;
@@ -124,73 +121,87 @@ const LineTimeSerieChartTooltip = ({
124
121
  ) => React.ReactNode;
125
122
  hoveredValue?: string;
126
123
  isSymmetrical?: boolean;
124
+ chartContainerRef: React.RefObject<HTMLDivElement>;
127
125
  }) => {
128
- const { active, payload, label } = tooltipProps;
126
+ const { active, payload, label, coordinate } = tooltipProps;
129
127
 
130
128
  if (!active || !payload || !payload.length || !label || !isChartActive)
131
129
  return null;
132
130
 
133
- if (renderTooltip) {
134
- return renderTooltip(tooltipProps, unitLabel, duration);
135
- }
136
- // We can't use the default itemSorter method because it's a custom tooltip.
137
- // Sort the payload here instead
138
- const sortedPayload = [...payload].sort((a, b) => {
139
- const aValue = a.value;
140
- const bValue = b.value;
141
-
142
- if (aValue >= 0 && bValue >= 0) {
143
- return bValue - aValue; // Higher positive values first
144
- }
145
- if (aValue < 0 && bValue < 0) {
146
- return bValue - aValue; // Lower negative values first
147
- }
148
- return bValue - aValue; // Positives before negatives
149
- });
150
-
151
- // Find the transition point between positive and negative values
152
- const separatorIndex = sortedPayload.findIndex((entry) => entry.value < 0);
153
- const hasBothPositiveAndNegative =
154
- separatorIndex > 0 && separatorIndex < sortedPayload.length;
155
-
156
- return (
157
- <ChartTooltipContainer>
131
+ const tooltipContent = renderTooltip ? (
132
+ renderTooltip(tooltipProps, unitLabel, duration)
133
+ ) : (
134
+ <>
158
135
  <ChartTooltipHeader>
159
136
  <TooltipHeader duration={duration} value={label} />
160
137
  </ChartTooltipHeader>
161
138
  <ChartTooltipItemsContainer>
162
- {sortedPayload.map((entry, index) => {
163
- const legendIcon = (
164
- <LegendShape
165
- color={entry.color}
166
- shape="line"
167
- chartColors={{ [entry.color]: entry.color }}
168
- />
139
+ {(() => {
140
+ // We can't use the default itemSorter method because it's a custom tooltip.
141
+ // Sort the payload here instead
142
+ const sortedPayload = [...payload].sort((a, b) => {
143
+ const aValue = a.value;
144
+ const bValue = b.value;
145
+
146
+ if (aValue >= 0 && bValue >= 0) {
147
+ return bValue - aValue; // Higher positive values first
148
+ }
149
+ if (aValue < 0 && bValue < 0) {
150
+ return bValue - aValue; // Lower negative values first
151
+ }
152
+ return bValue - aValue; // Positives before negatives
153
+ });
154
+
155
+ // Find the transition point between positive and negative values
156
+ const separatorIndex = sortedPayload.findIndex(
157
+ (entry) => entry.value < 0,
169
158
  );
170
-
171
- const isHovered = entry.name === hoveredValue;
172
-
173
- const formattedValue = !Number.isFinite(entry.value)
174
- ? '-'
175
- : `${entry.value.toFixed(2)} ${unitLabel}`;
176
-
177
- return (
178
- <React.Fragment key={index}>
179
- {/* Add separator between positive and negative values for symmetrical charts */}
180
- {isSymmetrical &&
181
- hasBothPositiveAndNegative &&
182
- index === separatorIndex && <ChartTooltipSeparator />}
183
- <ChartTooltipItem
184
- label={entry.name}
185
- value={formattedValue}
186
- legendIcon={legendIcon}
187
- isHovered={isHovered}
159
+ const hasBothPositiveAndNegative =
160
+ separatorIndex > 0 && separatorIndex < sortedPayload.length;
161
+
162
+ return sortedPayload.map((entry, index) => {
163
+ const legendIcon = (
164
+ <LegendShape
165
+ color={entry.color}
166
+ shape="line"
167
+ chartColors={{ [entry.color]: entry.color }}
188
168
  />
189
- </React.Fragment>
190
- );
191
- })}
169
+ );
170
+
171
+ const isHovered = entry.name === hoveredValue;
172
+
173
+ const formattedValue = !Number.isFinite(entry.value)
174
+ ? '-'
175
+ : `${entry.value.toFixed(2)} ${unitLabel}`;
176
+
177
+ return (
178
+ <React.Fragment key={index}>
179
+ {/* Add separator between positive and negative values for symmetrical charts */}
180
+ {isSymmetrical &&
181
+ hasBothPositiveAndNegative &&
182
+ index === separatorIndex && <ChartTooltipSeparator />}
183
+ <ChartTooltipItem
184
+ label={entry.name}
185
+ value={formattedValue}
186
+ legendIcon={legendIcon}
187
+ isHovered={isHovered}
188
+ />
189
+ </React.Fragment>
190
+ );
191
+ });
192
+ })()}
192
193
  </ChartTooltipItemsContainer>
193
- </ChartTooltipContainer>
194
+ </>
195
+ );
196
+
197
+ return (
198
+ <ChartTooltipPortal
199
+ coordinate={coordinate}
200
+ chartContainerRef={chartContainerRef}
201
+ isVisible={active && isChartActive}
202
+ >
203
+ {tooltipContent}
204
+ </ChartTooltipPortal>
194
205
  );
195
206
  };
196
207
 
@@ -382,8 +393,8 @@ export function LineTimeSerieChart({
382
393
  const maxValue = Math.max(top, bottom);
383
394
 
384
395
  const { valueBase, unitLabel } = getUnitLabel(unitRange ?? [], maxValue);
385
-
386
- const topValue = Math.ceil(maxValue / valueBase / 10) * 10;
396
+ // Use round reference value to add extra padding to the top value
397
+ const topValue = getRoundReferenceValue(maxValue / valueBase);
387
398
 
388
399
  const rechartsData = chartData.map((dataPoint) => {
389
400
  const normalizedDataPoint = { ...dataPoint };
@@ -450,22 +461,15 @@ export function LineTimeSerieChart({
450
461
 
451
462
  return (
452
463
  <LineTemporalChartWrapper>
453
- <ChartHeader>
464
+ <Stack gap="r4">
454
465
  <ChartTitleText>
455
466
  {title} {unitLabel && `(${unitLabel})`}
456
467
  </ChartTitleText>
457
468
  {helpText && (
458
- <Box ml={spacing.r4}>
459
- <TooltipComponent
460
- placement={'right'}
461
- overlay={<Text>{helpText}</Text>}
462
- >
463
- <Icon name="Info" color={theme.buttonSecondary} />
464
- </TooltipComponent>
465
- </Box>
469
+ <IconHelp tooltipMessage={helpText} overlayStyle={maxWidthTooltip} />
466
470
  )}
467
471
  {isLoading && <Loader />}
468
- </ChartHeader>
472
+ </Stack>
469
473
  <div
470
474
  onFocus={() => setIsChartActive(true)}
471
475
  onBlur={() => setIsChartActive(false)}
@@ -510,9 +514,8 @@ export function LineTimeSerieChart({
510
514
  label={{
511
515
  value: yAxisTitle,
512
516
  angle: 90,
513
- position: 'insideRight',
517
+ dx: 20,
514
518
  style: {
515
- textAnchor: 'middle',
516
519
  fill: theme.textSecondary,
517
520
  fontSize: fontSize.smaller,
518
521
  },
@@ -530,9 +533,9 @@ export function LineTimeSerieChart({
530
533
  fontSize: fontSize.smaller,
531
534
  }}
532
535
  tickFormatter={(value) =>
533
- new Intl.NumberFormat('fr-FR').format(value.toFixed(0))
536
+ new Intl.NumberFormat('fr-FR').format(value)
534
537
  }
535
- tickCount={5}
538
+ ticks={getTicks(topValue, yAxisType === 'symmetrical')}
536
539
  interval={0}
537
540
  />
538
541
  <Tooltip
@@ -545,6 +548,7 @@ export function LineTimeSerieChart({
545
548
  tooltipProps={props}
546
549
  isChartActive={isChartActive}
547
550
  hoveredValue={hoveredValue}
551
+ chartContainerRef={chartRef}
548
552
  />
549
553
  )}
550
554
  />
@@ -12,10 +12,10 @@ describe('formatXAxisLabel', () => {
12
12
  });
13
13
 
14
14
  describe('medium duration (≤ 7 days)', () => {
15
- it('should format timestamp with day-month-abbreviated format', () => {
15
+ it('should format timestamp with day-month-abbreviated-hour-minute format', () => {
16
16
  const duration = 3 * 24 * 60 * 60; // 3 days
17
17
  const result = formatXAxisLabel(mockTimestamp, duration);
18
- expect(result).toBe('15 Sep');
18
+ expect(result).toBe('15 Sep 14:30');
19
19
  });
20
20
  });
21
21
 
@@ -37,7 +37,7 @@ describe('formatXAxisLabel', () => {
37
37
  it('should handle exactly 7 days duration', () => {
38
38
  const duration = 7 * 24 * 60 * 60; // exactly 7 days
39
39
  const result = formatXAxisLabel(mockTimestamp, duration);
40
- expect(result).toBe('15 Sep');
40
+ expect(result).toBe('15 Sep 14:30');
41
41
  });
42
42
 
43
43
  it('should handle just over 7 days duration', () => {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TIME_FORMATER,
3
- DAY_MONTH_ABBREVIATED,
4
3
  DAY_MONTH_ABBREVIATED_YEAR,
4
+ DAY_MONTH_ABBREVIATED_HOUR_MINUTE,
5
5
  } from '../date/FormattedDateTime';
6
6
 
7
7
  export const ONE_YEAR_MILLISECONDS = 366 * 24 * 60 * 60 * 1000;
@@ -28,7 +28,7 @@ export const formatXAxisLabel = (
28
28
  if (duration <= 24 * 60 * 60) {
29
29
  return TIME_FORMATER.format(date);
30
30
  } else if (duration <= 7 * 24 * 60 * 60) {
31
- return DAY_MONTH_ABBREVIATED.format(date)
31
+ return DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date)
32
32
  .replace(',', '')
33
33
  .replace(/Sept/g, 'Sep');
34
34
  } else {
@@ -60,7 +60,7 @@ export const coreUIAvailableThemesNames = [
60
60
  'darkRebrand',
61
61
  'artescaLight',
62
62
  'ring9dark',
63
- 'G-Dark'
63
+ 'G-Dark',
64
64
  ] as const;
65
65
  export type CoreUIThemeName = (typeof coreUIAvailableThemesNames)[number];
66
66
 
@@ -39,17 +39,17 @@ export const Playground: Story = {
39
39
  {
40
40
  label: 'Success',
41
41
  data: [
42
- ['category1', 2],
43
- ['category2', 4],
44
- ['category3', 6],
42
+ ['category1', 1],
43
+ ['category2', 1],
44
+ ['category3', 2],
45
45
  ],
46
46
  },
47
47
  {
48
48
  label: 'Failed',
49
49
  data: [
50
- ['category1', 8],
51
- ['category2', 10],
52
- ['category3', 12],
50
+ ['category1', 1],
51
+ ['category2', 1],
52
+ ['category3', 2],
53
53
  ],
54
54
  },
55
55
  ] as const;
@@ -61,7 +61,11 @@ export const Playground: Story = {
61
61
  }}
62
62
  >
63
63
  <Stack direction="vertical" gap="r16">
64
- <Barchart type={{ type: 'category' }} bars={exampleData} />
64
+ <Barchart
65
+ type={{ type: 'category' }}
66
+ bars={exampleData}
67
+ title="Playground"
68
+ />
65
69
  <ChartLegend shape="rectangle" direction="horizontal" />
66
70
  </Stack>
67
71
  </ChartLegendWrapper>
@@ -122,6 +126,7 @@ export const Time7Days: Story = {
122
126
  >
123
127
  <Stack direction="vertical" gap="r16">
124
128
  <Barchart
129
+ title="Time 7 Days"
125
130
  type={{
126
131
  type: 'time',
127
132
  timeRange: {
@@ -192,6 +197,7 @@ export const Time7DaysWithMissingData: Story = {
192
197
  }}
193
198
  >
194
199
  <Barchart
200
+ title="Time 7 Days With Missing Data"
195
201
  type={{
196
202
  type: 'time',
197
203
  timeRange: {
@@ -285,6 +291,7 @@ export const TimeLast24Hours: Story = {
285
291
  }}
286
292
  >
287
293
  <Barchart
294
+ title="Time Last 24 Hours"
288
295
  type={{
289
296
  type: 'time',
290
297
  timeRange: {
@@ -332,6 +339,7 @@ export const CapacityWithUnitRange: Story = {
332
339
  }}
333
340
  >
334
341
  <Barchart
342
+ title="Capacity With Unit Range"
335
343
  type={{ type: 'category' }}
336
344
  bars={capacityDataWithUnitRange}
337
345
  unitRange={[
@@ -402,7 +410,12 @@ export const Stacked: Story = {
402
410
  }}
403
411
  >
404
412
  <Stack direction="vertical" gap="r16">
405
- <Barchart type={{ type: 'category' }} bars={stackedData} stacked />
413
+ <Barchart
414
+ type={{ type: 'category' }}
415
+ bars={stackedData}
416
+ stacked
417
+ title="Stacked"
418
+ />
406
419
  <ChartLegend shape="rectangle" />
407
420
  </Stack>
408
421
  </ChartLegendWrapper>
@@ -457,6 +470,7 @@ export const DefaultSort: Story = {
457
470
  stacked
458
471
  bars={defaultSortData}
459
472
  defaultSort={customSort}
473
+ title="Default Sort"
460
474
  />
461
475
  </ChartLegendWrapper>
462
476
  );
@@ -531,6 +545,7 @@ export const WithCustomTooltip: Story = {
531
545
  >
532
546
  <Stack direction="vertical" gap="r16">
533
547
  <Barchart
548
+ title="Custom Tooltip"
534
549
  type={{ type: 'category' }}
535
550
  bars={exampleData}
536
551
  tooltip={customTooltip}