@etsoo/materialui 1.0.55 → 1.0.56

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,56 @@
1
+ import { DataTypes, DelayedExecutorType, IdDefaultType } from '@etsoo/shared';
2
+ import { ListItemButtonProps, ListProps } from '@mui/material';
3
+ import React from 'react';
4
+ declare type QueryData = {
5
+ title?: string;
6
+ };
7
+ /**
8
+ * List chooser button props
9
+ */
10
+ export interface ListChooserButtonProps<T extends object, D extends DataTypes.Keys<T>> {
11
+ (id: T[D]): ListItemButtonProps;
12
+ }
13
+ /**
14
+ * List chooser props
15
+ */
16
+ export declare type ListChooserProps<T extends object, D extends DataTypes.Keys<T>, Q extends object> = ListProps & {
17
+ /**
18
+ * Condition renderer
19
+ */
20
+ conditionRenderer?: (rq: Partial<Q>, delayed: DelayedExecutorType) => React.ReactNode;
21
+ /**
22
+ * List item renderer
23
+ */
24
+ itemRenderer?: (data: T, props: ListChooserButtonProps<T, D>) => React.ReactNode;
25
+ /**
26
+ * Label field
27
+ */
28
+ labelField?: DataTypes.Keys<T, string> | ((data: T) => string);
29
+ /**
30
+ * Id field
31
+ */
32
+ idField?: D;
33
+ /**
34
+ * Load data callback
35
+ */
36
+ loadData: (rq: Partial<Q>) => PromiseLike<T[] | null | undefined>;
37
+ /**
38
+ * Multiple selected
39
+ */
40
+ multiple?: boolean;
41
+ /**
42
+ * Item onchange callback
43
+ */
44
+ onItemChange: (items: T[], ids: T[D][]) => void;
45
+ /**
46
+ * Title
47
+ */
48
+ title: string;
49
+ };
50
+ /**
51
+ * List chooser
52
+ * @param props Props
53
+ * @returns Component
54
+ */
55
+ export declare function ListChooser<T extends object, D extends DataTypes.Keys<T> = IdDefaultType<T>, Q extends object = QueryData>(props: ListChooserProps<T, D, Q>): JSX.Element;
56
+ export {};
@@ -0,0 +1,79 @@
1
+ import { useDelayedExecutor } from '@etsoo/react';
2
+ import { List, ListItem, ListItemButton, ListItemText, TextField } from '@mui/material';
3
+ import React from 'react';
4
+ import { VBox } from './FlexBox';
5
+ /**
6
+ * List chooser
7
+ * @param props Props
8
+ * @returns Component
9
+ */
10
+ export function ListChooser(props) {
11
+ var _a;
12
+ // Selected ids state
13
+ const [selectedIds, setSelectedIds] = React.useState([]);
14
+ const selectProps = (id) => ({
15
+ selected: selectedIds.includes(id),
16
+ onClick: () => {
17
+ if (multiple) {
18
+ const index = selectedIds.indexOf(id);
19
+ if (index === -1)
20
+ selectedIds.push(id);
21
+ else
22
+ selectedIds.splice(index, 1);
23
+ setSelectedIds([...selectedIds]);
24
+ }
25
+ else {
26
+ setSelectedIds([id]);
27
+ }
28
+ }
29
+ });
30
+ // Destruct
31
+ const { conditionRenderer = (rq, delayed) => (React.createElement(TextField, { autoFocus: true, margin: "dense", name: "title", label: title, fullWidth: true, variant: "standard", inputProps: { maxLength: 128 }, onChange: (event) => {
32
+ Reflect.set(rq, 'title', event.target.value);
33
+ delayed.call();
34
+ } })), itemRenderer = (item, selectProps) => {
35
+ const id = item[idField];
36
+ const label = typeof labelField === 'function'
37
+ ? labelField(item)
38
+ : Reflect.get(item, labelField);
39
+ return (React.createElement(ListItem, { disableGutters: true, key: `${id}` },
40
+ React.createElement(ListItemButton, { ...selectProps(id) },
41
+ React.createElement(ListItemText, { primary: label }))));
42
+ }, idField = 'id', labelField = 'label', loadData, multiple = false, onItemChange, title, ...rest } = props;
43
+ // Default minimum height
44
+ (_a = rest.sx) !== null && _a !== void 0 ? _a : (rest.sx = { minHeight: '220px' });
45
+ // State
46
+ const [items, setItems] = React.useState([]);
47
+ // Query request data
48
+ const mounted = React.useRef(false);
49
+ const rq = React.useRef({});
50
+ // Delayed execution
51
+ const delayed = useDelayedExecutor(async () => {
52
+ const result = await loadData(rq.current);
53
+ if (result == null || !mounted.current)
54
+ return;
55
+ if (!multiple &&
56
+ selectedIds.length > 0 &&
57
+ !result.some((item) => selectedIds.includes(item[idField]))) {
58
+ setSelectedIds([]);
59
+ }
60
+ setItems(result);
61
+ }, 480);
62
+ React.useEffect(() => {
63
+ if (!mounted.current)
64
+ return;
65
+ onItemChange(items.filter((item) => selectedIds.includes(item[idField])), selectedIds);
66
+ }, [selectedIds]);
67
+ React.useEffect(() => {
68
+ mounted.current = true;
69
+ delayed.call(0);
70
+ return () => {
71
+ mounted.current = false;
72
+ delayed.clear();
73
+ };
74
+ }, [delayed]);
75
+ // Layout
76
+ return (React.createElement(VBox, null,
77
+ conditionRenderer(rq.current, delayed),
78
+ React.createElement(List, { disablePadding: true, dense: true, ...rest }, items.map((item) => itemRenderer(item, selectProps)))));
79
+ }
@@ -63,6 +63,10 @@ export declare type OptionGroupProps<T extends object, D extends DataTypes.Keys<
63
63
  * Display group of elements in a compact row
