@arcblock/ux 2.12.64 → 2.12.71

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 (74) 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/Datatable/index.d.ts +2 -2
  5. package/lib/Datatable/index.js +2 -2
  6. package/lib/Header/header.js +6 -2
  7. package/lib/Img/index.js +4 -4
  8. package/lib/Locale/selector.js +8 -18
  9. package/lib/NavMenu/style.js +10 -8
  10. package/lib/NavMenu/sub-item-group.js +6 -2
  11. package/lib/SessionBlocklet/index.js +7 -15
  12. package/lib/SessionUser/components/logged-in.js +4 -8
  13. package/lib/Theme/theme.d.ts +2 -0
  14. package/lib/Theme/theme.js +2 -1
  15. package/lib/UserCard/Cards/avatar-only.d.ts +8 -0
  16. package/lib/UserCard/Cards/avatar-only.js +34 -0
  17. package/lib/UserCard/Cards/basic-info.d.ts +9 -0
  18. package/lib/UserCard/Cards/basic-info.js +51 -0
  19. package/lib/UserCard/Cards/index.d.ts +8 -0
  20. package/lib/UserCard/Cards/index.js +24 -0
  21. package/lib/UserCard/Cards/name-only.d.ts +3 -0
  22. package/lib/UserCard/Cards/name-only.js +18 -0
  23. package/lib/UserCard/Container/card.d.ts +13 -0
  24. package/lib/UserCard/Container/card.js +46 -0
  25. package/lib/UserCard/Container/dialog.d.ts +9 -0
  26. package/lib/UserCard/Container/dialog.js +25 -0
  27. package/lib/UserCard/Content/basic.d.ts +7 -0
  28. package/lib/UserCard/Content/basic.js +139 -0
  29. package/lib/UserCard/Content/clock.d.ts +4 -0
  30. package/lib/UserCard/Content/clock.js +68 -0
  31. package/lib/UserCard/Content/left-layout.d.ts +16 -0
  32. package/lib/UserCard/Content/left-layout.js +33 -0
  33. package/lib/UserCard/Content/minimal.d.ts +15 -0
  34. package/lib/UserCard/Content/minimal.js +61 -0
  35. package/lib/UserCard/Content/tooltip-avatar.d.ts +15 -0
  36. package/lib/UserCard/Content/tooltip-avatar.js +61 -0
  37. package/lib/UserCard/components.d.ts +4 -0
  38. package/lib/UserCard/components.js +45 -0
  39. package/lib/UserCard/index.d.ts +5 -0
  40. package/lib/UserCard/index.js +74 -0
  41. package/lib/UserCard/types.d.ts +107 -0
  42. package/lib/UserCard/types.js +42 -0
  43. package/lib/UserCard/utils.d.ts +2 -0
  44. package/lib/UserCard/utils.js +19 -0
  45. package/lib/hooks/use-clock.d.ts +10 -0
  46. package/lib/hooks/use-clock.js +71 -0
  47. package/package.json +5 -5
  48. package/src/Config/config-provider.tsx +7 -0
  49. package/src/Config/index.ts +1 -0
  50. package/src/Datatable/index.jsx +2 -2
  51. package/src/Header/header.tsx +2 -2
  52. package/src/Img/index.jsx +35 -32
  53. package/src/Locale/selector.tsx +7 -14
  54. package/src/NavMenu/style.ts +8 -8
  55. package/src/NavMenu/sub-item-group.tsx +2 -2
  56. package/src/SessionBlocklet/index.tsx +7 -20
  57. package/src/SessionUser/components/logged-in.tsx +4 -6
  58. package/src/Theme/theme.ts +1 -0
  59. package/src/UserCard/Cards/avatar-only.tsx +38 -0
  60. package/src/UserCard/Cards/basic-info.tsx +50 -0
  61. package/src/UserCard/Cards/index.tsx +24 -0
  62. package/src/UserCard/Cards/name-only.tsx +17 -0
  63. package/src/UserCard/Container/card.tsx +52 -0
  64. package/src/UserCard/Container/dialog.tsx +30 -0
  65. package/src/UserCard/Content/basic.tsx +134 -0
  66. package/src/UserCard/Content/clock.tsx +63 -0
  67. package/src/UserCard/Content/left-layout.tsx +40 -0
  68. package/src/UserCard/Content/minimal.tsx +60 -0
  69. package/src/UserCard/Content/tooltip-avatar.tsx +55 -0
  70. package/src/UserCard/components.tsx +49 -0
  71. package/src/UserCard/index.tsx +63 -0
  72. package/src/UserCard/types.ts +129 -0
  73. package/src/UserCard/utils.ts +22 -0
  74. package/src/hooks/use-clock.tsx +61 -0
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { Box } from '@mui/material';
3
+ import { InfoType, UserCardProps } from '../types';
4
+ import LeftLayout from '../Content/left-layout';
5
+ import MinimalContent from '../Content/minimal';
6
+ import BasicContent from '../Content/basic';
7
+
8
+ interface BasicCardProps extends UserCardProps {
9
+ shouldShowHoverCard: boolean;
10
+ renderCardContent?: () => React.ReactNode | null;
11
+ isFull?: boolean;
12
+ }
13
+
14
+ // 详细卡片模式下的Basic渲染组件
15
+ function BasicCard(props: BasicCardProps) {
16
+ const {
17
+ user,
18
+ avatarSize = 40,
19
+ showDid = false,
20
+ didProps = {},
21
+ shouldShowHoverCard,
22
+ renderCardContent,
23
+ renderTopRightContent,
24
+ topRightMaxWidth = 100,
25
+ renderCustomContent,
26
+ isFull = true,
27
+ infoType = InfoType.Minimal,
28
+ } = props;
29
+
30
+ return (
31
+ <Box display="flex" flexDirection="column" width="100%" sx={{ flex: 1, minWidth: 0 }}>
32
+ <MinimalContent
33
+ infoType={infoType}
34
+ user={user}
35
+ showDid={showDid}
36
+ didProps={didProps}
37
+ avatarSize={avatarSize}
38
+ shouldShowHoverCard={shouldShowHoverCard}
39
+ renderCardContent={renderCardContent}
40
+ renderTopRightContent={renderTopRightContent}
41
+ topRightMaxWidth={topRightMaxWidth}
42
+ />
43
+
44
+ {infoType === InfoType.Basic && <BasicContent user={user} isFull={isFull} />}
45
+ {renderCustomContent && <Box sx={{ mt: 1.5 }}>{renderCustomContent()}</Box>}
46
+ </Box>
47
+ );
48
+ }
49
+
50
+ export default BasicCard;
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { UserCardProps, InfoType } from '../types';
3
+ import NameOnlyCard from './name-only';
4
+ import BasicCard from './basic-info';
5
+
6
+ interface DetailedCardProps extends UserCardProps {
7
+ shouldShowHoverCard: boolean;
8
+ renderCardContent?: () => React.ReactNode | null;
9
+ }
10
+
11
+ // DetailedCard组件,根据infoType选择不同的卡片组件进行渲染
12
+ function DetailedCard(props: DetailedCardProps) {
13
+ const { infoType = InfoType.Minimal } = props;
14
+
15
+ // 根据信息类型选择合适的卡片组件
16
+ switch (infoType) {
17
+ case InfoType.NameOnly:
18
+ return <NameOnlyCard {...props} />;
19
+ default:
20
+ return <BasicCard {...props} />;
21
+ }
22
+ }
23
+
24
+ export default DetailedCard;
@@ -0,0 +1,17 @@
1
+ import { Typography } from '@mui/material';
2
+ import { UserCardProps } from '../types';
3
+ import { renderAvatar } from '../components';
4
+
5
+ // 详细卡片模式下的NameOnly渲染组件
6
+ function NameOnlyCard(props: UserCardProps) {
7
+ const { user, avatarSize = 48 } = props;
8
+
9
+ return (
10
+ <>
11
+ {renderAvatar(user, avatarSize)}
12
+ <Typography variant="body1">{user.fullName || user.email || user.did}</Typography>
13
+ </>
14
+ );
15
+ }
16
+
17
+ export default NameOnlyCard;
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { Box, Paper } from '@mui/material';
3
+ import { CardType } from '../types';
4
+
5
+ interface CardContainerProps {
6
+ children: React.ReactNode;
7
+ cardType?: CardType;
8
+ variant?: 'paper' | 'box';
9
+ containerRef?: React.RefObject<HTMLDivElement>;
10
+ }
11
+
12
+ /**
13
+ * 统一的卡片容器组件,处理常见的布局容器样式
14
+ */
15
+ function CardContainer({ children, cardType = CardType.Detailed, variant = 'box', containerRef }: CardContainerProps) {
16
+ const commonStyles = {
17
+ minWidth: 320,
18
+ p: 2,
19
+ borderRadius: 2,
20
+ ...(cardType === CardType.Detailed
21
+ ? {
22
+ border: '1px solid',
23
+ borderColor: 'divider',
24
+ }
25
+ : {}),
26
+ };
27
+
28
+ // Paper变体,用于Basic和Full类型卡片
29
+ if (variant === 'paper') {
30
+ return (
31
+ <Paper ref={containerRef} elevation={0} sx={commonStyles}>
32
+ {children}
33
+ </Paper>
34
+ );
35
+ }
36
+
37
+ // Box变体,用于Minimal和NameOnly类型卡片
38
+ return (
39
+ <Box
40
+ ref={containerRef}
41
+ sx={{
42
+ ...commonStyles,
43
+ display: 'flex',
44
+ alignItems: cardType === CardType.AvatarOnly ? 'center' : 'flex-start',
45
+ gap: 2,
46
+ }}>
47
+ {children}
48
+ </Box>
49
+ );
50
+ }
51
+
52
+ export default CardContainer;
@@ -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;