@arcblock/ux 3.1.28 → 3.1.31

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 (50) hide show
  1. package/lib/SessionUser/components/logged-in.js +41 -39
  2. package/lib/UserCard/Cards/avatar-only.js +11 -22
  3. package/lib/UserCard/Cards/index.js +4 -12
  4. package/lib/UserCard/Cards/social-actions.d.ts +7 -0
  5. package/lib/UserCard/Cards/social-actions.js +112 -0
  6. package/lib/UserCard/Content/minimal.js +62 -47
  7. package/lib/UserCard/index.d.ts +2 -0
  8. package/lib/UserCard/index.js +31 -28
  9. package/lib/UserCard/types.d.ts +8 -1
  10. package/lib/UserCard/types.js +4 -4
  11. package/lib/UserCard/use-follow.d.ts +16 -0
  12. package/lib/UserCard/use-follow.js +67 -0
  13. package/lib/Util/index.d.ts +15 -0
  14. package/lib/Util/index.js +191 -171
  15. package/lib/package.json.js +9 -0
  16. package/lib/withTracker/action/bind-wallet.d.ts +14 -0
  17. package/lib/withTracker/action/bind-wallet.js +1 -0
  18. package/lib/withTracker/action/login.d.ts +15 -0
  19. package/lib/withTracker/action/login.js +1 -0
  20. package/lib/withTracker/action/pay.d.ts +12 -0
  21. package/lib/withTracker/action/pay.js +1 -0
  22. package/lib/withTracker/action/switch-passport.d.ts +18 -0
  23. package/lib/withTracker/action/switch-passport.js +1 -0
  24. package/lib/withTracker/constant/index.d.ts +3 -0
  25. package/lib/withTracker/constant/index.js +6 -0
  26. package/lib/withTracker/env.d.ts +1 -0
  27. package/lib/withTracker/env.js +4 -0
  28. package/lib/withTracker/index.d.ts +2 -0
  29. package/lib/withTracker/index.js +21 -17
  30. package/package.json +10 -8
  31. package/src/SessionUser/components/logged-in.tsx +2 -0
  32. package/src/UserCard/Cards/avatar-only.tsx +1 -15
  33. package/src/UserCard/Cards/index.tsx +2 -11
  34. package/src/UserCard/Cards/social-actions.tsx +196 -0
  35. package/src/UserCard/Content/minimal.tsx +43 -31
  36. package/src/UserCard/UserCard.stories.jsx +1 -0
  37. package/src/UserCard/index.tsx +6 -0
  38. package/src/UserCard/types.ts +10 -1
  39. package/src/UserCard/use-follow.tsx +119 -0
  40. package/src/Util/index.ts +67 -0
  41. package/src/withTracker/action/bind-wallet.tsx +17 -0
  42. package/src/withTracker/action/login.tsx +18 -0
  43. package/src/withTracker/action/pay.tsx +14 -0
  44. package/src/withTracker/action/switch-passport.tsx +20 -0
  45. package/src/withTracker/constant/index.tsx +3 -0
  46. package/src/withTracker/env.tsx +1 -0
  47. package/src/withTracker/index.tsx +8 -11
  48. package/lib/UserCard/Cards/name-only.d.ts +0 -5
  49. package/lib/UserCard/Cards/name-only.js +0 -13
  50. package/src/UserCard/Cards/name-only.tsx +0 -17
