@scality/core-ui 0.162.0 → 0.164.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 (73) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts +9 -3
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +22 -5
  4. package/dist/components/barchartv2/utils.d.ts +26 -3
  5. package/dist/components/barchartv2/utils.d.ts.map +1 -1
  6. package/dist/components/barchartv2/utils.js +76 -22
  7. package/dist/components/chartlegend/ChartLegend.d.ts +8 -0
  8. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -0
  9. package/dist/components/chartlegend/ChartLegend.js +65 -0
  10. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +17 -0
  11. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -0
  12. package/dist/components/chartlegend/ChartLegendWrapper.js +50 -0
  13. package/dist/components/date/FormattedDateTime.d.ts +3 -1
  14. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  15. package/dist/components/date/FormattedDateTime.js +19 -1
  16. package/dist/components/date/FormattedDateTime.spec.js +12 -0
  17. package/dist/components/icon/Icon.component.d.ts +5 -5
  18. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  19. package/dist/components/icon/Icon.component.js +33 -31
  20. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +33 -0
  21. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -0
  22. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +249 -0
  23. package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
  24. package/dist/components/selectv2/Selectv2.component.js +11 -6
  25. package/dist/components/steppers/Stepper.component.d.ts.map +1 -1
  26. package/dist/components/steppers/Stepper.component.js +9 -8
  27. package/dist/components/toast/ToastProvider.d.ts.map +1 -1
  28. package/dist/components/toast/ToastProvider.js +4 -5
  29. package/dist/components/vegachartv2/SyncedCursorCharts.d.ts.map +1 -1
  30. package/dist/components/vegachartv2/SyncedCursorCharts.js +3 -5
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/next.d.ts +2 -0
  35. package/dist/next.d.ts.map +1 -1
  36. package/dist/next.js +2 -0
  37. package/dist/style/theme.d.ts +1 -0
  38. package/dist/style/theme.d.ts.map +1 -1
  39. package/dist/style/theme.js +28 -0
  40. package/package.json +2 -2
  41. package/src/lib/components/accordion/Accordion.test.tsx +8 -16
  42. package/src/lib/components/barchartv2/Barchart.component.test.tsx +117 -111
  43. package/src/lib/components/barchartv2/Barchart.component.tsx +54 -7
  44. package/src/lib/components/barchartv2/utils.test.ts +127 -2
  45. package/src/lib/components/barchartv2/utils.ts +103 -19
  46. package/src/lib/components/chartlegend/ChartLegend.tsx +113 -0
  47. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +85 -0
  48. package/src/lib/components/date/FormattedDateTime.spec.tsx +24 -0
  49. package/src/lib/components/date/FormattedDateTime.tsx +36 -2
  50. package/src/lib/components/healthselectorv2/HealthSelector.component.test.tsx +10 -10
  51. package/src/lib/components/icon/Icon.component.tsx +48 -60
  52. package/src/lib/components/infomessage/InfoMessageUtils.test.tsx +0 -1
  53. package/src/lib/components/inlineinput/InlineInput.test.tsx +28 -22
  54. package/src/lib/components/inputlist/InputList.test.tsx +22 -21
  55. package/src/lib/components/linetemporalchart/ChartUtil.test.ts +5 -4
  56. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +502 -0
  57. package/src/lib/components/searchinput/SearchInput.test.tsx +3 -7
  58. package/src/lib/components/selectv2/Selectv2.component.tsx +13 -5
  59. package/src/lib/components/selectv2/selectv2.test.tsx +70 -61
  60. package/src/lib/components/steppers/Stepper.component.tsx +10 -8
  61. package/src/lib/components/tablev2/TableSync.test.tsx +8 -12
  62. package/src/lib/components/tablev2/TableUtils.test.ts +6 -3
  63. package/src/lib/components/tablev2/Tablev2.test.tsx +38 -40
  64. package/src/lib/components/toast/ToastProvider.tsx +14 -6
  65. package/src/lib/components/toggle/Toggle.test.tsx +1 -1
  66. package/src/lib/components/vegachartv2/SyncedCursorCharts.tsx +5 -7
  67. package/src/lib/index.ts +1 -0
  68. package/src/lib/next.ts +2 -0
  69. package/src/lib/style/theme.ts +29 -0
  70. package/stories/BarChart/barchart.stories.tsx +387 -129
  71. package/stories/format.mdx +4 -2
  72. package/stories/linetimeseriechart.stories.tsx +485 -0
  73. package/tsconfig.json +0 -1
