@arcblock/ux 2.10.43 → 2.10.45
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/Address/compact-text.d.ts +3 -1
- package/lib/Address/compact-text.js +2 -1
- package/lib/Address/did-address.d.ts +22 -3
- package/lib/Address/did-address.js +37 -59
- package/lib/Address/index.d.ts +4 -13
- package/lib/Address/index.js +10 -13
- package/lib/Address/responsive-did-address.d.ts +8 -22
- package/lib/Address/responsive-did-address.js +15 -19
- package/lib/BlockletV2/blocklet.js +1 -0
- package/lib/BlockletV2/components/icon-text.d.ts +3 -1
- package/lib/BlockletV2/components/icon-text.js +4 -3
- package/lib/DID/index.d.ts +25 -22
- package/lib/DID/index.js +95 -81
- package/lib/type.d.ts +1 -0
- package/package.json +5 -5
- package/src/Address/compact-text.jsx +2 -1
- package/src/Address/did-address.tsx +215 -0
- package/src/Address/index.tsx +18 -0
- package/src/Address/responsive-did-address.tsx +90 -0
- package/src/BlockletV2/blocklet.tsx +3 -1
- package/src/BlockletV2/components/icon-text.tsx +8 -2
- package/src/DID/{index.jsx → index.tsx} +130 -71
- package/src/type.d.ts +1 -0
- package/src/Address/did-address.jsx +0 -220
- package/src/Address/index.jsx +0 -18
- package/src/Address/responsive-did-address.jsx +0 -79
package/lib/DID/index.js
CHANGED
@@ -1,47 +1,30 @@
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
-
import { forwardRef, useState } from 'react';
|
3
|
-
import PropTypes from 'prop-types';
|
4
2
|
import { getDIDMotifInfo } from '@arcblock/did-motif';
|
3
|
+
import { types } from '@ocap/mcrypto';
|
5
4
|
import QRCode from 'qrcode.react';
|
6
5
|
import { Icon } from '@iconify/react';
|
7
6
|
import IconQrCode from '@iconify-icons/material-symbols/qr-code-rounded';
|
8
|
-
import { Box, Dialog, DialogContent,
|
7
|
+
import { Box, Dialog, DialogContent, Typography } from '@mui/material';
|
9
8
|
import { useCreation, useMemoizedFn } from 'ahooks';
|
10
|
-
import {
|
11
|
-
import { DID_PREFIX } from '../Util/constant';
|
9
|
+
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
12
10
|
import Address from '../Address';
|
13
11
|
import Avatar from '../Avatar';
|
14
|
-
import {
|
12
|
+
import { temp as colors } from '../Colors';
|
13
|
+
import { DID_PREFIX } from '../Util/constant';
|
15
14
|
import { translate } from '../Locale/util';
|
15
|
+
import { getDIDColor, isEthereumDid } from '../Util';
|
16
16
|
const translations = {
|
17
17
|
en: {
|
18
|
-
scanQrcode: 'Scan with DID Wallet to
|
18
|
+
scanQrcode: 'Scan with DID Wallet to view this {role}',
|
19
19
|
download: 'Download',
|
20
20
|
downloadTips: "Don't have DID Wallet ?"
|
21
21
|
},
|
22
22
|
zh: {
|
23
|
-
scanQrcode: '
|
23
|
+
scanQrcode: '使用 DID Wallet 扫码查看该 {role}',
|
24
24
|
download: '下载',
|
25
25
|
downloadTips: '没有 DID Wallet ?'
|
26
26
|
}
|
27
27
|
};
|
28
|
-
const DIDPropTypes = {
|
29
|
-
did: PropTypes.string.isRequired,
|
30
|
-
size: PropTypes.number,
|
31
|
-
component: PropTypes.string,
|
32
|
-
copyable: PropTypes.bool,
|
33
|
-
responsive: PropTypes.bool,
|
34
|
-
showCopyButtonInTooltip: PropTypes.bool,
|
35
|
-
showAvatar: PropTypes.bool,
|
36
|
-
showQrcode: PropTypes.bool,
|
37
|
-
inline: PropTypes.bool,
|
38
|
-
append: PropTypes.any,
|
39
|
-
compact: PropTypes.bool,
|
40
|
-
startChars: PropTypes.number,
|
41
|
-
endChars: PropTypes.number,
|
42
|
-
locale: PropTypes.oneOf(['en', 'zh']),
|
43
|
-
chainId: PropTypes.string
|
44
|
-
};
|
45
28
|
const DEFAULT_CHAIN_ID = 'xenon-2020-01-15';
|
46
29
|
const CHAIN_ID_MAP = {
|
47
30
|
'main.abtnetwork.io': DEFAULT_CHAIN_ID,
|
@@ -63,6 +46,16 @@ const isSquareMotif = roleType => {
|
|
63
46
|
};
|
64
47
|
return !roles[roleType];
|
65
48
|
};
|
49
|
+
const getRoleName = roleType => {
|
50
|
+
const [roleName] = Object.entries(types.RoleType).find(item => item[1] === roleType) || [];
|
51
|
+
if (roleName) {
|
52
|
+
// UpperCase first word, example:
|
53
|
+
// ROLE_ACCOUNT -> Account
|
54
|
+
// ROLE_APPLICATION -> Application
|
55
|
+
return roleName.toLowerCase().replace(/role_(\S)/g, (_, $1) => $1.toUpperCase());
|
56
|
+
}
|
57
|
+
return 'Address';
|
58
|
+
};
|
66
59
|
const getAvatarSize = (didMotifInfo, isEthDid, size) => {
|
67
60
|
if (isEthDid) {
|
68
61
|
return size * 0.75;
|
@@ -72,28 +65,46 @@ const getAvatarSize = (didMotifInfo, isEthDid, size) => {
|
|
72
65
|
}
|
73
66
|
return size;
|
74
67
|
};
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
68
|
+
const DID = /*#__PURE__*/forwardRef((props, ref) => {
|
69
|
+
const {
|
70
|
+
did,
|
71
|
+
showAvatar = true,
|
72
|
+
showQrcode = false,
|
73
|
+
locale = 'en',
|
74
|
+
size = 0,
|
75
|
+
component = 'span',
|
76
|
+
copyable = true,
|
77
|
+
responsive = true,
|
78
|
+
showCopyButtonInTooltip = false,
|
79
|
+
inline = false,
|
80
|
+
append = null,
|
81
|
+
compact = false,
|
82
|
+
startChars = 6,
|
83
|
+
endChars = 6
|
84
|
+
} = props;
|
85
|
+
const rest = {
|
86
|
+
size,
|
87
|
+
component,
|
88
|
+
copyable,
|
89
|
+
responsive,
|
90
|
+
showCopyButtonInTooltip,
|
91
|
+
inline,
|
92
|
+
append,
|
93
|
+
compact,
|
94
|
+
startChars,
|
95
|
+
endChars
|
96
|
+
};
|
97
|
+
const addressRef = useRef(null);
|
87
98
|
const t = useMemoizedFn((key, data = {}) => {
|
88
99
|
return translate(translations, key, locale, 'en', data);
|
89
100
|
});
|
101
|
+
let chainId = props.chainId || '';
|
90
102
|
try {
|
91
103
|
if (!chainId) {
|
92
|
-
const chainHostUrl = window
|
104
|
+
const chainHostUrl = window.blocklet?.CHAIN_HOST;
|
93
105
|
if (chainHostUrl) {
|
94
106
|
const chainHost = new URL(chainHostUrl).hostname;
|
95
107
|
if (chainHost) {
|
96
|
-
// eslint-disable-next-line no-param-reassign
|
97
108
|
chainId = CHAIN_ID_MAP[chainHost];
|
98
109
|
}
|
99
110
|
}
|
@@ -104,23 +115,19 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
104
115
|
const [open, setOpen] = useState(false);
|
105
116
|
const isEthDid = isEthereumDid(did);
|
106
117
|
const didMotifInfo = isEthDid ? undefined : getDIDMotifInfo(did);
|
107
|
-
const
|
108
|
-
const prepend = [/*#__PURE__*/_jsx("span", {
|
118
|
+
const getPrepend = avatarSize => [/*#__PURE__*/_jsx("span", {
|
109
119
|
style: {
|
110
120
|
flex: '0 0 auto',
|
111
|
-
fontSize,
|
112
121
|
color: getFontColor(did, didMotifInfo, isEthDid)
|
113
122
|
},
|
114
123
|
children: "DID:"
|
115
124
|
}, "prefix-did"), /*#__PURE__*/_jsx("span", {
|
116
125
|
className: "did-address-text",
|
117
|
-
style: {
|
118
|
-
fontSize
|
119
|
-
},
|
120
126
|
children: "ABT:"
|
121
127
|
}, "prefix-abt"), showAvatar && /*#__PURE__*/_jsx(Avatar, {
|
128
|
+
src: "",
|
122
129
|
did: did,
|
123
|
-
size: getAvatarSize(didMotifInfo, isEthDid,
|
130
|
+
size: getAvatarSize(didMotifInfo, isEthDid, avatarSize || 18),
|
124
131
|
style: {
|
125
132
|
display: 'inline-flex',
|
126
133
|
alignItems: 'center',
|
@@ -138,8 +145,18 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
138
145
|
e.preventDefault();
|
139
146
|
setOpen(true);
|
140
147
|
});
|
148
|
+
useImperativeHandle(ref, () => {
|
149
|
+
return new Proxy({
|
150
|
+
openQRCode: () => setOpen(true),
|
151
|
+
closeQRCode: () => setOpen(false)
|
152
|
+
}, {
|
153
|
+
get(target, key) {
|
154
|
+
return target[key] || addressRef?.current?.[key];
|
155
|
+
}
|
156
|
+
});
|
157
|
+
});
|
141
158
|
const downloadUrl = useCreation(() => {
|
142
|
-
if (['zh', 'en'].includes) {
|
159
|
+
if (['zh', 'en'].includes(locale)) {
|
143
160
|
return `https://www.didwallet.io/${locale}`;
|
144
161
|
}
|
145
162
|
return 'https://www.didwallet.io/en';
|
@@ -164,13 +181,14 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
164
181
|
}, [downloadUrl]);
|
165
182
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
166
183
|
children: [/*#__PURE__*/_jsx(Address, {
|
167
|
-
ref: ref,
|
168
184
|
locale: locale,
|
169
185
|
content: `${DID_PREFIX}${did}`,
|
170
186
|
...rest,
|
171
|
-
|
187
|
+
ref: addressRef,
|
188
|
+
prepend: getPrepend(rest.size),
|
172
189
|
append: /*#__PURE__*/_jsxs(_Fragment, {
|
173
190
|
children: [showQrcode ? /*#__PURE__*/_jsx(Box, {
|
191
|
+
id: "did-qrcode-button",
|
174
192
|
component: Icon,
|
175
193
|
icon: IconQrCode,
|
176
194
|
onClick: openQrCode,
|
@@ -183,20 +201,31 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
183
201
|
}) : null, rest.append]
|
184
202
|
}),
|
185
203
|
children: did
|
186
|
-
}), /*#__PURE__*/
|
204
|
+
}), /*#__PURE__*/_jsx(Dialog, {
|
187
205
|
open: open,
|
188
206
|
onClose: closeQrCode,
|
189
207
|
maxWidth: "sm",
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
}), /*#__PURE__*/_jsxs(DialogContent, {
|
208
|
+
PaperProps: {
|
209
|
+
sx: {
|
210
|
+
boxShadow: 'none',
|
211
|
+
borderRadius: '12px'
|
212
|
+
}
|
213
|
+
},
|
214
|
+
children: /*#__PURE__*/_jsxs(DialogContent, {
|
198
215
|
align: "center",
|
199
|
-
|
216
|
+
sx: {
|
217
|
+
p: 3
|
218
|
+
},
|
219
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
220
|
+
sx: {
|
221
|
+
mb: 2,
|
222
|
+
fontWeight: 500,
|
223
|
+
fontSize: 18
|
224
|
+
},
|
225
|
+
children: t('scanQrcode', {
|
226
|
+
role: getRoleName(didMotifInfo?.roleType)
|
227
|
+
})
|
228
|
+
}), /*#__PURE__*/_jsx(QRCode
|
200
229
|
// eslint-disable-next-line max-len
|
201
230
|
, {
|
202
231
|
value: `abt://abtwallet.io/i?did=${DID_PREFIX}${did}&action=didRecognize&chainID=${chainId || DEFAULT_CHAIN_ID}`,
|
@@ -205,14 +234,16 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
205
234
|
level: "M"
|
206
235
|
}), /*#__PURE__*/_jsx(Box, {
|
207
236
|
sx: {
|
208
|
-
|
237
|
+
mt: 1.5,
|
209
238
|
textAlign: 'center'
|
210
239
|
},
|
211
240
|
children: /*#__PURE__*/_jsx(Address, {
|
212
|
-
copyable: true,
|
213
241
|
locale: locale,
|
214
242
|
content: `${DID_PREFIX}${did}`,
|
215
|
-
prepend:
|
243
|
+
prepend: getPrepend(16),
|
244
|
+
...rest,
|
245
|
+
size: 16,
|
246
|
+
copyable: true,
|
216
247
|
children: did
|
217
248
|
})
|
218
249
|
}), /*#__PURE__*/_jsx(Typography, {
|
@@ -224,25 +255,8 @@ const DID = /*#__PURE__*/forwardRef(({
|
|
224
255
|
},
|
225
256
|
children: downloadTips
|
226
257
|
})]
|
227
|
-
})
|
258
|
+
})
|
228
259
|
})]
|
229
260
|
});
|
230
261
|
});
|
231
|
-
export default DID;
|
232
|
-
DID.propTypes = DIDPropTypes;
|
233
|
-
DID.defaultProps = {
|
234
|
-
size: 0,
|
235
|
-
component: 'span',
|
236
|
-
copyable: true,
|
237
|
-
responsive: true,
|
238
|
-
showCopyButtonInTooltip: false,
|
239
|
-
showAvatar: true,
|
240
|
-
showQrcode: false,
|
241
|
-
inline: false,
|
242
|
-
append: null,
|
243
|
-
compact: false,
|
244
|
-
startChars: 6,
|
245
|
-
endChars: 6,
|
246
|
-
locale: 'en',
|
247
|
-
chainId: ''
|
248
|
-
};
|
262
|
+
export default DID;
|
package/lib/type.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
declare module '@arcblock/did-motif';
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@arcblock/ux",
|
3
|
-
"version": "2.10.
|
3
|
+
"version": "2.10.45",
|
4
4
|
"description": "Common used react components for arcblock products",
|
5
5
|
"keywords": [
|
6
6
|
"react",
|
@@ -64,12 +64,12 @@
|
|
64
64
|
"react": ">=18.2.0",
|
65
65
|
"react-router-dom": ">=6.22.3"
|
66
66
|
},
|
67
|
-
"gitHead": "
|
67
|
+
"gitHead": "f4387cd434dcdf0bab88973b014575613dcb3611",
|
68
68
|
"dependencies": {
|
69
69
|
"@arcblock/did-motif": "^1.1.13",
|
70
|
-
"@arcblock/icons": "^2.10.
|
71
|
-
"@arcblock/nft-display": "^2.10.
|
72
|
-
"@arcblock/react-hooks": "^2.10.
|
70
|
+
"@arcblock/icons": "^2.10.45",
|
71
|
+
"@arcblock/nft-display": "^2.10.45",
|
72
|
+
"@arcblock/react-hooks": "^2.10.45",
|
73
73
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
74
74
|
"@fontsource/inter": "^5.0.16",
|
75
75
|
"@fontsource/ubuntu-mono": "^5.0.18",
|
@@ -65,7 +65,7 @@ export default function CompactText({ startChars, endChars, children, showCopyBu
|
|
65
65
|
CompactText.propTypes = {
|
66
66
|
startChars: PropTypes.number,
|
67
67
|
endChars: PropTypes.number,
|
68
|
-
children: PropTypes.node
|
68
|
+
children: PropTypes.node,
|
69
69
|
// 在 tooltip 中完整地址后显示复制按钮
|
70
70
|
showCopyButtonInTooltip: PropTypes.bool,
|
71
71
|
};
|
@@ -73,5 +73,6 @@ CompactText.propTypes = {
|
|
73
73
|
CompactText.defaultProps = {
|
74
74
|
startChars: 6,
|
75
75
|
endChars: 6,
|
76
|
+
children: null,
|
76
77
|
showCopyButtonInTooltip: false,
|
77
78
|
};
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import '@fontsource/ubuntu-mono/400.css';
|
2
|
+
import { Check as CheckIcon, ContentCopy as CopyIcon } from '@mui/icons-material';
|
3
|
+
import { Box, BoxProps, Tooltip } from '@mui/material';
|
4
|
+
import { green } from '@mui/material/colors';
|
5
|
+
import copy from 'copy-to-clipboard';
|
6
|
+
import noop from 'lodash/noop';
|
7
|
+
import React, { forwardRef, ReactNode, useImperativeHandle, useRef, useState } from 'react';
|
8
|
+
import { styled } from '../Theme';
|
9
|
+
import { getFontSize } from '../Util';
|
10
|
+
import CompactText from './compact-text';
|
11
|
+
|
12
|
+
const translations = {
|
13
|
+
en: {
|
14
|
+
copy: 'Click To Copy',
|
15
|
+
copied: 'Copied!',
|
16
|
+
},
|
17
|
+
zh: {
|
18
|
+
copy: '点击复制',
|
19
|
+
copied: '已复制!',
|
20
|
+
},
|
21
|
+
};
|
22
|
+
|
23
|
+
export interface HTMLDidAddressElement extends HTMLDivElement {
|
24
|
+
copy: () => void;
|
25
|
+
}
|
26
|
+
export interface IDidAddressProps extends BoxProps {
|
27
|
+
component?: React.ElementType;
|
28
|
+
size?: number;
|
29
|
+
copyable?: boolean;
|
30
|
+
// compact mode 下, hover 时会在 tooltip 中显示完整地址, showCopyButtonInTooltip = true 时会在完整地址后显示一个复制按钮
|
31
|
+
showCopyButtonInTooltip?: boolean;
|
32
|
+
content?: string;
|
33
|
+
inline?: boolean;
|
34
|
+
prepend?: ReactNode;
|
35
|
+
append?: ReactNode;
|
36
|
+
// 紧凑模式
|
37
|
+
compact?: boolean;
|
38
|
+
startChars?: number;
|
39
|
+
endChars?: number;
|
40
|
+
locale?: 'en' | 'zh';
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* DidAddress 组件 (新版设计)
|
45
|
+
*
|
46
|
+
* - 样式调整
|
47
|
+
* - click-to-copy 调整
|
48
|
+
* - 长文本截断处理 (Ellipsis)
|
49
|
+
* - 支持 inline 或 block 的显示方式
|
50
|
+
* - 支持紧凑模式, 该模式下:
|
51
|
+
* - 占用宽度较小, 因此不考虑水平空间不够用的情况, 且忽略末尾省略号
|
52
|
+
* - 对于多层元素结构的 children, 保持元素结构, 将最内层 text 替换为 CompactText 组件
|
53
|
+
* - 为保证 copy 功能正常工作, 原 children 始终渲染, 但在紧凑式下会隐藏
|
54
|
+
* - 可配合 useMediaQuery 使用
|
55
|
+
*/
|
56
|
+
const DidAddress = forwardRef<HTMLDidAddressElement, IDidAddressProps>((props, ref) => {
|
57
|
+
const {
|
58
|
+
component = 'span',
|
59
|
+
size = 0,
|
60
|
+
copyable = true,
|
61
|
+
showCopyButtonInTooltip = false,
|
62
|
+
children = null,
|
63
|
+
content = '',
|
64
|
+
prepend = null,
|
65
|
+
append = null,
|
66
|
+
compact = false,
|
67
|
+
startChars = 6,
|
68
|
+
endChars = 6,
|
69
|
+
locale: originalLocale = 'en',
|
70
|
+
...rest
|
71
|
+
} = props;
|
72
|
+
let locale = originalLocale;
|
73
|
+
if (!translations[locale]) {
|
74
|
+
locale = 'en';
|
75
|
+
}
|
76
|
+
|
77
|
+
const [copied, setCopied] = useState(false);
|
78
|
+
const textRef = useRef<HTMLDivElement>(null);
|
79
|
+
const rootRef = useRef<any>(null);
|
80
|
+
|
81
|
+
const handleCopy = () => {
|
82
|
+
copy(content || textRef.current?.textContent || '');
|
83
|
+
setCopied(true);
|
84
|
+
// 恢复 copied 状态
|
85
|
+
setTimeout(() => {
|
86
|
+
setCopied(false);
|
87
|
+
}, 1500);
|
88
|
+
};
|
89
|
+
const onCopy = (e: any) => {
|
90
|
+
e.stopPropagation();
|
91
|
+
e.preventDefault();
|
92
|
+
handleCopy();
|
93
|
+
};
|
94
|
+
useImperativeHandle(
|
95
|
+
ref,
|
96
|
+
() =>
|
97
|
+
new Proxy(
|
98
|
+
{
|
99
|
+
copy: handleCopy,
|
100
|
+
},
|
101
|
+
{
|
102
|
+
get(target: any, key: string) {
|
103
|
+
return target[key] || rootRef.current?.[key];
|
104
|
+
},
|
105
|
+
}
|
106
|
+
)
|
107
|
+
);
|
108
|
+
|
109
|
+
let copyElement = null;
|
110
|
+
if (copyable) {
|
111
|
+
copyElement = (
|
112
|
+
<span className="did-address-copy-wrapper" title={copied ? '' : translations[locale].copy}>
|
113
|
+
{copied ? (
|
114
|
+
<Tooltip title={translations[locale].copied} placement="bottom" arrow open={copied}>
|
115
|
+
<CheckIcon className="did-address-copy" style={{ color: green[500] }} />
|
116
|
+
</Tooltip>
|
117
|
+
) : (
|
118
|
+
/* title prop 直接加在 icon 上不生效 */
|
119
|
+
<CopyIcon className="did-address-copy" onClick={onCopy} />
|
120
|
+
)}
|
121
|
+
</span>
|
122
|
+
);
|
123
|
+
}
|
124
|
+
|
125
|
+
return (
|
126
|
+
<Root as={component} size={size} {...rest} ref={rootRef}>
|
127
|
+
{prepend}
|
128
|
+
<Box sx={{ display: 'none' }} ref={textRef}>
|
129
|
+
{children}
|
130
|
+
</Box>
|
131
|
+
{/* 注意: 该元素必须渲染(可以隐藏), 以便 compact 模式下复制的文本是完整的 */}
|
132
|
+
<Tooltip title={copyable ? '' : translations[locale].copied} placement="bottom" arrow open={copied}>
|
133
|
+
{compact ? (
|
134
|
+
<Box
|
135
|
+
component="span"
|
136
|
+
className="did-address-text"
|
137
|
+
sx={{
|
138
|
+
cursor: copyable ? 'unset' : 'pointer',
|
139
|
+
}}
|
140
|
+
onDoubleClick={copyable ? noop : onCopy}>
|
141
|
+
<CompactText startChars={startChars} endChars={endChars} showCopyButtonInTooltip={showCopyButtonInTooltip}>
|
142
|
+
{children}
|
143
|
+
</CompactText>
|
144
|
+
</Box>
|
145
|
+
) : (
|
146
|
+
<Box
|
147
|
+
component="span"
|
148
|
+
className="did-address-text did-address-truncate"
|
149
|
+
sx={{
|
150
|
+
display: compact ? 'none' : 'inline',
|
151
|
+
cursor: copyable ? 'unset' : 'pointer',
|
152
|
+
}}
|
153
|
+
onDoubleClick={copyable ? noop : onCopy}>
|
154
|
+
{children}
|
155
|
+
</Box>
|
156
|
+
)}
|
157
|
+
</Tooltip>
|
158
|
+
{copyElement}
|
159
|
+
{append}
|
160
|
+
</Root>
|
161
|
+
);
|
162
|
+
});
|
163
|
+
|
164
|
+
export default DidAddress;
|
165
|
+
|
166
|
+
const Root = styled<any>(Box, { shouldForwardProp: (prop) => prop !== 'inline' })`
|
167
|
+
font-family: 'Ubuntu Mono', monospace;
|
168
|
+
&& {
|
169
|
+
display: ${({ inline }: any) => (inline ? 'inline-flex' : 'flex')};
|
170
|
+
align-items: center;
|
171
|
+
max-width: 100%;
|
172
|
+
overflow: hidden;
|
173
|
+
color: #ccc;
|
174
|
+
font-size: ${(props: any) => getFontSize(props.size)};
|
175
|
+
font-weight: 400;
|
176
|
+
|
177
|
+
svg {
|
178
|
+
fill: currentColor;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
.did-address-text {
|
183
|
+
color: #666;
|
184
|
+
}
|
185
|
+
/* truncate string with ellipsis */
|
186
|
+
.did-address-truncate {
|
187
|
+
white-space: nowrap;
|
188
|
+
overflow: hidden;
|
189
|
+
text-overflow: ellipsis;
|
190
|
+
}
|
191
|
+
|
192
|
+
.did-address-copy-wrapper {
|
193
|
+
display: flex;
|
194
|
+
justify-content: center;
|
195
|
+
align-items: center;
|
196
|
+
width: 1em;
|
197
|
+
height: 1em;
|
198
|
+
margin-left: 8px;
|
199
|
+
}
|
200
|
+
.did-address-copy {
|
201
|
+
flex: 0 0 auto;
|
202
|
+
font-size: 1em;
|
203
|
+
color: #999;
|
204
|
+
cursor: pointer;
|
205
|
+
}
|
206
|
+
|
207
|
+
/* link */
|
208
|
+
a {
|
209
|
+
color: #666;
|
210
|
+
}
|
211
|
+
&:hover a {
|
212
|
+
color: #222;
|
213
|
+
text-decoration: underline;
|
214
|
+
}
|
215
|
+
`;
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { forwardRef } from 'react';
|
2
|
+
import DidAddress, { HTMLDidAddressElement, IDidAddressProps } from './did-address';
|
3
|
+
import ResponsiveDidAddress from './responsive-did-address';
|
4
|
+
|
5
|
+
export interface IDidAddressWrapper extends IDidAddressProps {
|
6
|
+
responsive?: boolean;
|
7
|
+
}
|
8
|
+
|
9
|
+
const DidAddressWrapper = forwardRef<HTMLDidAddressElement, IDidAddressWrapper>(
|
10
|
+
({ responsive = true, ...rest }, ref) => {
|
11
|
+
if (responsive) {
|
12
|
+
return <ResponsiveDidAddress {...rest} ref={ref} />;
|
13
|
+
}
|
14
|
+
return <DidAddress {...rest} ref={ref} />;
|
15
|
+
}
|
16
|
+
);
|
17
|
+
|
18
|
+
export default DidAddressWrapper;
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { useSize } from 'ahooks';
|
2
|
+
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
3
|
+
import { styled } from '../Theme';
|
4
|
+
|
5
|
+
import DidAddress, { HTMLDidAddressElement, IDidAddressProps } from './did-address';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* 根据父容器宽度自动切换 compact 模式
|
9
|
+
*
|
10
|
+
* 实现逻辑:
|
11
|
+
* - DidAddress 外层包裹一个容器, 其宽度自动撑满父容器宽度 (即这个容器需要是块级元素或 100% 宽的 inline-block)
|
12
|
+
* - DidAddress 本身以 inline 形式渲染 (方便探测 did-address 的 full-width)
|
13
|
+
* - 组件 mounted 时记录 did address 的 full-width (非 compact 模式的宽度)
|
14
|
+
* - 监听容器宽度变化, 当容器宽度变化时, 对比容器宽度和 did address full-width, => 切换 compact 模式
|
15
|
+
* - TODO: 初始化时, 在确定是否应该以 compact 模式渲染前, 隐藏显示, 避免闪烁问题
|
16
|
+
*/
|
17
|
+
const ResponsiveDidAddress = forwardRef<HTMLDidAddressElement, IResponsiveDidAddressProps>(
|
18
|
+
({ style, className, component = 'span', ...rest }, ref) => {
|
19
|
+
const [compact, setCompact] = useState(false);
|
20
|
+
// did address 完整显示时的宽度
|
21
|
+
const [addressFullWidth, setAddressFullWidth] = useState<number | null | undefined>(null);
|
22
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
23
|
+
const innerRef = useRef<HTMLDidAddressElement>(null);
|
24
|
+
const size = useSize(containerRef);
|
25
|
+
const containerWidth = size?.width || 0;
|
26
|
+
|
27
|
+
useImperativeHandle(
|
28
|
+
ref,
|
29
|
+
() =>
|
30
|
+
new Proxy(
|
31
|
+
{},
|
32
|
+
{
|
33
|
+
get(target: any, key: keyof HTMLDidAddressElement) {
|
34
|
+
return innerRef.current?.[key];
|
35
|
+
},
|
36
|
+
}
|
37
|
+
)
|
38
|
+
);
|
39
|
+
|
40
|
+
// 存储完整显示时 address 组件的宽度
|
41
|
+
useEffect(() => {
|
42
|
+
if (!compact && addressFullWidth === null) {
|
43
|
+
setAddressFullWidth(innerRef.current?.offsetWidth);
|
44
|
+
}
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
46
|
+
}, []);
|
47
|
+
|
48
|
+
useEffect(() => {
|
49
|
+
if (containerWidth && addressFullWidth) {
|
50
|
+
setCompact(containerWidth < addressFullWidth);
|
51
|
+
}
|
52
|
+
}, [containerWidth, addressFullWidth]);
|
53
|
+
return (
|
54
|
+
<Root as={component} ref={containerRef} style={style} className={className}>
|
55
|
+
<StyledDidAddress {...rest} component={component} inline compact={compact} ref={innerRef} />
|
56
|
+
</Root>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
);
|
60
|
+
|
61
|
+
export default ResponsiveDidAddress;
|
62
|
+
|
63
|
+
export interface IResponsiveDidAddressProps extends IDidAddressProps {
|
64
|
+
style?: React.CSSProperties;
|
65
|
+
className?: string;
|
66
|
+
component?: React.ElementType;
|
67
|
+
}
|
68
|
+
|
69
|
+
const Root = styled('div')`
|
70
|
+
display: block;
|
71
|
+
overflow: hidden;
|
72
|
+
${({ inline }: any) =>
|
73
|
+
inline &&
|
74
|
+
`
|
75
|
+
display: inline-block;
|
76
|
+
width: 100%;
|
77
|
+
`}
|
78
|
+
`;
|
79
|
+
|
80
|
+
const StyledDidAddress = styled(DidAddress)`
|
81
|
+
&& {
|
82
|
+
max-width: none;
|
83
|
+
}
|
84
|
+
.did-address-text {
|
85
|
+
/* 禁止文本 Ellipsis/截断, 以便测量真实的宽度 */
|
86
|
+
white-space: nowrap;
|
87
|
+
overflow: visible;
|
88
|
+
text-overflow: unset;
|
89
|
+
}
|
90
|
+
`;
|
@@ -124,7 +124,9 @@ export default function BlockletStore(props: IBlockletStore) {
|
|
124
124
|
/>
|
125
125
|
</Stack>
|
126
126
|
<Stack direction="row" alignItems="center" gap={2} color="text.secondary">
|
127
|
-
<IconText icon={<Avatar src={avatar} did={did} size={20} variant="circle" />}
|
127
|
+
<IconText icon={<Avatar src={avatar} did={did} size={20} variant="circle" />} maxWidth={200}>
|
128
|
+
{author}
|
129
|
+
</IconText>
|
128
130
|
<IconText icon={<Icon icon={Download} />} title={download}>
|
129
131
|
{formatDownloadCount(download)}
|
130
132
|
</IconText>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Stack, Typography } from '@mui/material';
|
1
|
+
import { Stack, StackProps, Typography } from '@mui/material';
|
2
2
|
import React from 'react';
|
3
3
|
|
4
4
|
export default function IconText({
|
@@ -6,15 +6,21 @@ export default function IconText({
|
|
6
6
|
children,
|
7
7
|
maxWidth = 100,
|
8
8
|
title,
|
9
|
+
sx,
|
9
10
|
}: {
|
10
11
|
icon?: React.ReactNode;
|
11
12
|
children?: React.ReactNode;
|
12
13
|
maxWidth?: number;
|
13
14
|
title?: string;
|
15
|
+
sx?: StackProps['sx'];
|
14
16
|
}) {
|
15
17
|
return (
|
16
18
|
(children === 0 || children) && (
|
17
|
-
<Stack
|
19
|
+
<Stack
|
20
|
+
direction="row"
|
21
|
+
alignItems="center"
|
22
|
+
gap={1}
|
23
|
+
sx={[{ maxWidth, overflow: 'hidden' }, ...(Array.isArray(sx) ? sx : [sx])]}>
|
18
24
|
{icon}
|
19
25
|
<Typography
|
20
26
|
flex={1}
|