@arcblock/ux 2.10.43 → 2.10.45

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