@scality/core-ui 0.169.0 → 0.171.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 (50) hide show
  1. package/__mocks__/uuid.js +11 -0
  2. package/dist/components/barchartv2/Barchart.component.js +2 -2
  3. package/dist/components/buttonv2/Buttonv2.component.js +1 -1
  4. package/dist/components/chartlegend/ChartLegend.d.ts +3 -1
  5. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
  6. package/dist/components/chartlegend/ChartLegend.js +2 -2
  7. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +3 -1
  8. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
  9. package/dist/components/chartlegend/ChartLegendWrapper.js +43 -9
  10. package/dist/components/date/FormattedDateTime.d.ts +41 -2
  11. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  12. package/dist/components/date/FormattedDateTime.js +55 -8
  13. package/dist/components/date/FormattedDateTime.spec.js +12 -3
  14. package/dist/components/icon/Icon.component.d.ts +2 -0
  15. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  16. package/dist/components/icon/Icon.component.js +2 -0
  17. package/dist/components/layout/v2/index.d.ts +2 -1
  18. package/dist/components/layout/v2/index.d.ts.map +1 -1
  19. package/dist/components/layout/v2/index.js +3 -3
  20. package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
  21. package/dist/components/linetemporalchart/ChartUtil.js +12 -0
  22. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +10 -5
  23. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  24. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +84 -49
  25. package/dist/components/text/Text.component.d.ts +2 -1
  26. package/dist/components/text/Text.component.d.ts.map +1 -1
  27. package/dist/next.d.ts +1 -1
  28. package/dist/next.d.ts.map +1 -1
  29. package/dist/next.js +1 -1
  30. package/dist/style/theme.js +1 -1
  31. package/package.json +3 -1
  32. package/src/lib/components/barchartv2/Barchart.component.tsx +2 -2
  33. package/src/lib/components/barchartv2/ChartTooltip.test.tsx +1 -1
  34. package/src/lib/components/buttonv2/Buttonv2.component.tsx +1 -1
  35. package/src/lib/components/chartlegend/ChartLegend.tsx +4 -2
  36. package/src/lib/components/chartlegend/ChartLegendWrapper.test.tsx +197 -0
  37. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +65 -9
  38. package/src/lib/components/date/FormattedDateTime.spec.tsx +27 -2
  39. package/src/lib/components/date/FormattedDateTime.tsx +61 -11
  40. package/src/lib/components/icon/Icon.component.tsx +2 -0
  41. package/src/lib/components/layout/v2/index.tsx +5 -3
  42. package/src/lib/components/linetemporalchart/ChartUtil.ts +26 -0
  43. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +227 -157
  44. package/src/lib/components/text/Text.component.tsx +8 -1
  45. package/src/lib/next.ts +4 -1
  46. package/src/lib/style/theme.ts +1 -1
  47. package/stories/BarChart/barchart.stories.tsx +7 -1
  48. package/stories/formattedate.stories.tsx +7 -0
  49. package/stories/layout.stories.tsx +19 -0
  50. package/stories/linetimeseriechart.stories.tsx +217 -1
