@centreon/ui 24.4.72 → 24.4.74

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 (131) hide show
  1. package/package.json +24 -23
  2. package/src/Checkbox/fonts/roboto-bold-webfont.ttf +0 -0
  3. package/src/Checkbox/fonts/roboto-bold-webfont.woff +0 -0
  4. package/src/Checkbox/fonts/roboto-bold-webfont.woff2 +0 -0
  5. package/src/Checkbox/fonts/roboto-light-webfont.ttf +0 -0
  6. package/src/Checkbox/fonts/roboto-light-webfont.woff +0 -0
  7. package/src/Checkbox/fonts/roboto-light-webfont.woff2 +0 -0
  8. package/src/Checkbox/fonts/roboto-medium-webfont.ttf +0 -0
  9. package/src/Checkbox/fonts/roboto-medium-webfont.woff +0 -0
  10. package/src/Checkbox/fonts/roboto-medium-webfont.woff2 +0 -0
  11. package/src/Checkbox/fonts/roboto-regular-webfont.ttf +0 -0
  12. package/src/Checkbox/fonts/roboto-regular-webfont.woff +0 -0
  13. package/src/Checkbox/fonts/roboto-regular-webfont.woff2 +0 -0
  14. package/src/Dashboard/Item.tsx +2 -11
  15. package/src/Dashboard/Layout.tsx +2 -4
  16. package/src/Dashboard/utils.ts +1 -1
  17. package/src/Form/Inputs/Grid.tsx +8 -4
  18. package/src/Form/Inputs/models.ts +15 -14
  19. package/src/Graph/BarStack/BarStack.cypress.spec.tsx +87 -9
  20. package/src/Graph/BarStack/BarStack.stories.tsx +4 -2
  21. package/src/Graph/BarStack/BarStack.styles.ts +59 -30
  22. package/src/Graph/BarStack/Graph.tsx +176 -0
  23. package/src/Graph/BarStack/GraphAndLegend.tsx +119 -0
  24. package/src/Graph/BarStack/ResponsiveBarStack.tsx +62 -157
  25. package/src/Graph/BarStack/constants.ts +5 -0
  26. package/src/Graph/BarStack/models.ts +1 -1
  27. package/src/Graph/BarStack/useGraphAndLegend.ts +86 -0
  28. package/src/Graph/BarStack/useResponsiveBarStack.ts +74 -99
  29. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +19 -2
  30. package/src/Graph/HeatMap/model.ts +5 -1
  31. package/src/Graph/Legend/Legend.styles.ts +10 -0
  32. package/src/Graph/Legend/Legend.tsx +6 -1
  33. package/src/Graph/LineChart/BasicComponents/Lines/RegularLines/index.tsx +2 -1
  34. package/src/Graph/LineChart/BasicComponents/Lines/StackedLines/index.tsx +3 -4
  35. package/src/Graph/LineChart/Icons/Downtime.tsx +3 -3
  36. package/src/Graph/LineChart/InteractiveComponents/ZoomPreview/index.tsx +2 -1
  37. package/src/Graph/LineChart/Legend/Legend.styles.ts +16 -5
  38. package/src/Graph/LineChart/Legend/LegendHeader.tsx +4 -1
  39. package/src/Graph/LineChart/Legend/index.tsx +12 -5
  40. package/src/Graph/LineChart/LineChart.cypress.spec.tsx +53 -0
  41. package/src/Graph/LineChart/LineChart.tsx +10 -9
  42. package/src/Graph/LineChart/index.stories.tsx +13 -0
  43. package/src/Graph/LineChart/mockedData/curvesWithSameColor.json +252 -0
  44. package/src/Graph/LineChart/useLineChartData.ts +68 -18
  45. package/src/Graph/PieChart/ResponsivePie.tsx +3 -1
  46. package/src/Graph/PieChart/models.ts +1 -0
  47. package/src/Graph/Tree/DescendantNodes.tsx +0 -1
  48. package/src/Graph/Tree/Links.tsx +2 -15
  49. package/src/Graph/Tree/Tree.cypress.spec.tsx +0 -24
  50. package/src/Graph/Tree/Tree.stories.tsx +1 -17
  51. package/src/Graph/Tree/models.ts +0 -3
  52. package/src/Graph/common/utils.ts +51 -2
  53. package/src/Icon/BaseIcon.tsx +32 -0
  54. package/src/Icon/DowntimeIcon.tsx +14 -0
  55. package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +21 -1
  56. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +2 -2
  57. package/src/InputField/Select/Autocomplete/Connected/index.tsx +10 -7
  58. package/src/InputField/Select/Autocomplete/Multi/index.stories.tsx +19 -0
  59. package/src/InputField/Select/Autocomplete/Multi/index.tsx +8 -5
  60. package/src/InputField/Text/index.tsx +7 -5
  61. package/src/Listing/ActionBar/index.tsx +1 -0
  62. package/src/PopoverMenu/index.tsx +4 -4
  63. package/src/RichTextEditor/ContentEditable.tsx +195 -195
  64. package/src/StoryBookThemeProvider/index.tsx +35 -36
  65. package/src/ThemeProvider/index.tsx +12 -12
  66. package/src/ThemeProvider/palettes.ts +11 -8
  67. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +2 -3
  68. package/src/TimePeriods/DateTimePickerInput.tsx +4 -1
  69. package/src/TopCounterElements/TopCounterLayout.tsx +4 -3
  70. package/src/TopCounterElements/useCloseOnLegacyPage.tsx +9 -6
  71. package/src/api/buildListingEndpoint/getSearchQueryParameterValue.ts +7 -1
  72. package/src/api/buildListingEndpoint/models.ts +1 -0
  73. package/src/api/useGraphQuery/index.ts +1 -7
  74. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +13 -27
  75. package/src/components/Form/AccessRights/AccessRights.stories.tsx +19 -0
  76. package/src/components/Form/AccessRights/AccessRights.styles.ts +1 -1
  77. package/src/components/Form/AccessRights/AccessRights.tsx +5 -6
  78. package/src/components/Form/AccessRights/Actions/Actions.styles.ts +7 -3
  79. package/src/components/Form/AccessRights/Actions/Actions.tsx +32 -15
  80. package/src/components/Form/AccessRights/Actions/useActions.ts +37 -4
  81. package/src/components/Form/AccessRights/models.ts +3 -0
  82. package/src/components/Form/AccessRights/storiesData.ts +3 -0
  83. package/src/components/List/Item/ListItem.styles.ts +2 -2
  84. package/src/components/Zoom/Minimap.tsx +2 -4
  85. package/src/components/Zoom/Zoom.cypress.spec.tsx +13 -13
  86. package/src/components/Zoom/Zoom.tsx +1 -4
  87. package/src/components/Zoom/ZoomContent.tsx +2 -5
  88. package/src/components/index.ts +0 -1
  89. package/src/fonts/roboto-bold-webfont.ttf +0 -0
  90. package/src/fonts/roboto-bold-webfont.woff +0 -0
  91. package/src/fonts/roboto-bold-webfont.woff2 +0 -0
  92. package/src/fonts/roboto-light-webfont.ttf +0 -0
  93. package/src/fonts/roboto-light-webfont.woff +0 -0
  94. package/src/fonts/roboto-light-webfont.woff2 +0 -0
  95. package/src/fonts/roboto-medium-webfont.ttf +0 -0
  96. package/src/fonts/roboto-medium-webfont.woff +0 -0
  97. package/src/fonts/roboto-medium-webfont.woff2 +0 -0
  98. package/src/fonts/roboto-regular-webfont.ttf +0 -0
  99. package/src/fonts/roboto-regular-webfont.woff +0 -0
  100. package/src/fonts/roboto-regular-webfont.woff2 +0 -0
  101. package/src/index.ts +1 -0
  102. package/src/utils/index.ts +25 -25
  103. package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +3 -0
  104. package/src/utils/useInfiniteScrollListing.ts +6 -1
  105. package/src/utils/useLocale/index.ts +10 -0
  106. package/src/utils/useLocale/useLocale.cypress.spec.tsx +40 -0
  107. package/src/utils/useLocaleDateTimeFormat/index.ts +5 -2
  108. package/src/utils/usePluralizedTranslation.ts +4 -21
  109. package/src/@assets/icons/downtime.icon.svg +0 -1
  110. package/src/components/Form/AccessRights/useAccessRightsChange.ts +0 -30
  111. package/src/components/Form/AccessRights/utils.ts +0 -18
  112. package/src/components/Tabs/Tab.styles.ts +0 -25
  113. package/src/components/Tabs/TabPanel.tsx +0 -22
  114. package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -70
  115. package/src/components/Tabs/Tabs.stories.tsx +0 -55
  116. package/src/components/Tabs/Tabs.tsx +0 -55
  117. package/src/components/Tabs/index.ts +0 -6
  118. package/src/utils/resourcesStatusURL.ts +0 -166
  119. package/src/utils/usePluralizedTranslation.test.ts +0 -159
  120. /package/{public → src/Button}/fonts/roboto-bold-webfont.ttf +0 -0
  121. /package/{public → src/Button}/fonts/roboto-bold-webfont.woff +0 -0
  122. /package/{public → src/Button}/fonts/roboto-bold-webfont.woff2 +0 -0
  123. /package/{public → src/Button}/fonts/roboto-light-webfont.ttf +0 -0
  124. /package/{public → src/Button}/fonts/roboto-light-webfont.woff +0 -0
  125. /package/{public → src/Button}/fonts/roboto-light-webfont.woff2 +0 -0
  126. /package/{public → src/Button}/fonts/roboto-medium-webfont.ttf +0 -0
  127. /package/{public → src/Button}/fonts/roboto-medium-webfont.woff +0 -0
  128. /package/{public → src/Button}/fonts/roboto-medium-webfont.woff2 +0 -0
  129. /package/{public → src/Button}/fonts/roboto-regular-webfont.ttf +0 -0
  130. /package/{public → src/Button}/fonts/roboto-regular-webfont.woff +0 -0
  131. /package/{public → src/Button}/fonts/roboto-regular-webfont.woff2 +0 -0
