@etsoo/materialui 1.2.58 → 1.2.60

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/lib/ComboBox.d.ts CHANGED
@@ -29,6 +29,14 @@ export type ComboBoxProps<T extends object = ListType, D extends DataTypes.Keys<
29
29
  * Array of options.
30
30
  */
31
31
  options?: ReadonlyArray<T>;
32
+ /**
33
+ * Add label
34
+ */
35
+ addLabel?: string;
36
+ /**
37
+ * On add callback
38
+ */
39
+ onAdd?: (callback: () => void) => void;
32
40
  };
33
41
  /**
34
42
  * ComboBox
package/lib/ComboBox.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Keyboard } from "@etsoo/shared";
2
- import { Autocomplete } from "@mui/material";
2
+ import { Autocomplete, IconButton, Stack } from "@mui/material";
3
3
  import React from "react";
4
4
  import { Utils as SharedUtils } from "@etsoo/shared";
5
5
  import { ReactUtils } from "@etsoo/react";
6
+ import AddIcon from "@mui/icons-material/Add";
6
7
  import { SearchField } from "./SearchField";
7
8
  import { InputField } from "./InputField";
8
9
  import { globalApp } from "./app/ReactApp";
@@ -13,9 +14,9 @@ import { globalApp } from "./app/ReactApp";
13
14
  */
14
15
  export function ComboBox(props) {
15
16
  // Labels
16
- const labels = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "open");
17
+ const labels = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "open", "add");
17
18
  // Destruct
18
- const { search = false, autoAddBlankItem = search, idField = "id", idValue, inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputVariant, defaultValue, label, labelField = "label", loadData, onLoadData, name, inputAutoComplete = "off", options, dataReadonly = true, readOnly, onChange, onValueChange, openOnFocus = true, value, disableCloseOnSelect = false, getOptionLabel = (option) => `${option[labelField]}`, sx = { minWidth: "150px" }, noOptionsText = labels === null || labels === void 0 ? void 0 : labels.noOptions, loadingText = labels === null || labels === void 0 ? void 0 : labels.loading, openText = labels === null || labels === void 0 ? void 0 : labels.open, ...rest } = props;
19
+ const { search = false, autoAddBlankItem = search, idField = "id", idValue, inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputVariant, defaultValue, label, labelField = "label", loadData, onLoadData, name, inputAutoComplete = "off", options, dataReadonly = true, readOnly, onChange, onValueChange, openOnFocus = true, value, disableCloseOnSelect = false, getOptionLabel = (option) => `${option[labelField]}`, sx = { minWidth: "150px", flexGrow: 2 }, noOptionsText = labels === null || labels === void 0 ? void 0 : labels.noOptions, loadingText = labels === null || labels === void 0 ? void 0 : labels.loading, openText = labels === null || labels === void 0 ? void 0 : labels.open, addLabel = labels === null || labels === void 0 ? void 0 : labels.add, onAdd, ...rest } = props;
19
20
  // Value input ref
20
21
  const inputRef = React.createRef();
21
22
  // Options state
@@ -88,8 +89,8 @@ export function ComboBox(props) {
88
89
  }
89
90
  }
90
91
  };
