@centreon/ui 23.10.65-develop.0 → 23.10.66

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 (38) hide show
  1. package/package.json +1 -1
  2. package/src/Dashboard/Dashboard.styles.ts +6 -1
  3. package/src/Dashboard/Item.tsx +4 -1
  4. package/src/Graph/Gauge/Gauge.stories.tsx +10 -0
  5. package/src/Graph/Gauge/Gauge.tsx +10 -1
  6. package/src/Graph/Gauge/PieData.tsx +8 -8
  7. package/src/Graph/Gauge/ResponsiveGauge.tsx +32 -23
  8. package/src/Graph/Gauge/Thresholds.tsx +2 -25
  9. package/src/Graph/Gauge/models.ts +1 -0
  10. package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +15 -9
  11. package/src/Graph/SingleBar/SingleBar.stories.tsx +10 -0
  12. package/src/Graph/SingleBar/ThresholdLine.tsx +3 -5
  13. package/src/Graph/SingleBar/models.ts +2 -0
  14. package/src/Graph/Text/Text.stories.tsx +13 -0
  15. package/src/Graph/Text/Text.tsx +19 -12
  16. package/src/Graph/common/timeSeries/index.test.ts +106 -3
  17. package/src/Graph/common/timeSeries/index.ts +67 -4
  18. package/src/Graph/common/timeSeries/models.ts +4 -1
  19. package/src/Graph/common/utils.ts +4 -2
  20. package/src/InputField/Select/Autocomplete/Multi/index.tsx +4 -2
  21. package/src/InputField/Select/Autocomplete/index.tsx +3 -1
  22. package/src/InputField/Text/index.tsx +10 -7
  23. package/src/RichTextEditor/ContentEditable.tsx +0 -1
  24. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +5 -13
  25. package/src/RichTextEditor/RichTextEditor.tsx +5 -2
  26. package/src/RichTextEditor/plugins/AutoLinkPlugin/index.tsx +40 -1
  27. package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +33 -8
  28. package/src/RichTextEditor/plugins/ToolbarPlugin/index.tsx +10 -4
  29. package/src/ThemeProvider/index.tsx +6 -4
  30. package/src/api/useFetchQuery/index.ts +10 -8
  31. package/src/components/Inputs/Switch/Switch.stories.tsx +22 -0
  32. package/src/components/Inputs/Switch/Switch.styles.ts +13 -0
  33. package/src/components/Inputs/Switch/Switch.tsx +18 -0
  34. package/src/components/Inputs/index.ts +1 -0
  35. package/src/components/Modal/Modal.styles.ts +3 -1
  36. package/src/components/index.ts +1 -0
  37. package/src/utils/centreonBaseURL.ts +4 -0
  38. package/src/utils/index.ts +1 -0
@@ -28,7 +28,10 @@ import {
28
28
  lt,
29
29
  identity,
30
30
  head,
31
- last
31
+ last,
32
+ cond,
33
+ always,
34
+ T
32
35
  } from 'ramda';
33
36
 
34
37
  import { margin } from '../../LineChart/common';
@@ -440,6 +443,37 @@ const getRightScale = ({
440
443
  });
441
444
  };
442
445
 
