@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
@@ -0,0 +1,249 @@
1
+ import React from 'react';
2
+ import {
3
+ IconButton,
4
+ InputAdornment,
5
+ TextField,
6
+ TextFieldProps
7
+ } from '@mui/material';
8
+ import { MUGlobal } from './MUGlobal';
9
+ import { Clear, Visibility } from '@mui/icons-material';
10
+ import { Keyboard } from '@etsoo/shared';
11
+ import { useCombinedRefs, useDelayedExecutor } from '@etsoo/react';
12
+
13
+ /**
14
+ * Extended text field props
15
+ */
16
+ export type TextFieldExProps = TextFieldProps & {
17
+ /**
18
+ * Change delay (ms) to avoid repeatly dispatch onChange
19
+ */
20
+ changeDelay?: number;
21
+
22
+ /**
23
+ * On enter click
24
+ */
25
+ onEnter?: React.KeyboardEventHandler<HTMLDivElement>;
26
+
27
+ /**
28
+ * Is the field read only?
29
+ */
30
+ readOnly?: boolean;
31
+
32
+ /**
33
+ * Show clear button
34
+ */
35
+ showClear?: boolean;
36
+
37
+ /**
38
+ * Show password button
39
+ */
40
+ showPassword?: boolean;
41
+ };
42
+
43
+ /**
44
+ * Extended text field methods
45
+ */
46
+ export interface TextFieldExMethods {
47
+ /**
48
+ * Set error
49
+ * @param error Error
50
+ */
51
+ setError(error: React.ReactNode): void;
52
+ }
53
+
54
+ export const TextFieldEx = React.forwardRef<
55
+ TextFieldExMethods,
56
+ TextFieldExProps
57
+ >((props, ref) => {
58
+ // Destructure
59
+ const {
60
+ changeDelay,
61
+ error,
62
+ fullWidth = true,
63
+ helperText,
64
+ InputProps = {},
65
+ onChange,
66
+ onKeyPress,
67
+ onEnter,
68
+ inputRef,
69
+ readOnly,
70
+ showClear,
71
+ showPassword,
72
+ type,
73
+ variant = MUGlobal.textFieldVariant,
74
+ ...rest
75
+ } = props;
76
+
77
+ // State
78
+ const [errorText, updateErrorText] = React.useState<React.ReactNode>();
79
+ const [empty, updateEmpty] = React.useState<boolean>(true);
80
+
81
+ // Read only
82
+ if (readOnly != null) InputProps.readOnly = readOnly;
83
+
84
+ // Calculate
85
+ let errorEx = error;
86
+ let helperTextEx = helperText;
87
+ if (errorText != null) {
88
+ errorEx = true;
89
+ helperTextEx = errorText;
90
+ }
91
+
92
+ let typeEx = showPassword ? 'password' : type;
93
+
94
+ let input: HTMLInputElement | undefined;
95
+ const localRef = (ref: HTMLInputElement) => {
96
+ input = ref;
97
+
98
+ if (input.value !== '') {
99
+ updateEmpty(false);
100
+ }
101
+ };
102
+
103
+ const clearClick = () => {
104
+ if (input != null) {
105
+ input.value = '';
106
+ input.focus();
107
+ }
108
+
109
+ if (errorText != null) {
110
+ // Reset
111
+ updateErrorText(undefined);
112
+ }
113
+
114
+ updateEmpty(true);
115
+ };
116
+
117
+ const preventDefault = (e: React.TouchEvent | React.MouseEvent) => {
118
+ // Prevent long press
119
+ if (e.isPropagationStopped()) e.stopPropagation();
120
+
121
+ if (e.isDefaultPrevented()) e.preventDefault();
122
+ };
123
+
124
+ const touchStart = (e: React.TouchEvent | React.MouseEvent) => {
125
+ // Show the password
126
+ if (input) {
127
+ input.blur();
128
+ input.type = 'text';
129
+ }
130
+ preventDefault(e);
131
+ };
132
+
133
+ const touchEnd = (e: React.TouchEvent | React.MouseEvent) => {
134
+ // Show the password
135
+ if (input) input.type = 'password';
136
+ preventDefault(e);
137
+ };
138
+
139
+ // Show password and/or clear button
140
+ if (!empty && (showPassword || showClear)) {
141
+ InputProps.endAdornment = (
142
+ <InputAdornment position="end">
143
+ {showPassword && (
144
+ <IconButton
145
+ tabIndex={-1}
146
+ onContextMenu={(event) => event.preventDefault()}
147
+ onMouseDown={touchStart}
148
+ onMouseUp={touchEnd}
149
+ onTouchStart={touchStart}
150
+ onTouchCancel={touchEnd}
151
+ onTouchEnd={touchEnd}
152
+ >
153
+ <Visibility />
154
+ </IconButton>
155
+ )}
156
+ {showClear && (
157
+ <IconButton onClick={clearClick} tabIndex={-1}>
158
+ <Clear />
159
+ </IconButton>
160
+ )}
161
+ </InputAdornment>
162
+ );
163
+ }
164
+
165
+ // Extend key precess
166
+ const onKeyPressEx =
167
+ onEnter == null
168
+ ? onKeyPress
169
+ : (e: React.KeyboardEvent<HTMLDivElement>) => {
170
+ if (e.key === Keyboard.Keys.Enter) {
171
+ // Enter press callback
172
+ onEnter(e);
173
+ }
174
+
175
+ if (!e.isDefaultPrevented && onKeyPress != null) {
176
+ // Common press callback
177
+ onKeyPress(e);
178
+ }
179
+ };
180
+
181
+ React.useImperativeHandle(
182
+ ref,
183
+ () => ({
184
+ /**
185
+ * Set error
186
+ * @param error Error
187
+ */
188
+ setError(error: React.ReactNode): void {
189
+ updateErrorText(error);
190
+ }
191
+ }),
192
+ []
193
+ );
194
+
195
+ const isMounted = React.useRef(true);
196
+ const delayed =
197
+ onChange != null && changeDelay != null && changeDelay >= 1
198
+ ? useDelayedExecutor(onChange, changeDelay)
199
+ : undefined;
200
+
201
+ const onChangeEx = (
202
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
203
+ ) => {
204
+ if (errorText != null) {
205
+ // Reset
206
+ updateErrorText(undefined);
207
+ }
208
+
209
+ if (showClear || showPassword) {
210
+ if (event.target.value === '') {
211
+ updateEmpty(true);
212
+ } else if (empty) {
213
+ updateEmpty(false);
214
+ }
215
+ }
216
+
217
+ if (onChange == null) return;
218
+
219
+ if (changeDelay == null || changeDelay < 1) {
220
+ onChange(event);
221
+ return;
222
+ }
223
+
224
+ delayed?.call(undefined, event);
225
+ };
226
+
227
+ React.useEffect(() => {
228
+ return () => {
229
+ isMounted.current = false;
230
+ delayed?.clear();
231
+ };
232
+ }, []);
233
+
234
+ // Textfield
235
+ return (
236
+ <TextField
237
+ error={errorEx}
238
+ fullWidth={fullWidth}
239
+ helperText={helperTextEx}
240
+ inputRef={useCombinedRefs(inputRef, localRef)}
241
+ InputProps={InputProps}
242
+ onChange={onChangeEx}
243
+ onKeyPress={onKeyPressEx}
244
+ type={typeEx}
245
+ variant={variant}
246
+ {...rest}
247
+ />
248
+ );
249
+ });
@@ -0,0 +1,303 @@
1
+ import { ReactUtils, useDelayedExecutor } from '@etsoo/react';
2
+ import { DataTypes, IdDefaultType, ListType } from '@etsoo/shared';
3
+ import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
4
+ import React from 'react';
5
+ import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
6
+ import { InputField } from './InputField';
7
+ import { SearchField } from './SearchField';
8
+
9
+ /**
10
+ * Tiplist props
11
+ */
12
+ export type TiplistProps<T extends object, D extends DataTypes.Keys<T>> = Omit<
13
+ AutocompleteExtendedProps<T, D>,
14
+ 'open'
15
+ > & {
16
+ /**
17
+ * Load data callback
18
+ */
19
+ loadData: (
20
+ keyword?: string,
21
+ id?: T[D]
22
+ ) => PromiseLike<T[] | null | undefined>;
23
+ };
24
+
25
+ // Multiple states
26
+ interface States<T extends object> {
27
+ open: boolean;
28
+ options: T[];
29
+ value?: T | null;
30
+ loading?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Tiplist
35
+ * @param props Props
36
+ * @returns Component
37
+ */
38
+ export function Tiplist<
39
+ T extends object = ListType,
40
+ D extends DataTypes.Keys<T> = IdDefaultType<T>
41
+ >(props: TiplistProps<T, D>) {
42
+ // Destruct
43
+ const {
44
+ search = false,
45
+ idField = 'id' as D,
46
+ idValue,
47
+ inputAutoComplete = 'off',
48
+ inputError,
49
+ inputHelperText,
50
+ inputMargin,
51
+ inputOnChange,
52
+ inputRequired,
53
+ inputVariant,
54
+ label,
55
+ loadData,
56
+ defaultValue,
57
+ value,
58
+ name,
59
+ readOnly,
60
+ onChange,
61
+ openOnFocus = true,
62
+ sx = { minWidth: '180px' },
63
+ ...rest
64
+ } = props;
65
+
66
+ // Value input ref
67
+ const inputRef = React.createRef<HTMLInputElement>();
68
+
69
+ // Local value
70
+ let localValue = value ?? defaultValue;
71
+
72
+ // One time calculation for input's default value (uncontrolled)
73
+ const localIdValue =
74
+ idValue ?? DataTypes.getValue(localValue, idField as any);
75
+
76
+ // Changable states
77
+ const [states, stateUpdate] = React.useReducer(
78
+ (currentState: States<T>, newState: Partial<States<T>>) => {
79
+ return { ...currentState, ...newState };
80
+ },
81
+ {
82
+ // Loading unknown
83
+ open: false,
84
+ options: [],
85
+ value: null
86
+ }
87
+ );
88
+
89
+ // Input value
90
+ const inputValue = React.useMemo(
91
+ () => states.value && states.value[idField],
92
+ [states.value]
93
+ );
94
+
95
+ React.useEffect(() => {
96
+ if (localValue != null) stateUpdate({ value: localValue });
97
+ }, [localValue]);
98
+
99
+ // State
100
+ const [state] = React.useState<{
101
+ idLoaded?: boolean;
102
+ idSet?: boolean;
103
+ }>({});
104
+ const isMounted = React.useRef(true);
105
+
106
+ // Add readOnly
107
+ const addReadOnly = (params: AutocompleteRenderInputParams) => {
108
+ if (readOnly != null) {
109
+ Object.assign(params, { readOnly });
110
+ }
111
+
112
+ // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
113
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
114
+ Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
115
+
116
+ return params;
117
+ };
118
+
119
+ // Change handler
120
+ const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
121
+ // Stop processing with auto trigger event
122
+ if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
123
+ stateUpdate({ options: [] });
124
+ return;
125
+ }
126
+
127
+ // Stop bubble
128
+ event.stopPropagation();
129
+
130
+ // Call with delay
131
+ delayed.call(undefined, event.currentTarget.value);
132
+ };
133
+
134
+ // Directly load data
135
+ const loadDataDirect = (keyword?: string, id?: T[D]) => {
136
+ // Reset options
137
+ // setOptions([]);
138
+
139
+ if (id == null) {
140
+ // Reset real value
141
+ const input = inputRef.current;
142
+
143
+ if (input && input.value !== '') {
144
+ // Different value, trigger change event
145
+ ReactUtils.triggerChange(input, '', false);
146
+ }
147
+
148
+ if (states.options.length > 0) {
149
+ // Reset options
150
+ stateUpdate({ options: [] });
151
+ }
152
+ }
153
+
154
+ // Loading indicator
155
+ if (!states.loading) stateUpdate({ loading: true });
156
+
157
+ // Load list
158
+ loadData(keyword, id).then((options) => {
159
+ if (!isMounted.current) return;
160
+
161
+ // Indicates loading completed
162
+ stateUpdate({
163
+ loading: false,
164
+ ...(options != null && { options })
165
+ });
166
+ });
167
+ };
168
+
169
+ const delayed = useDelayedExecutor(loadDataDirect, 480);
170
+
171
+ const setInputValue = (value: T | null) => {
172
+ stateUpdate({ value });
173
+
174
+ // Input value
175
+ const input = inputRef.current;
176
+ if (input) {
177
+ // Update value
178
+ const newValue = DataTypes.getStringValue(value, idField) ?? '';
179
+ if (newValue !== input.value) {
180
+ // Different value, trigger change event
181
+ ReactUtils.triggerChange(input, newValue, false);
182
+ }
183
+ }
184
+ };
185
+
186
+ if (localIdValue != null && (localIdValue as any) !== '') {
187
+ if (state.idLoaded) {
188
+ // Set default
189
+ if (!state.idSet && states.options.length == 1) {
190
+ stateUpdate({ value: states.options[0] });
191
+ state.idSet = true;
192
+ }
193
+ } else {
194
+ // Load id data
195
+ loadDataDirect(undefined, localIdValue);
196
+ state.idLoaded = true;
197
+ }
198
+ }
199
+
200
+ React.useEffect(() => {
201
+ return () => {
202
+ isMounted.current = false;
203
+ delayed.clear();
204
+ };
205
+ }, []);
206
+
207
+ // Layout
208
+ return (
209
+ <div>
210
+ <input
211
+ ref={inputRef}
212
+ data-reset="true"
213
+ type="text"
214
+ style={{ display: 'none' }}
215
+ name={name}
216
+ value={`${inputValue ?? ''}`}
217
+ readOnly
218
+ onChange={inputOnChange}
219
+ />
220
+ {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
221
+ <Autocomplete<T, undefined, false, false>
222
+ filterOptions={(options, _state) => options}
223
+ value={states.value}
224
+ options={states.options}
225
+ onChange={(event, value, reason, details) => {
226
+ // Set value
227
+ setInputValue(value);
228
+
229
+ // Custom
230
+ if (onChange != null)
231
+ onChange(event, value, reason, details);
232
+
233
+ // For clear case
234
+ if (reason === 'clear') {
235
+ stateUpdate({ options: [] });
236
+ loadDataDirect();
237
+ }
238
+ }}
239
+ open={states.open}
240
+ openOnFocus={openOnFocus}
241
+ onOpen={() => {
242
+ // Should load
243
+ const loading = states.loading
244
+ ? true
245
+ : states.options.length === 0;
246
+
247
+ stateUpdate({ open: true, loading });
248
+
249
+ // If not loading
250
+ if (loading)
251
+ loadDataDirect(
252
+ undefined,
253
+ states.value == null
254
+ ? undefined
255
+ : states.value[idField]
256
+ );
257
+ }}
258
+ onClose={() => {
259
+ stateUpdate({
260
+ open: false,
261
+ ...(!states.value && { options: [] })
262
+ });
263
+ }}
264
+ loading={states.loading}
265
+ sx={sx}
266
+ renderInput={(params) =>
267
+ search ? (
268
+ <SearchField
269
+ onChange={changeHandle}
270
+ {...params}
271
+ readOnly={readOnly}
272
+ label={label}
273
+ name={name + 'Input'}
274
+ margin={inputMargin}
275
+ variant={inputVariant}
276
+ required={inputRequired}
277
+ autoComplete={inputAutoComplete}
278
+ error={inputError}
279
+ helperText={inputHelperText}
280
+ />
281
+ ) : (
282
+ <InputField
283
+ onChange={changeHandle}
284
+ {...addReadOnly(params)}
285
+ label={label}
286
+ name={name + 'Input'}
287
+ margin={inputMargin}
288
+ variant={inputVariant}
289
+ required={inputRequired}
290
+ autoComplete={inputAutoComplete}
291
+ error={inputError}
292
+ helperText={inputHelperText}
293
+ />
294
+ )
295
+ }
296
+ isOptionEqualToValue={(option: T, value: T) =>
297
+ option[idField] === value[idField]
298
+ }
299
+ {...rest}
300
+ />
301
+ </div>
302
+ );
303
+ }
@@ -0,0 +1,84 @@
1
+ import { useDelayedExecutor } from '@etsoo/react';
2
+ import { ClickAwayListener, Tooltip, TooltipProps } from '@mui/material';
3
+ import React from 'react';
4
+
5
+ /**
6
+ * Tooltip with click visibility props
7
+ */
8
+ export interface TooltipClickProps
9
+ extends Omit<
10
+ TooltipProps,
11
+ 'children' | 'open' | 'disableFocusListener' | 'disableTouchListener'
12
+ > {
13
+ children: (
14
+ openTooltip: (newTitle?: string) => void
15
+ ) => React.ReactElement<any, any>;
16
+
17
+ disableHoverListener?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Tooltip with click visibility
22
+ * @param props Props
23
+ * @returns Component
24
+ */
25
+ export function TooltipClick(props: TooltipClickProps) {
26
+ // Destruct
27
+ // leaveDelay set to 5 seconds to hide the tooltip automatically
28
+ const {
29
+ children,
30
+ disableHoverListener = true,
31
+ leaveDelay = 5000,
32
+ onClose,
33
+ title,
34
+ ...rest
35
+ } = props;
36
+
37
+ // State
38
+ const [localTitle, setTitle] = React.useState(title);
39
+ const [open, setOpen] = React.useState(false);
40
+
41
+ const delayed =
42
+ leaveDelay > 0
43
+ ? useDelayedExecutor(() => setOpen(false), leaveDelay)
44
+ : undefined;
45
+
46
+ // Callback for open the tooltip
47
+ const openTooltip = (newTitle?: string) => {
48
+ setOpen(true);
49
+ if (newTitle) setTitle(newTitle);
50
+ delayed?.call();
51
+ };
52
+
53
+ React.useEffect(() => {
54
+ return () => {
55
+ delayed?.clear();
56
+ };
57
+ }, []);
58
+
59
+ // Layout
60
+ return (
61
+ <ClickAwayListener onClickAway={() => setOpen(false)}>
62
+ <Tooltip
63
+ PopperProps={{
64
+ disablePortal: true
65
+ }}
66
+ onClose={(event) => {
67
+ setOpen(false);
68
+ if (onClose) onClose(event);
69
+ }}
70
+ title={localTitle}
71
+ open={open}
72
+ disableFocusListener
73
+ disableTouchListener
74
+ disableHoverListener={disableHoverListener}
75
+ onMouseOver={
76
+ disableHoverListener ? undefined : () => setOpen(true)
77
+ }
78
+ {...rest}
79
+ >
80
+ {children(openTooltip)}
81
+ </Tooltip>
82
+ </ClickAwayListener>
83
+ );
84
+ }
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { Avatar } from '@mui/material';
3
+ import { BusinessUtils } from '@etsoo/appscript';
4
+ import { globalApp } from './app/ReactApp';
5
+
6
+ /**
7
+ * User avatar props
8
+ */
9
+ export interface UserAvatarProps {
10
+ /**
11
+ * Photo src
12
+ */
13
+ src?: string;
14
+
15
+ /**
16
+ * Format title
17
+ */
18
+ formatTitle?: (title?: string) => string;
19
+
20
+ /**
21
+ * Title of the user
22
+ */
23
+ title?: string;
24
+ }
25
+
26
+ /**
27
+ * User avatar
28
+ * @param props Props
29
+ * @returns Component
30
+ */
31
+ export function UserAvatar(props: UserAvatarProps) {
32
+ // Destruct
33
+ const {
34
+ src,
35
+ title,
36
+ formatTitle = (title?: string) => {
37
+ return BusinessUtils.formatAvatarTitle(
38
+ title,
39
+ 3,
40
+ typeof globalApp === 'undefined'
41
+ ? 'ME'
42
+ : globalApp.get<string>('me')
43
+ );
44
+ }
45
+ } = props;
46
+
47
+ // Format
48
+ const fTitle = formatTitle(title);
49
+ const count = fTitle.length;
50
+
51
+ return (
52
+ <Avatar
53
+ title={title}
54
+ src={src}
55
+ sx={{
56
+ width: 48,
57
+ height: 32,
58
+ fontSize: count <= 2 ? '15px' : '12px'
59
+ }}
60
+ >
61
+ {fTitle}
62
+ </Avatar>
63
+ );
64
+ }