@centreon/ui 24.4.1-sync-release-34022.1 → 24.4.1-test-code-coverage.1

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 (220) hide show
  1. package/package.json +48 -40
  2. package/src/ActionsList/ActionsList.styles.ts +40 -71
  3. package/src/Button/Icon/index.stories.tsx +1 -1
  4. package/src/Button/Icon/index.tsx +1 -1
  5. package/src/Dashboard/Dashboard.styles.ts +6 -5
  6. package/src/Dialog/Confirm/index.tsx +10 -2
  7. package/src/Dialog/UnsavedChanges/index.tsx +21 -20
  8. package/src/Dialog/UnsavedChanges/translatedLabels.ts +4 -6
  9. package/src/Dialog/index.tsx +8 -1
  10. package/src/Form/Inputs/CheckboxGroup.tsx +4 -1
  11. package/src/Form/Inputs/Text.tsx +3 -1
  12. package/src/Form/Inputs/models.ts +1 -0
  13. package/src/Graph/BarStack/BarStack.cypress.spec.tsx +139 -0
  14. package/src/Graph/BarStack/BarStack.stories.tsx +123 -0
  15. package/src/Graph/BarStack/BarStack.styles.ts +37 -0
  16. package/src/Graph/BarStack/BarStack.tsx +14 -0
  17. package/src/Graph/BarStack/ResponsiveBarStack.tsx +209 -0
  18. package/src/Graph/BarStack/index.ts +1 -0
  19. package/src/Graph/BarStack/models.ts +19 -0
  20. package/src/Graph/BarStack/useResponsiveBarStack.ts +131 -0
  21. package/src/Graph/Gauge/Gauge.cypress.spec.tsx +102 -0
  22. package/src/Graph/Gauge/Gauge.tsx +1 -1
  23. package/src/Graph/HeatMap/HeatMap.cypress.spec.tsx +145 -0
  24. package/src/Graph/HeatMap/HeatMap.stories.tsx +0 -25
  25. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +8 -2
  26. package/src/Graph/Legend/Legend.tsx +21 -0
  27. package/src/Graph/Legend/index.ts +1 -0
  28. package/src/Graph/Legend/models.ts +11 -0
  29. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/index.tsx +4 -5
  30. package/src/Graph/LineChart/BasicComponents/ThresholdLine.tsx +3 -1
  31. package/src/Graph/LineChart/Header/index.tsx +3 -31
  32. package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/useTickGraph.ts +9 -11
  33. package/src/Graph/LineChart/InteractiveComponents/Annotations/Annotation/index.tsx +3 -2
  34. package/src/Graph/LineChart/InteractiveComponents/GraphValueTooltip/GraphValueTooltip.tsx +68 -0
  35. package/src/Graph/LineChart/InteractiveComponents/GraphValueTooltip/useGraphValueTooltip.ts +27 -0
  36. package/src/Graph/LineChart/InteractiveComponents/GraphValueTooltip/useGraphValueTooltipStyles.ts +31 -0
  37. package/src/Graph/LineChart/InteractiveComponents/index.tsx +132 -17
  38. package/src/Graph/LineChart/InteractiveComponents/interactionWithGraphAtoms.ts +7 -27
  39. package/src/Graph/LineChart/Legend/Legend.styles.ts +5 -9
  40. package/src/Graph/LineChart/Legend/LegendHeader.tsx +10 -22
  41. package/src/Graph/LineChart/Legend/index.tsx +17 -55
  42. package/src/Graph/LineChart/LineChart.cypress.spec.tsx +91 -0
  43. package/src/Graph/LineChart/LineChart.styles.ts +8 -0
  44. package/src/Graph/LineChart/LineChart.tsx +106 -116
  45. package/src/Graph/LineChart/LoadingSkeleton.tsx +2 -2
  46. package/src/Graph/LineChart/index.tsx +6 -7
  47. package/src/Graph/LineChart/mockedData/lastDayWithIncompleteValues.json +1320 -0
  48. package/src/Graph/LineChart/mockedData/lastDayWithNullValues.json +1314 -0
  49. package/src/Graph/LineChart/models.ts +12 -1
  50. package/src/Graph/PieChart/PieChart.cypress.spec.tsx +154 -0
  51. package/src/Graph/PieChart/PieChart.stories.tsx +194 -0
  52. package/src/Graph/PieChart/PieChart.styles.ts +39 -0
  53. package/src/Graph/PieChart/PieChart.tsx +14 -0
  54. package/src/Graph/PieChart/ResponsivePie.tsx +243 -0
  55. package/src/Graph/PieChart/index.ts +1 -0
  56. package/src/Graph/PieChart/models.ts +19 -0
  57. package/src/Graph/PieChart/useResponsivePie.ts +81 -0
  58. package/src/Graph/SingleBar/SingleBar.cypress.spec.tsx +121 -0
  59. package/src/Graph/Text/Text.cypress.spec.tsx +101 -0
  60. package/src/Graph/Text/Text.styles.ts +12 -1
  61. package/src/Graph/Text/Text.tsx +17 -12
  62. package/src/Graph/Tree/DescendantNodes.tsx +89 -0
  63. package/src/Graph/Tree/Links.tsx +77 -0
  64. package/src/Graph/Tree/StandaloneTree.tsx +32 -0
  65. package/src/Graph/Tree/Tree.cypress.spec.tsx +195 -0
  66. package/src/Graph/Tree/Tree.stories.tsx +160 -0
  67. package/src/Graph/Tree/Tree.tsx +116 -0
  68. package/src/Graph/Tree/constants.ts +2 -0
  69. package/src/Graph/Tree/index.ts +4 -0
  70. package/src/Graph/Tree/models.ts +55 -0
  71. package/src/Graph/Tree/stories/contents.tsx +164 -0
  72. package/src/Graph/Tree/stories/datas.ts +305 -0
  73. package/src/Graph/Tree/utils.ts +49 -0
  74. package/src/Graph/common/testUtils.ts +71 -0
  75. package/src/Graph/common/timeSeries/index.ts +50 -12
  76. package/src/Graph/common/utils.ts +19 -0
  77. package/src/Graph/index.ts +4 -0
  78. package/src/InputField/Number/Number.cypress.spec.tsx +85 -0
  79. package/src/InputField/Number/Number.stories.tsx +66 -0
  80. package/src/InputField/Number/Number.tsx +74 -0
  81. package/src/InputField/Search/index.tsx +2 -2
  82. package/src/InputField/Select/Autocomplete/Multi/index.tsx +4 -2
  83. package/src/InputField/Select/Autocomplete/index.tsx +10 -3
  84. package/src/InputField/Select/IconPopover/index.tsx +1 -1
  85. package/src/InputField/Select/index.tsx +14 -1
  86. package/src/InputField/Text/index.tsx +38 -38
  87. package/src/Listing/ActionBar/index.tsx +10 -10
  88. package/src/Listing/Cell/DataCell.styles.ts +3 -0
  89. package/src/Listing/Cell/DataCell.tsx +8 -4
  90. package/src/Listing/Listing.cypress.spec.tsx +217 -33
  91. package/src/Listing/Listing.styles.ts +3 -5
  92. package/src/Listing/Row/Row.tsx +7 -3
  93. package/src/Listing/index.stories.tsx +25 -2
  94. package/src/Listing/index.test.tsx +1 -1
  95. package/src/Listing/index.tsx +202 -143
  96. package/src/Listing/models.ts +1 -0
  97. package/src/Listing/useStyleTable.ts +1 -0
  98. package/src/Panel/index.tsx +1 -1
  99. package/src/PopoverMenu/index.tsx +6 -5
  100. package/src/ThemeProvider/index.tsx +3 -0
  101. package/src/TimePeriods/CustomTimePeriod/CompactCustomTimePeriod.styles.ts +6 -7
  102. package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +12 -9
  103. package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +27 -0
  104. package/src/Typography/FluidTypography/index.stories.tsx +2 -2
  105. package/src/Typography/FluidTypography/index.tsx +21 -28
  106. package/src/api/index.ts +3 -3
  107. package/src/api/useGraphQuery/index.ts +26 -5
  108. package/src/api/useGraphQuery/models.ts +5 -0
  109. package/src/api/useMutationQuery/index.test.ts +4 -4
  110. package/src/api/useMutationQuery/index.ts +24 -13
  111. package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +76 -0
  112. package/src/components/CollapsibleItem/CollapsibleItem.stories.tsx +26 -0
  113. package/src/components/CollapsibleItem/CollapsibleItem.tsx +43 -14
  114. package/src/components/CollapsibleItem/useCollapsibleItemStyles.ts +24 -1
  115. package/src/components/DataTable/DataTable.cypress.spec.tsx +14 -33
  116. package/src/components/DataTable/Item/DataTableItem.tsx +4 -60
  117. package/src/components/Form/{AccessRightsV2 → AccessRights}/AccessRights.cypress.spec.tsx +36 -13
  118. package/src/components/Form/{AccessRightsV2 → AccessRights}/ShareInput/ContactSwitch.tsx +11 -3
  119. package/src/components/Form/{AccessRightsV2 → AccessRights}/ShareInput/ShareInput.styles.ts +8 -0
  120. package/src/components/Form/{AccessRightsV2 → AccessRights}/ShareInput/ShareInput.tsx +1 -0
  121. package/src/components/Form/{AccessRightsV2 → AccessRights}/ShareInput/useShareInput.tsx +4 -0
  122. package/src/components/Form/{AccessRightsV2 → AccessRights}/models.ts +1 -0
  123. package/src/components/Form/{AccessRightsV2 → AccessRights}/storiesData.ts +23 -22
  124. package/src/components/Form/Dashboard/DashboardDuplicationForm.tsx +85 -0
  125. package/src/components/Form/Dashboard/index.ts +1 -0
  126. package/src/components/Form/FormActions.tsx +7 -2
  127. package/src/components/Form/index.ts +2 -2
  128. package/src/components/ItemComposition/Item.tsx +1 -1
  129. package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +113 -0
  130. package/src/components/ItemComposition/ItemComposition.stories.tsx +14 -0
  131. package/src/components/ItemComposition/ItemComposition.styles.ts +36 -3
  132. package/src/components/ItemComposition/ItemComposition.tsx +41 -17
  133. package/src/components/List/Item/ListItem.tsx +3 -3
  134. package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +168 -0
  135. package/src/components/Modal/ConfirmationModal/ConfirmationModal.stories.tsx +62 -0
  136. package/src/components/Modal/ConfirmationModal/ConfirmationModal.tsx +87 -0
  137. package/src/components/Modal/Modal.styles.ts +8 -3
  138. package/src/components/Modal/index.ts +2 -0
  139. package/src/components/Tooltip/ConfirmationTooltip/ConfirmationTooltip.stories.tsx +3 -3
  140. package/src/components/Tooltip/ConfirmationTooltip/ConfirmationTooltip.tsx +1 -1
  141. package/src/components/Tooltip/ConfirmationTooltip/models.ts +1 -1
  142. package/src/components/Zoom/Minimap.tsx +129 -0
  143. package/src/components/Zoom/Zoom.cypress.spec.tsx +246 -0
  144. package/src/components/Zoom/Zoom.stories.tsx +115 -0
  145. package/src/components/Zoom/Zoom.styles.tsx +68 -0
  146. package/src/components/Zoom/Zoom.tsx +64 -0
  147. package/src/components/Zoom/ZoomContent.tsx +170 -0
  148. package/src/components/Zoom/constants.ts +2 -0
  149. package/src/components/Zoom/localPoint.ts +51 -0
  150. package/src/components/Zoom/models.ts +25 -0
  151. package/src/components/Zoom/useMinimap.ts +156 -0
  152. package/src/components/Zoom/useZoom.ts +70 -0
  153. package/src/components/Zoom/utils.ts +55 -0
  154. package/src/components/index.ts +1 -0
  155. package/src/index.ts +1 -0
  156. package/src/utils/index.ts +3 -0
  157. package/src/utils/resourcesStatusURL.ts +166 -0
  158. package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +130 -0
  159. package/src/utils/useFullscreen/atoms.ts +3 -0
  160. package/src/utils/useFullscreen/index.ts +2 -0
  161. package/src/utils/useFullscreen/translatedLabels.ts +1 -0
  162. package/src/utils/useFullscreen/useFullscreen.ts +73 -0
  163. package/src/utils/useFullscreen/useFullscreenListener.ts +62 -0
  164. package/src/utils/useInfiniteScrollListing.ts +4 -1
  165. package/src/Graph/LineChart/BasicComponents/LoadingProgress.tsx +0 -46
  166. package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/TooltipAnchorPoint.tsx +0 -96
  167. package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/useTooltipAnchorPoint.ts +0 -107
  168. package/src/Graph/LineChart/Legend/InteractiveValue.tsx +0 -22
  169. package/src/Graph/LineChart/Legend/useInteractiveValues.ts +0 -99
  170. package/src/Typography/FluidTypography/useFluidResizeObserver.ts +0 -56
  171. package/src/components/Form/AccessRights/AccessRights.resource.ts +0 -45
  172. package/src/components/Form/AccessRights/AccessRightsForm.stories.tsx +0 -59
  173. package/src/components/Form/AccessRights/AccessRightsForm.styles.ts +0 -21
  174. package/src/components/Form/AccessRights/AccessRightsForm.tsx +0 -67
  175. package/src/components/Form/AccessRights/AccessRightsFormActions.tsx +0 -80
  176. package/src/components/Form/AccessRights/Input/AddAction.tsx +0 -31
  177. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.stories.tsx +0 -54
  178. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.tsx +0 -72
  179. package/src/components/Form/AccessRights/Input/ContactAccessRightsInput.styles.ts +0 -22
  180. package/src/components/Form/AccessRights/Input/ContactInputField.tsx +0 -105
  181. package/src/components/Form/AccessRights/Input/RoleInputField.tsx +0 -29
  182. package/src/components/Form/AccessRights/List/ContactAccessRightsList.stories.tsx +0 -97
  183. package/src/components/Form/AccessRights/List/ContactAccessRightsList.styles.ts +0 -71
  184. package/src/components/Form/AccessRights/List/ContactAccessRightsList.tsx +0 -51
  185. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.stories.tsx +0 -116
  186. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.tsx +0 -118
  187. package/src/components/Form/AccessRights/List/ContactAccessRightsListItemSkeleton.tsx +0 -26
  188. package/src/components/Form/AccessRights/List/ContactAccessRightsListSkeleton.tsx +0 -28
  189. package/src/components/Form/AccessRights/Stats/AccessRightsStats.styles.ts +0 -18
  190. package/src/components/Form/AccessRights/Stats/AccessRightsStats.tsx +0 -41
  191. package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +0 -54
  192. package/src/components/Form/AccessRights/common/GroupLabel.styles.ts +0 -18
  193. package/src/components/Form/AccessRights/common/GroupLabel.tsx +0 -15
  194. package/src/components/Form/AccessRights/common/Input.styles.ts +0 -48
  195. package/src/components/Form/AccessRights/common/RoleInputSelect.styles.ts +0 -11
  196. package/src/components/Form/AccessRights/common/RoleInputSelect.tsx +0 -57
  197. package/src/components/Form/AccessRights/index.ts +0 -3
  198. package/src/components/Form/AccessRights/useAccessRightsForm.test.tsx +0 -531
  199. package/src/components/Form/AccessRights/useAccessRightsForm.tsx +0 -282
  200. package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +0 -41
  201. /package/src/components/Form/{AccessRightsV2 → AccessRights}/AccessRights.stories.tsx +0 -0
  202. /package/src/components/Form/{AccessRightsV2 → AccessRights}/AccessRights.styles.ts +0 -0
  203. /package/src/components/Form/{AccessRightsV2 → AccessRights}/AccessRights.tsx +0 -0
  204. /package/src/components/Form/{AccessRightsV2 → AccessRights}/Actions/Actions.styles.ts +0 -0
  205. /package/src/components/Form/{AccessRightsV2 → AccessRights}/Actions/Actions.tsx +0 -0
  206. /package/src/components/Form/{AccessRightsV2 → AccessRights}/Actions/useActions.ts +0 -0
  207. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/Item.tsx +0 -0
  208. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/List.styles.tsx +0 -0
  209. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/List.tsx +0 -0
  210. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/ListItemSkeleton.tsx +0 -0
  211. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/ListSkeleton.tsx +0 -0
  212. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/RemoveAccessRight.tsx +0 -0
  213. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/StateChip.tsx +0 -0
  214. /package/src/components/Form/{AccessRightsV2 → AccessRights}/List/useItem.ts +0 -0
  215. /package/src/components/Form/{AccessRightsV2 → AccessRights}/Provider.tsx +0 -0
  216. /package/src/components/Form/{AccessRightsV2 → AccessRights}/Stats/Stats.tsx +0 -0
  217. /package/src/components/Form/{AccessRightsV2 → AccessRights}/atoms.ts +0 -0
  218. /package/src/components/Form/{AccessRightsV2 → AccessRights}/common/RoleSelectField.styles.tsx +0 -0
  219. /package/src/components/Form/{AccessRightsV2 → AccessRights}/common/RoleSelectField.tsx +0 -0
  220. /package/src/components/Form/{AccessRightsV2 → AccessRights}/useAccessRightsInitValues.ts +0 -0