446
+ const formatTime = (value: number): string => {
447
+ if (value < 1000) {
448
+ return `${numeral(value).format('0.[00]a')} ms`;
449
+ }
450
+
451
+ const t = numeral(value / 1000).format('0.[00]a');
452
+
453
+ return `${t} seconds`;
454
+ };
455
+
456
+ const registerMsUnitToNumeral = (): null => {
457
+ try {
458
+ numeral.register('format', 'milliseconds', {
459
+ format: (value) => {
460
+ return formatTime(value);
461
+ },
462
+ regexps: {
463
+ format: /(ms)/,
464
+ unformat: /(ms)/
465
+ },
466
+ unformat: () => ''
467
+ });
468
+
469
+ return null;
470
+ } catch (_) {
471
+ return null;
472
+ }
473
+ };
474
+
475
+ registerMsUnitToNumeral();
476
+
443
477
  const formatMetricValue = ({
444
478
  value,
445
479
  unit,
@@ -463,11 +497,14 @@ const formatMetricValue = ({
463
497
 
464
498
  const base1024 = base2Units.includes(unit) || Number(base) === 1024;
465
499
 
466
- const formatSuffix = base1024 ? ' ib' : 'a';
500
+ const formatSuffix = cond([
501
+ [equals('ms'), always(' ms')],
502
+ [T, always(base1024 ? ' ib' : 'a')]
503
+ ])(unit);
467
504
 
468
505
  const formattedMetricValue = numeral(Math.abs(value))
469
506
  .format(`0.[00]${formatSuffix}`)
470
- .replace(/\s|i|B/g, '');
507
+ .replace(/iB/g, unit);
471
508
 
472
509
  if (lt(value, 0)) {
473
510
  return `-${formattedMetricValue}`;
@@ -476,6 +513,31 @@ const formatMetricValue = ({
476
513
  return formattedMetricValue;
477
514
  };
478
515
 
516
+ const formatMetricValueWithUnit = ({
517
+ value,
518
+ unit,
519
+ base = 1000,
520
+ isRaw = false
521
+ }: FormatMetricValueProps & { isRaw?: boolean }): string | null => {
522
+ if (isNil(value)) {
523
+ return null;
524
+ }
525
+
526
+ if (isRaw) {
527
+ const unitText = equals('%', unit) ? unit : ` ${unit}`;
528
+
529
+ return `${value}${unitText}`;
530
+ }
531
+
532
+ if (equals('%', unit)) {
533
+ return `${numeral(Math.abs(value)).format('0.[00]')}%`;
534
+ }
535
+
536
+ const formattedMetricValue = formatMetricValue({ base, unit, value });
537
+
538
+ return formattedMetricValue;
539
+ };
540
+
479
541
  const getStackedYScale = ({
480
542
  leftScale,
481
543
  rightScale
@@ -557,5 +619,6 @@ export {
557
619
  getStackedYScale,
558
620
  getTimeValue,
559
621
  bisectDate,
560
- getMetricWithLatestData
622
+ getMetricWithLatestData,
623
+ formatMetricValueWithUnit
561
624
  };
@@ -14,6 +14,8 @@ interface DsData {
14
14
  export interface Metric {
15
15
  average_value: number | null;
16
16
  crit: number | null;
17
+ critical_high_threshold: number | null;
18
+ critical_low_threshold: number | null;
17
19
  data: Array<number>;
18
20
  ds_data: DsData;
19
21
  legend: string;
@@ -21,7 +23,8 @@ export interface Metric {
21
23
  metric: string;
22
24
  minimum_value: number | null;
23
25
  unit: string;
24
- warn: number | null;
26
+ warning_high_threshold: number | null;
27
+ warning_low_threshold: number | null;
25
28
  }
26
29
 
27
30
  type TimeSeries = { timeTick: string };
@@ -17,6 +17,7 @@ import { Theme } from '@mui/material';
17
17
  import { Thresholds } from './models';
18
18
 
19
19
  interface GetColorFromDataAndThresholdsProps {
20
+ baseColor?: string;
20
21
  data: number;
21
22
  theme: Theme;
22
23
  thresholds: Thresholds;
@@ -25,10 +26,11 @@ interface GetColorFromDataAndThresholdsProps {
25
26
  export const getColorFromDataAndTresholds = ({
26
27
  data,
27
28
  thresholds,
28
- theme
29
+ theme,
30
+ baseColor
29
31
  }: GetColorFromDataAndThresholdsProps): string => {
30
32
  if (!thresholds.enabled) {
31
- return theme.palette.success.main;
33
+ return baseColor || theme.palette.primary.main;
32
34
  }
33
35
 
34
36
  const criticalValues = pluck('value', thresholds.critical).sort();
@@ -1,7 +1,7 @@
1
1
  import { includes, map, prop, reject } from 'ramda';
2
2
  import { makeStyles } from 'tss-react/mui';
3
3
 
4
- import { Chip } from '@mui/material';
4
+ import { Chip, ChipProps } from '@mui/material';
5
5
  import { UseAutocompleteProps } from '@mui/material/useAutocomplete';
6
6
 
7
7
  import Autocomplete, { Props as AutocompleteProps } from '..';
@@ -18,7 +18,6 @@ const useStyles = makeStyles()((theme) => ({
18
18
  width: theme.spacing(1.5)
19
19
  },
20
20
  tag: {
21
- backgroundColor: theme.palette.divider,
22
21
  fontSize: theme.typography.caption.fontSize
23
22
  }
24
23
  }));
@@ -33,6 +32,7 @@ export interface Props
33
32
  UseAutocompleteProps<SelectEntry, Multiple, DisableClearable, FreeSolo>,
34
33
  'multiple'
35
34
  > {
35
+ chipProps?: ChipProps;
36
36
  disableSortedOptions?: boolean;
37
37
  getTagLabel?: (option) => string;
38
38
  optionProperty?: string;
@@ -45,6 +45,7 @@ const MultiAutocompleteField = ({
45
45
  optionProperty = 'name',
46
46
  getOptionLabel = (option): string => option.name,
47
47
  getTagLabel = (option): string => option[optionProperty],
48
+ chipProps,
48
49
  ...props
49
50
  }: Props): JSX.Element => {
50
51
  const { classes } = useStyles();
@@ -60,6 +61,7 @@ const MultiAutocompleteField = ({
60
61
  label={getTagLabel(option)}
61
62
  size="medium"
62
63
  {...getTagProps({ index })}
64
+ {...chipProps}
63
65
  />
64
66
  ));
65
67
 
@@ -32,6 +32,7 @@ export type Props = {
32
32
  displayPopupIcon?: boolean;
33
33
  endAdornment?: React.ReactElement;
34
34
  error?: string;
35
+ getOptionItemLabel?: (option) => string;
35
36
  hideInput?: boolean;
36
37
  label: string;
37
38
  loading?: boolean;
@@ -158,6 +159,7 @@ const AutocompleteField = ({
158
159
  autoSize = false,
159
160
  autoSizeDefaultWidth = 0,
160
161
  autoSizeCustomPadding,
162
+ getOptionItemLabel = (option) => option.name,
161
163
  ...autocompleteProps
162
164
  }: Props): JSX.Element => {
163
165
  const { classes, cx } = useStyles({ hideInput });
@@ -249,7 +251,7 @@ const AutocompleteField = ({
249
251
  <Option
250
252
  thumbnailUrl={displayOptionThumbnail ? option.url : undefined}
251
253
  >
252
- {option.name}
254
+ {getOptionItemLabel(option)}
253
255
  </Option>
254
256
  </li>
255
257
  );
@@ -1,6 +1,6 @@
1
1
  import { forwardRef, useCallback } from 'react';
2
2
 
3
- import { isNil } from 'ramda';
3
+ import { equals, isNil } from 'ramda';
4
4
  import { makeStyles } from 'tss-react/mui';
5
5
 
6
6
  import {
@@ -9,7 +9,8 @@ import {
9
9
  TextFieldProps,
10
10
  Theme,
11
11
  Tooltip,
12
- Typography
12
+ Typography,
13
+ Box
13
14
  } from '@mui/material';
14
15
 
15
16
  import { getNormalizedId } from '../../utils';
@@ -18,8 +19,9 @@ import useAutoSize from './useAutoSize';
18
19
 
19
20
  const useStyles = makeStyles<{ displayAsBlock: boolean }>()(
20
21
  (theme: Theme, { displayAsBlock }) => ({
21
- compact: {
22
- fontSize: 'x-small'
22
+ autoSizeCompact: {
23
+ paddingRight: theme.spacing(1),
24
+ paddingTop: theme.spacing(0.6)
23
25
  },
24
26
  hiddenText: {
25
27
  display: 'table',
@@ -140,7 +142,7 @@ const TextField = forwardRef(
140
142
  }, [innerValue, debounced, defaultValue]);
141
143
 
142
144
  return (
143
- <>
145
+ <Box sx={{ width: autoSize ? 'auto' : '100%' }}>
144
146
  <Tooltip placement="top" title={tooltipTitle}>
145
147
  <MuiTextField
146
148
  data-testid={dataTestId}
@@ -162,7 +164,8 @@ const TextField = forwardRef(
162
164
  className: cx(
163
165
  classes.inputBase,
164
166
  {
165
- [classes.transparent]: transparent
167
+ [classes.transparent]: transparent,
168
+ [classes.autoSizeCompact]: autoSize && equals(size, 'compact')
166
169
  },
167
170
  className
168
171
  ),
@@ -195,7 +198,7 @@ const TextField = forwardRef(
195
198
  {rest.value || externalValueForAutoSize || innerValue}
196
199
  </Typography>
197
200
  )}
198
- </>
201
+ </Box>
199
202
  );
200
203
  }
201
204
  );
@@ -160,7 +160,6 @@ const ContentEditable = ({
160
160
  className,
161
161
  isFocused && classes.inputFocused
162
162
  )}
163
- id={namespace}
164
163
  >
165
164
  {editable && isTextEmpty && (
166
165
  <Typography className={classes.placeholder}>
@@ -56,7 +56,7 @@ describe('Rich Text Editor', () => {
56
56
  .should('be.visible')
57
57
  .and('have.value', '');
58
58
 
59
- cy.get('#RichTextEditor').contains('Type here...');
59
+ cy.findByLabelText('RichTextEditor').parent().contains('Type here...');
60
60
  });
61
61
 
62
62
  it('displays changes with undo redo buttons', () => {
@@ -187,11 +187,6 @@ describe('Rich Text Editor', () => {
187
187
  });
188
188
 
189
189
  it('displays editor when editable props is false and an initialState exist', () => {
190
- cy.get('[data-testid="RichTextEditor"]').should(
191
- 'have.attr',
192
- 'contenteditable',
193
- 'false'
194
- );
195
190
  cy.findByLabelText('Undo').should('not.exist');
196
191
  cy.findByLabelText('Redo').should('not.exist');
197
192
  cy.findByLabelText('bold').should('not.exist');
@@ -216,11 +211,6 @@ describe('Rich Text Editor', () => {
216
211
  });
217
212
 
218
213
  it('displays editor when editable props is false and an initialState exist', () => {
219
- cy.get('[data-testid="RichTextEditor"]').should(
220
- 'have.attr',
221
- 'contenteditable',
222
- 'false'
223
- );
224
214
  cy.findByLabelText('Undo').should('be.disabled');
225
215
  cy.findByLabelText('Redo').should('be.disabled');
226
216
  cy.findByLabelText('bold').should('be.disabled');
@@ -245,12 +235,14 @@ describe('Rich Text Editor', () => {
245
235
  });
246
236
  });
247
237
  it('displays editor with custom namespace', () => {
248
- cy.get('#CypressComponentTest > p').should('be.exist');
238
+ cy.get('[data-testid="CypressComponentTest"] > p').should('be.exist');
249
239
  cy.get('[data-testid="CypressComponentTest"]').should('be.exist');
250
240
  });
251
241
 
252
242
  it('displays editor with custom placeholder', () => {
253
- cy.get('#CypressComponentTest > p').contains('Type Cypress test...');
243
+ cy.get('[data-testid="CypressComponentTest"]')
244
+ .parent()
245
+ .contains('Type Cypress test...');
254
246
  });
255
247
 
256
248
  it('displays editor with custom minimum input height', () => {
@@ -23,6 +23,7 @@ import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
23
23
  export interface RichTextEditorProps {
24
24
  contentClassName?: string;
25
25
  disabled?: boolean;
26
+ displayBlockButtons?: boolean;
26
27
  displayMacrosButton?: boolean;
27
28
  editable: boolean;
28
29
  editorState?: string;
@@ -132,9 +133,10 @@ const RichTextEditor = ({
132
133
  onBlur,
133
134
  contentClassName,
134
135
  displayMacrosButton = false,
135
- disabled,
136
+ disabled = false,
136
137
  openLinkInNewTab = true,
137
- initialize
138
+ initialize,
139
+ displayBlockButtons = true
138
140
  }: RichTextEditorProps): JSX.Element => {
139
141
  const { classes } = useStyles({ toolbarPositions });
140
142
 
@@ -174,6 +176,7 @@ const RichTextEditor = ({
174
176
  <div className={classes.toolbar}>
175
177
  <ToolbarPlugin
176
178
  disabled={disabled}
179
+ displayBlockButtons={displayBlockButtons}
177
180
  displayMacrosButton={displayMacrosButton}
178
181
  editable={editable}
179
182
  />
@@ -1,7 +1,11 @@
1
+ import { useEffect, useState } from 'react';
2
+
1
3
  import {
2
4
  AutoLinkPlugin,
3
5
  LinkMatcher
4
6
  } from '@lexical/react/LexicalAutoLinkPlugin';
7
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
8
+ import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical';
5
9
 
6
10
  interface LinkAttributes {
7
11
  rel?: null | string;
@@ -56,7 +60,42 @@ const AutoCompleteLinkPlugin = ({
56
60
  openLinkInNewTab
57
61
  }: {
58
62
  openLinkInNewTab: boolean;
59
- }): JSX.Element => {
63
+ }): JSX.Element | null => {
64
+ const [editor] = useLexicalComposerContext();
65
+ const [hasFocus, setFocus] = useState(false);
66
+
67
+ useEffect(
68
+ () =>
69
+ editor.registerCommand(
70
+ BLUR_COMMAND,
71
+ () => {
72
+ setFocus(false);
73
+
74
+ return false;
75
+ },
76
+ COMMAND_PRIORITY_LOW
77
+ ),
78
+ []
79
+ );
80
+
81
+ useEffect(
82
+ () =>
83
+ editor.registerCommand(
84
+ FOCUS_COMMAND,
85
+ () => {
86
+ setFocus(true);
87
+
88
+ return false;
89
+ },
90
+ COMMAND_PRIORITY_LOW
91
+ ),
92
+ []
93
+ );
94
+
95
+ if (!hasFocus) {
96
+ return null;
97
+ }
98
+
60
99
  return <AutoLinkPlugin matchers={getMatchers(openLinkInNewTab)} />;
61
100
  };
62
101
 
@@ -11,7 +11,7 @@ import { mergeRegister } from '@lexical/utils';
11
11
  import { useAtom, useAtomValue, useSetAtom } from 'jotai';
12
12
  import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
13
13
  import { useTranslation } from 'react-i18next';
14
- import { equals, isNil } from 'ramda';
14
+ import { dec, equals, gt, isNil, replace } from 'ramda';
15
15
 
16
16
  import { Popper, IconButton, Paper, Link, Box } from '@mui/material';
17
17
  import EditIcon from '@mui/icons-material/Edit';
@@ -56,6 +56,7 @@ const FloatingLinkEditor = ({
56
56
  x: 0,
57
57
  y: 0
58
58
  });
59
+ const [editedUrl, setEditedUrl] = useState('');
59
60
 
60
61
  const [editMode, setEditMode] = useAtom(editLinkModeAtom);
61
62
  const [linkUrl, setLinkUrl] = useAtom(linkValueAtom);
@@ -65,9 +66,10 @@ const FloatingLinkEditor = ({
65
66
  const acceptOrCancelNewLinkValue = useCallback(
66
67
  (event): void => {
67
68
  const { value } = event.target;
68
- if (event.key === 'Enter') {
69
- event.preventDefault();
70
69
 
70
+ event.preventDefault();
71
+
72
+ if (event.key === 'Enter') {
71
73
  if (value !== '') {
72
74
  editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
73
75
  target: openLinkInNewTab ? '_blank' : undefined,
@@ -75,14 +77,36 @@ const FloatingLinkEditor = ({
75
77
  });
76
78
  }
77
79
  setEditMode(false);
78
- } else if (event.key === 'Escape') {
79
- event.preventDefault();
80
+ }
81
+
82
+ if (event.key === 'Escape') {
80
83
  setEditMode(false);
81
84
  }
82
85
  },
83
86
  [setEditMode, setLinkUrl]
84
87
  );
85
88
 
89
+ const enterInEditMode = useCallback(() => {
90
+ setEditedUrl(linkUrl);
91
+ setEditMode(true);
92
+ }, [linkUrl]);
93
+
94
+ const changeValue = useCallback((event): void => {
95
+ const { value } = event.target;
96
+
97
+ const matched = value.match(/https?:\/\//g);
98
+
99
+ if (gt(matched.length, 1)) {
100
+ setEditedUrl(
101
+ replace(matched.join(''), matched[dec(matched.length)], value)
102
+ );
103
+
104
+ return;
105
+ }
106
+
107
+ setEditedUrl(value);
108
+ }, []);
109
+
86
110
  useEffect(() => {
87
111
  const isPositioned =
88
112
  rangeRect && (!equals(rangeRect.x, 0) || !equals(rangeRect.y, 0));
@@ -126,9 +150,9 @@ const FloatingLinkEditor = ({
126
150
  <InputField
127
151
  autoFocus
128
152
  dataTestId="InputLinkField"
129
- defaultValue={linkUrl}
130
153
  label={t(labelInputLink)}
131
154
  size="small"
155
+ value={editedUrl}
132
156
  onBlur={(event): void => {
133
157
  const { value } = event.target;
134
158
 
@@ -139,7 +163,8 @@ const FloatingLinkEditor = ({
139
163
  }
140
164
  setEditMode(false);
141
165
  }}
142
- onKeyDown={acceptOrCancelNewLinkValue}
166
+ onChange={changeValue}
167
+ onKeyUp={acceptOrCancelNewLinkValue}
143
168
  />
144
169
  ) : (
145
170
  <Box component="span" sx={{ margin: '10px' }}>
@@ -156,7 +181,7 @@ const FloatingLinkEditor = ({
156
181
  aria-label={labelEditLink}
157
182
  size="small"
158
183
  sx={{ marginLeft: '5px' }}
159
- onClick={(): void => setEditMode(true)}
184
+ onClick={enterInEditMode}
160
185
  >
161
186
  <EditIcon fontSize="small" />
162
187
  </IconButton>
@@ -8,7 +8,8 @@ import MacrosButton from './MacrosButton';
8
8
  import BlockButtons from './BlockButtons';
9
9
 
10
10
  interface Props {
11
- disabled?: boolean;
11
+ disabled: boolean;
12
+ displayBlockButtons: boolean;
12
13
  displayMacrosButton?: boolean;
13
14
  editable: boolean;
14
15
  }
@@ -33,15 +34,20 @@ export const useStyles = makeStyles()((theme) => ({
33
34
  const ToolbarPlugin = ({
34
35
  editable,
35
36
  displayMacrosButton,
36
- disabled
37
+ disabled,
38
+ displayBlockButtons
37
39
  }: Props): JSX.Element | null => {
38
40
  const { classes } = useStyles();
39
41
 
40
42
  return editable ? (
41
43
  <div className={classes.container}>
42
44
  <UndoRedoButtons disabled={disabled} />
43
- <Divider flexItem orientation="vertical" />
44
- <BlockButtons disabled={disabled} />
45
+ {displayBlockButtons && (
46
+ <>
47
+ <Divider flexItem orientation="vertical" />
48
+ <BlockButtons disabled={disabled} />
49
+ </>
50
+ )}
45
51
  <Divider flexItem orientation="vertical" />
46
52
  <FormatButtons disabled={disabled} />
47
53
  {displayMacrosButton && (
@@ -48,8 +48,7 @@ const getInputBaseRootStyle = ({
48
48
 
49
49
  if (equals(size, 'compact')) {
50
50
  return {
51
- padding: '8px 8px',
52
- paddingRight: '0px'
51
+ padding: '0px 0px 0px 8px'
53
52
  };
54
53
  }
55
54
  if (equals(size, 'small')) {
@@ -75,7 +74,6 @@ const getInputBaseRootStyle = ({
75
74
  const getInputBaseInputStyle = ({ size }: InputBaseProps): CSSInterpolation => {
76
75
  if (equals(size, 'compact')) {
77
76
  return {
78
- fontSize: 'x-small',
79
77
  minHeight: '32px'
80
78
  };
81
79
  }
@@ -123,6 +121,7 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
123
121
  MuiChip: {
124
122
  styleOverrides: {
125
123
  root: ({ ownerState, theme }) => ({
124
+ backgroundColor: !ownerState.color && theme.palette.divider,
126
125
  ...(equals(ownerState.size, 'medium') && {
127
126
  borderRadius: theme.spacing(1.25),
128
127
  fontSize: theme.typography.body2.fontSize,
@@ -189,7 +188,10 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
189
188
  },
190
189
  MuiInputBase: {
191
190
  styleOverrides: {
192
- root: ({ ownerState }) => getInputBaseInputStyle(ownerState)
191
+ root: ({ ownerState, theme }) => ({
192
+ ...getInputBaseInputStyle(ownerState),
193
+ backgroundColor: theme.palette.background.paper
194
+ })
193
195
  }
194
196
  },
195
197
  MuiList: {
@@ -90,14 +90,6 @@ const useFetchQuery = <T extends object>({
90
90
  }
91
91
  };
92
92
 
93
- useEffect(() => {
94
- return (): void => {
95
- queryClient.cancelQueries(getQueryKey());
96
- };
97
- }, []);
98
-
99
- manageError();
100
-
101
93
  const prefetchQuery = ({ endpointParams, queryKey }): void => {
102
94
  queryClient.prefetchQuery(
103
95
  queryKey,
@@ -162,6 +154,16 @@ const useFetchQuery = <T extends object>({
162
154
 
163
155
  const errorData = queryData.data as ResponseError | undefined;
164
156
 
157
+ useEffect(() => {
158
+ return (): void => {
159
+ queryClient.cancelQueries(getQueryKey());
160
+ };
161
+ }, []);
162
+
163
+ useEffect(() => {
164
+ manageError();
165
+ }, [queryData.data]);
166
+
165
167
  return {
166
168
  ...omit(['data'], queryData),
167
169
  data,
@@ -0,0 +1,22 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import Switch from './Switch';
4
+
5
+ const meta: Meta<typeof Switch> = {
6
+ component: Switch
7
+ };
8
+
9
+ export default meta;
10
+ type Story = StoryObj<typeof Switch>;
11
+
12
+ export const notChecked: Story = {
13
+ args: {
14
+ checked: false
15
+ }
16
+ };
17
+
18
+ export const checked: Story = {
19
+ args: {
20
+ checked: true
21
+ }
22
+ };
@@ -0,0 +1,13 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useSwitchStyles = makeStyles()((theme) => ({
4
+ switch: {
5
+ '& .MuiSwitch-thumb': {
6
+ backgroundColor: theme.palette.common.white
7
+ },
8
+ '& span[data-checked="true"] + .MuiSwitch-track': {
9
+ backgroundColor: theme.palette.success.main,
10
+ opacity: 1
11
+ }
12
+ }
13
+ }));
@@ -0,0 +1,18 @@
1
+ import { SwitchProps, Switch as MuiSwitch } from '@mui/material';
2
+
3
+ import { useSwitchStyles } from './Switch.styles';
4
+
5
+ const Switch = ({ checked, ...props }: SwitchProps): JSX.Element => {
6
+ const { classes } = useSwitchStyles();
7
+
8
+ return (
9
+ <MuiSwitch
10
+ checked={checked}
11
+ className={classes.switch}
12
+ data-checked={checked}
13
+ {...props}
14
+ />
15
+ );
16
+ };
17
+
18
+ export default Switch;
@@ -0,0 +1 @@
1
+ export { default as Switch } from './Switch/Switch';
@@ -48,10 +48,12 @@ const useStyles = makeStyles<{
48
48
  }
49
49
  },
50
50
  modalActions: {
51
+ bottom: 0,
51
52
  display: 'flex',
52
53
  flexDirection: 'row',
53
54
  gap: theme.spacing(2),
54
- justifyContent: 'flex-end'
55
+ justifyContent: 'flex-end',
56
+ position: 'sticky'
55
57
  },
56
58
  modalBody: {
57
59
  '& > p': {