64
64
  */
65
65
  row?: boolean;
66
+ /**
67
+ * Item size
68
+ */
69
+ itemSize?: 'small' | 'medium';
66
70
  };
67
71
  /**
68
72
  * OptionGroup
@@ -8,7 +8,7 @@ import React from 'react';
8
8
  */
9
9
  export function OptionGroup(props) {
10
10
  // Destruct
11
- const { getOptionLabel, defaultValue, idField = 'id', label, labelField = 'label', multiple = false, mRef, name, onValueChange, options, readOnly, row, size, ...rest } = props;
11
+ const { getOptionLabel, defaultValue, idField = 'id', label, labelField = 'label', multiple = false, mRef, name, onValueChange, options, readOnly, row, itemSize, ...rest } = props;
12
12
  // Get option value
13
13
  // D type should be the source id type
14
14
  const getOptionValue = (option) => {
@@ -45,7 +45,7 @@ export function OptionGroup(props) {
45
45
  // Value
46
46
  const ov = getOptionValue(option);
47
47
  // Control
48
- const control = multiple ? (React.createElement(Checkbox, { name: name, readOnly: readOnly, size: size, checked: itemChecked(option), disabled: disabledIds === null || disabledIds === void 0 ? void 0 : disabledIds.includes(ov), onChange: (event) => {
48
+ const control = multiple ? (React.createElement(Checkbox, { name: name, readOnly: readOnly, size: itemSize, checked: itemChecked(option), disabled: disabledIds === null || disabledIds === void 0 ? void 0 : disabledIds.includes(ov), onChange: (event) => {
49
49
  if (firstOptionValue == null)
50
50
  return;
51
51
  const typeValue = Utils.parseString(event.target.value, firstOptionValue);
@@ -64,7 +64,7 @@ export function OptionGroup(props) {
64
64
  if (onValueChange)
65
65
  onValueChange(changedValues);
66
66
  setValues(changedValues);
67
- } })) : (React.createElement(Radio, { disabled: disabledIds === null || disabledIds === void 0 ? void 0 : disabledIds.includes(ov), size: size, readOnly: readOnly }));
67
+ } })) : (React.createElement(Radio, { disabled: disabledIds === null || disabledIds === void 0 ? void 0 : disabledIds.includes(ov), size: itemSize, readOnly: readOnly }));
68
68
  // Label
69
69
  const label = getOptionLabel == null
70
70
  ? `${option[labelField]}`
package/lib/index.d.ts CHANGED
@@ -44,6 +44,7 @@ export * from './HiSelector';
44
44
  export * from './IconButtonLink';
45
45
  export * from './InputField';
46
46
  export * from './ItemList';
47
+ export * from './ListChooser';
47
48
  export * from './ListItemRightIcon';
48
49
  export * from './ListMoreDisplay';
49
50
  export * from './LoadingButton';
package/lib/index.js CHANGED
@@ -44,6 +44,7 @@ export * from './HiSelector';
44
44
  export * from './IconButtonLink';
45
45
  export * from './InputField';
46
46
  export * from './ItemList';
47
+ export * from './ListChooser';
47
48
  export * from './ListItemRightIcon';
48
49
  export * from './ListMoreDisplay';
49
50
  export * from './LoadingButton';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.0.55",
3
+ "version": "1.0.56",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -48,20 +48,20 @@
48
48
  "dependencies": {
49
49
  "@dnd-kit/core": "^6.0.5",
50
50
  "@dnd-kit/sortable": "^7.0.1",
51
- "@emotion/css": "^11.10.0",
52
- "@emotion/react": "^11.10.4",
53
- "@emotion/styled": "^11.10.4",
54
- "@etsoo/appscript": "^1.3.23",
51
+ "@emotion/css": "^11.10.5",
52
+ "@emotion/react": "^11.10.5",
53
+ "@emotion/styled": "^11.10.5",
54
+ "@etsoo/appscript": "^1.3.28",
55
55
  "@etsoo/notificationbase": "^1.1.13",
56
- "@etsoo/react": "^1.6.22",
57
- "@etsoo/shared": "^1.1.67",
56
+ "@etsoo/react": "^1.6.23",
57
+ "@etsoo/shared": "^1.1.70",
58
58
  "@mui/icons-material": "^5.10.9",
59
- "@mui/material": "^5.10.11",
59
+ "@mui/material": "^5.10.12",
60
60
  "@types/pica": "^9.0.1",
61
61
  "@types/pulltorefreshjs": "^0.1.5",
62
- "@types/react": "^18.0.23",
62
+ "@types/react": "^18.0.24",
63
63
  "@types/react-avatar-editor": "^13.0.0",
64
- "@types/react-dom": "^18.0.7",
64
+ "@types/react-dom": "^18.0.8",
65
65
  "@types/react-input-mask": "^3.0.1",
66
66
  "@types/react-window": "^1.8.5",
67
67
  "pica": "^9.0.1",
@@ -71,20 +71,20 @@
71
71
  "react-dom": "^18.2.0",
72
72
  "react-draggable": "^4.4.5",
73
73
  "react-imask": "^6.4.3",
74
- "react-router-dom": "^6.4.2",
75
- "react-window": "^1.8.7"
74
+ "react-router-dom": "^6.4.3",
75
+ "react-window": "^1.8.8"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@babel/cli": "^7.19.3",
79
79
  "@babel/core": "^7.19.6",
80
80
  "@babel/plugin-transform-runtime": "^7.19.6",
81
81
  "@babel/preset-env": "^7.19.4",
82
- "@babel/runtime-corejs3": "^7.19.6",
82
+ "@babel/runtime-corejs3": "^7.20.1",
83
83
  "@testing-library/jest-dom": "^5.16.5",
84
84
  "@testing-library/react": "^13.4.0",
85
- "@types/jest": "^29.2.0",
86
- "@typescript-eslint/eslint-plugin": "^5.41.0",
87
- "@typescript-eslint/parser": "^5.41.0",
85
+ "@types/jest": "^29.2.1",
86
+ "@typescript-eslint/eslint-plugin": "^5.42.0",
87
+ "@typescript-eslint/parser": "^5.42.0",
88
88
  "eslint": "^8.26.0",
89
89
  "eslint-config-airbnb-base": "^15.0.0",
90
90
  "eslint-plugin-import": "^2.26.0",
@@ -0,0 +1,204 @@
1
+ import { useDelayedExecutor } from '@etsoo/react';
2
+ import { DataTypes, DelayedExecutorType, IdDefaultType } from '@etsoo/shared';
3
+ import {
4
+ List,
5
+ ListItem,
6
+ ListItemButton,
7
+ ListItemButtonProps,
8
+ ListItemText,
9
+ ListProps,
10
+ TextField
11
+ } from '@mui/material';
12
+ import React from 'react';
13
+ import { VBox } from './FlexBox';
14
+
15
+ type QueryData = {
16
+ title?: string;
17
+ };
18
+
19
+ /**
20
+ * List chooser button props
21
+ */
22
+ export interface ListChooserButtonProps<
23
+ T extends object,
24
+ D extends DataTypes.Keys<T>
25
+ > {
26
+ (id: T[D]): ListItemButtonProps;
27
+ }
28
+
29
+ /**
30
+ * List chooser props
31
+ */
32
+ export type ListChooserProps<
33
+ T extends object,
34
+ D extends DataTypes.Keys<T>,
35
+ Q extends object
36
+ > = ListProps & {
37
+ /**
38
+ * Condition renderer
39
+ */
40
+ conditionRenderer?: (
41
+ rq: Partial<Q>,
42
+ delayed: DelayedExecutorType
43
+ ) => React.ReactNode;
44
+
45
+ /**
46
+ * List item renderer
47
+ */
48
+ itemRenderer?: (
49
+ data: T,
50
+ props: ListChooserButtonProps<T, D>
51
+ ) => React.ReactNode;
52
+
53
+ /**
54
+ * Label field
55
+ */
56
+ labelField?: DataTypes.Keys<T, string> | ((data: T) => string);
57
+
58
+ /**
59
+ * Id field
60
+ */
61
+ idField?: D;
62
+
63
+ /**
64
+ * Load data callback
65
+ */
66
+ loadData: (rq: Partial<Q>) => PromiseLike<T[] | null | undefined>;
67
+
68
+ /**
69
+ * Multiple selected
70
+ */
71
+ multiple?: boolean;
72
+
73
+ /**
74
+ * Item onchange callback
75
+ */
76
+ onItemChange: (items: T[], ids: T[D][]) => void;
77
+
78
+ /**
79
+ * Title
80
+ */
81
+ title: string;
82
+ };
83
+
84
+ /**
85
+ * List chooser
86
+ * @param props Props
87
+ * @returns Component
88
+ */
89
+ export function ListChooser<
90
+ T extends object,
91
+ D extends DataTypes.Keys<T> = IdDefaultType<T>,
92
+ Q extends object = QueryData
93
+ >(props: ListChooserProps<T, D, Q>) {
94
+ // Selected ids state
95
+ const [selectedIds, setSelectedIds] = React.useState<T[D][]>([]);
96
+
97
+ const selectProps: ListChooserButtonProps<T, D> = (id: T[D]) => ({
98
+ selected: selectedIds.includes(id),
99
+ onClick: () => {
100
+ if (multiple) {
101
+ const index = selectedIds.indexOf(id);
102
+ if (index === -1) selectedIds.push(id);
103
+ else selectedIds.splice(index, 1);
104
+ setSelectedIds([...selectedIds]);
105
+ } else {
106
+ setSelectedIds([id]);
107
+ }
108
+ }
109
+ });
110
+
111
+ // Destruct
112
+ const {
113
+ conditionRenderer = (rq: Partial<Q>, delayed: DelayedExecutorType) => (
114
+ <TextField
115
+ autoFocus
116
+ margin="dense"
117
+ name="title"
118
+ label={title}
119
+ fullWidth
120
+ variant="standard"
121
+ inputProps={{ maxLength: 128 }}
122
+ onChange={(event) => {
123
+ Reflect.set(rq, 'title', event.target.value);
124
+ delayed.call();
125
+ }}
126
+ />
127
+ ),
128
+ itemRenderer = (item, selectProps) => {
129
+ const id = item[idField];
130
+ const label =
131
+ typeof labelField === 'function'
132
+ ? labelField(item)
133
+ : Reflect.get(item, labelField);
134
+
135
+ return (
136
+ <ListItem disableGutters key={`${id}`}>
137
+ <ListItemButton {...selectProps(id)}>
138
+ <ListItemText primary={label} />
139
+ </ListItemButton>
140
+ </ListItem>
141
+ );
142
+ },
143
+ idField = 'id' as D,
144
+ labelField = 'label',
145
+ loadData,
146
+ multiple = false,
147
+ onItemChange,
148
+ title,
149
+ ...rest
150
+ } = props;
151
+
152
+ // Default minimum height
153
+ rest.sx ??= { minHeight: '220px' };
154
+
155
+ // State
156
+ const [items, setItems] = React.useState<T[]>([]);
157
+
158
+ // Query request data
159
+ const mounted = React.useRef<boolean>(false);
160
+ const rq = React.useRef<Partial<Q>>({});
161
+
162
+ // Delayed execution
163
+ const delayed = useDelayedExecutor(async () => {
164
+ const result = await loadData(rq.current);
165
+ if (result == null || !mounted.current) return;
166
+
167
+ if (
168
+ !multiple &&
169
+ selectedIds.length > 0 &&
170
+ !result.some((item) => selectedIds.includes(item[idField]))
171
+ ) {
172
+ setSelectedIds([]);
173
+ }
174
+
175
+ setItems(result);
176
+ }, 480);
177
+
178
+ React.useEffect(() => {
179
+ if (!mounted.current) return;
180
+ onItemChange(
181
+ items.filter((item) => selectedIds.includes(item[idField])),
182
+ selectedIds
183
+ );
184
+ }, [selectedIds]);
185
+
186
+ React.useEffect(() => {
187
+ mounted.current = true;
188
+ delayed.call(0);
189
+ return () => {
190
+ mounted.current = false;
191
+ delayed.clear();
192
+ };
193
+ }, [delayed]);
194
+
195
+ // Layout
196
+ return (
197
+ <VBox>
198
+ {conditionRenderer(rq.current, delayed)}
199
+ <List disablePadding dense {...rest}>
200
+ {items.map((item) => itemRenderer(item, selectProps))}
201
+ </List>
202
+ </VBox>
203
+ );
204
+ }
@@ -95,6 +95,11 @@ export type OptionGroupProps<
95
95
  * Display group of elements in a compact row
96
96
  */
97
97
  row?: boolean;
98
+
99
+ /**
100
+ * Item size
101
+ */
102
+ itemSize?: 'small' | 'medium';
98
103
  };
99
104
 
100
105
  /**
@@ -121,7 +126,7 @@ export function OptionGroup<
121
126
  options,
122
127
  readOnly,
123
128
  row,
124
- size,
129
+ itemSize,
125
130
  ...rest
126
131
  } = props;
127
132
 
@@ -173,7 +178,7 @@ export function OptionGroup<
173
178
  <Checkbox
174
179
  name={name}
175
180
  readOnly={readOnly}
176
- size={size}
181
+ size={itemSize}
177
182
  checked={itemChecked(option)}
178
183
  disabled={disabledIds?.includes(ov)}
179
184
  onChange={(event) => {
@@ -203,7 +208,7 @@ export function OptionGroup<
203
208
  ) : (
204
209
  <Radio
205
210
  disabled={disabledIds?.includes(ov)}
206
- size={size}
211
+ size={itemSize}
207
212
  readOnly={readOnly}
208
213
  />
209
214
  );
package/src/index.ts CHANGED
@@ -47,6 +47,7 @@ export * from './HiSelector';
47
47
  export * from './IconButtonLink';
48
48
  export * from './InputField';
49
49
  export * from './ItemList';
50
+ export * from './ListChooser';
50
51
  export * from './ListItemRightIcon';
51
52
  export * from './ListMoreDisplay';
52
53
  export * from './LoadingButton';