@etsoo/react 1.4.37 → 1.4.41
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/lib/app/Labels.d.ts +1 -0
- package/lib/app/Labels.js +1 -0
- package/lib/mu/DataGridRenderers.js +1 -1
- package/lib/mu/MUGlobal.d.ts +15 -0
- package/lib/mu/MUGlobal.js +28 -0
- package/lib/mu/MobileListItemRenderer.d.ts +1 -2
- package/lib/mu/MobileListItemRenderer.js +4 -41
- package/lib/mu/ResponsibleContainer.d.ts +2 -2
- package/lib/mu/ScrollerListEx.d.ts +20 -1
- package/lib/mu/ScrollerListEx.js +73 -6
- package/lib/mu/pages/EditPage.d.ts +8 -0
- package/lib/mu/pages/EditPage.js +5 -3
- package/lib/mu/pages/ResponsivePageProps.d.ts +2 -2
- package/package.json +1 -1
- package/src/app/Labels.ts +1 -0
- package/src/mu/DataGridRenderers.tsx +1 -1
- package/src/mu/MUGlobal.ts +32 -0
- package/src/mu/MobileListItemRenderer.tsx +5 -51
- package/src/mu/ResponsibleContainer.tsx +3 -2
- package/src/mu/ScrollerListEx.tsx +125 -8
- package/src/mu/pages/EditPage.tsx +26 -0
- package/src/mu/pages/ResponsivePageProps.ts +5 -2
package/lib/app/Labels.d.ts
CHANGED
package/lib/app/Labels.js
CHANGED
|
@@ -67,7 +67,7 @@ export var DataGridRenderers;
|
|
|
67
67
|
if (value)
|
|
68
68
|
return React.createElement(CheckIcon, { fontSize: "small" });
|
|
69
69
|
else
|
|
70
|
-
return React.createElement(ClearIcon, { fontSize: "small", color: "
|
|
70
|
+
return React.createElement(ClearIcon, { fontSize: "small", color: "warning" });
|
|
71
71
|
}
|
|
72
72
|
// To string
|
|
73
73
|
return new String(value);
|
package/lib/mu/MUGlobal.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Theme } from '@mui/material';
|
|
1
2
|
export declare class MUGlobal {
|
|
2
3
|
/**
|
|
3
4
|
* Search field shrink
|
|
@@ -62,6 +63,20 @@ export declare class MUGlobal {
|
|
|
62
63
|
* @returns Updated object
|
|
63
64
|
*/
|
|
64
65
|
static adjustWithTheme(size: number, adjust: {}, updateFunc: (value: number) => string): {};
|
|
66
|
+
/**
|
|
67
|
+
* Break points defined
|
|
68
|
+
*/
|
|
69
|
+
static breakpoints: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
70
|
+
/**
|
|
71
|
+
* Get multple medias theme space
|
|
72
|
+
* Responsive values and Breakpoints as an object
|
|
73
|
+
* xs = theme.breakpoints.up('xs')
|
|
74
|
+
* https://mui.com/system/basics/
|
|
75
|
+
* @param spaces Spaces
|
|
76
|
+
* @param theme Theme
|
|
77
|
+
* @returns Result
|
|
78
|
+
*/
|
|
79
|
+
static getSpace(spaces: {}, theme: Theme): number;
|
|
65
80
|
/**
|
|
66
81
|
* Update object number properties with theme
|
|
67
82
|
* @param input Input object
|
package/lib/mu/MUGlobal.js
CHANGED
|
@@ -70,6 +70,30 @@ export class MUGlobal {
|
|
|
70
70
|
});
|
|
71
71
|
return newObj;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Get multple medias theme space
|
|
75
|
+
* Responsive values and Breakpoints as an object
|
|
76
|
+
* xs = theme.breakpoints.up('xs')
|
|
77
|
+
* https://mui.com/system/basics/
|
|
78
|
+
* @param spaces Spaces
|
|
79
|
+
* @param theme Theme
|
|
80
|
+
* @returns Result
|
|
81
|
+
*/
|
|
82
|
+
static getSpace(spaces, theme) {
|
|
83
|
+
const start = this.breakpoints.length - 1;
|
|
84
|
+
for (let i = start; i >= 0; i--) {
|
|
85
|
+
const key = this.breakpoints[i];
|
|
86
|
+
const value = Reflect.get(spaces, key);
|
|
87
|
+
if (typeof value === 'number') {
|
|
88
|
+
const mediaRaw = theme.breakpoints.up(key);
|
|
89
|
+
const mediaQuery = mediaRaw.substring(mediaRaw.indexOf('('));
|
|
90
|
+
if (window.matchMedia(mediaQuery).matches) {
|
|
91
|
+
return parseInt(theme.spacing(value), 10);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
73
97
|
/**
|
|
74
98
|
* Update object number properties with theme
|
|
75
99
|
* @param input Input object
|
|
@@ -118,3 +142,7 @@ MUGlobal.textFieldVariant = 'filled';
|
|
|
118
142
|
* Page default paddings
|
|
119
143
|
*/
|
|
120
144
|
MUGlobal.pagePaddings = { xs: 2, sm: 3 };
|
|
145
|
+
/**
|
|
146
|
+
* Break points defined
|
|
147
|
+
*/
|
|
148
|
+
MUGlobal.breakpoints = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SxProps, Theme } from '@mui/material';
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import { ListItemReact } from '../components/ListItemReact';
|
|
4
3
|
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
@@ -9,7 +8,7 @@ import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
|
9
8
|
* @param renderer Renderer for card content
|
|
10
9
|
* @returns Component
|
|
11
10
|
*/
|
|
12
|
-
export declare function MobileListItemRenderer<T>({ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>,
|
|
11
|
+
export declare function MobileListItemRenderer<T>({ data, itemHeight, margins }: ScrollerListExInnerItemRendererProps<T>, renderer: (data: T) => [
|
|
13
12
|
string,
|
|
14
13
|
string | undefined,
|
|
15
14
|
React.ReactNode | (ListItemReact | boolean)[],
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { MoreFab } from './MoreFab';
|
|
4
|
-
import { MUGlobal } from './MUGlobal';
|
|
5
4
|
function getActions(input) {
|
|
6
5
|
// Actions
|
|
7
6
|
const actions = [];
|
|
@@ -19,48 +18,15 @@ function getActions(input) {
|
|
|
19
18
|
* @param renderer Renderer for card content
|
|
20
19
|
* @returns Component
|
|
21
20
|
*/
|
|
22
|
-
export function MobileListItemRenderer({ data, itemHeight },
|
|
21
|
+
export function MobileListItemRenderer({ data, itemHeight, margins }, renderer) {
|
|
23
22
|
// Loading
|
|
24
23
|
if (data == null)
|
|
25
24
|
return React.createElement(LinearProgress, null);
|
|
26
25
|
// Elements
|
|
27
26
|
const [title, subheader, actions, children] = renderer(data);
|
|
28
|
-
// Calculate margin
|
|
29
|
-
const calculateMargin = () => {
|
|
30
|
-
if (typeof margin === 'function')
|
|
31
|
-
return margin();
|
|
32
|
-
if (Array.isArray(margin)) {
|
|
33
|
-
const marginValue = margin[0];
|
|
34
|
-
const isNarrow = margin[1];
|
|
35
|
-
const half = MUGlobal.half(marginValue);
|
|
36
|
-
if (isNarrow) {
|
|
37
|
-
return {
|
|
38
|
-
marginLeft: 0,
|
|
39
|
-
marginRight: 0,
|
|
40
|
-
marginTop: half,
|
|
41
|
-
marginBottom: half
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
return {
|
|
46
|
-
marginLeft: half,
|
|
47
|
-
marginRight: half,
|
|
48
|
-
marginTop: half,
|
|
49
|
-
marginBottom: half
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const half = MUGlobal.half(margin);
|
|
54
|
-
return {
|
|
55
|
-
marginLeft: margin,
|
|
56
|
-
marginRight: margin,
|
|
57
|
-
marginTop: half,
|
|
58
|
-
marginBottom: half
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
27
|
return (React.createElement(Card, { sx: {
|
|
62
|
-
height:
|
|
63
|
-
...
|
|
28
|
+
height: itemHeight,
|
|
29
|
+
...margins
|
|
64
30
|
} },
|
|
65
31
|
React.createElement(CardHeader, { sx: { paddingBottom: 0.5 }, action: Array.isArray(actions) ? (React.createElement(MoreFab, { iconButton: true, size: "small", anchorOrigin: {
|
|
66
32
|
vertical: 'bottom',
|
|
@@ -95,9 +61,6 @@ export function MobileListItemRenderer({ data, itemHeight }, margin, renderer) {
|
|
|
95
61
|
}
|
|
96
62
|
} })) : (actions), title: title, titleTypographyProps: { variant: 'body2' }, subheader: subheader, subheaderTypographyProps: { variant: 'caption' } }),
|
|
97
63
|
React.createElement(CardContent, { sx: {
|
|
98
|
-
paddingTop: 0
|
|
99
|
-
'&:last-child': {
|
|
100
|
-
paddingBottom: 0 // default 24px
|
|
101
|
-
}
|
|
64
|
+
paddingTop: 0
|
|
102
65
|
} }, children)));
|
|
103
66
|
}
|
|
@@ -6,7 +6,7 @@ import { GridColumn } from '../components/GridColumn';
|
|
|
6
6
|
import { GridJsonData } from '../components/GridLoader';
|
|
7
7
|
import { DataGridExProps } from './DataGridEx';
|
|
8
8
|
import { GridMethodRef } from './GridMethodRef';
|
|
9
|
-
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
9
|
+
import { ScrollerListExInnerItemRendererProps, ScrollerListExItemSize } from './ScrollerListEx';
|
|
10
10
|
/**
|
|
11
11
|
* ResponsibleContainer props
|
|
12
12
|
*/
|
|
@@ -51,7 +51,7 @@ export interface ResponsibleContainerProps<T extends {}, F extends DataTypes.Bas
|
|
|
51
51
|
/**
|
|
52
52
|
* Item size, a function indicates its a variable size list
|
|
53
53
|
*/
|
|
54
|
-
itemSize:
|
|
54
|
+
itemSize: ScrollerListExItemSize;
|
|
55
55
|
/**
|
|
56
56
|
* Load data callback
|
|
57
57
|
*/
|
|
@@ -13,11 +13,26 @@ export interface ScrollerListExInnerItemRendererProps<T> extends ListChildCompon
|
|
|
13
13
|
* Item height
|
|
14
14
|
*/
|
|
15
15
|
itemHeight: number;
|
|
16
|
+
/**
|
|
17
|
+
* Item space
|
|
18
|
+
*/
|
|
19
|
+
space: number;
|
|
20
|
+
/**
|
|
21
|
+
* Default margins
|
|
22
|
+
*/
|
|
23
|
+
margins: {};
|
|
16
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Extended ScrollerList ItemSize type
|
|
27
|
+
* 1. Callback function
|
|
28
|
+
* 2. Static sets
|
|
29
|
+
* 3. Dynamic calculation
|
|
30
|
+
*/
|
|
31
|
+
export declare type ScrollerListExItemSize = ((index: number) => [number, number] | [number, number, {}]) | [number, number] | [number, {}, boolean?];
|
|
17
32
|
/**
|
|
18
33
|
* Extended ScrollerList Props
|
|
19
34
|
*/
|
|
20
|
-
export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'itemRenderer'> {
|
|
35
|
+
export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> {
|
|
21
36
|
/**
|
|
22
37
|
* Alternating colors for odd/even rows
|
|
23
38
|
*/
|
|
@@ -34,6 +49,10 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
|
|
|
34
49
|
* Item renderer
|
|
35
50
|
*/
|
|
36
51
|
itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
|
|
52
|
+
/**
|
|
53
|
+
* Item size, a function indicates its a variable size list
|
|
54
|
+
*/
|
|
55
|
+
itemSize: ScrollerListExItemSize;
|
|
37
56
|
/**
|
|
38
57
|
* On items select change
|
|
39
58
|
*/
|
package/lib/mu/ScrollerListEx.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { css } from '@emotion/css';
|
|
2
2
|
import { DataTypes, Utils } from '@etsoo/shared';
|
|
3
|
+
import { useTheme } from '@mui/material';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import { ScrollerList } from '../components/ScrollerList';
|
|
6
|
+
import { MUGlobal } from './MUGlobal';
|
|
5
7
|
// Scroll bar size
|
|
6
8
|
const scrollbarSize = 16;
|
|
7
9
|
// Selected class name
|
|
@@ -36,15 +38,44 @@ const createGridStyle = (alternatingColors, selectedColor) => {
|
|
|
36
38
|
}
|
|
37
39
|
});
|
|
38
40
|
};
|
|
41
|
+
// Default margin
|
|
42
|
+
const defaultMargin = (margin, isNarrow) => {
|
|
43
|
+
const half = MUGlobal.half(margin);
|
|
44
|
+
if (isNarrow == null) {
|
|
45
|
+
const half = MUGlobal.half(margin);
|
|
46
|
+
return {
|
|
47
|
+
marginLeft: margin,
|
|
48
|
+
marginRight: margin,
|
|
49
|
+
marginTop: half,
|
|
50
|
+
marginBottom: half
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (isNarrow) {
|
|
54
|
+
return {
|
|
55
|
+
marginLeft: 0,
|
|
56
|
+
marginRight: 0,
|
|
57
|
+
marginTop: half,
|
|
58
|
+
marginBottom: half
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
marginLeft: half,
|
|
63
|
+
marginRight: half,
|
|
64
|
+
marginTop: half,
|
|
65
|
+
marginBottom: half
|
|
66
|
+
};
|
|
67
|
+
};
|
|
39
68
|
// Default itemRenderer
|
|
40
|
-
function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight }) {
|
|
69
|
+
function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, space, margins }) {
|
|
41
70
|
// Child
|
|
42
71
|
const child = innerItemRenderer({
|
|
43
72
|
index,
|
|
44
73
|
data,
|
|
45
74
|
style,
|
|
46
75
|
selected,
|
|
47
|
-
itemHeight
|
|
76
|
+
itemHeight,
|
|
77
|
+
space,
|
|
78
|
+
margins
|
|
48
79
|
});
|
|
49
80
|
let rowClass = `ScrollerListEx-Row${index % 2}`;
|
|
50
81
|
if (selected)
|
|
@@ -82,17 +113,53 @@ export function ScrollerListEx(props) {
|
|
|
82
113
|
};
|
|
83
114
|
// Destruct
|
|
84
115
|
const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => { var _a; return (_a = DataTypes.getIdValue(data, idField)) !== null && _a !== void 0 ? _a : index; }, itemRenderer = (itemProps) => {
|
|
85
|
-
const itemHeight
|
|
86
|
-
? itemSize(itemProps.index)
|
|
87
|
-
: itemSize;
|
|
116
|
+
const [itemHeight, space, margins] = calculateItemSize(itemProps.index);
|
|
88
117
|
return defaultItemRenderer({
|
|
89
118
|
itemHeight,
|
|
90
119
|
innerItemRenderer,
|
|
91
120
|
onMouseDown,
|
|
121
|
+
space,
|
|
122
|
+
margins,
|
|
92
123
|
selected: isSelected(itemProps.data),
|
|
93
124
|
...itemProps
|
|
94
125
|
});
|
|
95
126
|
}, onSelectChange, selectedColor = '#edf4fb', ...rest } = props;
|
|
127
|
+
// Theme
|
|
128
|
+
const theme = useTheme();
|
|
129
|
+
// Cache calculation
|
|
130
|
+
const itemSizeResult = React.useMemo(() => {
|
|
131
|
+
if (typeof itemSize === 'function')
|
|
132
|
+
return undefined;
|
|
133
|
+
const [size, spaces, isNarrow] = itemSize;
|
|
134
|
+
if (typeof spaces === 'number')
|
|
135
|
+
return [
|
|
136
|
+
size,
|
|
137
|
+
spaces,
|
|
138
|
+
defaultMargin(MUGlobal.pagePaddings, undefined)
|
|
139
|
+
];
|
|
140
|
+
return [
|
|
141
|
+
size,
|
|
142
|
+
MUGlobal.getSpace(spaces, theme),
|
|
143
|
+
defaultMargin(spaces, isNarrow)
|
|
144
|
+
];
|
|
145
|
+
}, [itemSize]);
|
|
146
|
+
// Calculate size
|
|
147
|
+
const calculateItemSize = (index) => {
|
|
148
|
+
// Callback function
|
|
149
|
+
if (typeof itemSize === 'function') {
|
|
150
|
+
const result = itemSize(index);
|
|
151
|
+
if (result.length == 2)
|
|
152
|
+
return [...result, defaultMargin(MUGlobal.pagePaddings)];
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
// Calculation
|
|
156
|
+
return itemSizeResult;
|
|
157
|
+
};
|
|
158
|
+
// Local item size
|
|
159
|
+
const itemSizeLocal = (index) => {
|
|
160
|
+
const [size, space] = calculateItemSize(index);
|
|
161
|
+
return size + space;
|
|
162
|
+
};
|
|
96
163
|
// Layout
|
|
97
|
-
return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize:
|
|
164
|
+
return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
|
|
98
165
|
}
|
|
@@ -4,10 +4,18 @@ import { CommonPageProps } from './CommonPageProps';
|
|
|
4
4
|
* Add / Edit page props
|
|
5
5
|
*/
|
|
6
6
|
export interface EditPageProps extends Omit<CommonPageProps, 'onSubmit'> {
|
|
7
|
+
/**
|
|
8
|
+
* Is editing
|
|
9
|
+
*/
|
|
10
|
+
isEditing?: boolean;
|
|
7
11
|
/**
|
|
8
12
|
* On form submit
|
|
9
13
|
*/
|
|
10
14
|
onSubmit?: FormEventHandler<HTMLFormElement>;
|
|
15
|
+
/**
|
|
16
|
+
* On delete callback
|
|
17
|
+
*/
|
|
18
|
+
onDelete?: () => Promise<void> | void;
|
|
11
19
|
}
|
|
12
20
|
/**
|
|
13
21
|
* Add / Edit page
|
package/lib/mu/pages/EditPage.js
CHANGED
|
@@ -4,22 +4,24 @@ import { Labels } from '../../app/Labels';
|
|
|
4
4
|
import { MUGlobal } from '../MUGlobal';
|
|
5
5
|
import { CommonPage, CommonPageScrollContainer } from './CommonPage';
|
|
6
6
|
import SaveIcon from '@mui/icons-material/Save';
|
|
7
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
7
8
|
/**
|
|
8
9
|
* Add / Edit page
|
|
9
10
|
* @param props Props
|
|
10
11
|
*/
|
|
11
12
|
export function EditPage(props) {
|
|
12
13
|
// Destruct
|
|
13
|
-
const { children, onSubmit, paddings = MUGlobal.pagePaddings, scrollContainer = CommonPageScrollContainer, ...rest } = props;
|
|
14
|
+
const { children, isEditing, onDelete, onSubmit, paddings = MUGlobal.pagePaddings, scrollContainer = CommonPageScrollContainer, ...rest } = props;
|
|
14
15
|
// Labels
|
|
15
16
|
const labels = Labels.CommonPage;
|
|
16
17
|
return (React.createElement(CommonPage, { paddings: paddings, scrollContainer: scrollContainer, ...rest },
|
|
17
18
|
React.createElement("form", { onSubmit: onSubmit },
|
|
18
19
|
React.createElement(Grid, { container: true, justifyContent: "left", spacing: paddings, paddingTop: 1 }, children),
|
|
19
|
-
React.createElement(Grid, { container: true, position: "sticky", sx: {
|
|
20
|
+
React.createElement(Grid, { container: true, position: "sticky", display: "flex", gap: paddings, sx: {
|
|
20
21
|
top: 'auto',
|
|
21
22
|
bottom: (theme) => MUGlobal.updateWithTheme(paddings, theme.spacing),
|
|
22
23
|
paddingTop: paddings
|
|
23
24
|
} },
|
|
24
|
-
React.createElement(Button, {
|
|
25
|
+
isEditing && onDelete && (React.createElement(Button, { color: "primary", variant: "outlined", onClick: () => onDelete(), startIcon: React.createElement(DeleteIcon, { color: "warning" }) }, labels.delete)),
|
|
26
|
+
React.createElement(Button, { variant: "contained", type: "submit", fullWidth: true, startIcon: React.createElement(SaveIcon, null), sx: { flexGrow: 1 } }, labels.save)))));
|
|
25
27
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { DataTypes } from '@etsoo/shared';
|
|
3
3
|
import { ListChildComponentProps } from 'react-window';
|
|
4
4
|
import { GridMethodRef } from '../GridMethodRef';
|
|
5
|
-
import { ScrollerListExInnerItemRendererProps } from '../ScrollerListEx';
|
|
5
|
+
import { ScrollerListExInnerItemRendererProps, ScrollerListExItemSize } from '../ScrollerListEx';
|
|
6
6
|
import { DataGridPageProps } from './DataGridPageProps';
|
|
7
7
|
/**
|
|
8
8
|
* Response page props
|
|
@@ -23,7 +23,7 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate> extends
|
|
|
23
23
|
/**
|
|
24
24
|
* Item size, a function indicates its a variable size list
|
|
25
25
|
*/
|
|
26
|
-
itemSize:
|
|
26
|
+
itemSize: ScrollerListExItemSize;
|
|
27
27
|
/**
|
|
28
28
|
* Methods
|
|
29
29
|
*/
|
package/package.json
CHANGED
package/src/app/Labels.ts
CHANGED
|
@@ -103,7 +103,7 @@ export namespace DataGridRenderers {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (value) return <CheckIcon fontSize="small" />;
|
|
106
|
-
else return <ClearIcon fontSize="small" color="
|
|
106
|
+
else return <ClearIcon fontSize="small" color="warning" />;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// To string
|
package/src/mu/MUGlobal.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NumberUtils } from '@etsoo/shared';
|
|
2
|
+
import { Breakpoint, Theme } from '@mui/material';
|
|
2
3
|
|
|
3
4
|
export class MUGlobal {
|
|
4
5
|
/**
|
|
@@ -125,6 +126,37 @@ export class MUGlobal {
|
|
|
125
126
|
return newObj;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Break points defined
|
|
131
|
+
*/
|
|
132
|
+
static breakpoints = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get multple medias theme space
|
|
136
|
+
* Responsive values and Breakpoints as an object
|
|
137
|
+
* xs = theme.breakpoints.up('xs')
|
|
138
|
+
* https://mui.com/system/basics/
|
|
139
|
+
* @param spaces Spaces
|
|
140
|
+
* @param theme Theme
|
|
141
|
+
* @returns Result
|
|
142
|
+
*/
|
|
143
|
+
static getSpace(spaces: {}, theme: Theme) {
|
|
144
|
+
const start = this.breakpoints.length - 1;
|
|
145
|
+
for (let i = start; i >= 0; i--) {
|
|
146
|
+
const key = this.breakpoints[i];
|
|
147
|
+
const value = Reflect.get(spaces, key);
|
|
148
|
+
if (typeof value === 'number') {
|
|
149
|
+
const mediaRaw = theme.breakpoints.up(key as Breakpoint);
|
|
150
|
+
const mediaQuery = mediaRaw.substring(mediaRaw.indexOf('('));
|
|
151
|
+
if (window.matchMedia(mediaQuery).matches) {
|
|
152
|
+
return parseInt(theme.spacing(value), 10);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
128
160
|
/**
|
|
129
161
|
* Update object number properties with theme
|
|
130
162
|
* @param input Input object
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Card,
|
|
3
|
-
CardContent,
|
|
4
|
-
CardHeader,
|
|
5
|
-
LinearProgress,
|
|
6
|
-
SxProps,
|
|
7
|
-
Theme
|
|
8
|
-
} from '@mui/material';
|
|
1
|
+
import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
|
|
9
2
|
import React from 'react';
|
|
10
3
|
import { ListItemReact } from '../components/ListItemReact';
|
|
11
4
|
import { MoreFab } from './MoreFab';
|
|
12
|
-
import { MUGlobal } from './MUGlobal';
|
|
13
5
|
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
14
6
|
|
|
15
7
|
function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
|
|
@@ -30,8 +22,7 @@ function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
|
|
|
30
22
|
* @returns Component
|
|
31
23
|
*/
|
|
32
24
|
export function MobileListItemRenderer<T>(
|
|
33
|
-
{ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>,
|
|
34
|
-
margin: {} | [{}, boolean] | (() => SxProps<Theme>),
|
|
25
|
+
{ data, itemHeight, margins }: ScrollerListExInnerItemRendererProps<T>,
|
|
35
26
|
renderer: (
|
|
36
27
|
data: T
|
|
37
28
|
) => [
|
|
@@ -47,45 +38,11 @@ export function MobileListItemRenderer<T>(
|
|
|
47
38
|
// Elements
|
|
48
39
|
const [title, subheader, actions, children] = renderer(data);
|
|
49
40
|
|
|
50
|
-
// Calculate margin
|
|
51
|
-
const calculateMargin = () => {
|
|
52
|
-
if (typeof margin === 'function') return margin();
|
|
53
|
-
if (Array.isArray(margin)) {
|
|
54
|
-
const marginValue = margin[0];
|
|
55
|
-
const isNarrow = margin[1];
|
|
56
|
-
const half = MUGlobal.half(marginValue);
|
|
57
|
-
if (isNarrow) {
|
|
58
|
-
return {
|
|
59
|
-
marginLeft: 0,
|
|
60
|
-
marginRight: 0,
|
|
61
|
-
marginTop: half,
|
|
62
|
-
marginBottom: half
|
|
63
|
-
};
|
|
64
|
-
} else {
|
|
65
|
-
return {
|
|
66
|
-
marginLeft: half,
|
|
67
|
-
marginRight: half,
|
|
68
|
-
marginTop: half,
|
|
69
|
-
marginBottom: half
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const half = MUGlobal.half(margin);
|
|
75
|
-
return {
|
|
76
|
-
marginLeft: margin,
|
|
77
|
-
marginRight: margin,
|
|
78
|
-
marginTop: half,
|
|
79
|
-
marginBottom: half
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
41
|
return (
|
|
84
42
|
<Card
|
|
85
43
|
sx={{
|
|
86
|
-
height:
|
|
87
|
-
|
|
88
|
-
...calculateMargin()
|
|
44
|
+
height: itemHeight,
|
|
45
|
+
...margins
|
|
89
46
|
}}
|
|
90
47
|
>
|
|
91
48
|
<CardHeader
|
|
@@ -143,10 +100,7 @@ export function MobileListItemRenderer<T>(
|
|
|
143
100
|
/>
|
|
144
101
|
<CardContent
|
|
145
102
|
sx={{
|
|
146
|
-
paddingTop: 0
|
|
147
|
-
'&:last-child': {
|
|
148
|
-
paddingBottom: 0 // default 24px
|
|
149
|
-
}
|
|
103
|
+
paddingTop: 0
|
|
150
104
|
}}
|
|
151
105
|
>
|
|
152
106
|
{children}
|
|
@@ -21,7 +21,8 @@ import { MUGlobal } from './MUGlobal';
|
|
|
21
21
|
import { PullToRefreshUI } from './PullToRefreshUI';
|
|
22
22
|
import {
|
|
23
23
|
ScrollerListEx,
|
|
24
|
-
ScrollerListExInnerItemRendererProps
|
|
24
|
+
ScrollerListExInnerItemRendererProps,
|
|
25
|
+
ScrollerListExItemSize
|
|
25
26
|
} from './ScrollerListEx';
|
|
26
27
|
import { SearchBar } from './SearchBar';
|
|
27
28
|
|
|
@@ -91,7 +92,7 @@ export interface ResponsibleContainerProps<
|
|
|
91
92
|
/**
|
|
92
93
|
* Item size, a function indicates its a variable size list
|
|
93
94
|
*/
|
|
94
|
-
itemSize:
|
|
95
|
+
itemSize: ScrollerListExItemSize;
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* Load data callback
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { css } from '@emotion/css';
|
|
2
2
|
import { DataTypes, Utils } from '@etsoo/shared';
|
|
3
|
+
import { useTheme } from '@mui/material';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import { ListChildComponentProps } from 'react-window';
|
|
5
6
|
import { ScrollerList, ScrollerListProps } from '../components/ScrollerList';
|
|
7
|
+
import { MUGlobal } from './MUGlobal';
|
|
6
8
|
|
|
7
9
|
// Scroll bar size
|
|
8
10
|
const scrollbarSize = 16;
|
|
@@ -44,6 +46,37 @@ const createGridStyle = (
|
|
|
44
46
|
});
|
|
45
47
|
};
|
|
46
48
|
|
|
49
|
+
// Default margin
|
|
50
|
+
const defaultMargin = (margin: {}, isNarrow?: boolean) => {
|
|
51
|
+
const half = MUGlobal.half(margin);
|
|
52
|
+
|
|
53
|
+
if (isNarrow == null) {
|
|
54
|
+
const half = MUGlobal.half(margin);
|
|
55
|
+
return {
|
|
56
|
+
marginLeft: margin,
|
|
57
|
+
marginRight: margin,
|
|
58
|
+
marginTop: half,
|
|
59
|
+
marginBottom: half
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isNarrow) {
|
|
64
|
+
return {
|
|
65
|
+
marginLeft: 0,
|
|
66
|
+
marginRight: 0,
|
|
67
|
+
marginTop: half,
|
|
68
|
+
marginBottom: half
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
marginLeft: half,
|
|
74
|
+
marginRight: half,
|
|
75
|
+
marginTop: half,
|
|
76
|
+
marginBottom: half
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
47
80
|
/**
|
|
48
81
|
* Extended ScrollerList inner item renderer props
|
|
49
82
|
*/
|
|
@@ -58,13 +91,34 @@ export interface ScrollerListExInnerItemRendererProps<T>
|
|
|
58
91
|
* Item height
|
|
59
92
|
*/
|
|
60
93
|
itemHeight: number;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Item space
|
|
97
|
+
*/
|
|
98
|
+
space: number;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default margins
|
|
102
|
+
*/
|
|
103
|
+
margins: {};
|
|
61
104
|
}
|
|
62
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Extended ScrollerList ItemSize type
|
|
108
|
+
* 1. Callback function
|
|
109
|
+
* 2. Static sets
|
|
110
|
+
* 3. Dynamic calculation
|
|
111
|
+
*/
|
|
112
|
+
export type ScrollerListExItemSize =
|
|
113
|
+
| ((index: number) => [number, number] | [number, number, {}])
|
|
114
|
+
| [number, number]
|
|
115
|
+
| [number, {}, boolean?];
|
|
116
|
+
|
|
63
117
|
/**
|
|
64
118
|
* Extended ScrollerList Props
|
|
65
119
|
*/
|
|
66
120
|
export interface ScrollerListExProps<T>
|
|
67
|
-
extends Omit<ScrollerListProps<T>, 'itemRenderer'> {
|
|
121
|
+
extends Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> {
|
|
68
122
|
/**
|
|
69
123
|
* Alternating colors for odd/even rows
|
|
70
124
|
*/
|
|
@@ -87,6 +141,11 @@ export interface ScrollerListExProps<T>
|
|
|
87
141
|
*/
|
|
88
142
|
itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
|
|
89
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Item size, a function indicates its a variable size list
|
|
146
|
+
*/
|
|
147
|
+
itemSize: ScrollerListExItemSize;
|
|
148
|
+
|
|
90
149
|
/**
|
|
91
150
|
* On items select change
|
|
92
151
|
*/
|
|
@@ -116,6 +175,16 @@ interface defaultItemRendererProps<T> extends ListChildComponentProps<T> {
|
|
|
116
175
|
*/
|
|
117
176
|
itemHeight: number;
|
|
118
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Item space
|
|
180
|
+
*/
|
|
181
|
+
space: number;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Default margins
|
|
185
|
+
*/
|
|
186
|
+
margins: {};
|
|
187
|
+
|
|
119
188
|
/**
|
|
120
189
|
* Item selected
|
|
121
190
|
*/
|
|
@@ -130,7 +199,9 @@ function defaultItemRenderer<T>({
|
|
|
130
199
|
onMouseDown,
|
|
131
200
|
selected,
|
|
132
201
|
style,
|
|
133
|
-
itemHeight
|
|
202
|
+
itemHeight,
|
|
203
|
+
space,
|
|
204
|
+
margins
|
|
134
205
|
}: defaultItemRendererProps<T>) {
|
|
135
206
|
// Child
|
|
136
207
|
const child = innerItemRenderer({
|
|
@@ -138,7 +209,9 @@ function defaultItemRenderer<T>({
|
|
|
138
209
|
data,
|
|
139
210
|
style,
|
|
140
211
|
selected,
|
|
141
|
-
itemHeight
|
|
212
|
+
itemHeight,
|
|
213
|
+
space,
|
|
214
|
+
margins
|
|
142
215
|
});
|
|
143
216
|
|
|
144
217
|
let rowClass = `ScrollerListEx-Row${index % 2}`;
|
|
@@ -202,14 +275,15 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
|
|
|
202
275
|
itemKey = (index: number, data: T) =>
|
|
203
276
|
DataTypes.getIdValue(data, idField) ?? index,
|
|
204
277
|
itemRenderer = (itemProps) => {
|
|
205
|
-
const itemHeight =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
: itemSize;
|
|
278
|
+
const [itemHeight, space, margins] = calculateItemSize(
|
|
279
|
+
itemProps.index
|
|
280
|
+
);
|
|
209
281
|
return defaultItemRenderer({
|
|
210
282
|
itemHeight,
|
|
211
283
|
innerItemRenderer,
|
|
212
284
|
onMouseDown,
|
|
285
|
+
space,
|
|
286
|
+
margins,
|
|
213
287
|
selected: isSelected(itemProps.data),
|
|
214
288
|
...itemProps
|
|
215
289
|
});
|
|
@@ -219,6 +293,49 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
|
|
|
219
293
|
...rest
|
|
220
294
|
} = props;
|
|
221
295
|
|
|
296
|
+
// Theme
|
|
297
|
+
const theme = useTheme();
|
|
298
|
+
|
|
299
|
+
// Cache calculation
|
|
300
|
+
const itemSizeResult = React.useMemo(():
|
|
301
|
+
| [number, number, {}]
|
|
302
|
+
| undefined => {
|
|
303
|
+
if (typeof itemSize === 'function') return undefined;
|
|
304
|
+
const [size, spaces, isNarrow] = itemSize;
|
|
305
|
+
if (typeof spaces === 'number')
|
|
306
|
+
return [
|
|
307
|
+
size,
|
|
308
|
+
spaces,
|
|
309
|
+
defaultMargin(MUGlobal.pagePaddings, undefined)
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
return [
|
|
313
|
+
size,
|
|
314
|
+
MUGlobal.getSpace(spaces, theme),
|
|
315
|
+
defaultMargin(spaces, isNarrow)
|
|
316
|
+
];
|
|
317
|
+
}, [itemSize]);
|
|
318
|
+
|
|
319
|
+
// Calculate size
|
|
320
|
+
const calculateItemSize = (index: number): [number, number, {}] => {
|
|
321
|
+
// Callback function
|
|
322
|
+
if (typeof itemSize === 'function') {
|
|
323
|
+
const result = itemSize(index);
|
|
324
|
+
if (result.length == 2)
|
|
325
|
+
return [...result, defaultMargin(MUGlobal.pagePaddings)];
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Calculation
|
|
330
|
+
return itemSizeResult!;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Local item size
|
|
334
|
+
const itemSizeLocal = (index: number) => {
|
|
335
|
+
const [size, space] = calculateItemSize(index);
|
|
336
|
+
return size + space;
|
|
337
|
+
};
|
|
338
|
+
|
|
222
339
|
// Layout
|
|
223
340
|
return (
|
|
224
341
|
<ScrollerList<T>
|
|
@@ -229,7 +346,7 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
|
|
|
229
346
|
)}
|
|
230
347
|
itemKey={itemKey}
|
|
231
348
|
itemRenderer={itemRenderer}
|
|
232
|
-
itemSize={
|
|
349
|
+
itemSize={itemSizeLocal}
|
|
233
350
|
{...rest}
|
|
234
351
|
/>
|
|
235
352
|
);
|
|
@@ -5,15 +5,26 @@ import { MUGlobal } from '../MUGlobal';
|
|
|
5
5
|
import { CommonPage, CommonPageScrollContainer } from './CommonPage';
|
|
6
6
|
import { CommonPageProps } from './CommonPageProps';
|
|
7
7
|
import SaveIcon from '@mui/icons-material/Save';
|
|
8
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Add / Edit page props
|
|
11
12
|
*/
|
|
12
13
|
export interface EditPageProps extends Omit<CommonPageProps, 'onSubmit'> {
|
|
14
|
+
/**
|
|
15
|
+
* Is editing
|
|
16
|
+
*/
|
|
17
|
+
isEditing?: boolean;
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* On form submit
|
|
15
21
|
*/
|
|
16
22
|
onSubmit?: FormEventHandler<HTMLFormElement>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* On delete callback
|
|
26
|
+
*/
|
|
27
|
+
onDelete?: () => Promise<void> | void;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
/**
|
|
@@ -24,6 +35,8 @@ export function EditPage(props: EditPageProps) {
|
|
|
24
35
|
// Destruct
|
|
25
36
|
const {
|
|
26
37
|
children,
|
|
38
|
+
isEditing,
|
|
39
|
+
onDelete,
|
|
27
40
|
onSubmit,
|
|
28
41
|
paddings = MUGlobal.pagePaddings,
|
|
29
42
|
scrollContainer = CommonPageScrollContainer,
|
|
@@ -51,6 +64,8 @@ export function EditPage(props: EditPageProps) {
|
|
|
51
64
|
<Grid
|
|
52
65
|
container
|
|
53
66
|
position="sticky"
|
|
67
|
+
display="flex"
|
|
68
|
+
gap={paddings}
|
|
54
69
|
sx={{
|
|
55
70
|
top: 'auto',
|
|
56
71
|
bottom: (theme) =>
|
|
@@ -58,11 +73,22 @@ export function EditPage(props: EditPageProps) {
|
|
|
58
73
|
paddingTop: paddings
|
|
59
74
|
}}
|
|
60
75
|
>
|
|
76
|
+
{isEditing && onDelete && (
|
|
77
|
+
<Button
|
|
78
|
+
color="primary"
|
|
79
|
+
variant="outlined"
|
|
80
|
+
onClick={() => onDelete()}
|
|
81
|
+
startIcon={<DeleteIcon color="warning" />}
|
|
82
|
+
>
|
|
83
|
+
{labels.delete}
|
|
84
|
+
</Button>
|
|
85
|
+
)}
|
|
61
86
|
<Button
|
|
62
87
|
variant="contained"
|
|
63
88
|
type="submit"
|
|
64
89
|
fullWidth
|
|
65
90
|
startIcon={<SaveIcon />}
|
|
91
|
+
sx={{ flexGrow: 1 }}
|
|
66
92
|
>
|
|
67
93
|
{labels.save}
|
|
68
94
|
</Button>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { DataTypes } from '@etsoo/shared';
|
|
2
2
|
import { ListChildComponentProps } from 'react-window';
|
|
3
3
|
import { GridMethodRef } from '../GridMethodRef';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ScrollerListExInnerItemRendererProps,
|
|
6
|
+
ScrollerListExItemSize
|
|
7
|
+
} from '../ScrollerListEx';
|
|
5
8
|
import { DataGridPageProps } from './DataGridPageProps';
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -32,7 +35,7 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate>
|
|
|
32
35
|
/**
|
|
33
36
|
* Item size, a function indicates its a variable size list
|
|
34
37
|
*/
|
|
35
|
-
itemSize:
|
|
38
|
+
itemSize: ScrollerListExItemSize;
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Methods
|