@etsoo/react 1.3.91 → 1.3.95
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/app/ServiceApp.d.ts +1 -1
- package/lib/app/ServiceApp.js +0 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/mu/DnDList.d.ts +49 -0
- package/lib/mu/DnDList.js +74 -0
- package/package.json +8 -6
- package/src/app/ServiceApp.ts +1 -3
- package/src/index.ts +1 -0
- package/src/mu/DnDList.tsx +211 -0
package/lib/app/ServiceApp.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { RefreshTokenRQ } from './RefreshTokenRQ';
|
|
|
11
11
|
* Use the acess token to the service api, get a service access token
|
|
12
12
|
* Use the new acess token and refresh token to login
|
|
13
13
|
*/
|
|
14
|
-
export declare class ServiceApp<
|
|
14
|
+
export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends IServicePageData = IServicePageData, S extends IServiceAppSettings = IServiceAppSettings> extends ReactApp<S, U, P> {
|
|
15
15
|
/**
|
|
16
16
|
* Service API
|
|
17
17
|
*/
|
package/lib/app/ServiceApp.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export * from './mu/CustomFabProps';
|
|
|
38
38
|
export * from './mu/DataGridEx';
|
|
39
39
|
export * from './mu/DataGridRenderers';
|
|
40
40
|
export * from './mu/DialogButton';
|
|
41
|
+
export * from './mu/DnDList';
|
|
41
42
|
export * from './mu/DraggablePaperComponent';
|
|
42
43
|
export * from './mu/EmailInput';
|
|
43
44
|
export * from './mu/FabBox';
|
package/lib/index.js
CHANGED
|
@@ -41,6 +41,7 @@ export * from './mu/CustomFabProps';
|
|
|
41
41
|
export * from './mu/DataGridEx';
|
|
42
42
|
export * from './mu/DataGridRenderers';
|
|
43
43
|
export * from './mu/DialogButton';
|
|
44
|
+
export * from './mu/DnDList';
|
|
44
45
|
export * from './mu/DraggablePaperComponent';
|
|
45
46
|
export * from './mu/EmailInput';
|
|
46
47
|
export * from './mu/FabBox';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { CSSProperties } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* DnD list props
|
|
4
|
+
*/
|
|
5
|
+
export interface DnDListProps<D extends {}, L extends keyof D, E extends React.ElementType> {
|
|
6
|
+
/**
|
|
7
|
+
* Item renderer
|
|
8
|
+
*/
|
|
9
|
+
children: (item: D, index: number, onDelete: (index: number) => void) => React.ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* List container component
|
|
12
|
+
* https://javascript.plainenglish.io/building-a-polymorphic-component-in-react-and-typescript-d9f236950af4
|
|
13
|
+
*/
|
|
14
|
+
Component?: E;
|
|
15
|
+
/**
|
|
16
|
+
* List container props
|
|
17
|
+
*/
|
|
18
|
+
componentProps?: React.ComponentProps<E>;
|
|
19
|
+
/**
|
|
20
|
+
* Get list item style callback
|
|
21
|
+
*/
|
|
22
|
+
getItemStyle?: (isDragging: boolean) => CSSProperties;
|
|
23
|
+
/**
|
|
24
|
+
* Get list style callback
|
|
25
|
+
*/
|
|
26
|
+
getListStyle?: (isDraggingOver: boolean) => CSSProperties;
|
|
27
|
+
/**
|
|
28
|
+
* Label field
|
|
29
|
+
*/
|
|
30
|
+
labelField: L;
|
|
31
|
+
/**
|
|
32
|
+
* Load data
|
|
33
|
+
*/
|
|
34
|
+
loadData: (name: string) => PromiseLike<D[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Top and bottom sides renderer
|
|
37
|
+
*/
|
|
38
|
+
sideRenderer?: (top: boolean, onAdd: (item: D) => boolean) => React.ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Name for hidden form input
|
|
41
|
+
*/
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Drag and drop list
|
|
46
|
+
* @param props Props
|
|
47
|
+
* @returns Component
|
|
48
|
+
*/
|
|
49
|
+
export declare function DnDList<D extends {}, L extends keyof D, E extends React.ElementType = React.ElementType>(props: DnDListProps<D, L, E>): JSX.Element;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
|
4
|
+
/**
|
|
5
|
+
* Drag and drop list
|
|
6
|
+
* @param props Props
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export function DnDList(props) {
|
|
10
|
+
// Destruct
|
|
11
|
+
const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, sideRenderer } = props;
|
|
12
|
+
// State
|
|
13
|
+
const [items, setItems] = React.useState([]);
|
|
14
|
+
// Drag end handler
|
|
15
|
+
const onDragEnd = (result) => {
|
|
16
|
+
console.log(result);
|
|
17
|
+
// Dropped outside the list
|
|
18
|
+
if (!result.destination) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Clone
|
|
22
|
+
const newItems = [...items];
|
|
23
|
+
// Removed item
|
|
24
|
+
const [removed] = newItems.splice(result.source.index, 1);
|
|
25
|
+
// Insert to the destination index
|
|
26
|
+
newItems.splice(result.destination.index, 0, removed);
|
|
27
|
+
// Update the state
|
|
28
|
+
setItems(newItems);
|
|
29
|
+
};
|
|
30
|
+
// Add handler
|
|
31
|
+
const onAdd = (newItem) => {
|
|
32
|
+
// Existence check
|
|
33
|
+
if (items.some((item) => item[labelField] == newItem[labelField])) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
// Clone
|
|
37
|
+
const newItems = [newItem, ...items];
|
|
38
|
+
// Update the state
|
|
39
|
+
setItems(newItems);
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
// Delete handler
|
|
43
|
+
const onDelete = (index) => {
|
|
44
|
+
// Clone
|
|
45
|
+
const newItems = [...items];
|
|
46
|
+
// Remove the item
|
|
47
|
+
newItems.splice(index, 1);
|
|
48
|
+
// Update the state
|
|
49
|
+
setItems(newItems);
|
|
50
|
+
};
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
loadData(name).then((items) => setItems(items));
|
|
53
|
+
}, [name]);
|
|
54
|
+
// Layout
|
|
55
|
+
return (React.createElement(React.Fragment, null,
|
|
56
|
+
sideRenderer && sideRenderer(true, onAdd),
|
|
57
|
+
React.createElement(Component, { ...componentProps },
|
|
58
|
+
React.createElement(DragDropContext, { onDragEnd: onDragEnd },
|
|
59
|
+
React.createElement(Droppable, { droppableId: name }, (provided, snapshot) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, style: getListStyle(snapshot.isDraggingOver) },
|
|
60
|
+
items.map((item, index) => {
|
|
61
|
+
// Id
|
|
62
|
+
const id = DataTypes.convert(item[labelField], 'string');
|
|
63
|
+
if (id == null)
|
|
64
|
+
return;
|
|
65
|
+
return (React.createElement(Draggable, { key: id, draggableId: id, index: index }, (provided, snapshot) => (React.createElement("div", { ref: provided.innerRef, ...provided.draggableProps, ...provided.dragHandleProps, style: {
|
|
66
|
+
...getItemStyle(snapshot.isDragging),
|
|
67
|
+
...provided
|
|
68
|
+
.draggableProps
|
|
69
|
+
.style
|
|
70
|
+
} }, children(item, index, onDelete)))));
|
|
71
|
+
}),
|
|
72
|
+
provided.placeholder))))),
|
|
73
|
+
sideRenderer && sideRenderer(false, onAdd)));
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.95",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -50,17 +50,18 @@
|
|
|
50
50
|
"@emotion/react": "^11.7.1",
|
|
51
51
|
"@emotion/style": "^0.8.0",
|
|
52
52
|
"@emotion/styled": "^11.6.0",
|
|
53
|
-
"@etsoo/appscript": "^1.2.
|
|
53
|
+
"@etsoo/appscript": "^1.2.11",
|
|
54
54
|
"@etsoo/notificationbase": "^1.0.95",
|
|
55
|
-
"@etsoo/shared": "^1.0.
|
|
55
|
+
"@etsoo/shared": "^1.0.95",
|
|
56
56
|
"@mui/icons-material": "^5.2.5",
|
|
57
|
-
"@mui/material": "^5.2.
|
|
57
|
+
"@mui/material": "^5.2.6",
|
|
58
58
|
"@reach/router": "^1.3.4",
|
|
59
59
|
"@types/pica": "^5.1.3",
|
|
60
60
|
"@types/pulltorefreshjs": "^0.1.5",
|
|
61
61
|
"@types/reach__router": "^1.3.10",
|
|
62
62
|
"@types/react": "^17.0.38",
|
|
63
63
|
"@types/react-avatar-editor": "^10.3.6",
|
|
64
|
+
"@types/react-beautiful-dnd": "^13.1.2",
|
|
64
65
|
"@types/react-dom": "^17.0.11",
|
|
65
66
|
"@types/react-input-mask": "^3.0.1",
|
|
66
67
|
"@types/react-window": "^1.8.5",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"pulltorefreshjs": "^0.1.22",
|
|
69
70
|
"react": "^17.0.2",
|
|
70
71
|
"react-avatar-editor": "^12.0.0",
|
|
72
|
+
"react-beautiful-dnd": "^13.1.0",
|
|
71
73
|
"react-dom": "^17.0.2",
|
|
72
74
|
"react-draggable": "^4.4.4",
|
|
73
75
|
"react-imask": "^6.2.2",
|
|
@@ -81,8 +83,8 @@
|
|
|
81
83
|
"@babel/runtime-corejs3": "^7.16.5",
|
|
82
84
|
"@types/jest": "^27.0.3",
|
|
83
85
|
"@types/react-test-renderer": "^17.0.1",
|
|
84
|
-
"@typescript-eslint/eslint-plugin": "^5.8.
|
|
85
|
-
"@typescript-eslint/parser": "^5.8.
|
|
86
|
+
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
|
87
|
+
"@typescript-eslint/parser": "^5.8.1",
|
|
86
88
|
"eslint": "^8.5.0",
|
|
87
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
88
90
|
"eslint-plugin-import": "^2.25.3",
|
package/src/app/ServiceApp.ts
CHANGED
|
@@ -21,8 +21,8 @@ import { RefreshTokenRQ } from './RefreshTokenRQ';
|
|
|
21
21
|
* Use the new acess token and refresh token to login
|
|
22
22
|
*/
|
|
23
23
|
export class ServiceApp<
|
|
24
|
-
P extends IServicePageData = IServicePageData,
|
|
25
24
|
U extends IServiceUser = IServiceUser,
|
|
25
|
+
P extends IServicePageData = IServicePageData,
|
|
26
26
|
S extends IServiceAppSettings = IServiceAppSettings
|
|
27
27
|
> extends ReactApp<S, U, P> {
|
|
28
28
|
/**
|
|
@@ -76,8 +76,6 @@ export class ServiceApp<
|
|
|
76
76
|
coreUrl +
|
|
77
77
|
'?serviceId=' +
|
|
78
78
|
this.settings.serviceId +
|
|
79
|
-
'&serviceDeviceId=' +
|
|
80
|
-
encodeURIComponent(this.deviceId) +
|
|
81
79
|
'&' +
|
|
82
80
|
DomUtils.CultureField +
|
|
83
81
|
'=' +
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ export * from './mu/CustomFabProps';
|
|
|
45
45
|
export * from './mu/DataGridEx';
|
|
46
46
|
export * from './mu/DataGridRenderers';
|
|
47
47
|
export * from './mu/DialogButton';
|
|
48
|
+
export * from './mu/DnDList';
|
|
48
49
|
export * from './mu/DraggablePaperComponent';
|
|
49
50
|
export * from './mu/EmailInput';
|
|
50
51
|
export * from './mu/FabBox';
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
2
|
+
import React, { CSSProperties } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
DragDropContext,
|
|
5
|
+
Draggable,
|
|
6
|
+
Droppable,
|
|
7
|
+
DropResult
|
|
8
|
+
} from 'react-beautiful-dnd';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DnD list props
|
|
12
|
+
*/
|
|
13
|
+
export interface DnDListProps<
|
|
14
|
+
D extends {},
|
|
15
|
+
L extends keyof D,
|
|
16
|
+
E extends React.ElementType
|
|
17
|
+
> {
|
|
18
|
+
/**
|
|
19
|
+
* Item renderer
|
|
20
|
+
*/
|
|
21
|
+
children: (
|
|
22
|
+
item: D,
|
|
23
|
+
index: number,
|
|
24
|
+
onDelete: (index: number) => void
|
|
25
|
+
) => React.ReactNode;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* List container component
|
|
29
|
+
* https://javascript.plainenglish.io/building-a-polymorphic-component-in-react-and-typescript-d9f236950af4
|
|
30
|
+
*/
|
|
31
|
+
Component?: E;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* List container props
|
|
35
|
+
*/
|
|
36
|
+
componentProps?: React.ComponentProps<E>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get list item style callback
|
|
40
|
+
*/
|
|
41
|
+
getItemStyle?: (isDragging: boolean) => CSSProperties;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get list style callback
|
|
45
|
+
*/
|
|
46
|
+
getListStyle?: (isDraggingOver: boolean) => CSSProperties;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Label field
|
|
50
|
+
*/
|
|
51
|
+
labelField: L;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load data
|
|
55
|
+
*/
|
|
56
|
+
loadData: (name: string) => PromiseLike<D[]>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Top and bottom sides renderer
|
|
60
|
+
*/
|
|
61
|
+
sideRenderer?: (
|
|
62
|
+
top: boolean,
|
|
63
|
+
onAdd: (item: D) => boolean
|
|
64
|
+
) => React.ReactNode;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Name for hidden form input
|
|
68
|
+
*/
|
|
69
|
+
name: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Drag and drop list
|
|
74
|
+
* @param props Props
|
|
75
|
+
* @returns Component
|
|
76
|
+
*/
|
|
77
|
+
export function DnDList<
|
|
78
|
+
D extends {},
|
|
79
|
+
L extends keyof D,
|
|
80
|
+
E extends React.ElementType = React.ElementType
|
|
81
|
+
>(props: DnDListProps<D, L, E>) {
|
|
82
|
+
// Destruct
|
|
83
|
+
const {
|
|
84
|
+
children,
|
|
85
|
+
Component = 'div',
|
|
86
|
+
componentProps,
|
|
87
|
+
getItemStyle = (_isDragging) => ({}),
|
|
88
|
+
getListStyle = () => undefined,
|
|
89
|
+
labelField,
|
|
90
|
+
loadData,
|
|
91
|
+
name,
|
|
92
|
+
sideRenderer
|
|
93
|
+
} = props;
|
|
94
|
+
|
|
95
|
+
// State
|
|
96
|
+
const [items, setItems] = React.useState<D[]>([]);
|
|
97
|
+
|
|
98
|
+
// Drag end handler
|
|
99
|
+
const onDragEnd = (result: DropResult) => {
|
|
100
|
+
console.log(result);
|
|
101
|
+
// Dropped outside the list
|
|
102
|
+
if (!result.destination) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Clone
|
|
107
|
+
const newItems = [...items];
|
|
108
|
+
|
|
109
|
+
// Removed item
|
|
110
|
+
const [removed] = newItems.splice(result.source.index, 1);
|
|
111
|
+
|
|
112
|
+
// Insert to the destination index
|
|
113
|
+
newItems.splice(result.destination.index, 0, removed);
|
|
114
|
+
|
|
115
|
+
// Update the state
|
|
116
|
+
setItems(newItems);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Add handler
|
|
120
|
+
const onAdd = (newItem: D) => {
|
|
121
|
+
// Existence check
|
|
122
|
+
if (items.some((item) => item[labelField] == newItem[labelField])) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Clone
|
|
127
|
+
const newItems = [newItem, ...items];
|
|
128
|
+
|
|
129
|
+
// Update the state
|
|
130
|
+
setItems(newItems);
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Delete handler
|
|
136
|
+
const onDelete = (index: number) => {
|
|
137
|
+
// Clone
|
|
138
|
+
const newItems = [...items];
|
|
139
|
+
|
|
140
|
+
// Remove the item
|
|
141
|
+
newItems.splice(index, 1);
|
|
142
|
+
|
|
143
|
+
// Update the state
|
|
144
|
+
setItems(newItems);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
React.useEffect(() => {
|
|
148
|
+
loadData(name).then((items) => setItems(items));
|
|
149
|
+
}, [name]);
|
|
150
|
+
|
|
151
|
+
// Layout
|
|
152
|
+
return (
|
|
153
|
+
<React.Fragment>
|
|
154
|
+
{sideRenderer && sideRenderer(true, onAdd)}
|
|
155
|
+
<Component {...componentProps}>
|
|
156
|
+
<DragDropContext onDragEnd={onDragEnd}>
|
|
157
|
+
<Droppable droppableId={name}>
|
|
158
|
+
{(provided, snapshot) => (
|
|
159
|
+
<div
|
|
160
|
+
{...provided.droppableProps}
|
|
161
|
+
ref={provided.innerRef}
|
|
162
|
+
style={getListStyle(snapshot.isDraggingOver)}
|
|
163
|
+
>
|
|
164
|
+
{items.map((item, index) => {
|
|
165
|
+
// Id
|
|
166
|
+
const id = DataTypes.convert(
|
|
167
|
+
item[labelField],
|
|
168
|
+
'string'
|
|
169
|
+
);
|
|
170
|
+
if (id == null) return;
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<Draggable
|
|
174
|
+
key={id}
|
|
175
|
+
draggableId={id}
|
|
176
|
+
index={index}
|
|
177
|
+
>
|
|
178
|
+
{(provided, snapshot) => (
|
|
179
|
+
<div
|
|
180
|
+
ref={provided.innerRef}
|
|
181
|
+
{...provided.draggableProps}
|
|
182
|
+
{...provided.dragHandleProps}
|
|
183
|
+
style={{
|
|
184
|
+
...getItemStyle(
|
|
185
|
+
snapshot.isDragging
|
|
186
|
+
),
|
|
187
|
+
...provided
|
|
188
|
+
.draggableProps
|
|
189
|
+
.style
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
{children(
|
|
193
|
+
item,
|
|
194
|
+
index,
|
|
195
|
+
onDelete
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</Draggable>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
{provided.placeholder}
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
</Droppable>
|
|
206
|
+
</DragDropContext>
|
|
207
|
+
</Component>
|
|
208
|
+
{sideRenderer && sideRenderer(false, onAdd)}
|
|
209
|
+
</React.Fragment>
|
|
210
|
+
);
|
|
211
|
+
}
|