@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@refinedev/antd",
3
- "version": "5.27.0",
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.8.0",
28
- "@refinedev/core": "^4.25.1",
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.17.0",
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
  </>
@@ -24,3 +24,4 @@ export * from "./table";
24
24
  export * from "./pages";
25
25
  export * from "./breadcrumb";
26
26
  export * from "./pageHeader";
27
+ export * from "./autoSaveIndicator";
@@ -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 { ButtonProps } from "antd";
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 } = useFormCoreResult;
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