@@ -1,12 +1,45 @@
1
1
  import { makeStyles } from 'tss-react/mui';
2
2
 
3
3
  export const useItemCompositionStyles = makeStyles()((theme) => ({
4
+ buttonAndSecondaryLabel: {
5
+ alignItems: 'center',
6
+ display: 'flex',
7
+ flexDirection: 'row',
8
+ justifyContent: 'space-between',
9
+ width: '100%'
10
+ },
4
11
  itemCompositionContainer: {
12
+ width: '100%'
13
+ },
14
+ itemCompositionItems: {
5
15
  alignItems: 'flex-start',
6
16
  display: 'flex',
7
17
  flexDirection: 'column',
8
18
  gap: theme.spacing(2),
9
19
  width: '100%'
20
+ },
21
+ itemCompositionItemsAndLink: {
22
+ display: 'flex',
23
+ flexDirection: 'row-reverse',
24
+ gap: theme.spacing(0.5),
25
+ width: '100%'
26
+ },
27
+ linkIcon: {
28
+ backgroundColor: theme.palette.background.paper,
29
+ padding: theme.spacing(0.5, 0),
30
+ transform: `rotate3d(0, 0, 1, 90deg) translate3d(0, ${theme.spacing(
31
+ 1.6
32
+ )}, 0)`
33
+ },
34
+ linkedItems: {
35
+ alignItems: 'center',
36
+ border: `1px solid ${theme.palette.divider}`,
37
+ borderRight: 'none',
38
+ display: 'flex',
39
+ margin: theme.spacing(2, 0, 2, 1),
40
+ minHeight: '100%',
41
+ position: 'relative',
42
+ width: theme.spacing(1)
10
43
  }
11
44
  }));
