@etsoo/materialui 1.1.42 → 1.1.44

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.
@@ -0,0 +1,299 @@
1
+ import {
2
+ DataTypes,
3
+ IdDefaultType,
4
+ Keyboard,
5
+ LabelDefaultType,
6
+ ListType
7
+ } from "@etsoo/shared";
8
+ import {
9
+ Autocomplete,
10
+ AutocompleteRenderInputParams,
11
+ Checkbox
12
+ } from "@mui/material";
13
+ import React from "react";
14
+ import { Utils as SharedUtils } from "@etsoo/shared";
15
+ import { ReactUtils } from "@etsoo/react";
16
+
17
+ import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
18
+ import CheckBoxIcon from "@mui/icons-material/CheckBox";
19
+ import { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
20
+ import { SearchField } from "./SearchField";
21
+ import { InputField } from "./InputField";
22
+ import { globalApp } from "./app/ReactApp";
23
+
24
+ const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
25
+ const checkedIcon = <CheckBoxIcon fontSize="small" />;
26
+
27
+ /**
28
+ * ComboBox multiple props
29
+ */
30
+ export type ComboBoxMultipleProps<
31
+ T extends object = ListType,
32
+ D extends DataTypes.Keys<T> = IdDefaultType<T>,
33
+ L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
34
+ > = AutocompleteExtendedProps<T, D, true> & {
35
+ /**
36
+ * Auto add blank item
37
+ */
38
+ autoAddBlankItem?: boolean;
39
+
40
+ /**
41
+ * Data readonly
42
+ */
43
+ dataReadonly?: boolean;
44
+
45
+ /**
46
+ * Label field
47
+ */
48
+ labelField?: L;
49
+
50
+ /**
51
+ * Load data callback
52
+ */
53
+ loadData?: () => PromiseLike<T[] | null | undefined>;
54
+
55
+ /**
56
+ * On load data handler
57
+ */
58
+ onLoadData?: (options: T[]) => void;
59
+
60
+ /**
61
+ * Array of options.
62
+ */
63
+ options?: ReadonlyArray<T>;
64
+
65
+ /**
66
+ * Id values
67
+ */
68
+ idValues?: T[D][];
69
+ };
70
+
71
+ /**
72
+ * ComboBox multiple
73
+ * @param props Props
74
+ * @returns Component
75
+ */
76
+ export function ComboBoxMultiple<
77
+ T extends object = ListType,
78
+ D extends DataTypes.Keys<T> = IdDefaultType<T>,
79
+ L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
80
+ >(props: ComboBoxMultipleProps<T, D, L>) {
81
+ // Labels
82
+ const labels = globalApp?.getLabels("noOptions", "loading");
83
+
84
+ // Destruct
85
+ const {
86
+ search = false,
87
+ autoAddBlankItem = search,
88
+ idField = "id" as D,
89
+ idValue,
90
+ idValues,
91
+ inputError,
92
+ inputHelperText,
93
+ inputMargin,
94
+ inputOnChange,
95
+ inputRequired,
96
+ inputVariant,
97
+ defaultValue,
98
+ label,
99
+ labelField = "label" as L,
100
+ loadData,
101
+ onLoadData,
102
+ name,
103
+ inputAutoComplete = "new-password", // disable autocomplete and autofill, 'off' does not work
104
+ options,
105
+ dataReadonly = true,
106
+ readOnly,
107
+ onChange,
108
+ openOnFocus = true,
109
+ value,
110
+ disableCloseOnSelect = true,
111
+ renderOption = (props, option, { selected }) => (
112
+ <li {...props}>
113
+ <>
114
+ <Checkbox
115
+ icon={icon}
116
+ checkedIcon={checkedIcon}
117
+ style={{ marginRight: 8 }}
118
+ checked={selected}
119
+ />
120
+ {option[labelField]}
121
+ </>
122
+ </li>
123
+ ),
124
+ getOptionLabel = (option: T) => `${option[labelField]}`,
125
+ sx = { minWidth: "150px" },
126
+ noOptionsText = labels?.noOptions,
127
+ loadingText = labels?.loading,
128
+ ...rest
129
+ } = props;
130
+
131
+ // Value input ref
132
+ const inputRef = React.createRef<HTMLInputElement>();
133
+
134
+ // Options state
135
+ const [localOptions, setOptions] = React.useState(options ?? []);
136
+ const isMounted = React.useRef(true);
137
+
138
+ // When options change
139
+ // [options] will cause infinite loop
140
+ const propertyWay = loadData == null;
141
+ React.useEffect(() => {
142
+ if (propertyWay && options != null) setOptions(options);
143
+ }, [options, propertyWay]);
144
+
145
+ // Local default value
146
+ const localValue: T | T[] | null | undefined =
147
+ idValue != null
148
+ ? localOptions.filter((o) => o[idField] === idValue)
149
+ : idValues != null
150
+ ? localOptions.filter((o) => idValues?.includes(o[idField]))
151
+ : defaultValue ?? value;
152
+
153
+ // State
154
+ // null for controlled
155
+ const [stateValue, setStateValue] = React.useState<T | T[] | null>(null);
156
+
157
+ React.useEffect(() => {
158
+ if (localValue != null && localValue != stateValue) {
159
+ setStateValue(localValue);
160
+ }
161
+ }, [localValue]);
162
+
163
+ // Add readOnly
164
+ const addReadOnly = (params: AutocompleteRenderInputParams) => {
165
+ if (readOnly != null) {
166
+ Object.assign(params, { readOnly });
167
+
168
+ if (readOnly) {
169
+ Object.assign(params.inputProps, { "data-reset": true });
170
+ }
171
+ }
172
+
173
+ if (dataReadonly) {
174
+ params.inputProps.onKeyDown = (event) => {
175
+ if (Keyboard.isTypingContent(event.key)) {
176
+ event.preventDefault();
177
+ }
178
+ };
179
+ }
180
+
181
+ // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
182
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
183
+ Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
184
+
185
+ return params;
186
+ };
187
+
188
+ const getValue = (value: T | T[] | null): string => {
189
+ if (value == null) return "";
190
+ if (Array.isArray(value))
191
+ return value.map((item) => item[idField]).join(",");
192
+ return `${value[idField]}`;
193
+ };
194
+
195
+ const setInputValue = (value: T | T[] | null) => {
196
+ // Set state
197
+ setStateValue(value);
198
+
199
+ // Input value
200
+ const input = inputRef.current;
201
+ if (input) {
202
+ // Update value
203
+ const newValue = getValue(value);
204
+
205
+ if (newValue !== input.value) {
206
+ // Different value, trigger change event
207
+ ReactUtils.triggerChange(input, newValue, false);
208
+ }
209
+ }
210
+ };
211
+
212
+ React.useEffect(() => {
213
+ if (propertyWay || loadData == null) return;
214
+ loadData().then((result) => {
215
+ if (result == null || !isMounted.current) return;
216
+ if (onLoadData) onLoadData(result);
217
+ if (autoAddBlankItem) {
218
+ SharedUtils.addBlankItem(result, idField, labelField);
219
+ }
220
+ setOptions(result);
221
+ });
222
+ }, [propertyWay, autoAddBlankItem, idField, labelField]);
223
+
224
+ React.useEffect(() => {
225
+ return () => {
226
+ isMounted.current = false;
227
+ };
228
+ }, []);
229
+
230
+ // Layout
231
+ return (
232
+ <div>
233
+ <input
234
+ ref={inputRef}
235
+ data-reset="true"
236
+ type="text"
237
+ style={{ display: "none" }}
238
+ name={name}
239
+ value={getValue(stateValue)}
240
+ readOnly
241
+ onChange={inputOnChange}
242
+ />
243
+ {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
244
+ <Autocomplete<T, true, false, false>
245
+ value={
246
+ stateValue == null
247
+ ? undefined
248
+ : Array.isArray(stateValue)
249
+ ? stateValue
250
+ : [stateValue]
251
+ }
252
+ disableCloseOnSelect={disableCloseOnSelect}
253
+ getOptionLabel={getOptionLabel}
254
+ isOptionEqualToValue={(option: T, value: T) =>
255
+ option[idField] === value[idField]
256
+ }
257
+ onChange={(event, value, reason, details) => {
258
+ // Set value
259
+ setInputValue(value);
260
+
261
+ // Custom
262
+ if (onChange != null) onChange(event, value, reason, details);
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
+ renderOption={renderOption}
293
+ noOptionsText={noOptionsText}
294
+ loadingText={loadingText}
295
+ {...rest}
296
+ />
297
+ </div>
298
+ );
299
+ }
@@ -93,7 +93,7 @@ export function HiSelector<
93
93
  error,
94
94
  helperText,
95
95
  name,
96
- label = name,
96
+ label,
97
97
  labelField = "name" as L,
98
98
  loadData,
99
99
  onChange,
@@ -142,15 +142,17 @@ export function HiSelector<
142
142
 
143
143
  return (
144
144
  <React.Fragment>
145
- <Grid item xs={12}>
146
- <FormLabel
147
- required={required}
148
- sx={{ fontSize: (theme) => theme.typography.caption }}
149
- >
150
- {label}
151
- </FormLabel>
152
- <input type="hidden" name={name} value={`${currentValue ?? ""}`} />
153
- </Grid>
145
+ {label && (
146
+ <Grid item xs={12}>
147
+ <FormLabel
148
+ required={required}
149
+ sx={{ fontSize: (theme) => theme.typography.caption }}
150
+ >
151
+ {label}
152
+ </FormLabel>
153
+ </Grid>
154
+ )}
155
+ <input type="hidden" name={name} value={`${currentValue ?? ""}`} />
154
156
  <Grid item xs={6} md={4} lg={3}>
155
157
  <SelectEx<T, D, L>
156
158
  idField={idField}
@@ -142,15 +142,17 @@ export function HiSelectorTL<
142
142
 
143
143
  return (
144
144
  <React.Fragment>
145
- <Grid item xs={12}>
146
- <FormLabel
147
- required={required}
148
- sx={{ fontSize: (theme) => theme.typography.caption }}
149
- >
150
- {label}
151
- </FormLabel>
152
- <input type="hidden" name={name} value={`${currentValue ?? ""}`} />
153
- </Grid>
145
+ {label && (
146
+ <Grid item xs={12}>
147
+ <FormLabel
148
+ required={required}
149
+ sx={{ fontSize: (theme) => theme.typography.caption }}
150
+ >
151
+ {label}
152
+ </FormLabel>
153
+ </Grid>
154
+ )}
155
+ <input type="hidden" name={name} value={`${currentValue ?? ""}`} />
154
156
  <Grid item xs={6} md={4} lg={3}>
155
157
  <Tiplist<T, D>
156
158
  idField={idField}
@@ -120,8 +120,8 @@ export function SwitchField(props: SwitchFieldProps) {
120
120
  <SwitchAnt
121
121
  activeColor={activeColor}
122
122
  name={name}
123
- startLabel={startLabel ?? "NO"}
124
- endLabel={endLabel ?? "YES"}
123
+ startLabel={startLabel}
124
+ endLabel={endLabel}
125
125
  value={value}
126
126
  checked={checked}
127
127
  />
package/src/index.ts CHANGED
@@ -29,12 +29,14 @@ export * from "./texts/DateText";
29
29
  export * from "./texts/MoneyText";
30
30
  export * from "./texts/NumberText";
31
31
 
32
+ export * from "./AddresSelector";
32
33
  export * from "./AuditDisplay";
33
34
  export * from "./AutocompleteExtendedProps";
34
35
  export * from "./BackButton";
35
36
  export * from "./BridgeCloseButton";
36
37
  export * from "./ButtonLink";
37
38
  export * from "./ComboBox";
39
+ export * from "./ComboBoxMultiple";
38
40
  export * from "./CountdownButton";
39
41
  export * from "./CountryList";
40
42
  export * from "./CustomFabProps";