@centreon/ui 25.7.2 → 25.8.0

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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/Form/CollapsibleGroup.tsx +10 -10
  3. package/src/Form/Form.cypress.spec.tsx +137 -2
  4. package/src/Form/Form.stories.tsx +11 -31
  5. package/src/Form/Form.tsx +2 -0
  6. package/src/Form/Inputs/Grid.tsx +10 -28
  7. package/src/Form/Inputs/SubGroupDivider.tsx +7 -0
  8. package/src/Form/Inputs/Text.tsx +1 -0
  9. package/src/Form/Inputs/index.tsx +6 -0
  10. package/src/Form/Inputs/models.ts +2 -1
  11. package/src/Form/Section/FormSection.tsx +34 -0
  12. package/src/Form/Section/PanelTabs.tsx +13 -0
  13. package/src/Form/Section/navigateToSection.ts +9 -0
  14. package/src/Form/storiesData.tsx +12 -2
  15. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +27 -2
  16. package/src/Graph/BarChart/BarChart.stories.tsx +10 -0
  17. package/src/Graph/BarChart/BarChart.tsx +19 -3
  18. package/src/Graph/BarChart/ResponsiveBarChart.tsx +12 -2
  19. package/src/Graph/Chart/BasicComponents/Lines/StackedLines/index.tsx +7 -1
  20. package/src/Graph/Chart/BasicComponents/Lines/index.tsx +10 -2
  21. package/src/Graph/Chart/Chart.cypress.spec.tsx +66 -13
  22. package/src/Graph/Chart/Chart.stories.tsx +41 -0
  23. package/src/Graph/Chart/Chart.tsx +15 -3
  24. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/RegularAnchorPoint.tsx +8 -2
  25. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/StackedAnchorPoint.tsx +9 -2
  26. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -2
  27. package/src/Graph/Chart/InteractiveComponents/index.tsx +16 -3
  28. package/src/Graph/Chart/index.tsx +6 -0
  29. package/src/Graph/Chart/models.ts +3 -0
  30. package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +5 -4
  31. package/src/Graph/common/timeSeries/index.ts +105 -34
  32. package/src/Graph/common/utils.ts +19 -0
  33. package/src/InputField/Select/Autocomplete/Connected/index.tsx +26 -21
  34. package/src/ThemeProvider/palettes.ts +1 -1
  35. package/src/api/models.ts +6 -6
  36. package/src/components/Modal/Modal.stories.tsx +20 -0
  37. package/src/components/Modal/Modal.styles.ts +2 -23
  38. package/src/components/Modal/Modal.tsx +1 -1
  39. package/src/components/Modal/ModalBody.tsx +6 -4
  40. package/src/components/Modal/ModalHeader.tsx +5 -5
  41. package/src/components/Modal/modal.module.css +16 -0
  42. package/src/components/Tabs/Tab.styles.ts +0 -6
  43. package/src/components/Tabs/Tabs.tsx +31 -10
