@etsoo/react 1.4.10 → 1.4.14
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 +5 -1
- package/lib/mu/DnDList.js +15 -6
- 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 +78 -0
- package/package.json +3 -3
- package/src/app/ReactApp.ts +2 -1
- package/src/index.ts +2 -0
- package/src/mu/DnDList.tsx +23 -6
- package/src/mu/MobileListItemRenderer.tsx +113 -0
- package/src/mu/ResponsibleContainer.tsx +240 -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
|
@@ -36,10 +36,14 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
|
|
|
36
36
|
* Data change handler
|
|
37
37
|
*/
|
|
38
38
|
onChange?: (items: D[]) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Drag end handler
|
|
41
|
+
*/
|
|
42
|
+
onDragEnd?: (items: D[]) => void;
|
|
39
43
|
/**
|
|
40
44
|
* Top and bottom sides renderer
|
|
41
45
|
*/
|
|
42
|
-
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;
|
|
43
47
|
/**
|
|
44
48
|
* Name for hidden form input
|
|
45
49
|
*/
|
package/lib/mu/DnDList.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
|
|
9
9
|
*/
|
|
10
10
|
export function DnDList(props) {
|
|
11
11
|
// Destruct
|
|
12
|
-
const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, onChange, sideRenderer } = props;
|
|
12
|
+
const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, onChange, onDragEnd, sideRenderer } = props;
|
|
13
13
|
// State
|
|
14
14
|
const [items, setItems] = React.useState([]);
|
|
15
15
|
const changeItems = (items) => {
|
|
@@ -20,7 +20,7 @@ export function DnDList(props) {
|
|
|
20
20
|
setItems(items);
|
|
21
21
|
};
|
|
22
22
|
// Drag end handler
|
|
23
|
-
const
|
|
23
|
+
const onDragEndLocal = (result) => {
|
|
24
24
|
// Dropped outside the list
|
|
25
25
|
if (!result.destination) {
|
|
26
26
|
return;
|
|
@@ -33,6 +33,9 @@ export function DnDList(props) {
|
|
|
33
33
|
newItems.splice(result.destination.index, 0, removed);
|
|
34
34
|
// Update the state
|
|
35
35
|
changeItems(newItems);
|
|
36
|
+
// Drag end handler
|
|
37
|
+
if (onDragEnd)
|
|
38
|
+
onDragEnd(newItems);
|
|
36
39
|
};
|
|
37
40
|
// Add item
|
|
38
41
|
const addItem = (newItem) => {
|
|
@@ -87,15 +90,20 @@ export function DnDList(props) {
|
|
|
87
90
|
// Update the state
|
|
88
91
|
changeItems(newItems);
|
|
89
92
|
};
|
|
90
|
-
|
|
93
|
+
// Reload items
|
|
94
|
+
const reloadItems = () => {
|
|
91
95
|
// Load data
|
|
92
96
|
loadData(name).then((items) => setItems(items));
|
|
97
|
+
};
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
// Load data
|
|
100
|
+
reloadItems();
|
|
93
101
|
}, [name]);
|
|
94
102
|
// Layout
|
|
95
103
|
return (React.createElement(React.Fragment, null,
|
|
96
|
-
sideRenderer && sideRenderer(true, addItem, addItems),
|
|
104
|
+
sideRenderer && sideRenderer(true, addItem, addItems, reloadItems),
|
|
97
105
|
React.createElement(Component, { ...componentProps },
|
|
98
|
-
React.createElement(DragDropContext, { onDragEnd:
|
|
106
|
+
React.createElement(DragDropContext, { onDragEnd: onDragEndLocal },
|
|
99
107
|
React.createElement(Droppable, { droppableId: name }, (provided, snapshot) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, style: getListStyle(snapshot.isDraggingOver) },
|
|
100
108
|
items.map((item, index) => {
|
|
101
109
|
// Id
|
|
@@ -110,5 +118,6 @@ export function DnDList(props) {
|
|
|
110
118
|
} }, children(item, index, deleteItem, editItem)))));
|
|
111
119
|
}),
|
|
112
120
|
provided.placeholder))))),
|
|
113
|
-
sideRenderer &&
|
|
121
|
+
sideRenderer &&
|
|
122
|
+
sideRenderer(false, addItem, addItems, reloadItems)));
|
|
114
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,78 @@
|
|
|
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
|
+
React.useEffect(() => {
|
|
27
|
+
if (rect != null && rect.height > 50 && height == null) {
|
|
28
|
+
let gridHeight = window.innerHeight - Math.round(rect.top + rect.height + 1);
|
|
29
|
+
const style = window.getComputedStyle(dimensions[0][1]);
|
|
30
|
+
const boxPadding = parseFloat(style.paddingLeft);
|
|
31
|
+
if (!isNaN(boxPadding))
|
|
32
|
+
gridHeight -= 2 * boxPadding;
|
|
33
|
+
if (adjustHeight != null) {
|
|
34
|
+
gridHeight -= adjustHeight(gridHeight);
|
|
35
|
+
}
|
|
36
|
+
if (gridHeight !== refs.current.height) {
|
|
37
|
+
refs.current.height = gridHeight;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}, [rect]);
|
|
41
|
+
const localLoadData = (props) => {
|
|
42
|
+
const data = GridDataGet(props, fieldTemplate);
|
|
43
|
+
return loadData(data);
|
|
44
|
+
};
|
|
45
|
+
const gridHeight = refs.current.height;
|
|
46
|
+
const list = React.useMemo(() => {
|
|
47
|
+
if (gridHeight == null)
|
|
48
|
+
return;
|
|
49
|
+
if (showDataGrid) {
|
|
50
|
+
// Delete
|
|
51
|
+
delete rest.itemRenderer;
|
|
52
|
+
return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(true) },
|
|
53
|
+
React.createElement(DataGridEx, { autoLoad: !hasFields, height: gridHeight, loadData: localLoadData, mRef: mRefs, columns: columns, ...rest })));
|
|
54
|
+
}
|
|
55
|
+
// Delete
|
|
56
|
+
delete rest.checkable;
|
|
57
|
+
delete rest.borderRowsCount;
|
|
58
|
+
delete rest.bottomHeight;
|
|
59
|
+
delete rest.footerItemRenderer;
|
|
60
|
+
delete rest.headerHeight;
|
|
61
|
+
delete rest.hideFooter;
|
|
62
|
+
delete rest.hoverColor;
|
|
63
|
+
delete rest.selectable;
|
|
64
|
+
return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(false) },
|
|
65
|
+
React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: gridHeight, loadData: localLoadData, mRef: mRefs, ...rest })));
|
|
66
|
+
}, [gridHeight, showDataGrid]);
|
|
67
|
+
// On submit callback
|
|
68
|
+
const onSubmit = (data, _reset) => {
|
|
69
|
+
if (data == null || rect == null || refs.current.ref == null)
|
|
70
|
+
return;
|
|
71
|
+
refs.current.ref.reset({ data });
|
|
72
|
+
};
|
|
73
|
+
// Layout
|
|
74
|
+
return (React.createElement(Stack, null,
|
|
75
|
+
hasFields && (React.createElement(Box, { ref: dimensions[0][0], sx: searchBoxSx },
|
|
76
|
+
React.createElement(SearchBar, { fields: fields, onSubmit: onSubmit }))),
|
|
77
|
+
list));
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.14",
|
|
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
|
@@ -57,13 +57,19 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
|
|
|
57
57
|
*/
|
|
58
58
|
onChange?: (items: D[]) => void;
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Drag end handler
|
|
62
|
+
*/
|
|
63
|
+
onDragEnd?: (items: D[]) => void;
|
|
64
|
+
|
|
60
65
|
/**
|
|
61
66
|
* Top and bottom sides renderer
|
|
62
67
|
*/
|
|
63
68
|
sideRenderer?: (
|
|
64
69
|
top: boolean,
|
|
65
70
|
addItem: (item: D) => boolean,
|
|
66
|
-
addItems: (items: D[]) => number
|
|
71
|
+
addItems: (items: D[]) => number,
|
|
72
|
+
reloadItems: () => void
|
|
67
73
|
) => React.ReactNode;
|
|
68
74
|
|
|
69
75
|
/**
|
|
@@ -93,6 +99,7 @@ export function DnDList<
|
|
|
93
99
|
loadData,
|
|
94
100
|
name,
|
|
95
101
|
onChange,
|
|
102
|
+
onDragEnd,
|
|
96
103
|
sideRenderer
|
|
97
104
|
} = props;
|
|
98
105
|
|
|
@@ -108,7 +115,7 @@ export function DnDList<
|
|
|
108
115
|
};
|
|
109
116
|
|
|
110
117
|
// Drag end handler
|
|
111
|
-
const
|
|
118
|
+
const onDragEndLocal = (result: DropResult) => {
|
|
112
119
|
// Dropped outside the list
|
|
113
120
|
if (!result.destination) {
|
|
114
121
|
return;
|
|
@@ -125,6 +132,9 @@ export function DnDList<
|
|
|
125
132
|
|
|
126
133
|
// Update the state
|
|
127
134
|
changeItems(newItems);
|
|
135
|
+
|
|
136
|
+
// Drag end handler
|
|
137
|
+
if (onDragEnd) onDragEnd(newItems);
|
|
128
138
|
};
|
|
129
139
|
|
|
130
140
|
// Add item
|
|
@@ -201,17 +211,23 @@ export function DnDList<
|
|
|
201
211
|
changeItems(newItems);
|
|
202
212
|
};
|
|
203
213
|
|
|
204
|
-
|
|
214
|
+
// Reload items
|
|
215
|
+
const reloadItems = () => {
|
|
205
216
|
// Load data
|
|
206
217
|
loadData(name).then((items) => setItems(items));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
React.useEffect(() => {
|
|
221
|
+
// Load data
|
|
222
|
+
reloadItems();
|
|
207
223
|
}, [name]);
|
|
208
224
|
|
|
209
225
|
// Layout
|
|
210
226
|
return (
|
|
211
227
|
<React.Fragment>
|
|
212
|
-
{sideRenderer && sideRenderer(true, addItem, addItems)}
|
|
228
|
+
{sideRenderer && sideRenderer(true, addItem, addItems, reloadItems)}
|
|
213
229
|
<Component {...componentProps}>
|
|
214
|
-
<DragDropContext onDragEnd={
|
|
230
|
+
<DragDropContext onDragEnd={onDragEndLocal}>
|
|
215
231
|
<Droppable droppableId={name}>
|
|
216
232
|
{(provided, snapshot) => (
|
|
217
233
|
<div
|
|
@@ -265,7 +281,8 @@ export function DnDList<
|
|
|
265
281
|
</Droppable>
|
|
266
282
|
</DragDropContext>
|
|
267
283
|
</Component>
|
|
268
|
-
{sideRenderer &&
|
|
284
|
+
{sideRenderer &&
|
|
285
|
+
sideRenderer(false, addItem, addItems, reloadItems)}
|
|
269
286
|
</React.Fragment>
|
|
270
287
|
);
|
|
271
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,240 @@
|
|
|
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
|
+
React.useEffect(() => {
|
|
155
|
+
if (rect != null && rect.height > 50 && height == null) {
|
|
156
|
+
let gridHeight =
|
|
157
|
+
window.innerHeight - Math.round(rect.top + rect.height + 1);
|
|
158
|
+
|
|
159
|
+
const style = window.getComputedStyle(dimensions[0][1]!);
|
|
160
|
+
const boxPadding = parseFloat(style.paddingLeft);
|
|
161
|
+
if (!isNaN(boxPadding)) gridHeight -= 2 * boxPadding;
|
|
162
|
+
|
|
163
|
+
if (adjustHeight != null) {
|
|
164
|
+
gridHeight -= adjustHeight(gridHeight);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (gridHeight !== refs.current.height) {
|
|
168
|
+
refs.current.height = gridHeight;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}, [rect]);
|
|
172
|
+
|
|
173
|
+
const localLoadData = (props: GridLoadDataProps) => {
|
|
174
|
+
const data = GridDataGet(props, fieldTemplate);
|
|
175
|
+
return loadData(data);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const gridHeight = refs.current.height;
|
|
179
|
+
const list = React.useMemo(() => {
|
|
180
|
+
if (gridHeight == null) return;
|
|
181
|
+
|
|
182
|
+
if (showDataGrid) {
|
|
183
|
+
// Delete
|
|
184
|
+
delete rest.itemRenderer;
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<Box sx={listBoxSx == null ? undefined : listBoxSx(true)}>
|
|
188
|
+
<DataGridEx<T>
|
|
189
|
+
autoLoad={!hasFields}
|
|
190
|
+
height={gridHeight}
|
|
191
|
+
loadData={localLoadData}
|
|
192
|
+
mRef={mRefs}
|
|
193
|
+
columns={columns}
|
|
194
|
+
{...rest}
|
|
195
|
+
/>
|
|
196
|
+
</Box>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Delete
|
|
201
|
+
delete rest.checkable;
|
|
202
|
+
delete rest.borderRowsCount;
|
|
203
|
+
delete rest.bottomHeight;
|
|
204
|
+
delete rest.footerItemRenderer;
|
|
205
|
+
delete rest.headerHeight;
|
|
206
|
+
delete rest.hideFooter;
|
|
207
|
+
delete rest.hoverColor;
|
|
208
|
+
delete rest.selectable;
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Box sx={listBoxSx == null ? undefined : listBoxSx(false)}>
|
|
212
|
+
<ScrollerListEx<T>
|
|
213
|
+
autoLoad={!hasFields}
|
|
214
|
+
height={gridHeight}
|
|
215
|
+
loadData={localLoadData}
|
|
216
|
+
mRef={mRefs}
|
|
217
|
+
{...rest}
|
|
218
|
+
/>
|
|
219
|
+
</Box>
|
|
220
|
+
);
|
|
221
|
+
}, [gridHeight, showDataGrid]);
|
|
222
|
+
|
|
223
|
+
// On submit callback
|
|
224
|
+
const onSubmit = (data: FormData, _reset: boolean) => {
|
|
225
|
+
if (data == null || rect == null || refs.current.ref == null) return;
|
|
226
|
+
refs.current.ref.reset({ data });
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Layout
|
|
230
|
+
return (
|
|
231
|
+
<Stack>
|
|
232
|
+
{hasFields && (
|
|
233
|
+
<Box ref={dimensions[0][0]} sx={searchBoxSx}>
|
|
234
|
+
<SearchBar fields={fields} onSubmit={onSubmit} />
|
|
235
|
+
</Box>
|
|
236
|
+
)}
|
|
237
|
+
{list}
|
|
238
|
+
</Stack>
|
|
239
|
+
);
|
|
240
|
+
}
|