@blocklet/ui-react 2.12.8 → 2.12.10

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.
Files changed (54) hide show
  1. package/lib/@types/index.d.ts +34 -0
  2. package/lib/@types/index.js +16 -0
  3. package/lib/@types/shims.d.ts +1 -0
  4. package/lib/UserCenter/components/config-profile.js +23 -1
  5. package/lib/UserCenter/components/editable-field.d.ts +22 -0
  6. package/lib/UserCenter/components/editable-field.js +159 -0
  7. package/lib/UserCenter/components/nft.d.ts +4 -0
  8. package/lib/UserCenter/components/nft.js +93 -0
  9. package/lib/UserCenter/components/settings.js +32 -15
  10. package/lib/UserCenter/components/status-selector/duration-menu.d.ts +9 -0
  11. package/lib/UserCenter/components/status-selector/duration-menu.js +75 -0
  12. package/lib/UserCenter/components/status-selector/index.d.ts +9 -0
  13. package/lib/UserCenter/components/status-selector/index.js +39 -0
  14. package/lib/UserCenter/components/status-selector/menu-item.d.ts +24 -0
  15. package/lib/UserCenter/components/status-selector/menu-item.js +24 -0
  16. package/lib/UserCenter/components/user-center.js +119 -122
  17. package/lib/UserCenter/components/user-info/clock.d.ts +4 -0
  18. package/lib/UserCenter/components/user-info/clock.js +23 -0
  19. package/lib/UserCenter/components/user-info/link-preview-input.d.ts +5 -0
  20. package/lib/UserCenter/components/user-info/link-preview-input.js +181 -0
  21. package/lib/UserCenter/components/user-info/metadata.d.ts +7 -0
  22. package/lib/UserCenter/components/user-info/metadata.js +458 -0
  23. package/lib/UserCenter/components/user-info/switch-role.js +2 -3
  24. package/lib/UserCenter/components/user-info/user-basic-info.d.ts +2 -0
  25. package/lib/UserCenter/components/user-info/user-basic-info.js +159 -90
  26. package/lib/UserCenter/components/user-info/user-info.js +2 -16
  27. package/lib/UserCenter/components/user-info/user-status.d.ts +8 -0
  28. package/lib/UserCenter/components/user-info/user-status.js +153 -0
  29. package/lib/UserCenter/components/user-info/utils.d.ts +19 -0
  30. package/lib/UserCenter/components/user-info/utils.js +86 -0
  31. package/lib/UserCenter/libs/locales.d.ts +65 -0
  32. package/lib/UserCenter/libs/locales.js +67 -2
  33. package/lib/UserSessions/components/user-sessions.js +48 -14
  34. package/package.json +8 -5
  35. package/src/@types/index.ts +39 -0
  36. package/src/@types/shims.d.ts +1 -0
  37. package/src/UserCenter/components/config-profile.tsx +20 -1
  38. package/src/UserCenter/components/editable-field.tsx +180 -0
  39. package/src/UserCenter/components/nft.tsx +122 -0
  40. package/src/UserCenter/components/settings.tsx +16 -4
  41. package/src/UserCenter/components/status-selector/duration-menu.tsx +87 -0
  42. package/src/UserCenter/components/status-selector/index.tsx +52 -0
  43. package/src/UserCenter/components/status-selector/menu-item.tsx +52 -0
  44. package/src/UserCenter/components/user-center.tsx +104 -103
  45. package/src/UserCenter/components/user-info/clock.tsx +29 -0
  46. package/src/UserCenter/components/user-info/link-preview-input.tsx +227 -0
  47. package/src/UserCenter/components/user-info/metadata.tsx +465 -0
  48. package/src/UserCenter/components/user-info/switch-role.tsx +3 -3
  49. package/src/UserCenter/components/user-info/user-basic-info.tsx +150 -87
  50. package/src/UserCenter/components/user-info/user-info.tsx +6 -16
  51. package/src/UserCenter/components/user-info/user-status.tsx +182 -0
  52. package/src/UserCenter/components/user-info/utils.ts +114 -0
  53. package/src/UserCenter/libs/locales.ts +65 -0
  54. package/src/UserSessions/components/user-sessions.tsx +68 -18