@@ -34,7 +34,7 @@ export interface ConnectedAutoCompleteFieldProps<TData> {
34
34
  allowUniqOption?: boolean;
35
35
  baseEndpoint?: string;
36
36
  changeIdValue: (item: TData) => number | string;
37
- conditionField?: keyof SelectEntry;
37
+ exclusionOptionProperty?: keyof SelectEntry;
38
38
  field: string;
39
39
  getEndpoint: ({ search, page }) => string;
40
40
  getRenderedOptionText: (option: TData) => string;
@@ -55,7 +55,7 @@ const ConnectedAutocompleteField = (
55
55
  field,
56
56
  labelKey,
57
57
  open,
58
- conditionField = 'id',
58
+ exclusionOptionProperty = 'id',
59
59
  searchConditions = [],
60
60
  getRenderedOptionText = (option): string => option.name?.toString(),
61
61
  getRequestHeaders,
@@ -106,8 +106,8 @@ const ConnectedAutocompleteField = (
106
106
  ],
107
107
  isPaginated: true,
108
108
  queryOptions: {
109
- cacheTime: 0,
110
109
  enabled: false,
110
+ gcTime: 0,
111
111
  staleTime: 0,
112
112
  suspense: false
113
113
  }
@@ -134,10 +134,11 @@ const ConnectedAutocompleteField = (
134
134
  : [selectedValue];
135
135
 
136
136
  return {
137
- field: conditionField,
137
+ field,
138
+ operator: '$and',
138
139
  values: {
139
140
  $ni: map(
140
- prop(conditionField),
141
+ prop(exclusionOptionProperty),
141
142
  selectedValues as Array<
142
143
  Record<keyof SelectEntry, string | undefined>
143
144
  >
@@ -155,6 +156,7 @@ const ConnectedAutocompleteField = (
155
156
 
156
157
  return {
157
158
  field,
159
+ operator: '$and',
158
160
  values: {
159
161
  $lk: `%${searchedValue}%`
160
162
  }
@@ -322,12 +324,13 @@ const ConnectedAutocompleteField = (
322
324
  <AutocompleteField
323
325
  filterOptions={(opt): SelectEntry => opt}
324
326
  loading={isFetching}
325
- open={optionsOpen}
326
327
  options={
327
328
  allowUniqOption ? uniqBy(getRenderedOptionText, options) : options
328
329
  }
329
330
  renderOption={renderOptions}
330
- onChange={(_, value) => setAutocompleteChangedValue(value)}
331
+ onChange={(_, value) => {
332
+ setAutocompleteChangedValue(value);
333
+ }}
331
334
  onClose={(): void => setOptionsOpen(false)}
332
335
  onOpen={(): void => setOptionsOpen(true)}
333
336
  onTextChange={changeText}
@@ -58,3 +58,22 @@ export const popoverWithoutInput = (): JSX.Element => {
58
58
  />
59
59
  );
60
60
  };
61
+
62
+ export const customRenderedTags = (): JSX.Element => {
63
+ const customRender = (tags: React.ReactNode): React.ReactNode => (
64
+ <div style={{ display: 'flex' }}>
65
+ {tags}
66
+ <span style={{ color: '#999' }}>Custom wrapper</span>
67
+ </div>
68
+ );
69
+
70
+ return (
71
+ <MultiAutocompleteField
72
+ customRenderTags={customRender}
73
+ label="Custom Tags Render"
74
+ options={options}
75
+ placeholder="Type here..."
76
+ value={[options[0], options[1]]}
77
+ />
78
+ );
79
+ };
@@ -9,10 +9,6 @@ import { SelectEntry } from '../..';
9
9
  import Option from '../../Option';
10
10
 
11
11
  const useStyles = makeStyles()((theme) => ({
12
- checkbox: {
13
- marginRight: theme.spacing(1),
14
- padding: 0
15
- },
16
12
  deleteIcon: {
17
13
  height: theme.spacing(1.5),
18
14
  width: theme.spacing(1.5)
@@ -33,6 +29,7 @@ export interface Props
33
29
  'multiple'
34
30
  > {
35
31
  chipProps?: ChipProps;
32
+ customRenderTags?: (tags: React.ReactNode) => React.ReactNode;
36
33
  disableSortedOptions?: boolean;
37
34
  getOptionTooltipLabel?: (option) => string;
38
35
  getTagLabel?: (option) => string;
@@ -48,6 +45,7 @@ const MultiAutocompleteField = ({
48
45
  getTagLabel = (option): string => option[optionProperty],
49
46
  getOptionTooltipLabel,
50
47
  chipProps,
48
+ customRenderTags,
51
49
  ...props
52
50
  }: Props): JSX.Element => {
53
51
  const { classes } = useStyles();
@@ -65,6 +63,7 @@ const MultiAutocompleteField = ({
65
63
  deleteIcon: classes.deleteIcon,
66
64
  root: classes.tag
67
65
  }}
66
+ data-testid={`tag-option-chip-${option.id}`}
68
67
  label={getTagLabel(option)}
69
68
  size="medium"
70
69
  {...getTagProps({ index })}
@@ -106,7 +105,11 @@ const MultiAutocompleteField = ({
106
105
  <Option checkboxSelected={selected}>{getOptionLabel(option)}</Option>
107
106
  </li>
108
107
  )}
109
- renderTags={renderTags}
108
+ renderTags={(renderedValue, getTagProps): React.ReactNode =>
109
+ customRenderTags
110
+ ? customRenderTags(renderTags(renderedValue, getTagProps))
111
+ : renderTags(renderedValue, getTagProps)
112
+ }
110
113
  value={value}
111
114
  {...props}
112
115
  />
@@ -4,13 +4,13 @@ import { equals, isNil } from 'ramda';
4
4
  import { makeStyles } from 'tss-react/mui';
5
5
 
6
6
  import {
7
- TextField as MuiTextField,
7
+ Box,
8
8
  InputAdornment,
9
+ TextField as MuiTextField,
9
10
  TextFieldProps,
10
11
  Theme,
11
12
  Tooltip,
12
- Typography,
13
- Box
13
+ Typography
14
14
  } from '@mui/material';
15
15
 
16
16
  import { getNormalizedId } from '../../utils';
@@ -84,6 +84,7 @@ export type TextProps = {
84
84
  displayErrorInTooltip?: boolean;
85
85
  error?: string;
86
86
  externalValueForAutoSize?: string;
87
+ forceUncontrolled?: boolean;
87
88
  open?: boolean;
88
89
  required?: boolean;
89
90
  size?: SizeVariant;
@@ -113,6 +114,7 @@ const TextField = forwardRef(
113
114
  required = false,
114
115
  containerClassName,
115
116
  type,
117
+ forceUncontrolled,
116
118
  ...rest
117
119
  }: TextProps,
118
120
  ref: React.ForwardedRef<HTMLDivElement>
@@ -131,7 +133,7 @@ const TextField = forwardRef(
131
133
  const tooltipTitle = displayErrorInTooltip && !isNil(error) ? error : '';
132
134
 
133
135
  const getValueProps = useCallback((): object => {
134
- if (debounced) {
136
+ if (debounced || forceUncontrolled) {
135
137
  return {};
136
138
  }
137
139
 
@@ -140,7 +142,7 @@ const TextField = forwardRef(
140
142
  }
141
143
 
142
144
  return { value: innerValue };
143
- }, [innerValue, debounced, defaultValue]);
145
+ }, [innerValue, debounced, defaultValue, forceUncontrolled]);
144
146
 
145
147
  return (
146
148
  <Box
@@ -28,6 +28,7 @@ const useStyles = makeStyles<StyleProps>()(
28
28
  paddingLeft: theme.spacing(1)
29
29
  },
30
30
  actions: {
31
+ flex: 1,
31
32
  padding: theme.spacing(1, 0)
32
33
  },
33
34
  container: {
@@ -1,4 +1,4 @@
1
- import { Dispatch, SetStateAction, useEffect, useState } from 'react';
1
+ import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
2
2
 
3
3
  import { makeStyles } from 'tss-react/mui';
4
4
 
@@ -6,9 +6,9 @@ import {
6
6
  ClickAwayListener,
7
7
  Paper,
8
8
  Popper,
9
- PopperPlacementType
9
+ type PopperPlacementType
10
10
  } from '@mui/material';
11
- import { PopperProps } from '@mui/material/Popper';
11
+ import type { PopperProps } from '@mui/material/Popper';
12
12
 
13
13
  import { IconButton } from '..';
14
14
 
@@ -80,7 +80,7 @@ const PopoverMenu = ({
80
80
  };
81
81
 
82
82
  useEffect(() => {
83
- if (!canOpen) {
83
+ if (!canOpen && isOpen) {
84
84
  close();
85
85
  }
86
86
  }, [canOpen]);
@@ -1,195 +1,195 @@
1
- import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
2
-
3
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
- import { makeStyles } from 'tss-react/mui';
5
- import { isEmpty, isNil } from 'ramda';
6
- import { useTranslation } from 'react-i18next';
7
-
8
- import { Typography } from '@mui/material';
9
-
10
- interface StyleProps {
11
- editable: boolean;
12
- error?: string;
13
- minInputHeight: number;
14
- }
15
-
16
- const useStyles = makeStyles<StyleProps>()(
17
- (theme, { minInputHeight, editable, error }) => ({
18
- container: {
19
- backgroundColor: theme.palette.background.paper,
20
- border: error
21
- ? `1px solid ${theme.palette.error.main}`
22
- : '1px solid transparent',
23
- borderRadius: theme.shape.borderRadius,
24
- padding: theme.spacing(0.5, 1)
25
- },
26
- emptyInput: {
27
- marginTop: '-22px'
28
- },
29
- input: {
30
- minHeight: editable ? minInputHeight : 'min-content',
31
- outline: '0px solid transparent'
32
- },
33
- inputFocused: {
34
- border: error
35
- ? `1px solid ${theme.palette.error.main}`
36
- : `1px solid ${theme.palette.primary.main}`
37
- },
38
- notEditable: {
39
- backgroundColor: 'transparent'
40
- },
41
- placeholder: {
42
- color: theme.palette.grey[500],
43
- pointerEvents: 'none'
44
- },
45
- root: {
46
- '& p,h1,h2,h3,h4,h5,h6,span': {
47
- margin: 0
48
- }
49
- }
50
- })
51
- );
52
-
53
- interface Props {
54
- className?: string;
55
- disabled?: boolean;
56
- editable: boolean;
57
- editorState?: string;
58
- error?: string;
59
- hasInitialTextContent?: boolean;
60
- initialEditorState?: string;
61
- initialize?: (editor) => void;
62
- inputClassname?: string;
63
- minInputHeight: number;
64
- namespace: string;
65
- onBlur?: (e: string) => void;
66
- placeholder: string;
67
- resetEditorToInitialStateCondition?: () => boolean;
68
- }
69
-
70
- const defaultState =
71
- '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
72
-
73
- const ContentEditable = ({
74
- minInputHeight,
75
- inputClassname,
76
- placeholder,
77
- hasInitialTextContent,
78
- editable,
79
- editorState,
80
- namespace,
81
- resetEditorToInitialStateCondition,
82
- initialEditorState,
83
- error,
84
- onBlur,
85
- className,
86
- disabled,
87
- initialize
88
- }: Props): JSX.Element => {
89
- const { classes, cx } = useStyles({ editable, error, minInputHeight });
90
- const { t } = useTranslation();
91
-
92
- const [editor] = useLexicalComposerContext();
93
- const [isFocused, setFocused] = useState(false);
94
- const [root, setRoot] = useState('');
95
-
96
- const ref = useCallback(
97
- (rootElement: null | HTMLElement) => {
98
- editor.setRootElement(rootElement);
99
- },
100
- [editor]
101
- );
102
-
103
- useLayoutEffect(() => {
104
- if (!editable) {
105
- const newEditorState = editor.parseEditorState(
106
- editorState || defaultState
107
- );
108
-
109
- editor.setEditorState(newEditorState);
110
- }
111
- }, [editor, editorState]);
112
-
113
- useEffect(() => {
114
- editor.registerTextContentListener((currentRoot) => {
115
- setRoot(currentRoot);
116
- });
117
-
118
- if (!hasInitialTextContent) {
119
- return;
120
- }
121
-
122
- setRoot(' ');
123
- }, [editor]);
124
-
125
- useEffect(() => {
126
- const shouldResetEditorToInitialState =
127
- resetEditorToInitialStateCondition?.();
128
-
129
- if (!shouldResetEditorToInitialState) {
130
- return;
131
- }
132
-
133
- const newEditorState = editor.parseEditorState(
134
- initialEditorState || defaultState
135
- );
136
-
137
- editor.setEditorState(newEditorState);
138
- }, [editorState]);
139
-
140
- const isTextEmpty =
141
- isEmpty(root) &&
142
- !editor.getEditorState().toJSON().root.children?.[0]?.children?.length;
143
-
144
- const handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
145
- setFocused(false);
146
- onBlur?.(event);
147
- };
148
-
149
- const isEditable = editor.isEditable();
150
-
151
- useEffect(() => {
152
- if (isNil(disabled)) {
153
- return;
154
- }
155
-
156
- editor.setEditable(!disabled);
157
- }, [disabled]);
158
-
159
- useEffect(() => {
160
- initialize?.(editor);
161
- }, []);
162
-
163
- return (
164
- <div
165
- className={cx(
166
- classes.root,
167
- editable && classes.container,
168
- !isEditable && !disabled && classes.notEditable,
169
- className,
170
- isFocused && classes.inputFocused
171
- )}
172
- >
173
- {editable && isTextEmpty && (
174
- <Typography className={classes.placeholder}>
175
- {t(placeholder)}
176
- </Typography>
177
- )}
178
- <div
179
- aria-label={namespace}
180
- className={cx(
181
- editable && isTextEmpty && classes.emptyInput,
182
- classes.input,
183
- inputClassname
184
- )}
185
- contentEditable={isEditable}
186
- data-testid={namespace}
187
- ref={ref}
188
- onBlur={handleBlur}
189
- onFocus={(): void => setFocused(true)}
190
- />
191
- </div>
192
- );
193
- };
194
-
195
- export default ContentEditable;
1
+ import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
2
+
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { isEmpty, isNil } from 'ramda';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { makeStyles } from 'tss-react/mui';
7
+
8
+ import { Typography } from '@mui/material';
9
+
10
+ interface StyleProps {
11
+ editable: boolean;
12
+ error?: string;
13
+ minInputHeight: number;
14
+ }
15
+
16
+ const useStyles = makeStyles<StyleProps>()(
17
+ (theme, { minInputHeight, editable, error }) => ({
18
+ container: {
19
+ backgroundColor: theme.palette.background.paper,
20
+ border: error
21
+ ? `1px solid ${theme.palette.error.main}`
22
+ : '1px solid transparent',
23
+ borderRadius: theme.shape.borderRadius,
24
+ padding: theme.spacing(0.5, 1)
25
+ },
26
+ emptyInput: {
27
+ marginTop: '-22px'
28
+ },
29
+ input: {
30
+ minHeight: editable ? minInputHeight : 'min-content',
31
+ outline: '0px solid transparent'
32
+ },
33
+ inputFocused: {
34
+ border: error
35
+ ? `1px solid ${theme.palette.error.main}`
36
+ : `1px solid ${theme.palette.primary.main}`
37
+ },
38
+ notEditable: {
39
+ backgroundColor: 'transparent'
40
+ },
41
+ placeholder: {
42
+ color: theme.palette.grey[500],
43
+ pointerEvents: 'none'
44
+ },
45
+ root: {
46
+ '& p,h1,h2,h3,h4,h5,h6,span': {
47
+ margin: 0
48
+ }
49
+ }
50
+ })
51
+ );
52
+
53
+ interface Props {
54
+ className?: string;
55
+ disabled?: boolean;
56
+ editable: boolean;
57
+ editorState?: string;
58
+ error?: string;
59
+ hasInitialTextContent?: boolean;
60
+ initialEditorState?: string;
61
+ initialize?: (editor) => void;
62
+ inputClassname?: string;
63
+ minInputHeight: number;
64
+ namespace: string;
65
+ onBlur?: (e: string) => void;
66
+ placeholder: string;
67
+ resetEditorToInitialStateCondition?: () => boolean;
68
+ }
69
+
70
+ const defaultState =
71
+ '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
72
+
73
+ const ContentEditable = ({
74
+ minInputHeight,
75
+ inputClassname,
76
+ placeholder,
77
+ hasInitialTextContent,
78
+ editable,
79
+ editorState,
80
+ namespace,
81
+ resetEditorToInitialStateCondition,
82
+ initialEditorState,
83
+ error,
84
+ onBlur,
85
+ className,
86
+ disabled,
87
+ initialize
88
+ }: Props): JSX.Element => {
89
+ const { classes, cx } = useStyles({ editable, error, minInputHeight });
90
+ const { t } = useTranslation();
91
+
92
+ const [editor] = useLexicalComposerContext();
93
+ const [isFocused, setFocused] = useState(false);
94
+ const [root, setRoot] = useState('');
95
+
96
+ const ref = useCallback(
97
+ (rootElement: null | HTMLElement) => {
98
+ editor.setRootElement(rootElement);
99
+ },
100
+ [editor]
101
+ );
102
+
103
+ useLayoutEffect(() => {
104
+ if (!editable) {
105
+ const newEditorState = editor.parseEditorState(
106
+ editorState || defaultState
107
+ );
108
+
109
+ editor.setEditorState(newEditorState);
110
+ }
111
+ }, [editor, editorState]);
112
+
113
+ useEffect(() => {
114
+ editor.registerTextContentListener((currentRoot) => {
115
+ setRoot(currentRoot);
116
+ });
117
+
118
+ if (!hasInitialTextContent) {
119
+ return;
120
+ }
121
+
122
+ setRoot(' ');
123
+ }, [editor]);
124
+
125
+ useEffect(() => {
126
+ const shouldResetEditorToInitialState =
127
+ resetEditorToInitialStateCondition?.();
128
+
129
+ if (!shouldResetEditorToInitialState) {
130
+ return;
131
+ }
132
+
133
+ const newEditorState = editor.parseEditorState(
134
+ initialEditorState || defaultState
135
+ );
136
+
137
+ editor.setEditorState(newEditorState);
138
+ }, [editorState]);
139
+
140
+ const isTextEmpty =
141
+ isEmpty(root) &&
142
+ !editor.getEditorState().toJSON().root.children?.[0]?.children?.length;
143
+
144
+ const handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
145
+ setFocused(false);
146
+ onBlur?.(event);
147
+ };
148
+
149
+ const isEditable = editor.isEditable();
150
+
151
+ useEffect(() => {
152
+ if (isNil(disabled)) {
153
+ return;
154
+ }
155
+
156
+ editor.setEditable(!disabled);
157
+ }, [disabled]);
158
+
159
+ useEffect(() => {
160
+ initialize?.(editor);
161
+ }, []);
162
+
163
+ return (
164
+ <div
165
+ className={cx(
166
+ classes.root,
167
+ editable && classes.container,
168
+ !isEditable && !disabled && classes.notEditable,
169
+ className,
170
+ !disabled && isFocused && classes.inputFocused
171
+ )}
172
+ >
173
+ {editable && isTextEmpty && (
174
+ <Typography className={classes.placeholder}>
175
+ {t(placeholder)}
176
+ </Typography>
177
+ )}
178
+ <div
179
+ aria-label={namespace}
180
+ className={cx(
181
+ editable && isTextEmpty && classes.emptyInput,
182
+ classes.input,
183
+ inputClassname
184
+ )}
185
+ contentEditable={isEditable}
186
+ data-testid={namespace}
187
+ ref={ref}
188
+ onBlur={handleBlur}
189
+ onFocus={(): void => setFocused(true)}
190
+ />
191
+ </div>
192
+ );
193
+ };
194
+
195
+ export default ContentEditable;
@@ -1,36 +1,35 @@
1
- import * as React from 'react';
2
- import { useMemo } from 'react';
3
-
4
- import {
5
- ThemeProvider as MuiThemeProvider,
6
- StyledEngineProvider,
7
- createTheme,
8
- CssBaseline
9
- } from '@mui/material';
10
-
11
- import { ThemeMode } from '@centreon/ui-context';
12
-
13
- import { getTheme } from '../ThemeProvider';
14
-
15
- interface Props {
16
- children: React.ReactElement;
17
- themeMode: ThemeMode;
18
- }
19
-
20
- const StoryBookThemeProvider = ({
21
- children,
22
- themeMode
23
- }: Props): JSX.Element => {
24
- const theme = useMemo(() => createTheme(getTheme(themeMode)), [themeMode]);
25
-
26
- return (
27
- <StyledEngineProvider injectFirst>
28
- <MuiThemeProvider theme={theme}>
29
- {children}
30
- <CssBaseline />
31
- </MuiThemeProvider>
32
- </StyledEngineProvider>
33
- );
34
- };
35
-
36
- export default StoryBookThemeProvider;
1
+ import { useMemo } from 'react';
2
+
3
+ import {
4
+ CssBaseline,
5
+ ThemeProvider as MuiThemeProvider,
6
+ StyledEngineProvider,
7
+ createTheme
8
+ } from '@mui/material';
9
+
10
+ import { ThemeMode } from '@centreon/ui-context';
11
+
12
+ import { getTheme } from '../ThemeProvider';
13
+
14
+ interface Props {
15
+ children: React.ReactElement;
16
+ themeMode: ThemeMode;
17
+ }
18
+
19
+ const StoryBookThemeProvider = ({
20
+ children,
21
+ themeMode
22
+ }: Props): JSX.Element => {
23
+ const theme = useMemo(() => createTheme(getTheme(themeMode)), [themeMode]);
24
+
25
+ return (
26
+ <StyledEngineProvider injectFirst>
27
+ <MuiThemeProvider theme={theme}>
28
+ {children}
29
+ <CssBaseline />
30
+ </MuiThemeProvider>
31
+ </StyledEngineProvider>
32
+ );
33
+ };
34
+
35
+ export default StoryBookThemeProvider;