@centreon/ui 26.3.10 → 26.3.12
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 +2 -2
- package/src/Dashboard/DashboardLayout.stories.tsx +24 -17
- package/src/Dashboard/Item.tsx +134 -138
- package/src/Dashboard/Layout.tsx +37 -39
- package/src/Dashboard/utils.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "26.3.
|
|
3
|
+
"version": "26.3.12",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"notistack": "^3.0.2",
|
|
153
153
|
"numeral": "^2.0.6",
|
|
154
154
|
"ramda": "0.30.1",
|
|
155
|
-
"react-grid-layout": "^
|
|
155
|
+
"react-grid-layout": "^2.2.2",
|
|
156
156
|
"react-resizable": "^3.0.5",
|
|
157
157
|
"react-router": "7",
|
|
158
158
|
"react-transition-group": "^4.4.5",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Typography } from '@mui/material';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ReactElement } from 'react';
|
|
5
|
+
import type { LayoutItem } from 'react-grid-layout';
|
|
5
6
|
|
|
6
7
|
import FluidTypography from '../Typography/FluidTypography';
|
|
7
8
|
import { DashboardLayout } from '.';
|
|
8
9
|
|
|
9
|
-
interface CustomLayout extends
|
|
10
|
+
interface CustomLayout extends LayoutItem {
|
|
10
11
|
content: string;
|
|
11
12
|
shouldUseFluidTypography: boolean;
|
|
12
13
|
}
|
|
@@ -59,21 +60,21 @@ const generateLayout = (maxElements: number): Array<CustomLayout> => {
|
|
|
59
60
|
};
|
|
60
61
|
|
|
61
62
|
interface DashboardTemplateProps {
|
|
62
|
-
header?:
|
|
63
|
+
header?: ReactElement;
|
|
63
64
|
layout?: Array<CustomLayout>;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
const Header = ():
|
|
67
|
+
const Header = (): ReactElement => (
|
|
67
68
|
<Typography variant="body2">The title</Typography>
|
|
68
69
|
);
|
|
69
70
|
|
|
70
71
|
const DashboardTemplate = ({
|
|
71
72
|
header,
|
|
72
73
|
layout = dashboardLayout
|
|
73
|
-
}: DashboardTemplateProps):
|
|
74
|
+
}: DashboardTemplateProps): ReactElement => (
|
|
74
75
|
<DashboardLayout.Layout<CustomLayout> layout={layout}>
|
|
75
76
|
{layout.map(({ i, content, shouldUseFluidTypography }) => (
|
|
76
|
-
<DashboardLayout.Item header={header} key={i}>
|
|
77
|
+
<DashboardLayout.Item header={header} id={i} key={i}>
|
|
77
78
|
{shouldUseFluidTypography ? (
|
|
78
79
|
<FluidTypography text={content} />
|
|
79
80
|
) : (
|
|
@@ -84,20 +85,26 @@ const DashboardTemplate = ({
|
|
|
84
85
|
</DashboardLayout.Layout>
|
|
85
86
|
);
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
argTypes: {},
|
|
88
|
+
const meta: Meta<typeof DashboardTemplate> = {
|
|
89
89
|
component: DashboardTemplate,
|
|
90
90
|
title: 'Dashboard'
|
|
91
|
-
}
|
|
91
|
+
};
|
|
92
92
|
|
|
93
|
-
export
|
|
93
|
+
export default meta;
|
|
94
|
+
type Story = StoryObj<typeof DashboardTemplate>;
|
|
94
95
|
|
|
95
|
-
export const
|
|
96
|
-
|
|
97
|
-
layout: generateLayout(100)
|
|
96
|
+
export const normal: Story = {
|
|
97
|
+
args: {}
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
export const
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
export const withManyPanels: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
layout: generateLayout(100)
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const withItemHeader: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
header: <Header />
|
|
109
|
+
}
|
|
103
110
|
};
|
package/src/Dashboard/Item.tsx
CHANGED
|
@@ -4,10 +4,9 @@ import { useAtomValue } from 'jotai';
|
|
|
4
4
|
import { equals, isNil, omit, type } from 'ramda';
|
|
5
5
|
import {
|
|
6
6
|
type CSSProperties,
|
|
7
|
-
type ForwardedRef,
|
|
8
|
-
forwardRef,
|
|
9
7
|
type MouseEvent,
|
|
10
8
|
type ReactElement,
|
|
9
|
+
RefObject,
|
|
11
10
|
useEffect,
|
|
12
11
|
useMemo
|
|
13
12
|
} from 'react';
|
|
@@ -31,148 +30,145 @@ interface DashboardItemProps {
|
|
|
31
30
|
onMouseUp?: (e: MouseEvent<HTMLDivElement>) => void;
|
|
32
31
|
onTouchEnd?: (e) => void;
|
|
33
32
|
style?: CSSProperties;
|
|
33
|
+
ref?: RefObject<HTMLDivElement>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const Item =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
data-padding={!disablePadding}
|
|
123
|
-
>
|
|
124
|
-
{childrenHeader && (
|
|
125
|
-
<div
|
|
126
|
-
className={classes.widgetHeader}
|
|
127
|
-
data-can-move={canControl}
|
|
128
|
-
>
|
|
129
|
-
{canControl && (
|
|
130
|
-
<div
|
|
131
|
-
{...listeners}
|
|
132
|
-
className={classes.widgetHeaderDraggable}
|
|
133
|
-
data-testid={`${id}_move_panel`}
|
|
134
|
-
/>
|
|
135
|
-
)}
|
|
136
|
-
{childrenHeader}
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
36
|
+
const Item = ({
|
|
37
|
+
children,
|
|
38
|
+
style,
|
|
39
|
+
className,
|
|
40
|
+
header,
|
|
41
|
+
onMouseDown,
|
|
42
|
+
onMouseUp,
|
|
43
|
+
onTouchEnd,
|
|
44
|
+
id,
|
|
45
|
+
disablePadding = false,
|
|
46
|
+
canMove = false,
|
|
47
|
+
additionalMemoProps = [],
|
|
48
|
+
ref
|
|
49
|
+
}: DashboardItemProps): ReactElement => {
|
|
50
|
+
const { isInViewport, setElement } = useViewportIntersection({
|
|
51
|
+
rootMargin: '140px 0px 140px 0px'
|
|
52
|
+
});
|
|
53
|
+
const hasHeader = !isNil(header);
|
|
54
|
+
|
|
55
|
+
const { classes, cx } = useDashboardItemStyles({ hasHeader });
|
|
56
|
+
const theme = useTheme();
|
|
57
|
+
|
|
58
|
+
const isResizingItem = useAtomValue(isResizingItemAtom);
|
|
59
|
+
|
|
60
|
+
const isResizing = useMemo(
|
|
61
|
+
() => equals(id, isResizingItem),
|
|
62
|
+
[isResizingItem, id]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const sanitizedReactGridLayoutClassName = useMemo(
|
|
66
|
+
() => (isResizing ? className : className?.replace(' resizing ', '')),
|
|
67
|
+
[className, isResizing]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const listeners = {
|
|
71
|
+
onMouseDown,
|
|
72
|
+
onMouseUp,
|
|
73
|
+
onTouchEnd
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const cardContainerListeners = !hasHeader ? listeners : {};
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (isNil(ref?.current)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setElement(ref.current);
|
|
84
|
+
}, [ref, setElement]);
|
|
85
|
+
|
|
86
|
+
const newTransform =
|
|
87
|
+
style?.transform &&
|
|
88
|
+
`translate3d(${style.transform.match(/translate\(([a-z0-9 ,-]+)\)/)[1]}, 0px)`;
|
|
89
|
+
|
|
90
|
+
return useMemoComponent({
|
|
91
|
+
Component: (
|
|
92
|
+
<div
|
|
93
|
+
{...cardContainerListeners}
|
|
94
|
+
className={sanitizedReactGridLayoutClassName}
|
|
95
|
+
ref={ref}
|
|
96
|
+
style={{
|
|
97
|
+
...omit(['transform'], style || {}),
|
|
98
|
+
transform: newTransform
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<ExpandableContainer>
|
|
102
|
+
{({ isExpanded, label, key, ...rest }) => {
|
|
103
|
+
const canControl = isExpanded ? false : canMove;
|
|
104
|
+
|
|
105
|
+
const childrenHeader = equals(type(header), 'Function')
|
|
106
|
+
? (header as (params: Parameters) => ReactElement)({
|
|
107
|
+
isExpanded,
|
|
108
|
+
key,
|
|
109
|
+
label,
|
|
110
|
+
ref,
|
|
111
|
+
...rest
|
|
112
|
+
})
|
|
113
|
+
: header;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={classes.widgetSubContainer} key={key}>
|
|
117
|
+
<Card
|
|
118
|
+
className={classes.widgetContainer}
|
|
119
|
+
data-padding={!disablePadding}
|
|
120
|
+
>
|
|
121
|
+
{childrenHeader && (
|
|
139
122
|
<div
|
|
140
|
-
className={
|
|
141
|
-
|
|
142
|
-
!disablePadding && classes.widgetPadding
|
|
143
|
-
)}
|
|
123
|
+
className={classes.widgetHeader}
|
|
124
|
+
data-can-move={canControl}
|
|
144
125
|
>
|
|
145
|
-
{
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
width="100%"
|
|
126
|
+
{canControl && (
|
|
127
|
+
<div
|
|
128
|
+
{...listeners}
|
|
129
|
+
className={classes.widgetHeaderDraggable}
|
|
130
|
+
data-testid={`${id}_move_panel`}
|
|
151
131
|
/>
|
|
152
|
-
) : (
|
|
153
|
-
children
|
|
154
132
|
)}
|
|
133
|
+
{childrenHeader}
|
|
155
134
|
</div>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
);
|
|
135
|
+
)}
|
|
136
|
+
<div
|
|
137
|
+
className={cx(
|
|
138
|
+
classes.widgetContent,
|
|
139
|
+
!disablePadding && classes.widgetPadding
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{!isInViewport ? (
|
|
143
|
+
<LoadingSkeleton
|
|
144
|
+
animation={false}
|
|
145
|
+
data-widget-skeleton={id}
|
|
146
|
+
height="100%"
|
|
147
|
+
width="100%"
|
|
148
|
+
/>
|
|
149
|
+
) : (
|
|
150
|
+
children
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
</Card>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}}
|
|
157
|
+
</ExpandableContainer>
|
|
158
|
+
</div>
|
|
159
|
+
),
|
|
160
|
+
memoProps: isInViewport
|
|
161
|
+
? [
|
|
162
|
+
style,
|
|
163
|
+
className,
|
|
164
|
+
header,
|
|
165
|
+
theme.palette.mode,
|
|
166
|
+
canMove,
|
|
167
|
+
isInViewport,
|
|
168
|
+
...additionalMemoProps
|
|
169
|
+
]
|
|
170
|
+
: [isInViewport, theme.palette.mode, style]
|
|
171
|
+
});
|
|
172
|
+
};
|
|
177
173
|
|
|
178
174
|
export default Item;
|
package/src/Dashboard/Layout.tsx
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import { Box } from '@mui/material';
|
|
2
2
|
|
|
3
3
|
import { useSetAtom } from 'jotai';
|
|
4
|
-
import { useCallback, useEffect,
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { ReactElement, useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
type Layout,
|
|
7
|
+
LayoutItem,
|
|
8
|
+
ReactGridLayout,
|
|
9
|
+
useContainerWidth
|
|
10
|
+
} from 'react-grid-layout';
|
|
11
|
+
|
|
12
|
+
import { useMemoComponent } from '..';
|
|
8
13
|
import { isResizingItemAtom } from './atoms';
|
|
9
14
|
import { useDashboardLayoutStyles } from './Dashboard.styles';
|
|
10
15
|
import { getColumnsFromScreenSize, getLayout, rowHeight } from './utils';
|
|
11
16
|
import 'react-grid-layout/css/styles.css';
|
|
12
17
|
|
|
13
|
-
const ReactGridLayout = WidthProvider(GridLayout);
|
|
14
|
-
|
|
15
18
|
interface DashboardLayoutProps<T> {
|
|
16
19
|
additionalMemoProps?: Array<unknown>;
|
|
17
|
-
changeLayout?: (newLayout:
|
|
18
|
-
children: Array<
|
|
20
|
+
changeLayout?: (newLayout: Layout) => void;
|
|
21
|
+
children: Array<ReactElement>;
|
|
19
22
|
displayGrid?: boolean;
|
|
20
23
|
isStatic?: boolean;
|
|
21
24
|
layout: Array<T>;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
const Handle = (axis, ref) => {
|
|
27
|
+
const Handle = (axis: string, ref: React.Ref<HTMLSpanElement>) => {
|
|
25
28
|
return (
|
|
26
29
|
<span
|
|
27
30
|
className={`react-resizable-handle react-resizable-handle-${axis}`}
|
|
@@ -32,14 +35,14 @@ const Handle = (axis, ref) => {
|
|
|
32
35
|
);
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
const DashboardLayout = <T extends
|
|
38
|
+
const DashboardLayout = <T extends LayoutItem>({
|
|
36
39
|
children,
|
|
37
40
|
changeLayout,
|
|
38
41
|
layout,
|
|
39
42
|
isStatic = false,
|
|
40
43
|
additionalMemoProps = []
|
|
41
|
-
}: DashboardLayoutProps<T>):
|
|
42
|
-
const
|
|
44
|
+
}: DashboardLayoutProps<T>): ReactElement => {
|
|
45
|
+
const { width, containerRef } = useContainerWidth();
|
|
43
46
|
|
|
44
47
|
const { classes } = useDashboardLayoutStyles(isStatic);
|
|
45
48
|
|
|
@@ -52,8 +55,8 @@ const DashboardLayout = <T extends Layout>({
|
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
const startResize = useCallback(
|
|
55
|
-
(_, _e, newItem:
|
|
56
|
-
setIsResizingItem(newItem
|
|
58
|
+
(_: Layout, _e: LayoutItem | null, newItem: LayoutItem | null) => {
|
|
59
|
+
setIsResizingItem((newItem as LayoutItem & { id: string })?.id ?? null);
|
|
57
60
|
},
|
|
58
61
|
[setIsResizingItem]
|
|
59
62
|
);
|
|
@@ -70,35 +73,30 @@ const DashboardLayout = <T extends Layout>({
|
|
|
70
73
|
};
|
|
71
74
|
}, [resize]);
|
|
72
75
|
|
|
76
|
+
const currentLayout = getLayout(layout);
|
|
77
|
+
|
|
73
78
|
return useMemoComponent({
|
|
74
79
|
Component: (
|
|
75
|
-
<Box
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
width={width}
|
|
93
|
-
>
|
|
94
|
-
{children}
|
|
95
|
-
</ReactGridLayout>
|
|
96
|
-
</Box>
|
|
97
|
-
)}
|
|
98
|
-
</ParentSize>
|
|
80
|
+
<Box ref={containerRef} sx={{ overflowX: 'hidden', overflowY: 'auto' }}>
|
|
81
|
+
<Box className={classes.container}>
|
|
82
|
+
<ReactGridLayout
|
|
83
|
+
gridConfig={{ cols: columns, margin: [12, 12], rowHeight }}
|
|
84
|
+
layout={currentLayout}
|
|
85
|
+
onLayoutChange={changeLayout}
|
|
86
|
+
onResizeStart={startResize}
|
|
87
|
+
onResizeStop={stopResize}
|
|
88
|
+
resizeConfig={{
|
|
89
|
+
handleComponent: Handle,
|
|
90
|
+
handles: ['s', 'e', 'se', 'sw', 'w']
|
|
91
|
+
}}
|
|
92
|
+
width={width}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</ReactGridLayout>
|
|
96
|
+
</Box>
|
|
99
97
|
</Box>
|
|
100
98
|
),
|
|
101
|
-
memoProps: [columns,
|
|
99
|
+
memoProps: [columns, currentLayout, isStatic, ...additionalMemoProps]
|
|
102
100
|
});
|
|
103
101
|
};
|
|
104
102
|
|
package/src/Dashboard/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lt } from 'ramda';
|
|
2
|
-
import type {
|
|
2
|
+
import type { LayoutItem } from 'react-grid-layout';
|
|
3
3
|
|
|
4
4
|
const minColumns = 1;
|
|
5
5
|
const breakpoint = 768;
|
|
@@ -13,7 +13,7 @@ export const getIsSmallScreenSize = (): boolean =>
|
|
|
13
13
|
export const getColumnsFromScreenSize = (): number =>
|
|
14
14
|
getIsSmallScreenSize() ? minColumns : maxColumns;
|
|
15
15
|
|
|
16
|
-
export const getLayout = <T extends
|
|
16
|
+
export const getLayout = <T extends LayoutItem>(layout: Array<T>): Array<T> => {
|
|
17
17
|
const isSmallScreenSize = getIsSmallScreenSize();
|
|
18
18
|
if (!isSmallScreenSize) {
|
|
19
19
|
return layout;
|