@@ -7,12 +7,14 @@ import {
7
7
  useEffect,
8
8
  useState,
9
9
  } from 'react';
10
- import { useQuery } from 'react-query';
11
10
  import styled, { css } from 'styled-components';
12
11
  import { CoreUITheme } from '../../style/theme';
13
12
  import { Loader } from '../loader/Loader.component';
14
13
  import { RemoteGroup, RemoteUser } from './CustomsIcons';
15
14
 
15
+ // Module-level cache for imported icons
16
+ const iconCache: Record<string, any> = {};
17
+
16
18
  export const iconTable = {
17
19
  Account: 'fas faWallet',
18
20
  Backend: 'fas faNetworkWired',
@@ -141,10 +143,10 @@ export const iconTable = {
141
143
  };
142
144
 
143
145
  export const customIcons = {
144
- 'Remote-user': ({ ariaLabel, color, size }) => (
146
+ 'Remote-user': ({ 'aria-label': ariaLabel, color, size }) => (
145
147
  <RemoteUser ariaLabel={ariaLabel} color={color} size={size} />
146
148
  ),
147
- 'Remote-group': ({ ariaLabel, color, size }) => (
149
+ 'Remote-group': ({ 'aria-label': ariaLabel, color, size }) => (
148
150
  <RemoteGroup ariaLabel={ariaLabel} color={color} size={size} />
149
151
  ),
150
152
  };
@@ -169,7 +171,7 @@ type Props = {
169
171
  ariaLabel?: string;
170
172
  withWrapper?: boolean;
171
173
  style?: CSSProperties;
172
- onClick?: (event: MouseEvent) => void;
174
+ onClick?: (event: React.MouseEvent) => void;
173
175
  title?: string;
174
176
  };
175
177
 
@@ -226,7 +228,7 @@ export const IconWrapper = styled.div<{ size: SizeProp }>`
226
228
  function NonWrappedIcon({
227
229
  name,
228
230
  size = '1x',
229
- color = undefined,
231
+ color,
230
232
  ariaLabel = '',
231
233
  title,
232
234
  ...rest
@@ -234,63 +236,49 @@ function NonWrappedIcon({
234
236
  const iconInfo = iconTable[name] || customIcons[name];
235
237
  if (!iconInfo) throw new Error(`${name}: is not a valid icon.`);
236
238
 
237
- const { data, status } = useQuery({
238
- queryKey: ['icon', name],
239
- queryFn: async () => {
240
- if (customIcons[name]) {
241
- return {
242
- default: customIcons[name],
243
- };
244
- }
245
- const [iconType, iconClass] = iconInfo.split(' ');
246
- try {
247
- const fontAwesomeType =
248
- iconType === 'far'
249
- ? 'free-regular-svg-icons'
250
- : 'free-solid-svg-icons';
251
- const icon = await import(
252
- `@fortawesome/${fontAwesomeType}/${iconClass}.js`
253
- );
254
- return {
255
- default: ({ name, color, size, ariaLabel, ...rest }) => (
256
- <IconStyled
257
- color={color}
258
- icon={icon[iconClass]}
259
- size={size}
260
- aria-label={`${name} ${ariaLabel}`}
261
- {...rest}
262
- />
263
- ),
264
- };
265
- } catch {
266
- return {
267
- default: ({ name, ariaLabel }) => (
268
- <Loader size="base" aria-label={`${name} ${ariaLabel}`} />
269
- ),
270
- };
271
- }
272
- },
273
- });
239
+ // Loaded fortawesome icon if not a custom icon
240
+ const [icon, setIcon] = useState();
274
241
 
275
- return (
276
- <>
277
- {(status === 'loading' || status === 'error') && (
278
- <DelayedFallback aria-label={`${name} ${ariaLabel}`}>
279
- <Loader size="base" />
280
- </DelayedFallback>
281
- )}
242
+ useEffect(() => {
243
+ if (customIcons[name]) {
244
+ return;
245
+ }
246
+
247
+ const [iconType, iconClass] = iconInfo.split(' ');
248
+ const fontAwesomeType = iconType === 'far' ? 'free-regular-svg-icons' : 'free-solid-svg-icons';
249
+ const cacheKey = `${fontAwesomeType}/${iconClass}`;
250
+ if (iconCache[cacheKey]) {
251
+ setIcon(iconCache[cacheKey]);
252
+ return () => setIcon(undefined);
253
+ }
282
254
 
283
- {status === 'success' && (
284
- <data.default
285
- name={name}
286
- color={color}
287
- size={size}
288
- ariaLabel={ariaLabel}
289
- title={title}
290
- {...rest}
291
- />
292
- )}
293
- </>
255
+ // Handle FontAwesome icons with dynamic import
256
+ import(`@fortawesome/${fontAwesomeType}/${iconClass}.js`)
257
+ .then((module) => {
258
+ setIcon(module[iconClass]);
259
+ iconCache[cacheKey] = module[iconClass];
260
+ });
261
+ return () => setIcon(undefined);
262
+ }, [name, iconInfo]);
263
+
264
+ if (!icon && !customIcons[name]) {
265
+ return (
266
+ <DelayedFallback aria-label={`${name} ${ariaLabel}`}>
267
+ <Loader size="base" />
268
+ </DelayedFallback>
269
+ );
270
+ }
271
+
272
+ const IconComponent = customIcons[name] ?? IconStyled;
273
+ return (
274
+ <IconComponent
275
+ color={color}
276
+ icon={icon}
277
+ size={size}
278
+ title={title}
279
+ aria-label={`${name} ${ariaLabel}`}
280
+ {...rest}
281
+ />
294
282
  );
295
283
  }
296
284
 
@@ -1,6 +1,5 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import { render } from '@testing-library/react';
3
- import React from 'react';
4
3
  import { coreUIAvailableThemes } from '../../style/theme';
5
4
  import { CoreUiThemeProvider } from '../coreuithemeprovider/CoreUiThemeProvider';
6
5
  import { useComputeBackgroundColor } from './InfoMessageUtils';
@@ -1,4 +1,4 @@
1
- import React, { PropsWithChildren } from 'react';
1
+ import { PropsWithChildren } from 'react';
2
2
  import {
3
3
  QueryClient,
4
4
  QueryClientProvider,
@@ -7,10 +7,10 @@ import {
7
7
  } from 'react-query';
8
8
  import { ToastProvider } from '../toast/ToastProvider';
9
9
  import {
10
+ act,
10
11
  render,
11
12
  screen,
12
13
  waitFor,
13
- waitForElementToBeRemoved,
14
14
  within,
15
15
  } from '@testing-library/react';
16
16
  import userEvent from '@testing-library/user-event';
@@ -68,17 +68,19 @@ describe('InlineInput', () => {
68
68
  </ChangeMutationProvider>,
69
69
  { wrapper: Wrapper },
70
70
  );
71
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
71
72
 
72
73
  //E
73
74
  /// First focus the edit button
74
75
  await userEvent.tab();
75
76
  /// Then press enter to edit the input
76
- await userEvent.keyboard('{enter}');
77
+ await act(() => userEvent.keyboard('{enter}'));
77
78
  /// Then type a new value
78
- await userEvent.type(document.activeElement, 'new value');
79
+ await act(() => userEvent.type(document.activeElement!, 'new value'));
79
80
  /// Then press enter to confirm the new value
80
- await userEvent.keyboard('{enter}');
81
- await waitForElementToBeRemoved(() => screen.getByRole('textbox'));
81
+ await act(() => userEvent.keyboard('{enter}'));
82
+ await waitFor(() => screen.findByText('testnew value'));
83
+ expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
82
84
 
83
85
  //V
84
86
  expect(mock).toHaveBeenCalledWith('testnew value');
@@ -105,24 +107,26 @@ describe('InlineInput', () => {
105
107
  </ChangeMutationProvider>,
106
108
  { wrapper: Wrapper },
107
109
  );
110
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
108
111
 
109
112
  //E
110
113
  /// First focus the edit button
111
114
  await userEvent.tab();
112
115
  /// Then press enter to edit the input
113
- await userEvent.keyboard('{enter}');
116
+ await act(() => userEvent.keyboard('{enter}'));
114
117
  /// Then type a new value
115
- await userEvent.type(document.activeElement, 'new value');
118
+ await act(() => userEvent.type(document.activeElement!, 'new value'));
116
119
  /// Then press enter to confirm the new value
117
- await userEvent.keyboard('{enter}');
120
+
121
+ await act(() => userEvent.keyboard('{enter}'));
122
+ await waitFor(() => screen.findByRole('dialog', { name: /Confirm/i }));
118
123
  /// Expect the confirmation modal to be opened
119
- await waitFor(() =>
120
- expect(selectors.confirmationModal()).toBeInTheDocument(),
121
- );
124
+ expect(selectors.confirmationModal()).toBeInTheDocument()
122
125
  /// Click the confirm button
123
- await userEvent.click(screen.getByRole('button', { name: /confirm/i }));
124
- /// Wait for modal to be closed
125
- await waitForElementToBeRemoved(() => selectors.confirmationModal());
126
+ await act(() => userEvent.click(screen.getByRole('button', { name: /confirm/i })));
127
+
128
+ /// modal should be closed
129
+ expect(screen.queryByRole('dialog', { name: /Confirm/i })).not.toBeInTheDocument();
126
130
 
127
131
  //V
128
132
  expect(mock).toHaveBeenCalledWith('testnew value');
@@ -147,16 +151,17 @@ describe('InlineInput', () => {
147
151
  </ChangeMutationProvider>,
148
152
  { wrapper: Wrapper },
149
153
  );
154
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
150
155
 
151
156
  //E
152
157
  /// First focus the edit button
153
158
  await userEvent.tab();
154
159
  /// Then press enter to edit the input
155
- await userEvent.keyboard('{enter}');
160
+ await act(() => userEvent.keyboard('{enter}'));
156
161
  /// Then type a new value
157
- await userEvent.type(document.activeElement, 'new value');
162
+ await act(() => userEvent.type(document.activeElement!, 'new value'));
158
163
  /// Then press escape to cancel the new value
159
- await userEvent.keyboard('{esc}');
164
+ await act(() => userEvent.keyboard('{esc}'));
160
165
 
161
166
  //V
162
167
  expect(mock).not.toHaveBeenCalled();
@@ -181,16 +186,17 @@ describe('InlineInput', () => {
181
186
  </ChangeMutationProvider>,
182
187
  { wrapper: Wrapper },
183
188
  );
189
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
184
190
 
185
191
  //E
186
192
  /// First focus the edit button
187
193
  await userEvent.tab();
188
194
  /// Then press enter to edit the input
189
- await userEvent.keyboard('{enter}');
195
+ await act(() => userEvent.keyboard('{enter}'));
190
196
  /// Then type a new value
191
- await userEvent.type(document.activeElement, 'new value');
197
+ await act(() => userEvent.type(document.activeElement!, 'new value'));
192
198
  /// Then press enter to confirm the new value
193
- await userEvent.keyboard('{enter}');
199
+ await act(() => userEvent.keyboard('{enter}'));
194
200
  /// Expect the confirmation modal to be opened
195
201
  await waitFor(() =>
196
202
  expect(selectors.confirmationModal()).toBeInTheDocument(),
@@ -205,7 +211,7 @@ describe('InlineInput', () => {
205
211
  //V
206
212
  expect(mock).not.toHaveBeenCalled();
207
213
  expect(screen.getByRole('textbox')).toBeInTheDocument();
208
- expect(screen.getByRole('textbox').value).toBe('testnew value');
214
+ expect((screen.getByRole('textbox') as HTMLInputElement).value).toBe('testnew value');
209
215
  });
210
216
  });
211
217
  });
@@ -1,24 +1,20 @@
1
- import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
1
+ import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
3
2
  import { InputList, InputListProps } from './InputList.component';
4
3
  import { FormSection } from '../form/Form.component';
5
- import { QueryClient, QueryClientProvider } from 'react-query';
6
4
 
7
5
  describe('InputList', () => {
8
6
  const onChangeMock = jest.fn();
9
7
 
10
- const renderInputList = (props: InputListProps<string[]>) => {
8
+ const renderInputList = (props: InputListProps<string>) => {
11
9
  render(
12
- <QueryClientProvider client={new QueryClient()}>
13
- <FormSection>
14
- <InputList
15
- placeholder="Input list Test"
16
- onChange={onChangeMock}
17
- value={props.value}
18
- name="inputListTest"
19
- />
20
- </FormSection>
21
- </QueryClientProvider>,
10
+ <FormSection>
11
+ <InputList
12
+ placeholder="Input list Test"
13
+ onChange={onChangeMock}
14
+ value={props.value}
15
+ name="inputListTest"
16
+ />
17
+ </FormSection>
22
18
  );
23
19
  };
24
20
 
@@ -26,20 +22,22 @@ describe('InputList', () => {
26
22
  onChangeMock.mockClear();
27
23
  });
28
24
 
29
- it('should render an empty input list', () => {
25
+ it('should render an empty input list', async () => {
30
26
  renderInputList({
31
27
  value: [''],
32
28
  });
29
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
33
30
 
34
31
  expect(screen.getByLabelText('inputListTest0')).toHaveValue('');
35
32
  });
36
33
 
37
- it('should render an input list with initial values', () => {
34
+ it('should render an input list with initial values', async () => {
38
35
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
39
36
 
40
37
  renderInputList({
41
38
  value: initialValues,
42
39
  });
40
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
43
41
 
44
42
  const inputElements = screen.getAllByRole('textbox');
45
43
 
@@ -50,44 +48,47 @@ describe('InputList', () => {
50
48
  });
51
49
  });
52
50
 
53
- it('should add a new input when clicking the add button', () => {
51
+ it('should add a new input when clicking the add button', async () => {
54
52
  const initialValues = ['Value 1', 'Value 2'];
55
53
 
56
54
  renderInputList({
57
55
  value: initialValues,
58
56
  });
57
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
59
58
 
60
59
  const addButton = screen.getByLabelText('Add1');
61
60
 
62
- fireEvent.click(addButton);
61
+ await act(() => fireEvent.click(addButton));
63
62
 
64
63
  expect(onChangeMock).toHaveBeenCalledWith({
65
64
  target: { value: [...initialValues, ''] },
66
65
  });
67
66
  });
68
67
 
69
- it('should delete an input when clicking the delete button', () => {
68
+ it('should delete an input when clicking the delete button', async () => {
70
69
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
71
70
 
72
71
  renderInputList({
73
72
  value: initialValues,
74
73
  });
74
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
75
75
 
76
76
  const deleteButton = screen.getByLabelText('Remove1');
77
77
 
78
- fireEvent.click(deleteButton);
78
+ await act(() => fireEvent.click(deleteButton));
79
79
 
80
80
  expect(onChangeMock).toHaveBeenCalledWith({
81
81
  target: { value: ['Value 1', 'Value 3'] },
82
82
  });
83
83
  });
84
84
 
85
- it('should update the value of an input', () => {
85
+ it('should update the value of an input', async () => {
86
86
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
87
87
 
88
88
  renderInputList({
89
89
  value: initialValues,
90
90
  });
91
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
91
92
 
92
93
  const inputElements = screen.getAllByRole('textbox');
93
94
 
@@ -3,7 +3,8 @@ import {
3
3
  getUnitLabel,
4
4
  addMissingDataPoint,
5
5
  } from './ChartUtil';
6
- const series = [
6
+ import { Serie } from './LineTemporalChart.component';
7
+ const series: Serie[] = [
7
8
  {
8
9
  resource: 'node1',
9
10
  data: [
@@ -11,7 +12,7 @@ const series = [
11
12
  [1627460952, '18.73333333333335'],
12
13
  ],
13
14
  getTooltipLabel: (metricPrefix, resource) => {
14
- return resource;
15
+ return `${resource}`;
15
16
  },
16
17
  isLineDashed: false,
17
18
  },
@@ -22,12 +23,12 @@ const series = [
22
23
  [1627460952, null],
23
24
  ],
24
25
  getTooltipLabel: (metricPrefix, resource) => {
25
- return resource;
26
+ return `${resource}`;
26
27
  },
27
28
  isLineDashed: false,
28
29
  },
29
30
  ];
30
- const seriesSymmetrical = [
31
+ const seriesSymmetrical: Serie[] = [
31
32
  {
32
33
  metricPrefix: 'read',
33
34
  resource: 'node1',