@centreon/ui 23.10.47 → 23.10.49

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.47",
3
+ "version": "23.10.49",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -71,6 +71,7 @@
71
71
  "@testing-library/jest-dom": "^5.16.5",
72
72
  "@testing-library/react": "^13.4.0",
73
73
  "@testing-library/react-hooks": "^8.0.1",
74
+ "@types/cypress": "^1.1.3",
74
75
  "@types/jest": "^27.5.0",
75
76
  "@types/ramda": "^0.28.12",
76
77
  "@types/react": "^18.0.26",
@@ -100,10 +101,10 @@
100
101
  "vite-plugin-turbosnap": "^1.0.2"
101
102
  },
102
103
  "dependencies": {
103
- "@lexical/link": "0.8.0",
104
- "@lexical/react": "0.8.0",
105
- "@lexical/selection": "0.8.0",
106
- "@lexical/utils": "0.8.0",
104
+ "@lexical/link": "0.11.3",
105
+ "@lexical/react": "0.11.3",
106
+ "@lexical/selection": "0.11.3",
107
+ "@lexical/utils": "0.11.3",
107
108
  "@visx/curve": "^2.1.0",
108
109
  "@visx/pattern": "^3.0.0",
109
110
  "@visx/scale": "^3.0.0",
@@ -113,7 +114,7 @@
113
114
  "anylogger": "^1.0.11",
114
115
  "d3-array": "3.2.0",
115
116
  "humanize-duration": "^3.27.3",
116
- "lexical": "0.8.0",
117
+ "lexical": "0.11.3",
117
118
  "notistack": "^2.0.8",
118
119
  "react-grid-layout": "^1.3.4",
119
120
  "react-html-parser": "^2.0.2",
@@ -1,6 +1,4 @@
1
1
  /* eslint-disable react/no-array-index-key */
2
- import * as React from 'react';
3
-
4
2
  import { makeStyles } from 'tss-react/mui';
5
3
  import { useTranslation } from 'react-i18next';
6
4
  import { equals } from 'ramda';
@@ -15,14 +13,15 @@ import {
15
13
  } from '@mui/material';
16
14
 
17
15
  interface ActionsType {
18
- Icon: (props: SvgIconProps) => JSX.Element;
16
+ Icon?: (props: SvgIconProps) => JSX.Element;
19
17
  label: string;
20
- onClick?: () => void;
18
+ onClick?: (e?) => void;
21
19
  }
22
20
 
23
21
  interface Props {
24
22
  actions: Array<ActionsType | 'divider'>;
25
23
  className?: string;
24
+ listItemClassName?: string;
26
25
  }
27
26
 
28
27
  const useStyles = makeStyles()({
@@ -31,7 +30,11 @@ const useStyles = makeStyles()({
31
30
  }
32
31
  });
33
32
 
34
- const ActionsList = ({ className, actions }: Props): JSX.Element => {
33
+ const ActionsList = ({
34
+ className,
35
+ listItemClassName,
36
+ actions
37
+ }: Props): JSX.Element => {
35
38
  const { cx, classes } = useStyles();
36
39
  const { t } = useTranslation();
37
40
 
@@ -45,11 +48,15 @@ const ActionsList = ({ className, actions }: Props): JSX.Element => {
45
48
  const { label, Icon, onClick } = action as ActionsType;
46
49
 
47
50
  return (
48
- <MenuItem aria-label={label} key={label} onClick={onClick}>
49
- <ListItemIcon>
50
- <Icon fontSize="small" />
51
- </ListItemIcon>
52
- <ListItemText>{t(label)}</ListItemText>
51
+ <MenuItem aria-label={label} id={label} key={label} onClick={onClick}>
52
+ {Icon && (
53
+ <ListItemIcon>
54
+ <Icon fontSize="small" />
55
+ </ListItemIcon>
56
+ )}
57
+ <ListItemText className={listItemClassName}>
58
+ {t(label)}
59
+ </ListItemText>
53
60
  </MenuItem>
54
61
  );
55
62
  })}
@@ -66,22 +66,25 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
66
66
  })
67
67
  );
68
68
 
