@etsoo/materialui 1.1.80 → 1.1.82

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.
@@ -1,14 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import { AutocompleteProps } from "@mui/material";
3
3
  import { InputFieldProps } from "./InputField";
4
- type DataType = {
5
- id: number | string;
6
- } & ({
7
- label: string;
8
- } | {
9
- name: string;
10
- });
11
- export type ComboBoxProProps<D extends DataType = DataType> = Omit<AutocompleteProps<D, false, false, true>, "open" | "multiple" | "options" | "renderInput"> & {
4
+ import { ListType2 } from "@etsoo/shared";
5
+ export type ComboBoxProProps<D extends ListType2 = ListType2> = Omit<AutocompleteProps<D, false, false, true>, "open" | "multiple" | "options" | "renderInput"> & {
12
6
  /**
13
7
  * Label
14
8
  */
@@ -30,5 +24,4 @@ export type ComboBoxProProps<D extends DataType = DataType> = Omit<AutocompleteP
30
24
  */
31
25
  inputProps?: Omit<InputFieldProps, "onChange">;
32
26
  };
33
- export declare function ComboBoxPro<D extends DataType = DataType>(props: ComboBoxProProps<D>): JSX.Element;
34
- export {};
27
+ export declare function ComboBoxPro<D extends ListType2 = ListType2>(props: ComboBoxProProps<D>): JSX.Element;
@@ -4,8 +4,52 @@ import { InputFieldProps } from "./InputField";
4
4
  /**
5
5
  * Integer input field props
6
6
  */
7
- export type IntInputFieldProps = Omit<InputFieldProps, "type" | "inputProps"> & InputBaseProps["inputProps"];
7
+ export type IntInputFieldProps = Omit<InputFieldProps, "type" | "inputProps" | "value"> & InputBaseProps["inputProps"] & {
8
+ /**
9
+ * Display minus and plus buttons
10
+ */
11
+ buttons?: boolean;
12
+ /**
13
+ * End symbol
14
+ */
15
+ endSymbol?: string;
16
+ /**
17
+ * Start (Currency) symbol
18
+ */
19
+ symbol?: string;
20
+ /**
21
+ * Value
22
+ */
23
+ value?: number;
24
+ /**
25
+ * On value change callback
26
+ * @param value Value
27
+ */
28
+ onValueChange?: (value: number | undefined, init: boolean) => void;
29
+ };
8
30
  /**
9
31
  * Integer input field
10
32
  */
11
- export declare const IntInputField: React.ForwardRefExoticComponent<Omit<Omit<InputFieldProps, "type" | "inputProps"> & import("@mui/material").InputBaseComponentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
33
+ export declare const IntInputField: React.ForwardRefExoticComponent<Omit<Omit<InputFieldProps, "type" | "value" | "inputProps"> & import("@mui/material").InputBaseComponentProps & {
34
+ /**
35
+ * Display minus and plus buttons
36
+ */
37
+ buttons?: boolean | undefined;
38
+ /**
39
+ * End symbol
40
+ */
41
+ endSymbol?: string | undefined;
42
+ /**
43
+ * Start (Currency) symbol
44
+ */
45
+ symbol?: string | undefined;
46
+ /**
47
+ * Value
48
+ */
49
+ value?: number | undefined;
50
+ /**
51
+ * On value change callback
52
+ * @param value Value
53
+ */
54
+ onValueChange?: ((value: number | undefined, init: boolean) => void) | undefined;
55
+ }, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -1,3 +1,6 @@
1
+ import { Box, IconButton, InputAdornment } from "@mui/material";
2
+ import AddIcon from "@mui/icons-material/Add";
3
+ import RemoveIcon from "@mui/icons-material/Remove";
1
4
  import React from "react";
2
5
  import { InputField } from "./InputField";
3
6
  /**
@@ -5,7 +8,82 @@ import { InputField } from "./InputField";
5
8
  */
6
9
  export const IntInputField = React.forwardRef((props, ref) => {
7
10
  // Destruct
8
- const { min = 0, step = 1, max = 9999999, ...rest } = props;
11
+ const { min = 0, step = 1, max = 9999999, style = { textAlign: "right" }, buttons, endSymbol, symbol, value, defaultValue, onChange, onValueChange, ...rest } = props;
12
+ // State
13
+ const [localValue, setLocalValue] = React.useState();
14
+ const setValue = (value, init = false) => {
15
+ setLocalValue(value);
16
+ if (onValueChange)
17
+ onValueChange(value, init);
18
+ };
19
+ React.useEffect(() => {
20
+ setValue(value, true);
21
+ }, [value]);
22
+ React.useEffect(() => {
23
+ if (defaultValue == null || typeof defaultValue === "object")
24
+ return;
25
+ const value = typeof defaultValue === "number"
26
+ ? defaultValue
27
+ : parseFloat(defaultValue);
28
+ if (!isNaN(value))
29
+ setValue(value, true);
30
+ }, [defaultValue]);
9
31
  // Layout
10
- return (React.createElement(InputField, { ref: ref, type: "number", inputProps: { min, step, max, inputMode: "numeric" }, ...rest }));
32
+ const layout = (React.createElement(InputField, { ref: ref, type: "number", value: localValue == null ? "" : localValue, inputProps: {
33
+ min,
34
+ step,
35
+ max,
36
+ style,
37
+ inputMode: "numeric"
38
+ }, InputProps: {
39
+ startAdornment: symbol ? (React.createElement(React.Fragment, null,
40
+ React.createElement(InputAdornment, { position: "start" }, symbol))) : undefined,
41
+ endAdornment: endSymbol ? (React.createElement(InputAdornment, { position: "end" }, endSymbol)) : undefined
42
+ }, sx: {
43
+ "& input[type=number]::-webkit-inner-spin-button": {
44
+ WebkitAppearance: "none",
45
+ margin: 0
46
+ },
47
+ "& input[type=number]::-webkit-outer-spin-button": {
48
+ WebkitAppearance: "none",
49
+ margin: 0
50
+ }
51
+ }, onChange: (event) => {
52
+ const value = parseFloat(event.target.value);
53
+ if (isNaN(value))
54
+ setValue(undefined);
55
+ else if (value > max)
56
+ setValue(max);
57
+ else if (value < min)
58
+ setValue(min);
59
+ else
60
+ setValue(value);
61
+ if (onChange)
62
+ onChange(event);
63
+ }, ...rest }));
64
+ if (buttons)
65
+ return (React.createElement(Box, { sx: { display: "flex", alignItems: "flex-end", gap: 0.5 } },
66
+ React.createElement(IconButton, { size: "small", onClick: () => {
67
+ if (localValue == null)
68
+ return;
69
+ if (localValue <= min)
70
+ setValue(undefined);
71
+ else
72
+ setValue(localValue - step);
73
+ } },
74
+ React.createElement(RemoveIcon, null)),
75
+ layout,
76
+ React.createElement(IconButton, { size: "small", onClick: () => {
77
+ if (localValue == null) {
78
+ setValue(min);
79
+ return;
80
+ }
81
+ if (localValue >= max)
82
+ return;
83
+ else
84
+ setValue(localValue + step);
85
+ } },
86
+ React.createElement(AddIcon, { color: localValue == null ? undefined : "primary" }))));
87
+ else
88
+ return layout;
11
89
  });
@@ -1,11 +1,10 @@
1
- import { InputBaseProps } from "@mui/material";
2
1
  import React from "react";
3
- import { InputFieldProps } from "./InputField";
2
+ import { IntInputFieldProps } from "./IntInputField";
4
3
  /**
5
4
  * Money input field props
6
5
  */
7
- export type MoneyInputFieldProps = Omit<InputFieldProps, "type" | "inputProps"> & InputBaseProps["inputProps"];
6
+ export type MoneyInputFieldProps = IntInputFieldProps & {};
8
7
  /**
9
8
  * Money input field
10
9
  */
11
- export declare const MoneyInputField: React.ForwardRefExoticComponent<Omit<Omit<InputFieldProps, "type" | "inputProps"> & import("@mui/material").InputBaseComponentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
10
+ export declare const MoneyInputField: React.ForwardRefExoticComponent<Omit<MoneyInputFieldProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
- import { InputField } from "./InputField";
2
+ import { IntInputField } from "./IntInputField";
3
3
  /**
4
4
  * Money input field
5
5
  */
6
6
  export const MoneyInputField = React.forwardRef((props, ref) => {
7
7
  // Destruct
8
- const { min = 0, step = 0.01, max = 9999999, ...rest } = props;
8
+ const { step = 0.01, ...rest } = props;
9
9
  // Layout
10
- return (React.createElement(InputField, { ref: ref, type: "number", inputProps: { min, step, max, inputMode: "numeric" }, ...rest }));
10
+ return React.createElement(IntInputField, { ref: ref, step: step, ...rest });
11
11
  });
@@ -0,0 +1,56 @@
1
+ import { ListType2 } from "@etsoo/shared";
2
+ import { ListItemButtonProps, ListItemProps, StackProps } from "@mui/material";
3
+ import React from "react";
4
+ import { InputFieldProps } from "./InputField";
5
+ /**
6
+ * Quick list props
7
+ */
8
+ export type QuickListProps<T extends ListType2 = ListType2> = StackProps & {
9
+ /**
10
+ * Button props
11
+ */
12
+ buttonProps?: ListItemButtonProps;
13
+ /**
14
+ * Label
15
+ */
16
+ label?: string;
17
+ /**
18
+ * No matches label
19
+ */
20
+ noMatchesLabel?: string;
21
+ /**
22
+ * Input field props
23
+ */
24
+ inputProps?: Omit<InputFieldProps, "onChangeDelay">;
25
+ /**
26
+ * Get item label
27
+ * @param item Current item
28
+ * @returns Item label
29
+ */
30
+ itemLabel?: (item: T) => string;
31
+ /**
32
+ * Item renderer
33
+ * @param item Current item
34
+ * @returns UI
35
+ */
36
+ itemRenderer?: (item: T) => React.ReactNode;
37
+ /**
38
+ * List item props
39
+ */
40
+ itemProps?: ListItemProps;
41
+ /**
42
+ * Load data callback
43
+ */
44
+ loadData: (keyword: string | undefined) => PromiseLike<T[] | undefined>;
45
+ /**
46
+ * On item click
47
+ * @param item Clicked item
48
+ */
49
+ onItemClick?: (item: T) => void;
50
+ };
51
+ /**
52
+ * Quick list
53
+ * @param props Props
54
+ * @returns Component
55
+ */
56
+ export declare function QuickList<T extends ListType2 = ListType2>(props: QuickListProps<T>): JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { LinearProgress, List, ListItem, ListItemButton, Typography } from "@mui/material";
2
+ import React from "react";
3
+ import { InputField } from "./InputField";
4
+ import { globalApp } from "./app/ReactApp";
5
+ import { VBox } from "./FlexBox";
6
+ /**
7
+ * Quick list
8
+ * @param props Props
9
+ * @returns Component
10
+ */
11
+ export function QuickList(props) {
12
+ // Destruct
13
+ const { buttonProps = {}, label, inputProps, itemLabel = (item) => ("label" in item ? item.label : item.name), itemRenderer = (item) => itemLabel(item), itemProps, loadData, noMatchesLabel = globalApp === null || globalApp === void 0 ? void 0 : globalApp.get("noMatches"), gap = 1, height = "480px", onItemClick, ...rest } = props;
14
+ const { onClick, ...buttonRest } = buttonProps;
15
+ // States
16
+ const [loading, setLoading] = React.useState(false);
17
+ const [items, setItems] = React.useState([]);
18
+ const loadDataLocal = (keyword) => {
19
+ setLoading(true);
20
+ loadData(keyword).then((result) => {
21
+ setLoading(false);
22
+ setItems(result !== null && result !== void 0 ? result : []);
23
+ });
24
+ };
25
+ React.useEffect(() => {
26
+ loadDataLocal();
27
+ }, []);
28
+ // Layout
29
+ return (React.createElement(VBox, { gap: gap, height: height, ...rest },
30
+ React.createElement(InputField, { label: label, changeDelay: 480, onChangeDelay: (event) => {
31
+ // Stop bubble
32
+ event.preventDefault();
33
+ event.stopPropagation();
34
+ loadDataLocal(event.target.value);
35
+ }, fullWidth: true, ...inputProps }),
36
+ loading ? (React.createElement(LinearProgress, null)) : items.length === 0 ? (React.createElement(Typography, { textAlign: "center" }, noMatchesLabel)) : (React.createElement(List, null, items.map((item) => (React.createElement(ListItem, { key: item.id, disablePadding: true, ...itemProps },
37
+ React.createElement(ListItemButton, { onClick: (event) => {
38
+ if (onClick)
39
+ onClick(event);
40
+ if (!event.defaultPrevented && onItemClick)
41
+ onItemClick(item);
42
+ }, ...buttonRest }, itemRenderer(item)))))))));
43
+ }
@@ -1,14 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import { AutocompleteProps } from "@mui/material";
3
3
  import { InputFieldProps } from "./InputField";
4
- type DataType = {
5
- id: number | string;
6
- } & ({
7
- label: string;
8
- } | {
9
- name: string;
10
- });
11
- export type TagListProProps<D extends DataType = DataType> = Omit<AutocompleteProps<D, true, false, false>, "open" | "multiple" | "options" | "renderInput"> & {
4
+ import { ListType2 } from "@etsoo/shared";
5
+ export type TagListProProps<D extends ListType2 = ListType2> = Omit<AutocompleteProps<D, true, false, false>, "open" | "multiple" | "options" | "renderInput"> & {
12
6
  /**
13
7
  * Label
14
8
  */
@@ -26,5 +20,4 @@ export type TagListProProps<D extends DataType = DataType> = Omit<AutocompletePr
26
20
  */
27
21
  maxItems?: number;
28
22
  };
29
- export declare function TagListPro<D extends DataType = DataType>(props: TagListProProps<D>): JSX.Element;
30
- export {};
23
+ export declare function TagListPro<D extends ListType2 = ListType2>(props: TagListProProps<D>): JSX.Element;
package/lib/index.d.ts CHANGED
@@ -71,6 +71,7 @@ export * from "./OptionGroup";
71
71
  export * from "./PList";
72
72
  export * from "./ProgressCount";
73
73
  export * from "./PullToRefreshUI";
74
+ export * from "./QuickList";
74
75
  export * from "./ResponsibleContainer";
75
76
  export * from "./ScrollerListEx";
76
77
  export * from "./ScrollTopFab";
package/lib/index.js CHANGED
@@ -71,6 +71,7 @@ export * from "./OptionGroup";
71
71
  export * from "./PList";
72
72
  export * from "./ProgressCount";
73
73
  export * from "./PullToRefreshUI";
74
+ export * from "./QuickList";
74
75
  export * from "./ResponsibleContainer";
75
76
  export * from "./ScrollerListEx";
76
77
  export * from "./ScrollTopFab";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.1.80",
3
+ "version": "1.1.82",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,13 +50,13 @@
50
50
  "@emotion/css": "^11.10.6",
51
51
  "@emotion/react": "^11.10.6",
52
52
  "@emotion/styled": "^11.10.6",
53
- "@etsoo/appscript": "^1.3.85",
53
+ "@etsoo/appscript": "^1.3.86",
54
54
  "@etsoo/notificationbase": "^1.1.24",
55
- "@etsoo/react": "^1.6.56",
56
- "@etsoo/shared": "^1.1.94",
55
+ "@etsoo/react": "^1.6.57",
56
+ "@etsoo/shared": "^1.1.95",
57
57
  "@mui/icons-material": "^5.11.11",
58
58
  "@mui/material": "^5.11.15",
59
- "@mui/x-data-grid": "^6.0.3",
59
+ "@mui/x-data-grid": "^6.0.4",
60
60
  "@types/pica": "^9.0.1",
61
61
  "@types/pulltorefreshjs": "^0.1.5",
62
62
  "@types/react": "^18.0.31",
@@ -89,6 +89,6 @@
89
89
  "@typescript-eslint/parser": "^5.57.0",
90
90
  "jest": "^29.5.0",
91
91
  "jest-environment-jsdom": "^29.5.0",
92
- "typescript": "^5.0.2"
92
+ "typescript": "^5.0.3"
93
93
  }
94
94
  }
