@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,81 @@
|
|
|
1
|
+
import { ScrollerListProps } from '@etsoo/react';
|
|
2
|
+
import { DataTypes, IdDefaultType } from '@etsoo/shared';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ListChildComponentProps } from 'react-window';
|
|
5
|
+
import { MouseEventWithDataHandler } from './MUGlobal';
|
|
6
|
+
/**
|
|
7
|
+
* Extended ScrollerList inner item renderer props
|
|
8
|
+
*/
|
|
9
|
+
export interface ScrollerListExInnerItemRendererProps<T> extends ListChildComponentProps<T> {
|
|
10
|
+
/**
|
|
11
|
+
* Item selected
|
|
12
|
+
*/
|
|
13
|
+
selected: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Item height
|
|
16
|
+
*/
|
|
17
|
+
itemHeight: number;
|
|
18
|
+
/**
|
|
19
|
+
* Item space
|
|
20
|
+
*/
|
|
21
|
+
space: number;
|
|
22
|
+
/**
|
|
23
|
+
* Default margins
|
|
24
|
+
*/
|
|
25
|
+
margins: object;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extended ScrollerList ItemSize type
|
|
29
|
+
* 1. Callback function
|
|
30
|
+
* 2. Static sets
|
|
31
|
+
* 3. Dynamic calculation
|
|
32
|
+
*/
|
|
33
|
+
export declare type ScrollerListExItemSize = ((index: number) => [number, number] | [number, number, object]) | [number, number] | [number, object, boolean?];
|
|
34
|
+
/**
|
|
35
|
+
* Extended ScrollerList Props
|
|
36
|
+
*/
|
|
37
|
+
export declare type ScrollerListExProps<T extends object, D extends DataTypes.Keys<T>> = Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> & {
|
|
38
|
+
/**
|
|
39
|
+
* Alternating colors for odd/even rows
|
|
40
|
+
*/
|
|
41
|
+
alternatingColors?: [string?, string?];
|
|
42
|
+
/**
|
|
43
|
+
* Inner item renderer
|
|
44
|
+
*/
|
|
45
|
+
innerItemRenderer: (props: ScrollerListExInnerItemRendererProps<T>) => React.ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Item renderer
|
|
48
|
+
*/
|
|
49
|
+
itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
|
|
50
|
+
/**
|
|
51
|
+
* Id field
|
|
52
|
+
* Failed: D extends { id: DataTypes.IdType } ? { idField?: D } : { idField: D }
|
|
53
|
+
*/
|
|
54
|
+
idField?: D;
|
|
55
|
+
/**
|
|
56
|
+
* Item size, a function indicates its a variable size list
|
|
57
|
+
*/
|
|
58
|
+
itemSize: ScrollerListExItemSize;
|
|
59
|
+
/**
|
|
60
|
+
* Double click handler
|
|
61
|
+
*/
|
|
62
|
+
onDoubleClick?: MouseEventWithDataHandler<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Click handler
|
|
65
|
+
*/
|
|
66
|
+
onClick?: MouseEventWithDataHandler<T>;
|
|
67
|
+
/**
|
|
68
|
+
* On items select change
|
|
69
|
+
*/
|
|
70
|
+
onSelectChange?: (selectedItems: T[]) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Selected color
|
|
73
|
+
*/
|
|
74
|
+
selectedColor?: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Extended ScrollerList
|
|
78
|
+
* @param props Props
|
|
79
|
+
* @returns Component
|
|
80
|
+
*/
|
|
81
|
+
export declare function ScrollerListEx<T extends object, D extends DataTypes.Keys<T> = IdDefaultType<T>>(props: ScrollerListExProps<T, D>): JSX.Element;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { css } from '@emotion/css';
|
|
2
|
+
import { ScrollerList } from '@etsoo/react';
|
|
3
|
+
import { DataTypes, Utils } from '@etsoo/shared';
|
|
4
|
+
import { useTheme } from '@mui/material';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { MUGlobal } from './MUGlobal';
|
|
7
|
+
// Scroll bar size
|
|
8
|
+
const scrollbarSize = 16;
|
|
9
|
+
// Selected class name
|
|
10
|
+
const selectedClassName = 'ScrollerListEx-Selected';
|
|
11
|
+
const createGridStyle = (alternatingColors, selectedColor) => {
|
|
12
|
+
return css({
|
|
13
|
+
'& .ScrollerListEx-Selected': {
|
|
14
|
+
backgroundColor: selectedColor
|
|
15
|
+
},
|
|
16
|
+
'& .ScrollerListEx-Row0:not(.ScrollerListEx-Selected)': {
|
|
17
|
+
backgroundColor: alternatingColors[0]
|
|
18
|
+
},
|
|
19
|
+
'& .ScrollerListEx-Row1:not(.ScrollerListEx-Selected)': {
|
|
20
|
+
backgroundColor: alternatingColors[1]
|
|
21
|
+
},
|
|
22
|
+
'@media (min-width: 800px)': {
|
|
23
|
+
'::-webkit-scrollbar': {
|
|
24
|
+
width: scrollbarSize,
|
|
25
|
+
height: scrollbarSize,
|
|
26
|
+
backgroundColor: '#f6f6f6'
|
|
27
|
+
},
|
|
28
|
+
'::-webkit-scrollbar-thumb': {
|
|
29
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
30
|
+
borderRadius: '2px'
|
|
31
|
+
},
|
|
32
|
+
'::-webkit-scrollbar-track-piece:start': {
|
|
33
|
+
background: 'transparent'
|
|
34
|
+
},
|
|
35
|
+
'::-webkit-scrollbar-track-piece:end': {
|
|
36
|
+
background: 'transparent'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
// Default margin
|
|
42
|
+
const defaultMargin = (margin, isNarrow) => {
|
|
43
|
+
const half = MUGlobal.half(margin);
|
|
44
|
+
if (isNarrow == null) {
|
|
45
|
+
const half = MUGlobal.half(margin);
|
|
46
|
+
return {
|
|
47
|
+
marginLeft: margin,
|
|
48
|
+
marginRight: margin,
|
|
49
|
+
marginTop: half,
|
|
50
|
+
marginBottom: half
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (isNarrow) {
|
|
54
|
+
return {
|
|
55
|
+
marginLeft: 0,
|
|
56
|
+
marginRight: 0,
|
|
57
|
+
marginTop: half,
|
|
58
|
+
marginBottom: half
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
marginLeft: half,
|
|
63
|
+
marginRight: half,
|
|
64
|
+
marginTop: half,
|
|
65
|
+
marginBottom: half
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
// Default itemRenderer
|
|
69
|
+
function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, onClick, onDoubleClick, space, margins }) {
|
|
70
|
+
// Child
|
|
71
|
+
const child = innerItemRenderer({
|
|
72
|
+
index,
|
|
73
|
+
data,
|
|
74
|
+
style,
|
|
75
|
+
selected,
|
|
76
|
+
itemHeight,
|
|
77
|
+
space,
|
|
78
|
+
margins
|
|
79
|
+
});
|
|
80
|
+
let rowClass = `ScrollerListEx-Row${index % 2}`;
|
|
81
|
+
if (selected)
|
|
82
|
+
rowClass += ` ${selectedClassName}`;
|
|
83
|
+
// Layout
|
|
84
|
+
return (React.createElement("div", { className: rowClass, style: style, onMouseDown: (event) => onMouseDown(event.currentTarget, data), onClick: (event) => onClick && onClick(event, data), onDoubleClick: (event) => onDoubleClick && onDoubleClick(event, data) }, child));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extended ScrollerList
|
|
88
|
+
* @param props Props
|
|
89
|
+
* @returns Component
|
|
90
|
+
*/
|
|
91
|
+
export function ScrollerListEx(props) {
|
|
92
|
+
// Selected item ref
|
|
93
|
+
const selectedItem = React.useRef();
|
|
94
|
+
const onMouseDown = (div, data) => {
|
|
95
|
+
var _a;
|
|
96
|
+
// Destruct
|
|
97
|
+
const [selectedDiv, selectedData] = (_a = selectedItem.current) !== null && _a !== void 0 ? _a : [];
|
|
98
|
+
if (selectedData != null && selectedData[idField] === data[idField])
|
|
99
|
+
return;
|
|
100
|
+
selectedDiv === null || selectedDiv === void 0 ? void 0 : selectedDiv.classList.remove(selectedClassName);
|
|
101
|
+
div.classList.add(selectedClassName);
|
|
102
|
+
selectedItem.current = [div, data];
|
|
103
|
+
if (onSelectChange)
|
|
104
|
+
onSelectChange([data]);
|
|
105
|
+
};
|
|
106
|
+
const isSelected = (data) => {
|
|
107
|
+
var _a;
|
|
108
|
+
const [_, selectedData] = (_a = selectedItem.current) !== null && _a !== void 0 ? _a : [];
|
|
109
|
+
const selected = selectedData && data && selectedData[idField] === data[idField]
|
|
110
|
+
? true
|
|
111
|
+
: false;
|
|
112
|
+
return selected;
|
|
113
|
+
};
|
|
114
|
+
// Destruct
|
|
115
|
+
const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => { var _a; return (_a = DataTypes.getIdValue1(data, idField)) !== null && _a !== void 0 ? _a : index; }, itemRenderer = (itemProps) => {
|
|
116
|
+
const [itemHeight, space, margins] = calculateItemSize(itemProps.index);
|
|
117
|
+
return defaultItemRenderer({
|
|
118
|
+
itemHeight,
|
|
119
|
+
innerItemRenderer,
|
|
120
|
+
onMouseDown,
|
|
121
|
+
onClick,
|
|
122
|
+
onDoubleClick,
|
|
123
|
+
space,
|
|
124
|
+
margins,
|
|
125
|
+
selected: isSelected(itemProps.data),
|
|
126
|
+
...itemProps
|
|
127
|
+
});
|
|
128
|
+
}, onClick, onDoubleClick, onSelectChange, selectedColor = '#edf4fb', ...rest } = props;
|
|
129
|
+
// Theme
|
|
130
|
+
const theme = useTheme();
|
|
131
|
+
// Cache calculation
|
|
132
|
+
const itemSizeResult = React.useMemo(() => {
|
|
133
|
+
if (typeof itemSize === 'function')
|
|
134
|
+
return undefined;
|
|
135
|
+
const [size, spaces, isNarrow] = itemSize;
|
|
136
|
+
if (typeof spaces === 'number')
|
|
137
|
+
return [
|
|
138
|
+
size,
|
|
139
|
+
spaces,
|
|
140
|
+
defaultMargin(MUGlobal.pagePaddings, undefined)
|
|
141
|
+
];
|
|
142
|
+
return [
|
|
143
|
+
size,
|
|
144
|
+
MUGlobal.getSpace(spaces, theme),
|
|
145
|
+
defaultMargin(spaces, isNarrow)
|
|
146
|
+
];
|
|
147
|
+
}, [itemSize]);
|
|
148
|
+
// Calculate size
|
|
149
|
+
const calculateItemSize = (index) => {
|
|
150
|
+
// Callback function
|
|
151
|
+
if (typeof itemSize === 'function') {
|
|
152
|
+
const result = itemSize(index);
|
|
153
|
+
if (result.length == 2)
|
|
154
|
+
return [...result, defaultMargin(MUGlobal.pagePaddings)];
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
// Calculation
|
|
158
|
+
return itemSizeResult;
|
|
159
|
+
};
|
|
160
|
+
// Local item size
|
|
161
|
+
const itemSizeLocal = (index) => {
|
|
162
|
+
const [size, space] = calculateItemSize(index);
|
|
163
|
+
return size + space;
|
|
164
|
+
};
|
|
165
|
+
// Layout
|
|
166
|
+
return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
|
|
167
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Search bar props
|
|
4
|
+
*/
|
|
5
|
+
export interface SearchBarProps {
|
|
6
|
+
/**
|
|
7
|
+
* Style class name
|
|
8
|
+
*/
|
|
9
|
+
className?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Fields
|
|
12
|
+
*/
|
|
13
|
+
fields: React.ReactElement[];
|
|
14
|
+
/**
|
|
15
|
+
* Inner height
|
|
16
|
+
* @default 40
|
|
17
|
+
*/
|
|
18
|
+
innerHeight?: number;
|
|
19
|
+
/**
|
|
20
|
+
* On submit callback
|
|
21
|
+
*/
|
|
22
|
+
onSubmit: (data: FormData, reset: boolean) => void | PromiseLike<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Search bar
|
|
26
|
+
* @param props Props
|
|
27
|
+
* @returns Component
|
|
28
|
+
*/
|
|
29
|
+
export declare function SearchBar(props: SearchBarProps): JSX.Element;
|
package/lib/SearchBar.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Button, Drawer, IconButton, Stack, useTheme } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
4
|
+
import { DomUtils } from '@etsoo/shared';
|
|
5
|
+
import { ReactUtils, useDelayedExecutor, useDimensions } from '@etsoo/react';
|
|
6
|
+
import { Labels } from './app/Labels';
|
|
7
|
+
// Cached width attribute name
|
|
8
|
+
const cachedWidthName = 'data-cached-width';
|
|
9
|
+
// Reset form
|
|
10
|
+
const resetForm = (form) => {
|
|
11
|
+
for (const input of form.elements) {
|
|
12
|
+
// Ignore disabled inputs
|
|
13
|
+
if ('disabled' in input && input.disabled)
|
|
14
|
+
continue;
|
|
15
|
+
// All non hidden inputs
|
|
16
|
+
if (input instanceof HTMLInputElement) {
|
|
17
|
+
// Ignore hidden input
|
|
18
|
+
if (input.type === 'hidden')
|
|
19
|
+
continue;
|
|
20
|
+
// Ignore readOnly without data-reset=true inputs
|
|
21
|
+
if (!input.readOnly || input.dataset.reset === 'true') {
|
|
22
|
+
ReactUtils.triggerChange(input, '', true);
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// All selects
|
|
27
|
+
if (input instanceof HTMLSelectElement) {
|
|
28
|
+
if (input.options.length > 0 && input.options[0].value === '') {
|
|
29
|
+
input.selectedIndex = 0;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
input.selectedIndex = -1;
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Trigger reset event
|
|
38
|
+
const resetEvent = new Event('reset');
|
|
39
|
+
form.dispatchEvent(resetEvent);
|
|
40
|
+
};
|
|
41
|
+
// Disable inputs avoid auto trigger change events for them
|
|
42
|
+
const setChildState = (child, enabled) => {
|
|
43
|
+
const inputs = child.getElementsByTagName('input');
|
|
44
|
+
for (const input of inputs) {
|
|
45
|
+
input.disabled = !enabled;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Search bar
|
|
50
|
+
* @param props Props
|
|
51
|
+
* @returns Component
|
|
52
|
+
*/
|
|
53
|
+
export function SearchBar(props) {
|
|
54
|
+
// Destruct
|
|
55
|
+
const { className, fields, innerHeight = 40, onSubmit } = props;
|
|
56
|
+
// Labels
|
|
57
|
+
const labels = Labels.CommonPage;
|
|
58
|
+
// Spacing
|
|
59
|
+
const theme = useTheme();
|
|
60
|
+
const gap = parseFloat(theme.spacing(1));
|
|
61
|
+
// Menu index
|
|
62
|
+
const [index, updateIndex] = React.useState();
|
|
63
|
+
// Drawer open / close
|
|
64
|
+
const [open, updateOpen] = React.useState(false);
|
|
65
|
+
// State
|
|
66
|
+
const state = React.useRef({ hasMore: true, lastMaxWidth: 9999 }).current;
|
|
67
|
+
// Watch container
|
|
68
|
+
const { dimensions } = useDimensions(1, (target, rect) => {
|
|
69
|
+
// Same logic from resetButtonRef
|
|
70
|
+
if (rect.width === state.lastMaxWidth ||
|
|
71
|
+
(!state.hasMore && rect.width > state.lastMaxWidth))
|
|
72
|
+
return false;
|
|
73
|
+
// Len
|
|
74
|
+
const len = target.children.length;
|
|
75
|
+
for (let i = 0; i < len; i++) {
|
|
76
|
+
var classList = target.children[i].classList;
|
|
77
|
+
classList.remove('showChild');
|
|
78
|
+
}
|
|
79
|
+
}, 0);
|
|
80
|
+
// Show or hide element
|
|
81
|
+
const setElementVisible = (element, visible) => {
|
|
82
|
+
element.classList.remove(visible ? 'hiddenChild' : 'showChild');
|
|
83
|
+
element.classList.add(visible ? 'showChild' : 'hiddenChild');
|
|
84
|
+
};
|
|
85
|
+
// Reset button ref
|
|
86
|
+
const resetButtonRef = (instance) => {
|
|
87
|
+
// Reset button
|
|
88
|
+
const resetButton = instance;
|
|
89
|
+
if (resetButton == null)
|
|
90
|
+
return;
|
|
91
|
+
// First
|
|
92
|
+
const [_, container, containerRect] = dimensions[0];
|
|
93
|
+
if (container == null ||
|
|
94
|
+
containerRect == null ||
|
|
95
|
+
containerRect.width < 10)
|
|
96
|
+
return;
|
|
97
|
+
// Container width
|
|
98
|
+
let maxWidth = containerRect.width;
|
|
99
|
+
if (maxWidth === state.lastMaxWidth ||
|
|
100
|
+
(!state.hasMore && maxWidth > state.lastMaxWidth)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
state.lastMaxWidth = maxWidth;
|
|
104
|
+
// More button
|
|
105
|
+
const buttonMore = resetButton.previousElementSibling;
|
|
106
|
+
// Cached button width
|
|
107
|
+
const cachedButtonWidth = container.getAttribute(cachedWidthName);
|
|
108
|
+
if (cachedButtonWidth) {
|
|
109
|
+
maxWidth -= Number.parseFloat(cachedButtonWidth);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Reset button rect
|
|
113
|
+
const resetButtonRect = resetButton.getBoundingClientRect();
|
|
114
|
+
// More button rect
|
|
115
|
+
const buttonMoreRect = buttonMore.getBoundingClientRect();
|
|
116
|
+
// Total
|
|
117
|
+
const totalButtonWidth = resetButtonRect.width + buttonMoreRect.width + 3 * gap;
|
|
118
|
+
// Cache
|
|
119
|
+
container.setAttribute(cachedWidthName, totalButtonWidth.toString());
|
|
120
|
+
maxWidth -= totalButtonWidth;
|
|
121
|
+
}
|
|
122
|
+
// Children
|
|
123
|
+
const children = container.children;
|
|
124
|
+
// Len
|
|
125
|
+
const len = children.length;
|
|
126
|
+
// Other elements
|
|
127
|
+
const others = len - 2;
|
|
128
|
+
let hasMore = false;
|
|
129
|
+
let newIndex = others;
|
|
130
|
+
for (let c = 0; c < others; c++) {
|
|
131
|
+
const child = children[c];
|
|
132
|
+
const cachedWidth = child.getAttribute(cachedWidthName);
|
|
133
|
+
let childWidth;
|
|
134
|
+
if (cachedWidth) {
|
|
135
|
+
childWidth = Number.parseFloat(cachedWidth);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const childD = child.getBoundingClientRect();
|
|
139
|
+
childWidth = childD.width + gap;
|
|
140
|
+
child.setAttribute(cachedWidthName, childWidth.toString());
|
|
141
|
+
}
|
|
142
|
+
// No gap here, child width includes the gap
|
|
143
|
+
if (childWidth <= maxWidth) {
|
|
144
|
+
maxWidth -= childWidth;
|
|
145
|
+
setChildState(child, true);
|
|
146
|
+
setElementVisible(child, true);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
setChildState(child, false);
|
|
150
|
+
setElementVisible(child, false);
|
|
151
|
+
if (!hasMore) {
|
|
152
|
+
// Make sure coming logic to the block
|
|
153
|
+
maxWidth = 0;
|
|
154
|
+
// Keep the current index
|
|
155
|
+
newIndex = c;
|
|
156
|
+
// Indicates more
|
|
157
|
+
hasMore = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Show or hide more button
|
|
162
|
+
state.hasMore = hasMore;
|
|
163
|
+
setElementVisible(buttonMore, hasMore);
|
|
164
|
+
setElementVisible(resetButton, true);
|
|
165
|
+
// Update menu start index
|
|
166
|
+
updateIndex(newIndex);
|
|
167
|
+
};
|
|
168
|
+
// More items creator
|
|
169
|
+
const moreItems = [];
|
|
170
|
+
if (index != null) {
|
|
171
|
+
for (let i = index; i < fields.length; i++) {
|
|
172
|
+
moreItems.push(React.createElement(React.Fragment, { key: i }, fields[i]));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Handle main form
|
|
176
|
+
const handleForm = (event) => {
|
|
177
|
+
if (event.nativeEvent.cancelable && !event.nativeEvent.composed)
|
|
178
|
+
return;
|
|
179
|
+
if (state.form == null)
|
|
180
|
+
state.form = event.currentTarget;
|
|
181
|
+
delayed.call();
|
|
182
|
+
};
|
|
183
|
+
// Handle more button click
|
|
184
|
+
const handleMore = () => {
|
|
185
|
+
updateOpen(!open);
|
|
186
|
+
};
|
|
187
|
+
// More form change
|
|
188
|
+
const moreFormChange = (event) => {
|
|
189
|
+
if (event.nativeEvent.cancelable && !event.nativeEvent.composed)
|
|
190
|
+
return;
|
|
191
|
+
if (state.moreForm == null)
|
|
192
|
+
state.moreForm = event.currentTarget;
|
|
193
|
+
delayed.call();
|
|
194
|
+
};
|
|
195
|
+
// Submit at once
|
|
196
|
+
const handleSubmitInstant = (reset = false) => {
|
|
197
|
+
// Prepare data
|
|
198
|
+
const data = new FormData(state.form);
|
|
199
|
+
if (state.moreForm != null) {
|
|
200
|
+
DomUtils.mergeFormData(data, new FormData(state.moreForm));
|
|
201
|
+
}
|
|
202
|
+
onSubmit(data, reset);
|
|
203
|
+
};
|
|
204
|
+
const delayed = useDelayedExecutor(handleSubmitInstant, 480);
|
|
205
|
+
// Reset
|
|
206
|
+
const handleReset = () => {
|
|
207
|
+
// Clear form values
|
|
208
|
+
if (state.form != null)
|
|
209
|
+
resetForm(state.form);
|
|
210
|
+
if (state.moreForm != null)
|
|
211
|
+
resetForm(state.moreForm);
|
|
212
|
+
// Resubmit
|
|
213
|
+
handleSubmitInstant(true);
|
|
214
|
+
};
|
|
215
|
+
React.useEffect(() => {
|
|
216
|
+
// Delayed way
|
|
217
|
+
delayed.call(100);
|
|
218
|
+
return () => {
|
|
219
|
+
delayed.clear();
|
|
220
|
+
};
|
|
221
|
+
}, [className]);
|
|
222
|
+
// Layout
|
|
223
|
+
return (React.createElement(React.Fragment, null,
|
|
224
|
+
React.createElement("form", { id: "SearchBarForm", className: className, onChange: handleForm, ref: (form) => {
|
|
225
|
+
if (form)
|
|
226
|
+
state.form = form;
|
|
227
|
+
} },
|
|
228
|
+
React.createElement(Stack, { ref: dimensions[0][0], justifyContent: "center", alignItems: "center", direction: "row", spacing: 1, height: innerHeight, sx: {
|
|
229
|
+
'& > :not(style)': {
|
|
230
|
+
flexBasis: 'auto',
|
|
231
|
+
flexGrow: 0,
|
|
232
|
+
flexShrink: 0,
|
|
233
|
+
maxWidth: '180px',
|
|
234
|
+
visibility: 'hidden'
|
|
235
|
+
},
|
|
236
|
+
'& > .hiddenChild': {
|
|
237
|
+
display: 'none'
|
|
238
|
+
},
|
|
239
|
+
'& > .showChild': {
|
|
240
|
+
display: 'block',
|
|
241
|
+
visibility: 'visible'
|
|
242
|
+
}
|
|
243
|
+
} },
|
|
244
|
+
fields.map((item, index) => (React.createElement(React.Fragment, { key: index }, item))),
|
|
245
|
+
React.createElement(IconButton, { "aria-label": "delete", size: "medium", onClick: handleMore },
|
|
246
|
+
React.createElement(MoreHorizIcon, null)),
|
|
247
|
+
React.createElement(Button, { variant: "contained", size: "medium", ref: resetButtonRef, onClick: handleReset }, labels.reset))),
|
|
248
|
+
index != null && index < fields.length && (React.createElement(Drawer, { anchor: "right", sx: { minWidth: '250px' }, ModalProps: {
|
|
249
|
+
keepMounted: true // Better open performance on mobile.
|
|
250
|
+
}, open: open, onClose: () => updateOpen(false) },
|
|
251
|
+
React.createElement("form", { onChange: moreFormChange, ref: (form) => {
|
|
252
|
+
if (form)
|
|
253
|
+
state.moreForm = form;
|
|
254
|
+
} },
|
|
255
|
+
React.createElement(Stack, { direction: "column", alignItems: "stretch", spacing: 2, padding: 2, sx: {
|
|
256
|
+
'& > :not(style)': {
|
|
257
|
+
minWidth: '100px'
|
|
258
|
+
}
|
|
259
|
+
} }, moreItems))))));
|
|
260
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { TextFieldProps } from '@mui/material';
|
|
3
|
+
/**
|
|
4
|
+
* Search field props
|
|
5
|
+
*/
|
|
6
|
+
export declare type SearchFieldProps = TextFieldProps & {
|
|
7
|
+
/**
|
|
8
|
+
* Change delay (ms) to avoid repeatly dispatch onChange
|
|
9
|
+
*/
|
|
10
|
+
changeDelay?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Is the field read only?
|
|
13
|
+
*/
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Search field
|
|
18
|
+
* @param props Props
|
|
19
|
+
* @returns Component
|
|
20
|
+
*/
|
|
21
|
+
export declare function SearchField(props: SearchFieldProps): JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useDelayedExecutor } from '@etsoo/react';
|
|
2
|
+
import { TextField } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { MUGlobal } from './MUGlobal';
|
|
5
|
+
/**
|
|
6
|
+
* Search field
|
|
7
|
+
* @param props Props
|
|
8
|
+
* @returns Component
|
|
9
|
+
*/
|
|
10
|
+
export function SearchField(props) {
|
|
11
|
+
// Destruct
|
|
12
|
+
const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, readOnly, size = MUGlobal.searchFieldSize, variant = MUGlobal.searchFieldVariant, ...rest } = props;
|
|
13
|
+
// Shrink
|
|
14
|
+
InputLabelProps.shrink = MUGlobal.searchFieldShrink;
|
|
15
|
+
// Read only
|
|
16
|
+
if (readOnly != null)
|
|
17
|
+
InputProps.readOnly = readOnly;
|
|
18
|
+
const isMounted = React.useRef(true);
|
|
19
|
+
const delayed = onChange != null && changeDelay != null && changeDelay >= 1
|
|
20
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
21
|
+
: undefined;
|
|
22
|
+
const onChangeEx = (event) => {
|
|
23
|
+
if (onChange == null)
|
|
24
|
+
return;
|
|
25
|
+
if (changeDelay == null || changeDelay < 1) {
|
|
26
|
+
onChange(event);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.call(undefined, event);
|
|
30
|
+
};
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
return () => {
|
|
33
|
+
isMounted.current = false;
|
|
34
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.clear();
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
// Layout
|
|
38
|
+
return (React.createElement(TextField, { InputLabelProps: InputLabelProps, InputProps: InputProps, onChange: onChangeEx, size: size, variant: variant, ...rest }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { DataTypes, IdDefaultType, LabelDefaultType, ListType } from '@etsoo/shared';
|
|
3
|
+
import { OptionGroupProps } from './OptionGroup';
|
|
4
|
+
/**
|
|
5
|
+
* Search OptionGroup
|
|
6
|
+
* @param props Props
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export declare function SearchOptionGroup<T extends object = ListType, D extends DataTypes.Keys<T> = IdDefaultType<T>, L extends DataTypes.Keys<T, string> = LabelDefaultType<T>>(props: OptionGroupProps<T, D, L>): JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MUGlobal } from './MUGlobal';
|
|
3
|
+
import { OptionGroup } from './OptionGroup';
|
|
4
|
+
/**
|
|
5
|
+
* Search OptionGroup
|
|
6
|
+
* @param props Props
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export function SearchOptionGroup(props) {
|
|
10
|
+
// Destruct
|
|
11
|
+
const { row = true, size = MUGlobal.searchFieldSize, sx = { '& .MuiFormLabel-root': { fontSize: '0.75em' } }, ...rest } = props;
|
|
12
|
+
// Layout
|
|
13
|
+
return React.createElement(OptionGroup, { row: row, size: size, sx: sx, ...rest });
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { ListType1 } from '@etsoo/shared';
|
|
3
|
+
import { SelectExProps } from './SelectEx';
|
|
4
|
+
/**
|
|
5
|
+
* SelectBool props
|
|
6
|
+
*/
|
|
7
|
+
export declare type SelectBoolProps = Omit<SelectExProps<ListType1>, 'options' | 'loadData'>;
|
|
8
|
+
/**
|
|
9
|
+
* SelectBool (yes/no)
|
|
10
|
+
* @param props Props
|
|
11
|
+
* @returns Component
|
|
12
|
+
*/
|
|
13
|
+
export declare function SelectBool(props: SelectBoolProps): JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Utils } from '@etsoo/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { globalApp } from './app/ReactApp';
|
|
4
|
+
import { SelectEx } from './SelectEx';
|
|
5
|
+
/**
|
|
6
|
+
* SelectBool (yes/no)
|
|
7
|
+
* @param props Props
|
|
8
|
+
* @returns Component
|
|
9
|
+
*/
|
|
10
|
+
export function SelectBool(props) {
|
|
11
|
+
// Destruct
|
|
12
|
+
const { search = true, autoAddBlankItem = search, ...rest } = props;
|
|
13
|
+
// Options
|
|
14
|
+
const options = [
|
|
15
|
+
{ id: 'false', label: globalApp.get('no') },
|
|
16
|
+
{ id: 'true', label: globalApp.get('yes') }
|
|
17
|
+
];
|
|
18
|
+
if (autoAddBlankItem)
|
|
19
|
+
Utils.addBlankItem(options);
|
|
20
|
+
// Layout
|
|
21
|
+
return React.createElement(SelectEx, { options: options, search: search, ...rest });
|
|
22
|
+
}
|