@centreon/ui 25.3.4 → 25.4.0-MON-191119-npm-develop.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 (176) hide show
  1. package/package.json +25 -11
  2. package/public/mockServiceWorker.js +8 -31
  3. package/src/Button/Save/index.stories.tsx +1 -0
  4. package/src/Checkbox/Checkbox.tsx +3 -1
  5. package/src/Checkbox/CheckboxGroup/index.tsx +6 -1
  6. package/src/Colors/index.tsx +1 -1
  7. package/src/Dashboard/Dashboard.styles.ts +1 -1
  8. package/src/Dashboard/Layout.tsx +1 -1
  9. package/src/Dialog/UnsavedChanges/index.stories.tsx +1 -0
  10. package/src/Form/CollapsibleGroup.tsx +13 -13
  11. package/src/Form/Form.cypress.spec.tsx +137 -2
  12. package/src/Form/Form.stories.tsx +11 -31
  13. package/src/Form/Form.tsx +2 -0
  14. package/src/Form/Inputs/Checkbox.tsx +3 -2
  15. package/src/Form/Inputs/ConnectedAutocomplete.tsx +6 -1
  16. package/src/Form/Inputs/Grid.tsx +18 -29
  17. package/src/Form/Inputs/SubGroupDivider.tsx +7 -0
  18. package/src/Form/Inputs/Text.tsx +1 -0
  19. package/src/Form/Inputs/index.tsx +31 -24
  20. package/src/Form/Inputs/models.ts +8 -1
  21. package/src/Form/Section/FormSection.tsx +34 -0
  22. package/src/Form/Section/PanelTabs.tsx +13 -0
  23. package/src/Form/Section/navigateToSection.ts +9 -0
  24. package/src/Form/storiesData.tsx +14 -4
  25. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +46 -6
  26. package/src/Graph/BarChart/BarChart.stories.tsx +60 -0
  27. package/src/Graph/BarChart/BarChart.tsx +56 -32
  28. package/src/Graph/BarChart/BarGroup.tsx +22 -32
  29. package/src/Graph/BarChart/MemoizedGroup.tsx +8 -11
  30. package/src/Graph/BarChart/ResponsiveBarChart.tsx +145 -32
  31. package/src/Graph/BarChart/Tooltip/BarChartTooltip.tsx +2 -2
  32. package/src/Graph/Chart/BasicComponents/Lines/StackedLines/index.tsx +7 -1
  33. package/src/Graph/Chart/BasicComponents/Lines/StackedLines/useStackedLines.ts +18 -45
  34. package/src/Graph/Chart/BasicComponents/Lines/index.tsx +42 -28
  35. package/src/Graph/Chart/Chart.cypress.spec.tsx +85 -15
  36. package/src/Graph/Chart/Chart.stories.tsx +84 -1
  37. package/src/Graph/Chart/Chart.tsx +17 -4
  38. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/RegularAnchorPoint.tsx +8 -2
  39. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/StackedAnchorPoint.tsx +10 -3
  40. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -2
  41. package/src/Graph/Chart/InteractiveComponents/GraphValueTooltip/useGraphValueTooltip.ts +2 -4
  42. package/src/Graph/Chart/InteractiveComponents/ZoomPreview/index.tsx +14 -3
  43. package/src/Graph/Chart/InteractiveComponents/ZoomPreview/models.ts +3 -0
  44. package/src/Graph/Chart/InteractiveComponents/ZoomPreview/useZoomPreview.ts +12 -10
  45. package/src/Graph/Chart/InteractiveComponents/index.tsx +63 -5
  46. package/src/Graph/Chart/Legend/index.tsx +26 -2
  47. package/src/Graph/Chart/index.tsx +45 -45
  48. package/src/Graph/Chart/models.ts +8 -0
  49. package/src/Graph/Chart/useChartData.ts +14 -2
  50. package/src/Graph/Gauge/Gauge.tsx +18 -14
  51. package/src/Graph/Gauge/ResponsiveGauge.tsx +10 -6
  52. package/src/Graph/Gauge/useResizeObserver.ts +68 -0
  53. package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +18 -16
  54. package/src/Graph/SingleBar/ThresholdLine.tsx +4 -4
  55. package/src/Graph/SingleBar/models.ts +1 -0
  56. package/src/Graph/Text/Text.styles.ts +2 -2
  57. package/src/Graph/Text/Text.tsx +23 -10
  58. package/src/Graph/Timeline/ResponsiveTimeline.tsx +4 -0
  59. package/src/Graph/Timeline/Timeline.tsx +21 -4
  60. package/src/Graph/Tree/Links.tsx +2 -2
  61. package/src/Graph/Tree/Tree.tsx +2 -2
  62. package/src/Graph/Tree/constants.ts +1 -1
  63. package/src/Graph/common/BaseChart/BaseChart.tsx +6 -1
  64. package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +5 -4
  65. package/src/Graph/common/BaseChart/Header/index.tsx +3 -1
  66. package/src/Graph/common/BaseChart/useComputeBaseChartDimensions.ts +13 -9
  67. package/src/Graph/common/timeSeries/index.test.ts +20 -0
  68. package/src/Graph/common/timeSeries/index.ts +225 -44
  69. package/src/Graph/common/timeSeries/models.ts +6 -2
  70. package/src/Graph/common/utils.ts +45 -12
  71. package/src/Graph/index.ts +3 -1
  72. package/src/Graph/mockedData/dataWithMissingPoint.json +74 -0
  73. package/src/Graph/mockedData/pingServiceWithStackedKeys.json +205 -0
  74. package/src/Icon/RegexIcon.tsx +20 -0
  75. package/src/Icon/index.ts +1 -0
  76. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +68 -14
  77. package/src/InputField/Select/Autocomplete/Connected/index.tsx +49 -14
  78. package/src/InputField/Select/Autocomplete/Multi/Listbox.tsx +78 -0
  79. package/src/InputField/Select/Autocomplete/Multi/Multi.styles.ts +26 -0
  80. package/src/InputField/Select/Autocomplete/Multi/Multi.tsx +124 -0
  81. package/src/InputField/Select/Autocomplete/Multi/index.tsx +1 -117
  82. package/src/InputField/Select/Autocomplete/index.tsx +28 -17
  83. package/src/InputField/Select/Option.tsx +3 -3
  84. package/src/InputField/Select/index.tsx +4 -0
  85. package/src/InputField/Text/index.tsx +4 -2
  86. package/src/InputField/translatedLabels.ts +4 -0
  87. package/src/Listing/ActionBar/Pagination.tsx +10 -23
  88. package/src/Listing/ActionBar/PaginationActions.tsx +1 -10
  89. package/src/Listing/ActionBar/index.tsx +1 -1
  90. package/src/Listing/Cell/DataCell.tsx +6 -6
  91. package/src/Listing/Cell/EllipsisTypography.tsx +10 -32
  92. package/src/Listing/Cell/index.tsx +37 -76
  93. package/src/Listing/Checkbox.tsx +8 -20
  94. package/src/Listing/Header/Cell/ListingHeaderCell.tsx +17 -14
  95. package/src/Listing/Header/Cell/SelectActionListingHeaderCell.tsx +5 -9
  96. package/src/Listing/Header/ListingHeader.tsx +2 -5
  97. package/src/Listing/Header/_internals/Label.tsx +1 -17
  98. package/src/Listing/Row/EmptyRow.tsx +2 -6
  99. package/src/Listing/Row/Row.tsx +7 -36
  100. package/src/Listing/index.stories.tsx +1 -0
  101. package/src/Listing/index.tsx +26 -26
  102. package/src/Listing/useStyleTable.ts +58 -32
  103. package/src/ListingPage/index.stories.tsx +1 -0
  104. package/src/Module/index.tsx +8 -2
  105. package/src/MultiSelectEntries/index.stories.tsx +1 -0
  106. package/src/MultiSelectEntries/index.tsx +1 -1
  107. package/src/Pagination/Pagination.cypress.spec.tsx +137 -0
  108. package/src/Pagination/Pagination.stories.tsx +46 -0
  109. package/src/Pagination/Pagination.styles.ts +56 -0
  110. package/src/Pagination/Pagination.tsx +146 -0
  111. package/src/Pagination/index.ts +3 -0
  112. package/src/Pagination/utils.ts +7 -0
  113. package/src/SortableItems/index.stories.tsx +2 -2
  114. package/src/StoryBookThemeProvider/index.tsx +3 -1
  115. package/src/ThemeProvider/base.css +49 -0
  116. package/src/ThemeProvider/index.tsx +21 -47
  117. package/src/ThemeProvider/palettes.ts +3 -1
  118. package/src/ThemeProvider/tailwindcss.css +230 -0
  119. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +9 -11
  120. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/models.ts +1 -0
  121. package/src/TimePeriods/DateTimePickerInput.tsx +3 -1
  122. package/src/api/models.ts +9 -0
  123. package/src/api/useGraphQuery/index.ts +108 -12
  124. package/src/components/Avatar/Avatar.stories.tsx +1 -0
  125. package/src/components/Button/Button.module.css +38 -0
  126. package/src/components/Button/Button.stories.tsx +25 -0
  127. package/src/components/Button/Button.tsx +2 -5
  128. package/src/components/CrudPage/CrudPage.stories.tsx +1 -0
  129. package/src/components/CrudPage/CrudPageRoot.tsx +1 -1
  130. package/src/components/DataTable/DataTable.stories.tsx +1 -0
  131. package/src/components/DataTable/EmptyState/DataTableEmptyState.stories.tsx +1 -0
  132. package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +3 -1
  133. package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +4 -1
  134. package/src/components/DataTable/Item/DataTableItem.stories.tsx +1 -0
  135. package/src/components/Form/AccessRights/AccessRights.stories.tsx +1 -0
  136. package/src/components/Form/AccessRights/ShareInput/ShareInput.tsx +4 -3
  137. package/src/components/Form/AccessRights/ShareInput/useShareInput.tsx +15 -10
  138. package/src/components/Form/FormActions.tsx +21 -12
  139. package/src/components/Layout/AreaIndicator.tsx +4 -6
  140. package/src/components/Layout/PageLayout/PageLayout.stories.tsx +1 -0
  141. package/src/components/Layout/PageLayout/PageLayout.tsx +9 -3
  142. package/src/components/Layout/PageLayout/PageLayoutActions.tsx +5 -3
  143. package/src/components/Layout/PageLayout/PageLayoutBody.tsx +5 -3
  144. package/src/components/Layout/PageLayout/PageLayoutHeader.tsx +5 -3
  145. package/src/components/Layout/PageLayout/PageQuickAccess.tsx +17 -17
  146. package/src/components/Menu/Button/MenuButton.tsx +6 -6
  147. package/src/components/Menu/MenuDivider.tsx +1 -5
  148. package/src/components/Menu/MenuItem.tsx +1 -5
  149. package/src/components/Menu/MenuItems.tsx +5 -4
  150. package/src/components/Modal/ConfirmationModal/ConfirmationModal.stories.tsx +1 -0
  151. package/src/components/Modal/ConfirmationModal/ConfirmationModal.tsx +4 -1
  152. package/src/components/Modal/Modal.stories.tsx +21 -0
  153. package/src/components/Modal/Modal.styles.ts +1 -19
  154. package/src/components/Modal/Modal.tsx +1 -1
  155. package/src/components/Modal/ModalBody.tsx +6 -4
  156. package/src/components/Modal/ModalHeader.tsx +9 -5
  157. package/src/components/Modal/modal.module.css +16 -0
  158. package/src/components/Tabs/Tab.styles.ts +0 -6
  159. package/src/components/Tabs/Tabs.tsx +37 -15
  160. package/src/index.ts +3 -0
  161. package/src/queryParameters/url/index.ts +7 -2
  162. package/src/utils/index.ts +1 -0
  163. package/src/utils/useLocale/index.ts +9 -0
  164. package/src/utils/useLocale/useLocale.cypress.spec.tsx +38 -0
  165. package/src/utils/useLocaleDateTimeFormat/index.ts +4 -2
  166. package/src/utils/usePluralizedTranslation.ts +2 -3
  167. package/src/Listing/Cell/DataCell.styles.ts +0 -27
  168. package/src/Listing/Header/Cell/ListingHeaderCell.styles.ts +0 -71
  169. package/src/Listing/Header/Cell/SelectActionListingHeaderCell.styles.ts +0 -26
  170. package/src/Listing/Header/ListingHeader.styles.ts +0 -16
  171. package/src/Listing/Listing.styles.ts +0 -78
  172. package/src/Listing/Row/EmptyRow.styles.ts +0 -14
  173. package/src/components/Button/Button.styles.ts +0 -44
  174. package/src/components/Layout/AreaIndicator.styles.ts +0 -33
  175. package/src/components/Menu/Button/MenuButton.styles.ts +0 -27
  176. package/src/components/Menu/Menu.styles.ts +0 -68