@@ -39,7 +39,10 @@ export declare const translations: {
39
39
  required: string;
40
40
  invalid: string;
41
41
  setting: string;
42
+ save: string;
42
43
  profile: string;
44
+ nft: string;
45
+ noNFT: string;
43
46
  };
44
47
  toPublic: string;
45
48
  currentPassport: string;
@@ -94,6 +97,35 @@ export declare const translations: {
94
97
  title: string;
95
98
  locale: string;
96
99
  };
100
+ userStatus: {
101
+ Online: string;
102
+ Meeting: string;
103
+ Community: string;
104
+ Holiday: string;
105
+ OffSick: string;
106
+ WorkingRemotely: string;
107
+ duration: {
108
+ ThirtyMinutes: string;
109
+ OneHour: string;
110
+ FourHours: string;
111
+ Today: string;
112
+ ThisWeek: string;
113
+ };
114
+ };
115
+ profile: {
116
+ addLink: string;
117
+ location: string;
118
+ timezone: string;
119
+ setStatus: string;
120
+ localTime: string;
121
+ email: string;
122
+ emailInvalid: string;
123
+ phone: string;
124
+ phoneInvalid: string;
125
+ editProfile: string;
126
+ removeStatusAfter: string;
127
+ justForYou: string;
128
+ };
97
129
  };
98
130
  en: {
99
131
  settings: string;
@@ -135,7 +167,10 @@ export declare const translations: {
135
167
  required: string;
136
168
  invalid: string;
137
169
  setting: string;
170
+ save: string;
138
171
  profile: string;
172
+ nft: string;
173
+ noNFT: string;
139
174
  };
140
175
  toPublic: string;
141
176
  currentPassport: string;
@@ -190,5 +225,35 @@ export declare const translations: {
190
225
  title: string;
191
226
  locale: string;
192
227
  };
228
+ userStatus: {
229
+ Online: string;
230
+ Meeting: string;
231
+ Community: string;
232
+ Holiday: string;
233
+ OffSick: string;
234
+ WorkingRemotely: string;
235
+ duration: {
236
+ ThirtyMinutes: string;
237
+ OneHour: string;
238
+ FourHours: string;
239
+ Today: string;
240
+ ThisWeek: string;
241
+ };
242
+ };
243
+ profile: {
244
+ addLink: string;
245
+ location: string;
246
+ timezone: string;
247
+ setStatus: string;
248
+ localTime: string;
249
+ email: string;
250
+ emailInvalid: string;
251
+ phone: string;
252
+ phoneInvalid: string;
253
+ editProfile: string;
254
+ removeStatusAfter: string;
255
+ justForYou: string;
256
+ maxInputLength: string;
257
+ };
193
258
  };
194
259
  };
@@ -39,7 +39,10 @@ export const translations = {
39
39
  required: "\u5FC5\u586B\u7684",
40
40
  invalid: "\u65E0\u6548",
41
41
  setting: "\u8BBE\u7F6E",
42
- profile: "\u4FE1\u606F"
42
+ save: "\u4FDD\u5B58",
43
+ profile: "\u4FE1\u606F",
44
+ nft: "NFTs",
45
+ noNFT: "\u6682\u65E0 NFT"
43
46
  },
44
47
  toPublic: "\u516C\u5F00 \u201C{name}\u201D \u9875\u9762",
45
48
  currentPassport: "\u5F53\u524D\u4F7F\u7528\u7684\u901A\u884C\u8BC1",
