@etsoo/react 1.4.11 → 1.4.15
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/ReactApp.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/mu/DnDList.d.ts +1 -1
- package/lib/mu/DnDList.js +9 -3
- package/lib/mu/MobileListItemRenderer.d.ts +16 -0
- package/lib/mu/MobileListItemRenderer.js +70 -0
- package/lib/mu/ResponsibleContainer.d.ts +69 -0
- package/lib/mu/ResponsibleContainer.js +79 -0
- package/package.json +3 -3
- package/src/app/ReactApp.ts +2 -1
- package/src/index.ts +2 -0
- package/src/mu/DnDList.tsx +12 -4
- package/src/mu/MobileListItemRenderer.tsx +113 -0
- package/src/mu/ResponsibleContainer.tsx +242 -0
package/lib/app/ReactApp.js
CHANGED
|
@@ -96,7 +96,7 @@ export class ReactApp extends CoreApp {
|
|
|
96
96
|
if (startPos > 0) {
|
|
97
97
|
const main = message.substring(0, startPos).trim();
|
|
98
98
|
const tip = message.substring(startPos);
|
|
99
|
-
const titleNode = React.createElement(React.Fragment, null, main, React.createElement('
|
|
99
|
+
const titleNode = React.createElement(React.Fragment, null, main, React.createElement('br'), React.createElement('span', { style: { fontSize: '9px' } }, tip));
|
|
100
100
|
this.notifier.alert(titleNode, callback);
|
|
101
101
|
return;
|
|
102
102
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export * from './mu/ItemList';
|
|
|
48
48
|
export * from './mu/ListItemRightIcon';
|
|
49
49
|
export * from './mu/LoadingButton';
|
|
50
50
|
export * from './mu/MaskInput';
|
|
51
|
+
export * from './mu/MobileListItemRenderer';
|
|
51
52
|
export * from './mu/MoreFab';
|
|
52
53
|
export * from './mu/MUGlobal';
|
|
53
54
|
export * from './mu/NotifierMU';
|
|
@@ -55,6 +56,7 @@ export * from './mu/OptionGroup';
|
|
|
55
56
|
export * from './mu/PList';
|
|
56
57
|
export * from './mu/ProgressCount';
|
|
57
58
|
export * from './mu/PullToRefreshUI';
|
|
59
|
+
export * from './mu/ResponsibleContainer';
|
|
58
60
|
export * from './mu/RLink';
|
|
59
61
|
export * from './mu/ScrollerListEx';
|
|
60
62
|
export * from './mu/ScrollTopFab';
|
package/lib/index.js
CHANGED
|
@@ -51,6 +51,7 @@ export * from './mu/ItemList';
|
|
|
51
51
|
export * from './mu/ListItemRightIcon';
|
|
52
52
|
export * from './mu/LoadingButton';
|
|
53
53
|
export * from './mu/MaskInput';
|
|
54
|
+
export * from './mu/MobileListItemRenderer';
|
|
54
55
|
export * from './mu/MoreFab';
|
|
55
56
|
export * from './mu/MUGlobal';
|
|
56
57
|
export * from './mu/NotifierMU';
|
|
@@ -58,6 +59,7 @@ export * from './mu/OptionGroup';
|
|
|
58
59
|
export * from './mu/PList';
|
|
59
60
|
export * from './mu/ProgressCount';
|
|
60
61
|
export * from './mu/PullToRefreshUI';
|
|
62
|
+
export * from './mu/ResponsibleContainer';
|
|
61
63
|
export * from './mu/RLink';
|
|
62
64
|
export * from './mu/ScrollerListEx';
|
|
63
65
|
export * from './mu/ScrollTopFab';
|
package/lib/mu/DnDList.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
|
|
|
43
43
|
/**
|
|
44
44
|
* Top and bottom sides renderer
|
|
45
45
|
*/
|
|
46
|
-
sideRenderer?: (top: boolean, addItem: (item: D) => boolean, addItems: (items: D[]) => number) => React.ReactNode;
|
|
46
|
+
sideRenderer?: (top: boolean, addItem: (item: D) => boolean, addItems: (items: D[]) => number, reloadItems: () => void) => React.ReactNode;
|
|
47
47
|
/**
|
|
48
48
|
* Name for hidden form input
|
|
49
49
|
*/
|
package/lib/mu/DnDList.js
CHANGED
|
@@ -90,13 +90,18 @@ export function DnDList(props) {
|
|
|
90
90
|
// Update the state
|
|
91
91
|
changeItems(newItems);
|
|
92
92
|
};
|
|
93
|
-
|
|
93
|
+
// Reload items
|
|
94
|
+
const reloadItems = () => {
|
|
94
95
|
// Load data
|
|
95
96
|
loadData(name).then((items) => setItems(items));
|
|
97
|
+
};
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
// Load data
|
|
100
|
+
reloadItems();
|
|
96
101
|
}, [name]);
|
|
97
102
|
// Layout
|
|
98
103
|
return (React.createElement(React.Fragment, null,
|
|
99
|
-
sideRenderer && sideRenderer(true, addItem, addItems),
|
|
104
|
+
sideRenderer && sideRenderer(true, addItem, addItems, reloadItems),
|
|
100
105
|
React.createElement(Component, { ...componentProps },
|
|
101
106
|
React.createElement(DragDropContext, { onDragEnd: onDragEndLocal },
|
|
102
107
|
React.createElement(Droppable, { droppableId: name }, (provided, snapshot) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, style: getListStyle(snapshot.isDraggingOver) },
|
|
@@ -113,5 +118,6 @@ export function DnDList(props) {
|
|
|
113
118
|
} }, children(item, index, deleteItem, editItem)))));
|
|
114
119
|
}),
|
|
115
120
|
provided.placeholder))))),
|
|
116
|
-
sideRenderer &&
|
|
121
|
+
sideRenderer &&
|
|
122
|
+
sideRenderer(false, addItem, addItems, reloadItems)));
|
|
117
123
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ListItemReact } from '../components/ListItemReact';
|
|
3
|
+
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
4
|
+
/**
|
|
5
|
+
* Default mobile list item renderer
|
|
6
|
+
* @param param0 List renderer props
|
|
7
|
+
* @param margin Margin
|
|
8
|
+
* @param renderer Renderer for card content
|
|
9
|
+
* @returns Component
|
|
10
|
+
*/
|
|
11
|
+
export declare function MobileListItemRenderer<T>({ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>, margin: {}, renderer: (data: T) => [
|
|
12
|
+
string,
|
|
13
|
+
string | undefined,
|
|
14
|
+
React.ReactNode | (ListItemReact | boolean)[],
|
|
15
|
+
React.ReactNode
|
|
16
|
+
]): JSX.Element;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { MoreFab } from './MoreFab';
|
|
4
|
+
import { MUGlobal } from './MUGlobal';
|
|
5
|
+
function getActions(input) {
|
|
6
|
+
// Actions
|
|
7
|
+
const actions = [];
|
|
8
|
+
input.forEach((action) => {
|
|
9
|
+
if (typeof action === 'boolean')
|
|
10
|
+
return;
|
|
11
|
+
actions.push(action);
|
|
12
|
+
});
|
|
13
|
+
return actions;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default mobile list item renderer
|
|
17
|
+
* @param param0 List renderer props
|
|
18
|
+
* @param margin Margin
|
|
19
|
+
* @param renderer Renderer for card content
|
|
20
|
+
* @returns Component
|
|
21
|
+
*/
|
|
22
|
+
export function MobileListItemRenderer({ data, itemHeight }, margin, renderer) {
|
|
23
|
+
// Loading
|
|
24
|
+
if (data == null)
|
|
25
|
+
return React.createElement(LinearProgress, null);
|
|
26
|
+
// Elements
|
|
27
|
+
const [title, subheader, actions, children] = renderer(data);
|
|
28
|
+
// Half
|
|
29
|
+
const halfMargin = MUGlobal.half(margin);
|
|
30
|
+
return (React.createElement(Card, { sx: {
|
|
31
|
+
height: (theme) => MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
|
|
32
|
+
marginLeft: margin,
|
|
33
|
+
marginRight: margin,
|
|
34
|
+
marginTop: halfMargin,
|
|
35
|
+
marginBottom: halfMargin
|
|
36
|
+
} },
|
|
37
|
+
React.createElement(CardHeader, { sx: { paddingBottom: 0.5 }, action: Array.isArray(actions) ? (React.createElement(MoreFab, { iconButton: true, size: "small", anchorOrigin: {
|
|
38
|
+
vertical: 'bottom',
|
|
39
|
+
horizontal: 'right'
|
|
40
|
+
}, transformOrigin: {
|
|
41
|
+
vertical: 'top',
|
|
42
|
+
horizontal: 'right'
|
|
43
|
+
}, actions: getActions(actions), PaperProps: {
|
|
44
|
+
elevation: 0,
|
|
45
|
+
sx: {
|
|
46
|
+
overflow: 'visible',
|
|
47
|
+
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
|
|
48
|
+
mt: -0.4,
|
|
49
|
+
'& .MuiAvatar-root': {
|
|
50
|
+
width: 32,
|
|
51
|
+
height: 32,
|
|
52
|
+
ml: -0.5,
|
|
53
|
+
mr: 1
|
|
54
|
+
},
|
|
55
|
+
'&:before': {
|
|
56
|
+
content: '""',
|
|
57
|
+
display: 'block',
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
top: 0,
|
|
60
|
+
right: 14,
|
|
61
|
+
width: 10,
|
|
62
|
+
height: 10,
|
|
63
|
+
bgcolor: 'background.paper',
|
|
64
|
+
transform: 'translateY(-50%) rotate(45deg)',
|
|
65
|
+
zIndex: 0
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} })) : (actions), title: title, titleTypographyProps: { variant: 'body2' }, subheader: subheader, subheaderTypographyProps: { variant: 'caption' } }),
|
|
69
|
+
React.createElement(CardContent, { sx: { paddingTop: 0 } }, children)));
|
|
70
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
2
|
+
import { SxProps, Theme } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ListChildComponentProps } from 'react-window';
|
|
5
|
+
import { GridColumn } from '../components/GridColumn';
|
|
6
|
+
import { GridJsonData } from '../components/GridLoader';
|
|
7
|
+
import { DataGridExProps } from './DataGridEx';
|
|
8
|
+
import { GridMethodRef } from './GridMethodRef';
|
|
9
|
+
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
10
|
+
export interface ResponsibleContainerProps<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate> extends Omit<DataGridExProps<T>, 'height' | 'itemKey' | 'loadData' | 'mRef' | 'onScroll' | 'onItemsRendered'> {
|
|
11
|
+
/**
|
|
12
|
+
* Height will be deducted
|
|
13
|
+
* @param height Current calcuated height
|
|
14
|
+
*/
|
|
15
|
+
adjustHeight?: (height: number) => number;
|
|
16
|
+
/**
|
|
17
|
+
* Columns
|
|
18
|
+
*/
|
|
19
|
+
columns: GridColumn<T>[];
|
|
20
|
+
/**
|
|
21
|
+
* Min width to show Datagrid
|
|
22
|
+
*/
|
|
23
|
+
dataGridMinWidth?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Search fields
|
|
26
|
+
*/
|
|
27
|
+
fields?: React.ReactElement[];
|
|
28
|
+
/**
|
|
29
|
+
* Search field template
|
|
30
|
+
*/
|
|
31
|
+
fieldTemplate?: F;
|
|
32
|
+
/**
|
|
33
|
+
* Grid height
|
|
34
|
+
*/
|
|
35
|
+
height?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Inner item renderer
|
|
38
|
+
*/
|
|
39
|
+
innerItemRenderer: (props: ScrollerListExInnerItemRendererProps<T>) => React.ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Item renderer
|
|
42
|
+
*/
|
|
43
|
+
itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
|
|
44
|
+
/**
|
|
45
|
+
* Item size, a function indicates its a variable size list
|
|
46
|
+
*/
|
|
47
|
+
itemSize: ((index: number) => number) | number;
|
|
48
|
+
/**
|
|
49
|
+
* Listbox SX (dataGrid determines the case)
|
|
50
|
+
*/
|
|
51
|
+
listBoxSx?: (dataGrid: boolean) => SxProps<Theme>;
|
|
52
|
+
/**
|
|
53
|
+
* Load data callback
|
|
54
|
+
*/
|
|
55
|
+
loadData: (data: GridJsonData & DataTypes.BasicTemplateType<F>) => PromiseLike<T[] | null | undefined>;
|
|
56
|
+
/**
|
|
57
|
+
* Methods
|
|
58
|
+
*/
|
|
59
|
+
mRef?: React.MutableRefObject<GridMethodRef | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Searchbox SX
|
|
62
|
+
*/
|
|
63
|
+
searchBoxSx?: SxProps<Theme>;
|
|
64
|
+
/**
|
|
65
|
+
* Size ready to read miliseconds span
|
|
66
|
+
*/
|
|
67
|
+
sizeReadyMiliseconds?: number;
|
|
68
|
+
}
|
|
69
|
+
export declare function ResponsibleContainer<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ResponsibleContainerProps<T, F>): JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Box, Stack } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { GridDataGet } from '../components/GridLoader';
|
|
4
|
+
import useCombinedRefs from '../uses/useCombinedRefs';
|
|
5
|
+
import { useDimensions } from '../uses/useDimensions';
|
|
6
|
+
import { DataGridEx, DataGridExCalColumns } from './DataGridEx';
|
|
7
|
+
import { ScrollerListEx } from './ScrollerListEx';
|
|
8
|
+
import { SearchBar } from './SearchBar';
|
|
9
|
+
export function ResponsibleContainer(props) {
|
|
10
|
+
var _a;
|
|
11
|
+
// Destruct
|
|
12
|
+
const { adjustHeight, columns, dataGridMinWidth = DataGridExCalColumns(columns).total, fields, fieldTemplate, height, listBoxSx, loadData, mRef, searchBoxSx, sizeReadyMiliseconds = 0, ...rest } = props;
|
|
13
|
+
// Refs
|
|
14
|
+
const refs = React.useRef({});
|
|
15
|
+
const mRefs = useCombinedRefs(mRef, (ref) => {
|
|
16
|
+
if (ref == null)
|
|
17
|
+
return;
|
|
18
|
+
refs.current.ref = ref;
|
|
19
|
+
});
|
|
20
|
+
// Has fields
|
|
21
|
+
const hasFields = fields != null && fields.length > 0;
|
|
22
|
+
// Watch container
|
|
23
|
+
const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds);
|
|
24
|
+
const rect = dimensions[0][2];
|
|
25
|
+
const showDataGrid = ((_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0) >= dataGridMinWidth;
|
|
26
|
+
console.log(hasFields, showDataGrid, refs);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (rect != null && rect.height > 50 && height == null) {
|
|
29
|
+
let gridHeight = window.innerHeight - Math.round(rect.top + rect.height + 1);
|
|
30
|
+
const style = window.getComputedStyle(dimensions[0][1]);
|
|
31
|
+
const boxPadding = parseFloat(style.paddingLeft);
|
|
32
|
+
if (!isNaN(boxPadding))
|
|
33
|
+
gridHeight -= 2 * boxPadding;
|
|
34
|
+
if (adjustHeight != null) {
|
|
35
|
+
gridHeight -= adjustHeight(gridHeight);
|
|
36
|
+
}
|
|
37
|
+
if (gridHeight !== refs.current.height) {
|
|
38
|
+
refs.current.height = gridHeight;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, [rect]);
|
|
42
|
+
const localLoadData = (props) => {
|
|
43
|
+
const data = GridDataGet(props, fieldTemplate);
|
|
44
|
+
return loadData(data);
|
|
45
|
+
};
|
|
46
|
+
const gridHeight = refs.current.height;
|
|
47
|
+
const list = React.useMemo(() => {
|
|
48
|
+
if (gridHeight == null)
|
|
49
|
+
return;
|
|
50
|
+
if (showDataGrid) {
|
|
51
|
+
// Delete
|
|
52
|
+
delete rest.itemRenderer;
|
|
53
|
+
return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(true) },
|
|
54
|
+
React.createElement(DataGridEx, { autoLoad: !hasFields, height: gridHeight, loadData: localLoadData, mRef: mRefs, columns: columns, ...rest })));
|
|
55
|
+
}
|
|
56
|
+
// Delete
|
|
57
|
+
delete rest.checkable;
|
|
58
|
+
delete rest.borderRowsCount;
|
|
59
|
+
delete rest.bottomHeight;
|
|
60
|
+
delete rest.footerItemRenderer;
|
|
61
|
+
delete rest.headerHeight;
|
|
62
|
+
delete rest.hideFooter;
|
|
63
|
+
delete rest.hoverColor;
|
|
64
|
+
delete rest.selectable;
|
|
65
|
+
return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(false) },
|
|
66
|
+
React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: gridHeight, loadData: localLoadData, mRef: mRefs, ...rest })));
|
|
67
|
+
}, [gridHeight, showDataGrid]);
|
|
68
|
+
// On submit callback
|
|
69
|
+
const onSubmit = (data, _reset) => {
|
|
70
|
+
if (data == null || rect == null || refs.current.ref == null)
|
|
71
|
+
return;
|
|
72
|
+
refs.current.ref.reset({ data });
|
|
73
|
+
};
|
|
74
|
+
// Layout
|
|
75
|
+
return (React.createElement(Stack, null,
|
|
76
|
+
hasFields && (React.createElement(Box, { ref: dimensions[0][0], sx: searchBoxSx },
|
|
77
|
+
React.createElement(SearchBar, { fields: fields, onSubmit: onSubmit }))),
|
|
78
|
+
list));
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.15",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@emotion/react": "^11.7.1",
|
|
51
51
|
"@emotion/style": "^0.8.0",
|
|
52
52
|
"@emotion/styled": "^11.6.0",
|
|
53
|
-
"@etsoo/appscript": "^1.2.
|
|
53
|
+
"@etsoo/appscript": "^1.2.20",
|
|
54
54
|
"@etsoo/notificationbase": "^1.1.0",
|
|
55
55
|
"@etsoo/shared": "^1.1.0",
|
|
56
56
|
"@mui/icons-material": "^5.2.5",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
90
90
|
"eslint-plugin-import": "^2.25.4",
|
|
91
91
|
"eslint-plugin-react": "^7.28.0",
|
|
92
|
-
"jest": "^27.4.
|
|
92
|
+
"jest": "^27.4.7",
|
|
93
93
|
"react-test-renderer": "^17.0.2",
|
|
94
94
|
"ts-jest": "^27.1.2",
|
|
95
95
|
"typescript": "^4.5.4"
|
package/src/app/ReactApp.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export * from './mu/ItemList';
|
|
|
55
55
|
export * from './mu/ListItemRightIcon';
|
|
56
56
|
export * from './mu/LoadingButton';
|
|
57
57
|
export * from './mu/MaskInput';
|
|
58
|
+
export * from './mu/MobileListItemRenderer';
|
|
58
59
|
export * from './mu/MoreFab';
|
|
59
60
|
export * from './mu/MUGlobal';
|
|
60
61
|
export * from './mu/NotifierMU';
|
|
@@ -62,6 +63,7 @@ export * from './mu/OptionGroup';
|
|
|
62
63
|
export * from './mu/PList';
|
|
63
64
|
export * from './mu/ProgressCount';
|
|
64
65
|
export * from './mu/PullToRefreshUI';
|
|
66
|
+
export * from './mu/ResponsibleContainer';
|
|
65
67
|
export * from './mu/RLink';
|
|
66
68
|
export * from './mu/ScrollerListEx';
|
|
67
69
|
export * from './mu/ScrollTopFab';
|
package/src/mu/DnDList.tsx
CHANGED
|
@@ -68,7 +68,8 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
|
|
|
68
68
|
sideRenderer?: (
|
|
69
69
|
top: boolean,
|
|
70
70
|
addItem: (item: D) => boolean,
|
|
71
|
-
addItems: (items: D[]) => number
|
|
71
|
+
addItems: (items: D[]) => number,
|
|
72
|
+
reloadItems: () => void
|
|
72
73
|
) => React.ReactNode;
|
|
73
74
|
|
|
74
75
|
/**
|
|
@@ -210,15 +211,21 @@ export function DnDList<
|
|
|
210
211
|
changeItems(newItems);
|
|
211
212
|
};
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
// Reload items
|
|
215
|
+
const reloadItems = () => {
|
|
214
216
|
// Load data
|
|
215
217
|
loadData(name).then((items) => setItems(items));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
React.useEffect(() => {
|
|
221
|
+
// Load data
|
|
222
|
+
reloadItems();
|
|
216
223
|
}, [name]);
|
|
217
224
|
|
|
218
225
|
// Layout
|
|
219
226
|
return (
|
|
220
227
|
<React.Fragment>
|
|
221
|
-
{sideRenderer && sideRenderer(true, addItem, addItems)}
|
|
228
|
+
{sideRenderer && sideRenderer(true, addItem, addItems, reloadItems)}
|
|
222
229
|
<Component {...componentProps}>
|
|
223
230
|
<DragDropContext onDragEnd={onDragEndLocal}>
|
|
224
231
|
<Droppable droppableId={name}>
|
|
@@ -274,7 +281,8 @@ export function DnDList<
|
|
|
274
281
|
</Droppable>
|
|
275
282
|
</DragDropContext>
|
|
276
283
|
</Component>
|
|
277
|
-
{sideRenderer &&
|
|
284
|
+
{sideRenderer &&
|
|
285
|
+
sideRenderer(false, addItem, addItems, reloadItems)}
|
|
278
286
|
</React.Fragment>
|
|
279
287
|
);
|
|
280
288
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ListItemReact } from '../components/ListItemReact';
|
|
4
|
+
import { MoreFab } from './MoreFab';
|
|
5
|
+
import { MUGlobal } from './MUGlobal';
|
|
6
|
+
import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
|
|
7
|
+
|
|
8
|
+
function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
|
|
9
|
+
// Actions
|
|
10
|
+
const actions: ListItemReact[] = [];
|
|
11
|
+
input.forEach((action) => {
|
|
12
|
+
if (typeof action === 'boolean') return;
|
|
13
|
+
actions.push(action);
|
|
14
|
+
});
|
|
15
|
+
return actions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default mobile list item renderer
|
|
20
|
+
* @param param0 List renderer props
|
|
21
|
+
* @param margin Margin
|
|
22
|
+
* @param renderer Renderer for card content
|
|
23
|
+
* @returns Component
|
|
24
|
+
*/
|
|
25
|
+
export function MobileListItemRenderer<T>(
|
|
26
|
+
{ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>,
|
|
27
|
+
margin: {},
|
|
28
|
+
renderer: (
|
|
29
|
+
data: T
|
|
30
|
+
) => [
|
|
31
|
+
string,
|
|
32
|
+
string | undefined,
|
|
33
|
+
React.ReactNode | (ListItemReact | boolean)[],
|
|
34
|
+
React.ReactNode
|
|
35
|
+
]
|
|
36
|
+
) {
|
|
37
|
+
// Loading
|
|
38
|
+
if (data == null) return <LinearProgress />;
|
|
39
|
+
|
|
40
|
+
// Elements
|
|
41
|
+
const [title, subheader, actions, children] = renderer(data);
|
|
42
|
+
|
|
43
|
+
// Half
|
|
44
|
+
const halfMargin = MUGlobal.half(margin);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Card
|
|
48
|
+
sx={{
|
|
49
|
+
height: (theme) =>
|
|
50
|
+
MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
|
|
51
|
+
marginLeft: margin,
|
|
52
|
+
marginRight: margin,
|
|
53
|
+
marginTop: halfMargin,
|
|
54
|
+
marginBottom: halfMargin
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<CardHeader
|
|
58
|
+
sx={{ paddingBottom: 0.5 }}
|
|
59
|
+
action={
|
|
60
|
+
Array.isArray(actions) ? (
|
|
61
|
+
<MoreFab
|
|
62
|
+
iconButton
|
|
63
|
+
size="small"
|
|
64
|
+
anchorOrigin={{
|
|
65
|
+
vertical: 'bottom',
|
|
66
|
+
horizontal: 'right'
|
|
67
|
+
}}
|
|
68
|
+
transformOrigin={{
|
|
69
|
+
vertical: 'top',
|
|
70
|
+
horizontal: 'right'
|
|
71
|
+
}}
|
|
72
|
+
actions={getActions(actions)}
|
|
73
|
+
PaperProps={{
|
|
74
|
+
elevation: 0,
|
|
75
|
+
sx: {
|
|
76
|
+
overflow: 'visible',
|
|
77
|
+
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
|
|
78
|
+
mt: -0.4,
|
|
79
|
+
'& .MuiAvatar-root': {
|
|
80
|
+
width: 32,
|
|
81
|
+
height: 32,
|
|
82
|
+
ml: -0.5,
|
|
83
|
+
mr: 1
|
|
84
|
+
},
|
|
85
|
+
'&:before': {
|
|
86
|
+
content: '""',
|
|
87
|
+
display: 'block',
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
top: 0,
|
|
90
|
+
right: 14,
|
|
91
|
+
width: 10,
|
|
92
|
+
height: 10,
|
|
93
|
+
bgcolor: 'background.paper',
|
|
94
|
+
transform:
|
|
95
|
+
'translateY(-50%) rotate(45deg)',
|
|
96
|
+
zIndex: 0
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
) : (
|
|
102
|
+
actions
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
title={title}
|
|
106
|
+
titleTypographyProps={{ variant: 'body2' }}
|
|
107
|
+
subheader={subheader}
|
|
108
|
+
subheaderTypographyProps={{ variant: 'caption' }}
|
|
109
|
+
/>
|
|
110
|
+
<CardContent sx={{ paddingTop: 0 }}>{children}</CardContent>
|
|
111
|
+
</Card>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
2
|
+
import { Box, Stack, SxProps, Theme } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ListChildComponentProps } from 'react-window';
|
|
5
|
+
import { GridColumn } from '../components/GridColumn';
|
|
6
|
+
import {
|
|
7
|
+
GridDataGet,
|
|
8
|
+
GridJsonData,
|
|
9
|
+
GridLoadDataProps
|
|
10
|
+
} from '../components/GridLoader';
|
|
11
|
+
import useCombinedRefs from '../uses/useCombinedRefs';
|
|
12
|
+
import { useDimensions } from '../uses/useDimensions';
|
|
13
|
+
import {
|
|
14
|
+
DataGridEx,
|
|
15
|
+
DataGridExCalColumns,
|
|
16
|
+
DataGridExProps
|
|
17
|
+
} from './DataGridEx';
|
|
18
|
+
import { GridMethodRef } from './GridMethodRef';
|
|
19
|
+
import {
|
|
20
|
+
ScrollerListEx,
|
|
21
|
+
ScrollerListExInnerItemRendererProps
|
|
22
|
+
} from './ScrollerListEx';
|
|
23
|
+
import { SearchBar } from './SearchBar';
|
|
24
|
+
|
|
25
|
+
export interface ResponsibleContainerProps<
|
|
26
|
+
T extends {},
|
|
27
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
28
|
+
> extends Omit<
|
|
29
|
+
DataGridExProps<T>,
|
|
30
|
+
| 'height'
|
|
31
|
+
| 'itemKey'
|
|
32
|
+
| 'loadData'
|
|
33
|
+
| 'mRef'
|
|
34
|
+
| 'onScroll'
|
|
35
|
+
| 'onItemsRendered'
|
|
36
|
+
> {
|
|
37
|
+
/**
|
|
38
|
+
* Height will be deducted
|
|
39
|
+
* @param height Current calcuated height
|
|
40
|
+
*/
|
|
41
|
+
adjustHeight?: (height: number) => number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Columns
|
|
45
|
+
*/
|
|
46
|
+
columns: GridColumn<T>[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Min width to show Datagrid
|
|
50
|
+
*/
|
|
51
|
+
dataGridMinWidth?: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Search fields
|
|
55
|
+
*/
|
|
56
|
+
fields?: React.ReactElement[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Search field template
|
|
60
|
+
*/
|
|
61
|
+
fieldTemplate?: F;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Grid height
|
|
65
|
+
*/
|
|
66
|
+
height?: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Inner item renderer
|
|
70
|
+
*/
|
|
71
|
+
innerItemRenderer: (
|
|
72
|
+
props: ScrollerListExInnerItemRendererProps<T>
|
|
73
|
+
) => React.ReactNode;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Item renderer
|
|
77
|
+
*/
|
|
78
|
+
itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Item size, a function indicates its a variable size list
|
|
82
|
+
*/
|
|
83
|
+
itemSize: ((index: number) => number) | number;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Listbox SX (dataGrid determines the case)
|
|
87
|
+
*/
|
|
88
|
+
listBoxSx?: (dataGrid: boolean) => SxProps<Theme>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load data callback
|
|
92
|
+
*/
|
|
93
|
+
loadData: (
|
|
94
|
+
data: GridJsonData & DataTypes.BasicTemplateType<F>
|
|
95
|
+
) => PromiseLike<T[] | null | undefined>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Methods
|
|
99
|
+
*/
|
|
100
|
+
mRef?: React.MutableRefObject<GridMethodRef | undefined>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Searchbox SX
|
|
104
|
+
*/
|
|
105
|
+
searchBoxSx?: SxProps<Theme>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Size ready to read miliseconds span
|
|
109
|
+
*/
|
|
110
|
+
sizeReadyMiliseconds?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface LocalRefs {
|
|
114
|
+
height?: number;
|
|
115
|
+
ref?: GridMethodRef;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function ResponsibleContainer<
|
|
119
|
+
T extends {},
|
|
120
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
121
|
+
>(props: ResponsibleContainerProps<T, F>) {
|
|
122
|
+
// Destruct
|
|
123
|
+
const {
|
|
124
|
+
adjustHeight,
|
|
125
|
+
columns,
|
|
126
|
+
dataGridMinWidth = DataGridExCalColumns(columns).total,
|
|
127
|
+
fields,
|
|
128
|
+
fieldTemplate,
|
|
129
|
+
height,
|
|
130
|
+
listBoxSx,
|
|
131
|
+
loadData,
|
|
132
|
+
mRef,
|
|
133
|
+
searchBoxSx,
|
|
134
|
+
sizeReadyMiliseconds = 0,
|
|
135
|
+
...rest
|
|
136
|
+
} = props;
|
|
137
|
+
|
|
138
|
+
// Refs
|
|
139
|
+
const refs = React.useRef<LocalRefs>({});
|
|
140
|
+
|
|
141
|
+
const mRefs = useCombinedRefs(mRef, (ref: GridMethodRef) => {
|
|
142
|
+
if (ref == null) return;
|
|
143
|
+
refs.current.ref = ref;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Has fields
|
|
147
|
+
const hasFields = fields != null && fields.length > 0;
|
|
148
|
+
|
|
149
|
+
// Watch container
|
|
150
|
+
const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds);
|
|
151
|
+
const rect = dimensions[0][2];
|
|
152
|
+
const showDataGrid = (rect?.width ?? 0) >= dataGridMinWidth;
|
|
153
|
+
|
|
154
|
+
console.log(hasFields, showDataGrid, refs);
|
|
155
|
+
|
|
156
|
+
React.useEffect(() => {
|
|
157
|
+
if (rect != null && rect.height > 50 && height == null) {
|
|
158
|
+
let gridHeight =
|
|
159
|
+
window.innerHeight - Math.round(rect.top + rect.height + 1);
|
|
160
|
+
|
|
161
|
+
const style = window.getComputedStyle(dimensions[0][1]!);
|
|
162
|
+
const boxPadding = parseFloat(style.paddingLeft);
|
|
163
|
+
if (!isNaN(boxPadding)) gridHeight -= 2 * boxPadding;
|
|
164
|
+
|
|
165
|
+
if (adjustHeight != null) {
|
|
166
|
+
gridHeight -= adjustHeight(gridHeight);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (gridHeight !== refs.current.height) {
|
|
170
|
+
refs.current.height = gridHeight;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [rect]);
|
|
174
|
+
|
|
175
|
+
const localLoadData = (props: GridLoadDataProps) => {
|
|
176
|
+
const data = GridDataGet(props, fieldTemplate);
|
|
177
|
+
return loadData(data);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const gridHeight = refs.current.height;
|
|
181
|
+
const list = React.useMemo(() => {
|
|
182
|
+
if (gridHeight == null) return;
|
|
183
|
+
|
|
184
|
+
if (showDataGrid) {
|
|
185
|
+
// Delete
|
|
186
|
+
delete rest.itemRenderer;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Box sx={listBoxSx == null ? undefined : listBoxSx(true)}>
|
|
190
|
+
<DataGridEx<T>
|
|
191
|
+
autoLoad={!hasFields}
|
|
192
|
+
height={gridHeight}
|
|
193
|
+
loadData={localLoadData}
|
|
194
|
+
mRef={mRefs}
|
|
195
|
+
columns={columns}
|
|
196
|
+
{...rest}
|
|
197
|
+
/>
|
|
198
|
+
</Box>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Delete
|
|
203
|
+
delete rest.checkable;
|
|
204
|
+
delete rest.borderRowsCount;
|
|
205
|
+
delete rest.bottomHeight;
|
|
206
|
+
delete rest.footerItemRenderer;
|
|
207
|
+
delete rest.headerHeight;
|
|
208
|
+
delete rest.hideFooter;
|
|
209
|
+
delete rest.hoverColor;
|
|
210
|
+
delete rest.selectable;
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<Box sx={listBoxSx == null ? undefined : listBoxSx(false)}>
|
|
214
|
+
<ScrollerListEx<T>
|
|
215
|
+
autoLoad={!hasFields}
|
|
216
|
+
height={gridHeight}
|
|
217
|
+
loadData={localLoadData}
|
|
218
|
+
mRef={mRefs}
|
|
219
|
+
{...rest}
|
|
220
|
+
/>
|
|
221
|
+
</Box>
|
|
222
|
+
);
|
|
223
|
+
}, [gridHeight, showDataGrid]);
|
|
224
|
+
|
|
225
|
+
// On submit callback
|
|
226
|
+
const onSubmit = (data: FormData, _reset: boolean) => {
|
|
227
|
+
if (data == null || rect == null || refs.current.ref == null) return;
|
|
228
|
+
refs.current.ref.reset({ data });
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Layout
|
|
232
|
+
return (
|
|
233
|
+
<Stack>
|
|
234
|
+
{hasFields && (
|
|
235
|
+
<Box ref={dimensions[0][0]} sx={searchBoxSx}>
|
|
236
|
+
<SearchBar fields={fields} onSubmit={onSubmit} />
|
|
237
|
+
</Box>
|
|
238
|
+
)}
|
|
239
|
+
{list}
|
|
240
|
+
</Stack>
|
|
241
|
+
);
|
|
242
|
+
}
|