@etsoo/materialui 1.0.1
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/.eslintignore +3 -0
- package/.eslintrc.json +38 -0
- package/.gitattributes +2 -0
- package/.github/workflows/main.yml +48 -0
- package/.prettierignore +5 -0
- package/.prettierrc +6 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/__tests__/ComboBox.tsx +30 -0
- package/__tests__/MUGlobalTests.tsx +58 -0
- package/__tests__/NotifierMUTests.tsx +217 -0
- package/__tests__/SelectEx.tsx +26 -0
- package/__tests__/tsconfig.json +19 -0
- package/babel.config.json +11 -0
- package/lib/AuditDisplay.d.ts +33 -0
- package/lib/AuditDisplay.js +52 -0
- package/lib/AutocompleteExtendedProps.d.ts +64 -0
- package/lib/AutocompleteExtendedProps.js +1 -0
- package/lib/BackButton.d.ts +13 -0
- package/lib/BackButton.js +33 -0
- package/lib/BridgeCloseButton.d.ts +23 -0
- package/lib/BridgeCloseButton.js +32 -0
- package/lib/ButtonLink.d.ts +17 -0
- package/lib/ButtonLink.js +19 -0
- package/lib/ComboBox.d.ts +38 -0
- package/lib/ComboBox.js +108 -0
- package/lib/CountdownButton.d.ts +23 -0
- package/lib/CountdownButton.js +81 -0
- package/lib/CustomFabProps.d.ts +27 -0
- package/lib/CustomFabProps.js +1 -0
- package/lib/DataGridEx.d.ts +94 -0
- package/lib/DataGridEx.js +329 -0
- package/lib/DataGridRenderers.d.ts +22 -0
- package/lib/DataGridRenderers.js +99 -0
- package/lib/DialogButton.d.ts +54 -0
- package/lib/DialogButton.js +45 -0
- package/lib/DnDList.d.ts +87 -0
- package/lib/DnDList.js +153 -0
- package/lib/DraggablePaperComponent.d.ts +8 -0
- package/lib/DraggablePaperComponent.js +12 -0
- package/lib/EmailInput.d.ts +11 -0
- package/lib/EmailInput.js +15 -0
- package/lib/FabBox.d.ts +21 -0
- package/lib/FabBox.js +31 -0
- package/lib/FlexBox.d.ts +14 -0
- package/lib/FlexBox.js +18 -0
- package/lib/GridDataFormat.d.ts +10 -0
- package/lib/GridDataFormat.js +43 -0
- package/lib/IconButtonLink.d.ts +17 -0
- package/lib/IconButtonLink.js +16 -0
- package/lib/InputField.d.ts +21 -0
- package/lib/InputField.js +39 -0
- package/lib/ItemList.d.ts +56 -0
- package/lib/ItemList.js +69 -0
- package/lib/ListItemRightIcon.d.ts +4 -0
- package/lib/ListItemRightIcon.js +8 -0
- package/lib/ListMoreDisplay.d.ts +35 -0
- package/lib/ListMoreDisplay.js +99 -0
- package/lib/LoadingButton.d.ts +16 -0
- package/lib/LoadingButton.js +41 -0
- package/lib/MUGlobal.d.ts +102 -0
- package/lib/MUGlobal.js +184 -0
- package/lib/MaskInput.d.ts +34 -0
- package/lib/MaskInput.js +43 -0
- package/lib/MobileListItemRenderer.d.ts +17 -0
- package/lib/MobileListItemRenderer.js +35 -0
- package/lib/MoreFab.d.ts +45 -0
- package/lib/MoreFab.js +95 -0
- package/lib/NotifierMU.d.ts +47 -0
- package/lib/NotifierMU.js +387 -0
- package/lib/NotifierPromptProps.d.ts +22 -0
- package/lib/NotifierPromptProps.js +1 -0
- package/lib/OptionGroup.d.ts +58 -0
- package/lib/OptionGroup.js +81 -0
- package/lib/PList.d.ts +15 -0
- package/lib/PList.js +12 -0
- package/lib/ProgressCount.d.ts +44 -0
- package/lib/ProgressCount.js +79 -0
- package/lib/PullToRefreshUI.d.ts +9 -0
- package/lib/PullToRefreshUI.js +18 -0
- package/lib/RLink.d.ts +14 -0
- package/lib/RLink.js +37 -0
- package/lib/ResponsibleContainer.d.ts +87 -0
- package/lib/ResponsibleContainer.js +156 -0
- package/lib/ScrollTopFab.d.ts +7 -0
- package/lib/ScrollTopFab.js +25 -0
- package/lib/ScrollerListEx.d.ts +81 -0
- package/lib/ScrollerListEx.js +167 -0
- package/lib/SearchBar.d.ts +29 -0
- package/lib/SearchBar.js +260 -0
- package/lib/SearchField.d.ts +21 -0
- package/lib/SearchField.js +39 -0
- package/lib/SearchOptionGroup.d.ts +9 -0
- package/lib/SearchOptionGroup.js +14 -0
- package/lib/SelectBool.d.ts +13 -0
- package/lib/SelectBool.js +22 -0
- package/lib/SelectEx.d.ts +50 -0
- package/lib/SelectEx.js +156 -0
- package/lib/ShowDataComparison.d.ts +20 -0
- package/lib/ShowDataComparison.js +58 -0
- package/lib/Switch.d.ts +29 -0
- package/lib/Switch.js +34 -0
- package/lib/SwitchAnt.d.ts +25 -0
- package/lib/SwitchAnt.js +40 -0
- package/lib/TabBox.d.ts +54 -0
- package/lib/TabBox.js +31 -0
- package/lib/TableEx.d.ts +65 -0
- package/lib/TableEx.js +270 -0
- package/lib/TextFieldEx.d.ts +101 -0
- package/lib/TextFieldEx.js +126 -0
- package/lib/Tiplist.d.ts +18 -0
- package/lib/Tiplist.js +157 -0
- package/lib/TooltipClick.d.ts +15 -0
- package/lib/TooltipClick.js +40 -0
- package/lib/UserAvatar.d.ts +24 -0
- package/lib/UserAvatar.js +25 -0
- package/lib/UserAvatarEditor.d.ts +53 -0
- package/lib/UserAvatarEditor.js +129 -0
- package/lib/app/CommonApp.d.ts +38 -0
- package/lib/app/CommonApp.js +149 -0
- package/lib/app/IServiceAppSettings.d.ts +11 -0
- package/lib/app/IServiceAppSettings.js +1 -0
- package/lib/app/IServicePage.d.ts +6 -0
- package/lib/app/IServicePage.js +1 -0
- package/lib/app/IServiceUser.d.ts +14 -0
- package/lib/app/IServiceUser.js +1 -0
- package/lib/app/ISmartERPUser.d.ts +14 -0
- package/lib/app/ISmartERPUser.js +1 -0
- package/lib/app/Labels.d.ts +65 -0
- package/lib/app/Labels.js +62 -0
- package/lib/app/ReactApp.d.ts +195 -0
- package/lib/app/ReactApp.js +296 -0
- package/lib/app/ServiceApp.d.ts +78 -0
- package/lib/app/ServiceApp.js +244 -0
- package/lib/index.d.ts +74 -0
- package/lib/index.js +74 -0
- package/lib/pages/CommonPage.d.ts +11 -0
- package/lib/pages/CommonPage.js +60 -0
- package/lib/pages/CommonPageProps.d.ts +59 -0
- package/lib/pages/CommonPageProps.js +1 -0
- package/lib/pages/DataGridPage.d.ts +9 -0
- package/lib/pages/DataGridPage.js +79 -0
- package/lib/pages/DataGridPageProps.d.ts +17 -0
- package/lib/pages/DataGridPageProps.js +1 -0
- package/lib/pages/EditPage.d.ts +33 -0
- package/lib/pages/EditPage.js +29 -0
- package/lib/pages/FixedListPage.d.ts +15 -0
- package/lib/pages/FixedListPage.js +70 -0
- package/lib/pages/ListPage.d.ts +9 -0
- package/lib/pages/ListPage.js +50 -0
- package/lib/pages/ListPageProps.d.ts +7 -0
- package/lib/pages/ListPageProps.js +1 -0
- package/lib/pages/ResponsivePage.d.ts +9 -0
- package/lib/pages/ResponsivePage.js +45 -0
- package/lib/pages/ResponsivePageProps.d.ts +39 -0
- package/lib/pages/ResponsivePageProps.js +1 -0
- package/lib/pages/SearchPageProps.d.ts +30 -0
- package/lib/pages/SearchPageProps.js +1 -0
- package/lib/pages/TablePage.d.ts +9 -0
- package/lib/pages/TablePage.js +69 -0
- package/lib/pages/TablePageProps.d.ts +7 -0
- package/lib/pages/TablePageProps.js +1 -0
- package/lib/pages/ViewPage.d.ts +66 -0
- package/lib/pages/ViewPage.js +105 -0
- package/lib/texts/DateText.d.ts +34 -0
- package/lib/texts/DateText.js +25 -0
- package/lib/texts/MoneyText.d.ts +21 -0
- package/lib/texts/MoneyText.js +14 -0
- package/lib/texts/NumberText.d.ts +25 -0
- package/lib/texts/NumberText.js +14 -0
- package/package.json +97 -0
- package/src/AuditDisplay.tsx +114 -0
- package/src/AutocompleteExtendedProps.ts +83 -0
- package/src/BackButton.tsx +55 -0
- package/src/BridgeCloseButton.tsx +69 -0
- package/src/ButtonLink.tsx +32 -0
- package/src/ComboBox.tsx +251 -0
- package/src/CountdownButton.tsx +119 -0
- package/src/CustomFabProps.ts +32 -0
- package/src/DataGridEx.tsx +713 -0
- package/src/DataGridRenderers.tsx +140 -0
- package/src/DialogButton.tsx +163 -0
- package/src/DnDList.tsx +344 -0
- package/src/DraggablePaperComponent.tsx +19 -0
- package/src/EmailInput.tsx +24 -0
- package/src/FabBox.tsx +51 -0
- package/src/FlexBox.tsx +20 -0
- package/src/GridDataFormat.tsx +77 -0
- package/src/IconButtonLink.tsx +29 -0
- package/src/InputField.tsx +82 -0
- package/src/ItemList.tsx +204 -0
- package/src/ListItemRightIcon.tsx +9 -0
- package/src/ListMoreDisplay.tsx +205 -0
- package/src/LoadingButton.tsx +75 -0
- package/src/MUGlobal.ts +220 -0
- package/src/MaskInput.tsx +107 -0
- package/src/MobileListItemRenderer.tsx +79 -0
- package/src/MoreFab.tsx +211 -0
- package/src/NotifierMU.tsx +654 -0
- package/src/NotifierPromptProps.ts +24 -0
- package/src/OptionGroup.tsx +223 -0
- package/src/PList.tsx +27 -0
- package/src/ProgressCount.tsx +166 -0
- package/src/PullToRefreshUI.tsx +21 -0
- package/src/RLink.tsx +64 -0
- package/src/ResponsibleContainer.tsx +394 -0
- package/src/ScrollTopFab.tsx +34 -0
- package/src/ScrollerListEx.tsx +387 -0
- package/src/SearchBar.tsx +396 -0
- package/src/SearchField.tsx +82 -0
- package/src/SearchOptionGroup.tsx +31 -0
- package/src/SelectBool.tsx +33 -0
- package/src/SelectEx.tsx +290 -0
- package/src/ShowDataComparison.tsx +106 -0
- package/src/Switch.tsx +94 -0
- package/src/SwitchAnt.tsx +95 -0
- package/src/TabBox.tsx +118 -0
- package/src/TableEx.tsx +558 -0
- package/src/TextFieldEx.tsx +249 -0
- package/src/Tiplist.tsx +303 -0
- package/src/TooltipClick.tsx +84 -0
- package/src/UserAvatar.tsx +64 -0
- package/src/UserAvatarEditor.tsx +287 -0
- package/src/app/CommonApp.ts +223 -0
- package/src/app/IServiceAppSettings.ts +13 -0
- package/src/app/IServicePage.ts +6 -0
- package/src/app/IServiceUser.ts +17 -0
- package/src/app/ISmartERPUser.ts +16 -0
- package/src/app/Labels.ts +77 -0
- package/src/app/ReactApp.ts +504 -0
- package/src/app/ServiceApp.ts +352 -0
- package/src/index.ts +77 -0
- package/src/pages/CommonPage.tsx +128 -0
- package/src/pages/CommonPageProps.ts +70 -0
- package/src/pages/DataGridPage.tsx +140 -0
- package/src/pages/DataGridPageProps.ts +24 -0
- package/src/pages/EditPage.tsx +114 -0
- package/src/pages/FixedListPage.tsx +141 -0
- package/src/pages/ListPage.tsx +90 -0
- package/src/pages/ListPageProps.ts +12 -0
- package/src/pages/ResponsivePage.tsx +68 -0
- package/src/pages/ResponsivePageProps.ts +57 -0
- package/src/pages/SearchPageProps.ts +39 -0
- package/src/pages/TablePage.tsx +126 -0
- package/src/pages/TablePageProps.ts +12 -0
- package/src/pages/ViewPage.tsx +282 -0
- package/src/texts/DateText.tsx +74 -0
- package/src/texts/MoneyText.tsx +49 -0
- package/src/texts/NumberText.tsx +40 -0
- package/tsconfig.json +19 -0
package/src/FabBox.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Box, BoxProps, useTheme } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fabs container box props
|
|
6
|
+
*/
|
|
7
|
+
export type FabBoxProps = BoxProps & {
|
|
8
|
+
/**
|
|
9
|
+
* Item gap
|
|
10
|
+
*/
|
|
11
|
+
itemGap?: number;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Flex direction, row or column
|
|
15
|
+
*/
|
|
16
|
+
columnDirection?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fabs container box
|
|
21
|
+
* @param props Props
|
|
22
|
+
* @returns Component
|
|
23
|
+
*/
|
|
24
|
+
export function FabBox(props: FabBoxProps) {
|
|
25
|
+
// Destruct
|
|
26
|
+
const { columnDirection, itemGap = 1, sx = {}, ...rest } = props;
|
|
27
|
+
|
|
28
|
+
// Theme
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
const spaceGap = theme.spacing(itemGap);
|
|
31
|
+
|
|
32
|
+
if (columnDirection == null) return <React.Fragment />;
|
|
33
|
+
|
|
34
|
+
// margin
|
|
35
|
+
const margin = columnDirection
|
|
36
|
+
? { marginTop: spaceGap }
|
|
37
|
+
: { marginLeft: spaceGap };
|
|
38
|
+
|
|
39
|
+
// Default style
|
|
40
|
+
if (typeof sx === 'object') {
|
|
41
|
+
Object.assign(sx as any, {
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
flexDirection: columnDirection ? 'column' : 'row',
|
|
46
|
+
'& > :not(style) + :not(style)': margin
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return <Box sx={sx} {...rest} />;
|
|
51
|
+
}
|
package/src/FlexBox.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Stack, StackProps } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Horizonal box
|
|
6
|
+
* @param props Props
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export function HBox(props: Omit<StackProps, 'ref'>) {
|
|
10
|
+
return <Stack direction="row" width="100%" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Vertial box
|
|
15
|
+
* @param props Props
|
|
16
|
+
* @returns Component
|
|
17
|
+
*/
|
|
18
|
+
export function VBox(props: Omit<StackProps, 'ref'>) {
|
|
19
|
+
return <Stack direction="column" {...props} />;
|
|
20
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { GridColumnRenderProps, GridDataType } from '@etsoo/react';
|
|
2
|
+
import { DateUtils, NumberUtils } from '@etsoo/shared';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { DateText } from './texts/DateText';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Grid data format
|
|
8
|
+
* @param data Input data
|
|
9
|
+
* @param type Data type
|
|
10
|
+
* @param renderProps Render props
|
|
11
|
+
* @returns Result
|
|
12
|
+
*/
|
|
13
|
+
export function GridDataFormat(
|
|
14
|
+
data: unknown,
|
|
15
|
+
type: GridDataType,
|
|
16
|
+
renderProps?: GridColumnRenderProps
|
|
17
|
+
): React.ReactNode {
|
|
18
|
+
// Null
|
|
19
|
+
if (data == null) return undefined;
|
|
20
|
+
|
|
21
|
+
// For date time
|
|
22
|
+
// Conversion if necessary
|
|
23
|
+
if (type === GridDataType.Date || type === GridDataType.DateTime) {
|
|
24
|
+
const dateValue =
|
|
25
|
+
data instanceof Date
|
|
26
|
+
? data
|
|
27
|
+
: typeof data === 'number' || typeof data === 'string'
|
|
28
|
+
? new Date(data)
|
|
29
|
+
: undefined;
|
|
30
|
+
|
|
31
|
+
if (dateValue == null) return undefined;
|
|
32
|
+
|
|
33
|
+
const option = type === GridDataType.DateTime ? 'ds' : 'd';
|
|
34
|
+
|
|
35
|
+
const nearDays = renderProps?.nearDays;
|
|
36
|
+
if (nearDays != null) {
|
|
37
|
+
return (
|
|
38
|
+
<DateText
|
|
39
|
+
value={dateValue}
|
|
40
|
+
locale={renderProps?.culture}
|
|
41
|
+
timeZone={renderProps?.timeZone}
|
|
42
|
+
options={option}
|
|
43
|
+
nearDays={nearDays}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return DateUtils.format(
|
|
49
|
+
dateValue,
|
|
50
|
+
renderProps?.culture,
|
|
51
|
+
option,
|
|
52
|
+
renderProps?.timeZone
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// For numbers
|
|
57
|
+
if (typeof data === 'number') {
|
|
58
|
+
if (type === GridDataType.Money || type === GridDataType.IntMoney)
|
|
59
|
+
return NumberUtils.formatMoney(
|
|
60
|
+
data,
|
|
61
|
+
renderProps?.currency,
|
|
62
|
+
renderProps?.culture,
|
|
63
|
+
type === GridDataType.IntMoney,
|
|
64
|
+
renderProps?.numberFormatOptions
|
|
65
|
+
);
|
|
66
|
+
else
|
|
67
|
+
return NumberUtils.format(
|
|
68
|
+
data,
|
|
69
|
+
renderProps?.culture,
|
|
70
|
+
renderProps?.numberFormatOptions
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof data === 'string') return data;
|
|
75
|
+
|
|
76
|
+
return `${data}`;
|
|
77
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IconButton, IconButtonProps } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useNavigate } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IconButtonLink props
|
|
7
|
+
*/
|
|
8
|
+
export type IconButtonLinkProps = Omit<IconButtonProps, 'href' | 'onClick'> & {
|
|
9
|
+
/**
|
|
10
|
+
* To href
|
|
11
|
+
*/
|
|
12
|
+
href: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* IconButtonLink
|
|
17
|
+
* @param props Props
|
|
18
|
+
* @returns Component
|
|
19
|
+
*/
|
|
20
|
+
export function IconButtonLink(props: IconButtonLinkProps) {
|
|
21
|
+
// Destruct
|
|
22
|
+
const { href, ...rest } = props;
|
|
23
|
+
|
|
24
|
+
// Navigate
|
|
25
|
+
const navigate = useNavigate();
|
|
26
|
+
|
|
27
|
+
// Layout
|
|
28
|
+
return <IconButton {...rest} onClick={() => navigate(href)} />;
|
|
29
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useDelayedExecutor } from '@etsoo/react';
|
|
2
|
+
import { TextField, TextFieldProps } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { MUGlobal } from './MUGlobal';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Input field props
|
|
8
|
+
*/
|
|
9
|
+
export type InputFieldProps = TextFieldProps & {
|
|
10
|
+
/**
|
|
11
|
+
* Change delay (ms) to avoid repeatly dispatch onChange
|
|
12
|
+
*/
|
|
13
|
+
changeDelay?: number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Is the field read only?
|
|
17
|
+
*/
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Input field
|
|
23
|
+
* @param props Props
|
|
24
|
+
* @returns Component
|
|
25
|
+
*/
|
|
26
|
+
export function InputField(props: InputFieldProps) {
|
|
27
|
+
// Destruct
|
|
28
|
+
const {
|
|
29
|
+
changeDelay,
|
|
30
|
+
InputLabelProps = {},
|
|
31
|
+
InputProps = {},
|
|
32
|
+
onChange,
|
|
33
|
+
readOnly,
|
|
34
|
+
size = MUGlobal.inputFieldSize,
|
|
35
|
+
variant = MUGlobal.inputFieldVariant,
|
|
36
|
+
...rest
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
// Shrink
|
|
40
|
+
InputLabelProps.shrink = MUGlobal.searchFieldShrink;
|
|
41
|
+
|
|
42
|
+
// Read only
|
|
43
|
+
if (readOnly != null) InputProps.readOnly = readOnly;
|
|
44
|
+
|
|
45
|
+
const isMounted = React.useRef(true);
|
|
46
|
+
const delayed =
|
|
47
|
+
onChange != null && changeDelay != null && changeDelay >= 1
|
|
48
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
49
|
+
: undefined;
|
|
50
|
+
|
|
51
|
+
const onChangeEx = (
|
|
52
|
+
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
53
|
+
) => {
|
|
54
|
+
if (onChange == null) return;
|
|
55
|
+
|
|
56
|
+
if (changeDelay == null || changeDelay < 1) {
|
|
57
|
+
onChange(event);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
delayed?.call(undefined, event);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
return () => {
|
|
66
|
+
isMounted.current = false;
|
|
67
|
+
delayed?.clear();
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
// Layout
|
|
72
|
+
return (
|
|
73
|
+
<TextField
|
|
74
|
+
InputLabelProps={InputLabelProps}
|
|
75
|
+
InputProps={InputProps}
|
|
76
|
+
onChange={onChangeEx}
|
|
77
|
+
size={size}
|
|
78
|
+
variant={variant}
|
|
79
|
+
{...rest}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
package/src/ItemList.tsx
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogTitle,
|
|
5
|
+
List,
|
|
6
|
+
ListItemText,
|
|
7
|
+
DialogContent,
|
|
8
|
+
Button,
|
|
9
|
+
ListItemButton
|
|
10
|
+
} from '@mui/material';
|
|
11
|
+
import {
|
|
12
|
+
DataTypes,
|
|
13
|
+
IdDefaultType,
|
|
14
|
+
LabelDefaultType,
|
|
15
|
+
ListType
|
|
16
|
+
} from '@etsoo/shared';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Item list properties
|
|
20
|
+
*/
|
|
21
|
+
export interface ItemListProps<
|
|
22
|
+
T extends object,
|
|
23
|
+
D extends DataTypes.Keys<T>,
|
|
24
|
+
L extends DataTypes.Keys<T, string>
|
|
25
|
+
> {
|
|
26
|
+
/**
|
|
27
|
+
* Style class name
|
|
28
|
+
*/
|
|
29
|
+
className?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Id field name
|
|
33
|
+
*/
|
|
34
|
+
idField?: D;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Label field name or callback
|
|
38
|
+
*/
|
|
39
|
+
labelField?: L | ((item: T) => string);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Button icon
|
|
43
|
+
*/
|
|
44
|
+
icon?: React.ReactNode;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Button color
|
|
48
|
+
*/
|
|
49
|
+
color?: 'inherit' | 'primary' | 'secondary';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Close event
|
|
53
|
+
*/
|
|
54
|
+
onClose?(item: T, changed: boolean): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Current selected language
|
|
58
|
+
*/
|
|
59
|
+
selectedValue?: T[D];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Button size
|
|
63
|
+
*/
|
|
64
|
+
size?: 'small' | 'medium' | 'large';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Title
|
|
68
|
+
*/
|
|
69
|
+
title?: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Items
|
|
73
|
+
*/
|
|
74
|
+
items: T[];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Button variant
|
|
78
|
+
*/
|
|
79
|
+
variant?: 'text' | 'outlined' | 'contained';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Item list component
|
|
84
|
+
* @param props Properties
|
|
85
|
+
*/
|
|
86
|
+
export function ItemList<
|
|
87
|
+
T extends object = ListType,
|
|
88
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
89
|
+
L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
|
|
90
|
+
>(props: ItemListProps<T, D, L>) {
|
|
91
|
+
// properties destructure
|
|
92
|
+
const {
|
|
93
|
+
className,
|
|
94
|
+
color = 'primary',
|
|
95
|
+
items,
|
|
96
|
+
idField = 'id' as D,
|
|
97
|
+
labelField = 'label' as L,
|
|
98
|
+
icon,
|
|
99
|
+
onClose,
|
|
100
|
+
selectedValue,
|
|
101
|
+
size = 'medium',
|
|
102
|
+
title,
|
|
103
|
+
variant = 'outlined'
|
|
104
|
+
} = props;
|
|
105
|
+
|
|
106
|
+
// Get label
|
|
107
|
+
const getLabel = (item: T): string => {
|
|
108
|
+
if (typeof labelField === 'function') {
|
|
109
|
+
return labelField(item);
|
|
110
|
+
} else {
|
|
111
|
+
return DataTypes.convert(item[labelField], 'string') ?? '';
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Dialog open or not state
|
|
116
|
+
const [open, setOpen] = React.useState(false);
|
|
117
|
+
|
|
118
|
+
// Default state
|
|
119
|
+
const defaultItem =
|
|
120
|
+
items.find((item) => item[idField] === selectedValue) ?? items[0];
|
|
121
|
+
|
|
122
|
+
// Current item
|
|
123
|
+
const [currentItem, setCurrentItem] = React.useState(defaultItem);
|
|
124
|
+
|
|
125
|
+
// Click handler
|
|
126
|
+
const clickHandler = () => {
|
|
127
|
+
// More than one language
|
|
128
|
+
if (items.length < 2) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Open the dialog
|
|
133
|
+
setOpen(true);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Close handler
|
|
137
|
+
const closeHandler = () => {
|
|
138
|
+
if (!open) return;
|
|
139
|
+
|
|
140
|
+
// Close the dialog
|
|
141
|
+
setOpen(false);
|
|
142
|
+
|
|
143
|
+
// Emit close event
|
|
144
|
+
if (onClose) {
|
|
145
|
+
onClose(currentItem, false);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Close item handler
|
|
150
|
+
const closeItemHandler = (item: any) => {
|
|
151
|
+
// Update the current item
|
|
152
|
+
setCurrentItem(item);
|
|
153
|
+
|
|
154
|
+
// Close the dialog
|
|
155
|
+
setOpen(false);
|
|
156
|
+
|
|
157
|
+
// Emit close event
|
|
158
|
+
if (onClose) {
|
|
159
|
+
onClose(item, true);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<>
|
|
165
|
+
<Button
|
|
166
|
+
className={className}
|
|
167
|
+
variant={variant}
|
|
168
|
+
startIcon={icon}
|
|
169
|
+
color={color}
|
|
170
|
+
size={size}
|
|
171
|
+
onClick={clickHandler}
|
|
172
|
+
>
|
|
173
|
+
{getLabel(currentItem)}
|
|
174
|
+
</Button>
|
|
175
|
+
<Dialog
|
|
176
|
+
aria-labelledby="dialog-title"
|
|
177
|
+
open={open}
|
|
178
|
+
onClose={closeHandler}
|
|
179
|
+
>
|
|
180
|
+
<DialogTitle sx={{ minWidth: '200px' }} id="dialog-title">
|
|
181
|
+
{title || ''}
|
|
182
|
+
</DialogTitle>
|
|
183
|
+
<DialogContent>
|
|
184
|
+
<List>
|
|
185
|
+
{items.map((item) => {
|
|
186
|
+
const id = item[idField];
|
|
187
|
+
return (
|
|
188
|
+
<ListItemButton
|
|
189
|
+
key={id as unknown as React.Key}
|
|
190
|
+
disabled={id === currentItem[idField]}
|
|
191
|
+
onClick={() => closeItemHandler(item)}
|
|
192
|
+
>
|
|
193
|
+
<ListItemText>
|
|
194
|
+
{getLabel(item)}
|
|
195
|
+
</ListItemText>
|
|
196
|
+
</ListItemButton>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</List>
|
|
200
|
+
</DialogContent>
|
|
201
|
+
</Dialog>
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardActions,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardProps,
|
|
8
|
+
CircularProgress
|
|
9
|
+
} from '@mui/material';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import {
|
|
12
|
+
GridData,
|
|
13
|
+
GridDataGet,
|
|
14
|
+
GridLoadDataProps,
|
|
15
|
+
GridLoader,
|
|
16
|
+
GridLoaderStates
|
|
17
|
+
} from '@etsoo/react';
|
|
18
|
+
import { LoadingButton } from './LoadingButton';
|
|
19
|
+
import { globalApp } from './app/ReactApp';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ListMoreDisplay props
|
|
23
|
+
*/
|
|
24
|
+
export interface ListMoreDisplayProps<
|
|
25
|
+
T extends object,
|
|
26
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
27
|
+
> extends Omit<CardProps, 'children'>,
|
|
28
|
+
GridLoader<T> {
|
|
29
|
+
/**
|
|
30
|
+
* Children to display the list
|
|
31
|
+
*/
|
|
32
|
+
children: (data: T, index: number) => React.ReactNode;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Search field template
|
|
36
|
+
*/
|
|
37
|
+
fieldTemplate?: F;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Header renderer
|
|
41
|
+
*/
|
|
42
|
+
headerRenderer?: (reset: (data?: GridData) => void) => React.ReactNode;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Header title
|
|
46
|
+
*/
|
|
47
|
+
headerTitle?: React.ReactNode;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* More button label
|
|
51
|
+
*/
|
|
52
|
+
moreLabel?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type states<T> = {
|
|
56
|
+
items?: T[];
|
|
57
|
+
completed: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ListMoreDisplay
|
|
62
|
+
* @param props Props
|
|
63
|
+
* @returns Component
|
|
64
|
+
*/
|
|
65
|
+
export function ListMoreDisplay<
|
|
66
|
+
T extends object,
|
|
67
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
68
|
+
>(props: ListMoreDisplayProps<T, F>) {
|
|
69
|
+
// Destruct
|
|
70
|
+
const {
|
|
71
|
+
children,
|
|
72
|
+
defaultOrderBy,
|
|
73
|
+
headerRenderer,
|
|
74
|
+
autoLoad = headerRenderer == null,
|
|
75
|
+
headerTitle,
|
|
76
|
+
loadBatchSize,
|
|
77
|
+
loadData,
|
|
78
|
+
moreLabel = typeof globalApp === 'undefined'
|
|
79
|
+
? undefined
|
|
80
|
+
: globalApp.get('more') + '...',
|
|
81
|
+
fieldTemplate,
|
|
82
|
+
threshold,
|
|
83
|
+
...rest
|
|
84
|
+
} = props;
|
|
85
|
+
|
|
86
|
+
// Refs
|
|
87
|
+
const refs = React.useRef<GridLoaderStates<T>>({
|
|
88
|
+
autoLoad,
|
|
89
|
+
currentPage: 0,
|
|
90
|
+
hasNextPage: true,
|
|
91
|
+
isNextPageLoading: false,
|
|
92
|
+
orderBy: defaultOrderBy,
|
|
93
|
+
batchSize: 10,
|
|
94
|
+
loadedItems: 0,
|
|
95
|
+
selectedItems: []
|
|
96
|
+
});
|
|
97
|
+
const ref = refs.current;
|
|
98
|
+
|
|
99
|
+
// States
|
|
100
|
+
const [states, setStates] = React.useReducer(
|
|
101
|
+
(currentStates: states<T>, newStates: Partial<states<T>>) => {
|
|
102
|
+
return { ...currentStates, ...newStates };
|
|
103
|
+
},
|
|
104
|
+
{ completed: false }
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Load data
|
|
108
|
+
const loadDataLocal = async (reset: boolean = false) => {
|
|
109
|
+
// Prevent multiple loadings
|
|
110
|
+
if (!ref.hasNextPage || ref.isNextPageLoading) return;
|
|
111
|
+
|
|
112
|
+
// Update state
|
|
113
|
+
ref.isNextPageLoading = true;
|
|
114
|
+
|
|
115
|
+
// Parameters
|
|
116
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } = ref;
|
|
117
|
+
|
|
118
|
+
const loadProps: GridLoadDataProps = {
|
|
119
|
+
currentPage,
|
|
120
|
+
batchSize,
|
|
121
|
+
orderBy,
|
|
122
|
+
orderByAsc,
|
|
123
|
+
data
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const mergedData = GridDataGet(loadProps, fieldTemplate);
|
|
127
|
+
|
|
128
|
+
const items = await loadData(mergedData);
|
|
129
|
+
if (items == null || ref.isMounted === false) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
ref.isMounted = true;
|
|
133
|
+
|
|
134
|
+
const newItems = items.length;
|
|
135
|
+
const hasNextPage = newItems >= batchSize;
|
|
136
|
+
ref.lastLoadedItems = newItems;
|
|
137
|
+
ref.isNextPageLoading = false;
|
|
138
|
+
ref.hasNextPage = hasNextPage;
|
|
139
|
+
|
|
140
|
+
// Next page
|
|
141
|
+
ref.currentPage = currentPage + 1;
|
|
142
|
+
|
|
143
|
+
// Update rows
|
|
144
|
+
if (states.items == null || reset)
|
|
145
|
+
setStates({ items, completed: !hasNextPage });
|
|
146
|
+
else
|
|
147
|
+
setStates({
|
|
148
|
+
items: [...states.items, ...items],
|
|
149
|
+
completed: !hasNextPage
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const reset = (data?: GridData) => {
|
|
154
|
+
// Update the form data
|
|
155
|
+
ref.data = data;
|
|
156
|
+
|
|
157
|
+
// Reset page number
|
|
158
|
+
ref.isNextPageLoading = false;
|
|
159
|
+
ref.currentPage = 0;
|
|
160
|
+
ref.hasNextPage = true;
|
|
161
|
+
|
|
162
|
+
// Load data
|
|
163
|
+
loadDataLocal(true);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (autoLoad) loadDataLocal();
|
|
168
|
+
}, [autoLoad]);
|
|
169
|
+
|
|
170
|
+
React.useEffect(() => {
|
|
171
|
+
return () => {
|
|
172
|
+
ref.isMounted = false;
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<React.Fragment>
|
|
178
|
+
{headerRenderer && headerRenderer(reset)}
|
|
179
|
+
<Card {...rest}>
|
|
180
|
+
<CardHeader title={headerTitle}></CardHeader>
|
|
181
|
+
<CardContent
|
|
182
|
+
sx={{
|
|
183
|
+
paddingTop: 0,
|
|
184
|
+
paddingBottom: states.completed ? 0 : 'inherit'
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{states.items == null ? (
|
|
188
|
+
<CircularProgress size={20} />
|
|
189
|
+
) : (
|
|
190
|
+
states.items.map((item, index) => children(item, index))
|
|
191
|
+
)}
|
|
192
|
+
</CardContent>
|
|
193
|
+
{!states.completed && (
|
|
194
|
+
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
|
195
|
+
<LoadingButton
|
|
196
|
+
onClick={async () => await loadDataLocal()}
|
|
197
|
+
>
|
|
198
|
+
{moreLabel}
|
|
199
|
+
</LoadingButton>
|
|
200
|
+
</CardActions>
|
|
201
|
+
)}
|
|
202
|
+
</Card>
|
|
203
|
+
</React.Fragment>
|
|
204
|
+
);
|
|
205
|
+
}
|