@@ -1,19 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Line, LineChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, } from 'recharts';
3
- import { useCallback, useMemo, useRef } from 'react';
4
- import { useTheme } from 'styled-components';
5
- import { addMissingDataPoint } from '../linetemporalchart/ChartUtil';
6
- import styled from 'styled-components';
2
+ import { CartesianGrid, Line, LineChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
3
+ import { useCallback, useMemo, useRef, useState } from 'react';
4
+ import styled, { useTheme } from 'styled-components';
5
+ import { spacing } from '../../spacing';
7
6
  import { fontSize, fontWeight } from '../../style/theme';
7
+ import { Box } from '../box/Box';
8
8
  import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
9
- import { ChartTitleText, SmallerText } from '../text/Text.component';
10
- import { Loader } from '../loader/Loader.component';
11
- import { spacing } from '../../spacing';
12
- import { getUnitLabel } from '../linetemporalchart/ChartUtil';
9
+ import { FormattedDateTime } from '../date/FormattedDateTime';
13
10
  import { Icon } from '../icon/Icon.component';
11
+ import { addMissingDataPoint, getUnitLabel, } from '../linetemporalchart/ChartUtil';
12
+ import { Loader } from '../loader/Loader.component';
13
+ import { ChartTitleText, SmallerText } from '../text/Text.component';
14
14
  import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
15
- import { FormattedDateTime } from '../date/FormattedDateTime';
16
- import { Box } from '../box/Box';
17
15
  import { formatXAxisLabel } from './utils';
18
16
  const LineTemporalChartWrapper = styled.div `
19
17
  display: flex;
@@ -25,7 +23,7 @@ const ChartHeader = styled.div `
25
23
  display: flex;
26
24
  align-items: center;
27
25
  `;
28
- const TooltipContainer = styled.div `
26
+ export const TooltipContainer = styled.div `
29
27
  background-color: ${(props) => props.theme.backgroundLevel1};
30
28
  padding: ${spacing.r8};
31
29
  border: 1px solid ${(props) => props.theme.border};
@@ -33,7 +31,7 @@ const TooltipContainer = styled.div `
33
31
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
34
32
  max-width: 250px;
35
33
  `;
36
- const TooltipTime = styled.div `
34
+ export const TooltipTime = styled.div `
37
35
  margin-bottom: ${spacing.r8};
38
36
  color: ${(props) => props.theme.textPrimary};
39
37
  font-size: ${fontSize.smaller};
@@ -73,9 +71,13 @@ const TooltipInstanceValue = styled.div `
73
71
  flex-shrink: 0;
74
72
  text-align: right;
75
73
  `;
76
- const CustomTooltip = ({ active, payload, label, unitLabel, timeFormat, }) => {
77
- if (!active || !payload || !payload.length || !label)
74
+ const CustomTooltip = ({ unitLabel, timeFormat, isChartActive, tooltipProps, renderTooltip, }) => {
75
+ const { active, payload, label, ...rest } = tooltipProps;
76
+ if (!active || !payload || !payload.length || !label || !isChartActive)
78
77
  return null;
78
+ if (renderTooltip) {
79
+ return renderTooltip(tooltipProps, unitLabel, timeFormat);
80
+ }
79
81
  // We can't use the default itemSorter method because it's a custom tooltip.
80
82
  // Sort the payload here instead
81
83
  const sortedPayload = [...payload].sort((a, b) => {
@@ -98,23 +100,42 @@ const CustomTooltip = ({ active, payload, label, unitLabel, timeFormat, }) => {
98
100
  const isSymmetricalSeries = (series) => {
99
101
  return 'above' in series && 'below' in series;
100
102
  };
101
- export function LineTimeSerieChart({ series, title, height, startingTimeStamp, interval, duration, unitRange, isLoading = false, timeFormat = 'date-time', yAxisType = 'default', yAxisTitle, helpText, ...rest }) {
103
+ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, interval, duration, unitRange, isLoading = false, timeFormat = 'date-time', yAxisType = 'default', yAxisTitle, helpText, syncId, renderTooltip, ...rest }) {
102
104
  const theme = useTheme();
103
- const { getColor } = useChartLegend();
105
+ const { getColor, selectedResources } = useChartLegend();
104
106
  const chartRef = useRef(null);
107
+ const [isChartActive, setIsChartActive] = useState(false);
105
108
  const chartData = useMemo(() => {
109
+ // Guard against empty/undefined series data
110
+ if (!series || (Array.isArray(series) && series.length === 0)) {
111
+ return [];
112
+ }
113
+ // Handle symmetrical series with empty above/below arrays
114
+ if (isSymmetricalSeries(series)) {
115
+ if ((!series.above || series.above.length === 0) &&
116
+ (!series.below || series.below.length === 0)) {
117
+ return [];
118
+ }
119
+ }
106
120
  // 1. Add missing data points
107
121
  const normalizedSeries = yAxisType === 'symmetrical' && isSymmetricalSeries(series)
108
122
  ? {
109
- above: series.above.map((line) => ({
110
- ...line,
111
- data: addMissingDataPoint(line.data, startingTimeStamp, duration, interval),
112
- })),
123
+ above: series.above
124
+ ? series.above.map((line) => ({
125
+ ...line,
126
+ data: addMissingDataPoint(line.data, startingTimeStamp, duration, interval),
127
+ }))
128
+ : [],
113
129
  // Convert positive values to negative values
114
- below: series.below.map((line) => ({
115
- ...line,
116
- data: addMissingDataPoint(line.data, startingTimeStamp, duration, interval).map(([timestamp, value]) => [timestamp, value === null ? null : `-${Number(value)}`]),
117
- })),
130
+ below: series.below
131
+ ? series.below.map((line) => ({
132
+ ...line,
133
+ data: addMissingDataPoint(line.data, startingTimeStamp, duration, interval).map(([timestamp, value]) => [
134
+ timestamp,
135
+ value === null ? null : `-${Number(value)}`,
136
+ ]),
137
+ }))
138
+ : [],
118
139
  }
119
140
  : series.map((line) => ({
120
141
  ...line,
@@ -177,6 +198,14 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
177
198
  return !isNaN(num) && num !== null ? num : null;
178
199
  })
179
200
  .filter((value) => value !== null));
201
+ // Guard against empty values array
202
+ if (values.length === 0) {
203
+ return {
204
+ topValue: 100, // Default value for empty charts
205
+ unitLabel: '',
206
+ rechartsData: [],
207
+ };
208
+ }
180
209
  const top = Math.abs(Math.max(...values));
181
210
  const bottom = Math.abs(Math.min(...values));
182
211
  const maxValue = Math.max(top, bottom);
@@ -196,11 +225,17 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
196
225
  // Group series by resource and create color mapping
197
226
  const { colorMapping, groupedSeries } = useMemo(() => {
198
227
  const mapping = {};
228
+ // Guard against empty/undefined series
229
+ if (!series) {
230
+ return { colorMapping: mapping, groupedSeries: {} };
231
+ }
199
232
  const allSeries = isSymmetricalSeries(series)
200
- ? [...series.above, ...series.below]
233
+ ? [...(series.above || []), ...(series.below || [])]
201
234
  : series;
202
235
  // Group series by resource
203
- const groups = allSeries.reduce((acc, serie) => {
236
+ const groups = allSeries
237
+ .filter((serie) => selectedResources.includes(serie.resource))
238
+ .reduce((acc, serie) => {
204
239
  const key = serie.resource;
205
240
  if (!acc[key]) {
206
241
  acc[key] = [];
@@ -222,30 +257,30 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
222
257
  colorMapping: mapping,
223
258
  groupedSeries: groups,
224
259
  };
225
- }, [series, getColor]);
260
+ }, [series, getColor, selectedResources]);
226
261
  // Format time for display the tick in the x axis
227
262
  const formatXAxisLabelCallback = useCallback((timestamp) => formatXAxisLabel(timestamp, timeFormat, chartData), [timeFormat, chartData]);
228
- 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(SmallerText, { children: helpText }), children: _jsx(Icon, { name: "Info", color: theme.buttonSecondary }) }) })), isLoading && _jsx(Loader, {})] }), _jsx(ResponsiveContainer, { 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}`, 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: {
229
- fill: theme.textSecondary,
230
- fontSize: fontSize.smaller,
231
- }, axisLine: { stroke: theme.border } }), _jsx(YAxis, { orientation: "right", allowDataOverflow: false, label: {
232
- value: yAxisTitle,
233
- angle: 90,
234
- position: 'insideRight',
235
- style: {
236
- textAnchor: 'middle',
263
+ 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(SmallerText, { 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(ResponsiveContainer, { 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), 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: {
264
+ fill: theme.textSecondary,
265
+ fontSize: fontSize.smaller,
266
+ }, axisLine: { stroke: theme.border } }), _jsx(YAxis, { orientation: "right", allowDataOverflow: false, label: {
267
+ value: yAxisTitle,
268
+ angle: 90,
269
+ position: 'insideRight',
270
+ style: {
271
+ textAnchor: 'middle',
272
+ fill: theme.textSecondary,
273
+ fontSize: fontSize.smaller,
274
+ },
275
+ }, domain: yAxisType === 'percentage'
276
+ ? [0, 100]
277
+ : yAxisType === 'symmetrical'
278
+ ? [-topValue, topValue]
279
+ : [0, topValue], axisLine: { stroke: theme.border }, tick: {
237
280
  fill: theme.textSecondary,
238
281
  fontSize: fontSize.smaller,
239
- },
240
- }, domain: yAxisType === 'percentage'
241
- ? [0, 100]
242
- : yAxisType === 'symmetrical'
243
- ? [-topValue, topValue]
244
- : [0, topValue], axisLine: { stroke: theme.border }, tick: {
245
- fill: theme.textSecondary,
246
- fontSize: fontSize.smaller,
247
- }, tickFormatter: (value) => Math.round(value).toString(), tickCount: 5, interval: 'preserveStartEnd' }), _jsx(Tooltip, { content: _jsx(CustomTooltip, { unitLabel: unitLabel, timeFormat: timeFormat }) }), yAxisType === 'symmetrical' && (_jsx(ReferenceLine, { y: 0, stroke: theme.border })), Object.entries(groupedSeries).map(([resource, resourceSeries]) => resourceSeries.map((serie, serieIndex) => {
248
- const label = serie.getTooltipLabel(serie.metricPrefix, serie.resource);
249
- return (_jsx(Line, { type: "monotone", dataKey: label, stroke: colorMapping[resource], dot: false, isAnimationActive: false }, `${title}-${resource}-${serieIndex}`));
250
- }))] }) })] }));
282
+ }, tickFormatter: (value) => Math.round(value).toString(), tickCount: 5, interval: 'preserveStartEnd' }), _jsx(Tooltip, { content: (props) => (_jsx(CustomTooltip, { unitLabel: unitLabel, timeFormat: timeFormat, renderTooltip: renderTooltip, tooltipProps: props, isChartActive: isChartActive })) }), yAxisType === 'symmetrical' && (_jsx(ReferenceLine, { y: 0, stroke: theme.border })), Object.entries(groupedSeries).map(([resource, resourceSeries]) => resourceSeries.map((serie, serieIndex) => {
283
+ const label = serie.getTooltipLabel(serie.metricPrefix, serie.resource);
284
+ return (_jsx(Line, { type: "monotone", dataKey: label, stroke: colorMapping[resource], dot: false, isAnimationActive: false }, `${title}-${resource}-${serieIndex}`));
285
+ }))] }) }) })] }));
251
286
  }
