@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CircularProgress } from '@mui/material';
|
|
3
|
+
import { DateUtils, NumberUtils } from '@etsoo/shared';
|
|
4
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
5
|
+
import ClearIcon from '@mui/icons-material/Clear';
|
|
6
|
+
import { DataGridExFooterItemRendererProps } from './DataGridEx';
|
|
7
|
+
import { DateText } from './texts/DateText';
|
|
8
|
+
import { GridCellRendererProps, GridDataType } from '@etsoo/react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Data grid renderers
|
|
12
|
+
*/
|
|
13
|
+
export namespace DataGridRenderers {
|
|
14
|
+
/**
|
|
15
|
+
* Default cell renderer
|
|
16
|
+
* @param param Props
|
|
17
|
+
* @returns Component
|
|
18
|
+
*/
|
|
19
|
+
export function defaultCellRenderer<T extends Record<string, any>>({
|
|
20
|
+
cellProps,
|
|
21
|
+
data,
|
|
22
|
+
field,
|
|
23
|
+
formattedValue,
|
|
24
|
+
columnIndex,
|
|
25
|
+
type,
|
|
26
|
+
renderProps
|
|
27
|
+
}: GridCellRendererProps<T>): React.ReactNode {
|
|
28
|
+
// Is loading
|
|
29
|
+
if (data == null) {
|
|
30
|
+
// First column, show loading indicator
|
|
31
|
+
if (columnIndex === 0) return <CircularProgress size={15} />;
|
|
32
|
+
|
|
33
|
+
// Others return undefined
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// No formatted value and data field
|
|
38
|
+
if (formattedValue == null && field == null) return undefined;
|
|
39
|
+
|
|
40
|
+
// Cell value
|
|
41
|
+
const value = formattedValue ?? data[field!];
|
|
42
|
+
if (value == null) return undefined;
|
|
43
|
+
|
|
44
|
+
// For date time
|
|
45
|
+
// Conversion if necessary
|
|
46
|
+
if (type === GridDataType.Date || type === GridDataType.DateTime) {
|
|
47
|
+
const dateValue = value instanceof Date ? value : new Date(value);
|
|
48
|
+
|
|
49
|
+
const option = type === GridDataType.DateTime ? 'ds' : 'd';
|
|
50
|
+
|
|
51
|
+
const nearDays = renderProps?.nearDays;
|
|
52
|
+
if (nearDays != null) {
|
|
53
|
+
return (
|
|
54
|
+
<DateText
|
|
55
|
+
value={dateValue}
|
|
56
|
+
locale={renderProps?.culture}
|
|
57
|
+
timeZone={renderProps?.timeZone}
|
|
58
|
+
options={option}
|
|
59
|
+
nearDays={nearDays}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return DateUtils.format(
|
|
65
|
+
dateValue,
|
|
66
|
+
renderProps?.culture,
|
|
67
|
+
option,
|
|
68
|
+
renderProps?.timeZone
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// For numbers
|
|
73
|
+
if (typeof value === 'number') {
|
|
74
|
+
if (type === GridDataType.Money || type === GridDataType.IntMoney)
|
|
75
|
+
return NumberUtils.formatMoney(
|
|
76
|
+
value,
|
|
77
|
+
renderProps?.currency,
|
|
78
|
+
renderProps?.culture,
|
|
79
|
+
type === GridDataType.IntMoney,
|
|
80
|
+
renderProps?.numberFormatOptions
|
|
81
|
+
);
|
|
82
|
+
else
|
|
83
|
+
return NumberUtils.format(
|
|
84
|
+
value,
|
|
85
|
+
renderProps?.culture,
|
|
86
|
+
renderProps?.numberFormatOptions
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// For boolean
|
|
91
|
+
if (typeof value === 'boolean') {
|
|
92
|
+
// Add style
|
|
93
|
+
if ('align' in cellProps) {
|
|
94
|
+
cellProps.sx = {
|
|
95
|
+
paddingTop: '12px!important',
|
|
96
|
+
paddingBottom: '6px!important'
|
|
97
|
+
};
|
|
98
|
+
} else {
|
|
99
|
+
cellProps.sx = {
|
|
100
|
+
paddingTop: '16px!important',
|
|
101
|
+
paddingBottom: '8px!important'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (value) return <CheckIcon fontSize="small" />;
|
|
106
|
+
else return <ClearIcon fontSize="small" color="warning" />;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// To string
|
|
110
|
+
return new String(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Default footer item renderer
|
|
115
|
+
* @param rows Rows
|
|
116
|
+
* @param props Renderer props
|
|
117
|
+
* @param location Renderer location (column index)
|
|
118
|
+
* @returns Component
|
|
119
|
+
*/
|
|
120
|
+
export function defaultFooterItemRenderer<T extends object>(
|
|
121
|
+
_rows: T[],
|
|
122
|
+
{ index, states, checkable }: DataGridExFooterItemRendererProps<T>,
|
|
123
|
+
location: number = 0
|
|
124
|
+
) {
|
|
125
|
+
const { selectedItems, loadedItems, hasNextPage } = states;
|
|
126
|
+
|
|
127
|
+
if (index === location) {
|
|
128
|
+
if (checkable) {
|
|
129
|
+
return [
|
|
130
|
+
selectedItems.length,
|
|
131
|
+
loadedItems.toLocaleString() + (hasNextPage ? '+' : '')
|
|
132
|
+
].join(' / ');
|
|
133
|
+
} else {
|
|
134
|
+
return loadedItems.toLocaleString() + (hasNextPage ? '+' : '');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Breakpoint,
|
|
3
|
+
Button,
|
|
4
|
+
ButtonProps,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogActions,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogContentText,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
IconButton
|
|
11
|
+
} from '@mui/material';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Labels } from './app/Labels';
|
|
14
|
+
|
|
15
|
+
export interface DialogButtonProps extends ButtonProps {
|
|
16
|
+
/**
|
|
17
|
+
* Button label
|
|
18
|
+
*/
|
|
19
|
+
buttonLabel?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Dialog content
|
|
23
|
+
*/
|
|
24
|
+
content: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Show content in pre component
|
|
28
|
+
*/
|
|
29
|
+
contentPre?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Default is label
|
|
33
|
+
*/
|
|
34
|
+
dialogTitle?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Disable the scroll lock behavior.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
disableScrollLock?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Show fullscreen dialog
|
|
44
|
+
*/
|
|
45
|
+
fullScreen?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* If `true`, the dialog stretches to `maxWidth`.
|
|
49
|
+
*
|
|
50
|
+
* Notice that the dialog width grow is limited by the default margin.
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
fullWidth?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Other layouts
|
|
57
|
+
*/
|
|
58
|
+
inputs?: React.ReactNode;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Max width of the dialog
|
|
62
|
+
*/
|
|
63
|
+
maxWidth?: Breakpoint | false;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Icon button
|
|
67
|
+
*/
|
|
68
|
+
icon?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Dialog button
|
|
73
|
+
* @param props Props
|
|
74
|
+
* @returns Component
|
|
75
|
+
*/
|
|
76
|
+
export function DialogButton(props: DialogButtonProps) {
|
|
77
|
+
// Labels shared with NotificationMU
|
|
78
|
+
const labels = Labels.NotificationMU;
|
|
79
|
+
|
|
80
|
+
// Destruct
|
|
81
|
+
const {
|
|
82
|
+
buttonLabel = labels.alertOK,
|
|
83
|
+
children,
|
|
84
|
+
content,
|
|
85
|
+
contentPre,
|
|
86
|
+
dialogTitle,
|
|
87
|
+
disableScrollLock,
|
|
88
|
+
fullScreen,
|
|
89
|
+
fullWidth,
|
|
90
|
+
icon,
|
|
91
|
+
inputs,
|
|
92
|
+
maxWidth,
|
|
93
|
+
onClick,
|
|
94
|
+
title,
|
|
95
|
+
...rest
|
|
96
|
+
} = props;
|
|
97
|
+
|
|
98
|
+
// Open state
|
|
99
|
+
const [open, setOpen] = React.useState(false);
|
|
100
|
+
|
|
101
|
+
const handleClickOpen = () => {
|
|
102
|
+
setOpen(true);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Onclick handler
|
|
106
|
+
const onClickLocal = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
107
|
+
// Stop propagation
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
|
|
111
|
+
// Show dialog
|
|
112
|
+
handleClickOpen();
|
|
113
|
+
|
|
114
|
+
// Additional callback
|
|
115
|
+
if (onClick) onClick(event);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Layout
|
|
119
|
+
return (
|
|
120
|
+
<React.Fragment>
|
|
121
|
+
{icon == null ? (
|
|
122
|
+
<Button {...rest} title={title} onClick={onClickLocal}>
|
|
123
|
+
{children}
|
|
124
|
+
</Button>
|
|
125
|
+
) : (
|
|
126
|
+
<IconButton
|
|
127
|
+
{...rest}
|
|
128
|
+
onClick={onClickLocal}
|
|
129
|
+
title={title ?? children?.toString()}
|
|
130
|
+
>
|
|
131
|
+
{icon}
|
|
132
|
+
</IconButton>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
<Dialog
|
|
136
|
+
disableScrollLock={disableScrollLock}
|
|
137
|
+
fullScreen={fullScreen}
|
|
138
|
+
fullWidth={fullWidth}
|
|
139
|
+
maxWidth={maxWidth}
|
|
140
|
+
open={open}
|
|
141
|
+
onClose={() => setOpen(false)}
|
|
142
|
+
onClick={(event) => {
|
|
143
|
+
// The dialog will be embeded and the click event will bubble up
|
|
144
|
+
// Stop propatation but will also cancel onClose and onBackdropClick event
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<DialogTitle>{dialogTitle ?? title ?? children}</DialogTitle>
|
|
149
|
+
<DialogContent>
|
|
150
|
+
<DialogContentText component={contentPre ? 'pre' : 'span'}>
|
|
151
|
+
{content}
|
|
152
|
+
</DialogContentText>
|
|
153
|
+
{inputs}
|
|
154
|
+
</DialogContent>
|
|
155
|
+
<DialogActions>
|
|
156
|
+
<Button onClick={() => setOpen(false)}>
|
|
157
|
+
{buttonLabel}
|
|
158
|
+
</Button>
|
|
159
|
+
</DialogActions>
|
|
160
|
+
</Dialog>
|
|
161
|
+
</React.Fragment>
|
|
162
|
+
);
|
|
163
|
+
}
|
package/src/DnDList.tsx
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DndContext,
|
|
3
|
+
DragEndEvent,
|
|
4
|
+
DragStartEvent,
|
|
5
|
+
UniqueIdentifier
|
|
6
|
+
} from '@dnd-kit/core';
|
|
7
|
+
import {
|
|
8
|
+
SortableContext,
|
|
9
|
+
useSortable,
|
|
10
|
+
verticalListSortingStrategy
|
|
11
|
+
} from '@dnd-kit/sortable';
|
|
12
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
13
|
+
import { DataTypes } from '@etsoo/shared';
|
|
14
|
+
import { Theme, useTheme } from '@mui/material';
|
|
15
|
+
import React, { CSSProperties } from 'react';
|
|
16
|
+
|
|
17
|
+
function SortableItem(props: {
|
|
18
|
+
id: UniqueIdentifier;
|
|
19
|
+
itemRenderer: (
|
|
20
|
+
nodeRef: React.ComponentProps<any>,
|
|
21
|
+
actionNodeRef: React.ComponentProps<any>
|
|
22
|
+
) => React.ReactElement;
|
|
23
|
+
style?: React.CSSProperties;
|
|
24
|
+
}) {
|
|
25
|
+
// Destruct
|
|
26
|
+
const { id, itemRenderer, style = {} } = props;
|
|
27
|
+
|
|
28
|
+
// Use sortable
|
|
29
|
+
const {
|
|
30
|
+
attributes,
|
|
31
|
+
listeners,
|
|
32
|
+
setNodeRef,
|
|
33
|
+
transform,
|
|
34
|
+
transition,
|
|
35
|
+
setActivatorNodeRef
|
|
36
|
+
} = useSortable({ id });
|
|
37
|
+
|
|
38
|
+
const allStyle = {
|
|
39
|
+
...style,
|
|
40
|
+
transform: CSS.Transform.toString(transform),
|
|
41
|
+
transition
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const nodeRef = {
|
|
45
|
+
style: allStyle,
|
|
46
|
+
ref: setNodeRef,
|
|
47
|
+
...attributes
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const actionNodeRef = {
|
|
51
|
+
...listeners,
|
|
52
|
+
ref: setActivatorNodeRef
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return itemRenderer(nodeRef, actionNodeRef);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* DnD item default style
|
|
60
|
+
* @param index Item index
|
|
61
|
+
* @param isDragging Is dragging
|
|
62
|
+
* @param theme Theme
|
|
63
|
+
* @returns Style
|
|
64
|
+
*/
|
|
65
|
+
export const DnDItemStyle = (
|
|
66
|
+
index: number,
|
|
67
|
+
isDragging: boolean,
|
|
68
|
+
theme: Theme
|
|
69
|
+
) => ({
|
|
70
|
+
padding: theme.spacing(1),
|
|
71
|
+
zIndex: isDragging ? 1 : 'auto',
|
|
72
|
+
background: isDragging
|
|
73
|
+
? theme.palette.primary.light
|
|
74
|
+
: index % 2 === 0
|
|
75
|
+
? theme.palette.grey[100]
|
|
76
|
+
: theme.palette.grey[50]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* DnD list forward ref
|
|
81
|
+
*/
|
|
82
|
+
export interface DnDListRef<D extends object> {
|
|
83
|
+
/**
|
|
84
|
+
* Add item
|
|
85
|
+
* @param item New item
|
|
86
|
+
*/
|
|
87
|
+
addItem(item: D): void;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Add items
|
|
91
|
+
* @param items items
|
|
92
|
+
*/
|
|
93
|
+
addItems(items: D[]): void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete item
|
|
97
|
+
* @param index Item index
|
|
98
|
+
*/
|
|
99
|
+
deleteItem(index: number): void;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Edit item
|
|
103
|
+
* @param newItem New item
|
|
104
|
+
* @param index Index
|
|
105
|
+
*/
|
|
106
|
+
editItem(newItem: D, index: number): boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* DnD sortable list properties
|
|
111
|
+
*/
|
|
112
|
+
export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
113
|
+
/**
|
|
114
|
+
* Get list item style callback
|
|
115
|
+
*/
|
|
116
|
+
getItemStyle?: (index: number, isDragging: boolean) => CSSProperties;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Item renderer
|
|
120
|
+
*/
|
|
121
|
+
itemRenderer: (
|
|
122
|
+
item: D,
|
|
123
|
+
index: number,
|
|
124
|
+
nodeRef: React.ComponentProps<any>,
|
|
125
|
+
actionNodeRef: React.ComponentProps<any>
|
|
126
|
+
) => React.ReactElement;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* List items
|
|
130
|
+
*/
|
|
131
|
+
items: D[];
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Unique key field
|
|
135
|
+
*/
|
|
136
|
+
keyField: K;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Label field
|
|
140
|
+
*/
|
|
141
|
+
labelField: K;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Methods ref
|
|
145
|
+
*/
|
|
146
|
+
mRef?: React.Ref<DnDListRef<D>>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Data change handler
|
|
150
|
+
*/
|
|
151
|
+
onChange?: (items: D[]) => void;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Drag end handler
|
|
155
|
+
*/
|
|
156
|
+
onDragEnd?: (items: D[]) => void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* DnD (Drag and Drop) sortable list
|
|
161
|
+
* @param props Props
|
|
162
|
+
* @returns Component
|
|
163
|
+
*/
|
|
164
|
+
export function DnDList<
|
|
165
|
+
D extends { id: UniqueIdentifier },
|
|
166
|
+
K extends DataTypes.Keys<D, UniqueIdentifier> = DataTypes.Keys<
|
|
167
|
+
D,
|
|
168
|
+
UniqueIdentifier
|
|
169
|
+
>
|
|
170
|
+
>(props: DnDListPros<D, K>) {
|
|
171
|
+
// Destruct
|
|
172
|
+
const { keyField, itemRenderer, labelField, mRef, onChange, onDragEnd } =
|
|
173
|
+
props;
|
|
174
|
+
|
|
175
|
+
let getItemStyle = props.getItemStyle;
|
|
176
|
+
if (getItemStyle == null) {
|
|
177
|
+
// Theme
|
|
178
|
+
const theme = useTheme();
|
|
179
|
+
getItemStyle = (index, isDragging) =>
|
|
180
|
+
DnDItemStyle(index, isDragging, theme);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// States
|
|
184
|
+
const [items, setItems] = React.useState<D[]>([]);
|
|
185
|
+
const [activeId, setActiveId] = React.useState<UniqueIdentifier>();
|
|
186
|
+
|
|
187
|
+
const changeItems = (newItems: D[]) => {
|
|
188
|
+
// Possible to alter items with the handler
|
|
189
|
+
if (onChange) onChange(newItems);
|
|
190
|
+
|
|
191
|
+
// Update state
|
|
192
|
+
setItems(newItems);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Drag event handlers
|
|
196
|
+
function handleDragStart(event: DragStartEvent) {
|
|
197
|
+
const { active } = event;
|
|
198
|
+
setActiveId(active.id);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function handleDragEnd(event: DragEndEvent) {
|
|
202
|
+
const { active, over } = event;
|
|
203
|
+
|
|
204
|
+
if (over && active.id !== over.id) {
|
|
205
|
+
// Indices
|
|
206
|
+
const oldIndex = items.findIndex((item) => item.id === active.id);
|
|
207
|
+
const newIndex = items.findIndex((item) => item.id === over.id);
|
|
208
|
+
|
|
209
|
+
// Clone
|
|
210
|
+
const newItems = [...items];
|
|
211
|
+
|
|
212
|
+
// Removed item
|
|
213
|
+
const [removed] = newItems.splice(oldIndex, 1);
|
|
214
|
+
|
|
215
|
+
// Insert to the destination index
|
|
216
|
+
newItems.splice(newIndex, 0, removed);
|
|
217
|
+
|
|
218
|
+
changeItems(newItems);
|
|
219
|
+
|
|
220
|
+
// Drag end handler
|
|
221
|
+
if (onDragEnd) onDragEnd(newItems);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
setActiveId(undefined);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Methods
|
|
228
|
+
React.useImperativeHandle(
|
|
229
|
+
mRef,
|
|
230
|
+
() => {
|
|
231
|
+
return {
|
|
232
|
+
addItem(newItem: D) {
|
|
233
|
+
// Existence check
|
|
234
|
+
if (
|
|
235
|
+
items.some(
|
|
236
|
+
(item) => item[labelField] === newItem[labelField]
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Clone
|
|
243
|
+
const newItems = [newItem, ...items];
|
|
244
|
+
|
|
245
|
+
// Update the state
|
|
246
|
+
changeItems(newItems);
|
|
247
|
+
|
|
248
|
+
return true;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
addItems(inputItems: D[]) {
|
|
252
|
+
// Clone
|
|
253
|
+
const newItems = [...items];
|
|
254
|
+
|
|
255
|
+
// Insert items
|
|
256
|
+
inputItems.forEach((newItem) => {
|
|
257
|
+
// Existence check
|
|
258
|
+
if (
|
|
259
|
+
newItems.some(
|
|
260
|
+
(item) =>
|
|
261
|
+
item[labelField] === newItem[labelField]
|
|
262
|
+
)
|
|
263
|
+
) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
newItems.push(newItem);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Update the state
|
|
271
|
+
changeItems(newItems);
|
|
272
|
+
|
|
273
|
+
return newItems.length - items.length;
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
editItem(newItem: D, index: number) {
|
|
277
|
+
// Existence check
|
|
278
|
+
const newIndex = items.findIndex(
|
|
279
|
+
(item) => item[labelField] === newItem[labelField]
|
|
280
|
+
);
|
|
281
|
+
if (newIndex >= 0 && newIndex !== index) {
|
|
282
|
+
// Label field is the same with a different item
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Clone
|
|
287
|
+
const newItems = [...items];
|
|
288
|
+
|
|
289
|
+
// Remove the item
|
|
290
|
+
newItems.splice(index, 1, newItem);
|
|
291
|
+
|
|
292
|
+
// Update the state
|
|
293
|
+
changeItems(newItems);
|
|
294
|
+
|
|
295
|
+
return true;
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
deleteItem(index: number) {
|
|
299
|
+
// Clone
|
|
300
|
+
const newItems = [...items];
|
|
301
|
+
|
|
302
|
+
// Remove the item
|
|
303
|
+
newItems.splice(index, 1);
|
|
304
|
+
|
|
305
|
+
// Update the state
|
|
306
|
+
changeItems(newItems);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
[items]
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
React.useEffect(() => {
|
|
314
|
+
setItems(props.items);
|
|
315
|
+
}, [props.items]);
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
|
319
|
+
<SortableContext
|
|
320
|
+
items={items}
|
|
321
|
+
strategy={verticalListSortingStrategy}
|
|
322
|
+
>
|
|
323
|
+
{items.map((item, index) => {
|
|
324
|
+
const id = item[keyField] as unknown as UniqueIdentifier;
|
|
325
|
+
return (
|
|
326
|
+
<SortableItem
|
|
327
|
+
id={id}
|
|
328
|
+
key={id}
|
|
329
|
+
style={getItemStyle!(index, id === activeId)}
|
|
330
|
+
itemRenderer={(nodeRef, actionNodeRef) =>
|
|
331
|
+
itemRenderer(
|
|
332
|
+
item,
|
|
333
|
+
index,
|
|
334
|
+
nodeRef,
|
|
335
|
+
actionNodeRef
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
/>
|
|
339
|
+
);
|
|
340
|
+
})}
|
|
341
|
+
</SortableContext>
|
|
342
|
+
</DndContext>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Paper, PaperProps } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Draggable from 'react-draggable';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draggable paper component
|
|
7
|
+
* @param props Props
|
|
8
|
+
* @returns Component
|
|
9
|
+
*/
|
|
10
|
+
export function DraggablePaperComponent(props: PaperProps) {
|
|
11
|
+
return (
|
|
12
|
+
<Draggable
|
|
13
|
+
handle=".draggable-dialog-title"
|
|
14
|
+
cancel={'[class*="MuiDialogContent-root"]'}
|
|
15
|
+
>
|
|
16
|
+
<Paper {...props} />
|
|
17
|
+
</Draggable>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TextField, TextFieldProps } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Email input props
|
|
6
|
+
*/
|
|
7
|
+
export type EmailInputProps = Omit<TextFieldProps, 'type'> & {};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Email input
|
|
11
|
+
* @param props Props
|
|
12
|
+
*/
|
|
13
|
+
export function EmailInput(props: EmailInputProps) {
|
|
14
|
+
// Destruct
|
|
15
|
+
const { inputProps = {}, ...rest } = props;
|
|
16
|
+
|
|
17
|
+
// Default max length
|
|
18
|
+
inputProps.maxLength ??= 128;
|
|
19
|
+
|
|
20
|
+
// Layout
|
|
21
|
+
return (
|
|
22
|
+
<TextField type="email" fullWidth inputProps={inputProps} {...rest} />
|
|
23
|
+
);
|
|
24
|
+
}
|