@refinedev/antd 5.27.0 → 5.29.0
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/CHANGELOG.md +72 -0
- package/dist/components/autoSaveIndicator/index.d.ts +4 -0
- package/dist/components/autoSaveIndicator/index.d.ts.map +1 -0
- package/dist/components/crud/edit/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/hooks/form/useForm.d.ts +10 -4
- package/dist/hooks/form/useForm.d.ts.map +1 -1
- package/dist/iife/index.js +14 -6
- package/dist/iife/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/autoSaveIndicator/index.tsx +54 -0
- package/src/components/crud/edit/index.tsx +3 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/form/useForm.ts +104 -7
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@refinedev/antd",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.29.0",
|
4
4
|
"description": "refine is a React-based framework for building internal tools, rapidly. It ships with Ant Design System, an enterprise-level UI toolkit.",
|
5
5
|
"private": false,
|
6
6
|
"main": "dist/index.js",
|
@@ -24,8 +24,8 @@
|
|
24
24
|
},
|
25
25
|
"devDependencies": {
|
26
26
|
"@refinedev/cli": "^2.7.0",
|
27
|
-
"@refinedev/ui-tests": "^1.
|
28
|
-
"@refinedev/core": "^4.
|
27
|
+
"@refinedev/ui-tests": "^1.10.0",
|
28
|
+
"@refinedev/core": "^4.27.0",
|
29
29
|
"@esbuild-plugins/node-resolve": "^0.1.4",
|
30
30
|
"@testing-library/jest-dom": "^5.16.4",
|
31
31
|
"@testing-library/react": "^13.1.1",
|
@@ -49,7 +49,7 @@
|
|
49
49
|
"dependencies": {
|
50
50
|
"@ant-design/icons": "5.0.1",
|
51
51
|
"@ant-design/pro-layout": "7.8.3",
|
52
|
-
"@refinedev/ui-types": "^1.
|
52
|
+
"@refinedev/ui-types": "^1.19.0",
|
53
53
|
"@tanstack/react-query": "^4.10.1",
|
54
54
|
"antd": "^5.0.5",
|
55
55
|
"dayjs": "^1.10.7",
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import { AutoSaveIndicatorProps, useTranslate } from "@refinedev/core";
|
3
|
+
import { Typography, theme } from "antd";
|
4
|
+
import {
|
5
|
+
EllipsisOutlined,
|
6
|
+
SyncOutlined,
|
7
|
+
CheckCircleOutlined,
|
8
|
+
ExclamationCircleOutlined,
|
9
|
+
} from "@ant-design/icons";
|
10
|
+
|
11
|
+
export const AutoSaveIndicator: React.FC<AutoSaveIndicatorProps> = ({
|
12
|
+
status,
|
13
|
+
}) => {
|
14
|
+
const translate = useTranslate();
|
15
|
+
const { useToken } = theme;
|
16
|
+
const { token } = useToken();
|
17
|
+
|
18
|
+
let message = null;
|
19
|
+
let icon = <EllipsisOutlined />;
|
20
|
+
|
21
|
+
switch (status) {
|
22
|
+
case "success":
|
23
|
+
message = translate("autoSave.success", "saved");
|
24
|
+
icon = <CheckCircleOutlined />;
|
25
|
+
break;
|
26
|
+
case "error":
|
27
|
+
message = translate("autoSave.error", "auto save failure");
|
28
|
+
icon = <ExclamationCircleOutlined />;
|
29
|
+
|
30
|
+
break;
|
31
|
+
case "loading":
|
32
|
+
message = translate("autoSave.loading", "saving...");
|
33
|
+
icon = <SyncOutlined />;
|
34
|
+
|
35
|
+
break;
|
36
|
+
default:
|
37
|
+
// for idle
|
38
|
+
message = translate("autoSave.idle", "waiting for changes");
|
39
|
+
break;
|
40
|
+
}
|
41
|
+
|
42
|
+
return (
|
43
|
+
<Typography.Text
|
44
|
+
style={{
|
45
|
+
marginRight: 5,
|
46
|
+
color: token.colorTextTertiary,
|
47
|
+
fontSize: ".8rem",
|
48
|
+
}}
|
49
|
+
>
|
50
|
+
{message}
|
51
|
+
<span style={{ marginLeft: ".2rem" }}>{icon}</span>
|
52
|
+
</Typography.Text>
|
53
|
+
);
|
54
|
+
};
|
@@ -25,6 +25,7 @@ import {
|
|
25
25
|
RefreshButtonProps,
|
26
26
|
DeleteButtonProps,
|
27
27
|
SaveButtonProps,
|
28
|
+
AutoSaveIndicator,
|
28
29
|
} from "@components";
|
29
30
|
import { EditProps } from "../types";
|
30
31
|
|
@@ -54,6 +55,7 @@ export const Edit: React.FC<EditProps> = ({
|
|
54
55
|
footerButtonProps,
|
55
56
|
footerButtons,
|
56
57
|
goBack: goBackFromProps,
|
58
|
+
autoSaveProps,
|
57
59
|
}) => {
|
58
60
|
const translate = useTranslate();
|
59
61
|
const { options: { breadcrumb: globalBreadcrumb } = {} } =
|
@@ -133,6 +135,7 @@ export const Edit: React.FC<EditProps> = ({
|
|
133
135
|
|
134
136
|
const defaultHeaderButtons = (
|
135
137
|
<>
|
138
|
+
{autoSaveProps && <AutoSaveIndicator {...autoSaveProps} />}
|
136
139
|
{hasList && <ListButton {...listButtonProps} />}
|
137
140
|
<RefreshButton {...refreshButtonProps} />
|
138
141
|
</>
|
package/src/components/index.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from "react";
|
2
|
-
import { FormInstance, FormProps, Form } from "antd";
|
2
|
+
import { FormInstance, FormProps, Form, ButtonProps } from "antd";
|
3
3
|
import { useForm as useFormSF } from "sunflower-antd";
|
4
|
-
import {
|
4
|
+
import { AutoSaveProps } from "@refinedev/core";
|
5
5
|
|
6
6
|
import {
|
7
7
|
HttpError,
|
@@ -13,6 +13,8 @@ import {
|
|
13
13
|
CreateResponse,
|
14
14
|
UpdateResponse,
|
15
15
|
pickNotDeprecated,
|
16
|
+
useTranslate,
|
17
|
+
useRefineContext,
|
16
18
|
} from "@refinedev/core";
|
17
19
|
|
18
20
|
export type UseFormProps<
|
@@ -35,7 +37,13 @@ export type UseFormProps<
|
|
35
37
|
* Shows notification when unsaved changes exist
|
36
38
|
*/
|
37
39
|
warnWhenUnsavedChanges?: boolean;
|
38
|
-
|
40
|
+
/**
|
41
|
+
* Disables server-side validation
|
42
|
+
* @default false
|
43
|
+
* @see {@link https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/}
|
44
|
+
*/
|
45
|
+
disableServerSideValidation?: boolean;
|
46
|
+
} & AutoSaveProps<TVariables>;
|
39
47
|
|
40
48
|
export type UseFormReturnType<
|
41
49
|
TQueryFnData extends BaseRecord = BaseRecord,
|
@@ -87,7 +95,8 @@ export const useForm = <
|
|
87
95
|
action,
|
88
96
|
resource,
|
89
97
|
onMutationSuccess: onMutationSuccessProp,
|
90
|
-
onMutationError,
|
98
|
+
onMutationError: onMutationErrorProp,
|
99
|
+
autoSave,
|
91
100
|
submitOnEnter = false,
|
92
101
|
warnWhenUnsavedChanges: warnWhenUnsavedChangesProp,
|
93
102
|
redirect,
|
@@ -109,6 +118,7 @@ export const useForm = <
|
|
109
118
|
updateMutationOptions,
|
110
119
|
id: idFromProps,
|
111
120
|
overtimeOptions,
|
121
|
+
disableServerSideValidation: disableServerSideValidationProp = false,
|
112
122
|
}: UseFormProps<
|
113
123
|
TQueryFnData,
|
114
124
|
TError,
|
@@ -124,6 +134,12 @@ export const useForm = <
|
|
124
134
|
TResponse,
|
125
135
|
TResponseError
|
126
136
|
> => {
|
137
|
+
const { options } = useRefineContext();
|
138
|
+
const disableServerSideValidation =
|
139
|
+
options?.disableServerSideValidation || disableServerSideValidationProp;
|
140
|
+
|
141
|
+
const translate = useTranslate();
|
142
|
+
|
127
143
|
const [formAnt] = Form.useForm();
|
128
144
|
const formSF = useFormSF<TResponse, TVariables>({
|
129
145
|
form: formAnt,
|
@@ -141,7 +157,77 @@ export const useForm = <
|
|
141
157
|
onMutationSuccess: onMutationSuccessProp
|
142
158
|
? onMutationSuccessProp
|
143
159
|
: undefined,
|
144
|
-
onMutationError,
|
160
|
+
onMutationError: async (error, _variables, _context) => {
|
161
|
+
if (disableServerSideValidation) {
|
162
|
+
onMutationErrorProp?.(error, _variables, _context);
|
163
|
+
return;
|
164
|
+
}
|
165
|
+
|
166
|
+
// antd form expects error object to be in a specific format.
|
167
|
+
let parsedErrors: {
|
168
|
+
name: string | number | (string | number)[];
|
169
|
+
errors?: string[] | undefined;
|
170
|
+
}[] = [];
|
171
|
+
|
172
|
+
// reset antd errors before setting new errors
|
173
|
+
const fieldsValue = form.getFieldsValue() as unknown as object;
|
174
|
+
const fields = Object.keys(fieldsValue);
|
175
|
+
parsedErrors = fields.map((field) => {
|
176
|
+
return {
|
177
|
+
name: field,
|
178
|
+
errors: undefined,
|
179
|
+
};
|
180
|
+
});
|
181
|
+
form.setFields(parsedErrors);
|
182
|
+
|
183
|
+
const errors = error?.errors;
|
184
|
+
// parse errors to antd form errors
|
185
|
+
for (const key in errors) {
|
186
|
+
const fieldError = errors[key];
|
187
|
+
|
188
|
+
let newError: string[] = [];
|
189
|
+
|
190
|
+
if (Array.isArray(fieldError)) {
|
191
|
+
newError = fieldError;
|
192
|
+
}
|
193
|
+
|
194
|
+
if (typeof fieldError === "string") {
|
195
|
+
newError = [fieldError];
|
196
|
+
}
|
197
|
+
|
198
|
+
if (typeof fieldError === "boolean" && fieldError) {
|
199
|
+
newError = ["Field is not valid."];
|
200
|
+
}
|
201
|
+
|
202
|
+
if (typeof fieldError === "object" && "key" in fieldError) {
|
203
|
+
const translatedMessage = translate(
|
204
|
+
fieldError.key,
|
205
|
+
fieldError.message,
|
206
|
+
);
|
207
|
+
|
208
|
+
newError = [translatedMessage];
|
209
|
+
}
|
210
|
+
|
211
|
+
// antd form expects the key to be an array.
|
212
|
+
// if the key is a number, it will be parsed to a number because.
|
213
|
+
const newKey = key.split(".").map((item) => {
|
214
|
+
// check if item is a number
|
215
|
+
if (!isNaN(Number(item))) {
|
216
|
+
return Number(item);
|
217
|
+
}
|
218
|
+
return item;
|
219
|
+
});
|
220
|
+
|
221
|
+
parsedErrors.push({
|
222
|
+
name: newKey,
|
223
|
+
errors: newError,
|
224
|
+
});
|
225
|
+
}
|
226
|
+
|
227
|
+
form.setFields([...parsedErrors]);
|
228
|
+
|
229
|
+
onMutationErrorProp?.(error, _variables, _context);
|
230
|
+
},
|
145
231
|
redirect,
|
146
232
|
action,
|
147
233
|
resource,
|
@@ -165,7 +251,8 @@ export const useForm = <
|
|
165
251
|
overtimeOptions,
|
166
252
|
});
|
167
253
|
|
168
|
-
const { formLoading, onFinish, queryResult, id } =
|
254
|
+
const { formLoading, onFinish, queryResult, id, onFinishAutoSave } =
|
255
|
+
useFormCoreResult;
|
169
256
|
|
170
257
|
const {
|
171
258
|
warnWhenUnsavedChanges: warnWhenUnsavedChangesRefine,
|
@@ -184,10 +271,20 @@ export const useForm = <
|
|
184
271
|
}
|
185
272
|
};
|
186
273
|
|
187
|
-
const onValuesChange = (changeValues: object) => {
|
274
|
+
const onValuesChange = (changeValues: object, allValues: any) => {
|
188
275
|
if (changeValues && warnWhenUnsavedChanges) {
|
189
276
|
setWarnWhen(true);
|
190
277
|
}
|
278
|
+
|
279
|
+
if (autoSave?.enabled) {
|
280
|
+
setWarnWhen(false);
|
281
|
+
|
282
|
+
const onFinishFromProps =
|
283
|
+
autoSave?.onFinish ?? ((values) => values);
|
284
|
+
|
285
|
+
return onFinishAutoSave(onFinishFromProps(allValues));
|
286
|
+
}
|
287
|
+
|
191
288
|
return changeValues;
|
192
289
|
};
|
193
290
|
|