@scality/core-ui 0.167.0 → 0.169.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 (45) hide show
  1. package/.github/workflows/github-pages.yml +5 -3
  2. package/.storybook/preview.js +1 -0
  3. package/dist/components/barchartv2/Barchart.component.d.ts +5 -1
  4. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  5. package/dist/components/barchartv2/Barchart.component.js +12 -7
  6. package/dist/components/barchartv2/ChartTooltip.d.ts +9 -13
  7. package/dist/components/barchartv2/ChartTooltip.d.ts.map +1 -1
  8. package/dist/components/barchartv2/ChartTooltip.js +14 -4
  9. package/dist/components/barchartv2/utils.d.ts +9 -2
  10. package/dist/components/barchartv2/utils.d.ts.map +1 -1
  11. package/dist/components/barchartv2/utils.js +14 -18
  12. package/dist/components/buttonv2/Buttonv2.component.d.ts.map +1 -1
  13. package/dist/components/buttonv2/Buttonv2.component.js +27 -6
  14. package/dist/components/date/FormattedDateTime.d.ts +20 -1
  15. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  16. package/dist/components/date/FormattedDateTime.js +36 -0
  17. package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
  18. package/dist/components/linetemporalchart/ChartUtil.js +6 -0
  19. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +7 -1
  20. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  21. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +36 -32
  22. package/dist/components/linetimeseriechart/utils.d.ts +16 -0
  23. package/dist/components/linetimeseriechart/utils.d.ts.map +1 -0
  24. package/dist/components/linetimeseriechart/utils.js +28 -0
  25. package/dist/style/theme.d.ts +2 -2
  26. package/dist/style/theme.d.ts.map +1 -1
  27. package/dist/style/theme.js +26 -0
  28. package/package.json +5 -4
  29. package/src/lib/components/barchartv2/Barchart.component.test.tsx +12 -8
  30. package/src/lib/components/barchartv2/Barchart.component.tsx +29 -9
  31. package/src/lib/components/barchartv2/ChartTooltip.test.tsx +119 -0
  32. package/src/lib/components/barchartv2/ChartTooltip.tsx +49 -19
  33. package/src/lib/components/barchartv2/utils.test.ts +31 -46
  34. package/src/lib/components/barchartv2/utils.ts +24 -31
  35. package/src/lib/components/buttonv2/Buttonv2.component.tsx +27 -6
  36. package/src/lib/components/date/FormattedDateTime.tsx +43 -1
  37. package/src/lib/components/linetemporalchart/ChartUtil.ts +6 -0
  38. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +81 -54
  39. package/src/lib/components/linetimeseriechart/linetimeseriechart.test.tsx +71 -0
  40. package/src/lib/components/linetimeseriechart/utils.test.ts +87 -0
  41. package/src/lib/components/linetimeseriechart/utils.ts +43 -0
  42. package/src/lib/style/theme.ts +26 -0
  43. package/stories/BarChart/barchart.stories.tsx +108 -13
  44. package/stories/color.mdx +12 -0
  45. package/stories/linetimeseriechart.stories.tsx +97 -0
@@ -1,10 +1,17 @@
1
1
  import styled from 'styled-components';
2
2
  import { spacing, Stack, Wrap } from '../../spacing';
3
3
  import { Text } from '../text/Text.component';
4
- import { BarchartBars } from './Barchart.component';
4
+ import {
5
+ BarchartBars,
6
+ BarchartTooltipFn,
7
+ CategoryType,
8
+ TimeType,
9
+ } from './Barchart.component';
5
10
  import { fontSize, fontWeight } from '../../style/theme';
6
11
  import { LegendShape } from '../chartlegend/ChartLegend';
7
12
  import { FormattedDateTime } from '../date/FormattedDateTime';
13
+ import { TooltipContentProps } from 'recharts';
14
+ import { getCurrentPoint } from './utils';
8
15
 
9
16
  export const ChartTooltipContainer = styled.div`
10
17
  background-color: ${({ theme }) => theme.backgroundLevel1};
@@ -30,24 +37,45 @@ export const ChartTooltipItem = styled.div<{ isHovered: boolean }>`
30
37
 