@@ -93,6 +96,35 @@ export const translations = {
93
96
  commonSetting: {
94
97
  title: "\u901A\u7528\u8BBE\u7F6E",
95
98
  locale: "\u504F\u597D\u8BED\u8A00"
99
+ },
100
+ userStatus: {
101
+ Online: "\u5728\u7EBF",
102
+ Meeting: "\u5F00\u4F1A\u4E2D",
103
+ Community: "\u901A\u52E4\u4E2D",
104
+ Holiday: "\u4F11\u5047",
105
+ OffSick: "\u75C5\u5047",
106
+ WorkingRemotely: "\u8FDC\u7A0B\u5DE5\u4F5C",
107
+ duration: {
108
+ ThirtyMinutes: "30\u5206\u949F",
109
+ OneHour: "1\u5C0F\u65F6",
110
+ FourHours: "4\u5C0F\u65F6",
111
+ Today: "\u4ECA\u5929",
112
+ ThisWeek: "\u672C\u5468"
113
+ }
114
+ },
115
+ profile: {
116
+ addLink: "\u6DFB\u52A0\u8FDE\u63A5",
117
+ location: "\u4F4D\u7F6E",
118
+ timezone: "\u65F6\u533A",
119
+ setStatus: "\u8BBE\u7F6E\u72B6\u6001",
120
+ localTime: "\u5F53\u5730\u65F6\u95F4:",
121
+ email: "\u90AE\u7BB1\u5730\u5740",
122
+ emailInvalid: "\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E",
123
+ phone: "\u7535\u8BDD\u53F7\u7801",
124
+ phoneInvalid: "\u7535\u8BDD\u53F7\u7801\u683C\u5F0F\u4E0D\u6B63\u786E",
125
+ editProfile: "\u7F16\u8F91\u8D44\u6599",
126
+ removeStatusAfter: "\u79FB\u9664\u72B6\u6001",
127
+ justForYou: "\u4EC5\u5BF9\u60A8\u53EF\u89C1"
96
128
  }
97
129
  },
98
130
  en: {
@@ -135,7 +167,10 @@ export const translations = {
135
167
  required: "Required",
136
168
  invalid: "Invalid",
137
169
  setting: "Settings",
138
- profile: "Profile"
170
+ save: "Save",
171
+ profile: "Profile",
172
+ nft: "NFTs",
173
+ noNFT: "No NFT"
139
174
  },
140
175
  toPublic: 'Public "{name}" page',
141
176
  currentPassport: "Passport currently in use",
@@ -189,6 +224,36 @@ export const translations = {
189
224
  commonSetting: {
190
225
  title: "Common Settings",
191
226
  locale: "Preferred language"
227
+ },
228
+ userStatus: {
229
+ Online: "Online",
230
+ Meeting: "In a Meeting",
231
+ Community: "Commuting",
232
+ Holiday: "On Holiday",
233
+ OffSick: "Off Sick",
234
+ WorkingRemotely: "Working Remotely",
235
+ duration: {
236
+ ThirtyMinutes: "30 minutes",
237
+ OneHour: "1 hour",
238
+ FourHours: "4 hours",
239
+ Today: "Today",
240
+ ThisWeek: "This Week"
241
+ }
242
+ },
243
+ profile: {
244
+ addLink: "Add link",
245
+ location: "Location",
246
+ timezone: "Timezone",
247
+ setStatus: "Set a Status",
248
+ localTime: "Local Time: ",
249
+ email: "Email",
250
+ emailInvalid: "Email is invalid",
251
+ phone: "Phone",
252
+ phoneInvalid: "Phone number is invalid",
253
+ editProfile: "Edit Profile",
254
+ removeStatusAfter: "Remove status after",
255
+ justForYou: "Visible to you only",
256
+ maxInputLength: "Max input length is"
192
257
  }
193
258
  }
194
259
  };
@@ -8,6 +8,7 @@ import sortBy from "lodash/sortBy";
8
8
  import UAParser from "ua-parser-js";
9
9
  import { getVisitorId } from "@arcblock/ux/lib/Util";
10
10
  import { useConfirm } from "@arcblock/ux/lib/Dialog";
11
+ import { temp as colors } from "@arcblock/ux/lib/Colors";
11
12
  import pAll from "p-all";