12
45
 
@@ -19,11 +52,11 @@ export const useItemStyles = makeStyles()((theme) => ({
19
52
  width: '100%'
20
53
  },
21
54
  itemContent: {
22
- display: 'flex',
23
- flexDirection: 'row',
55
+ display: 'grid',
56
+ gridAutoFlow: 'column',
24
57
  width: '100%'
25
58
  },
26
59
  visibilityHiden: {
27
- visibility: 'hidden'
60
+ display: 'none'
28
61
  }
29
62
  }));
@@ -1,18 +1,24 @@
1
1
  import { ReactElement } from 'react';
2
2
 
3
+ import { gt } from 'ramda';
4
+
3
5
  import AddIcon from '@mui/icons-material/Add';
6
+ import LinkIcon from '@mui/icons-material/Link';
7
+ import { Typography } from '@mui/material';
4
8
 
5
9
  import { Button } from '..';
6
10
 
7
11
  import { useItemCompositionStyles } from './ItemComposition.styles';
8
12
 
9
- type Props = {
13
+ export type Props = {
10
14
  IconAdd?;
11
15
  addButtonHidden?: boolean;
12
16
  addbuttonDisabled?: boolean;
13
17
  children: Array<ReactElement>;
18
+ displayItemsAsLinked?: boolean;
14
19
  labelAdd: string;
15
20
  onAddItem: () => void;
21
+ secondaryLabel?: string;
16
22
  };
17
23
 
18
24
  export const ItemComposition = ({
@@ -21,27 +27,45 @@ export const ItemComposition = ({
21
27
  labelAdd,
22
28
  addbuttonDisabled,
23
29
  addButtonHidden,
24
- IconAdd
30
+ IconAdd,
31
+ displayItemsAsLinked,
32
+ secondaryLabel
25
33
  }: Props): JSX.Element => {
26
34
  const { classes } = useItemCompositionStyles();
27
35
 
36
+ const hasMoreThanOneChildren = gt(children.length, 1);
37
+
28
38
  return (
29
39
  <div className={classes.itemCompositionContainer}>
30
- {children}
31
- {!addButtonHidden && (
32
- <Button
33
- aria-label={labelAdd}
34
- data-testid={labelAdd}
35
- disabled={addbuttonDisabled}
36
- icon={IconAdd || <AddIcon />}
37
- iconVariant="start"
38
- size="small"
39
- variant="ghost"
40
- onClick={onAddItem}
41
- >
42
- {labelAdd}
43
- </Button>
44
- )}
40
+ <div className={classes.itemCompositionItemsAndLink}>
41
+ <div className={classes.itemCompositionItems}>{children}</div>
42
+ {displayItemsAsLinked && hasMoreThanOneChildren && (
43
+ <div data-linked className={classes.linkedItems}>
44
+ <LinkIcon className={classes.linkIcon} viewBox="0 0 24 24" />
45
+ </div>
46
+ )}
47
+ </div>
48
+ <div className={classes.buttonAndSecondaryLabel}>
49
+ {!addButtonHidden && (
50
+ <Button
51
+ aria-label={labelAdd}
52
+ data-testid={labelAdd}
53
+ disabled={addbuttonDisabled}
54
+ icon={IconAdd || <AddIcon />}
55
+ iconVariant="start"
56
+ size="small"
57
+ variant="ghost"
58
+ onClick={onAddItem}
59
+ >
60
+ {labelAdd}
61
+ </Button>
62
+ )}
63
+ {secondaryLabel && (
64
+ <Typography sx={{ color: 'text.secondary' }}>
65
+ {secondaryLabel}
66
+ </Typography>
67
+ )}
68
+ </div>
45
69
  </div>
46
70
  );
47
71
  };
@@ -1,10 +1,10 @@
1
1
  import { ForwardedRef, forwardRef, ReactElement, ReactNode } from 'react';
2
2
 
3
- import { ListItem as MuiListItem } from '@mui/material';
3
+ import { ListItemProps, ListItem as MuiListItem } from '@mui/material';
4
4
 
5
5
  import { useStyles } from './ListItem.styles';
6
6
 
7
- type ListItemProps = {
7
+ type Props = {
8
8
  action?: ReactElement;
9
9
  children: ReactNode | Array<ReactNode>;
10
10
  className?: string;
@@ -12,7 +12,7 @@ type ListItemProps = {
12
12
 
13
13
  export const ListItem = forwardRef(
14
14
  (
15
- { action, children, className, ...attr }: ListItemProps,
15
+ { action, children, className, ...attr }: Props & ListItemProps,
16
16
  ref?: ForwardedRef<HTMLLIElement>
17
17
  ) => {
18
18
  const { classes, cx } = useStyles();
@@ -0,0 +1,168 @@
1
+ import { atom, createStore, useSetAtom, Provider } from 'jotai';
2
+
3
+ import { Button } from '@mui/material';
4
+
5
+ import { ConfirmationModal, ConfirmationModalProps } from './ConfirmationModal';
6
+
7
+ const testAtom = atom<string | null>(null);
8
+
9
+ const buttonLabel = 'Click to open modal';
10
+
11
+ const TestComponent = (args: ConfirmationModalProps<string>): JSX.Element => {
12
+ const setAtom = useSetAtom(testAtom);
13
+
14
+ return (
15
+ <>
16
+ <Button onClick={() => setAtom('John')}>{buttonLabel}</Button>
17
+ <ConfirmationModal<string> {...args} />
18
+ </>
19
+ );
20
+ };
21
+
22
+ const staticLabels = {
23
+ cancel: 'Cancel',
24
+ confirm: 'Confirm',
25
+ description: 'Description',
26
+ title: 'Title'
27
+ };
28
+
29
+ const dynamicLabels = {
30
+ cancel: 'Cancel',
31
+ confirm: 'Confirm',
32
+ description: (data) => `Description ${data}`,
33
+ title: (data) => `Hello ${data}`
34
+ };
35
+
36
+ const initialize = (
37
+ props: Pick<
38
+ ConfirmationModalProps<string>,
39
+ 'labels' | 'disabled' | 'hasCloseButton' | 'isDanger'
40
+ >
41
+ ): { cancel; confirm } => {
42
+ const store = createStore();
43
+
44
+ const cancel = cy.stub();
45
+ const confirm = cy.stub();
46
+
47
+ cy.mount({
48
+ Component: (
49
+ <Provider store={store}>
50
+ <TestComponent
51
+ {...props}
52
+ atom={testAtom}
53
+ onCancel={cancel}
54
+ onConfirm={confirm}
55
+ />
56
+ </Provider>
57
+ )
58
+ });
59
+
60
+ return {
61
+ cancel,
62
+ confirm
63
+ };
64
+ };
65
+
66
+ describe('Confirmation modal', () => {
67
+ it('displays the modal with static labels', () => {
68
+ initialize({ labels: staticLabels });
69
+
70
+ cy.contains(buttonLabel).click();
71
+
72
+ cy.contains('Title').should('be.visible');
73
+ cy.contains('Description').should('be.visible');
74
+ cy.findByLabelText('close').should('be.visible');
75
+
76
+ cy.makeSnapshot();
77
+ });
78
+
79
+ it('displays the modal with dynamic labels', () => {
80
+ initialize({ labels: dynamicLabels });
81
+
82
+ cy.contains(buttonLabel).click();
83
+
84
+ cy.contains('Hello John').should('be.visible');
85
+ cy.contains('Description John').should('be.visible');
86
+
87
+ cy.makeSnapshot();
88
+ });
89
+
90
+ it('displays the confirm button as disabled when a prop is set', () => {
91
+ initialize({ disabled: true, labels: staticLabels });
92
+
93
+ cy.contains(buttonLabel).click();
94
+
95
+ cy.contains('Confirm').should('be.disabled');
96
+
97
+ cy.makeSnapshot();
98
+ });
99
+
100
+ it('displays the confirm button as danger when a prop is set', () => {
101
+ initialize({ isDanger: true, labels: staticLabels });
102
+
103
+ cy.contains(buttonLabel).click();
104
+
105
+ cy.contains('Confirm').should('have.attr', 'data-is-danger', 'true');
106
+
107
+ cy.makeSnapshot();
108
+ });
109
+
110
+ it('displays the modal without the close button when a prop is set', () => {
111
+ initialize({ hasCloseButton: false, labels: staticLabels });
112
+
113
+ cy.contains(buttonLabel).click();
114
+
115
+ cy.findByLabelText('close').should('not.exist');
116
+
117
+ cy.makeSnapshot();
118
+ });
119
+
120
+ it('closes the modal when the close button is clicked', () => {
121
+ initialize({ labels: staticLabels });
122
+
123
+ cy.contains(buttonLabel).click();
124
+
125
+ cy.contains('Title').should('be.visible');
126
+
127
+ cy.findByLabelText('close').click();
128
+
129
+ cy.contains('Title').should('not.exist');
130
+
131
+ cy.makeSnapshot();
132
+ });
133
+
134
+ it('closes the modal when the cancel button is clicked', () => {
135
+ const { cancel } = initialize({ labels: staticLabels });
136
+
137
+ cy.contains(buttonLabel).click();
138
+
139
+ cy.contains('Title').should('be.visible');
140
+
141
+ cy.contains('Cancel')
142
+ .click()
143
+ .then(() => {
144
+ expect(cancel).to.be.calledWith('John');
145
+ });
146
+
147
+ cy.contains('Title').should('not.exist');
148
+
149
+ cy.makeSnapshot();
150
+ });
151
+ it('closes the modal when the confirm button is clicked', () => {
152
+ const { confirm } = initialize({ labels: staticLabels });
153
+
154
+ cy.contains(buttonLabel).click();
155
+
156
+ cy.contains('Title').should('be.visible');
157
+
158
+ cy.contains('Confirm')
159
+ .click()
160
+ .then(() => {
161
+ expect(confirm).to.be.calledWith('John');
162
+ });
163
+
164
+ cy.contains('Title').should('not.exist');
165
+
166
+ cy.makeSnapshot();
167
+ });
168
+ });
@@ -0,0 +1,62 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { atom, useSetAtom, createStore, Provider } from 'jotai';
3
+
4
+ import { Button } from '../../Button';
5
+
6
+ import { ConfirmationModal } from './ConfirmationModal';
7
+
8
+ const meta: Meta<typeof ConfirmationModal> = {
9
+ component: ConfirmationModal
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof ConfirmationModal>;
14
+
15
+ const testAtom = atom<string | null>(null);
16
+
17
+ const store = createStore();
18
+
19
+ const Component = (args): JSX.Element => {
20
+ const setAtom = useSetAtom(testAtom);
21
+
22
+ return (
23
+ <>
24
+ <Button onClick={() => setAtom('John')}>Click to open modal</Button>
25
+ <ConfirmationModal<string> {...args} />
26
+ </>
27
+ );
28
+ };
29
+
30
+ export const Default: Story = {
31
+ args: {
32
+ hasCloseButton: true,
33
+ labels: {
34
+ cancel: 'Cancel',
35
+ confirm: 'Confirm',
36
+ description: 'Description',
37
+ title: 'Title'
38
+ }
39
+ },
40
+ render: (args) => (
41
+ <Provider store={store}>
42
+ <Component {...args} atom={testAtom} />
43
+ </Provider>
44
+ )
45
+ };
46
+
47
+ export const WithDynamicLabels: Story = {
48
+ args: {
49
+ hasCloseButton: true,
50
+ labels: {
51
+ cancel: 'Cancel',
52
+ confirm: 'Confirm',
53
+ description: (data) => `Hello ${data} from description`,
54
+ title: (data) => `Hello ${data}`
55
+ }
56
+ },
57
+ render: (args) => (
58
+ <Provider store={store}>
59
+ <Component {...args} atom={testAtom} />
60
+ </Provider>
61
+ )
62
+ };
@@ -0,0 +1,87 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useAtom, PrimitiveAtom } from 'jotai';
4
+ import { equals, pick, type } from 'ramda';
5
+
6
+ import { Modal } from '..';
7
+
8
+ export interface ConfirmationModalProps<TAtom> {
9
+ atom: PrimitiveAtom<string | null>;
10
+ disabled?: boolean;
11
+ hasCloseButton?: boolean;
12
+ isDanger?: boolean;
13
+ labels: {
14
+ cancel: string | ((atom: Awaited<TAtom> | null) => string);
15
+ confirm: string | ((atom: Awaited<TAtom> | null) => string);
16
+ description: string | ((atom: Awaited<TAtom> | null) => string);
17
+ title: string | ((atom: Awaited<TAtom> | null) => string);
18
+ };
19
+ onCancel?: (atomData: Awaited<TAtom> | null) => void;
20
+ onClose?: (atomData: Awaited<TAtom> | null) => void;
21
+ onConfirm?: (atomData: Awaited<TAtom> | null) => void;
22
+ }
23
+
24
+ interface GetLabelProps<TAtom> {
25
+ atomData: Awaited<TAtom> | null;
26
+ label: string | ((atom: Awaited<TAtom> | null) => string);
27
+ }
28
+
29
+ const getLabel = <TAtom,>({ label, atomData }: GetLabelProps<TAtom>): string =>
30
+ equals(type(label), 'String')
31
+ ? (label as string)
32
+ : (label as (atom: Awaited<TAtom> | null) => string)(atomData);
33
+
34
+ export const ConfirmationModal = <TAtom,>({
35
+ atom,
36
+ labels,
37
+ onConfirm,
38
+ onCancel,
39
+ onClose,
40
+ hasCloseButton = true,
41
+ isDanger,
42
+ disabled
43
+ }: ConfirmationModalProps<TAtom>): JSX.Element => {
44
+ const [atomData, setAtomData] = useAtom<TAtom | null>(atom);
45
+
46
+ const closeModal = (): void => {
47
+ onClose?.(atomData);
48
+ setAtomData(null);
49
+ };
50
+
51
+ const formattedLabels = useMemo(() => {
52
+ return {
53
+ cancel: getLabel({ atomData, label: labels.cancel }),
54
+ confirm: getLabel({ atomData, label: labels.confirm }),
55
+ description: getLabel({ atomData, label: labels.description }),
56
+ title: getLabel({ atomData, label: labels.title })
57
+ };
58
+ }, [labels, atomData]);
59
+
60
+ const confirm = (): void => {
61
+ onConfirm?.(atomData);
62
+ setAtomData(null);
63
+ };
64
+
65
+ const cancel = (): void => {
66
+ onCancel?.(atomData);
67
+ setAtomData(null);
68
+ };
69
+
70
+ return (
71
+ <Modal
72
+ hasCloseButton={hasCloseButton}
73
+ open={Boolean(atomData)}
74
+ onClose={closeModal}
75
+ >
76
+ <Modal.Header>{formattedLabels.title}</Modal.Header>
77
+ <Modal.Body>{formattedLabels.description}</Modal.Body>
78
+ <Modal.Actions
79
+ disabled={disabled}
80
+ isDanger={isDanger}
81
+ labels={pick(['confirm', 'cancel'], formattedLabels)}
82
+ onCancel={cancel}
83
+ onConfirm={confirm}
84
+ />
85
+ </Modal>
86
+ );
87
+ };
@@ -28,6 +28,7 @@ const useStyles = makeStyles<{
28
28
  left: props?.left ?? 0,
29
29
  margin: 0,
30
30
  maxWidth: 'unset',
31
+ paddingBottom: theme.spacing(8),
31
32
  position: 'absolute',
32
33
  right: props?.right ?? 0,
33
34
  top: props?.top ?? 0,
@@ -50,14 +51,18 @@ const useStyles = makeStyles<{
50
51
  },
51
52
  modalActions: {
52
53
  '&[data-fixed="true"]': {
53
- position: 'fixed'
54
+ background: theme.palette.background.paper,
55
+ position: 'fixed',
56
+ width: '100%'
54
57
  },
55
- bottom: theme.spacing(2),
58
+ bottom: 0,
56
59
  display: 'flex',
57
60
  flexDirection: 'row',
58
61
  gap: theme.spacing(2),
59
62
  justifyContent: 'flex-end',
60
- right: theme.spacing(2.5)
63
+ padding: theme.spacing(1, 2.5, 2.5, 0),
64
+ right: 0,
65
+ zIndex: theme.zIndex.modal
61
66
  },
62
67
  modalBody: {
63
68
  '& > p': {
@@ -3,6 +3,8 @@ import { ModalHeader } from './ModalHeader';
3
3
  import { ModalBody } from './ModalBody';
4
4
  import { ModalActions } from './ModalActions';
5
5
 
6
+ export { ConfirmationModal } from './ConfirmationModal/ConfirmationModal';
7
+
6
8
  export const Modal = Object.assign(ModalRoot, {
7
9
  Actions: ModalActions,
8
10
  Body: ModalBody,
@@ -15,7 +15,7 @@ type Story = StoryObj<typeof ConfirmationTooltip>;
15
15
 
16
16
  export const Default: Story = {
17
17
  args: {
18
- children: (toggleTooltip) => (
18
+ children: ({ toggleTooltip }) => (
19
19
  <IconButton icon={<SaveIcon />} onClick={toggleTooltip} />
20
20
  ),
21
21
  labels: {
@@ -30,7 +30,7 @@ export const Default: Story = {
30
30
 
31
31
  export const WithConfirmVariant: Story = {
32
32
  args: {
33
- children: (toggleTooltip) => (
33
+ children: ({ toggleTooltip }) => (
34
34
  <IconButton icon={<DeleteIcon color="error" />} onClick={toggleTooltip} />
35
35
  ),
36
36
  confirmVariant: 'error',
@@ -46,7 +46,7 @@ export const WithConfirmVariant: Story = {
46
46
 
47
47
  export const WithSecondaryLabel: Story = {
48
48
  args: {
49
- children: (toggleTooltip) => (
49
+ children: ({ toggleTooltip }) => (
50
50
  <IconButton icon={<DeleteIcon color="error" />} onClick={toggleTooltip} />
51
51
  ),
52
52
  confirmVariant: 'error',
@@ -49,7 +49,7 @@ export const ConfirmationTooltip = ({
49
49
  return (
50
50
  <ClickAwayListener onClickAway={close}>
51
51
  <div>
52
- {children(isOpen ? close : open)}
52
+ {children({ isOpen, toggleTooltip: isOpen ? close : open })}
53
53
  <Popper
54
54
  anchorEl={anchorElement}
55
55
  className={classes.popper}
@@ -11,7 +11,7 @@ interface Labels {
11
11
  }
12
12
 
13
13
  export interface Props {
14
- children: (toggleTooltip) => ReactElement;
14
+ children: ({ toggleTooltip, isOpen }) => ReactElement;
15
15
  confirmVariant?: ActionVariants;
16
16
  labels: Labels;
17
17
  onConfirm: () => void;