@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.
Files changed (250) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.json +38 -0
  3. package/.gitattributes +2 -0
  4. package/.github/workflows/main.yml +48 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +6 -0
  7. package/LICENSE +21 -0
  8. package/README.md +16 -0
  9. package/__tests__/ComboBox.tsx +30 -0
  10. package/__tests__/MUGlobalTests.tsx +58 -0
  11. package/__tests__/NotifierMUTests.tsx +217 -0
  12. package/__tests__/SelectEx.tsx +26 -0
  13. package/__tests__/tsconfig.json +19 -0
  14. package/babel.config.json +11 -0
  15. package/lib/AuditDisplay.d.ts +33 -0
  16. package/lib/AuditDisplay.js +52 -0
  17. package/lib/AutocompleteExtendedProps.d.ts +64 -0
  18. package/lib/AutocompleteExtendedProps.js +1 -0
  19. package/lib/BackButton.d.ts +13 -0
  20. package/lib/BackButton.js +33 -0
  21. package/lib/BridgeCloseButton.d.ts +23 -0
  22. package/lib/BridgeCloseButton.js +32 -0
  23. package/lib/ButtonLink.d.ts +17 -0
  24. package/lib/ButtonLink.js +19 -0
  25. package/lib/ComboBox.d.ts +38 -0
  26. package/lib/ComboBox.js +108 -0
  27. package/lib/CountdownButton.d.ts +23 -0
  28. package/lib/CountdownButton.js +81 -0
  29. package/lib/CustomFabProps.d.ts +27 -0
  30. package/lib/CustomFabProps.js +1 -0
  31. package/lib/DataGridEx.d.ts +94 -0
  32. package/lib/DataGridEx.js +329 -0
  33. package/lib/DataGridRenderers.d.ts +22 -0
  34. package/lib/DataGridRenderers.js +99 -0
  35. package/lib/DialogButton.d.ts +54 -0
  36. package/lib/DialogButton.js +45 -0
  37. package/lib/DnDList.d.ts +87 -0
  38. package/lib/DnDList.js +153 -0
  39. package/lib/DraggablePaperComponent.d.ts +8 -0
  40. package/lib/DraggablePaperComponent.js +12 -0
  41. package/lib/EmailInput.d.ts +11 -0
  42. package/lib/EmailInput.js +15 -0
  43. package/lib/FabBox.d.ts +21 -0
  44. package/lib/FabBox.js +31 -0
  45. package/lib/FlexBox.d.ts +14 -0
  46. package/lib/FlexBox.js +18 -0
  47. package/lib/GridDataFormat.d.ts +10 -0
  48. package/lib/GridDataFormat.js +43 -0
  49. package/lib/IconButtonLink.d.ts +17 -0
  50. package/lib/IconButtonLink.js +16 -0
  51. package/lib/InputField.d.ts +21 -0
  52. package/lib/InputField.js +39 -0
  53. package/lib/ItemList.d.ts +56 -0
  54. package/lib/ItemList.js +69 -0
  55. package/lib/ListItemRightIcon.d.ts +4 -0
  56. package/lib/ListItemRightIcon.js +8 -0
  57. package/lib/ListMoreDisplay.d.ts +35 -0
  58. package/lib/ListMoreDisplay.js +99 -0
  59. package/lib/LoadingButton.d.ts +16 -0
  60. package/lib/LoadingButton.js +41 -0
  61. package/lib/MUGlobal.d.ts +102 -0
  62. package/lib/MUGlobal.js +184 -0
  63. package/lib/MaskInput.d.ts +34 -0
  64. package/lib/MaskInput.js +43 -0
  65. package/lib/MobileListItemRenderer.d.ts +17 -0
  66. package/lib/MobileListItemRenderer.js +35 -0
  67. package/lib/MoreFab.d.ts +45 -0
  68. package/lib/MoreFab.js +95 -0
  69. package/lib/NotifierMU.d.ts +47 -0
  70. package/lib/NotifierMU.js +387 -0
  71. package/lib/NotifierPromptProps.d.ts +22 -0
  72. package/lib/NotifierPromptProps.js +1 -0
  73. package/lib/OptionGroup.d.ts +58 -0
  74. package/lib/OptionGroup.js +81 -0
  75. package/lib/PList.d.ts +15 -0
  76. package/lib/PList.js +12 -0
  77. package/lib/ProgressCount.d.ts +44 -0
  78. package/lib/ProgressCount.js +79 -0
  79. package/lib/PullToRefreshUI.d.ts +9 -0
  80. package/lib/PullToRefreshUI.js +18 -0
  81. package/lib/RLink.d.ts +14 -0
  82. package/lib/RLink.js +37 -0
  83. package/lib/ResponsibleContainer.d.ts +87 -0
  84. package/lib/ResponsibleContainer.js +156 -0
  85. package/lib/ScrollTopFab.d.ts +7 -0
  86. package/lib/ScrollTopFab.js +25 -0
  87. package/lib/ScrollerListEx.d.ts +81 -0
  88. package/lib/ScrollerListEx.js +167 -0
  89. package/lib/SearchBar.d.ts +29 -0
  90. package/lib/SearchBar.js +260 -0
  91. package/lib/SearchField.d.ts +21 -0
  92. package/lib/SearchField.js +39 -0
  93. package/lib/SearchOptionGroup.d.ts +9 -0
  94. package/lib/SearchOptionGroup.js +14 -0
  95. package/lib/SelectBool.d.ts +13 -0
  96. package/lib/SelectBool.js +22 -0
  97. package/lib/SelectEx.d.ts +50 -0
  98. package/lib/SelectEx.js +156 -0
  99. package/lib/ShowDataComparison.d.ts +20 -0
  100. package/lib/ShowDataComparison.js +58 -0
  101. package/lib/Switch.d.ts +29 -0
  102. package/lib/Switch.js +34 -0
  103. package/lib/SwitchAnt.d.ts +25 -0
  104. package/lib/SwitchAnt.js +40 -0
  105. package/lib/TabBox.d.ts +54 -0
  106. package/lib/TabBox.js +31 -0
  107. package/lib/TableEx.d.ts +65 -0
  108. package/lib/TableEx.js +270 -0
  109. package/lib/TextFieldEx.d.ts +101 -0
  110. package/lib/TextFieldEx.js +126 -0
  111. package/lib/Tiplist.d.ts +18 -0
  112. package/lib/Tiplist.js +157 -0
  113. package/lib/TooltipClick.d.ts +15 -0
  114. package/lib/TooltipClick.js +40 -0
  115. package/lib/UserAvatar.d.ts +24 -0
  116. package/lib/UserAvatar.js +25 -0
  117. package/lib/UserAvatarEditor.d.ts +53 -0
  118. package/lib/UserAvatarEditor.js +129 -0
  119. package/lib/app/CommonApp.d.ts +38 -0
  120. package/lib/app/CommonApp.js +149 -0
  121. package/lib/app/IServiceAppSettings.d.ts +11 -0
  122. package/lib/app/IServiceAppSettings.js +1 -0
  123. package/lib/app/IServicePage.d.ts +6 -0
  124. package/lib/app/IServicePage.js +1 -0
  125. package/lib/app/IServiceUser.d.ts +14 -0
  126. package/lib/app/IServiceUser.js +1 -0
  127. package/lib/app/ISmartERPUser.d.ts +14 -0
  128. package/lib/app/ISmartERPUser.js +1 -0
  129. package/lib/app/Labels.d.ts +65 -0
  130. package/lib/app/Labels.js +62 -0
  131. package/lib/app/ReactApp.d.ts +195 -0
  132. package/lib/app/ReactApp.js +296 -0
  133. package/lib/app/ServiceApp.d.ts +78 -0
  134. package/lib/app/ServiceApp.js +244 -0
  135. package/lib/index.d.ts +74 -0
  136. package/lib/index.js +74 -0
  137. package/lib/pages/CommonPage.d.ts +11 -0
  138. package/lib/pages/CommonPage.js +60 -0
  139. package/lib/pages/CommonPageProps.d.ts +59 -0
  140. package/lib/pages/CommonPageProps.js +1 -0
  141. package/lib/pages/DataGridPage.d.ts +9 -0
  142. package/lib/pages/DataGridPage.js +79 -0
  143. package/lib/pages/DataGridPageProps.d.ts +17 -0
  144. package/lib/pages/DataGridPageProps.js +1 -0
  145. package/lib/pages/EditPage.d.ts +33 -0
  146. package/lib/pages/EditPage.js +29 -0
  147. package/lib/pages/FixedListPage.d.ts +15 -0
  148. package/lib/pages/FixedListPage.js +70 -0
  149. package/lib/pages/ListPage.d.ts +9 -0
  150. package/lib/pages/ListPage.js +50 -0
  151. package/lib/pages/ListPageProps.d.ts +7 -0
  152. package/lib/pages/ListPageProps.js +1 -0
  153. package/lib/pages/ResponsivePage.d.ts +9 -0
  154. package/lib/pages/ResponsivePage.js +45 -0
  155. package/lib/pages/ResponsivePageProps.d.ts +39 -0
  156. package/lib/pages/ResponsivePageProps.js +1 -0
  157. package/lib/pages/SearchPageProps.d.ts +30 -0
  158. package/lib/pages/SearchPageProps.js +1 -0
  159. package/lib/pages/TablePage.d.ts +9 -0
  160. package/lib/pages/TablePage.js +69 -0
  161. package/lib/pages/TablePageProps.d.ts +7 -0
  162. package/lib/pages/TablePageProps.js +1 -0
  163. package/lib/pages/ViewPage.d.ts +66 -0
  164. package/lib/pages/ViewPage.js +105 -0
  165. package/lib/texts/DateText.d.ts +34 -0
  166. package/lib/texts/DateText.js +25 -0
  167. package/lib/texts/MoneyText.d.ts +21 -0
  168. package/lib/texts/MoneyText.js +14 -0
  169. package/lib/texts/NumberText.d.ts +25 -0
  170. package/lib/texts/NumberText.js +14 -0
  171. package/package.json +97 -0
  172. package/src/AuditDisplay.tsx +114 -0
  173. package/src/AutocompleteExtendedProps.ts +83 -0
  174. package/src/BackButton.tsx +55 -0
  175. package/src/BridgeCloseButton.tsx +69 -0
  176. package/src/ButtonLink.tsx +32 -0
  177. package/src/ComboBox.tsx +251 -0
  178. package/src/CountdownButton.tsx +119 -0
  179. package/src/CustomFabProps.ts +32 -0
  180. package/src/DataGridEx.tsx +713 -0
  181. package/src/DataGridRenderers.tsx +140 -0
  182. package/src/DialogButton.tsx +163 -0
  183. package/src/DnDList.tsx +344 -0
  184. package/src/DraggablePaperComponent.tsx +19 -0
  185. package/src/EmailInput.tsx +24 -0
  186. package/src/FabBox.tsx +51 -0
  187. package/src/FlexBox.tsx +20 -0
  188. package/src/GridDataFormat.tsx +77 -0
  189. package/src/IconButtonLink.tsx +29 -0
  190. package/src/InputField.tsx +82 -0
  191. package/src/ItemList.tsx +204 -0
  192. package/src/ListItemRightIcon.tsx +9 -0
  193. package/src/ListMoreDisplay.tsx +205 -0
  194. package/src/LoadingButton.tsx +75 -0
  195. package/src/MUGlobal.ts +220 -0
  196. package/src/MaskInput.tsx +107 -0
  197. package/src/MobileListItemRenderer.tsx +79 -0
  198. package/src/MoreFab.tsx +211 -0
  199. package/src/NotifierMU.tsx +654 -0
  200. package/src/NotifierPromptProps.ts +24 -0
  201. package/src/OptionGroup.tsx +223 -0
  202. package/src/PList.tsx +27 -0
  203. package/src/ProgressCount.tsx +166 -0
  204. package/src/PullToRefreshUI.tsx +21 -0
  205. package/src/RLink.tsx +64 -0
  206. package/src/ResponsibleContainer.tsx +394 -0
  207. package/src/ScrollTopFab.tsx +34 -0
  208. package/src/ScrollerListEx.tsx +387 -0
  209. package/src/SearchBar.tsx +396 -0
  210. package/src/SearchField.tsx +82 -0
  211. package/src/SearchOptionGroup.tsx +31 -0
  212. package/src/SelectBool.tsx +33 -0
  213. package/src/SelectEx.tsx +290 -0
  214. package/src/ShowDataComparison.tsx +106 -0
  215. package/src/Switch.tsx +94 -0
  216. package/src/SwitchAnt.tsx +95 -0
  217. package/src/TabBox.tsx +118 -0
  218. package/src/TableEx.tsx +558 -0
  219. package/src/TextFieldEx.tsx +249 -0
  220. package/src/Tiplist.tsx +303 -0
  221. package/src/TooltipClick.tsx +84 -0
  222. package/src/UserAvatar.tsx +64 -0
  223. package/src/UserAvatarEditor.tsx +287 -0
  224. package/src/app/CommonApp.ts +223 -0
  225. package/src/app/IServiceAppSettings.ts +13 -0
  226. package/src/app/IServicePage.ts +6 -0
  227. package/src/app/IServiceUser.ts +17 -0
  228. package/src/app/ISmartERPUser.ts +16 -0
  229. package/src/app/Labels.ts +77 -0
  230. package/src/app/ReactApp.ts +504 -0
  231. package/src/app/ServiceApp.ts +352 -0
  232. package/src/index.ts +77 -0
  233. package/src/pages/CommonPage.tsx +128 -0
  234. package/src/pages/CommonPageProps.ts +70 -0
  235. package/src/pages/DataGridPage.tsx +140 -0
  236. package/src/pages/DataGridPageProps.ts +24 -0
  237. package/src/pages/EditPage.tsx +114 -0
  238. package/src/pages/FixedListPage.tsx +141 -0
  239. package/src/pages/ListPage.tsx +90 -0
  240. package/src/pages/ListPageProps.ts +12 -0
  241. package/src/pages/ResponsivePage.tsx +68 -0
  242. package/src/pages/ResponsivePageProps.ts +57 -0
  243. package/src/pages/SearchPageProps.ts +39 -0
  244. package/src/pages/TablePage.tsx +126 -0
  245. package/src/pages/TablePageProps.ts +12 -0
  246. package/src/pages/ViewPage.tsx +282 -0
  247. package/src/texts/DateText.tsx +74 -0
  248. package/src/texts/MoneyText.tsx +49 -0
  249. package/src/texts/NumberText.tsx +40 -0
  250. 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
+ }
@@ -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
+ }
@@ -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,9 @@
1
+ import { ListItemIcon, styled } from '@mui/material';
2
+
3
+ /**
4
+ * List item right icon component
5
+ */
6
+ export const ListItemRightIcon = styled(ListItemIcon)(({ theme }) => ({
7
+ minWidth: '20px!important',
8
+ paddingLeft: theme.spacing(2)
9
+ }));
@@ -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
+ }