@@ -20,7 +20,6 @@ import {
20
20
  includes,
21
21
  isEmpty,
22
22
  isNil,
23
- isNotNil,
24
23
  keys,
25
24
  last,
26
25
  lt,
@@ -353,6 +352,19 @@ const getScaleType = (
353
352
  const hasOnlyZeroesHasValue = (graphValues: Array<number>): boolean =>
354
353
  graphValues.every((value) => equals(value, 0) || equals(value, null));
355
354
 
355
+ const getSanitizedValues = reject(
356
+ (
357
+ value:
358
+ | number
359
+ | boolean
360
+ | typeof Number.POSITIVE_INFINITY
361
+ | typeof Number.NEGATIVE_INFINITY
362
+ ) =>
363
+ equals(value, false) ||
364
+ equals(value, Number.POSITIVE_INFINITY) ||
365
+ equals(value, Number.NEGATIVE_INFINITY)
366
+ );
367
+
356
368
  const getScale = ({
357
369
  graphValues,
358
370
  height,
@@ -363,23 +375,39 @@ const getScale = ({
363
375
  scaleLogarithmicBase,
364
376
  isHorizontal,
365
377
  invert,
366
- hasDisplayAsBar
378
+ hasDisplayAsBar,
379
+ hasLineFilled,
380
+ min,
381
+ max
367
382
  }): ScaleLinear<number, number> => {
368
383
  const isLogScale = equals(scale, 'logarithmic');
369
- const minValue = Math.min(
370
- hasDisplayAsBar && 0,
371
- invert && graphValues.every(lt(0))
372
- ? negate(getMax(graphValues))
373
- : getMin(graphValues),
374
- getMin(stackedValues),
375
- Math.min(...thresholds)
376
- );
377
- const maxValue = Math.max(
378
- getMax(graphValues),
379
- getMax(stackedValues),
380
- hasOnlyZeroesHasValue(graphValues) ? 1 : 0,
381
- Math.max(...thresholds)
382
- );
384
+ const sanitizedValuesForMinimum = min
385
+ ? [min]
386
+ : getSanitizedValues([
387
+ invert && graphValues.every(lt(0))
388
+ ? negate(getMax(graphValues))
389
+ : getMin(graphValues),
390
+ !isEmpty(stackedValues) &&
391
+ !equals(stackedValues, [0]) &&
392
+ getMin(stackedValues),
393
+ Math.min(...thresholds)
394
+ ]);
395
+ const minValue = Math.min(...sanitizedValuesForMinimum);
396
+ const minValueWithMargin =
397
+ (hasDisplayAsBar || hasLineFilled) && minValue > 0 && !min
398
+ ? 0
399
+ : minValue - Math.abs(minValue) * 0.05;
400
+
401
+ const sanitizedValuesForMaximum = max
402
+ ? [max]
403
+ : getSanitizedValues([
404
+ getMax(graphValues),
405
+ getMax(stackedValues),
406
+ hasOnlyZeroesHasValue(graphValues) ? 1 : 0,
407
+ Math.max(...thresholds)
408
+ ]);
409
+ const maxValue = Math.max(...sanitizedValuesForMaximum);
410
+ const maxValueWithMargin = maxValue + Math.abs(maxValue) * 0.05;
383
411
 
384
412
  const scaleType = getScaleType(scale);
385
413
 
@@ -387,21 +415,26 @@ const getScale = ({
387
415
  const range = [height, upperRangeValue];
388
416
 
389
417
  if (isCenteredZero) {
390
- const greatestValue = Math.max(Math.abs(maxValue), Math.abs(minValue));
418
+ const greatestValue = Math.max(
419
+ Math.abs(maxValueWithMargin),
420
+ Math.abs(minValueWithMargin)
421
+ );
391
422
 
392
423
  return scaleType<number>({
393
424
  base: scaleLogarithmicBase || 2,
394
425
  domain: [-greatestValue, greatestValue],
395
- range: isHorizontal ? range : range.reverse()
426
+ range: isHorizontal ? range : range.reverse(),
427
+ clamp: min || max
396
428
  });
397
429
  }
398
430
 
399
- const domain = [isLogScale ? 0.001 : minValue, maxValue];
431
+ const domain = [isLogScale ? 0.001 : minValueWithMargin, maxValueWithMargin];
400
432
 
401
433
  return scaleType<number>({
402
434
  base: scaleLogarithmicBase || 2,
403
435
  domain,
404
- range: isHorizontal ? range : range.reverse()
436
+ range: isHorizontal ? range : range.reverse(),
437
+ clamp: min || max
405
438
  });
406
439
  };
407
440
 
@@ -437,11 +470,19 @@ const getYScaleUnit = ({
437
470
  scaleLogarithmicBase,
438
471
  isHorizontal = true,
439
472
  unit,
440
- invert
441
- }: AxeScale & { invert?: boolean | string | null; unit: string }): ScaleLinear<
442
- number,
443
- number
444
- > => {
473
+ invert,
474
+ min,
475
+ max,
476
+ isBarChart,
477
+ boundariesUnit
478
+ }: AxeScale & {
479
+ invert?: boolean | string | null;
480
+ unit: string;
481
+ max?: number;
482
+ min?: number;
483
+ boundariesUnit?: string;
484
+ isBarChart?: boolean;
485
+ }): ScaleLinear<number, number> => {
445
486
  const [firstUnit] = getUnits(dataLines);
446
487
  const shouldApplyThresholds =
447
488
  equals(unit, thresholdUnit) || (!thresholdUnit && equals(firstUnit, unit));
@@ -468,11 +509,14 @@ const getYScaleUnit = ({
468
509
 
469
510
  return getScale({
470
511
  graphValues,
471
- hasDisplayAsBar: dataLines.some(
472
- ({ displayAs, unit: lineUnit, stackOrder }) =>
473
- equals(unit, lineUnit) &&
474
- equals(displayAs, 'bar') &&
475
- isNotNil(stackOrder)
512
+ hasDisplayAsBar:
513
+ isBarChart ||
514
+ dataLines.some(
515
+ ({ displayAs, unit: lineUnit }) =>
516
+ equals(unit, lineUnit) && equals(displayAs, 'bar')
517
+ ),
518
+ hasLineFilled: dataLines.some(
519
+ ({ unit: lineUnit, filled }) => equals(unit, lineUnit) && filled
476
520
  ),
477
521
  height: valueGraphHeight,
478
522
  invert,
@@ -481,10 +525,24 @@ const getYScaleUnit = ({
481
525
  scale,
482
526
  scaleLogarithmicBase,
483
527
  stackedValues,
484
- thresholds: shouldApplyThresholds ? thresholds : []
528
+ thresholds: shouldApplyThresholds ? thresholds : [],
529
+ min: boundaryToApplyToUnit({ unit, boundariesUnit, boundary: min }),
530
+ max: boundaryToApplyToUnit({ unit, boundariesUnit, boundary: max })
485
531
  });
486
532
  };
487
533
 
534
+ const boundaryToApplyToUnit = ({
535
+ boundary,
536
+ boundariesUnit,
537
+ unit
538
+ }): number | undefined => {
539
+ if (!boundariesUnit) {
540
+ return boundary;
541
+ }
542
+
543
+ return equals(boundariesUnit, unit) ? boundary : undefined;
544
+ };
545
+
488
546
  const getYScalePerUnit = ({
489
547
  dataLines,
490
548
  dataTimeSeries,
@@ -494,8 +552,17 @@ const getYScalePerUnit = ({
494
552
  isCenteredZero,
495
553
  scale,
496
554
  scaleLogarithmicBase,
497
- isHorizontal = true
498
- }: AxeScale): Record<string, ScaleLinear<number, number>> => {
555
+ isHorizontal = true,
556
+ isBarChart,
557
+ min,
558
+ max,
559
+ boundariesUnit
560
+ }: AxeScale & {
561
+ min?: number;
562
+ max?: number;
563
+ isBarChart?: boolean;
564
+ boundariesUnit?: string;
565
+ }): Record<string, ScaleLinear<number, number>> => {
499
566
  const units = getUnits(dataLines);
500
567
 
501
568
  const scalePerUnit = units.reduce((acc, unit) => {
@@ -514,7 +581,11 @@ const getYScalePerUnit = ({
514
581
  thresholdUnit,
515
582
  thresholds,
516
583
  unit,
517
- valueGraphHeight
584
+ valueGraphHeight,
585
+ min,
586
+ max,
587
+ isBarChart,
588
+ boundariesUnit
518
589
  })
519
590
  };
520
591
  }, {});
@@ -20,7 +20,9 @@ import {
20
20
 
21
21
  import { Theme, darken, getLuminance, lighten } from '@mui/material';
22
22
 
23
+ import dayjs from 'dayjs';
23
24
  import { BarStyle } from '../BarChart/models';
25
+ import { margin } from '../Chart/common';
24
26
  import { LineStyle } from '../Chart/models';
25
27
  import { Threshold, Thresholds } from './models';
26
28
  import { formatMetricValue } from './timeSeries';
@@ -256,3 +258,20 @@ export const getFormattedAxisValues = ({
256
258
  .concat(formattedThresholdValues)
257
259
  .filter((v) => v) as Array<string>;
258
260
  };
261
+
262
+ interface ComputeGElementMarginLeftProps {
263
+ maxCharacters: number;
264
+ hasSecondUnit?: boolean;
265
+ }
266
+
267
+ export const computeGElementMarginLeft = ({
268
+ maxCharacters,
269
+ hasSecondUnit
270
+ }: ComputeGElementMarginLeftProps): number =>
271
+ maxCharacters * 5 + (hasSecondUnit ? margin.top * 0.8 : margin.top * 0.6);
272
+
273
+ export const computPixelsToShiftMouse = (xScale): number => {
274
+ const domain = xScale.domain();
275
+
276
+ return Math.round(8 / dayjs(domain[1]).diff(domain[0], 'h'));
277
+ };
@@ -17,7 +17,7 @@ import {
17
17
  import { CircularProgress, useTheme } from '@mui/material';
18
18
 
19
19
  import { Props as AutocompleteFieldProps } from '..';
20
- import { ListingModel, ListingMapModel, SelectEntry } from '../../../..';
20
+ import { ListingMapModel, ListingModel, SelectEntry } from '../../../..';
21
21
  import {
22
22
  ConditionsSearchParameter,
23
23
  SearchParameter
@@ -122,26 +122,31 @@ const ConnectedAutocompleteField = (
122
122
  }
123
123
  });
124
124
 
125
- const getOptionResult = useCallback((
126
- newOptions: ListingModel<TData> | ListingMapModel<TData>
127
- ): OptionResult<TData> => {
128
- if ('result' in newOptions) return {
129
- result: newOptions.result || [],
130
- total: newOptions.meta.total || 1,
131
- limit: newOptions.meta.limit || 1,
132
- };
133
- if ('content' in newOptions) return {
134
- result: newOptions.content || [],
135
- total: newOptions.totalElements || 1,
136
- limit: newOptions.size || 1,
137
- };
138
-
139
- return {
140
- result: [],
141
- total: 1,
142
- limit: 1,
143
- }
144
- }, []);
125
+ const getOptionResult = useCallback(
126
+ (
127
+ newOptions: ListingModel<TData> | ListingMapModel<TData>
128
+ ): OptionResult<TData> => {
129
+ if ('result' in newOptions)
130
+ return {
131
+ result: newOptions.result || [],
132
+ total: newOptions.meta.total || 1,
133
+ limit: newOptions.meta.limit || 1
134
+ };
135
+ if ('content' in newOptions)
136
+ return {
137
+ result: newOptions.content || [],
138
+ total: newOptions.totalElements || 1,
139
+ limit: newOptions.size || 1
140
+ };
141
+
142
+ return {
143
+ result: [],
144
+ total: 1,
145
+ limit: 1
146
+ };
147
+ },
148
+ []
149
+ );
145
150
 
146
151
  const lastOptionRef = useIntersectionObserver({
147
152
  action: () => setPage(page + 1),
@@ -297,7 +297,7 @@ export const lightPalette: PaletteOptions = {
297
297
  contrastText: '#000',
298
298
  main: '#FD9B27',
299
299
  light: '#FCC481',
300
- dark: '#FC7E00',
300
+ dark: '#FC7E00'
301
301
  }
302
302
  };
303
303
 
package/src/api/models.ts CHANGED
@@ -10,10 +10,10 @@ export interface Listing<TEntity> {
10
10
  }
11
11
 
12
12
  export interface ListingMap<TEntity> {
13
- content: Array<TEntity>;
14
- totalPages: number;
15
- totalElements: number;
16
- size: number;
17
- number: number;
18
- numberOfElements: number;
13
+ content: Array<TEntity>;
14
+ totalPages: number;
15
+ totalElements: number;
16
+ size: number;
17
+ number: number;
18
+ numberOfElements: number;
19
19
  }
@@ -4,6 +4,7 @@ import { Button } from '../Button';
4
4
 
5
5
  import { Modal } from '.';
6
6
  import '../../ThemeProvider/tailwindcss.css';
7
+ import { basicFormWithCollapsibleGroups } from '../../Form/Form.stories';
7
8
 
8
9
  const meta: Meta<typeof Modal> = {
9
10
  argTypes: {
@@ -45,6 +46,25 @@ export const Default: Story = {
45
46
  )
46
47
  };
47
48
 
49
+ export const WithForm: Story = {
50
+ args: {
51
+ ...Default.args
52
+ },
53
+
54
+ render: (args) => (
55
+ <Modal {...args}>
56
+ <Modal.Header>Modal title</Modal.Header>
57
+ <Modal.Body>{basicFormWithCollapsibleGroups()}</Modal.Body>
58
+ <Modal.Actions
59
+ labels={{
60
+ cancel: 'Cancel',
61
+ confirm: 'Confirm'
62
+ }}
63
+ />
64
+ </Modal>
65
+ )
66
+ };
67
+
48
68
  export const AsDangerAction: Story = {
49
69
  args: {
50
70
  ...Default.args
@@ -8,7 +8,7 @@ const useStyles = makeStyles<{
8
8
  }>()((theme, props) => ({
9
9
  modal: {
10
10
  '& .MuiDialog-paper': {
11
- gap: theme.spacing(2),
11
+ gap: theme.spacing(3),
12
12
  padding: theme.spacing(2.5)
13
13
  },
14
14
  '&[data-size="fullscreen"]': {
@@ -65,15 +65,6 @@ const useStyles = makeStyles<{
65
65
  right: 0,
66
66
  zIndex: theme.zIndex.modal
67
67
  },
68
- modalBody: {
69
- '& > p': {
70
- '&:first-of-type': {
71
- margin: theme.spacing(0, 0, 1, 0)
72
- },
73
- margin: theme.spacing(1, 0, 1, 0),
74
- width: '90%'
75
- }
76
- },
77
68
  modalCloseButton: {
78
69
  position: 'absolute',
79
70
  right: theme.spacing(1),
@@ -82,19 +73,7 @@ const useStyles = makeStyles<{
82
73
  },
83
74
  top: theme.spacing(1)
84
75
  },
85
- modalHeader: {
86
- '& .MuiDialogTitle-root': {
87
- padding: theme.spacing(0)
88
- },
89
- display: 'flex',
90
- gap: theme.spacing(2),
91
-
92
- justifyContent: 'space-between'
93
- },
94
- modalTitle: {
95
- fontSize: theme.typography.h5.fontSize,
96
- fontWeight: theme.typography.fontWeightBold
97
- }
76
+
98
77
  }));
99
78
 
100
79
  export { useStyles };
@@ -54,7 +54,7 @@ const Modal = ({
54
54
  TransitionProps={{
55
55
  direction: 'up'
56
56
  }}
57
- className={classes.modal}
57
+ className={`${classes.modal} gap-6`}
58
58
  data-size={size}
59
59
  open={open}
60
60
  onClose={onClose}
@@ -1,15 +1,17 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
2
 
3
- import { useStyles } from './Modal.styles';
3
+ import { modalBody } from './modal.module.css';
4
4
 
5
5
  export type ModalHeaderProps = {
6
6
  children?: ReactNode;
7
7
  };
8
8
 
9
9
  const ModalBody = ({ children }: ModalHeaderProps): ReactElement => {
10
- const { classes } = useStyles();
11
-
12
- return <div className={classes.modalBody}>{children}</div>;
10
+ return (
11
+ <div className={modalBody} data-testid="modal-body">
12
+ {children}
13
+ </div>
14
+ );
13
15
  };
14
16
 
15
17
  export { ModalBody };
@@ -2,7 +2,9 @@ import { ReactElement, ReactNode } from 'react';
2
2
 
3
3
  import { DialogTitleProps, DialogTitle as MuiDialogTitle } from '@mui/material';
4
4
 
5
- import { useStyles } from './Modal.styles';
5
+ import '../../../src/ThemeProvider/tailwindcss.css';
6
+
7
+ import { modalHeader } from './modal.module.css';
6
8
 
7
9
  export type ModalHeaderProps = {
8
10
  children?: ReactNode;
@@ -12,11 +14,9 @@ const ModalHeader = ({
12
14
  children,
13
15
  ...rest
14
16
  }: ModalHeaderProps & DialogTitleProps): ReactElement => {
15
- const { classes } = useStyles();
16
-
17
17
  return (
18
- <div className={classes.modalHeader}>
19
- <MuiDialogTitle color="primary" className={classes.modalTitle} {...rest}>
18
+ <div className={modalHeader}>
19
+ <MuiDialogTitle className="p-0 font-bold text-2xl" color="primary" {...rest}>
20
20
  {children}
21
21
  </MuiDialogTitle>
22
22
  </div>
@@ -0,0 +1,16 @@
1
+ .modalHeader {
2
+ & .MuiDialogTitle-root {
3
+ padding: 0;
4
+ }
5
+
6
+ display: flex;
7
+ gap: var(--spacing-4);
8
+ justify-content: space-between;
9
+ }
10
+
11
+ .modalBody {
12
+ overflow-y: auto;
13
+ overflow-x: hidden;
14
+ height: 100%;
15
+ padding-right: var(--spacing-4); /* To prevent scrollbar from overlapping content */
16
+ }
@@ -5,12 +5,6 @@ export const useTabsStyles = makeStyles()((theme) => ({
5
5
  bottom: 'unset'
6
6
  },
7
7
  tab: {
8
- '&[aria-selected="true"]': {
9
- color: theme.palette.text.primary,
10
- fontWeight: theme.typography.fontWeightBold
11
- },
12
- color: theme.palette.text.primary,
13
- fontWeight: theme.typography.fontWeightRegular,
14
8
  marginRight: theme.spacing(2),
15
9
  minHeight: 0,
16
10
  minWidth: 0,
@@ -5,33 +5,53 @@ import { Tabs as MuiTabs, Tab, TabsProps } from '@mui/material';
5
5
 
6
6
  import { useTabsStyles } from './Tab.styles';
7
7
 
8
+ import '../../ThemeProvider/tailwindcss.css';
9
+
10
+ export interface TabI {
11
+ label: string;
12
+ value: string;
13
+ }
14
+
8
15
  type Props = {
9
- children: Array<JSX.Element>;
16
+ children?: Array<JSX.Element>;
10
17
  defaultTab: string;
11
18
  tabList?: TabsProps;
12
- tabs: Array<{
13
- label: string;
14
- value: string;
15
- }>;
19
+ variant?: 'standard' | 'scrollable' | 'fullWidth';
20
+ scrollButtons?: boolean | 'auto';
21
+ tabs: TabI[];
22
+ onChange?: (newValue: string) => void;
16
23
  };
17
24
 
18
25
  export const Tabs = ({
19
26
  children,
20
27
  defaultTab,
21
28
  tabs,
22
- tabList
29
+ tabList,
30
+ variant,
31
+ scrollButtons = 'auto',
32
+ onChange
23
33
  }: Props): JSX.Element => {
24
34
  const { classes } = useTabsStyles();
25
35
 
26
36
  const [selectedTab, setSelectedTab] = useState(defaultTab);
27
37
 
28
- const changeTab = useCallback((_, newValue: string): void => {
29
- setSelectedTab(newValue);
30
- }, []);
38
+ const changeTab = useCallback(
39
+ (_, newValue: string): void => {
40
+ if (onChange) onChange(newValue);
41
+
42
+ setSelectedTab(newValue);
43
+ },
44
+ [onChange]
45
+ );
46
+
47
+ const selectedTabStyle = ' font-bold text-primary-main';
48
+ const defaultTabStyle = ' font-normal';
31
49
 
32
50
  return (
33
51
  <TabContext value={selectedTab}>
34
52
  <MuiTabs
53
+ scrollButtons={scrollButtons}
54
+ variant={variant}
35
55
  classes={{
36
56
  indicator: classes.indicator,
37
57
  root: classes.tabs
@@ -43,10 +63,11 @@ export const Tabs = ({
43
63
  {tabs.map(({ value, label }) => (
44
64
  <Tab
45
65
  aria-label={label}
46
- className={classes.tab}
66
+ className={`${classes.tab} ${selectedTab === value ? selectedTabStyle : defaultTabStyle}`}
47
67
  key={value}
48
68
  label={label}
49
69
  value={value}
70
+ data-testid={`tab-${value}`}
50
71
  />
51
72
  ))}
52
73
  </MuiTabs>