@@ -0,0 +1,9 @@
1
+ const e = "3.1.31", s = { "@blocklet/js-sdk": "^1.16.49-beta-20250823-082650-626c1473" }, t = {
2
+ version: e,
3
+ dependencies: s
4
+ };
5
+ export {
6
+ t as default,
7
+ s as dependencies,
8
+ e as version
9
+ };
@@ -0,0 +1,14 @@
1
+ import { LiteralUnion } from 'type-fest';
2
+ export interface BindWalletEvent {
3
+ action: 'bindWalletSuccess' | 'bindWalletFailed';
4
+ provider: LiteralUnion<'wallet' | 'apple' | 'github' | 'google' | 'email' | 'twitter' | 'passkey', string>;
5
+ }
6
+ export interface BindWalletSuccessEvent extends BindWalletEvent {
7
+ action: 'bindWalletSuccess';
8
+ success: true;
9
+ }
10
+ export interface BindWalletFailedEvent extends BindWalletEvent {
11
+ action: 'bindWalletFailed';
12
+ success: false;
13
+ errorMessage: string;
14
+ }
@@ -0,0 +1,15 @@
1
+ import { LiteralUnion } from 'type-fest';
2
+ export interface LoginEvent {
3
+ action: 'loginSuccess' | 'loginFailed';
4
+ provider: LiteralUnion<'wallet' | 'apple' | 'github' | 'google' | 'email' | 'twitter' | 'passkey', string>;
5
+ success: boolean;
6
+ }
7
+ export interface LoginSuccessEvent extends LoginEvent {
8
+ action: 'loginSuccess';
9
+ success: true;
10
+ }
11
+ export interface LoginFailedEvent extends LoginEvent {
12
+ action: 'loginFailed';
13
+ success: false;
14
+ errorMessage: string;
15
+ }
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,12 @@
1
+ export interface PayEvent {
2
+ action: 'paySuccess' | 'payFailed';
3
+ checkoutSessionMode: string;
4
+ success: boolean;
5
+ }
6
+ export interface PaySuccessEvent extends PayEvent {
7
+ success: true;
8
+ }
9
+ export interface PayFailedEvent extends PayEvent {
10
+ success: false;
11
+ errorMessage: string;
12
+ }
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,18 @@
1
+ export interface SwitchPassportEvent {
2
+ action: 'switchPassportSuccess' | 'switchPassportFailed';
3
+ success: boolean;
4
+ }
5
+ export interface SwitchPassportSuccessEvent extends SwitchPassportEvent {
6
+ success: true;
7
+ /**
8
+ *
9
+ * @example guest -> admin
10
+ * @type {string}
11
+ * @memberof SwitchPassportEvent
12
+ */
13
+ change: string;
14
+ }
15
+ export interface SwitchPassportFailedEvent extends SwitchPassportEvent {
16
+ success: false;
17
+ errorMessage: string;
18
+ }
@@ -0,0 +1,3 @@
1
+ export declare const GA_LAST_LOGIN_METHOD = "ga-last-login-method";
2
+ export declare const GA_LAST_ROLE = "ga-last-role";
3
+ export declare const GA_LAST_SOURCE_PROVIDER = "ga-last-source-provider";
@@ -0,0 +1,6 @@
1
+ const o = "ga-last-login-method", t = "ga-last-role", _ = "ga-last-source-provider";
2
+ export {
3
+ o as GA_LAST_LOGIN_METHOD,
4
+ t as GA_LAST_ROLE,
5
+ _ as GA_LAST_SOURCE_PROVIDER
6
+ };
@@ -0,0 +1 @@
1
+ export declare const googleAnalyticsMeasurementId: string;
@@ -0,0 +1,4 @@
1
+ const e = window.blocklet?.GA_MEASUREMENT_ID || "";
2
+ export {
3
+ e as googleAnalyticsMeasurementId
4
+ };
@@ -1,2 +1,4 @@
1
+ import { default as ReactGA } from 'react-ga4';
2
+ export { ReactGA };
1
3
  declare const _default: <P extends object>(WrappedComponent: React.ComponentType<P>) => (props: P) => import("react/jsx-runtime").JSX.Element;
2
4
  export default _default;
