@etsoo/react 1.4.12 → 1.4.16
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/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 +1 -1
- package/src/app/ReactApp.ts +2 -1
- package/src/index.ts +2 -0
- package/src/mu/MobileListItemRenderer.tsx +113 -0
- package/src/mu/ResponsibleContainer.tsx +241 -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';
|
|
@@ -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
|
+
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
|
+
console.log(data, rect, refs.current.ref, showDataGrid);
|
|
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
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';
|
|
@@ -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,241 @@
|
|
|
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
|
+
console.log(data, rect, refs.current.ref, showDataGrid);
|
|
226
|
+
if (data == null || rect == null || refs.current.ref == null) return;
|
|
227
|
+
refs.current.ref.reset({ data });
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Layout
|
|
231
|
+
return (
|
|
232
|
+
<Stack>
|
|
233
|
+
{hasFields && (
|
|
234
|
+
<Box ref={dimensions[0][0]} sx={searchBoxSx}>
|
|
235
|
+
<SearchBar fields={fields} onSubmit={onSubmit} />
|
|
236
|
+
</Box>
|
|
237
|
+
)}
|
|
238
|
+
{list}
|
|
239
|
+
</Stack>
|
|
240
|
+
);
|
|
241
|
+
}
|