12
13
  import PQueue from "p-queue";
13
14
  import { Box, Button, CircularProgress, Tooltip, Typography, useMediaQuery } from "@mui/material";
@@ -27,7 +28,7 @@ const parseUa = (ua) => {
27
28
  return result;
28
29
  };
29
30
  const queue = new PQueue({ concurrency: 1 });
30
- const UserSessionIp = memo(({ userSession }) => {
31
+ const UserSessionIp = memo(({ userSession, isMobile = false }) => {
31
32
  const currentState = useReactive({
32
33
  loading: true,
33
34
  ipRegion: ""
@@ -42,7 +43,7 @@ const UserSessionIp = memo(({ userSession }) => {
42
43
  }
43
44
  });
44
45
  }, [currentState, userSession.lastLoginIp]);
45
- return /* @__PURE__ */ jsx(Box, { children: currentState.ipRegion ? /* @__PURE__ */ jsxs(Fragment, { children: [
46
+ return /* @__PURE__ */ jsx(Box, { ...isMobile && { display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }, children: currentState.ipRegion ? /* @__PURE__ */ jsxs(Fragment, { children: [
46
47
  /* @__PURE__ */ jsx(Typography, { variant: "body2", children: currentState.ipRegion }),
47
48
  /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", children: userSession.lastLoginIp || t("unknown") })
48
49
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -165,7 +166,7 @@ export default function UserSessions({
165
166
  customBodyRenderLite: (rawIndex) => {
166
167
  const x = safeData[rawIndex];
167
168
  const result = parseUa(x.ua);
168
- return /* @__PURE__ */ jsx(Box, { children: [result.os?.name, result.os?.version].filter(Boolean).join("/") || t("unknown") });
169
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: [result.os?.name, result.os?.version].filter(Boolean).join("/") || t("unknown") });
169
170
  }
170
171
  }
171
172
  },
@@ -176,7 +177,7 @@ export default function UserSessions({
176
177
  customBodyRenderLite: (rawIndex) => {
177
178
  const x = safeData[rawIndex];
178
179
  const result = parseUa(x.ua);
179
- return /* @__PURE__ */ jsx(Box, { children: [result.browser?.name, result.browser?.version].filter(Boolean).join("/") || t("unknown") });
180
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: [result.browser?.name, result.browser?.version].filter(Boolean).join("/") || t("unknown") });
180
181
  }
181
182
  }
182
183
  },
@@ -186,7 +187,7 @@ export default function UserSessions({
186
187
  options: {
187
188
  customBodyRenderLite: (rawIndex) => {
188
189
  const x = safeData[rawIndex];
189
- return /* @__PURE__ */ jsx(Box, { children: x.extra?.walletOS || t("unknown") });
190
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.extra?.walletOS || t("unknown") });
190
191
  }
191
192
  }
192
193
  },
@@ -196,7 +197,7 @@ export default function UserSessions({
196
197
  options: {
197
198
  customBodyRenderLite: (rawIndex) => {
198
199
  const x = safeData[rawIndex];
199
- return /* @__PURE__ */ jsx(Box, { sx: { minWidth: 150, maxWidth: 250 }, children: /* @__PURE__ */ jsx(UserSessionInfo, { sessionUser: x.user, user }) });
200
+ return /* @__PURE__ */ jsx(Box, { sx: { minWidth: 150, maxWidth: 250, ...isMobile && { textAlign: "right" } }, children: /* @__PURE__ */ jsx(UserSessionInfo, { sessionUser: x.user, user }) });
200
201
  }
201
202
  }
202
203
  },
@@ -206,7 +207,7 @@ export default function UserSessions({
206
207
  options: {
207
208
  customBodyRenderLite: (rawIndex) => {
208
209
  const x = safeData[rawIndex];
209
- return x.createdAt ? /* @__PURE__ */ jsx(RelativeTime, { value: x.createdAt, relativeRange: 3 * 86400 * 1e3, locale }) : t("unknown");
210
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.createdAt ? /* @__PURE__ */ jsx(RelativeTime, { value: x.createdAt, relativeRange: 3 * 86400 * 1e3, locale }) : t("unknown") });
210
211
  }
211
212
  }
212
213
  },
@@ -216,7 +217,7 @@ export default function UserSessions({
216
217
  options: {
217
218
  customBodyRenderLite: (rawIndex) => {
218
219
  const x = safeData[rawIndex];
219
- return x.status === "expired" ? t("expired") : /* @__PURE__ */ jsx(RelativeTime, { value: x.updatedAt, relativeRange: 3 * 86400 * 1e3, locale });
220
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.status === "expired" ? t("expired") : /* @__PURE__ */ jsx(RelativeTime, { value: x.updatedAt, relativeRange: 3 * 86400 * 1e3, locale }) });
220
221
  }
221
222
  }
222
223
  },
@@ -226,7 +227,7 @@ export default function UserSessions({
226
227
  options: {
227
228
  customBodyRenderLite: (rawIndex) => {
228
229
  const x = safeData[rawIndex];
229
- return /* @__PURE__ */ jsx(UserSessionIp, { userSession: x });
230
+ return /* @__PURE__ */ jsx(UserSessionIp, { userSession: x, isMobile });
230
231
  }
231
232
  }
232
233
  },
@@ -236,12 +237,13 @@ export default function UserSessions({
236
237
  options: {
237
238
  customBodyRenderLite: (rawIndex) => {
238
239
  const x = safeData[rawIndex];
239
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
240
+ return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: /* @__PURE__ */ jsx(
240
241
  Button,
241
242
  {
242
243
  sx: {
243
244
  whiteSpace: "nowrap",
244
- fontSize: "12px",
245
+ fontSize: "12px !important",
246
+ lineHeight: "1.25",
245
247
  px: 1
246
248
  },
247
249
  disabled: currentVisitorId === x.visitorId,
@@ -259,14 +261,45 @@ export default function UserSessions({
259
261
  return /* @__PURE__ */ jsxs(
260
262
  Box,
261
263
  {
264
+ className: "pc-user-sessions",
262
265
  sx: {
263
- maxWidth: isMobile ? "unset" : isLg ? "calc(100vw - 300px)" : "920px",
266
+ maxWidth: isMobile ? "unset" : isLg ? "calc(100vw - 300px)" : "100%",
267
+ ...isMobile && {
268
+ ".pc-user-sessions-table > div:nth-child(2)": {
269
+ border: `1px solid ${colors.dividerColor}`,
270
+ borderRadius: "8px"
271
+ }
272
+ },
264
273
  ".MuiTableCell-head": {
265
274
  whiteSpace: "nowrap",
266
275
  fontWeight: "bold"
267
276
  },
277
+ ".MuiTableRow-root": {
278
+ border: "unset",
279
+ "&:nth-child(even)": {
280
+ backgroundColor: "#F9FAFB",
281
+ "&:hover": {
282
+ backgroundColor: "#F9FAFB !important"
283
+ }
284
+ }
285
+ },
286
+ ".MuiTableRow-hover": {
287
+ "&:hover": {
288
+ backgroundColor: "inherit !important"
289
+ }
290
+ },
268
291
  ".MuiTableCell-root": {
269
- paddingRight: "8px"
292
+ paddingRight: "8px",
293
+ paddingLeft: "8px",
294
+ ...isMobile && {
295
+ padding: "8px !important",
296
+ "&:first-child": {
297
+ paddingTop: "20px!important"
298
+ },
299
+ "&:last-child": {
300
+ paddingBottom: "20px!important"
301
+ }
302
+ }
270
303
  }
271
304
  },
272
305
  children: [
@@ -279,7 +312,8 @@ export default function UserSessions({
279
312
  columns,
280
313
  customButtons,
281
314
  options: tableOptions,
282
- loading: pageState.loading
315
+ loading: pageState.loading,
316
+ className: "pc-user-sessions-table"
283
317
  }
284
318
  )
285
319
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.12.8",
3
+ "version": "2.12.10",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@abtnode/constant": "^1.16.39",
36
- "@arcblock/bridge": "^2.12.8",
37
- "@arcblock/react-hooks": "^2.12.8",
36
+ "@arcblock/bridge": "^2.12.10",
37
+ "@arcblock/react-hooks": "^2.12.10",
38
38
  "@arcblock/ws": "^1.19.15",
39
39
  "@blocklet/did-space-react": "^1.0.26",
40
40
  "@iconify-icons/logos": "^1.2.36",
@@ -44,11 +44,13 @@
44
44
  "axios": "^1.7.5",
45
45
  "clsx": "^2.1.0",
46
46
  "core-js": "^3.25.5",
47
+ "dayjs": "^1.11.5",
47
48
  "iconify-icon": "^1.0.8",
48
49
  "iconify-icons-material-symbols-400": "^0.0.1",
49
50
  "is-url": "^1.2.4",
50
51
  "js-cookie": "^2.2.1",
51
52
  "lodash": "^4.17.21",
53
+ "moment-timezone": "^0.5.37",
52
54
  "p-all": "^5.0.0",
53
55
  "p-queue": "^6.6.2",
54
56
  "p-wait-for": "^5.0.2",
@@ -57,7 +59,8 @@
57
59
  "react-placeholder": "^4.1.0",
58
60
  "type-fest": "^4.22.0",
59
61
  "ua-parser-js": "^1.0.37",
60
- "ufo": "^1.5.3"
62
+ "ufo": "^1.5.3",
63
+ "validator": "^13.9.0"
61
64
  },
62
65
  "peerDependencies": {
63
66
  "@arcblock/did-connect": "^2.11.48",
@@ -84,5 +87,5 @@
84
87
  "jest": "^29.7.0",
85
88
  "unbuild": "^2.0.0"
86
89
  },
87
- "gitHead": "4cfeed31d56495f1368b96adfae11f938e68ef46"
90
+ "gitHead": "a4e740e658ec69a4889872ce0c4f1ef6746fa57b"
88
91
  }
@@ -44,6 +44,44 @@ export type NFTAccount = {
44
44
 
45
45
  export type ConnectedAccount = OAuthAccount | WalletAccount | NFTAccount;
46
46
 
47
+ export type UserMetadataLink = {
48
+ url: string;
49
+ favicon?: string;
50
+ };
51
+
52
+ export enum DurationEnum {
53
+ ThirtyMinutes = '30_minutes',
54
+ OneHour = '1_hour',
55
+ FourHours = '4_hours',
56
+ Today = 'today',
57
+ ThisWeek = 'this_week',
58
+ }
59
+
60
+ export enum StatusEnum {
61
+ Meeting = 'meeting',
62
+ Community = 'community',
63
+ Holiday = 'holiday',
64
+ OffSick = 'off_sick',
65
+ WorkingRemotely = 'working_remotely',
66
+ }
67
+
68
+ export type UserMetadata = {
69
+ bio?: string;
70
+ location?: string;
71
+ timezone?: string;
72
+ joinedAt?: string;
73
+ status?: {
74
+ value: string;
75
+ duration?: DurationEnum;
76
+ dateRange?: Date[];
77
+ };
78
+ links?: UserMetadataLink[];
79
+ cover?: string;
80
+ // 这两个字段是 User, 方便数据更新,在保存时同步
81
+ email?: string;
82
+ phone?: string;
83
+ };
84
+
47
85
  export type User = UserPublicInfo & {
48
86
  role: string;
49
87
  email?: string;
@@ -61,6 +99,7 @@ export type User = UserPublicInfo & {
61
99
  inviter?: string;
62
100
  emailVerified?: boolean;
63
101
  phoneVerified?: boolean;
102
+ metadata?: UserMetadata;
64
103
  };
65
104
 
66
105
  export type UserCenterTab = {
@@ -6,6 +6,7 @@ declare module '@arcblock/ux/lib/ErrorBoundary';
6
6
 
7
7
  declare module '@arcblock/did-connect/*';
8
8
  declare module '@arcblock/did-connect/lib/Session';
9
+ declare module '@abtnode/constant';
9
10
 
10
11
  declare module 'is-url';
11
12
 
@@ -3,6 +3,9 @@ import { useMemoizedFn, useReactive } from 'ahooks';
3
3
  import { translate } from '@arcblock/ux/lib/Locale/util';
4
4
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
5
  import Toast from '@arcblock/ux/lib/Toast';
6
+ import { temp as colors } from '@arcblock/ux/lib/Colors';
7
+ // eslint-disable-next-line import/no-extraneous-dependencies
8
+ import ArrowDownwardIcon from '@arcblock/icons/lib/ArrowDown';
6
9
 
7
10
  import { translations } from '../libs/locales';
8
11
  import { client } from '../../libs/client';
@@ -60,7 +63,23 @@ export default function ConfigProfile({ user, onSave }: { user: User; onSave: (t
60
63
  height: 2,
61
64
  }}
62
65
  />
63
- <Select value={currentState.locale} onChange={handleChange} size="small" sx={{ minWidth: 300 }}>
66
+ <Select
67
+ value={currentState.locale}
68
+ onChange={handleChange}
69
+ size="small"
70
+ // eslint-disable-next-line react/no-unstable-nested-components
71
+ IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
72
+ sx={{
73
+ minWidth: 300,
74
+ '&:hover': {
75
+ 'fieldset.MuiOutlinedInput-notchedOutline': {
76
+ borderColor: colors.dividerColor,
77
+ },
78
+ },
79
+ fieldset: {
80
+ borderColor: colors.dividerColor,
81
+ },
82
+ }}>
64
83
  {(window.blocklet.languages || languages).map((x: { name: string; code: string }) => (
65
84
  <MenuItem value={x.code} key={x.code}>
66
85
  {x.name}
@@ -0,0 +1,180 @@
1
+ import React from 'react';
2
+ import { Box, TextField, Typography, Tooltip, TooltipProps } from '@mui/material';
3
+ import { useCreation, useMemoizedFn } from 'ahooks';
4
+ import { temp as colors } from '@arcblock/ux/lib/Colors';
5
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
6
+ import { translate } from '@arcblock/ux/lib/Locale/util';
7
+ import VerifiedIcon from '@mui/icons-material/Verified';
8
+ import { translations } from '../libs/locales';
9
+
10
+ interface EditableFieldProps {
11
+ value: string;
12
+ onChange?: (value: string) => void;
13
+ onValueValidate?: (value: string) => void;
14
+ editable: boolean;
15
+ component?: 'input' | 'textarea';
16
+ placeholder?: string;
17
+ rows?: number;
18
+ maxLength?: number; // 最大字符数
19
+ icon?: React.ReactNode;
20
+ label?: string;
21
+ children?: React.ReactNode;
22
+ tooltip?: TooltipProps['title'];
23
+ inline?: boolean;
24
+ style?: React.CSSProperties;
25
+ verified?: boolean;
26
+ errorMsg?: string;
27
+ }
28
+
29
+ const inputFieldStyle = {
30
+ width: '100%',
31
+ '.MuiOutlinedInput-root': {
32
+ '&:hover': {
33
+ fieldset: {
34
+ borderColor: colors.dividerColor,
35
+ },
36
+ },
37
+ '&.Mui-focused': {
38
+ fieldset: {
39
+ borderColor: colors.dividerColor,
40
+ },
41
+ },
42
+ },
43
+
44
+ fieldset: {
45
+ borderColor: colors.dividerColor,
46
+ },
47
+ };
48
+
49
+ function EditableField({
50
+ value,
51
+ onChange = () => {},
52
+ onValueValidate = () => {},
53
+ errorMsg = '',
54
+ editable = false,
55
+ component = 'input',
56
+ placeholder = '',
57
+ rows = 2,
58
+ maxLength,
59
+ icon,
60
+ label = '',
61
+ children,
62
+ tooltip,
63
+ inline = true,
64
+ style = {},
65
+ verified = false,
66
+ }: EditableFieldProps) {
67
+ const { locale } = useLocaleContext();
68
+ const t = useMemoizedFn((key, data = {}) => {
69
+ return translate(translations, key, locale, 'en', data);
70
+ });
71
+
72
+ const handleChange = useMemoizedFn((v: string) => {
73
+ if (onChange) {
74
+ onChange(v);
75
+ }
76
+ if (onValueValidate) {
77
+ onValueValidate(v);
78
+ }
79
+ });
80
+
81
+ const content = useCreation(() => {
82
+ if (children) {
83
+ return children;
84
+ }
85
+ if (component === 'input') {
86
+ return (
87
+ <TextField
88
+ variant="outlined"
89
+ className="editable-field"
90
+ InputProps={{
91
+ sx: { backgroundColor: 'transparent' },
92
+ placeholder,
93
+ }}
94
+ value={value}
95
+ onChange={(e) => handleChange(e.target.value)}
96
+ sx={inputFieldStyle}
97
+ error={Boolean(errorMsg)}
98
+ helperText={errorMsg}
99
+ />
100
+ );
101
+ }
102
+
103
+ const isTooLong = value.length > (maxLength || 300);
104
+ return (
105
+ <Box position="relative">
106
+ <TextField
107
+ variant="outlined"
108
+ InputProps={{
109
+ multiline: true,
110
+ minRows: rows,
111
+ placeholder,
112
+ }}
113
+ sx={inputFieldStyle}
114
+ value={value}
115
+ onChange={(e) => handleChange(e.target.value)}
116
+ error={Boolean(errorMsg)}
117
+ helperText={errorMsg}
118
+ />
119
+ <Typography
120
+ position="absolute"
121
+ bottom={-22}
122
+ right={2}
123
+ variant="caption"
124
+ fontSize="12px"
125
+ component="span"
126
+ color={isTooLong ? 'error' : 'text.secondary'}>
127
+ {isTooLong ? `(${t('profile.maxInputLength')} ${maxLength}) ` : ''}
128
+ {value.length} / {maxLength}
129
+ </Typography>
130
+ </Box>
131
+ );
132
+ }, [value, handleChange, component, placeholder, rows, children]);
133
+
134
+ if (!editable) {
135
+ return value ? (
136
+ <Tooltip title={tooltip} placement="top">
137
+ <Typography
138
+ variant="subtitle1"
139
+ component="p"
140
+ gutterBottom
141
+ sx={{
142
+ width: '100%',
143
+ mb: 0,
144
+ lineHeight: 1.4,
145
+ overflow: 'hidden',
146
+ }}
147
+ display="flex"
148
+ alignItems="center"
149
+ gap={1}>
150
+ {icon}
151
+ <Box display="flex" flexDirection="row" alignItems="center" width="90%">
152
+ <Typography
153
+ sx={{
154
+ whiteSpace: 'pre-wrap',
155
+ ...(inline ? { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } : {}),
156
+ }}>
157
+ {value}
158
+ </Typography>
159
+ {verified && (
160
+ <VerifiedIcon color="success" style={{ fontSize: 16, width: 16, marginLeft: 4, flexShrink: 0 }} />
161
+ )}
162
+ </Box>
163
+ </Typography>
164
+ </Tooltip>
165
+ ) : null;
166
+ }
167
+
168
+ return (
169
+ <Box sx={{ width: '100%' }} style={style}>
170
+ {label && (
171
+ <Typography variant="subtitle1" gutterBottom sx={{ mb: 0.5, fontSize: '12px', color: '#4B5563' }}>
172
+ {label}
173
+ </Typography>
174
+ )}
175
+ {content}
176
+ </Box>
177
+ );
178
+ }
179
+
180
+ export default EditableField;