@centreon/ui 23.10.94 → 23.10.96

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "23.10.94",
3
+ "version": "23.10.96",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -3,27 +3,37 @@ import { useTranslation } from 'react-i18next';
3
3
  import { equals } from 'ramda';
4
4
 
5
5
  import {
6
- SvgIconProps,
7
6
  MenuList,
8
7
  MenuItem,
9
8
  ListItemText,
10
9
  ListItemIcon,
11
- Divider
10
+ Divider,
11
+ SvgIconTypeMap
12
12
  } from '@mui/material';
13
+ import { OverridableComponent } from '@mui/material/OverridableComponent';
14
+
15
+ import { sanitizedHTML } from '..';
13
16
 
14
17
  import { useStyles } from './ActionsList.styles';
15
18
  import { ActionVariants } from './models';
16
19
 
17
20
  interface ActionsType {
18
- Icon?: (props: SvgIconProps) => JSX.Element;
21
+ Icon?: OverridableComponent<SvgIconTypeMap<object, 'svg'>> & {
22
+ muiName: string;
23
+ };
19
24
  label: string;
20
25
  onClick?: (e?) => void;
21
26
  secondaryLabel?: string;
22
27
  variant?: ActionVariants;
23
28
  }
24
29
 
30
+ export enum ActionsListActionDivider {
31
+ divider = 'divider'
32
+ }
33
+ export type ActionsListActions = Array<ActionsType | ActionsListActionDivider>;
34
+
25
35
  interface Props {
26
- actions: Array<ActionsType | 'divider'>;
36
+ actions: ActionsListActions;
27
37
  className?: string;
28
38
  listItemClassName?: string;
29
39
  }