@@ -1,4 +1,5 @@
1
1
  import { CoreUITheme } from '../../style/theme';
2
+ export type TextVariant = 'ChartTitle' | 'Basic' | 'Smaller' | 'Larger' | 'Large';
2
3
  type Status = 'unknown' | 'healthy' | 'warning' | 'critical';
3
4
  type Props = {
4
5
  children: React.ReactNode | string;
@@ -22,7 +23,7 @@ export declare const GentleEmphaseSecondaryText: import("styled-components").Sty
22
23
  alignRight?: boolean;
23
24
  }, never>;
24
25
  export declare const Text: import("styled-components").StyledComponent<"span", import("styled-components").DefaultTheme, {
25
- variant?: "ChartTitle" | "Basic" | "Smaller" | "Larger" | "Large";
26
+ variant?: TextVariant;
26
27
  color?: keyof CoreUITheme;
27
28
  isEmphazed?: boolean;
28
29
  isGentleEmphazed?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Text.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/text/Text.component.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,KAAK,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAC7D,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAsDF,eAAO,MAAM,uBAAuB;iBACrB,MAAM;SAIpB,CAAC;AAIF,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAErD;AACD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEzD;AACD,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEtD;AACD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEvD;AACD,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAO9D;AACD,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAErD;AACD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEvD;AACD,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAIhE;AACD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAOtE;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAE1D;AACD,eAAO,MAAM,0BAA0B;iBACxB,OAAO;SAUrB,CAAC;AAEF,eAAO,MAAM,IAAI;cACL,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO;YACzD,MAAM,WAAW;iBACZ,OAAO;uBACD,OAAO;SA2C3B,CAAC;AAEF,eAAO,MAAM,IAAI,uGAchB,CAAC"}
1
+ {"version":3,"file":"Text.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/text/Text.component.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,OAAO,GACP,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;AAEZ,KAAK,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAC7D,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAsDF,eAAO,MAAM,uBAAuB;iBACrB,MAAM;SAIpB,CAAC;AAIF,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAErD;AACD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEzD;AACD,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEtD;AACD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEvD;AACD,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAO9D;AACD,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAErD;AACD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAEvD;AACD,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAIhE;AACD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAOtE;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,2CAE1D;AACD,eAAO,MAAM,0BAA0B;iBACxB,OAAO;SAUrB,CAAC;AAEF,eAAO,MAAM,IAAI;cACL,WAAW;YACb,MAAM,WAAW;iBACZ,OAAO;uBACD,OAAO;SA2C3B,CAAC;AAEF,eAAO,MAAM,IAAI,uGAchB,CAAC"}
package/dist/next.d.ts CHANGED
@@ -15,7 +15,7 @@ export { Input } from './components/inputv2/inputv2';
15
15
  export { Accordion } from './components/accordion/Accordion.component';
16
16
  export { Barchart, BarchartSortFn, BarchartTooltipFn, } from './components/barchartv2/Barchart.component';
17
17
  export { ChartTooltip } from './components/barchartv2/ChartTooltip';
18
- export { ChartLegendWrapper } from './components/chartlegend/ChartLegendWrapper';
18
+ export { ChartLegendWrapper, useChartId, } from './components/chartlegend/ChartLegendWrapper';
19
19
  export { ChartLegend } from './components/chartlegend/ChartLegend';
20
20
  export { LineTimeSerieChart } from './components/linetimeseriechart/linetimeseriechart.component';
21
21
  export { CoreUITheme } from './style/theme';
@@ -1 +1 @@
1
- {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../src/lib/next.ts"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAC3F,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACL,QAAQ,EACR,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8DAA8D,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../src/lib/next.ts"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAC3F,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACL,QAAQ,EACR,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EACL,kBAAkB,EAClB,UAAU,GACX,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8DAA8D,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
package/dist/next.js CHANGED
@@ -15,6 +15,6 @@ export { Input } from './components/inputv2/inputv2';
15
15
  export { Accordion } from './components/accordion/Accordion.component';
16
16
  export { Barchart, } from './components/barchartv2/Barchart.component';
17
17
  export { ChartTooltip } from './components/barchartv2/ChartTooltip';
18
- export { ChartLegendWrapper } from './components/chartlegend/ChartLegendWrapper';
18
+ export { ChartLegendWrapper, useChartId, } from './components/chartlegend/ChartLegendWrapper';
19
19
  export { ChartLegend } from './components/chartlegend/ChartLegend';
20
20
  export { LineTimeSerieChart } from './components/linetimeseriechart/linetimeseriechart.component';
@@ -121,7 +121,7 @@ export const coreUIAvailableThemes = {
121
121
  selectedActive: '#037AFF',
122
122
  highlight: '#1A3C75',
123
123
  border: '#4A4A4A',
124
- buttonPrimary: 'linear-gradient(130deg, #9355E7 0%, #2E4AA3 100%)',
124
+ buttonPrimary: 'linear-gradient(130deg, #9355E7 0%, #2E4AA3 60%)',
125
125
  buttonSecondary: 'linear-gradient(130deg, #595A78 0%, #44455F 100%)',
126
126
  buttonDelete: '#3D0808',
127
127
  infoPrimary: '#8E8EAC',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/core-ui",
3
- "version": "0.169.0",
3
+ "version": "0.171.0",
4
4
  "description": "Scality common React component library",
5
5
  "author": "Scality Engineering",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -61,6 +61,7 @@
61
61
  "@types/react-window": "^1.8.5",
62
62
  "@types/styled-components": "^5.1.25",
63
63
  "@types/styled-system": "^5.1.15",
64
+ "@types/uuid": "^10.0.0",
64
65
  "@typescript-eslint/eslint-plugin": "^6.12.0",
65
66
  "@typescript-eslint/parser": "^6.12.0",
66
67
  "copyfiles": "^2.4.1",
@@ -109,6 +110,7 @@
109
110
  "recharts": "^3.0.2",
110
111
  "styled-components": "^5.2.1",
111
112
  "styled-system": "^5.1.5",
113
+ "uuid": "^13.0.0",
112
114
  "vega": "^5.17.3",
113
115
  "vega-embed": "6.0.0",
114
116
  "vega-lite": "5.0.0",
@@ -24,13 +24,13 @@ import { ChartTooltip } from './ChartTooltip';
24
24
  import { UnitRange, useChartData } from './utils';
25
25
 
26
26
  const CHART_CONSTANTS = {
27
- TICK_WIDTH_OFFSET: 5,
27
+ TICK_WIDTH_OFFSET: 4,
28
28
  BAR_SIZE: 12,
29
29
  MIN_POINT_SIZE: 3,
30
30
  DEFAULT_HEIGHT: 200,
31
31
  CHART_MARGIN: {
32
32
  left: 0,
33
- right: 0,
33
+ right: -10,
34
34
  top: 0,
35
35
  bottom: 0,
36
36
  },
@@ -27,7 +27,7 @@ describe('ChartTooltip', () => {
27
27
  successValue: () => screen.queryByText(SUCCESS_VALUE),
28
28
  failed: () => screen.queryByText(/Failed/),
29
29
  failedValue: () => screen.queryByText(FAILED_VALUE),
30
- date: () => screen.queryByText(/Monday, 1 July 2024/),
30
+ date: () => screen.queryByText(/Monday, 01 July 2024/),
31
31
  time: () => screen.queryByText(/00:00/),
32
32
  };
33
33
  it('should render the ChartTooltip component', () => {
@@ -118,6 +118,7 @@ export const ButtonStyled = styled.button<Props>`
118
118
  case 'outline':
119
119
  return css`
120
120
  border: ${spacing.r1} solid transparent;
121
+ border-color: ${brand.border}; // fallback for linear-gradient button themes
121
122
  border-color: ${brand.buttonSecondary};
122
123
  background-color: transparent;
123
124
  color: ${brand.textPrimary};
@@ -144,7 +145,6 @@ export const ButtonStyled = styled.button<Props>`
144
145
  position: absolute;
145
146
  inset: 0;
146
147
  padding: ${spacing.r1};
147
- background-image: ${brand.buttonSecondary};
148
148
  border-radius: inherit;
149
149
  mask: linear-gradient(white, white) content-box, linear-gradient(white, white);
150
150
  mask-composite: exclude;
@@ -1,6 +1,6 @@
1
1
  import styled from 'styled-components';
2
2
  import { useChartLegend } from './ChartLegendWrapper';
3
- import { Text } from '../text/Text.component';
3
+ import { Text, TextVariant } from '../text/Text.component';
4
4
  import { chartColors } from '../../style/theme';
5
5
  import { useCallback } from 'react';
6
6
 
@@ -8,6 +8,7 @@ type ChartLegendProps = {
8
8
  shape: 'line' | 'rectangle';
9
9
  disabled?: boolean;
10
10
  direction?: 'horizontal' | 'vertical';
11
+ legendSize?: TextVariant;
11
12
  };
12
13
 
13
14
  const Legend = styled.div<{ direction: 'horizontal' | 'vertical' }>`
@@ -65,6 +66,7 @@ export const ChartLegend = ({
65
66
  shape,
66
67
  disabled = false,
67
68
  direction = 'horizontal',
69
+ legendSize = 'Basic',
68
70
  }: ChartLegendProps) => {
69
71
  const {
70
72
  listResources,
@@ -132,7 +134,7 @@ export const ChartLegend = ({
132
134
  shape={shape}
133
135
  chartColors={chartColors}
134
136
  />
135
- <Text variant="Basic">{resource}</Text>
137
+ <Text variant={legendSize}>{resource}</Text>
136
138
  </LegendItem>
137
139
  );
138
140
  })}
@@ -0,0 +1,197 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import React, { useEffect } from 'react';
3
+ import {
4
+ ChartLegendWrapper,
5
+ useChartId,
6
+ useChartLegend,
7
+ } from './ChartLegendWrapper';
8
+ import { ChartLegend } from './ChartLegend';
9
+ import userEvent from '@testing-library/user-event';
10
+
11
+ describe('ChartLegendWrapper', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ const TestChart = ({ seriesNames }: { seriesNames: string[] }) => {
17
+ const chartId = useChartId();
18
+ const { register } = useChartLegend();
19
+
20
+ useEffect(() => {
21
+ register(chartId, seriesNames);
22
+ }, [chartId, register, seriesNames]);
23
+
24
+ return <div data-testid={`chart-${chartId}`}>Test Chart</div>;
25
+ };
26
+
27
+ const generateColors = (seriesNames: string[]) => {
28
+ const colors: Record<string, string> = {};
29
+ const colorPalette = ['red', 'blue', 'green', 'yellow', 'purple'];
30
+ seriesNames.forEach((name, index) => {
31
+ colors[name] = colorPalette[index % colorPalette.length];
32
+ });
33
+ return colors;
34
+ };
35
+
36
+ describe('Dynamic Color Generation', () => {
37
+ it('should generate colors dynamically based on registered series', () => {
38
+ render(
39
+ <ChartLegendWrapper colorSet={generateColors}>
40
+ <TestChart seriesNames={['CPU', 'Memory']} />
41
+ <ChartLegend shape="line" />
42
+ </ChartLegendWrapper>,
43
+ );
44
+
45
+ expect(screen.getByText('CPU')).toBeInTheDocument();
46
+ expect(screen.getByText('Memory')).toBeInTheDocument();
47
+ expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
48
+ expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
49
+ });
50
+
51
+ it('should handle multiple charts with overlapping series', () => {
52
+ const TestChart1 = () => {
53
+ const chartId = useChartId();
54
+ const { register } = useChartLegend();
55
+
56
+ useEffect(() => {
57
+ register(chartId, ['CPU', 'Memory']);
58
+ }, [chartId, register]);
59
+
60
+ return <div data-testid={`chart1-${chartId}`}>Test Chart 1</div>;
61
+ };
62
+
63
+ const TestChart2 = () => {
64
+ const chartId = useChartId();
65
+ const { register } = useChartLegend();
66
+
67
+ useEffect(() => {
68
+ register(chartId, ['CPU', 'Disk']);
69
+ }, [chartId, register]);
70
+
71
+ return <div data-testid={`chart2-${chartId}`}>Test Chart 2</div>;
72
+ };
73
+
74
+ render(
75
+ <ChartLegendWrapper colorSet={generateColors}>
76
+ <TestChart1 />
77
+ <TestChart2 />
78
+ <ChartLegend shape="line" />
79
+ </ChartLegendWrapper>,
80
+ );
81
+
82
+ // Should show unique series from both charts
83
+ expect(screen.getByText('CPU')).toBeInTheDocument();
84
+ expect(screen.getByText('Memory')).toBeInTheDocument();
85
+ expect(screen.getByText('Disk')).toBeInTheDocument();
86
+
87
+ // All should be selected by default
88
+ expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
89
+ expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
90
+ expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
91
+ });
92
+
93
+ it('should handle empty series registration', () => {
94
+ render(
95
+ <ChartLegendWrapper colorSet={generateColors}>
96
+ <TestChart seriesNames={[]} />
97
+ <ChartLegend shape="line" />
98
+ </ChartLegendWrapper>,
99
+ );
100
+
101
+ // Should not crash and should render empty legend
102
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
103
+ });
104
+
105
+ it('should maintain selection state when new series are added', () => {
106
+ const { rerender } = render(
107
+ <ChartLegendWrapper colorSet={generateColors}>
108
+ <TestChart seriesNames={['CPU']} />
109
+ <ChartLegend shape="line" />
110
+ </ChartLegendWrapper>,
111
+ );
112
+
113
+ // Initially only CPU
114
+ expect(screen.getByText('CPU')).toBeInTheDocument();
115
+ expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
116
+
117
+ // Select only CPU
118
+ userEvent.click(screen.getByText('CPU'));
119
+ expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
120
+
121
+ // Add more series
122
+ rerender(
123
+ <ChartLegendWrapper colorSet={generateColors}>
124
+ <TestChart seriesNames={['CPU', 'Memory']} />
125
+ <ChartLegend shape="line" />
126
+ </ChartLegendWrapper>,
127
+ );
128
+
129
+ // New series should be added and all should be selected (reset behavior)
130
+ expect(screen.getByText('CPU')).toBeInTheDocument();
131
+ expect(screen.getByText('Memory')).toBeInTheDocument();
132
+ expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
133
+ expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
134
+ });
135
+
136
+ it('should work with different chart configurations', () => {
137
+ render(
138
+ <ChartLegendWrapper colorSet={generateColors}>
139
+ <TestChart seriesNames={['Series1', 'Series2', 'Series3']} />
140
+ <ChartLegend shape="rectangle" direction="vertical" />
141
+ </ChartLegendWrapper>,
142
+ );
143
+
144
+ expect(screen.getByText('Series1')).toBeInTheDocument();
145
+ expect(screen.getByText('Series2')).toBeInTheDocument();
146
+ expect(screen.getByText('Series3')).toBeInTheDocument();
147
+ });
148
+ });
149
+
150
+ describe('Static Color Set', () => {
151
+ const staticColorSet = {
152
+ CPU: 'red',
153
+ Memory: 'blue',
154
+ Disk: 'green',
155
+ };
156
+
157
+ it('should work with static color sets', () => {
158
+ render(
159
+ <ChartLegendWrapper colorSet={staticColorSet}>
160
+ <ChartLegend shape="line" />
161
+ </ChartLegendWrapper>,
162
+ );
163
+
164
+ expect(screen.getByText('CPU')).toBeInTheDocument();
165
+ expect(screen.getByText('Memory')).toBeInTheDocument();
166
+ expect(screen.getByText('Disk')).toBeInTheDocument();
167
+ });
168
+
169
+ it('should ignore registration when using static color sets', () => {
170
+ render(
171
+ <ChartLegendWrapper colorSet={staticColorSet}>
172
+ <TestChart seriesNames={['DifferentSeries']} />
173
+ <ChartLegend shape="line" />
174
+ </ChartLegendWrapper>,
175
+ );
176
+
177
+ // Should only show static color set items, not registered series
178
+ expect(screen.getByText('CPU')).toBeInTheDocument();
179
+ expect(screen.getByText('Memory')).toBeInTheDocument();
180
+ expect(screen.getByText('Disk')).toBeInTheDocument();
181
+ expect(screen.queryByText('DifferentSeries')).not.toBeInTheDocument();
182
+ });
183
+ });
184
+
185
+ describe('Error Handling', () => {
186
+ it('should throw error when useChartLegend is used outside wrapper', () => {
187
+ const TestComponent = () => {
188
+ useChartLegend();
189
+ return <div>Test</div>;
190
+ };
191
+
192
+ expect(() => render(<TestComponent />)).toThrow(
193
+ 'useChartLegend must be used within a ChartLegendWrapper',
194
+ );
195
+ });
196
+ });
197
+ });