@@ -1,22 +1,26 @@
1
- import { jsx as s } from "react/jsx-runtime";
2
- import { useLocation as m } from "react-router-dom";
3
- import { useMount as f, useDeepCompareEffect as d } from "ahooks";
4
- import a from "react-ga4";
5
- const v = (i) => {
6
- const n = window.blocklet?.GA_MEASUREMENT_ID || "", r = (o, e = document.title) => {
7
- !process.env.NODE_ENV || process.env.NODE_ENV === "development" || n && a.send({ hitType: "pageview", page: o, title: e });
8
- }, c = (o, e, t, p) => {
9
- n && a.event({ category: o, action: e, label: t, value: p, transport: "beacon" });
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import { useLocation as f } from "react-router-dom";
3
+ import { useMount as s, useDeepCompareEffect as u } from "ahooks";
4
+ import r from "react-ga4";
5
+ import { default as A } from "react-ga4";
6
+ import { googleAnalyticsMeasurementId as n } from "./env.js";
7
+ n && r.initialize(n);
8
+ const h = (i) => {
9
+ const a = (o, t = document.title) => {
10
+ n && r.send({ hitType: "pageview", page: o, title: t });
11
+ }, c = (o, t, e, m) => {
12
+ n && r.event({ category: o, action: t, label: e, value: m, transport: "beacon" });
10
13
  };
11
- return n && a.initialize(n), window.trackPage = r, window.trackEvent = c, function(e) {
12
- const t = m();
13
- return f(() => {
14
- r(t.pathname);
15
- }), d(() => {
16
- r(t.pathname);
17
- }, [t.pathname]), /* @__PURE__ */ s(i, { ...e });
14
+ return window.trackPage = a, window.trackEvent = c, function(t) {
15
+ const e = f();
16
+ return s(() => {
17
+ a(e.pathname);
18
+ }), u(() => {
19
+ a(e.pathname);
20
+ }, [e.pathname]), /* @__PURE__ */ p(i, { ...t });
18
21
  };
19
22
  };
20
23
  export {
21
- v as default
24
+ A as ReactGA,
25
+ h as default
22
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "3.1.28",
3
+ "version": "3.1.31",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -45,6 +45,7 @@
45
45
  "@types/pako": "^2.0.3",
46
46
  "@types/react": "^19.1.8",
47
47
  "@types/react-helmet": "^6.1.11",
48
+ "@types/semver": "^7.7.0",
48
49
  "@types/webfontloader": "^1.6.38",
49
50
  "babel-jest": "29",
50
51
  "babel-plugin-inline-react-svg": "^2.0.2",
@@ -67,16 +68,16 @@
67
68
  "react": "^19.0.0",
68
69
  "react-router-dom": "^6.22.3"
69
70
  },
70
- "gitHead": "8a62be8796c0ab9129b24a08ecc051d1ac23e0de",
71
+ "gitHead": "a904bcfb38156f7cdff5b2134c126b19d528f4b2",
71
72
  "dependencies": {
72
- "@arcblock/bridge": "3.1.28",
73
+ "@arcblock/bridge": "3.1.31",
73
74
  "@arcblock/did": "^1.21.3",
74
75
  "@arcblock/did-motif": "^1.1.14",
75
- "@arcblock/icons": "3.1.28",
76
- "@arcblock/nft-display": "3.1.28",
77
- "@arcblock/react-hooks": "3.1.28",
78
- "@blocklet/js-sdk": "^1.16.48",
79
- "@blocklet/theme": "3.1.28",
76
+ "@arcblock/icons": "3.1.31",
77
+ "@arcblock/nft-display": "3.1.31",
78
+ "@arcblock/react-hooks": "3.1.31",
79
+ "@blocklet/js-sdk": "^1.16.49-beta-20250823-082650-626c1473",
80
+ "@blocklet/theme": "3.1.31",
80
81
  "@fontsource/roboto": "~5.1.1",
81
82
  "@fontsource/ubuntu-mono": "^5.2.6",
82
83
  "@iconify-icons/logos": "^1.2.36",
@@ -123,6 +124,7 @@
123
124
  "react-svg": "^16.3.0",
124
125
  "react-use": "^17.6.0",
125
126
  "rebound": "^0.1.0",
127
+ "semver": "^7.7.2",
126
128
  "topojson-client": "^3.1.0",
127
129
  "type-fest": "^4.41.0",
128
130
  "validator": "^13.15.15",
@@ -33,6 +33,7 @@ import type { Locale, Session } from '../../type';
33
33
  import DidSpace from './did-space';
34
34
  import { mergeSx } from '../../Util/style';
35
35
  import { createDebug } from '../../Util/logger';
36
+ import { GA_LAST_ROLE } from '../../withTracker/constant';
36
37
 
37
38
  const getInviteLink = (inviter: string) => {
38
39
  const url = new URL(window.location.href);
@@ -86,6 +87,7 @@ export default function LoggedIn({
86
87
  const oauth = session.useOAuth();
87
88
  const passkey = typeof session.usePasskey === 'function' ? session.usePasskey() : null;
88
89
  const handleSwitchPassport = useMemoizedFn(({ inArcSphere = false } = {}) => {
90
+ localStorage.setItem(GA_LAST_ROLE, session?.user?.role);
89
91
  const extraParams: Record<string, any> = {};
90
92
  if (inArcSphere) {
91
93
  if (session?.user?.sourceAppPid) {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { UserCardProps, InfoType, User } from '../types';
2
+ import { UserCardProps, User } from '../types';
3
3
  import TooltipAvatar from '../Content/tooltip-avatar';
4
4
 
5
5
  interface AvatarOnlyCardProps extends Omit<UserCardProps, 'user'> {
@@ -12,20 +12,6 @@ interface AvatarOnlyCardProps extends Omit<UserCardProps, 'user'> {
12
12
  function AvatarOnlyCard(props: AvatarOnlyCardProps) {
13
13
  const { user, infoType, avatarSize = 48, shouldShowHoverCard = false, renderCardContent, ...rest } = props;
14
14
 
15
- // 对于NameOnly类型使用普通Tooltip
16
- if (infoType === InfoType.NameOnly) {
17
- return (
18
- <TooltipAvatar
19
- user={user}
20
- avatarSize={avatarSize}
21
- shouldShowHoverCard={false}
22
- renderCardContent={renderCardContent}
23
- tooltipTitle={user.fullName || user.email || user.did}
24
- {...rest}
25
- />
26
- );
27
- }
28
-
29
15
  // 其他类型使用自定义Tooltip或无Tooltip
30
16
  return (
31
17
  <TooltipAvatar
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import { UserCardProps, InfoType, User } from '../types';
3
- import NameOnlyCard from './name-only';
2
+ import { UserCardProps, User } from '../types';
4
3
  import BasicCard from './basic-info';
5
4
 
6
5
  interface DetailedCardProps extends Omit<UserCardProps, 'user'> {
@@ -11,15 +10,7 @@ interface DetailedCardProps extends Omit<UserCardProps, 'user'> {
11
10
 
12
11
  // DetailedCard组件,根据infoType选择不同的卡片组件进行渲染
13
12
  function DetailedCard({ ...props }: DetailedCardProps) {
14
- const { infoType = InfoType.Minimal } = props;
15
-
16
- // 根据信息类型选择合适的卡片组件
17
- switch (infoType) {
18
- case InfoType.NameOnly:
19
- return <NameOnlyCard {...props} />;
20
- default:
21
- return <BasicCard {...props} />;
22
- }
13
+ return <BasicCard {...props} />;
23
14
  }
24
15
 
25
16
  export default DetailedCard;
@@ -0,0 +1,196 @@
1
+ import { useMemo } from 'react';
2
+ import { Box, Button, styled } from '@mui/material';
3
+ import { useMemoizedFn } from 'ahooks';
4
+ import { joinURL } from 'ufo';
5
+ import noop from 'lodash/noop';
6
+ import { Icon } from '@iconify/react';
7
+ import NotificationsIcon from '@mui/icons-material/Notifications';
8
+ import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
9
+
10
+ import useFollow from '../use-follow';
11
+ import { User, UserCardProps } from '../types';
12
+ import { useLocaleContext } from '../../Locale/context';
13
+ import { translate } from '../../Locale/util';
14
+ import { isSupportFollow } from '../../Util';
15
+
16
+ type SocialActionsProps = Pick<UserCardProps, 'showSocialActions' | 'session'> & {
17
+ user: User; // 当前卡片显示的用户,要与 session.user 区分开
18
+ };
19
+
20
+ const translations = {
21
+ zh: {
22
+ chat: '聊天',
23
+ follow: '关注',
24
+ unfollow: '取消关注',
25
+ follow_success: '关注成功',
26
+ unfollow_success: '取消关注成功',
27
+ },
28
+ en: {
29
+ chat: 'Chat',
30
+ follow: 'Follow',
31
+ unfollow: 'Unfollow',
32
+ follow_success: 'Follow success',
33
+ unfollow_success: 'Unfollow success',
34
+ },
35
+ };
36
+
37
+ const getDiscussKitMountPoint = () => {
38
+ const { componentMountPoints = [] } = window.blocklet || {};
39
+ const component = componentMountPoints.find((c: any) => c.name === 'did-comments');
40
+ return component?.mountPoint;
41
+ };
42
+
43
+ // chat: 1. showSocialActions.chat 为 true,2. 必须安装了 discuss kit
44
+ function Chat({
45
+ user,
46
+ showSocialActions,
47
+ session,
48
+ }: {
49
+ user: SocialActionsProps['user'];
50
+ showSocialActions: SocialActionsProps['showSocialActions'];
51
+ session: SocialActionsProps['session'];
52
+ }) {
53
+ const { locale } = useLocaleContext() || { locale: 'en' };
54
+ const t = useMemoizedFn((key, data = {}) => {
55
+ return translate(translations, key, locale, 'en', data);
56
+ });
57
+
58
+ const isLogin = useMemo(() => {
59
+ return !!session?.user?.did;
60
+ }, [session?.user?.did]);
61
+
62
+ // 获取 discuss kit 的挂载点
63
+ const discussKitMountPoint = useMemo(() => getDiscussKitMountPoint(), []);
64
+
65
+ const onNavigateToChat = useMemoizedFn(() => {
66
+ if (!isLogin) {
67
+ session?.login(noop, {
68
+ openMode: 'redirect',
69
+ redirect: window.location.href,
70
+ }); // TODO: 需要确认使用是否正确
71
+ return;
72
+ }
73
+ window.open(joinURL(discussKitMountPoint, `/chat/dm/${user?.did}`), '_blank');
74
+ });
75
+
76
+ const showChat = useMemo(() => {
77
+ // boolean:直接使用原值;对象:取对应属性值
78
+ const chatEnabled = typeof showSocialActions === 'boolean' ? !!showSocialActions : showSocialActions?.chat;
79
+
80
+ return chatEnabled && !!discussKitMountPoint;
81
+ }, [showSocialActions, discussKitMountPoint]);
82
+
83
+ if (!showChat) {
84
+ return null;
85
+ }
86
+ return (
87
+ <ButtonWrapper
88
+ className="user-card__social-actions-chat"
89
+ variant="outlined"
90
+ color="inherit"
91
+ onClick={onNavigateToChat}>
92
+ <Icon icon="tabler:message-dots" style={{ marginRight: 4 }} />
93
+ {t('chat')}
94
+ </ButtonWrapper>
95
+ );
96
+ }
97
+
98
+ // follow: 1. showSocialActions.follow 为 true
99
+ function Follow({
100
+ user,
101
+ showSocialActions,
102
+ session,
103
+ }: {
104
+ user: SocialActionsProps['user'];
105
+ showSocialActions: SocialActionsProps['showSocialActions'];
106
+ session: SocialActionsProps['session'];
107
+ }) {
108
+ const { locale } = useLocaleContext() || { locale: 'en' };
109
+ const t = useMemoizedFn((key, data = {}) => {
110
+ return translate(translations, key, locale, 'en', data);
111
+ });
112
+
113
+ const isLogin = useMemo(() => {
114
+ return !!session?.user?.did;
115
+ }, [session?.user?.did]);
116
+
117
+ const showFollow = useMemo(() => {
118
+ // boolean:直接使用原值;对象:取对应属性值
119
+ return typeof showSocialActions === 'boolean' ? !!showSocialActions : showSocialActions?.follow;
120
+ }, [showSocialActions]);
121
+
122
+ const { followed, followUser, unfollowUser } = useFollow({
123
+ user,
124
+ t,
125
+ isMySelf: false,
126
+ visible: !!showFollow,
127
+ });
128
+ const handleFollowAction = useMemoizedFn(() => {
129
+ if (!isLogin) {
130
+ session?.login(noop, {
131
+ openMode: 'redirect',
132
+ redirect: window.location.href,
133
+ });
134
+ return;
135
+ }
136
+ if (followed) {
137
+ unfollowUser(user.did);
138
+ } else {
139
+ followUser(user.did);
140
+ }
141
+ });
142
+
143
+ if (!showFollow) {
144
+ return null;
145
+ }
146
+
147
+ return (
148
+ <ButtonWrapper
149
+ className="user-card__social-actions-follow"
150
+ variant="outlined"
151
+ color="inherit"
152
+ onClick={handleFollowAction}>
153
+ {followed ? (
154
+ <NotificationsOffIcon sx={{ fontSize: 14, mr: 0.5 }} />
155
+ ) : (
156
+ <NotificationsIcon sx={{ fontSize: 14, mr: 0.5 }} />
157
+ )}
158
+ {followed ? t('unfollow') : t('follow')}
159
+ </ButtonWrapper>
160
+ );
161
+ }
162
+
163
+ // 社交操作组件
164
+ // 按钮的显示逻辑
165
+ // 通用逻辑: 1. user 和 session.user 不能是同一个用户, 2. 相关依赖支持,3. showSocialActions 为 true 或 为 object
166
+ // 其他逻辑: 1. 如果 session 中没有用户信息,点击后需要重定向到登录页面
167
+ function SocialActions({ showSocialActions, session, user }: SocialActionsProps) {
168
+ const isSameUser = useMemo(() => {
169
+ return session?.user?.did === user?.did;
170
+ }, [session?.user?.did, user?.did]);
171
+
172
+ const isSupportSocialActions = useMemo(() => {
173
+ return isSupportFollow();
174
+ }, []);
175
+
176
+ // 如果 session 中没有用户信息,或者 user 和 session.user 是同一个用户,则不显示社交操作
177
+ if (isSameUser || !isSupportSocialActions || !showSocialActions) {
178
+ return null;
179
+ }
180
+
181
+ return (
182
+ <Box className="user-card__social-actions" sx={{ display: 'flex', gap: 1 }}>
183
+ <Chat session={session} user={user} showSocialActions={showSocialActions} />
184
+ <Follow session={session} user={user} showSocialActions={showSocialActions} />
185
+ </Box>
186
+ );
187
+ }
188
+
189
+ export default SocialActions;
190
+
191
+ export const ButtonWrapper = styled(Button)`
192
+ color: ${({ theme }) => theme.palette.text.primary};
193
+ font-weight: 500;
194
+ border-color: ${({ theme }) => theme.palette.grey[300]};
195
+ line-height: 1.5;
196
+ `;
@@ -5,6 +5,7 @@ import { User, UserCardProps } from '../types';
5
5
  import TooltipAvatar from './tooltip-avatar';
6
6
  import { renderTopRight } from '../components';
7
7
  import ShortenLabel from './shorten-label';
8
+ import SocialActions from '../Cards/social-actions';
8
9
 
9
10
  interface MinimalContentProps extends UserCardProps {
10
11
  user: User;
@@ -42,44 +43,55 @@ function MinimalContent({ ...props }: MinimalContentProps) {
42
43
  <Box
43
44
  sx={{
44
45
  display: 'flex',
45
- justifyContent: 'flex-start',
46
+ justifyContent: 'space-between',
46
47
  alignItems: 'center',
47
48
  gap: 1,
48
49
  flex: 1,
49
50
  minWidth: 0,
50
51
  }}>
51
- <TooltipAvatar
52
- user={user}
53
- avatarSize={avatarSize}
54
- shouldShowHoverCard={shouldShowHoverCard}
55
- renderCardContent={renderCardContent}
56
- avatarProps={avatarProps}
57
- {...rest}
58
- />
59
- <Box>
60
- <Typography
61
- variant="subtitle1"
62
- className="user-card__full-name-label"
63
- noWrap
64
- sx={{
65
- fontWeight: 500,
66
- color: 'text.primary',
67
- fontSize: 18,
68
- lineHeight: 1.1,
69
- }}>
70
- {renderName ? (
71
- renderName(user)
72
- ) : (
73
- <ShortenLabel sx={{ fontWeight: 500 }} {...shortenLabelProps}>
74
- {user.fullName || user.email || user.did}
75
- </ShortenLabel>
76
- )}
77
- </Typography>
52
+ <Box sx={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1, flex: 1 }}>
53
+ <TooltipAvatar
54
+ user={user}
55
+ avatarSize={avatarSize}
56
+ shouldShowHoverCard={shouldShowHoverCard}
57
+ renderCardContent={renderCardContent}
58
+ avatarProps={avatarProps}
59
+ {...rest}
60
+ />
61
+ <Box>
62
+ <Typography
63
+ variant="subtitle1"
64
+ className="user-card__full-name-label"
65
+ noWrap
66
+ sx={{
67
+ fontWeight: 500,
68
+ color: 'text.primary',
69
+ fontSize: 18,
70
+ lineHeight: 1.1,
71
+ }}>
72
+ {renderName ? (
73
+ renderName(user)
74
+ ) : (
75
+ <ShortenLabel sx={{ fontWeight: 500 }} {...shortenLabelProps}>
76
+ {user.fullName || user.email || user.did}
77
+ </ShortenLabel>
78
+ )}
79
+ </Typography>
78
80
 
79
- {showDid && user.did ? (
80
- <DID did={user.did} size={14} copyable compact locale="en" sx={{ lineHeight: 1.5 }} {...(didProps ?? {})} />
81
- ) : null}
81
+ {showDid && user.did ? (
82
+ <DID
83
+ did={user.did}
84
+ size={14}
85
+ copyable
86
+ compact
87
+ locale="en"
88
+ sx={{ lineHeight: 1.5 }}
89
+ {...(didProps ?? {})}
90
+ />
91
+ ) : null}
92
+ </Box>
82
93
  </Box>
94
+ <SocialActions showSocialActions={rest.showSocialActions} user={user!} session={rest.session} />
83
95
  </Box>
84
96
  {renderTopRight(renderTopRightContent, topRightMaxWidth)}
85
97
  </Box>
@@ -5,6 +5,7 @@ export { default as DidControl } from './demo/didControl';
5
5
  export { default as TopRightContent } from './demo/topRightContent';
6
6
  export { default as CustomFooter } from './demo/customFooter';
7
7
  export { default as DidOnlyAvatar } from './demo/did-only-avatar';
8
+ export { default as SocialActions } from './demo/socialActions';
8
9
 
9
10
  export default {
10
11
  title: 'Data Display/UserCard',
@@ -6,6 +6,7 @@ import DetailedCard from './Cards';
6
6
  import DialogContainer from './Container/dialog';
7
7
  import CardContainer from './Container/card';
8
8
  import Avatar from '../Avatar';
9
+ import SocialActions from './Cards/social-actions';
9
10
  import { getUserByDid, isUserDid } from './utils';
10
11
 
11
12
  // 创建仅显示名称首字母的头像
@@ -93,6 +94,9 @@ function UserCard(props: UserCardProps) {
93
94
  infoType={props.popupInfoType || props.infoType}
94
95
  didProps={props.popupDidProps || props.didProps}
95
96
  showDid={props.popupShowDid || props.showDid}
97
+ showSocialActions={
98
+ props.popupShowSocialActions === undefined ? props.showSocialActions : props.popupShowSocialActions
99
+ }
96
100
  />
97
101
  </DialogContainer>
98
102
  );
@@ -124,3 +128,5 @@ function UserCard(props: UserCardProps) {
124
128
  }
125
129
 
126
130
  export default UserCard;
131
+
132
+ export { SocialActions };
@@ -3,6 +3,7 @@ import { SxProps, Theme, TooltipProps } from '@mui/material';
3
3
  import { DIDProps } from '../DID';
4
4
  import { AvatarProps } from '../Avatar';
5
5
  import { ShortenLabelProps } from './Content/shorten-label';
6
+ import type { Session } from '../type';
6
7
 
7
8
  type UserPublicInfo = {
8
9
  avatar: string;
@@ -100,13 +101,17 @@ export enum CardType {
100
101
 
101
102
  // 信息类型
102
103
  export enum InfoType {
103
- NameOnly = 'NameOnly', // 仅显示名称
104
104
  Minimal = 'Minimal', // 极简模式,显示头像、名称和DID
105
105
  Basic = 'Basic', // 基本信息模式
106
106
  }
107
107
 
108
+ export type SocialActionProps = {
109
+ chat: boolean;
110
+ follow: boolean;
111
+ };
108
112
  // 定义UserCard属性接口
109
113
  export interface UserCardProps {
114
+ session?: Session;
110
115
  user?: User;
111
116
  did?: string;
112
117
  cardType?: CardType;
@@ -146,6 +151,10 @@ export interface UserCardProps {
146
151
  renderTopRightContent?: () => React.ReactNode;
147
152
  topRightMaxWidth?: number;
148
153
 
154
+ // 社交按钮相关属性
155
+ showSocialActions?: SocialActionProps | boolean;
156
+ popupShowSocialActions?: SocialActionProps | boolean;
157
+
149
158
  // 自定义内容渲染函数
150
159
  renderCustomContent?: () => React.ReactNode;
151
160