91
- React.useEffect(() => {
92
- if (propertyWay || loadData == null)
92
+ const doLoadData = React.useCallback(() => {
93
+ if (loadData == null)
93
94
  return;
94
95
  loadData().then((result) => {
95
96
  if (result == null || !isMounted.current)
@@ -101,7 +102,12 @@ export function ComboBox(props) {
101
102
  }
102
103
  setOptions(result);
103
104
  });
104
- }, [propertyWay, autoAddBlankItem, idField, labelField]);
105
+ }, [loadData, autoAddBlankItem, idField, labelField]);
106
+ React.useEffect(() => {
107
+ if (propertyWay)
108
+ return;
109
+ doLoadData();
110
+ }, [propertyWay, doLoadData]);
105
111
  React.useEffect(() => {
106
112
  return () => {
107
113
  isMounted.current = false;
@@ -110,13 +116,18 @@ export function ComboBox(props) {
110
116
  // Layout
111
117
  return (React.createElement("div", null,
112
118
  React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: getValue(stateValue), readOnly: true, onChange: inputOnChange }),
113
- React.createElement(Autocomplete, { value: stateValue, disableCloseOnSelect: disableCloseOnSelect, getOptionLabel: getOptionLabel, isOptionEqualToValue: (option, value) => option[idField] === value[idField], onChange: (event, value, reason, details) => {
114
- // Set value
115
- setInputValue(value);
116
- // Custom
117
- if (onChange != null)
118
- onChange(event, value, reason, details);
119
- if (onValueChange)
120
- onValueChange(value);
121
- }, openOnFocus: openOnFocus, sx: sx, renderInput: (params) => search ? (React.createElement(SearchField, { ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, error: inputError, helperText: inputHelperText })) : (React.createElement(InputField, { ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, error: inputError, helperText: inputHelperText })), options: localOptions, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, ...rest })));
119
+ React.createElement(Stack, { gap: 0.5, direction: "row", width: "100%" },
120
+ React.createElement(Autocomplete, { value: stateValue, disableCloseOnSelect: disableCloseOnSelect, getOptionLabel: getOptionLabel, isOptionEqualToValue: (option, value) => option[idField] === value[idField], onChange: (event, value, reason, details) => {
121
+ // Set value
122
+ setInputValue(value);
123
+ // Custom
124
+ if (onChange != null)
125
+ onChange(event, value, reason, details);
126
+ if (onValueChange)
127
+ onValueChange(value);
128
+ }, openOnFocus: openOnFocus, sx: sx, renderInput: (params) => search ? (React.createElement(SearchField, { ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, error: inputError, helperText: inputHelperText })) : (React.createElement(InputField, { ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, error: inputError, helperText: inputHelperText })), options: localOptions, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, ...rest }),
129
+ onAdd && (React.createElement(IconButton, { size: "small", onClick: () => {
130
+ onAdd(doLoadData);
131
+ }, title: addLabel },
132
+ React.createElement(AddIcon, null))))));
122
133
  }
@@ -0,0 +1,22 @@
1
+ import { ButtonProps } from "@mui/material";
2
+ import React from "react";
3
+ /**
4
+ * File upload button props
5
+ */
6
+ export type FileUploadButtonProps = ButtonProps<"label"> & {
7
+ /**
8
+ * Input field attributes
9
+ */
10
+ inputProps?: Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "hidden">;
11
+ /**
12
+ * Upload files callback
13
+ * @param files Files
14
+ */
15
+ onUploadFiles?: (files: FileList) => void;
16
+ };
17
+ /**
18
+ * File upload button
19
+ * @param props Props
20
+ * @returns Component
21
+ */
22
+ export declare function FileUploadButton(props: FileUploadButtonProps): React.JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { Button } from "@mui/material";
2
+ import React from "react";
3
+ /**
4
+ * File upload button
5
+ * @param props Props
6
+ * @returns Component
7
+ */
8
+ export function FileUploadButton(props) {
9
+ // Destruct
10
+ const { inputProps, onUploadFiles, children, ...rest } = props;
11
+ const { onChange } = inputProps !== null && inputProps !== void 0 ? inputProps : {};
12
+ // Layout
13
+ return (React.createElement(Button, { component: "label", ...rest },
14
+ children,
15
+ React.createElement("input", { type: "file", hidden: true, onChange: (event) => {
16
+ if (onChange)
17
+ onChange(event);
18
+ if (event.isDefaultPrevented())
19
+ return;
20
+ if (onUploadFiles) {
21
+ const files = event.target.files;
22
+ if (files == null || files.length == 0)
23
+ return;
24
+ onUploadFiles(files);
25
+ }
26
+ }, ...inputProps })));
27
+ }
@@ -8,6 +8,7 @@ import DoneIcon from "@mui/icons-material/Done";
8
8
  import RemoveIcon from "@mui/icons-material/Remove";
9
9
  import AddIcon from "@mui/icons-material/Add";
10
10
  import { Labels } from "./app/Labels";
