@arcblock/ux 2.12.3 → 2.12.5

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.
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ const paddingTop = 8;
4
+
5
+ // 下拉子菜单容器
6
+ export function SubContainer({
7
+ children,
8
+ ...props
9
+ }) {
10
+ const rootRef = useRef(null);
11
+ const [position, setPosition] = useState(0); // 只需要保存水平偏移量
12
+
13
+ const updatePosition = () => {
14
+ if (!rootRef.current) return;
15
+ const anchor = rootRef.current.parentElement; // 以父容器作为参照
16
+ if (!anchor) return;
17
+ const anchorRect = anchor.getBoundingClientRect();
18
+ const containerRect = rootRef.current.getBoundingClientRect();
19
+ const windowWidth = window.innerWidth;
20
+ const padding = 32;
21
+
22
+ // 1. 计算相对于父元素的水平居中位置
23
+ let left = (anchorRect.width - containerRect.width) / 2;
24
+
25
+ // 2. 检查容器在视口中的绝对位置
26
+ const absoluteLeft = anchorRect.left + left;
27
+
28
+ // 3. 调整水平位置确保不超出窗口
29
+ if (absoluteLeft < 0) {
30
+ // 如果超出左边界,向右偏移
31
+ left = left - absoluteLeft + padding;
32
+ } else if (absoluteLeft + containerRect.width > windowWidth) {
33
+ // 如果超出右边界,向左偏移
34
+ left -= absoluteLeft + containerRect.width - windowWidth + padding;
35
+ }
36
+ setPosition(left);
37
+ };
38
+
39
+ // 监听自身大小变化
40
+ useEffect(() => {
41
+ const resizeObserver = new ResizeObserver(() => {
42
+ updatePosition();
43
+ });
44
+ resizeObserver.observe(rootRef.current);
45
+ return () => resizeObserver.disconnect();
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, []);
48
+
49
+ // 监听 anchor 的大小变化
50
+ useEffect(() => {
51
+ const anchor = rootRef.current?.parentElement;
52
+ let resizeObserver = null;
53
+ if (anchor) {
54
+ resizeObserver = new ResizeObserver(() => {
55
+ updatePosition();
56
+ });
57
+ resizeObserver.observe(anchor);
58
+ }
59
+ return () => resizeObserver?.disconnect();
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ }, []);
62
+
63
+ // 监听窗口大小变化
64
+ useEffect(() => {
65
+ const handleResize = () => {
66
+ updatePosition();
67
+ };
68
+ window.addEventListener('resize', handleResize);
69
+ return () => window.removeEventListener('resize', handleResize);
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, []);
72
+ return /*#__PURE__*/_jsx("div", {
73
+ ref: rootRef,
74
+ className: "navmenu-sub__container",
75
+ style: {
76
+ paddingTop: `${paddingTop}px`,
77
+ transform: `translateX(${position}px)`
78
+ },
79
+ ...props,
80
+ children: children
81
+ });
82
+ }
83
+ export default SubContainer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.12.3",
3
+ "version": "2.12.5",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -68,12 +68,12 @@
68
68
  "react": ">=18.2.0",
69
69
  "react-router-dom": ">=6.22.3"
70
70
  },
71
- "gitHead": "d0ad7fcbd9bbae010ad58235494150e3c0814d43",
71
+ "gitHead": "3999edcf6f4b5fd30f3c5d27b27f85e0051315b4",
72
72
  "dependencies": {
73
73
  "@arcblock/did-motif": "^1.1.13",
74
- "@arcblock/icons": "^2.12.3",
75
- "@arcblock/nft-display": "^2.12.3",
76
- "@arcblock/react-hooks": "^2.12.3",
74
+ "@arcblock/icons": "^2.12.5",
75
+ "@arcblock/nft-display": "^2.12.5",
76
+ "@arcblock/react-hooks": "^2.12.5",
77
77
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
78
78
  "@fontsource/inter": "^5.0.16",
79
79
  "@fontsource/ubuntu-mono": "^5.0.18",
@@ -5,7 +5,8 @@ import { MoreHoriz as MoreHorizIcon, ExpandMore as ExpandMoreIcon, Menu as MenuI
5
5
  import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
6
6
  import { useCreation, useMemoizedFn, useReactive, useSize, useThrottleFn } from 'ahooks';
7
7
  import { NavMenuProvider, useNavMenuContext } from './nav-menu-context';
8
- import { NavMenuRoot, NavMenuList, NavMenuItem, NavMenuSub, NavMenuSubList } from './style';
8
+ import { NavMenuRoot, NavMenuItem, NavMenuSub, NavMenuSubList, NavMenuStyled } from './style';
9
+ import { SubContainer } from './sub-container';
9
10
 
10
11
  // 过滤 children 中的 Item/Sub, 忽略其它类型的 element
11
12
  function filterItems(children: React.ReactNode) {
@@ -67,7 +68,6 @@ function NavMenu({
67
68
  if (!items?.length && !children?.length) {
68
69
  throw new Error("One of 'items' or 'children' is required by NavMenu component.");
69
70
  }
70
-
71
71
  const currentState = useReactive({
72
72
  activeId,
73
73
  openedIds: [] as string[],
@@ -112,6 +112,7 @@ function NavMenu({
112
112
  const navMenuRef = useRef<HTMLUListElement | null>(null);
113
113
  const itemRefs = useRef<HTMLElement[]>([]);
114
114
  const moreIconRef = useRef<HTMLLIElement | null>(null);
115
+ const containerWidth = useRef(0);
115
116
  const isAllItemsHidden = currentState.hiddenItemCount === itemRefs.current?.length;
116
117
  const style = isAllItemsHidden ? { marginLeft: '0px' } : undefined;
117
118
 
@@ -136,7 +137,6 @@ function NavMenu({
136
137
  let totalWidthUsed = 0;
137
138
  let newHiddenCount = 0;
138
139
  let leftAllHidden = false;
139
- const containerWidth = containerSize?.width || 0;
140
140
  const moreIconWidth = moreIconRef.current
141
141
  ? moreIconRef.current.offsetWidth + parseFloat(window.getComputedStyle(moreIconRef.current).marginLeft)
142
142
  : 0;
@@ -147,7 +147,7 @@ function NavMenu({
147
147
  const marginLeft = index > 0 ? parseFloat(window.getComputedStyle(item).marginLeft) : 0;
148
148
  const currentItemWidth = item.offsetWidth + marginLeft;
149
149
 
150
- if (containerWidth - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
150
+ if (containerWidth.current - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
151
151
  totalWidthUsed += currentItemWidth;
152
152
  } else {
153
153
  item.style.display = 'none';
@@ -165,6 +165,7 @@ function NavMenu({
165
165
  );
166
166
 
167
167
  useLayoutEffect(() => {
168
+ containerWidth.current = containerSize?.width || navMenuRef.current?.clientWidth || 0;
168
169
  if (mode === 'horizontal') {
169
170
  checkItemsFit();
170
171
  }
@@ -195,8 +196,15 @@ function NavMenu({
195
196
 
196
197
  const classes = clsx('navmenu', `navmenu--${mode}`, rest.className);
197
198
 
198
- const renderItem = (item: ItemOptions, index: number, isTopLevel = false) => {
199
+ const renderItem = (item: ItemOptions, index: number, level = 0) => {
200
+ const isTopLevel = level === 0;
201
+
199
202
  if (item?.children) {
203
+ // 只渲染两级子菜单
204
+ if (level > 0) {
205
+ return null;
206
+ }
207
+
200
208
  // 对于 Sub 组件,如果它是顶级组件,则包含 ref
201
209
  return (
202
210
  <Sub
@@ -215,7 +223,7 @@ function NavMenu({
215
223
  {typeof item.children === 'function'
216
224
  ? item.children
217
225
  : item.children.map((childItem, childIndex: number) =>
218
- renderItem({ ...childItem, variant: 'panel' }, childIndex, false)
226
+ renderItem({ ...childItem, variant: 'panel' }, childIndex, level + 1)
219
227
  )}
220
228
  </Sub>
221
229
  );
@@ -243,21 +251,24 @@ function NavMenu({
243
251
  };
244
252
 
245
253
  const content = items
246
- ? items?.slice(-currentState.hiddenItemCount).map((item, index) => renderItem(item, index))
254
+ ? items?.slice(-currentState.hiddenItemCount).map((item, index) => renderItem(item, index, 1))
247
255
  : children?.slice(-currentState.hiddenItemCount);
248
256
 
257
+ // 当前展开的子菜单
258
+ const openedId = currentState.openedIds[0];
259
+
249
260
  return (
250
261
  <NavMenuProvider value={contextValue}>
251
- <NavMenuRoot {...rest} className={classes} $textColor={textColor} $bgColor={bgColor}>
252
- <NavMenuList className={clsx('navmenu-list', `navmenu-list--${mode}`)} ref={navMenuRef}>
253
- {items ? items.map((item, index) => renderItem(item, index, true)) : renderChildrenWithRef(children || [])}
262
+ <NavMenuStyled {...rest} className={classes} $textColor={textColor} $bgColor={bgColor}>
263
+ <NavMenuRoot className={clsx('navmenu-root', `navmenu-root--${mode}`)} ref={navMenuRef}>
264
+ {items ? items.map((item, index) => renderItem(item, index)) : renderChildrenWithRef(children || [])}
254
265
  {currentState.hiddenItemCount > 0 && (
255
266
  <Sub expandIcon={false} icon={icon} label="" ref={moreIconRef} style={style}>
256
267
  {content}
257
268
  </Sub>
258
269
  )}
259
- </NavMenuList>
260
- </NavMenuRoot>
270
+ </NavMenuRoot>
271
+ </NavMenuStyled>
261
272
  </NavMenuProvider>
262
273
  );
263
274
  }
@@ -324,7 +335,7 @@ export const Item = forwardRef<HTMLLIElement, ItemProps>(
324
335
  Item.displayName = 'NavMenu.Item';
325
336
 
326
337
  export interface SubProps extends Omit<ItemProps, 'children' | 'active'> {
327
- children?: Array<React.ReactElement> | ((props: { isOpen: boolean }) => React.ReactElement | null);
338
+ children?: Array<React.ReactElement | null> | ((props: { isOpen: boolean }) => React.ReactElement | null);
328
339
  expandIcon?: React.ReactNode | ((props: { isOpen: boolean }) => React.ReactNode);
329
340
  }
330
341
 
@@ -394,13 +405,13 @@ export const Sub = forwardRef<HTMLLIElement, SubProps>(
394
405
  {typeof expandIcon === 'function' ? expandIcon({ isOpen }) : expandIcon}
395
406
  </span>
396
407
  )}
397
- <div className="navmenu-sub__container" {...containerProps}>
408
+ <SubContainer {...containerProps}>
398
409
  {typeof children === 'function' ? (
399
410
  children({ isOpen }) // 自定义渲染
400
411
  ) : (
401
412
  <NavMenuSubList className="navmenu-sub__list">{filterItems(children)}</NavMenuSubList>
402
413
  )}
403
- </div>
414
+ </SubContainer>
404
415
  </NavMenuSub>
405
416
  );
406
417
  }
@@ -1,8 +1,7 @@
1
- import { useLayoutEffect, useRef } from 'react';
1
+ import { useRef } from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { useCreation, useMemoizedFn } from 'ahooks';
4
4
  import { Box, BoxProps, Grid } from '@mui/material';
5
- import { useWindowSize } from 'react-use';
6
5
  import SubItemGroup from './sub-item-group';
7
6
  import { Item } from './nav-menu';
8
7
  import { styled } from '../Theme';
@@ -38,58 +37,58 @@ const translations = {
38
37
  },
39
38
  products: {
40
39
  nftStudio: {
41
- description: 'Toolkit for NFT creation',
40
+ description: 'Mint and manage NFTs',
42
41
  },
43
42
  creatorStudio: {
44
- description: "Content creators' Swiss knife",
43
+ description: 'All-in-one creator tool',
45
44
  },
46
45
  aigne: {
47
- description: 'Chat with AIGNE',
46
+ description: 'Your AI assistant',
48
47
  },
49
48
  aistro: {
50
- description: 'Al Language Model Astrologer',
49
+ description: 'AI-powered astrology',
51
50
  },
52
51
  blockletLauncher: {
53
- description: 'Launch your blocklet here',
52
+ description: 'One-click app launcher',
54
53
  },
55
54
  alKit: {
56
- description: 'Empowers Al for application',
55
+ description: 'Boost apps with AI',
57
56
  },
58
57
  blockletStore: {
59
- description: 'App Store for all Blocklets',
58
+ description: 'Discover & deploy apps',
60
59
  },
61
60
  web3Kit: {
62
- description: 'Provides series components',
61
+ description: 'Web3 dev toolkit',
63
62
  },
64
63
  blockletFramework: {
65
- description: 'Launch your blocklet here',
64
+ description: 'Build and run blocklets',
66
65
  },
67
66
  didSpaces: {
68
- description: 'Decentralized storage space',
67
+ description: 'Secure personal storage',
69
68
  },
70
69
  abtNetwork: {
71
- description: "ArcBlock's blockchain system",
70
+ description: 'Fast blockchain network',
72
71
  },
73
72
  blockletServer: {
74
- description: 'Runtime Container Service',
73
+ description: 'Host your apps easily',
75
74
  },
76
- osar: {
77
- description: 'Supports multpy blockchains',
75
+ ocap: {
76
+ description: 'Multi-chain connector',
78
77
  },
79
78
  did: {
80
- description: 'Decentralized Identity',
79
+ description: 'Self-sovereign ID',
81
80
  },
82
81
  didWallet: {
83
- description: 'Intelligent digital wallet',
82
+ description: 'Smart digital wallet',
84
83
  },
85
84
  didNameService: {
86
- description: 'Purchase/host domain',
85
+ description: 'Web3 domain names',
87
86
  },
88
87
  vc: {
89
88
  description: 'Verifiable Credentials',
90
89
  },
91
90
  didConnect: {
92
- description: 'Login with DID',
91
+ description: 'Passwordless login',
93
92
  },
94
93
  },
95
94
  },
@@ -102,58 +101,58 @@ const translations = {
102
101
  },
103
102
  products: {
104
103
  nftStudio: {
105
- description: 'NFT 创作工具包',
104
+ description: '铸造和管理 NFT',
106
105
  },
107
106
  creatorStudio: {
108
- description: '内容创作者的瑞士军刀',
107
+ description: '一体化创作工具',
109
108
  },
110
109
  aigne: {
111
- description: '与 AIGNE 聊天',
110
+ description: '您的人工智能助手',
112
111
  },
113
112
  aistro: {
114
- description: 'Al 语言模型占星师',
113
+ description: 'AI 占星术',
115
114
  },
116
115
  blockletLauncher: {
117
- description: '在这里启动你的 blocklet',
116
+ description: '一键启动应用程序',
118
117
  },
119
118
  alKit: {
120
- description: '为应用赋能 Al',
119
+ description: 'AI 赋能应用',
121
120
  },
122
121
  blockletStore: {
123
- description: '所有 Blocklets 的应用商店',
122
+ description: '发现和部署应用程序',
124
123
  },
125
124
  web3Kit: {
126
- description: '提供系列组件',
125
+ description: 'Web3 开发工具包',
127
126
  },
128
127
  blockletFramework: {
129
- description: '在这里启动你的 blocklet',
128
+ description: '构建并运行 Blocklet',
130
129
  },
131
130
  didSpaces: {
132
- description: '去中心化存储空间',
131
+ description: '安全的个人存储',
133
132
  },
134
133
  abtNetwork: {
135
- description: 'ArcBlock 的区块链系统',
134
+ description: '快速区块链网络',
136
135
  },
137
136
  blockletServer: {
138
- description: '运行时容器服务',
137
+ description: '轻松托管应用程序',
139
138
  },
140
- osar: {
141
- description: '支持多链',
139
+ ocap: {
140
+ description: '多链连接器',
142
141
  },
143
142
  did: {
144
- description: '去中心化身份',
143
+ description: '自主身份',
145
144
  },
146
145
  didWallet: {
147
146
  description: '智能数字钱包',
148
147
  },
149
148
  didNameService: {
150
- description: '购买/托管域名',
149
+ description: 'Web3 域名',
151
150
  },
152
151
  vc: {
153
152
  description: '可验证凭证',
154
153
  },
155
154
  didConnect: {
156
- description: '使用 DID 登录',
155
+ description: '无密码登录',
157
156
  },
158
157
  },
159
158
  },
@@ -191,7 +190,6 @@ export interface ProductsProps extends BoxProps {
191
190
  export default function Products({ className, isOpen, ...rest }: ProductsProps) {
192
191
  const { mode } = useNavMenuContext();
193
192
  const wrapperRef = useRef<HTMLDivElement>(null);
194
- const { width, height } = useWindowSize();
195
193
  const { locale = 'en' } = useLocaleContext() || {};
196
194
  const t = useMemoizedFn((key, data = {}) => translate(translations, key, locale, 'en', data));
197
195
  const groups = useCreation(() => {
@@ -202,13 +200,22 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
202
200
  children: [
203
201
  [
204
202
  {
205
- label: <Link to={`https://www.nftstudio.rocks/${locale}`}>NFT Studio</Link>,
203
+ label: (
204
+ <Link to={`https://www.nftstudio.rocks/${locale}`} target="_blank" rel="noreferrer noopener">
205
+ NFT Studio
206
+ </Link>
207
+ ),
206
208
  description: t('products.nftStudio.description'),
207
209
  icon: <NftStudioSvg />,
208
210
  },
209
211
  {
210
212
  label: (
211
- <Link to={`https://www.arcblock.io/content/collections/${locale}/creator-studio`}>Creator Studio</Link>
213
+ <Link
214
+ to={`https://www.arcblock.io/content/collections/${locale}/creator-studio`}
215
+ target="_blank"
216
+ rel="noreferrer noopener">
217
+ Creator Studio
218
+ </Link>
212
219
  ),
213
220
  description: t('products.creatorStudio.description'),
214
221
  icon: <CreatorStudioSvg />,
@@ -216,12 +223,20 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
216
223
  ],
217
224
  [
218
225
  {
219
- label: <Link to={`https://www.aigne.io/${locale}`}>AIGNE</Link>,
226
+ label: (
227
+ <Link to={`https://www.aigne.io/${locale}`} target="_blank" rel="noreferrer noopener">
228
+ AIGNE
229
+ </Link>
230
+ ),
220
231
  description: t('products.aigne.description'),
221
232
  icon: <AigneSvg />,
222
233
  },
223
234
  {
224
- label: <Link to={`https://www.aistro.io/${locale}`}>Aistro</Link>,
235
+ label: (
236
+ <Link to={`https://www.aistro.io/${locale}`} target="_blank" rel="noreferrer noopener">
237
+ Aistro
238
+ </Link>
239
+ ),
225
240
  description: t('products.aistro.description'),
226
241
  icon: <AistroSvg />,
227
242
  },
@@ -234,24 +249,43 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
234
249
  children: [
235
250
  [
236
251
  {
237
- label: <Link to={`https://launcher.arcblock.io/${locale}`}>Blocklet Launcher</Link>,
252
+ label: (
253
+ <Link to={`https://launcher.arcblock.io/${locale}`} target="_blank" rel="noreferrer noopener">
254
+ Blocklet Launcher
255
+ </Link>
256
+ ),
238
257
  description: t('products.blockletLauncher.description'),
239
258
  icon: <BlockletLauncherSvg />,
240
259
  },
241
260
  {
242
- label: <Link to={`https://www.arcblock.io/content/collections/${locale}/ai-kit`}>Al Kit</Link>,
261
+ label: (
262
+ <Link
263
+ to={`https://www.arcblock.io/content/collections/${locale}/ai-kit`}
264
+ target="_blank"
265
+ rel="noreferrer noopener">
266
+ Al Kit
267
+ </Link>
268
+ ),
243
269
  description: t('products.alKit.description'),
244
270
  icon: <AIKitSvg />,
245
271
  },
246
272
  ],
247
273
  [
248
274
  {
249
- label: <Link to={`https://store.blocklet.dev/${locale}`}>Blocklet Store</Link>,
275
+ label: (
276
+ <Link to={`https://store.blocklet.dev/${locale}`} target="_blank" rel="noreferrer noopener">
277
+ Blocklet Store
278
+ </Link>
279
+ ),
250
280
  description: t('products.blockletStore.description'),
251
281
  icon: <BlockletStoreSvg />,
252
282
  },
253
283
  {
254
- label: <Link to={`https://www.web3kit.rocks/${locale}`}>Web3 Kit</Link>,
284
+ label: (
285
+ <Link to={`https://www.web3kit.rocks/${locale}`} target="_blank" rel="noreferrer noopener">
286
+ Web3 Kit
287
+ </Link>
288
+ ),
255
289
  description: t('products.web3Kit.description'),
256
290
  icon: <Web3KitSvg />,
257
291
  },
@@ -265,18 +299,31 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
265
299
  [
266
300
  {
267
301
  label: (
268
- <Link to={`https://www.arcblock.io/content/collections/${locale}/blocklet`}>Blocklet Framework</Link>
302
+ <Link
303
+ to={`https://www.arcblock.io/content/collections/${locale}/blocklet`}
304
+ target="_blank"
305
+ rel="noreferrer noopener">
306
+ Blocklet Framework
307
+ </Link>
269
308
  ),
270
309
  description: t('products.blockletFramework.description'),
271
310
  icon: <BlockletFrameworkSvg />,
272
311
  },
273
312
  {
274
- label: <Link to={`https://www.didspaces.com/${locale}`}>DID Spaces</Link>,
313
+ label: (
314
+ <Link to={`https://www.didspaces.com/${locale}`} target="_blank" rel="noreferrer noopener">
315
+ DID Spaces
316
+ </Link>
317
+ ),
275
318
  description: t('products.didSpaces.description'),
276
319
  icon: <DidSvg />,
277
320
  },
278
321
  {
279
- label: <Link to={`https://main.abtnetwork.io/${locale}`}>ABT Network</Link>,
322
+ label: (
323
+ <Link to={`https://main.abtnetwork.io/${locale}`} target="_blank" rel="noreferrer noopener">
324
+ ABT Network
325
+ </Link>
326
+ ),
280
327
  description: t('products.abtNetwork.description'),
281
328
  icon: <AbtNetworkSvg />,
282
329
  },
@@ -284,7 +331,10 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
284
331
  [
285
332
  {
286
333
  label: (
287
- <Link to={`https://www.arcblock.io/content/collections/${locale}/blocklet-server`}>
334
+ <Link
335
+ to={`https://www.arcblock.io/content/collections/${locale}/blocklet-server`}
336
+ target="_blank"
337
+ rel="noreferrer noopener">
288
338
  Blocklet Server
289
339
  </Link>
290
340
  ),
@@ -292,8 +342,15 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
292
342
  icon: <BlockletServerSvg />,
293
343
  },
294
344
  {
295
- label: <Link to={`https://www.arcblock.io/content/collections/${locale}/blockchain`}>ОСАР</Link>,
296
- description: t('products.osar.description'),
345
+ label: (
346
+ <Link
347
+ to={`https://www.arcblock.io/content/collections/${locale}/blockchain`}
348
+ target="_blank"
349
+ rel="noreferrer noopener">
350
+ ОСАР
351
+ </Link>
352
+ ),
353
+ description: t('products.ocap.description'),
297
354
  icon: <OCAPSvg />,
298
355
  },
299
356
  ],
@@ -305,29 +362,55 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
305
362
  children: [
306
363
  [
307
364
  {
308
- label: <Link to={`https://www.arcblock.io/content/collections/${locale}/did`}>DID</Link>,
365
+ label: (
366
+ <Link
367
+ to={`https://www.arcblock.io/content/collections/${locale}/did`}
368
+ target="_blank"
369
+ rel="noreferrer noopener">
370
+ DID
371
+ </Link>
372
+ ),
309
373
  description: t('products.did.description'),
310
374
  icon: <DidSvg />,
311
375
  },
312
376
  {
313
- label: <Link to={`https://www.didwallet.io/${locale}`}>DID Wallet</Link>,
377
+ label: (
378
+ <Link to={`https://www.didwallet.io/${locale}`} target="_blank" rel="noreferrer noopener">
379
+ DID Wallet
380
+ </Link>
381
+ ),
314
382
  description: t('products.didWallet.description'),
315
383
  icon: <DidWalletSvg />,
316
384
  },
317
385
  {
318
- label: <Link to={`https://www.didnames.io/${locale}`}>DID Name Service</Link>,
386
+ label: (
387
+ <Link to={`https://www.didnames.io/${locale}`} target="_blank" rel="noreferrer noopener">
388
+ DID Names
389
+ </Link>
390
+ ),
319
391
  description: t('products.didNameService.description'),
320
392
  icon: <DidNameServiceSvg />,
321
393
  },
322
394
  ],
323
395
  [
324
396
  {
325
- label: <Link to={`https://www.arcblock.io/content/collections/${locale}/verifiable-credential`}>VC</Link>,
397
+ label: (
398
+ <Link
399
+ to={`https://www.arcblock.io/content/collections/${locale}/verifiable-credential`}
400
+ target="_blank"
401
+ rel="noreferrer noopener">
402
+ VC
403
+ </Link>
404
+ ),
326
405
  description: t('products.vc.description'),
327
406
  icon: <VCSvg />,
328
407
  },
329
408
  {
330
- label: <Link to={`https://www.didconnect.io/${locale}`}>DID Connect</Link>,
409
+ label: (
410
+ <Link to={`https://www.didconnect.io/${locale}`} target="_blank" rel="noreferrer noopener">
411
+ DID Connect
412
+ </Link>
413
+ ),
331
414
  description: t('products.didConnect.description'),
332
415
  icon: <DidConnectSvg />,
333
416
  },
@@ -337,27 +420,6 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
337
420
  ];
338
421
  }, [t, locale]);
339
422
 
340
- // 防止弹框超出 window
341
- useLayoutEffect(() => {
342
- const wrapper = wrapperRef.current;
343
- if (!wrapper) return;
344
- if (!isOpen) {
345
- wrapper.style.transform = '';
346
- return;
347
- }
348
-
349
- const rect = wrapper.getBoundingClientRect();
350
- const windowWidth = window.innerWidth;
351
- if (rect.right > windowWidth) {
352
- const offset = rect.right - windowWidth;
353
- wrapper.style.transform = `translateX(-${offset + 16}px)`;
354
- } else if (rect.left < 0) {
355
- wrapper.style.transform = `translateX(${Math.abs(rect.left) + 16}px)`;
356
- } else {
357
- wrapper.style.transform = '';
358
- }
359
- }, [width, height, isOpen]);
360
-
361
423
  return (
362
424
  <Wrapper ref={wrapperRef} className={`is-${mode} ${className}`} {...rest}>
363
425
  {groups.map((group) => (