31
38
  export const ChartTooltip = <T extends BarchartBars>({
32
39
  type,
33
- currentPoint,
40
+ tooltipProps,
34
41
  colorSet,
42
+ hoveredValue,
43
+ tooltip,
35
44
  }: {
36
- type: 'time' | 'category';
37
- currentPoint: {
38
- category: string | number;
39
- values: { label: T[number]['label']; value: number; isHovered: boolean }[];
40
- };
41
- colorSet: Record<string, string>;
45
+ type: TimeType | CategoryType;
46
+ tooltipProps: TooltipContentProps<number, string>;
47
+ colorSet?: Record<string, string>;
48
+ hoveredValue: string | undefined;
49
+ tooltip?: BarchartTooltipFn<T>;
42
50
  }) => {
51
+ const { active } = tooltipProps;
52
+
53
+ if (!active) {
54
+ return null;
55
+ }
56
+
57
+ const currentPoint = getCurrentPoint(tooltipProps, hoveredValue);
58
+ if (tooltip) {
59
+ return tooltip(currentPoint);
60
+ }
61
+
43
62
  return (
44
63
  <ChartTooltipContainer>
45
64
  <Text isEmphazed>
46
- {type === 'time' ? (
47
- <FormattedDateTime
48
- format="long-date"
49
- value={new Date(currentPoint.category)}
50
- />
65
+ {type.type === 'time' ? (
66
+ <>
67
+ <FormattedDateTime
68
+ format="long-date"
69
+ value={new Date(currentPoint.category)}
70
+ />{' '}
71
+ {type.type === 'time' &&
72
+ type.timeRange.interval < 24 * 60 * 60 * 1000 && (
73
+ <FormattedDateTime
74
+ format="time"
75
+ value={new Date(currentPoint.category)}
76
+ />
77
+ )}
78
+ </>
51
79
  ) : (
52
80
  currentPoint.category
53
81
  )}
@@ -55,13 +83,15 @@ export const ChartTooltip = <T extends BarchartBars>({
55
83
  <Stack direction="vertical" gap="r8" style={{ width: '100%' }}>
56
84
  {currentPoint.values.map((value) => {
57
85
  return (
58
- <Wrap key={value.label}>
86
+ <Wrap key={value.label} gap={spacing.r32}>
59
87
  <ChartTooltipItem isHovered={value.isHovered}>
60
- <LegendShape
61
- color={colorSet[value.label as keyof typeof colorSet]}
62
- shape="rectangle"
63
- chartColors={colorSet}
64
- />
88
+ {colorSet && (
89
+ <LegendShape
90
+ color={colorSet[value.label as keyof typeof colorSet]}
91
+ shape="rectangle"
92
+ chartColors={colorSet}
93
+ />
94
+ )}
65
95
  {value.label}
66
96
  </ChartTooltipItem>
67
97
  <ChartTooltipItem isHovered={value.isHovered}>
@@ -4,9 +4,9 @@ import {
4
4
  computeUnitLabelAndRoundReferenceValue,
5
5
  filterChartDataAndBarsByLegendSelection,
6
6
  formatPrometheusDataToRechartsDataAndBars,
7
+ getCurrentPoint,
7
8
  getMaxBarValue,
8
9
  getRoundReferenceValue,
9
- renderTooltipContent,
10
10
  sortStackedBars,
11
11
  transformCategoryData,
12
12
  transformTimeData,
@@ -570,7 +570,7 @@ describe('formatPrometheusDataToRechartsDataAndBars', () => {
570
570
 
571
571
  const result = formatPrometheusDataToRechartsDataAndBars(
572
572
  bars,
573
- 'category',
573
+ { type: 'category' },
574
574
  {
575
575
  Success: coreUIAvailableThemes.darkRebrand.statusHealthy,
576
576
  Failed: coreUIAvailableThemes.darkRebrand.statusCritical,
@@ -647,7 +647,7 @@ describe('formatPrometheusDataToRechartsDataAndBars', () => {
647
647
 
648
648
  const result = formatPrometheusDataToRechartsDataAndBars(
649
649
  bars,
650
- 'category',
650
+ { type: 'category' },
651
651
  mockTheme,
652
652
  false,
653
653
  (pointA, pointB) => {
@@ -770,53 +770,38 @@ describe('sortStackedBars', () => {
770
770
  });
771
771
  });
772
772
 
773
- describe('renderTooltipContent', () => {
774
- it('should return null when active is false', () => {
775
- const props = {
776
- active: false,
777
- payload: [],
778
- label: 'test',
779
- coordinate: { x: 0, y: 0 },
780
- accessibilityLayer: false,
781
- };
782
- const result = renderTooltipContent(props, undefined, undefined);
783
- expect(result).toBeNull();
784
- });
785
-
786
- it('should return null when tooltip is undefined', () => {
787
- const props = {
788
- active: true,
789
- payload: [{ name: 'test', value: 10 }],
790
- label: 'test',
791
- coordinate: { x: 0, y: 0 },
792
- accessibilityLayer: false,
793
- };
794
- const result = renderTooltipContent(props, undefined, 'test');
795
- expect(result).toBeNull();
796
- });
797
- it('should call tooltip with the correct props', () => {
798
- const tooltip = jest.fn();
799
- const props = {
800
- active: true,
801
- payload: [
802
- { name: 'Success', value: 10 },
803
- { name: 'Failed', value: 20 },
804
- ],
805
- label: 'Test',
806
- coordinate: { x: 0, y: 0 },
807
- accessibilityLayer: false,
808
- };
809
- renderTooltipContent(props, tooltip, 'Success');
810
- expect(tooltip).toHaveBeenCalledWith({
773
+ describe('getCurrentPoint', () => {
774
+ it('should return the current point', () => {
775
+ const result = getCurrentPoint(
776
+ {
777
+ payload: [{ name: 'Success', value: 10 }],
778
+ label: 'Test',
779
+ coordinate: { x: 10, y: 10 },
780
+ active: true,
781
+ accessibilityLayer: false,
782
+ },
783
+ 'Success',
784
+ );
785
+ expect(result).toEqual({
811
786
  category: 'Test',
812
- values: [
813
- { label: 'Success', value: 10, isHovered: true },
814
- { label: 'Failed', value: 20, isHovered: false },
815
- ],
787
+ values: [{ label: 'Success', value: 10, isHovered: true }],
788
+ });
789
+ const result2 = getCurrentPoint(
790
+ {
791
+ payload: [{ name: 'Success', value: 10 }],
792
+ label: 'Test',
793
+ coordinate: { x: 10, y: 10 },
794
+ active: true,
795
+ accessibilityLayer: false,
796
+ },
797
+ 'Failed',
798
+ );
799
+ expect(result2).toEqual({
800
+ category: 'Test',
801
+ values: [{ label: 'Success', value: 10, isHovered: false }],
816
802
  });
817
803
  });
818
804
  });
819
-
820
805
  describe('filterChartDataAndBarsByLegendSelection', () => {
821
806
  const mockChartData = [
822
807
  { category: 'Jan', Success: 10, Failed: 5, Warning: 3, Pending: 2 },
@@ -271,11 +271,11 @@ export const formatPrometheusDataToRechartsDataAndBars = <
271
271
  );
272
272
 
273
273
  let data =
274
- type !== 'category' && type.type === 'time'
274
+ type.type !== 'category' && type.type === 'time'
275
275
  ? transformTimeData(bars, type, barDataKeys)
276
276
  : transformCategoryData(bars, barDataKeys);
277
277
 
278
- if (type === 'category' && defaultSort) {
278
+ if (type.type === 'category' && defaultSort) {
279
279
  data = applySortingToData(data, barDataKeys, defaultSort);
280
280
  }
281
281
 
@@ -428,35 +428,6 @@ export const sortStackedBars = (
428
428
  return barAverages.map(({ average, ...bar }) => bar);
429
429
  };
430
430
 
431
- export const renderTooltipContent = <T extends BarchartBars>(
432
- props: TooltipContentProps<number, string>,
433
- tooltip: BarchartTooltipFn<T> | undefined,
434
- hoveredValue: string | undefined,
435
- ) => {
436
- const { active, payload, label } = props;
437
-
438
- if (!active || !tooltip) {
439
- return null;
440
- }
441
-
442
- const tooltipValues: {
443
- label: T[number]['label'];
444
- value: number;
445
- isHovered: boolean;
446
- }[] = payload.map((item) => ({
447
- label: item.name,
448
- value: item.value,
449
- isHovered: item.name === hoveredValue,
450
- }));
451
-
452
- const currentPoint = {
453
- category: label as string | number,
454
- values: tooltipValues,
455
- };
456
-
457
- return tooltip(currentPoint);
458
- };
459
-
460
431
  /**
461
432
  * Filters both chart data and recharts bars to only include selected resources from legend
462
433
  * @param data - Array of chart data objects with category and resource values
@@ -538,3 +509,25 @@ export const useChartData = <T extends BarchartBars>(
538
509
  rechartsData,
539
510
  };
540
511
  };
512
+
513
+ export const getCurrentPoint = <T extends BarchartBars>(
514
+ props: TooltipContentProps<number, string>,
515
+ hoveredValue: string | undefined,
516
+ ) => {
517
+ const { payload, label } = props;
518
+
519
+ const tooltipValues: {
520
+ label: T[number]['label'];
521
+ value: number;
522
+ isHovered: boolean;
523
+ }[] = payload.map((item) => ({
524
+ label: item.name,
525
+ value: item.value,
526
+ isHovered: item.name === hoveredValue,
527
+ }));
528
+
529
+ return {
530
+ category: label as string | number,
531
+ values: tooltipValues,
532
+ };
533
+ };
@@ -51,8 +51,10 @@ export const ButtonStyled = styled.button<Props>`
51
51
  switch (props.variant) {
52
52
  case 'primary':
53
53
  return css`
54
- background-color: ${brand.buttonPrimary};
55
- border: ${spacing.r1} solid ${brand.buttonPrimary};
54
+ background: ${brand.buttonPrimary};
55
+ background-clip: padding-box, border-box;
56
+ border: ${spacing.r1} solid transparent;
57
+ border-color: ${brand.buttonPrimary};
56
58
  color: ${brand.textPrimary};
57
59
  &:hover:enabled {
58
60
  cursor: pointer;
@@ -73,8 +75,10 @@ export const ButtonStyled = styled.button<Props>`
73
75
 
74
76
  case 'secondary':
75
77
  return css`
76
- background-color: ${brand.buttonSecondary};
77
- border: ${spacing.r1} solid ${brand.buttonSecondary};
78
+ background: ${brand.buttonSecondary};
79
+ background-clip: padding-box, border-box;
80
+ border: ${spacing.r1} solid transparent;
81
+ border-color: ${brand.buttonSecondary};
78
82
  color: ${brand.textPrimary};
79
83
  &:hover:enabled {
80
84
  cursor: pointer;
@@ -88,7 +92,8 @@ export const ButtonStyled = styled.button<Props>`
88
92
  &:active:enabled {
89
93
  cursor: pointer;
90
94
  color: ${brand.textPrimary};
91
- border: ${spacing.r1} solid ${brand.buttonSecondary};
95
+ border: ${spacing.r1} solid transparent;
96
+ border-color: ${brand.buttonSecondary};
92
97
  }
93
98
  `;
94
99
 
@@ -112,13 +117,18 @@ export const ButtonStyled = styled.button<Props>`
112
117
 
113
118
  case 'outline':
114
119
  return css`
115
- border: ${spacing.r1} solid ${brand.buttonSecondary};
120
+ border: ${spacing.r1} solid transparent;
121
+ border-color: ${brand.buttonSecondary};
116
122
  background-color: transparent;
117
123
  color: ${brand.textPrimary};
118
124
  &:hover:enabled {
119
125
  cursor: pointer;
120
126
  border-color: ${brand.infoPrimary};
121
127
  color: ${brand.textPrimary};
128
+
129
+ &::before {
130
+ background-image: ${brand.buttonPrimary};
131
+ }
122
132
  }
123
133
  &:focus-visible:enabled {
124
134
  ${FocusVisibleStyle}
@@ -129,6 +139,17 @@ export const ButtonStyled = styled.button<Props>`
129
139
  border: ${spacing.r1} solid ${brand.infoSecondary};
130
140
  color: ${brand.textPrimary};
131
141
  }
142
+ &::before {
143
+ content: '';
144
+ position: absolute;
145
+ inset: 0;
146
+ padding: ${spacing.r1};
147
+ background-image: ${brand.buttonSecondary};
148
+ border-radius: inherit;
149
+ mask: linear-gradient(white, white) content-box, linear-gradient(white, white);
150
+ mask-composite: exclude;
151
+ pointer-events: none;
152
+ }
132
153
  `;
133
154
 
134
155
  default:
@@ -8,6 +8,16 @@ export const LONG_DATE_FORMATER = Intl.DateTimeFormat('en-GB', {
8
8
  day: 'numeric',
9
9
  });
10
10
 
11
+ /**
12
+ * @description Long date formatter, without weekday.
13
+ * @example 01 September 2025
14
+ */
15
+ export const LONG_DATE_FORMATER_WITHOUT_WEEKDAY = Intl.DateTimeFormat('en-GB', {
16
+ year: 'numeric',
17
+ month: 'long',
18
+ day: '2-digit',
19
+ });
20
+
11
21
  export const DATE_FORMATER = Intl.DateTimeFormat('fr-CA', {
12
22
  year: 'numeric',
13
23
  month: '2-digit',
@@ -46,6 +56,10 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
46
56
  },
47
57
  );
48
58
 
59
+ /**
60
+ * @description Day month abbreviated hour minute formatter, without second.
61
+ * @example 15 Sept 14:30
62
+ */
49
63
  export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
50
64
  day: 'numeric',
51
65
  month: 'short',
@@ -54,6 +68,25 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
54
68
  hour12: false,
55
69
  });
56
70
 
71
+ /**
72
+ * @description Year month day formatter, without time. Used for describing long term date.
73
+ * @example 2025-01-01
74
+ */
75
+ export const YEAR_MONTH_DAY_FORMATTER = Intl.DateTimeFormat('en-CA', {
76
+ year: 'numeric',
77
+ month: '2-digit',
78
+ day: '2-digit',
79
+ });
80
+
81
+ /**
82
+ * @description Month day formatter, without year. Used for short term date ranges.
83
+ * @example 01-15
84
+ */
85
+ export const MONTH_DAY_FORMATTER = Intl.DateTimeFormat('en-CA', {
86
+ month: '2-digit',
87
+ day: '2-digit',
88
+ });
89
+
57
90
  type FormattedDateTimeProps = {
58
91
  format:
59
92
  | 'date'
@@ -65,7 +98,10 @@ type FormattedDateTimeProps = {
65
98
  | 'day-month-abbreviated-hour-minute'
66
99
  | 'day-month-abbreviated-hour-minute-second'
67
100
  | 'long-date'
68
- | 'chart-date';
101
+ | 'long-date-without-weekday'
102
+ | 'chart-date'
103
+ | 'year-month-day'
104
+ | 'month-day';
69
105
 
70
106
  value: Date;
71
107
  };
@@ -196,8 +232,14 @@ export const FormattedDateTime = ({
196
232
  );
197
233
  case 'long-date':
198
234
  return <>{LONG_DATE_FORMATER.format(value)}</>;
235
+ case 'long-date-without-weekday':
236
+ return <>{LONG_DATE_FORMATER_WITHOUT_WEEKDAY.format(value)}</>;
199
237
  case 'chart-date':
200
238
  return <>{DAY_MONTH_FORMATER.format(value).replace(/[ ,]/g, '')}</>;
239
+ case 'year-month-day':
240
+ return <>{YEAR_MONTH_DAY_FORMATTER.format(value)}</>;
241
+ case 'month-day':
242
+ return <>{MONTH_DAY_FORMATTER.format(value)}</>;
201
243
  default:
202
244
  return <></>;
203
245
  }
@@ -72,6 +72,12 @@ export function getUnitLabel(
72
72
  valueBase: number;
73
73
  unitLabel: string;
74
74
  } {
75
+ if (!unitRange || unitRange.length === 0) {
76
+ return {
77
+ valueBase: 1,
78
+ unitLabel: '',
79
+ };
80
+ }
75
81
  // first sort the unitRange
76
82
  unitRange.sort(
77
83
  (