11
+ import { FileUploadButton } from "./FileUploadButton";
11
12
  const defaultState = {
12
13
  scale: 1,
13
14
  rotate: 0
@@ -70,12 +71,9 @@ export function UserAvatarEditor(props) {
70
71
  const handleLoad = () => {
71
72
  setReady(true);
72
73
  };
73
- // Handle file change
74
- const handleFileChange = (event) => {
74
+ // Handle file upload
75
+ const handleFileUpload = (files) => {
75
76
  var _a;
76
- const files = event.target.files;
77
- if (files == null || files.length == 0)
78
- return;
79
77
  // Reset all settings
80
78
  handleReset();
81
79
  // Set new preview image
@@ -140,9 +138,7 @@ export function UserAvatarEditor(props) {
140
138
  // Load the component
141
139
  const AE = React.lazy(() => import("react-avatar-editor"));
142
140
  return (React.createElement(Stack, { direction: "column", spacing: 0.5, width: containerWidth },
143
- React.createElement(Button, { variant: "outlined", size: "medium", component: "label", startIcon: React.createElement(ComputerIcon, null), fullWidth: true },
144
- labels.upload,
145
- React.createElement("input", { id: "fileInput", type: "file", accept: "image/png, image/jpeg", multiple: false, hidden: true, onChange: handleFileChange })),
141
+ React.createElement(FileUploadButton, { variant: "outlined", size: "medium", startIcon: React.createElement(ComputerIcon, null), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { multiple: false, accept: "image/png, image/jpeg" } }, labels.upload),
146
142
  React.createElement(Stack, { direction: "row", spacing: 0.5 },
147
143
  React.createElement(React.Suspense, { fallback: React.createElement(Skeleton, { variant: "rounded", width: width, height: height }) },
148
144
  React.createElement(AE, { ref: ref, border: border, width: width, height: height, onLoadSuccess: handleLoad, image: previewImage !== null && previewImage !== void 0 ? previewImage : "", scale: editorState.scale, rotate: editorState.rotate })),
package/lib/index.d.ts CHANGED
@@ -47,6 +47,7 @@ export * from "./DnDList";
47
47
  export * from "./DraggablePaperComponent";
48
48
  export * from "./EmailInput";
49
49
  export * from "./FabBox";
50
+ export * from "./FileUploadButton";
50
51
  export * from "./FlexBox";
51
52
  export * from "./GridDataFormat";
52
53
  export * from "./HiSelector";
package/lib/index.js CHANGED
@@ -47,6 +47,7 @@ export * from "./DnDList";
47
47
  export * from "./DraggablePaperComponent";
48
48
  export * from "./EmailInput";
49
49
  export * from "./FabBox";
50
+ export * from "./FileUploadButton";
50
51
  export * from "./FlexBox";
51
52
  export * from "./GridDataFormat";
52
53
  export * from "./HiSelector";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.2.58",
3
+ "version": "1.2.60",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -52,7 +52,7 @@
52
52
  "@emotion/styled": "^11.11.0",
53
53
  "@etsoo/appscript": "^1.4.19",
54
54
  "@etsoo/notificationbase": "^1.1.25",
55
- "@etsoo/react": "^1.6.92",
55
+ "@etsoo/react": "^1.6.93",
56
56
  "@etsoo/shared": "^1.2.5",
57
57
  "@mui/icons-material": "^5.11.16",
58
58
  "@mui/material": "^5.13.5",
package/src/ComboBox.tsx CHANGED
@@ -5,10 +5,16 @@ import {
5
5
  LabelDefaultType,
6
6
  ListType
7
7
  } from "@etsoo/shared";
8
- import { Autocomplete, AutocompleteRenderInputParams } from "@mui/material";
8
+ import {
9
+ Autocomplete,
10
+ AutocompleteRenderInputParams,
11
+ IconButton,
12
+ Stack
13
+ } from "@mui/material";
9
14
  import React from "react";
10
15
  import { Utils as SharedUtils } from "@etsoo/shared";
11
16
  import { ReactUtils } from "@etsoo/react";
17
+ import AddIcon from "@mui/icons-material/Add";
12
18
  import { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
13
19
  import { SearchField } from "./SearchField";
14
20
  import { InputField } from "./InputField";
@@ -51,6 +57,16 @@ export type ComboBoxProps<
51
57
  * Array of options.
52
58
  */
53
59
  options?: ReadonlyArray<T>;
60
+
61
+ /**
62
+ * Add label
63
+ */
64
+ addLabel?: string;
65
+
66
+ /**
67
+ * On add callback
68
+ */
69
+ onAdd?: (callback: () => void) => void;
54
70
  };
55
71
 
56
72
  /**
@@ -64,7 +80,7 @@ export function ComboBox<
64
80
  L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
65
81
  >(props: ComboBoxProps<T, D, L>) {
66
82
  // Labels
67
- const labels = globalApp?.getLabels("noOptions", "loading", "open");
83
+ const labels = globalApp?.getLabels("noOptions", "loading", "open", "add");
68
84
 
69
85
  // Destruct
70
86
  const {
@@ -94,10 +110,12 @@ export function ComboBox<
94
110
  value,
95
111
  disableCloseOnSelect = false,
96
112
  getOptionLabel = (option: T) => `${option[labelField]}`,
97
- sx = { minWidth: "150px" },
113
+ sx = { minWidth: "150px", flexGrow: 2 },
98
114
  noOptionsText = labels?.noOptions,
99
115
  loadingText = labels?.loading,
100
116
  openText = labels?.open,
117
+ addLabel = labels?.add,
118
+ onAdd,
101
119
  ...rest
102
120
  } = props;
103
121
 
@@ -189,8 +207,8 @@ export function ComboBox<
189
207
  }
190
208
  };
191
209
 
192
- React.useEffect(() => {
193
- if (propertyWay || loadData == null) return;
210
+ const doLoadData = React.useCallback(() => {
211
+ if (loadData == null) return;
194
212
  loadData().then((result) => {
195
213
  if (result == null || !isMounted.current) return;
196
214
  if (onLoadData) onLoadData(result);
@@ -199,7 +217,12 @@ export function ComboBox<
199
217
  }
200
218
  setOptions(result);
201
219
  });
202
- }, [propertyWay, autoAddBlankItem, idField, labelField]);
220
+ }, [loadData, autoAddBlankItem, idField, labelField]);
221
+
222
+ React.useEffect(() => {
223
+ if (propertyWay) return;
224
+ doLoadData();
225
+ }, [propertyWay, doLoadData]);
203
226
 
204
227
  React.useEffect(() => {
205
228
  return () => {
@@ -221,55 +244,68 @@ export function ComboBox<
221
244
  onChange={inputOnChange}
222
245
  />
223
246
  {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
224
- <Autocomplete<T, false, false, false>
225
- value={stateValue}
226
- disableCloseOnSelect={disableCloseOnSelect}
227
- getOptionLabel={getOptionLabel}
228
- isOptionEqualToValue={(option: T, value: T) =>
229
- option[idField] === value[idField]
230
- }
231
- onChange={(event, value, reason, details) => {
232
- // Set value
233
- setInputValue(value);
247
+ <Stack gap={0.5} direction="row" width="100%">
248
+ <Autocomplete<T, false, false, false>
249
+ value={stateValue}
250
+ disableCloseOnSelect={disableCloseOnSelect}
251
+ getOptionLabel={getOptionLabel}
252
+ isOptionEqualToValue={(option: T, value: T) =>
253
+ option[idField] === value[idField]
254
+ }
255
+ onChange={(event, value, reason, details) => {
256
+ // Set value
257
+ setInputValue(value);
234
258
 
235
- // Custom
236
- if (onChange != null) onChange(event, value, reason, details);
259
+ // Custom
260
+ if (onChange != null) onChange(event, value, reason, details);
237
261
 
238
- if (onValueChange) onValueChange(value);
239
- }}
240
- openOnFocus={openOnFocus}
241
- sx={sx}
242
- renderInput={(params) =>
243
- search ? (
244
- <SearchField
245
- {...addReadOnly(params)}
246
- label={label}
247
- name={name + "Input"}
248
- margin={inputMargin}
249
- variant={inputVariant}
250
- required={inputRequired}
251
- error={inputError}
252
- helperText={inputHelperText}
253
- />
254
- ) : (
255
- <InputField
256
- {...addReadOnly(params)}
257
- label={label}
258
- name={name + "Input"}
259
- margin={inputMargin}
260
- variant={inputVariant}
261
- required={inputRequired}
262
- error={inputError}
263
- helperText={inputHelperText}
264
- />
265
- )
266
- }
267
- options={localOptions}
268
- noOptionsText={noOptionsText}
269
- loadingText={loadingText}
270
- openText={openText}
271
- {...rest}
272
- />
262
+ if (onValueChange) onValueChange(value);
263
+ }}
264
+ openOnFocus={openOnFocus}
265
+ sx={sx}
266
+ renderInput={(params) =>
267
+ search ? (
268
+ <SearchField
269
+ {...addReadOnly(params)}
270
+ label={label}
271
+ name={name + "Input"}
272
+ margin={inputMargin}
273
+ variant={inputVariant}
274
+ required={inputRequired}
275
+ error={inputError}
276
+ helperText={inputHelperText}
277
+ />
278
+ ) : (
279
+ <InputField
280
+ {...addReadOnly(params)}
281
+ label={label}
282
+ name={name + "Input"}
283
+ margin={inputMargin}
284
+ variant={inputVariant}
285
+ required={inputRequired}
286
+ error={inputError}
287
+ helperText={inputHelperText}
288
+ />
289
+ )
290
+ }
291
+ options={localOptions}
292
+ noOptionsText={noOptionsText}
293
+ loadingText={loadingText}
294
+ openText={openText}
295
+ {...rest}
296
+ />
297
+ {onAdd && (
298
+ <IconButton
299
+ size="small"
300
+ onClick={() => {
301
+ onAdd(doLoadData);
302
+ }}
303
+ title={addLabel}
304
+ >
305
+ <AddIcon />
306
+ </IconButton>
307
+ )}
308
+ </Stack>
273
309
  </div>
274
310
  );
275
311
  }
@@ -0,0 +1,55 @@
1
+ import { Button, ButtonProps } from "@mui/material";
2
+ import React from "react";
3
+
4
+ /**
5
+ * File upload button props
6
+ */
7
+ export type FileUploadButtonProps = ButtonProps<"label"> & {
8
+ /**
9
+ * Input field attributes
10
+ */
11
+ inputProps?: Omit<
12
+ React.InputHTMLAttributes<HTMLInputElement>,
13
+ "type" | "hidden"
14
+ >;
15
+
16
+ /**
17
+ * Upload files callback
18
+ * @param files Files
19
+ */
20
+ onUploadFiles?: (files: FileList) => void;
21
+ };
22
+
23
+ /**
24
+ * File upload button
25
+ * @param props Props
26
+ * @returns Component
27
+ */
28
+ export function FileUploadButton(props: FileUploadButtonProps) {
29
+ // Destruct
30
+ const { inputProps, onUploadFiles, children, ...rest } = props;
31
+
32
+ const { onChange } = inputProps ?? {};
33
+
34
+ // Layout
35
+ return (
36
+ <Button component="label" {...rest}>
37
+ {children}
38
+ <input
39
+ type="file"
40
+ hidden={true}
41
+ onChange={(event) => {
42
+ if (onChange) onChange(event);
43
+ if (event.isDefaultPrevented()) return;
44
+
45
+ if (onUploadFiles) {
46
+ const files = event.target.files;
47
+ if (files == null || files.length == 0) return;
48
+ onUploadFiles(files);
49
+ }
50
+ }}
51
+ {...inputProps}
52
+ />
53
+ </Button>
54
+ );
55
+ }
@@ -16,6 +16,7 @@ import DoneIcon from "@mui/icons-material/Done";
16
16
  import RemoveIcon from "@mui/icons-material/Remove";
17
17
  import AddIcon from "@mui/icons-material/Add";
18
18
  import { Labels } from "./app/Labels";
19
+ import { FileUploadButton } from "./FileUploadButton";
19
20
 
20
21
  /**
21
22
  * User avatar editor to Blob helper
@@ -182,11 +183,8 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
182
183
  setReady(true);
183
184
  };
184
185
 
185
- // Handle file change
186
- const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
187
- const files = event.target.files;
188
- if (files == null || files.length == 0) return;
189
-
186
+ // Handle file upload
187
+ const handleFileUpload = (files: FileList) => {
190
188
  // Reset all settings
191
189
  handleReset();
192
190
 
@@ -266,23 +264,16 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
266
264
 
267
265
  return (
268
266
  <Stack direction="column" spacing={0.5} width={containerWidth}>
269
- <Button
267
+ <FileUploadButton
270
268
  variant="outlined"
271
269
  size="medium"
272
- component="label"
273
270
  startIcon={<ComputerIcon />}
274
271
  fullWidth
272
+ onUploadFiles={handleFileUpload}
273
+ inputProps={{ multiple: false, accept: "image/png, image/jpeg" }}
275
274
  >
276
275
  {labels.upload}
277
- <input
278
- id="fileInput"
279
- type="file"
280
- accept="image/png, image/jpeg"
281
- multiple={false}
282
- hidden
283
- onChange={handleFileChange}
284
- />
285
- </Button>
276
+ </FileUploadButton>
286
277
  <Stack direction="row" spacing={0.5}>
287
278
  <React.Suspense
288
279
  fallback={
package/src/index.ts CHANGED
@@ -50,6 +50,7 @@ export * from "./DnDList";
50
50
  export * from "./DraggablePaperComponent";
51
51
  export * from "./EmailInput";
52
52
  export * from "./FabBox";
53
+ export * from "./FileUploadButton";
53
54
  export * from "./FlexBox";
54
55
  export * from "./GridDataFormat";
55
56
  export * from "./HiSelector";