@@ -39,7 +49,7 @@ const ActionsList = ({
39
49
  return (
40
50
  <MenuList className={cx(classes.list, className)}>
41
51
  {actions?.map((action, idx) => {
42
- if (equals(action, 'divider')) {
52
+ if (equals(action, ActionsListActionDivider.divider)) {
43
53
  return <Divider key={`divider_${idx}`} />;
44
54
  }
45
55
 
@@ -63,7 +73,10 @@ const ActionsList = ({
63
73
  <ListItemText
64
74
  className={listItemClassName}
65
75
  primary={t(label)}
66
- secondary={secondaryLabel && t(secondaryLabel)}
76
+ secondary={
77
+ secondaryLabel &&
78
+ sanitizedHTML({ initialContent: t(secondaryLabel) })
79
+ }
67
80
  />
68
81
  </MenuItem>
69
82
  );
@@ -2,12 +2,8 @@ import { makeStyles } from 'tss-react/mui';
2
2
  import { T, always, cond, equals } from 'ramda';
3
3
 
4
4
  import { SvgIconComponent } from '@mui/icons-material';
5
- import {
6
- FormControlLabel,
7
- Checkbox as MuiCheckbox,
8
- Box,
9
- Typography
10
- } from '@mui/material';
5
+ import Typography, { TypographyTypeMap } from '@mui/material/Typography';
6
+ import { FormControlLabel, Checkbox as MuiCheckbox, Box } from '@mui/material';
11
7
 
12
8
  export type LabelPlacement = 'bottom' | 'top' | 'end' | 'start' | undefined;
13
9
 
@@ -60,6 +56,7 @@ interface Props {
60
56
  disabled?: boolean;
61
57
  label: string;
62
58
  labelPlacement?: LabelPlacement;
59
+ labelProps?: TypographyTypeMap['props'];
63
60
  onChange?: (e) => void;
64
61
  }
65
62
 
@@ -71,7 +68,8 @@ const Checkbox = ({
71
68
  className,
72
69
  disabled = false,
73
70
  labelPlacement = 'end',
74
- dataTestId
71
+ dataTestId,
72
+ labelProps
75
73
  }: Props): JSX.Element => {
76
74
  const { classes, cx } = useStyles({
77
75
  hasIcon: !!Icon,
@@ -96,7 +94,11 @@ const Checkbox = ({
96
94
  }
97
95
  data-testid={dataTestId || ''}
98
96
  key={label}
99
- label={<Typography className={classes.label}>{label}</Typography>}
97
+ label={
98
+ <Typography classes={{ root: classes.label }} {...labelProps}>
99
+ {label}
100
+ </Typography>
101
+ }
100
102
  labelPlacement={labelPlacement}
101
103
  sx={{ margin: 0, padding: 0 }}
102
104
  />
@@ -1,15 +1,19 @@
1
1
  import { equals, includes } from 'ramda';
2
2
  import { makeStyles } from 'tss-react/mui';
3
3
 
4
- import { FormGroup } from '@mui/material';
4
+ import FormGroup, { FormGroupProps } from '@mui/material/FormGroup';
5
+ import { TypographyTypeMap } from '@mui/material/Typography';
5
6
 
6
7
  import Checkbox, { LabelPlacement } from '../Checkbox';
7
8
 
8
9
  interface Props {
10
+ className?: string;
9
11
  dataTestId?: string;
10
12
  direction?: 'horizontal' | 'vertical';
11
13
  disabled?: boolean;
14
+ formGroupProps?: FormGroupProps;
12
15
  labelPlacement?: LabelPlacement;
16
+ labelProps?: TypographyTypeMap['props'];
13
17
  onChange?: (e) => void;
14
18
  options: Array<string>;
15
19
  values: Array<string>;
@@ -34,27 +38,32 @@ const CheckboxGroup = ({
34
38
  onChange,
35
39
  labelPlacement = 'end',
36
40
  disabled = false,
37
- dataTestId
41
+ dataTestId,
42
+ className,
43
+ labelProps,
44
+ formGroupProps
38
45
  }: Props): JSX.Element => {
39
- const { classes } = useStyles();
46
+ const { classes, cx } = useStyles();
40
47
 
41
48
  const row = !equals(direction, 'vertical');
42
49
 
43
50
  return (
44
51
  <FormGroup
45
- className={classes.container}
52
+ classes={{ root: classes.container }}
46
53
  data-testid={dataTestId || ''}
47
54
  row={row}
55
+ {...formGroupProps}
48
56
  >
49
57
  {options.map((value) => {
50
58
  return (
51
59
  <Checkbox
52
60
  checked={includes(value, values)}
53
- className={classes.checkbox}
61
+ className={cx(classes.checkbox, className)}
54
62
  disabled={disabled}
55
63
  key={value}
56
64
  label={value}
57
65
  labelPlacement={labelPlacement}
66
+ labelProps={labelProps}
58
67
  onChange={onChange}
59
68
  />
60
69
  );
@@ -69,6 +69,9 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
69
69
  export const useDashboardItemStyles = makeStyles<{ hasHeader: boolean }>()(
70
70
  (theme, { hasHeader }) => ({
71
71
  widgetContainer: {
72
+ '&[data-padding="false"]': {
73
+ padding: 0
74
+ },
72
75
  background: theme.palette.background.widget,
73
76
  border: 'none',
74
77
  borderRadius: theme.spacing(1),
@@ -67,7 +67,10 @@ const Item = forwardRef(
67
67
  width: `calc(${prop('width', style) || '0px'} - 12px)`
68
68
  }}
69
69
  >
70
- <Card className={classes.widgetContainer}>
70
+ <Card
71
+ className={classes.widgetContainer}
72
+ data-padding={!disablePadding}
73
+ >
71
74
  {header && (
72
75
  <div
73
76
  {...listeners}
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { Dispatch, SetStateAction, useEffect, useState } from 'react';
2
2
 
3
3
  import { makeStyles } from 'tss-react/mui';
4
4
 
@@ -9,6 +9,7 @@ import {
9
9
  PopperPlacementType,
10
10
  useTheme
11
11
  } from '@mui/material';
12
+ import { PopperProps } from '@mui/material/Popper';
12
13
 
13
14
  import { IconButton } from '..';
14
15
 
@@ -19,15 +20,22 @@ const useStyles = makeStyles()(() => ({
19
20
  }
20
21
  }));
21
22
 
23
+ interface PopoverData {
24
+ anchorEl: HTMLElement | undefined;
25
+ setAnchorEl: Dispatch<SetStateAction<HTMLElement | undefined>>;
26
+ }
27
+
22
28
  interface Props {
23
29
  canOpen?: boolean;
24
30
  children: (props?) => JSX.Element;
25
31
  className?: string;
26
32
  dataTestId?: string;
33
+ getPopoverData?: (data: PopoverData) => void;
27
34
  icon: JSX.Element;
28
35
  onClose?: () => void;
29
36
  onOpen?: () => void;
30
37
  popperPlacement?: PopperPlacementType;
38
+ popperProps?: Partial<PopperProps>;
31
39
  title?: string;
32
40
  }
33
41
 
@@ -40,20 +48,21 @@ const PopoverMenu = ({
40
48
  onClose,
41
49
  canOpen = true,
42
50
  className,
43
- dataTestId
51
+ dataTestId,
52
+ getPopoverData,
53
+ popperProps
44
54
  }: Props): JSX.Element => {
45
55
  const theme = useTheme();
46
56
  const { classes, cx } = useStyles();
47
57
  const [anchorEl, setAnchorEl] = useState<HTMLElement | undefined>();
48
-
49
58
  const isOpen = Boolean(anchorEl);
50
59
 
51
60
  const close = (reason?): void => {
52
61
  const isClosedByInputClick = reason?.type === 'mousedown';
53
-
54
62
  if (isClosedByInputClick) {
55
63
  return;
56
64
  }
65
+
57
66
  onClose?.();
58
67
  setAnchorEl(undefined);
59
68
  };
@@ -75,6 +84,10 @@ const PopoverMenu = ({
75
84
  }
76
85
  }, [canOpen]);
77
86
 
87
+ useEffect(() => {
88
+ getPopoverData?.({ anchorEl, setAnchorEl });
89
+ }, [anchorEl]);
90
+
78
91
  return (
79
92
  <>
80
93
  <IconButton
@@ -97,6 +110,7 @@ const PopoverMenu = ({
97
110
  style={{ zIndex: theme.zIndex.tooltip }}
98
111
  onResize={(): undefined => undefined}
99
112
  onResizeCapture={(): undefined => undefined}
113
+ {...popperProps}
100
114
  >
101
115
  <Paper>{children({ close })}</Paper>
102
116
  </Popper>
@@ -21,7 +21,7 @@ import {
21
21
  GetConditionsSearchQueryParameterValueState
22
22
  } from './models';
23
23
 
24
- const getFoundFields = ({
24
+ export const getFoundFields = ({
25
25
  value,
26
26
  fields
27
27
  }: RegexSearchParameter): Array<SearchMatch> => {
@@ -108,7 +108,7 @@ const getConditionsSearchQueryParameterValue = (
108
108
  };
109
109
  };
110
110
 
111
- const getSearchQueryParameterValue = (
111
+ export const getSearchQueryParameterValue = (
112
112
  search: SearchParameter | undefined
113
113
  ): SearchQueryParameterValue => {
114
114
  if (search === undefined) {
@@ -134,5 +134,3 @@ const getSearchQueryParameterValue = (
134
134
 
135
135
  return { $and: result };
136
136
  };
137
-
138
- export default getSearchQueryParameterValue;
@@ -1,7 +1,7 @@
1
1
  import toRawQueryParameters from '../../queryParameters';
2
2
  import { QueryParameter } from '../../queryParameters/models';
3
3
 
4
- import getSearchQueryParameterValue from './getSearchQueryParameterValue';
4
+ import { getSearchQueryParameterValue } from './getSearchQueryParameterValue';
5
5
  import { Parameters, BuildListingEndpointParameters } from './models';
6
6
 
7
7
  const getQueryParameters = ({
@@ -14,6 +14,7 @@ import { has, includes, not, omit } from 'ramda';
14
14
 
15
15
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
16
16
  import useSnackbar from '../../Snackbar/useSnackbar';
17
+ import { useDeepCompare } from '../../utils';
17
18
 
18
19
  export interface UseFetchQueryProps<T> {
19
20
  catchError?: (props: CatchErrorProps) => void;
@@ -162,7 +163,7 @@ const useFetchQuery = <T extends object>({
162
163
 
163
164
  useEffect(() => {
164
165
  manageError();
165
- }, [queryData.data]);
166
+ }, useDeepCompare([queryData.data]));
166
167
 
167
168
  return {
168
169
  ...omit(['data'], queryData),
@@ -12,6 +12,7 @@ import { includes } from 'ramda';
12
12
 
13
13
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
14
14
  import useSnackbar from '../../Snackbar/useSnackbar';
15
+ import { useDeepCompare } from '../../utils';
15
16
 
16
17
  export enum Method {
17
18
  DELETE = 'DELETE',
@@ -98,7 +99,7 @@ const useMutationQuery = <T extends object, TMeta>({
98
99
 
99
100
  useEffect(() => {
100
101
  manageError();
101
- }, [queryData.data]);
102
+ }, useDeepCompare([queryData.data]));
102
103
 
103
104
  return {
104
105
  ...queryData,
@@ -14,6 +14,7 @@ import {
14
14
  } from '@mui/icons-material';
15
15
 
16
16
  import { IconButton } from '../../Button';
17
+ import { ConfirmationTooltip } from '../../Tooltip/ConfirmationTooltip';
17
18
 
18
19
  import { useStyles } from './DataTableItem.styles';
19
20
 
@@ -21,6 +22,13 @@ export interface DataTableItemProps {
21
22
  description?: string;
22
23
  hasActions?: boolean;
23
24
  hasCardAction?: boolean;
25
+ labelsDelete: {
26
+ cancel: string;
27
+ confirm: {
28
+ label: string;
29
+ secondaryLabel?: string;
30
+ };
31
+ };
24
32
  onClick?: () => void;
25
33
  onDelete?: () => void;
26
34
  onEdit?: () => void;
@@ -38,7 +46,8 @@ const DataTableItem = forwardRef(
38
46
  onClick,
39
47
  onEdit,
40
48
  onDelete,
41
- onEditAccessRights
49
+ onEditAccessRights,
50
+ labelsDelete
42
51
  }: DataTableItemProps,
43
52
  ref
44
53
  ): ReactElement => {
@@ -67,14 +76,24 @@ const DataTableItem = forwardRef(
67
76
  {hasActions && (
68
77
  <MuiCardActions>
69
78
  <span>
70
- <IconButton
71
- aria-label="delete"
72
- data-testid="delete"
73
- icon={<DeleteIcon />}
74
- size="small"
75
- variant="ghost"
76
- onClick={() => onDelete?.()}
77
- />
79
+ {onDelete && (
80
+ <ConfirmationTooltip
81
+ confirmVariant="error"
82
+ labels={labelsDelete}
83
+ onConfirm={onDelete}
84
+ >
85
+ {(openTooltip) => (
86
+ <IconButton
87
+ aria-label="delete"
88
+ data-testid="delete"
89
+ icon={<DeleteIcon />}
90
+ size="small"
91
+ variant="ghost"
92
+ onClick={openTooltip}
93
+ />
94
+ )}
95
+ </ConfirmationTooltip>
96
+ )}
78
97
  </span>
79
98
  <span>
80
99
  <IconButton
@@ -15,5 +15,8 @@ export const useStyles = makeStyles()((theme) => ({
15
15
  boxShadow: theme.shadows[3],
16
16
  marginLeft: theme.spacing(2),
17
17
  maxWidth: '350px'
18
+ },
19
+ popper: {
20
+ zIndex: theme.zIndex.tooltip + 1
18
21
  }
19
22
  }));
@@ -52,6 +52,7 @@ export const ConfirmationTooltip = ({
52
52
  {children(isOpen ? close : open)}
53
53
  <Popper
54
54
  anchorEl={anchorElement}
55
+ className={classes.popper}
55
56
  open={isOpen}
56
57
  popperOptions={{
57
58
  placement: 'bottom-start'
@@ -0,0 +1 @@
1
+ export * from './ConfirmationTooltip';
@@ -1 +1,2 @@
1
1
  export * from './Tooltip';
2
+ export * from './ConfirmationTooltip';
package/src/index.ts CHANGED
@@ -75,7 +75,10 @@ export { default as useCancelTokenSource } from './api/useCancelTokenSource';
75
75
  export { getData, patchData, postData, putData, deleteData } from './api';
76
76
  export { default as useRequest } from './api/useRequest';
77
77
  export { default as buildListingEndpoint } from './api/buildListingEndpoint';
78
- export { default as getSearchQueryParameterValue } from './api/buildListingEndpoint/getSearchQueryParameterValue';
78
+ export {
79
+ getSearchQueryParameterValue,
80
+ getFoundFields
81
+ } from './api/buildListingEndpoint/getSearchQueryParameterValue';
79
82
  export type {
80
83
  Parameters as ListingParameters,
81
84
  BuildListingEndpointParameters,
@@ -140,7 +143,12 @@ export * from './TopCounterElements';
140
143
  export { default as Image, ImageVariant } from './Image/Image';
141
144
  export { default as WallpaperPage } from './WallpaperPage';
142
145
  export { RichTextEditor } from './RichTextEditor';
143
- export { default as ActionsList } from './ActionsList';
146
+ export {
147
+ default as ActionsList,
148
+ ActionsListActionDivider
149
+ } from './ActionsList';
150
+ export type { ActionsListActions } from './ActionsList';
151
+ export type { ActionVariants } from './ActionsList/models';
144
152
 
145
153
  export type { ResponseError, CatchErrorProps } from './api/customFetch';
146
154
 
@@ -1,4 +1,4 @@
1
- import { useRef, useCallback } from 'react';
1
+ import { useRef, useCallback, useEffect } from 'react';
2
2
 
3
3
  interface Props {
4
4
  functionToDebounce: (...args) => void;
@@ -12,6 +12,11 @@ export const useDebounce = ({
12
12
  memoProps = []
13
13
  }: Props): ((...args) => void) => {
14
14
  const timeoutRef = useRef<number | null>(null);
15
+ const ref = useRef();
16
+
17
+ useEffect(() => {
18
+ ref.current = functionToDebounce;
19
+ }, [functionToDebounce]);
15
20
 
16
21
  return useCallback((...args): void => {
17
22
  if (timeoutRef.current) {
@@ -19,7 +24,7 @@ export const useDebounce = ({
19
24
  }
20
25
 
21
26
  timeoutRef.current = setTimeout(() => {
22
- functionToDebounce(...args);
27
+ ref.current?.(...args);
23
28
  }, wait);
24
29
  }, memoProps);
25
30
  };