@@ -1,7 +1,7 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useMemo } from 'react';
2
2
 
3
3
  import { useAtomValue, useSetAtom } from 'jotai';
4
- import { equals, isNil, pick, type } from 'ramda';
4
+ import { equals, isEmpty, isNil, pick, type, update } from 'ramda';
5
5
  import { CSSObject } from 'tss-react';
6
6
 
7
7
  import { Theme } from '@mui/material';
@@ -12,14 +12,11 @@ import { Column, TableStyleAtom as Style } from './models';
12
12
  import { tableStyleAtom, tableStyleDerivedAtom } from './tableAtoms';
13
13
 
14
14
  interface TableStyle {
15
- checkable?: boolean;
16
- currentVisibleColumns?: Array<Column>;
17
15
  listingVariant?: ListingVariant;
18
16
  }
19
17
 
20
18
  interface TableStyleState {
21
19
  dataStyle: Style;
22
- getGridTemplateColumn: string;
23
20
  }
24
21
 
25
22
  const isCompactMode = equals<ListingVariant | undefined>(
@@ -40,35 +37,11 @@ export const getTextStyleByViewMode = ({
40
37
  theme.typography[isCompactMode(listingVariant) ? 'body2' : 'body1']
41
38
  );
42
39
 
43
- const useStyleTable = ({
44
- checkable,
45
- currentVisibleColumns,
46
- listingVariant
47
- }: TableStyle): TableStyleState => {
40
+ const useStyleTable = ({ listingVariant }: TableStyle): TableStyleState => {
48
41
  const dataStyle = useAtomValue(tableStyleAtom);
49
42
 
50
43
  const updateStyleTable = useSetAtom(tableStyleDerivedAtom);
51
44
 
52
- const getGridTemplateColumn = (): string => {
53
- const checkbox = checkable ? 'fit-content(1rem) ' : ''; // SelectAction (checkbox) cell adjusts to content
54
-
55
- const columnTemplate = currentVisibleColumns
56
- ?.filter((column) => column)
57
- ?.map(({ width, shortLabel }) => {
58
- if (!isNil(shortLabel)) {
59
- return 'min-content';
60
- }
61
- if (isNil(width)) {
62
- return 'auto';
63
- }
64
-
65
- return equals(type(width), 'Number') ? `${width}px` : width;
66
- })
67
- .join(' ');
68
-
69
- return `${checkbox}${columnTemplate}`;
70
- };
71
-
72
45
  useEffect(() => {
73
46
  if (listingVariant) {
74
47
  updateStyleTable({ listingVariant });
@@ -76,9 +49,62 @@ const useStyleTable = ({
76
49
  }, [listingVariant]);
77
50
 
78
51
  return {
79
- dataStyle,
80
- getGridTemplateColumn: getGridTemplateColumn()
52
+ dataStyle
81
53
  };
82
54
  };
83
55
 
84
56
  export default useStyleTable;
57
+
58
+ interface UseColumnStyleProps {
59
+ checkable?: boolean;
60
+ currentVisibleColumns?: Array<Column>;
61
+ }
62
+
63
+ export const useColumnStyle = ({
64
+ checkable,
65
+ currentVisibleColumns
66
+ }: UseColumnStyleProps): string => {
67
+ const gridTemplateColumn = useMemo((): string => {
68
+ const checkbox = checkable ? 'fit-content(1rem) ' : ''; // SelectAction (checkbox) cell adjusts to content
69
+
70
+ const columnTemplate: Array<string> =
71
+ currentVisibleColumns
72
+ ?.filter((column) => column)
73
+ ?.map(({ width, shortLabel }) => {
74
+ if (!isNil(shortLabel)) {
75
+ return 'min-content';
76
+ }
77
+ if (isNil(width)) {
78
+ return 'auto';
79
+ }
80
+
81
+ return (
82
+ equals(type(width), 'Number') ? `${width}px` : width
83
+ ) as string;
84
+ }) || [];
85
+
86
+ const hasOnlyContainerResponsiveColumns =
87
+ !isEmpty(columnTemplate) &&
88
+ columnTemplate.every(
89
+ (width: string) =>
90
+ width.includes('auto') ||
91
+ width.includes('fr') ||
92
+ width.includes('%') ||
93
+ width.includes('px')
94
+ );
95
+
96
+ if (!hasOnlyContainerResponsiveColumns) {
97
+ const fixedColumnTemplate = update(
98
+ columnTemplate.length - 1,
99
+ 'auto',
100
+ columnTemplate
101
+ );
102
+
103
+ return `${checkbox}${fixedColumnTemplate.join(' ')}`;
104
+ }
105
+
106
+ return `${checkbox}${columnTemplate.join(' ')}`;
107
+ }, [checkable, currentVisibleColumns]);
108
+
109
+ return gridTemplateColumn;
110
+ };
@@ -15,6 +15,7 @@ import Panel from '../Panel';
15
15
  import Filter from './Filter';
16
16
 
17
17
  import ListingPage, { ListingPageProps } from '.';
18
+ import '../ThemeProvider/tailwindcss.css';
18
19
 
19
20
  export default { title: 'Listing Page' };
20
21
 
@@ -3,6 +3,7 @@ import { Provider as JotaiProvider, createStore } from 'jotai';
3
3
 
4
4
  import { StylesProvider, createGenerateClassName } from '@mui/styles';
5
5
 
6
+ import { ThemeOptions } from '@mui/material';
6
7
  import { QueryProvider, ThemeProvider } from '..';
7
8
  import SnackbarProvider from '../Snackbar/SnackbarProvider';
8
9
 
@@ -12,6 +13,10 @@ export interface ModuleProps {
12
13
  queryClient?: QueryClient;
13
14
  seedName: string;
14
15
  store: ReturnType<typeof createStore>;
16
+ overrideTheme?: {
17
+ light: Partial<ThemeOptions>;
18
+ dark: Partial<ThemeOptions>;
19
+ };
15
20
  }
16
21
 
17
22
  const Module = ({
@@ -19,7 +24,8 @@ const Module = ({
19
24
  seedName,
20
25
  maxSnackbars = 3,
21
26
  store,
22
- queryClient
27
+ queryClient,
28
+ overrideTheme
23
29
  }: ModuleProps): JSX.Element => {
24
30
  const generateClassName = createGenerateClassName({
25
31
  seed: seedName
@@ -29,7 +35,7 @@ const Module = ({
29
35
  <QueryProvider queryClient={queryClient}>
30
36
  <JotaiProvider store={store}>
31
37
  <StylesProvider generateClassName={generateClassName}>
32
- <ThemeProvider>
38
+ <ThemeProvider overrideTheme={overrideTheme}>
33
39
  <SnackbarProvider maxSnackbars={maxSnackbars}>
34
40
  {children}
35
41
  </SnackbarProvider>
@@ -1,6 +1,7 @@
1
1
  import { ComponentMeta, ComponentStory } from '@storybook/react';
2
2
 
3
3
  import MultiSelectEntries from '.';
4
+ import '../ThemeProvider/tailwindcss.css';
4
5
 
5
6
  export default {
6
7
  argTypes: {
@@ -63,7 +63,7 @@ const EntryChip = ({
63
63
  const { classes } = useStyles();
64
64
 
65
65
  return (
66
- <Grid item xs={gridWidth}>
66
+ <Grid item size={gridWidth}>
67
67
  <Chip
68
68
  className={classes.chip}
69
69
  label={<div className={classes.labelChip}>{label}</div>}
@@ -0,0 +1,137 @@
1
+ import { labelNextPage, labelPreviousPage } from '../Listing/translatedLabels';
2
+ import TestQueryProvider from '../api/TestQueryProvider';
3
+ import { Method } from '../api/useMutationQuery';
4
+ import Pagination from './Pagination';
5
+ import { generateItems } from './utils';
6
+
7
+ const defaultTotalItems = 25;
8
+ const itemsPerPage = 6;
9
+ const totalPages = Math.ceil(defaultTotalItems / itemsPerPage);
10
+
11
+ const initialize = ({
12
+ total = defaultTotalItems,
13
+ currentPage = 1
14
+ }: { total?: number; currentPage?: number }) => {
15
+ cy.interceptAPIRequest({
16
+ alias: 'list',
17
+ method: Method.GET,
18
+ path: '**/listing**',
19
+ response: {
20
+ result: generateItems(itemsPerPage),
21
+ meta: {
22
+ page: currentPage,
23
+ total,
24
+ limit: itemsPerPage
25
+ }
26
+ }
27
+ });
28
+
29
+ cy.mount({
30
+ Component: (
31
+ <div
32
+ style={{
33
+ width: '100%',
34
+ height: '100vh',
35
+ display: 'flex',
36
+ justifyContent: 'center',
37
+ alignItems: 'center'
38
+ }}
39
+ >
40
+ <div
41
+ style={{
42
+ height: '176px',
43
+ boxShadow: '2px 2px 4px rgba(0, 0, 0, 0.2)'
44
+ }}
45
+ >
46
+ <TestQueryProvider>
47
+ <Pagination
48
+ api={{ baseEndpoint: '/test/listing', queryKey: ['test'] }}
49
+ />
50
+ </TestQueryProvider>
51
+ </div>
52
+ </div>
53
+ )
54
+ });
55
+ };
56
+
57
+ describe('Pagination Component', () => {
58
+ it('render with correct initial state', () => {
59
+ initialize({});
60
+ cy.waitForRequest('@list');
61
+
62
+ cy.findByTestId(labelPreviousPage).should('be.disabled');
63
+
64
+ cy.findByTestId(labelNextPage).should('not.be.disabled');
65
+
66
+ cy.contains(`Page 1/${totalPages}`);
67
+
68
+ cy.makeSnapshot();
69
+ });
70
+
71
+ it('hides pagination controls when only one page exists', () => {
72
+ initialize({ total: itemsPerPage });
73
+ cy.waitForRequest('@list');
74
+
75
+ cy.findByTestId(labelPreviousPage).should('not.exist');
76
+ cy.findByTestId(labelNextPage).should('not.exist');
77
+ cy.contains(/Page \d+\/\d+/).should('not.exist');
78
+
79
+ cy.makeSnapshot();
80
+ });
81
+
82
+ it('navigates forward through pages correctly', () => {
83
+ initialize({});
84
+ cy.waitForRequest('@list');
85
+
86
+ cy.contains(`Page 1/${totalPages}`);
87
+
88
+ Array.from({ length: totalPages - 1 }).forEach((_, index) => {
89
+ cy.findByTestId(labelNextPage).click();
90
+ cy.waitForRequest('@list');
91
+
92
+ cy.contains(`Page ${index + 2}/${totalPages}`);
93
+ });
94
+
95
+ cy.findByTestId(labelNextPage).should('be.disabled');
96
+
97
+ cy.makeSnapshot();
98
+ });
99
+
100
+ it('navigates backward through pages correctly', () => {
101
+ initialize({});
102
+
103
+ cy.waitForRequest('@list');
104
+
105
+ Array.from({ length: totalPages - 1 }).forEach(() => {
106
+ cy.findByTestId(labelNextPage).click();
107
+
108
+ cy.waitForRequest('@list');
109
+ });
110
+
111
+ Array.from({ length: totalPages - 1 }).forEach((_, index) => {
112
+ cy.findByTestId(labelPreviousPage).click();
113
+ cy.waitForRequest('@list');
114
+
115
+ cy.contains(`Page ${totalPages - index - 1}/${totalPages}`);
116
+ });
117
+
118
+ cy.findByTestId(labelPreviousPage).should('be.disabled');
119
+
120
+ cy.makeSnapshot();
121
+ });
122
+
123
+ it('enables both buttons when on middle page', () => {
124
+ initialize({});
125
+
126
+ cy.waitForRequest('@list');
127
+
128
+ cy.findByTestId(labelNextPage).click();
129
+
130
+ cy.waitForRequest('@list');
131
+
132
+ cy.findByTestId(labelPreviousPage).should('not.be.disabled');
133
+ cy.findByTestId(labelNextPage).should('not.be.disabled');
134
+
135
+ cy.makeSnapshot();
136
+ });
137
+ });
@@ -0,0 +1,46 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { http, HttpResponse } from 'msw';
3
+ import Pagination from '.';
4
+ import { generateItems } from './utils';
5
+
6
+ const mockedListing = {
7
+ result: generateItems(6),
8
+ meta: {
9
+ page: 1,
10
+ total: 35,
11
+ limit: 6
12
+ }
13
+ };
14
+
15
+ const meta: Meta<typeof Pagination> = {
16
+ args: {},
17
+ component: Pagination,
18
+ parameters: {
19
+ msw: {
20
+ handlers: [
21
+ http.get('**/listing**', () => {
22
+ return HttpResponse.json(mockedListing);
23
+ })
24
+ ]
25
+ }
26
+ },
27
+ render: (args) => {
28
+ return (
29
+ <div
30
+ style={{
31
+ width: '240px',
32
+ background: '#EDEDED'
33
+ }}
34
+ >
35
+ <Pagination {...args} />
36
+ </div>
37
+ );
38
+ }
39
+ };
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof Pagination>;
43
+
44
+ export const Default: Story = {
45
+ args: { api: { baseEndpoint: '/test/listing', queryKey: ['pagination'] } }
46
+ };
@@ -0,0 +1,56 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useStyles = makeStyles()((theme) => ({
4
+ container: {
5
+ height: theme.spacing(22),
6
+ width: theme.spacing(30),
7
+ padding: theme.spacing(1),
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ justifyContent: 'space-between',
11
+ alignItems: 'center',
12
+ gap: theme.spacing(2)
13
+ },
14
+ notFound: {
15
+ height: theme.spacing(10),
16
+ width: theme.spacing(30),
17
+ padding: theme.spacing(1)
18
+ },
19
+ body: {
20
+ width: '100%',
21
+ height: '100%',
22
+ display: 'flex',
23
+ justifyContent: 'space-between',
24
+ gap: theme.spacing(1)
25
+ },
26
+ content: {
27
+ width: '100%',
28
+ display: 'flex',
29
+ flexDirection: 'column',
30
+ gap: theme.spacing(0.5)
31
+ },
32
+ page: {
33
+ fontWeight: theme.typography.fontWeightMedium
34
+ },
35
+ arrowContainer: {
36
+ display: 'flex',
37
+ justifyContent: 'space-between',
38
+ alignItems: 'center'
39
+ },
40
+ icon: {
41
+ color: theme.palette.text.primary
42
+ },
43
+ arrow: {
44
+ fontSize: theme.spacing(2)
45
+ },
46
+ item: {
47
+ color: 'inherit',
48
+ textDecoration: 'none'
49
+ },
50
+ link: {
51
+ '&:hover': {
52
+ cursor: 'pointer',
53
+ color: theme.palette.primary.main
54
+ }
55
+ }
56
+ }));
@@ -0,0 +1,146 @@
1
+ import { CircularProgress, Link, Typography } from '@mui/material';
2
+ import { equals, isEmpty, isNil } from 'ramda';
3
+ import { useMemo, useState } from 'react';
4
+
5
+ import ArrowBackIcon from '@mui/icons-material/ArrowBackIosNew';
6
+ import ArrowForwardIcon from '@mui/icons-material/ArrowForwardIos';
7
+ import { useTranslation } from 'react-i18next';
8
+ import IconButton from '../Button/Icon';
9
+ import {
10
+ labelNextPage,
11
+ labelNoResultFound,
12
+ labelPreviousPage
13
+ } from '../Listing/translatedLabels';
14
+ import buildListingEndpoint from '../api/buildListingEndpoint';
15
+ import { Listing } from '../api/models';
16
+ import useFetchQuery from '../api/useFetchQuery';
17
+ import { truncate } from '../utils';
18
+ import { useStyles } from './Pagination.styles';
19
+
20
+ interface Props {
21
+ api: {
22
+ baseEndpoint: string;
23
+ queryKey: Array<string>;
24
+ searchConditions?;
25
+ };
26
+ labelHasNoElements?: string;
27
+ onItemClick?: ({ id }: { id: number }) => void;
28
+ }
29
+
30
+ const limit = 6;
31
+
32
+ const Pagination = ({
33
+ api: { baseEndpoint, queryKey, searchConditions },
34
+ labelHasNoElements = labelNoResultFound,
35
+ onItemClick
36
+ }: Props) => {
37
+ const { t } = useTranslation();
38
+ const { cx, classes } = useStyles();
39
+ const [page, setPage] = useState(1);
40
+
41
+ const { data, isLoading } = useFetchQuery<
42
+ Listing<{ id: number; name: string }>
43
+ >({
44
+ getEndpoint: (parameters): string =>
45
+ buildListingEndpoint({
46
+ baseEndpoint: baseEndpoint,
47
+ parameters: {
48
+ ...parameters,
49
+ page,
50
+ limit,
51
+ ...(searchConditions
52
+ ? {
53
+ search: {
54
+ conditions: searchConditions
55
+ }
56
+ }
57
+ : {}),
58
+ sort: { status: 'DESC' }
59
+ }
60
+ }),
61
+ getQueryKey: () => [...queryKey, page],
62
+ queryOptions: {
63
+ suspense: false
64
+ }
65
+ });
66
+
67
+ const pagesCount = Math.ceil(data?.meta.total / limit);
68
+ const arePaginationComponentsDisplayed = !equals(pagesCount, 1);
69
+
70
+ const hasNoElements = useMemo(
71
+ () => isEmpty(data?.result) || isNil(data?.result),
72
+ [data]
73
+ );
74
+
75
+ if (hasNoElements) {
76
+ return (
77
+ <div className={classes.notFound}>
78
+ <Typography color="disabled">{t(labelHasNoElements)}</Typography>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ return (
84
+ <div className={classes.container}>
85
+ <div className={classes.body}>
86
+ {arePaginationComponentsDisplayed && (
87
+ <div className={classes.arrowContainer}>
88
+ <IconButton
89
+ onClick={() => setPage(page - 1)}
90
+ disabled={equals(page, 1)}
91
+ dataTestid={labelPreviousPage}
92
+ className={classes.icon}
93
+ >
94
+ <ArrowBackIcon className={classes.arrow} />
95
+ </IconButton>
96
+ </div>
97
+ )}
98
+
99
+ <div className={classes.content}>
100
+ {isLoading ? (
101
+ <CircularProgress color="inherit" size={25} />
102
+ ) : (
103
+ data?.result.map(({ id, name }) => (
104
+ <Link
105
+ key={id}
106
+ variant="body2"
107
+ className={cx({
108
+ [classes.item]: true,
109
+ [classes.link]: !!onItemClick
110
+ })}
111
+ onClick={() => onItemClick?.({ id })}
112
+ >
113
+ {truncate({
114
+ content: name,
115
+ maxLength: 25
116
+ })}
117
+ </Link>
118
+ ))
119
+ )}
120
+ </div>
121
+
122
+ {arePaginationComponentsDisplayed && (
123
+ <div className={classes.arrowContainer}>
124
+ <IconButton
125
+ onClick={() => setPage(page + 1)}
126
+ disabled={equals(pagesCount, page)}
127
+ className={classes.icon}
128
+ dataTestid={labelNextPage}
129
+ >
130
+ <ArrowForwardIcon className={classes.arrow} />
131
+ </IconButton>
132
+ </div>
133
+ )}
134
+ </div>
135
+
136
+ {arePaginationComponentsDisplayed && (
137
+ <Typography
138
+ className={classes.page}
139
+ variant="body2"
140
+ >{`Page ${page}/${pagesCount}`}</Typography>
141
+ )}
142
+ </div>
143
+ );
144
+ };
145
+
146
+ export default Pagination;
@@ -0,0 +1,3 @@
1
+ import Pagination from './Pagination';
2
+
3
+ export default Pagination;
@@ -0,0 +1,7 @@
1
+ export const generateItems = (count: number) =>
2
+ Array(count)
3
+ .fill(0)
4
+ .map((_, idx) => ({
5
+ id: idx,
6
+ name: `Item Item Item ${idx}`
7
+ }));
@@ -230,7 +230,7 @@ const ContentWithGrid = ({
230
230
  const { classes } = useContentStyles({ isDragging });
231
231
 
232
232
  return (
233
- <Grid item style={style} xs={xs} {...listeners} {...attributes}>
233
+ <Grid item style={style} size={xs} {...listeners} {...attributes}>
234
234
  <Paper className={classes.content} ref={itemRef}>
235
235
  <Typography>{name as string}</Typography>
236
236
  </Paper>
@@ -244,7 +244,7 @@ const RootComponent = ({
244
244
  }: RootComponentProps): JSX.Element => (
245
245
  <Grid container spacing={1} style={{ width: '550px' }}>
246
246
  {not(isInDragOverlay) && (
247
- <Grid item xs={12}>
247
+ <Grid item size={12}>
248
248
  <Typography align="center">This item cannot move</Typography>
249
249
  </Grid>
250
250
  )}
@@ -9,6 +9,7 @@ import {
9
9
 
10
10
  import { ThemeMode } from '@centreon/ui-context';
11
11
 
12
+ import { GlobalStyles } from '@mui/system';
12
13
  import { getTheme } from '../ThemeProvider';
13
14
 
14
15
  interface Props {
@@ -23,7 +24,8 @@ const StoryBookThemeProvider = ({
23
24
  const theme = useMemo(() => createTheme(getTheme(themeMode)), [themeMode]);
24
25
 
25
26
  return (
26
- <StyledEngineProvider injectFirst>
27
+ <StyledEngineProvider injectFirst enableCssLayer>
28
+ <GlobalStyles styles="@layer theme,base,mui,components,utilities;" />
27
29
  <MuiThemeProvider theme={theme}>
28
30
  {children}
29
31
  <CssBaseline />
@@ -0,0 +1,49 @@
1
+ @layer base {
2
+ ::-webkit-scrollbar {
3
+ height: var(--spacing-2);
4
+ width: var(--spacing-2);
5
+ background-color: transparent;
6
+ }
7
+
8
+ ::-webkit-scrollbar-thumb {
9
+ background-color: var(--color-text-disabled);
10
+ border-radius: var(--spacing-1);
11
+ }
12
+
13
+ ::-webkit-scrollbar-thumb:hover {
14
+ background-color: var(--color-primary-main);
15
+ }
16
+
17
+ * {
18
+ scrollbar-color: var(--color-text-disabled) var(--color-background-default);
19
+ scrollbar-width: thin;
20
+ }
21
+
22
+ html {
23
+ margin: 0;
24
+ padding: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ text-rendering: optimizeLegibility;
28
+ }
29
+
30
+ body {
31
+ background-color: var(--color-background-paper);
32
+ height: 100%;
33
+ padding: 0;
34
+ width: 100%;
35
+ }
36
+
37
+ #root {
38
+ background-color: var(--color-background-paper) !important;
39
+ }
40
+
41
+ @variant dark {
42
+ ::-webkit-scrollbar-thumb {
43
+ background-color: var(--color-divider);
44
+ }
45
+ * {
46
+ scrollbar-color: var(--color-divider) var(--color-background-default);
47
+ }
48
+ }
49
+ }