69
- export const useDashboardItemStyles = makeStyles()((theme) => ({
70
- widgetContainer: {
71
- height: '100%',
72
- width: '100%'
73
- },
74
- widgetContent: {
75
- height: '100%'
76
- },
77
- widgetHeader: {
78
- '&:hover': {
79
- backgroundColor: theme.palette.action.hover
69
+ export const useDashboardItemStyles = makeStyles<{ hasHeader: boolean }>()(
70
+ (theme, { hasHeader }) => ({
71
+ widgetContainer: {
72
+ height: '100%',
73
+ width: '100%'
80
74
  },
81
- cursor: 'move',
82
- padding: theme.spacing(0.5, 2)
83
- },
84
- widgetPadding: {
85
- padding: theme.spacing(1, 2)
86
- }
87
- }));
75
+ widgetContent: {
76
+ height: '100%'
77
+ },
78
+ widgetHeader: {
79
+ '&:hover': {
80
+ backgroundColor: theme.palette.action.hover
81
+ },
82
+ cursor: 'move',
83
+ padding: theme.spacing(0.5, 2)
84
+ },
85
+ widgetPadding: {
86
+ overflowX: 'auto',
87
+ padding: hasHeader ? theme.spacing(1, 2, 2) : theme.spacing(1, 2)
88
+ }
89
+ })
90
+ );
@@ -41,10 +41,10 @@ const Item = forwardRef(
41
41
  }: DashboardItemProps,
42
42
  ref: ForwardedRef<HTMLDivElement>
43
43
  ): ReactElement => {
44
- const { classes, cx } = useDashboardItemStyles();
45
-
46
44
  const hasHeader = !isNil(header);
47
45
 
46
+ const { classes, cx } = useDashboardItemStyles({ hasHeader });
47
+
48
48
  const listeners = {
49
49
  onMouseDown,
50
50
  onMouseUp,
@@ -152,7 +152,7 @@ const ContentEditable = ({
152
152
  <div
153
153
  aria-label={namespace}
154
154
  className={cx(
155
- isTextEmpty && classes.emptyInput,
155
+ editable && isTextEmpty && classes.emptyInput,
156
156
  classes.input,
157
157
  inputClassname
158
158
  )}
@@ -1,4 +1,5 @@
1
1
  import RichTextEditor from './RichTextEditor';
2
+ import { standardMacros } from './plugins/ToolbarPlugin/MacrosButton';
2
3
 
3
4
  interface CheckElementStyleOnRichTextEditorProps {
4
5
  check: boolean;
@@ -120,7 +121,7 @@ describe('Rich Text Editor', () => {
120
121
 
121
122
  cy.wrap(element)
122
123
  .should('have.attr', 'rel')
123
- .and('match', /^noopener/);
124
+ .and('match', /^noreferrer/);
124
125
 
125
126
  cy.wrap(element)
126
127
  .should('have.attr', 'target')
@@ -201,4 +202,32 @@ describe('Rich Text Editor', () => {
201
202
  );
202
203
  });
203
204
  });
205
+
206
+ describe('Rich Text Editor with Macros Plugin', () => {
207
+ beforeEach(() => {
208
+ cy.mount({
209
+ Component: <RichTextEditor displayMacrosButton editable />
210
+ });
211
+ });
212
+
213
+ it('displays the Macros button when the "displayMacrosButton" prop is set to true', () => {
214
+ cy.findByLabelText('Macros').should('be.visible');
215
+ cy.findByLabelText('Macros').click();
216
+
217
+ standardMacros.forEach((macro) => {
218
+ cy.findByText(macro).should('be.visible');
219
+ });
220
+ });
221
+
222
+ it('ensures that the selected macro is inserted into the RichTextEditor', () => {
223
+ cy.get('[data-testid="RichTextEditor"]').type('macro : ');
224
+
225
+ cy.findByLabelText('Macros').click({});
226
+
227
+ standardMacros.forEach((macro) => {
228
+ cy.findByText(macro).should('be.visible').click({ force: true });
229
+ cy.get('[data-testid="RichTextEditor"]').should('contain', macro);
230
+ });
231
+ });
232
+ });
204
233
  });
@@ -18,6 +18,7 @@ import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
18
18
 
19
19
  export interface RichTextEditorProps {
20
20
  contentClassName?: string;
21
+ displayMacrosButton?: boolean;
21
22
  editable: boolean;
22
23
  editorState?: string;
23
24
  error?: string;
@@ -92,7 +93,8 @@ const RichTextEditor = ({
92
93
  toolbarPositions = 'start',
93
94
  error,
94
95
  onBlur,
95
- contentClassName
96
+ contentClassName,
97
+ displayMacrosButton = false
96
98
  }: RichTextEditorProps): JSX.Element => {
97
99
  const { classes } = useStyles({ toolbarPositions });
98
100
 
@@ -122,7 +124,11 @@ const RichTextEditor = ({
122
124
  <LexicalComposer initialConfig={initialConfig}>
123
125
  <div className={classes.container}>
124
126
  <div className={classes.toolbar}>
125
- <ToolbarPlugin editable={editable} getEditorState={getEditorState} />
127
+ <ToolbarPlugin
128
+ displayMacrosButton={displayMacrosButton}
129
+ editable={editable}
130
+ getEditorState={getEditorState}
131
+ />
126
132
  </div>
127
133
  <div>
128
134
  <RichTextPlugin
@@ -56,6 +56,11 @@ withCustomPlaceholder.args = {
56
56
  placeholder: 'Custom placeholder...'
57
57
  };
58
58
 
59
+ export const withMacrosPlugin = Template.bind({});
60
+ withMacrosPlugin.args = {
61
+ displayMacrosButton: true
62
+ };
63
+
59
64
  export const withEditableFalse = Template.bind({});
60
65
  withEditableFalse.args = {
61
66
  editable: false,
@@ -106,7 +106,12 @@ const FloatingLinkEditor = ({
106
106
  const yOffset = tooltipPosition.y - rootElementY + 30;
107
107
 
108
108
  return (
109
- <Popper open anchorEl={rootElement} placement="top-start">
109
+ <Popper
110
+ open
111
+ anchorEl={rootElement}
112
+ placement="top-start"
113
+ sx={{ zIndex: 'tooltip' }}
114
+ >
110
115
  <Paper
111
116
  sx={{
112
117
  transform: `translate3d(${xOffset}px, ${
@@ -0,0 +1,87 @@
1
+ import { useState } from 'react';
2
+
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { createCommand, LexicalCommand } from 'lexical';
5
+
6
+ import MacrosIcon from '@mui/icons-material/TerminalOutlined';
7
+
8
+ import { ActionsList, IconButton, PopoverMenu } from '../../..';
9
+
10
+ import { useStyles } from '.';
11
+
12
+ const LowPriority = 1;
13
+
14
+ export const standardMacros = [
15
+ '{{SHORTDATETIME}}',
16
+ '{{LONGDATETIME}}',
17
+ '{{ALIAS}}',
18
+ '{{ID}}',
19
+ '{{NAME}}',
20
+ '{{OUTPUT}}',
21
+ '{{STATE}}',
22
+ '{{STATETYPE}}'
23
+ ];
24
+
25
+ const MacrosButton = (): JSX.Element => {
26
+ const { classes } = useStyles();
27
+ const [arMacrosDiplayed, setAreMacrosDiplayed] = useState(false);
28
+
29
+ const [editor] = useLexicalComposerContext();
30
+
31
+ const INSERT_MACROS_COMMAND: LexicalCommand<string> = createCommand();
32
+
33
+ const onClick = (e): void => {
34
+ const macro = e.target.id || e.target.textContent.trim();
35
+
36
+ editor.dispatchCommand(INSERT_MACROS_COMMAND, macro);
37
+ };
38
+
39
+ editor.registerCommand(
40
+ INSERT_MACROS_COMMAND,
41
+ (payload: string) => {
42
+ if (!editor) {
43
+ return false;
44
+ }
45
+
46
+ document.execCommand('insertText', false, payload);
47
+
48
+ return true;
49
+ },
50
+ LowPriority
51
+ );
52
+
53
+ const displayMacros = (): void => {
54
+ setAreMacrosDiplayed(true);
55
+ };
56
+
57
+ const actions = standardMacros.map((macro) => ({
58
+ label: macro,
59
+ onClick
60
+ }));
61
+
62
+ return (
63
+ <PopoverMenu
64
+ icon={
65
+ <IconButton
66
+ ariaLabel="Macros"
67
+ className={classes.macrosButton}
68
+ title="Macros"
69
+ tooltipPlacement="top"
70
+ onClick={displayMacros}
71
+ >
72
+ <MacrosIcon />
73
+ </IconButton>
74
+ }
75
+ >
76
+ {(): JSX.Element => (
77
+ <div>
78
+ {arMacrosDiplayed && (
79
+ <ActionsList actions={actions} listItemClassName={classes.macros} />
80
+ )}
81
+ </div>
82
+ )}
83
+ </PopoverMenu>
84
+ );
85
+ };
86
+
87
+ export default MacrosButton;
@@ -5,23 +5,35 @@ import { Divider } from '@mui/material';
5
5
 
6
6
  import FormatButtons from './FormatButtons';
7
7
  import UndoRedoButtons from './UndoRedoButtons';
8
+ import MacrosButton from './MacrosButton';
8
9
 
9
10
  interface Props {
11
+ displayMacrosButton?: boolean;
10
12
  editable: boolean;
11
13
  getEditorState?: (editorState: EditorState) => void;
12
14
  }
13
15
 
14
- const useStyles = makeStyles()((theme) => ({
16
+ export const useStyles = makeStyles()((theme) => ({
15
17
  container: {
18
+ alignItems: 'center',
16
19
  columnGap: theme.spacing(1),
17
20
  display: 'flex',
18
21
  marginBottom: theme.spacing(1)
22
+ },
23
+ macros: {
24
+ '& span': {
25
+ fontSize: theme.typography.caption.fontSize
26
+ }
27
+ },
28
+ macrosButton: {
29
+ marginBottom: theme.spacing(0.5)
19
30
  }
20
31
  }));
21
32
 
22
33
  const ToolbarPlugin = ({
23
34
  getEditorState,
24
- editable
35
+ editable,
36
+ displayMacrosButton
25
37
  }: Props): JSX.Element | null => {
26
38
  const { classes } = useStyles();
27
39
 
@@ -30,6 +42,12 @@ const ToolbarPlugin = ({
30
42
  <UndoRedoButtons />
31
43
  <Divider flexItem orientation="vertical" />
32
44
  <FormatButtons getEditorState={getEditorState} />
45
+ {displayMacrosButton && (
46
+ <>
47
+ <Divider flexItem orientation="vertical" />
48
+ <MacrosButton />
49
+ </>
50
+ )}
33
51
  </div>
34
52
  ) : null;
35
53
  };