@centreon/ui 25.1.6 → 25.1.8

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": "25.1.6",
3
+ "version": "25.1.8",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -148,10 +148,10 @@ export const useDashboardItemStyles = makeStyles<{ hasHeader: boolean }>()(
148
148
  '&:hover': {
149
149
  backgroundColor: theme.palette.action.hover
150
150
  },
151
- '&[data-canMove="false"]': {
151
+ '&[data-can-move="false"]': {
152
152
  cursor: 'default'
153
153
  },
154
- '&[data-canMove="true"]': {
154
+ '&[data-can-move="true"]': {
155
155
  cursor: 'move'
156
156
  },
157
157
  padding: theme.spacing(0, 1.5),
@@ -124,7 +124,7 @@ const Item = forwardRef<HTMLDivElement, DashboardItemProps>(
124
124
  {childrenHeader && (
125
125
  <div
126
126
  className={classes.widgetHeader}
127
- data-canMove={canControl}
127
+ data-can-move={canControl}
128
128
  >
129
129
  {canControl && (
130
130
  <div
@@ -1,4 +1,4 @@
1
- import { ButtonProps, DialogContentText, Typography } from '@mui/material';
1
+ import { ButtonProps, DialogContentText, DialogContentTextProps, Typography } from '@mui/material';
2
2
 
3
3
  import Dialog, { Props as DialogProps } from '..';
4
4
 
@@ -8,6 +8,7 @@ type Props = DialogProps & {
8
8
  labelSecondMessage?: string | null;
9
9
  restCancelButtonProps?: ButtonProps;
10
10
  restConfirmButtonProps?: ButtonProps;
11
+ dialogContentTextProps?:DialogContentTextProps
11
12
  };
12
13
 
13
14
  const Confirm = ({
@@ -16,6 +17,7 @@ const Confirm = ({
16
17
  children,
17
18
  restCancelButtonProps,
18
19
  restConfirmButtonProps,
20
+ dialogContentTextProps,
19
21
  ...rest
20
22
  }: Props): JSX.Element => (
21
23
  <Dialog
@@ -23,7 +25,7 @@ const Confirm = ({
23
25
  restConfirmButtonProps={restConfirmButtonProps}
24
26
  {...rest}
25
27
  >
26
- <DialogContentText>
28
+ <DialogContentText {...dialogContentTextProps}>
27
29
  {labelMessage && <Typography>{labelMessage}</Typography>}
28
30
  {labelSecondMessage && <Typography>{labelSecondMessage}</Typography>}
29
31
  {children}
@@ -56,10 +56,12 @@ const Duplicate = ({
56
56
  fullWidth
57
57
  className={classes.container}
58
58
  color="primary"
59
- inputProps={{
60
- 'aria-label': 'Duplications',
61
- max: limit,
62
- min: 1
59
+ slotProps={{
60
+ htmlInput: {
61
+ 'aria-label': 'Duplications',
62
+ max: limit,
63
+ min: 1
64
+ }
63
65
  }}
64
66
  label={labelInput}
65
67
  margin="dense"
@@ -10,6 +10,7 @@ import {
10
10
  DialogContent,
11
11
  DialogProps,
12
12
  DialogTitle,
13
+ DialogTitleProps,
13
14
  Dialog as MuiDialog
14
15
  } from '@mui/material';
15
16
 
@@ -37,6 +38,7 @@ export type Props = {
37
38
  className?: string;
38
39
  confirmDisabled?: boolean;
39
40
  contentWidth?: number;
41
+ dialogTitleProps?:DialogTitleProps
40
42
  dialogActionsClassName?: string;
41
43
  dialogConfirmButtonClassName?: string;
42
44
  dialogContentClassName?: string;
@@ -68,6 +70,7 @@ const Dialog = ({
68
70
  confirmDisabled = false,
69
71
  cancelDisabled = false,
70
72
  submitting = false,
73
+ dialogTitleProps,
71
74
  dialogPaperClassName,
72
75
  dialogTitleClassName,
73
76
  dialogContentClassName,
@@ -90,7 +93,7 @@ const Dialog = ({
90
93
  {...rest}
91
94
  >
92
95
  {labelTitle && (
93
- <DialogTitle className={dialogTitleClassName}>{labelTitle}</DialogTitle>
96
+ <DialogTitle className={dialogTitleClassName} {...dialogTitleProps}>{labelTitle}</DialogTitle>
94
97
  )}
95
98
  {children && (
96
99
  <DialogContent
@@ -66,7 +66,7 @@ const FormButtons = (): JSX.Element => {
66
66
  {t(labelReset)}
67
67
  </Button>
68
68
  <SaveButton
69
- dataTestId={labelSave}
69
+ data-testid={labelSave}
70
70
  disabled={not(canSubmit)}
71
71
  labelLoading={labelSaving}
72
72
  labelSave={labelSave}
@@ -138,10 +138,14 @@ const Text = ({
138
138
  value={value || ''}
139
139
  onBlur={handleBlur(fieldName)}
140
140
  onChange={changeText}
141
- inputProps={{
142
- 'data-testid': dataTestId || label,
143
- 'aria-label': label,
144
- min: text?.min
141
+ textFieldSlotsAndSlotProps={{
142
+ slotProps: {
143
+ htmlInput: {
144
+ 'data-testid': dataTestId || label,
145
+ 'aria-label': label,
146
+ min: text?.min
147
+ }
148
+ }
145
149
  }}
146
150
  />
147
151
  ),
@@ -232,7 +232,7 @@ const Chart = ({
232
232
  [axis?.showGridLines]
233
233
  );
234
234
 
235
- if (!isInViewport && !skipIntersectionObserver) {
235
+ if ((!isInViewport && !skipIntersectionObserver) || !height) {
236
236
  return (
237
237
  <Skeleton
238
238
  height={graphSvgRef?.current?.clientHeight ?? graphHeight}
@@ -1,3 +1,4 @@
1
+ import { Skeleton } from '@mui/material';
1
2
  import { ParentSize } from '../..';
2
3
 
3
4
  import ResponsiveSingleBar from './ResponsiveSingleBar';
@@ -10,14 +11,20 @@ const SingleBar = ({ data, ...props }: SingleBarProps): JSX.Element | null => {
10
11
 
11
12
  return (
12
13
  <ParentSize>
13
- {({ width, height }) => (
14
- <ResponsiveSingleBar
15
- {...props}
16
- data={data}
17
- height={height}
18
- width={width}
19
- />
20
- )}
14
+ {({ width, height }) => {
15
+ if (!height || !width) {
16
+ return <Skeleton height={20} variant="rectangular" width="100%" />;
17
+ }
18
+
19
+ return (
20
+ <ResponsiveSingleBar
21
+ {...props}
22
+ data={data}
23
+ height={height}
24
+ width={width}
25
+ />
26
+ );
27
+ }}
21
28
  </ParentSize>
22
29
  );
23
30
  };
@@ -0,0 +1,22 @@
1
+ import { SvgIconProps } from '@mui/material';
2
+
3
+ import BaseIcon from './BaseIcon';
4
+
5
+ const icon = (
6
+ <g>
7
+ <path d="M22 10.07c-2.24 0-4.01-1.04-5.72-2.04-1.61-.94-3.13-1.83-4.96-1.83s-3.18.92-4.57 1.9c-1.33 1.16-3 1.85-4.76 1.97v-.83c1.7 0 2.95-.88 4.28-1.82 1.41-1.22 3.19-2.05 5.05-2.05 1.95 0 3.8.82 5.38 1.95 1.56 1.11 3.39 1.78 5.3 1.92v.83Z" />
8
+ <path d="M22 14.07c-2.24 0-4.01-1.04-5.72-2.04-1.61-.94-3.13-1.83-4.96-1.83s-3.18.92-4.57 1.9c-1.33 1.16-3 1.85-4.76 1.97v-.83c1.7 0 2.95-.88 4.28-1.82 1.41-1.22 3.18-2.05 5.05-2.05 1.87 0 3.8.82 5.38 1.95 1.56 1.11 3.39 1.78 5.3 1.92v.83Z" />
9
+ <path d="M22 18.07c-2.24 0-4.01-1.04-5.72-2.04-1.61-.94-3.13-1.83-4.96-1.83s-3.18.92-4.57 1.9c-1.33 1.16-3 1.85-4.76 1.97v-.83c1.7 0 2.95-.88 4.28-1.82 1.41-1.22 3.18-2.07 5.05-2.05 1.87.02 3.8.82 5.38 1.95 1.56 1.11 3.39 1.78 5.3 1.92v.83Z" />
10
+ </g>
11
+ );
12
+
13
+ export const FlappingIcon = (props: SvgIconProps): JSX.Element => (
14
+ <BaseIcon
15
+ {...props}
16
+ dataTestId="FlappingIcon"
17
+ Icon={icon}
18
+ height="24"
19
+ viewBox="0 0 24 24"
20
+ width="24"
21
+ />
22
+ );
package/src/Icon/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { DowntimeIcon } from './DowntimeIcon';
2
+ export { FlappingIcon } from './FlappingIcon';
2
3
  export { AcknowledgementIcon } from './AcknowledgementIcon';
3
4
  export { HostIcon } from './HostIcon';
4
5
  export { ServiceIcon } from './ServiceIcon';
@@ -68,8 +68,12 @@ describe('Number field', () => {
68
68
  initialize({
69
69
  dataTestId: 'test',
70
70
  defaultValue: 25,
71
- inputProps: {
72
- min: 2
71
+ textFieldSlotsAndSlotProps: {
72
+ slotProps: {
73
+ htmlInput: {
74
+ min: 2
75
+ }
76
+ }
73
77
  },
74
78
  onChange: cy.stub()
75
79
  });
@@ -31,7 +31,7 @@ const NumberField = ({
31
31
  defaultValue ? `${defaultValue}` : ''
32
32
  );
33
33
 
34
- const { inputProps } = props;
34
+ const { textFieldSlotsAndSlotProps } = props;
35
35
 
36
36
  const changeValue = (event: ChangeEvent<HTMLInputElement>): void => {
37
37
  const inputValue = event.target.value;
@@ -44,8 +44,10 @@ const NumberField = ({
44
44
  T,
45
45
  always(
46
46
  clamp(
47
- inputProps?.min || Number.NEGATIVE_INFINITY,
48
- inputProps?.max || Number.POSITIVE_INFINITY,
47
+ textFieldSlotsAndSlotProps?.slotProps?.htmlInput?.min ||
48
+ Number.NEGATIVE_INFINITY,
49
+ textFieldSlotsAndSlotProps?.slotProps?.htmlInput?.max ||
50
+ Number.POSITIVE_INFINITY,
49
51
  number
50
52
  )
51
53
  )
@@ -59,12 +61,15 @@ const NumberField = ({
59
61
 
60
62
  return (
61
63
  <TextField
62
- defaultValue={defaultValue}
63
64
  type="number"
64
65
  value={actualValue}
65
66
  onChange={changeValue}
66
67
  {...props}
67
- inputProps={inputProps}
68
+ textFieldSlotsAndSlotProps={{
69
+ slotProps: {
70
+ htmlInput: { ...textFieldSlotsAndSlotProps?.slotProps?.htmlInput }
71
+ }
72
+ }}
68
73
  placeholder={
69
74
  placeholder || (!defaultValue ? `${fallbackValue}` : undefined)
70
75
  }
@@ -181,9 +181,19 @@ const DraggableAutocomplete = (
181
181
  {...renderProps}
182
182
  error={error}
183
183
  helperText={error}
184
- inputProps={{
185
- ...renderProps.inputProps,
186
- value: inputText || ''
184
+ textFieldSlotsAndSlotProps={{
185
+ slotProps: {
186
+ input:{
187
+ ...renderProps?.InputProps
188
+ },
189
+ inputLabel:{
190
+ ...renderProps?.inputLabel
191
+ },
192
+ htmlInput: {
193
+ ...renderProps.inputProps,
194
+ value: inputText || ''
195
+ }
196
+ }
187
197
  }}
188
198
  label={label}
189
199
  required={required}
@@ -0,0 +1,92 @@
1
+ import { equals } from 'ramda';
2
+ import { makeStyles } from 'tss-react/mui';
3
+
4
+ import { autocompleteClasses } from '@mui/material/Autocomplete';
5
+
6
+ import { ThemeMode } from '@centreon/ui-context';
7
+
8
+ interface StyledProps {
9
+ hideInput?: boolean;
10
+ }
11
+
12
+ const textfieldHeight = (hideInput?: boolean): number | undefined =>
13
+ hideInput ? 0 : undefined;
14
+
15
+ export const useAutoCompleteStyles = makeStyles<StyledProps>()(
16
+ (theme, { hideInput }) => ({
17
+ hiddenText: {
18
+ transform: 'scale(0)'
19
+ },
20
+ input: {
21
+ '&:after': {
22
+ borderBottom: 0
23
+ },
24
+ '&:before': {
25
+ borderBottom: 0,
26
+ content: 'unset'
27
+ },
28
+ '&:hover:before': {
29
+ borderBottom: 0
30
+ },
31
+ height: textfieldHeight(hideInput)
32
+ },
33
+ inputLabel: {
34
+ '&&': {
35
+ fontSize: theme.typography.body1.fontSize,
36
+ maxWidth: '72%',
37
+ overflow: 'hidden',
38
+ textOverflow: 'ellipsis',
39
+ transform: 'translate(12px, 14px) scale(1)',
40
+ whiteSpace: 'nowrap'
41
+ }
42
+ },
43
+ inputLabelShrink: {
44
+ '&&': {
45
+ maxWidth: '90%'
46
+ }
47
+ },
48
+ inputWithLabel: {
49
+ '&[class*="MuiFilledInput-root"]': {
50
+ paddingTop: theme.spacing(2)
51
+ },
52
+ paddingTop: theme.spacing(1)
53
+ },
54
+ inputWithoutLabel: {
55
+ '&[class*="MuiFilledInput-root"][class*="MuiFilledInput-marginDense"]': {
56
+ paddingBottom: hideInput ? 0 : theme.spacing(0.75),
57
+ paddingRight: hideInput ? 0 : theme.spacing(1),
58
+ paddingTop: hideInput ? 0 : theme.spacing(0.75)
59
+ }
60
+ },
61
+ loadingIndicator: {
62
+ textAlign: 'center'
63
+ },
64
+ options: {
65
+ alignItems: 'center',
66
+ display: 'grid',
67
+ gridAutoFlow: 'column',
68
+ gridGap: theme.spacing(1)
69
+ },
70
+ popper: {
71
+ [`& .${autocompleteClasses.listbox}`]: {
72
+ [`& .${autocompleteClasses.option}`]: {
73
+ [`&:hover, &[aria-selected="true"], &.${autocompleteClasses.focused},
74
+ &.${autocompleteClasses.focused}[aria-selected="true"]`]: {
75
+ background: equals(theme.palette.mode, ThemeMode.dark)
76
+ ? theme.palette.primary.dark
77
+ : theme.palette.primary.light,
78
+ color: equals(theme.palette.mode, ThemeMode.dark)
79
+ ? theme.palette.common.white
80
+ : theme.palette.primary.main
81
+ }
82
+ },
83
+ padding: 0
84
+ },
85
+ zIndex: theme.zIndex.tooltip + 1
86
+ },
87
+ textfield: {
88
+ height: textfieldHeight(hideInput),
89
+ visibility: hideInput ? 'hidden' : 'visible'
90
+ }
91
+ })
92
+ );
@@ -1,25 +1,25 @@
1
1
  import { equals, isEmpty, isNil, pick } from 'ramda';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { makeStyles } from 'tss-react/mui';
4
3
 
5
4
  import {
6
5
  Autocomplete,
7
6
  AutocompleteProps,
8
7
  CircularProgress,
9
8
  InputAdornment,
9
+ InputProps,
10
10
  useTheme
11
11
  } from '@mui/material';
12
- import { autocompleteClasses } from '@mui/material/Autocomplete';
12
+ import { AutocompleteSlotsAndSlotProps } from '@mui/material/Autocomplete';
13
+ import { TextFieldSlotsAndSlotProps } from '@mui/material/TextField';
13
14
  import { UseAutocompleteProps } from '@mui/material/useAutocomplete';
14
15
 
15
- import { ThemeMode } from '@centreon/ui-context';
16
-
17
16
  import { ForwardedRef, HTMLAttributes, ReactElement, forwardRef } from 'react';
18
17
  import { SelectEntry } from '..';
19
18
  import { getNormalizedId } from '../../../utils';
20
19
  import TextField from '../../Text';
21
20
  import { searchLabel } from '../../translatedLabels';
22
21
  import Option from '../Option';
22
+ import { useAutoCompleteStyles } from './autoComplete.styles';
23
23
 
24
24
  export type Props = {
25
25
  autoFocus?: boolean;
@@ -39,96 +39,21 @@ export type Props = {
39
39
  placeholder?: string | undefined;
40
40
  required?: boolean;
41
41
  forceInputRenderValue?: boolean;
42
+ textFieldSlotsAndSlotProps?: TextFieldSlotsAndSlotProps<InputProps>;
43
+ autocompleteSlotsAndSlotProps?: AutocompleteSlotsAndSlotProps<
44
+ SelectEntry,
45
+ Multiple,
46
+ DisableClearable,
47
+ FreeSolo
48
+ >;
42
49
  } & Omit<
43
50
  AutocompleteProps<SelectEntry, Multiple, DisableClearable, FreeSolo>,
44
51
  'renderInput'
45
52
  > &
46
53
  UseAutocompleteProps<SelectEntry, Multiple, DisableClearable, FreeSolo>;
47
54
 
48
- type StyledProps = Partial<Pick<Props, 'hideInput'>>;
49
-
50
- const textfieldHeight = (hideInput?: boolean): number | undefined =>
51
- hideInput ? 0 : undefined;
52
-
53
- const useStyles = makeStyles<StyledProps>()((theme, { hideInput }) => ({
54
- hiddenText: {
55
- transform: 'scale(0)'
56
- },
57
- input: {
58
- '&:after': {
59
- borderBottom: 0
60
- },
61
- '&:before': {
62
- borderBottom: 0,
63
- content: 'unset'
64
- },
65
- '&:hover:before': {
66
- borderBottom: 0
67
- },
68
- height: textfieldHeight(hideInput)
69
- },
70
- inputLabel: {
71
- '&&': {
72
- fontSize: theme.typography.body1.fontSize,
73
- maxWidth: '72%',
74
- overflow: 'hidden',
75
- textOverflow: 'ellipsis',
76
- transform: 'translate(12px, 14px) scale(1)',
77
- whiteSpace: 'nowrap'
78
- }
79
- },
80
- inputLabelShrink: {
81
- '&&': {
82
- maxWidth: '90%'
83
- }
84
- },
85
- inputWithLabel: {
86
- '&[class*="MuiFilledInput-root"]': {
87
- paddingTop: theme.spacing(2)
88
- },
89
- paddingTop: theme.spacing(1)
90
- },
91
- inputWithoutLabel: {
92
- '&[class*="MuiFilledInput-root"][class*="MuiFilledInput-marginDense"]': {
93
- paddingBottom: hideInput ? 0 : theme.spacing(0.75),
94
- paddingRight: hideInput ? 0 : theme.spacing(1),
95
- paddingTop: hideInput ? 0 : theme.spacing(0.75)
96
- }
97
- },
98
- loadingIndicator: {
99
- textAlign: 'center'
100
- },
101
- options: {
102
- alignItems: 'center',
103
- display: 'grid',
104
- gridAutoFlow: 'column',
105
- gridGap: theme.spacing(1)
106
- },
107
- popper: {
108
- [`& .${autocompleteClasses.listbox}`]: {
109
- [`& .${autocompleteClasses.option}`]: {
110
- [`&:hover, &[aria-selected="true"], &.${autocompleteClasses.focused},
111
- &.${autocompleteClasses.focused}[aria-selected="true"]`]: {
112
- background: equals(theme.palette.mode, ThemeMode.dark)
113
- ? theme.palette.primary.dark
114
- : theme.palette.primary.light,
115
- color: equals(theme.palette.mode, ThemeMode.dark)
116
- ? theme.palette.common.white
117
- : theme.palette.primary.main
118
- }
119
- },
120
- padding: 0
121
- },
122
- zIndex: theme.zIndex.tooltip + 1
123
- },
124
- textfield: {
125
- height: textfieldHeight(hideInput),
126
- visibility: hideInput ? 'hidden' : 'visible'
127
- }
128
- }));
129
-
130
55
  const LoadingIndicator = (): JSX.Element => {
131
- const { classes } = useStyles({});
56
+ const { classes } = useAutoCompleteStyles({});
132
57
 
133
58
  return (
134
59
  <div className={classes.loadingIndicator}>
@@ -163,11 +88,13 @@ const AutocompleteField = forwardRef(
163
88
  autoSizeCustomPadding,
164
89
  getOptionItemLabel = (option) => option?.name,
165
90
  forceInputRenderValue = false,
91
+ textFieldSlotsAndSlotProps,
92
+ autocompleteSlotsAndSlotProps,
166
93
  ...autocompleteProps
167
94
  }: Props,
168
95
  ref?: ForwardedRef<HTMLDivElement>
169
96
  ): JSX.Element => {
170
- const { classes, cx } = useStyles({ hideInput });
97
+ const { classes, cx } = useAutoCompleteStyles({ hideInput });
171
98
  const { t } = useTranslation();
172
99
  const theme = useTheme();
173
100
 
@@ -184,33 +111,6 @@ const AutocompleteField = forwardRef(
184
111
  return (
185
112
  <TextField
186
113
  {...params}
187
- InputLabelProps={{
188
- classes: {
189
- marginDense: classes.inputLabel,
190
- shrink: classes.inputLabelShrink
191
- }
192
- }}
193
- InputProps={{
194
- ...params.InputProps,
195
- endAdornment: (
196
- <>
197
- {endAdornment && (
198
- <InputAdornment position="end">{endAdornment}</InputAdornment>
199
- )}
200
- {params.InputProps.endAdornment}
201
- </>
202
- ),
203
- style: {
204
- background: 'transparent',
205
- minWidth: 0,
206
- padding: theme.spacing(
207
- 0.75,
208
- isEmpty(placeholder) ? 0 : 5,
209
- 0.75,
210
- 0.75
211
- )
212
- }
213
- }}
214
114
  autoFocus={autoFocus}
215
115
  autoSize={autoSize}
216
116
  autoSizeCustomPadding={7 + (autoSizeCustomPadding || 0)}
@@ -220,20 +120,6 @@ const AutocompleteField = forwardRef(
220
120
  }}
221
121
  error={error}
222
122
  externalValueForAutoSize={autocompleteProps?.value?.name}
223
- inputProps={{
224
- ...params.inputProps,
225
- 'aria-label': label,
226
- 'data-testid': dataTestId || label,
227
- id: getNormalizedId(label || ''),
228
- ...(forceInputRenderValue
229
- ? {
230
- value: getOptionItemLabel(
231
- autocompleteProps?.value || undefined
232
- )
233
- }
234
- : {}),
235
- ...autocompleteProps?.inputProps
236
- }}
237
123
  label={label}
238
124
  placeholder={isNil(placeholder) ? t(searchLabel) : placeholder}
239
125
  required={required}
@@ -245,6 +131,51 @@ const AutocompleteField = forwardRef(
245
131
  undefined
246
132
  }
247
133
  onChange={onTextChange}
134
+ slotProps={{
135
+ input: {
136
+ ...params.InputProps,
137
+ endAdornment: (
138
+ <>
139
+ {endAdornment && (
140
+ <InputAdornment position="end">
141
+ {endAdornment}
142
+ </InputAdornment>
143
+ )}
144
+ {params.InputProps.endAdornment}
145
+ </>
146
+ ),
147
+ style: {
148
+ background: 'transparent',
149
+ minWidth: 0,
150
+ padding: theme.spacing(
151
+ 0.75,
152
+ isEmpty(placeholder) ? 0 : 5,
153
+ 0.75,
154
+ 0.75
155
+ )
156
+ }
157
+ },
158
+ inputLabel: {
159
+ classes: {
160
+ marginDense: classes.inputLabel,
161
+ shrink: classes.inputLabelShrink
162
+ }
163
+ },
164
+ htmlInput: {
165
+ ...params.inputProps,
166
+ 'aria-label': label,
167
+ 'data-testid': dataTestId || label,
168
+ id: getNormalizedId(label || ''),
169
+ ...(forceInputRenderValue
170
+ ? {
171
+ value: getOptionItemLabel(
172
+ autocompleteProps?.value || undefined
173
+ )
174
+ }
175
+ : {}),
176
+ ...textFieldSlotsAndSlotProps?.slotProps?.htmlInput
177
+ }
178
+ }}
248
179
  />
249
180
  );
250
181
  };
@@ -286,6 +217,7 @@ const AutocompleteField = forwardRef(
286
217
  );
287
218
  }}
288
219
  size="small"
220
+ slotProps={autocompleteSlotsAndSlotProps?.slotProps}
289
221
  {...autocompleteProps}
290
222
  />
291
223
  );
@@ -6,8 +6,10 @@ import { makeStyles } from 'tss-react/mui';
6
6
  import {
7
7
  Box,
8
8
  InputAdornment,
9
+ InputProps,
9
10
  TextField as MuiTextField,
10
11
  TextFieldProps,
12
+ TextFieldSlotsAndSlotProps,
11
13
  Theme,
12
14
  Tooltip,
13
15
  Typography
@@ -95,6 +97,7 @@ export type TextProps = {
95
97
  size?: SizeVariant;
96
98
  transparent?: boolean;
97
99
  value?: string;
100
+ textFieldSlotsAndSlotProps?: TextFieldSlotsAndSlotProps<InputProps>;
98
101
  } & Omit<TextFieldProps, 'variant' | 'size' | 'error'>;
99
102
 
100
103
  const TextField = forwardRef(
@@ -119,6 +122,7 @@ const TextField = forwardRef(
119
122
  required = false,
120
123
  containerClassName,
121
124
  type,
125
+ textFieldSlotsAndSlotProps,
122
126
  ...rest
123
127
  }: TextProps,
124
128
  ref: React.ForwardedRef<HTMLDivElement>
@@ -140,7 +144,6 @@ const TextField = forwardRef(
140
144
  if (debounced) {
141
145
  return {};
142
146
  }
143
-
144
147
  if (defaultValue) {
145
148
  return { defaultValue };
146
149
  }
@@ -159,50 +162,11 @@ const TextField = forwardRef(
159
162
  error={!isNil(error)}
160
163
  helperText={displayErrorInTooltip ? undefined : error}
161
164
  id={getNormalizedId(dataTestId || '')}
162
- inputProps={{
163
- 'aria-label': ariaLabel,
164
- 'data-testid': dataTestId,
165
- ...rest.inputProps
166
- }}
167
165
  label={label}
168
166
  ref={ref}
169
167
  size={size || 'small'}
170
168
  onChange={changeInputValue}
171
169
  {...getValueProps()}
172
- {...rest}
173
- InputLabelProps={{
174
- classes: {
175
- root: cx(equals(size, 'compact') && classes.compactLabel),
176
- shrink: cx(
177
- equals(size, 'compact') && classes.compactLabelShrink
178
- )
179
- }
180
- }}
181
- InputProps={{
182
- className: cx(
183
- classes.inputBase,
184
- {
185
- [classes.transparent]: transparent,
186
- [classes.autoSizeCompact]: autoSize && equals(size, 'compact')
187
- },
188
- className
189
- ),
190
- endAdornment: (
191
- <OptionalLabelInputAdornment label={label} position="end">
192
- {EndAdornment ? (
193
- <EndAdornment />
194
- ) : (
195
- rest.InputProps?.endAdornment
196
- )}
197
- </OptionalLabelInputAdornment>
198
- ),
199
- startAdornment: StartAdornment && (
200
- <OptionalLabelInputAdornment label={label} position="start">
201
- <StartAdornment />
202
- </OptionalLabelInputAdornment>
203
- ),
204
- ...rest.InputProps
205
- }}
206
170
  className={classes.textField}
207
171
  required={required}
208
172
  sx={{
@@ -210,6 +174,48 @@ const TextField = forwardRef(
210
174
  ...rest?.sx
211
175
  }}
212
176
  type={type}
177
+ slotProps={{
178
+ input: {
179
+ className: cx(
180
+ classes.inputBase,
181
+ {
182
+ [classes.transparent]: transparent,
183
+ [classes.autoSizeCompact]:
184
+ autoSize && equals(size, 'compact')
185
+ },
186
+ className
187
+ ),
188
+ endAdornment: (
189
+ <OptionalLabelInputAdornment label={label} position="end">
190
+ {EndAdornment ? (
191
+ <EndAdornment />
192
+ ) : (
193
+ textFieldSlotsAndSlotProps?.slotProps?.input?.endAdornment
194
+ )}
195
+ </OptionalLabelInputAdornment>
196
+ ),
197
+ startAdornment: StartAdornment && (
198
+ <OptionalLabelInputAdornment label={label} position="start">
199
+ <StartAdornment />
200
+ </OptionalLabelInputAdornment>
201
+ ),
202
+ ...textFieldSlotsAndSlotProps?.slotProps?.input
203
+ },
204
+ inputLabel: {
205
+ classes: {
206
+ root: cx(equals(size, 'compact') && classes.compactLabel),
207
+ shrink: cx(
208
+ equals(size, 'compact') && classes.compactLabelShrink
209
+ )
210
+ }
211
+ },
212
+ htmlInput: {
213
+ 'aria-label': ariaLabel,
214
+ 'data-testid': dataTestId,
215
+ ...textFieldSlotsAndSlotProps?.slotProps?.htmlInput
216
+ }
217
+ }}
218
+ {...rest}
213
219
  />
214
220
  </Tooltip>
215
221
  {autoSize && (
@@ -64,7 +64,9 @@ const ListingHeaderCell = ({
64
64
 
65
65
  const headerContent = (
66
66
  <Tooltip placement="top" title={getTooltipLabel(column.shortLabel)}>
67
- <HeaderLabel>{columnLabel}</HeaderLabel>
67
+ <span>
68
+ <HeaderLabel>{columnLabel}</HeaderLabel>
69
+ </span>
68
70
  </Tooltip>
69
71
  );
70
72
 
@@ -227,8 +227,8 @@ describe('Listing', () => {
227
227
 
228
228
  cy.contains('Sub Item 0').realHover();
229
229
 
230
- cy.get('[data-isHovered="true"]').should('have.length', 1);
231
- cy.get('[data-isHovered="true"]').contains('Sub Item 0').should('exist');
230
+ cy.get('[data-is-hovered="true"]').should('have.length', 1);
231
+ cy.get('[data-is-hovered="true"]').contains('Sub Item 0').should('exist');
232
232
 
233
233
  cy.findByLabelText('Collapse 0').click();
234
234
  cy.findByLabelText('Collapse 2').click();
@@ -215,7 +215,7 @@ const IntersectionRow = ({ isHovered, ...rest }: Props): JSX.Element => {
215
215
  return (
216
216
  <div
217
217
  className={classes.intersectionRow}
218
- data-isHovered={isHovered}
218
+ data-is-hovered={isHovered}
219
219
  ref={rowRef}
220
220
  >
221
221
  <Row {...rest} isHovered={isHovered} isInViewport={isInViewport} />
@@ -27,6 +27,8 @@ declare module '@mui/material/styles/createPalette' {
27
27
  acknowledgedBackground: string;
28
28
  inDowntime: string;
29
29
  inDowntimeBackground: string;
30
+ inFlapping: string;
31
+ inFlappingBackground: string;
30
32
  }
31
33
  }
32
34
 
@@ -159,6 +161,8 @@ export const lightPalette: PaletteOptions = {
159
161
  action: {
160
162
  acknowledged: '#745F35',
161
163
  acknowledgedBackground: '#DFD2B9',
164
+ inFlapping: '#064A3F',
165
+ inFlappingBackground: '#D8F3EF',
162
166
  activatedOpacity: 0.12,
163
167
  active: '#666666',
164
168
  disabled: '#999999',
@@ -299,6 +303,8 @@ export const darkPalette: PaletteOptions = {
299
303
  action: {
300
304
  acknowledged: '#DFD2B9',
301
305
  acknowledgedBackground: '#745F35',
306
+ inFlapping: '#D8F3EF',
307
+ inFlappingBackground: '#064A3F',
302
308
  activatedOpacity: 0.3,
303
309
  active: '#B5B5B5',
304
310
  disabled: '#999999',
@@ -53,9 +53,7 @@ export default ({ counters }: CounterProps): JSX.Element => {
53
53
  {counters.map(
54
54
  ({ to, ariaLabel, onClick, count, severityCode }, index) => (
55
55
  <Fragment key={to.toString().replace(/\W/g, '')}>
56
- {index === 2 && (
57
- <li aria-hidden="true" className={classes.splitter} />
58
- )}
56
+ {index === 2 && <li className={classes.splitter} />}
59
57
  <li className={classes.item}>
60
58
  <Link
61
59
  aria-label={ariaLabel}
@@ -60,9 +60,12 @@ export const useOptimisticMutation = <T, TMeta>({
60
60
  }: GetOptimisticMutationListingProps<T, TMeta>): object => {
61
61
  const listingQueryKey = getListingQueryKey();
62
62
 
63
+ const updatedPayload = payload && 'id' in payload ? payload :{...payload, id: optimisticListing?.total}
64
+
65
+
63
66
  const hasOnlyOnePage =
64
67
  (optimisticListing?.total || 0) <= (optimisticListing?.limit || 0);
65
- const isFormDataPayload = equals(type(payload), 'FormData');
68
+ const isFormDataPayload = equals(type(updatedPayload), 'FormData');
66
69
 
67
70
  const items = last(
68
71
  queryClient.getQueriesData({
@@ -71,7 +74,7 @@ export const useOptimisticMutation = <T, TMeta>({
71
74
  )?.[1];
72
75
 
73
76
  if (equals(Method.POST, method) && !isFormDataPayload && hasOnlyOnePage) {
74
- const newItems = append(payload, items.result);
77
+ const newItems = append(updatedPayload, items.result);
75
78
 
76
79
  return { ...items, result: newItems };
77
80
  }
@@ -96,12 +99,12 @@ export const useOptimisticMutation = <T, TMeta>({
96
99
  );
97
100
  const item = items.result.find(({ id }) => equals(id, _meta.id));
98
101
  const updatedItem = equals(Method.PUT, method)
99
- ? payload
102
+ ? updatedPayload
100
103
  : {
101
104
  ...item,
102
105
  ...(isFormDataPayload
103
- ? Object.fromEntries(payload.entries())
104
- : payload)
106
+ ? Object.fromEntries(updatedPayload.entries())
107
+ : updatedPayload)
105
108
  };
106
109
  const newItems = update(itemIndex, updatedItem, items.result);
107
110
 
@@ -1,4 +1,4 @@
1
- import { ReactElement, ReactNode } from 'react';
1
+ import { ReactElement, ReactNode, forwardRef } from 'react';
2
2
 
3
3
  import {
4
4
  IconButton as MuiIconButton,
@@ -32,31 +32,36 @@ type IconButtonProps = {
32
32
  /**
33
33
  * @todo re-factor as `iconVariant: 'icon-only'` Button variant, and remove IconButton component (reason: code duplication)
34
34
  */
35
- const IconButton = ({
36
- variant = 'primary',
37
- size = 'medium',
38
- icon,
39
- disabled = false,
40
- onClick,
41
- ...attr
42
- }: IconButtonProps): ReactElement => {
43
- const { classes } = useStyles();
44
-
45
- return (
46
- <MuiIconButton
47
- className={classes.iconButton}
48
- data-size={size}
49
- data-variant={variant}
50
- disabled={disabled}
51
- size={size}
52
- onClick={(e) => onClick?.(e)}
53
- {...attr}
54
- // Mui overrides
55
- color={muiColorMap[variant]}
56
- >
57
- {icon}
58
- </MuiIconButton>
59
- );
60
- };
35
+ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
36
+ (
37
+ {
38
+ variant = 'primary',
39
+ size = 'medium',
40
+ icon,
41
+ disabled = false,
42
+ onClick,
43
+ ...attr
44
+ },
45
+ ref
46
+ ): ReactElement => {
47
+ const { classes } = useStyles();
61
48
 
49
+ return (
50
+ <MuiIconButton
51
+ ref={ref}
52
+ className={classes.iconButton}
53
+ data-size={size}
54
+ data-variant={variant}
55
+ disabled={disabled}
56
+ size={size}
57
+ onClick={(e) => onClick?.(e)}
58
+ {...attr}
59
+ // Mui overrides
60
+ color={muiColorMap[variant]}
61
+ >
62
+ {icon}
63
+ </MuiIconButton>
64
+ );
65
+ }
66
+ );
62
67
  export { IconButton };
@@ -21,8 +21,12 @@ const Search = ({ label, filters }: Props): JSX.Element => {
21
21
  dataTestId={label}
22
22
  placeholder={label}
23
23
  onChange={change}
24
- InputProps={{
25
- endAdornment: <Filters label="filters" filters={filters} />
24
+ textFieldSlotsAndSlotProps={{
25
+ slotProps: {
26
+ input: {
27
+ endAdornment: <Filters label="filters" filters={filters} />
28
+ }
29
+ }
26
30
  }}
27
31
  />
28
32
  );
@@ -52,7 +52,7 @@ const DataTableItem = forwardRef(
52
52
  <img
53
53
  alt={`thumbnail-${title}-${description}`}
54
54
  className={classes.thumbnail}
55
- data-testId={`thumbnail-${title}-${description}`}
55
+ data-testid={`thumbnail-${title}-${description}`}
56
56
  loading="lazy"
57
57
  src={thumbnail}
58
58
  />
@@ -42,9 +42,13 @@ const GlobalRefreshFieldOption = (): JSX.Element => {
42
42
  <TextField
43
43
  autoSize
44
44
  dataTestId={labelInterval}
45
- inputProps={{
46
- 'aria-label': t(labelInterval) as string,
47
- min: 1
45
+ textFieldSlotsAndSlotProps={{
46
+ slotProps: {
47
+ htmlInput: {
48
+ 'aria-label': t(labelInterval) as string,
49
+ min: 1
50
+ }
51
+ }
48
52
  }}
49
53
  size="compact"
50
54
  type="number"