@arcblock/ux 2.12.64 → 2.12.70

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 (69) hide show
  1. package/lib/Config/config-provider.js +9 -0
  2. package/lib/Config/index.d.ts +1 -0
  3. package/lib/Config/index.js +1 -0
  4. package/lib/Header/header.js +6 -2
  5. package/lib/Locale/selector.js +8 -18
  6. package/lib/NavMenu/style.js +10 -8
  7. package/lib/NavMenu/sub-item-group.js +6 -2
  8. package/lib/SessionBlocklet/index.js +7 -15
  9. package/lib/SessionUser/components/logged-in.js +4 -8
  10. package/lib/Theme/theme.d.ts +2 -0
  11. package/lib/Theme/theme.js +2 -1
  12. package/lib/UserCard/Cards/avatar-only.d.ts +8 -0
  13. package/lib/UserCard/Cards/avatar-only.js +34 -0
  14. package/lib/UserCard/Cards/basic-info.d.ts +9 -0
  15. package/lib/UserCard/Cards/basic-info.js +51 -0
  16. package/lib/UserCard/Cards/index.d.ts +8 -0
  17. package/lib/UserCard/Cards/index.js +24 -0
  18. package/lib/UserCard/Cards/name-only.d.ts +3 -0
  19. package/lib/UserCard/Cards/name-only.js +18 -0
  20. package/lib/UserCard/Container/card.d.ts +13 -0
  21. package/lib/UserCard/Container/card.js +46 -0
  22. package/lib/UserCard/Container/dialog.d.ts +9 -0
  23. package/lib/UserCard/Container/dialog.js +25 -0
  24. package/lib/UserCard/Content/basic.d.ts +7 -0
  25. package/lib/UserCard/Content/basic.js +139 -0
  26. package/lib/UserCard/Content/clock.d.ts +4 -0
  27. package/lib/UserCard/Content/clock.js +68 -0
  28. package/lib/UserCard/Content/left-layout.d.ts +16 -0
  29. package/lib/UserCard/Content/left-layout.js +33 -0
  30. package/lib/UserCard/Content/minimal.d.ts +15 -0
  31. package/lib/UserCard/Content/minimal.js +61 -0
  32. package/lib/UserCard/Content/tooltip-avatar.d.ts +15 -0
  33. package/lib/UserCard/Content/tooltip-avatar.js +61 -0
  34. package/lib/UserCard/components.d.ts +4 -0
  35. package/lib/UserCard/components.js +45 -0
  36. package/lib/UserCard/index.d.ts +5 -0
  37. package/lib/UserCard/index.js +74 -0
  38. package/lib/UserCard/types.d.ts +107 -0
  39. package/lib/UserCard/types.js +42 -0
  40. package/lib/UserCard/utils.d.ts +2 -0
  41. package/lib/UserCard/utils.js +19 -0
  42. package/lib/hooks/use-clock.d.ts +10 -0
  43. package/lib/hooks/use-clock.js +71 -0
  44. package/package.json +5 -5
  45. package/src/Config/config-provider.tsx +7 -0
  46. package/src/Config/index.ts +1 -0
  47. package/src/Header/header.tsx +2 -2
  48. package/src/Locale/selector.tsx +7 -14
  49. package/src/NavMenu/style.ts +8 -8
  50. package/src/NavMenu/sub-item-group.tsx +2 -2
  51. package/src/SessionBlocklet/index.tsx +7 -20
  52. package/src/SessionUser/components/logged-in.tsx +4 -6
  53. package/src/Theme/theme.ts +1 -0
  54. package/src/UserCard/Cards/avatar-only.tsx +38 -0
  55. package/src/UserCard/Cards/basic-info.tsx +50 -0
  56. package/src/UserCard/Cards/index.tsx +24 -0
  57. package/src/UserCard/Cards/name-only.tsx +17 -0
  58. package/src/UserCard/Container/card.tsx +52 -0
  59. package/src/UserCard/Container/dialog.tsx +30 -0
  60. package/src/UserCard/Content/basic.tsx +134 -0
  61. package/src/UserCard/Content/clock.tsx +63 -0
  62. package/src/UserCard/Content/left-layout.tsx +40 -0
  63. package/src/UserCard/Content/minimal.tsx +60 -0
  64. package/src/UserCard/Content/tooltip-avatar.tsx +55 -0
  65. package/src/UserCard/components.tsx +49 -0
  66. package/src/UserCard/index.tsx +63 -0
  67. package/src/UserCard/types.ts +129 -0
  68. package/src/UserCard/utils.ts +22 -0
  69. package/src/hooks/use-clock.tsx +61 -0
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { Box, useTheme } from '@mui/material';
3
+
4
+ interface DialogContainerProps {
5
+ children: React.ReactNode;
6
+ }
7
+
8
+ /**
9
+ * 统一的卡片容器组件,处理常见的布局容器样式
10
+ */
11
+ function DialogContainer({ children }: DialogContainerProps) {
12
+ // Box变体,用于Minimal和NameOnly类型卡片
13
+ const theme = useTheme();
14
+ return (
15
+ <Box
16
+ sx={{
17
+ p: 2,
18
+ backgroundColor: theme.palette.background.paper,
19
+ border: '1px solid',
20
+ borderColor: 'divider',
21
+ borderRadius: 2,
22
+ maxWidth: 400,
23
+ display: 'flex',
24
+ }}>
25
+ {children}
26
+ </Box>
27
+ );
28
+ }
29
+
30
+ export default DialogContainer;
@@ -0,0 +1,134 @@
1
+ import { Typography, Box } from '@mui/material';
2
+ import { useCreation } from 'ahooks';
3
+ import styled from '@emotion/styled';
4
+ import LinkIcon from '@arcblock/icons/lib/Link';
5
+ import LocationIcon from '@arcblock/icons/lib/Location';
6
+ import EmailIcon from '@arcblock/icons/lib/Email';
7
+ import TimezoneIcon from '@arcblock/icons/lib/Timezone';
8
+ import { withoutProtocol } from 'ufo';
9
+
10
+ import { User } from '../types';
11
+ import Clock from './clock';
12
+
13
+ /**
14
+ * 格式化链接显示
15
+ * 对于 http/https 协议只显示域名,其他协议显示完整链接
16
+ */
17
+ function formatLinkDisplay(link: string): string {
18
+ return withoutProtocol(link);
19
+ }
20
+
21
+ const iconSize = {
22
+ width: 16,
23
+ height: 16,
24
+ };
25
+ interface BasicContentProps {
26
+ user: User;
27
+ isFull?: boolean;
28
+ }
29
+
30
+ function BasicContent({ user, isFull = false }: BasicContentProps) {
31
+ const metadata = useCreation(() => {
32
+ return (
33
+ user.metadata ?? {
34
+ joinedAt: user?.createdAt,
35
+ email: user?.email,
36
+ phone: {
37
+ country: 'cn',
38
+ phoneNumber: user?.phone ?? '',
39
+ },
40
+ }
41
+ );
42
+ }, [user]);
43
+
44
+ // 获取第一个链接(如果有)
45
+ const firstLink = metadata.links && metadata.links.length > 0 ? metadata.links[0] : null;
46
+
47
+ const moreContent = () => {
48
+ return metadata.timezone ? (
49
+ <Box display="flex" alignItems="center" gap={1}>
50
+ <TimezoneIcon {...iconSize} />
51
+ <Typography variant="body2" color="grey.800">
52
+ <Clock value={metadata.timezone} variant="body2" color="grey.800" />
53
+ </Typography>
54
+ </Box>
55
+ ) : null;
56
+ };
57
+
58
+ return (
59
+ <Box mt={1} display="flex" flexDirection="column" gap={1.5}>
60
+ {user.metadata?.bio && (
61
+ <Typography
62
+ variant="body2"
63
+ color="grey.800"
64
+ sx={{
65
+ lineHeight: 1.5,
66
+ display: '-webkit-box',
67
+ WebkitLineClamp: 1,
68
+ WebkitBoxOrient: 'vertical',
69
+ overflow: 'hidden',
70
+ textOverflow: 'ellipsis',
71
+ }}>
72
+ {user.metadata.bio}
73
+ </Typography>
74
+ )}
75
+ <Box display="flex" flexDirection="column" gap={0.5}>
76
+ {/* 显示第一个链接(如果有) */}
77
+ {firstLink && (
78
+ <Box display="flex" alignItems="center" gap={1}>
79
+ <LinkIcon {...iconSize} />
80
+ <LinkDiv>
81
+ <Typography
82
+ component="a"
83
+ href={firstLink.url}
84
+ style={{ textDecoration: 'none' }}
85
+ target="_blank"
86
+ variant="body2"
87
+ color="grey.800"
88
+ rel="noopener noreferrer">
89
+ {formatLinkDisplay(firstLink.url)}
90
+ </Typography>
91
+ </LinkDiv>
92
+ </Box>
93
+ )}
94
+ {metadata.location && (
95
+ <Box display="flex" alignItems="center" gap={1}>
96
+ <LocationIcon {...iconSize} />
97
+ <Typography variant="body2" color="grey.800">
98
+ {metadata.location}
99
+ </Typography>
100
+ </Box>
101
+ )}
102
+
103
+ {isFull && moreContent()}
104
+
105
+ {/* 显示邮箱 */}
106
+ {(metadata.email || user.email) && (
107
+ <Box display="flex" alignItems="center" gap={1}>
108
+ <EmailIcon {...iconSize} />
109
+ <Typography variant="body2" color="grey.800">
110
+ {metadata.email || user.email}
111
+ </Typography>
112
+ </Box>
113
+ )}
114
+ </Box>
115
+ </Box>
116
+ );
117
+ }
118
+
119
+ export default BasicContent;
120
+
121
+ const LinkDiv = styled('span')`
122
+ flex: 1;
123
+ white-space: nowrap;
124
+ overflow: hidden;
125
+ text-overflow: ellipsis;
126
+
127
+ & > * {
128
+ word-break: break-all;
129
+ }
130
+
131
+ .status {
132
+ margin-left: 8px;
133
+ }
134
+ `;
@@ -0,0 +1,63 @@
1
+ import Tooltip from '@mui/material/Tooltip';
2
+ import Typography from '@mui/material/Typography';
3
+ import Box from '@mui/material/Box';
4
+ import { useMemoizedFn } from 'ahooks';
5
+ import useClock from '../../hooks/use-clock';
6
+ import { translate } from '../../Locale/util';
7
+ import { useLocaleContext } from '../../Locale/context';
8
+
9
+ const translations = {
10
+ en: {
11
+ localTime: 'Local Time',
12
+ timezonePhase: {
13
+ dawn: 'AM',
14
+ morning: 'AM',
15
+ afternoon: 'PM',
16
+ night: 'PM',
17
+ },
18
+ },
19
+ zh: {
20
+ localTime: '本地时间',
21
+ timezonePhase: {
22
+ dawn: '凌晨',
23
+ morning: '上午',
24
+ afternoon: '下午',
25
+ night: '晚上',
26
+ },
27
+ },
28
+ };
29
+ export default function Clock({ value, ...props }: { value: string; [key: string]: any }) {
30
+ const { locale } = useLocaleContext() || { locale: 'en' };
31
+ const t = useMemoizedFn((key, data = {}) => {
32
+ return translate(translations, key, locale, 'en', data);
33
+ });
34
+ const timeInfo = useClock(value, locale);
35
+
36
+ return (
37
+ <Box
38
+ sx={{
39
+ whiteSpace: 'nowrap',
40
+ overflow: 'hidden',
41
+ textOverflow: 'ellipsis',
42
+ }}
43
+ display="flex"
44
+ alignItems="center"
45
+ gap={1}>
46
+ <Typography {...props}>{value}</Typography>
47
+ <Tooltip
48
+ enterDelay={200}
49
+ title={
50
+ <span>
51
+ {t('localTime')} {timeInfo.fullDateTime}
52
+ </span>
53
+ }
54
+ placement="top"
55
+ arrow>
56
+ <Typography component="span" fontSize={14}>
57
+ ({locale === 'zh' ? `${t(`timezonePhase.${timeInfo.phase}`)} ` : ''}
58
+ {timeInfo.formattedTime})
59
+ </Typography>
60
+ </Tooltip>
61
+ </Box>
62
+ );
63
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { Box } from '@mui/material';
3
+ import { User } from '../types';
4
+ import TooltipAvatar from './tooltip-avatar';
5
+
6
+ interface LeftColumnLayoutProps {
7
+ user: User;
8
+ avatarSize: number;
9
+ shouldShowHoverCard: boolean;
10
+ renderCardContent: () => React.ReactNode;
11
+ followButtonStyle: string;
12
+ isFollowed: boolean;
13
+ followLoading: boolean;
14
+ }
15
+
16
+ /**
17
+ * 统一左侧列布局组件,包含头像和关注按钮
18
+ */
19
+ function LeftColumnLayout({
20
+ user,
21
+ avatarSize,
22
+ shouldShowHoverCard,
23
+ renderCardContent,
24
+ followButtonStyle,
25
+ isFollowed,
26
+ followLoading,
27
+ }: LeftColumnLayoutProps) {
28
+ return (
29
+ <Box sx={{ mr: 2, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}>
30
+ <TooltipAvatar
31
+ user={user}
32
+ avatarSize={avatarSize}
33
+ shouldShowHoverCard={shouldShowHoverCard}
34
+ renderCardContent={renderCardContent}
35
+ />
36
+ </Box>
37
+ );
38
+ }
39
+
40
+ export default LeftColumnLayout;
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { Typography, Box } from '@mui/material';
3
+ import DID from '../../DID';
4
+ import { InfoType, User } from '../types';
5
+ import TooltipAvatar from './tooltip-avatar';
6
+ import { renderTopRight } from '../components';
7
+
8
+ interface MinimalContentProps {
9
+ user: User;
10
+ infoType: InfoType;
11
+ showDid: boolean;
12
+ avatarSize: number;
13
+ shouldShowHoverCard: boolean;
14
+ renderCardContent?: () => React.ReactNode | null;
15
+ didProps: Record<string, any>;
16
+ renderTopRightContent?: () => React.ReactNode;
17
+ topRightMaxWidth: number;
18
+ }
19
+
20
+ function MinimalContent(props: MinimalContentProps) {
21
+ const {
22
+ user,
23
+ showDid,
24
+ didProps,
25
+ avatarSize,
26
+ shouldShowHoverCard,
27
+ renderCardContent,
28
+ renderTopRightContent,
29
+ topRightMaxWidth,
30
+ infoType = InfoType.NameOnly,
31
+ } = props;
32
+
33
+ return (
34
+ <Box display="flex" justifyContent="space-between" alignItems="center">
35
+ <Box display="flex" justifyContent="flex-start" alignItems="center" gap={2} sx={{ flex: 1, minWidth: 0 }}>
36
+ <TooltipAvatar
37
+ user={user}
38
+ avatarSize={avatarSize}
39
+ shouldShowHoverCard={shouldShowHoverCard}
40
+ renderCardContent={renderCardContent}
41
+ />
42
+ <Box>
43
+ <Typography
44
+ variant="subtitle1"
45
+ fontWeight={500}
46
+ color="text.primary"
47
+ fontSize={18}
48
+ noWrap
49
+ sx={{ lineHeight: 1.1 }}>
50
+ {user.fullName || user.email || user.did}
51
+ </Typography>
52
+
53
+ {showDid && user.did && <DID did={user.did} size={14} sx={{ lineHeight: 1.5 }} {...didProps} />}
54
+ </Box>
55
+ </Box>
56
+ {renderTopRight(renderTopRightContent, topRightMaxWidth)}
57
+ </Box>
58
+ );
59
+ }
60
+ export default MinimalContent;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { Box, Tooltip } from '@mui/material';
3
+ import { User } from '../types';
4
+ import { renderAvatar } from '../components';
5
+
6
+ interface TooltipAvatarProps {
7
+ user: User;
8
+ avatarSize: number;
9
+ shouldShowHoverCard: boolean;
10
+ renderCardContent?: () => React.ReactNode | null;
11
+ tooltipTitle?: string;
12
+ }
13
+
14
+ /**
15
+ * 统一处理头像的Tooltip显示组件
16
+ * 根据条件显示普通Tooltip、自定义Tooltip内容或无Tooltip的头像
17
+ */
18
+ function TooltipAvatar({ user, avatarSize, shouldShowHoverCard, renderCardContent, tooltipTitle }: TooltipAvatarProps) {
19
+ // 使用普通文本Tooltip
20
+ if (tooltipTitle) {
21
+ return (
22
+ <Tooltip enterDelay={200} title={tooltipTitle} placement="bottom">
23
+ <div style={{ display: 'inline-block' }}>{renderAvatar(user, avatarSize)}</div>
24
+ </Tooltip>
25
+ );
26
+ }
27
+
28
+ // 使用自定义内容Tooltip
29
+ if (shouldShowHoverCard) {
30
+ return (
31
+ <Tooltip
32
+ enterDelay={200}
33
+ title={renderCardContent ? renderCardContent() : null}
34
+ placement="bottom"
35
+ arrow={false}
36
+ PopperProps={{
37
+ sx: {
38
+ '& .MuiTooltip-tooltip': {
39
+ backgroundColor: 'transparent',
40
+ p: 0,
41
+ maxWidth: 400,
42
+ zIndex: 1000,
43
+ },
44
+ },
45
+ }}>
46
+ <Box display="inline-block">{renderAvatar(user, avatarSize)}</Box>
47
+ </Tooltip>
48
+ );
49
+ }
50
+
51
+ // 无Tooltip
52
+ return <Box display="inline-block">{renderAvatar(user, avatarSize)}</Box>;
53
+ }
54
+
55
+ export default TooltipAvatar;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Box, Avatar } from '@mui/material';
3
+ import { User } from './types';
4
+ import { createNameOnlyAvatar } from './utils';
5
+
6
+ // 渲染头像
7
+ export const renderAvatar = (user: User, avatarSize: number = 48) => {
8
+ // 如果用户没有头像,则显示名称首字母头像
9
+ if (!user.avatar) {
10
+ const avatarContent = createNameOnlyAvatar(user);
11
+
12
+ return (
13
+ <Avatar
14
+ sx={{
15
+ width: avatarSize,
16
+ height: avatarSize,
17
+ fontSize: avatarSize * 0.4,
18
+ cursor: 'pointer',
19
+ }}>
20
+ {avatarContent}
21
+ </Avatar>
22
+ );
23
+ }
24
+
25
+ // 显示用户头像
26
+ return (
27
+ <Avatar
28
+ sx={{
29
+ width: avatarSize,
30
+ height: avatarSize,
31
+ cursor: 'pointer',
32
+ }}
33
+ src={user.avatar}
34
+ alt={user.fullName || ''}
35
+ />
36
+ );
37
+ };
38
+
39
+ // 渲染右上角内容
40
+ export const renderTopRight = (
41
+ renderTopRightContent: (() => React.ReactNode) | undefined,
42
+ topRightMaxWidth: number
43
+ ) => {
44
+ if (renderTopRightContent) {
45
+ return <Box sx={{ maxWidth: topRightMaxWidth }}>{renderTopRightContent()}</Box>;
46
+ }
47
+
48
+ return null;
49
+ };
@@ -0,0 +1,63 @@
1
+ import React, { useRef } from 'react';
2
+ import type { User } from './types';
3
+ import { UserCardProps, CardType } from './types';
4
+ import AvatarOnlyCard from './Cards/avatar-only';
5
+ import DetailedCard from './Cards';
6
+ import DialogContainer from './Container/dialog';
7
+ import CardContainer from './Container/card';
8
+
9
+ // 创建仅显示名称首字母的头像
10
+ export function createNameOnlyAvatar(user: User) {
11
+ if (!user) return null;
12
+
13
+ // 使用全名或邮箱前缀作为显示内容
14
+ let content = '';
15
+ if (user.fullName) {
16
+ // 提取名称首字母
17
+ content = user.fullName.charAt(0).toUpperCase();
18
+ } else if (user.email) {
19
+ // 使用邮箱前缀首字母
20
+ content = user.email.split('@')[0].charAt(0).toUpperCase();
21
+ } else {
22
+ // 如果都没有,使用DID的第一个字符
23
+ content = user.did ? user.did.charAt(0).toUpperCase() : '?';
24
+ }
25
+
26
+ return content;
27
+ }
28
+
29
+ // UserCard组件入口
30
+ function UserCard(props: UserCardProps) {
31
+ const { user, cardType = CardType.Detailed, showHoverCard, onFollow, onUnfollow, followLoading = false } = props;
32
+
33
+ // 计算是否显示悬停卡片
34
+ // 默认规则:AvatarOnly模式下默认显示悬停卡片,Detailed模式下默认不显示
35
+ const shouldShowHoverCard = showHoverCard !== undefined ? showHoverCard : cardType === CardType.AvatarOnly;
36
+
37
+ const containerRef = useRef<HTMLDivElement>(null);
38
+
39
+ // 渲染卡片内容(用于Tooltip)
40
+ const renderCardContent = () => {
41
+ return (
42
+ <DialogContainer>
43
+ <DetailedCard {...props} shouldShowHoverCard={false} />
44
+ </DialogContainer>
45
+ );
46
+ };
47
+
48
+ // 根据卡片类型选择合适的组件
49
+ if (cardType === CardType.AvatarOnly) {
50
+ return (
51
+ <AvatarOnlyCard {...props} shouldShowHoverCard={shouldShowHoverCard} renderCardContent={renderCardContent} />
52
+ );
53
+ }
54
+
55
+ // 详细卡片模式
56
+ return (
57
+ <CardContainer containerRef={containerRef} cardType={cardType}>
58
+ <DetailedCard {...props} shouldShowHoverCard={shouldShowHoverCard} renderCardContent={renderCardContent} />
59
+ </CardContainer>
60
+ );
61
+ }
62
+
63
+ export default UserCard;
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { DIDProps } from '../DID';
3
+
4
+ type UserPublicInfo = {
5
+ avatar: string;
6
+ did: string;
7
+ fullName: string;
8
+ sourceAppPid: string | null;
9
+ };
10
+ export type UserMetadataLink = {
11
+ url: string;
12
+ favicon?: string;
13
+ };
14
+
15
+ export enum DurationEnum {
16
+ NoClear = 'no_clear',
17
+ ThirtyMinutes = '30_minutes',
18
+ OneHour = '1_hour',
19
+ FourHours = '4_hours',
20
+ Today = 'today',
21
+ ThisWeek = 'this_week',
22
+ Custom = 'custom',
23
+ }
24
+
25
+ export enum StatusEnum {
26
+ Meeting = 'meeting',
27
+ Community = 'community',
28
+ Holiday = 'holiday',
29
+ OffSick = 'off_sick',
30
+ WorkingRemotely = 'working_remotely',
31
+ }
32
+ export type UserPhoneProps = {
33
+ country: string;
34
+ phoneNumber?: string;
35
+ };
36
+
37
+ export type UserMetadata = {
38
+ bio?: string;
39
+ location?: string;
40
+ timezone?: string;
41
+ joinedAt?: string;
42
+ status?: {
43
+ value: string;
44
+ duration?: DurationEnum;
45
+ dateRange?: Date[];
46
+ };
47
+ links?: UserMetadataLink[];
48
+ cover?: string;
49
+ // 这两个字段是 User, 方便数据更新,在保存时同步
50
+ email?: string;
51
+ phone?: UserPhoneProps;
52
+ };
53
+
54
+ export type UserAddress = {
55
+ country?: string;
56
+ province?: string;
57
+ city?: string;
58
+ line1?: string;
59
+ line2?: string;
60
+ postalCode?: string;
61
+ };
62
+
63
+ export type User = UserPublicInfo & {
64
+ role: string;
65
+ email?: string;
66
+ phone?: string;
67
+ sourceProvider?: string;
68
+ sourceAppPid?: string;
69
+ lastLoginAt?: string;
70
+ lastLoginIp?: string;
71
+ createdAt?: string;
72
+ passports?: any[];
73
+ didSpace?: Record<string, any>;
74
+ connectedAccounts?: any[];
75
+ locale?: string;
76
+ url: string;
77
+ inviter?: string;
78
+ emailVerified?: boolean;
79
+ phoneVerified?: boolean;
80
+ // 1.16.40 新增
81
+ metadata?: UserMetadata;
82
+ // 1.16.41 新增
83
+ address?: UserAddress;
84
+ };
85
+
86
+ // 头像大小
87
+ export const AvatarSize = {
88
+ small: 32,
89
+ medium: 40,
90
+ large: 48,
91
+ xlarge: 64,
92
+ };
93
+
94
+ // 卡片类型
95
+ export enum CardType {
96
+ AvatarOnly = 'AvatarOnly', // 仅头像
97
+ Detailed = 'Detailed', // 详细卡片
98
+ }
99
+
100
+ // 信息类型
101
+ export enum InfoType {
102
+ NameOnly = 'NameOnly', // 仅显示名称
103
+ Minimal = 'Minimal', // 极简模式,显示头像、名称和DID
104
+ Basic = 'Basic', // 基本信息模式
105
+ }
106
+
107
+ // 定义UserCard属性接口
108
+ export interface UserCardProps {
109
+ user: User;
110
+ cardType?: CardType;
111
+ infoType?: InfoType;
112
+ avatarSize?: number;
113
+ showHoverCard?: boolean;
114
+ showDid?: boolean;
115
+ didProps?: DIDProps;
116
+
117
+ // 关注功能相关属性
118
+ isFollowed?: boolean;
119
+ onFollow?: (user: User) => void;
120
+ onUnfollow?: (user: User) => void;
121
+ followLoading?: boolean;
122
+
123
+ // 右上角内容相关属性
124
+ renderTopRightContent?: () => React.ReactNode;
125
+ topRightMaxWidth?: number;
126
+
127
+ // 自定义内容渲染函数
128
+ renderCustomContent?: () => React.ReactNode;
129
+ }
@@ -0,0 +1,22 @@
1
+ import { User } from './types';
2
+
3
+ // 创建仅显示名称首字母的头像
4
+ // eslint-disable-next-line import/prefer-default-export
5
+ export function createNameOnlyAvatar(user: User) {
6
+ if (!user) return null;
7
+
8
+ // 使用全名或邮箱前缀作为显示内容
9
+ let content = '';
10
+ if (user.fullName) {
11
+ // 提取名称首字母
12
+ content = user.fullName.charAt(0).toUpperCase();
13
+ } else if (user.email) {
14
+ // 使用邮箱前缀首字母
15
+ content = user.email.split('@')[0].charAt(0).toUpperCase();
16
+ } else {
17
+ // 如果都没有,使用DID的第一个字符
18
+ content = user.did ? user.did.charAt(0).toUpperCase() : '?';
19
+ }
20
+
21
+ return content;
22
+ }