@@ -2,12 +2,9 @@ import { Autocomplete, AutocompleteProps } from "@mui/material";
2
2
  import React from "react";
3
3
  import { globalApp } from "./app/ReactApp";
4
4
  import { InputField, InputFieldProps } from "./InputField";
5
+ import { ListType2 } from "@etsoo/shared";
5
6
 
6
- type DataType = {
7
- id: number | string;
8
- } & ({ label: string } | { name: string });
9
-
10
- export type ComboBoxProProps<D extends DataType = DataType> = Omit<
7
+ export type ComboBoxProProps<D extends ListType2 = ListType2> = Omit<
11
8
  AutocompleteProps<D, false, false, true>,
12
9
  "open" | "multiple" | "options" | "renderInput"
13
10
  > & {
@@ -37,7 +34,7 @@ export type ComboBoxProProps<D extends DataType = DataType> = Omit<
37
34
  inputProps?: Omit<InputFieldProps, "onChange">;
38
35
  };
39
36
 
40
- export function ComboBoxPro<D extends DataType = DataType>(
37
+ export function ComboBoxPro<D extends ListType2 = ListType2>(
41
38
  props: ComboBoxProProps<D>
42
39
  ) {
43
40
  // Labels
@@ -1,12 +1,43 @@
1
- import { InputBaseProps } from "@mui/material";
1
+ import { Box, IconButton, InputAdornment, InputBaseProps } from "@mui/material";
2
+ import AddIcon from "@mui/icons-material/Add";
3
+ import RemoveIcon from "@mui/icons-material/Remove";
2
4
  import React from "react";
3
5
  import { InputField, InputFieldProps } from "./InputField";
4
6
 
5
7
  /**
6
8
  * Integer input field props
7
9
  */
8
- export type IntInputFieldProps = Omit<InputFieldProps, "type" | "inputProps"> &
9
- InputBaseProps["inputProps"];
10
+ export type IntInputFieldProps = Omit<
11
+ InputFieldProps,
12
+ "type" | "inputProps" | "value"
13
+ > &
14
+ InputBaseProps["inputProps"] & {
15
+ /**
16
+ * Display minus and plus buttons
17
+ */
18
+ buttons?: boolean;
19
+
20
+ /**
21
+ * End symbol
22
+ */
23
+ endSymbol?: string;
24
+
25
+ /**
26
+ * Start (Currency) symbol
27
+ */
28
+ symbol?: string;
29
+
30
+ /**
31
+ * Value
32
+ */
33
+ value?: number;
34
+
35
+ /**
36
+ * On value change callback
37
+ * @param value Value
38
+ */
39
+ onValueChange?: (value: number | undefined, init: boolean) => void;
40
+ };
10
41
 
11
42
  /**
12
43
  * Integer input field
@@ -16,15 +47,115 @@ export const IntInputField = React.forwardRef<
16
47
  IntInputFieldProps
17
48
  >((props, ref) => {
18
49
  // Destruct
19
- const { min = 0, step = 1, max = 9999999, ...rest } = props;
50
+ const {
51
+ min = 0,
52
+ step = 1,
53
+ max = 9999999,
54
+ style = { textAlign: "right" },
55
+ buttons,
56
+ endSymbol,
57
+ symbol,
58
+ value,
59
+ defaultValue,
60
+ onChange,
61
+ onValueChange,
62
+ ...rest
63
+ } = props;
64
+
65
+ // State
66
+ const [localValue, setLocalValue] = React.useState<number>();
67
+
68
+ const setValue = (value: number | undefined, init: boolean = false) => {
69
+ setLocalValue(value);
70
+ if (onValueChange) onValueChange(value, init);
71
+ };
72
+
73
+ React.useEffect(() => {
74
+ setValue(value, true);
75
+ }, [value]);
76
+
77
+ React.useEffect(() => {
78
+ if (defaultValue == null || typeof defaultValue === "object") return;
79
+ const value =
80
+ typeof defaultValue === "number"
81
+ ? defaultValue
82
+ : parseFloat(defaultValue);
83
+ if (!isNaN(value)) setValue(value, true);
84
+ }, [defaultValue]);
20
85
 
21
86
  // Layout
22
- return (
87
+ const layout = (
23
88
  <InputField
24
89
  ref={ref}
25
90
  type="number"
26
- inputProps={{ min, step, max, inputMode: "numeric" }}
91
+ value={localValue == null ? "" : localValue}
92
+ inputProps={{
93
+ min,
94
+ step,
95
+ max,
96
+ style,
97
+ inputMode: "numeric"
98
+ }}
99
+ InputProps={{
100
+ startAdornment: symbol ? (
101
+ <React.Fragment>
102
+ <InputAdornment position="start">{symbol}</InputAdornment>
103
+ </React.Fragment>
104
+ ) : undefined,
105
+ endAdornment: endSymbol ? (
106
+ <InputAdornment position="end">{endSymbol}</InputAdornment>
107
+ ) : undefined
108
+ }}
109
+ sx={{
110
+ "& input[type=number]::-webkit-inner-spin-button": {
111
+ WebkitAppearance: "none",
112
+ margin: 0
113
+ },
114
+ "& input[type=number]::-webkit-outer-spin-button": {
115
+ WebkitAppearance: "none",
116
+ margin: 0
117
+ }
118
+ }}
119
+ onChange={(event) => {
120
+ const value = parseFloat(event.target.value);
121
+ if (isNaN(value)) setValue(undefined);
122
+ else if (value > max) setValue(max);
123
+ else if (value < min) setValue(min);
124
+ else setValue(value);
125
+ if (onChange) onChange(event);
126
+ }}
27
127
  {...rest}
28
128
  />
29
129
  );
130
+
131
+ if (buttons)
132
+ return (
133
+ <Box sx={{ display: "flex", alignItems: "flex-end", gap: 0.5 }}>
134
+ <IconButton
135
+ size="small"
136
+ onClick={() => {
137
+ if (localValue == null) return;
138
+ if (localValue <= min) setValue(undefined);
139
+ else setValue(localValue - step);
140
+ }}
141
+ >
142
+ <RemoveIcon />
143
+ </IconButton>
144
+ {layout}
145
+ <IconButton
146
+ size="small"
147
+ onClick={() => {
148
+ if (localValue == null) {
149
+ setValue(min);
150
+ return;
151
+ }
152
+ if (localValue >= max) return;
153
+ else setValue(localValue + step);
154
+ }}
155
+ >
156
+ <AddIcon color={localValue == null ? undefined : "primary"} />
157
+ </IconButton>
158
+ </Box>
159
+ );
160
+ else return layout;
30
161
  });
@@ -1,15 +1,10 @@
1
- import { InputBaseProps } from "@mui/material";
2
1
  import React from "react";
3
- import { InputField, InputFieldProps } from "./InputField";
2
+ import { IntInputField, IntInputFieldProps } from "./IntInputField";
4
3
 
5
4
  /**
6
5
  * Money input field props
7
6
  */
8
- export type MoneyInputFieldProps = Omit<
9
- InputFieldProps,
10
- "type" | "inputProps"
11
- > &
12
- InputBaseProps["inputProps"];
7
+ export type MoneyInputFieldProps = IntInputFieldProps & {};
13
8
 
14
9
  /**
15
10
  * Money input field
@@ -19,15 +14,8 @@ export const MoneyInputField = React.forwardRef<
19
14
  MoneyInputFieldProps
20
15
  >((props, ref) => {
21
16
  // Destruct
22
- const { min = 0, step = 0.01, max = 9999999, ...rest } = props;
17
+ const { step = 0.01, ...rest } = props;
23
18
 
24
19
  // Layout
25
- return (
26
- <InputField
27
- ref={ref}
28
- type="number"
29
- inputProps={{ min, step, max, inputMode: "numeric" }}
30
- {...rest}
31
- />
32
- );
20
+ return <IntInputField ref={ref} step={step} {...rest} />;
33
21
  });
@@ -0,0 +1,153 @@
1
+ import { ListType2 } from "@etsoo/shared";
2
+ import {
3
+ LinearProgress,
4
+ List,
5
+ ListItem,
6
+ ListItemButton,
7
+ ListItemButtonProps,
8
+ ListItemProps,
9
+ StackProps,
10
+ Typography
11
+ } from "@mui/material";
12
+ import React from "react";
13
+ import { InputField, InputFieldProps } from "./InputField";
14
+ import { globalApp } from "./app/ReactApp";
15
+ import { VBox } from "./FlexBox";
16
+
17
+ /**
18
+ * Quick list props
19
+ */
20
+ export type QuickListProps<T extends ListType2 = ListType2> = StackProps & {
21
+ /**
22
+ * Button props
23
+ */
24
+ buttonProps?: ListItemButtonProps;
25
+
26
+ /**
27
+ * Label
28
+ */
29
+ label?: string;
30
+
31
+ /**
32
+ * No matches label
33
+ */
34
+ noMatchesLabel?: string;
35
+
36
+ /**
37
+ * Input field props
38
+ */
39
+ inputProps?: Omit<InputFieldProps, "onChangeDelay">;
40
+
41
+ /**
42
+ * Get item label
43
+ * @param item Current item
44
+ * @returns Item label
45
+ */
46
+ itemLabel?: (item: T) => string;
47
+
48
+ /**
49
+ * Item renderer
50
+ * @param item Current item
51
+ * @returns UI
52
+ */
53
+ itemRenderer?: (item: T) => React.ReactNode;
54
+
55
+ /**
56
+ * List item props
57
+ */
58
+ itemProps?: ListItemProps;
59
+
60
+ /**
61
+ * Load data callback
62
+ */
63
+ loadData: (keyword: string | undefined) => PromiseLike<T[] | undefined>;
64
+
65
+ /**
66
+ * On item click
67
+ * @param item Clicked item
68
+ */
69
+ onItemClick?: (item: T) => void;
70
+ };
71
+
72
+ /**
73
+ * Quick list
74
+ * @param props Props
75
+ * @returns Component
76
+ */
77
+ export function QuickList<T extends ListType2 = ListType2>(
78
+ props: QuickListProps<T>
79
+ ) {
80
+ // Destruct
81
+ const {
82
+ buttonProps = {},
83
+ label,
84
+ inputProps,
85
+ itemLabel = (item: T) => ("label" in item ? item.label : item.name),
86
+ itemRenderer = (item: T) => itemLabel(item),
87
+ itemProps,
88
+ loadData,
89
+ noMatchesLabel = globalApp?.get("noMatches"),
90
+ gap = 1,
91
+ height = "480px",
92
+ onItemClick,
93
+ ...rest
94
+ } = props;
95
+
96
+ const { onClick, ...buttonRest } = buttonProps;
97
+
98
+ // States
99
+ const [loading, setLoading] = React.useState(false);
100
+ const [items, setItems] = React.useState<T[]>([]);
101
+
102
+ const loadDataLocal = (keyword?: string) => {
103
+ setLoading(true);
104
+
105
+ loadData(keyword).then((result) => {
106
+ setLoading(false);
107
+ setItems(result ?? []);
108
+ });
109
+ };
110
+
111
+ React.useEffect(() => {
112
+ loadDataLocal();
113
+ }, []);
114
+
115
+ // Layout
116
+ return (
117
+ <VBox gap={gap} height={height} {...rest}>
118
+ <InputField
119
+ label={label}
120
+ changeDelay={480}
121
+ onChangeDelay={(event) => {
122
+ // Stop bubble
123
+ event.preventDefault();
124
+ event.stopPropagation();
125
+ loadDataLocal(event.target.value);
126
+ }}
127
+ fullWidth
128
+ {...inputProps}
129
+ />
130
+ {loading ? (
131
+ <LinearProgress />
132
+ ) : items.length === 0 ? (
133
+ <Typography textAlign="center">{noMatchesLabel}</Typography>
134
+ ) : (
135
+ <List>
136
+ {items.map((item) => (
137
+ <ListItem key={item.id} disablePadding {...itemProps}>
138
+ <ListItemButton
139
+ onClick={(event) => {
140
+ if (onClick) onClick(event);
141
+ if (!event.defaultPrevented && onItemClick) onItemClick(item);
142
+ }}
143
+ {...buttonRest}
144
+ >
145
+ {itemRenderer(item)}
146
+ </ListItemButton>
147
+ </ListItem>
148
+ ))}
149
+ </List>
150
+ )}
151
+ </VBox>
152
+ );
153
+ }
@@ -4,12 +4,9 @@ import CheckBoxIcon from "@mui/icons-material/CheckBox";
4
4
  import React from "react";
5
5
  import { InputField, InputFieldProps } from "./InputField";
6
6
  import { globalApp } from "./app/ReactApp";
7
+ import { ListType2 } from "@etsoo/shared";
7
8
 
8
- type DataType = {
9
- id: number | string;
10
- } & ({ label: string } | { name: string });
11
-
12
- export type TagListProProps<D extends DataType = DataType> = Omit<
9
+ export type TagListProProps<D extends ListType2 = ListType2> = Omit<
13
10
  AutocompleteProps<D, true, false, false>,
14
11
  "open" | "multiple" | "options" | "renderInput"
15
12
  > & {
@@ -37,7 +34,7 @@ export type TagListProProps<D extends DataType = DataType> = Omit<
37
34
  maxItems?: number;
38
35
  };
39
36
 
40
- export function TagListPro<D extends DataType = DataType>(
37
+ export function TagListPro<D extends ListType2 = ListType2>(
41
38
  props: TagListProProps<D>
42
39
  ) {
43
40
  // Labels
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ export * from "./OptionGroup";
74
74
  export * from "./PList";
75
75
  export * from "./ProgressCount";
76
76
  export * from "./PullToRefreshUI";
77
+ export * from "./QuickList";
77
78
  export * from "./ResponsibleContainer";
78
79
  export * from "./ScrollerListEx";
79
80
  export * from "./ScrollTopFab";