@blocklet/ui-react 2.6.9 → 2.7.0
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.
- package/babel.config.es.js +8 -0
- package/es/Dashboard/index.js +151 -0
- package/es/Footer/brand.js +90 -0
- package/es/Footer/copyright.js +27 -0
- package/es/Footer/index.js +98 -0
- package/es/Footer/internal-footer.js +144 -0
- package/es/Footer/layout/plain.js +57 -0
- package/es/Footer/layout/row.js +51 -0
- package/es/Footer/layout/standard.js +81 -0
- package/es/Footer/links.js +238 -0
- package/es/Footer/social-media.js +63 -0
- package/es/Header/index.js +194 -0
- package/es/Icon/index.js +90 -0
- package/es/blocklets.js +167 -0
- package/es/common/header-addons.js +93 -0
- package/es/common/link-blocker.js +25 -0
- package/es/common/overridable-theme-provider.js +25 -0
- package/es/common/wallet-hidden-topbar.js +14 -0
- package/es/types.js +37 -0
- package/es/utils.js +83 -0
- package/lib/Dashboard/index.js +2 -3
- package/lib/Icon/index.js +6 -8
- package/lib/common/header-addons.js +3 -4
- package/package.json +37 -6
- package/src/Dashboard/index.js +1 -1
- package/src/Icon/index.js +3 -3
- package/src/common/header-addons.js +1 -3
package/es/blocklets.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mapRecursive, filterRecursive, isUrl } from './utils';
|
|
2
|
+
export const publicPath = window?.blocklet?.groupPrefix || window?.blocklet?.prefix || '/';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 格式化 theme (目前仅考虑 background)
|
|
6
|
+
*/
|
|
7
|
+
export const formatTheme = theme => {
|
|
8
|
+
const formatted = {
|
|
9
|
+
...theme
|
|
10
|
+
};
|
|
11
|
+
const background = theme?.background;
|
|
12
|
+
if (typeof background === 'string') {
|
|
13
|
+
formatted.background = {
|
|
14
|
+
header: background,
|
|
15
|
+
footer: background,
|
|
16
|
+
default: background
|
|
17
|
+
};
|
|
18
|
+
} else if (background && typeof background === 'object') {
|
|
19
|
+
formatted.background = {
|
|
20
|
+
header: background.header || background.default,
|
|
21
|
+
footer: background.footer || background.default,
|
|
22
|
+
default: background.default
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return formatted;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 获取指定 locale 对应的 navigation 数据, 仅考虑 zh/en
|
|
30
|
+
*/
|
|
31
|
+
export const getLocalizedNavigation = (navigation, locale = 'en') => {
|
|
32
|
+
if (!navigation?.length) {
|
|
33
|
+
return navigation;
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line no-shadow
|
|
36
|
+
const getTitle = (title, locale) => {
|
|
37
|
+
if (typeof title === 'string') {
|
|
38
|
+
return title;
|
|
39
|
+
}
|
|
40
|
+
if (typeof title === 'object') {
|
|
41
|
+
return title[locale] || title?.en || title?.zh;
|
|
42
|
+
}
|
|
43
|
+
return title;
|
|
44
|
+
};
|
|
45
|
+
// eslint-disable-next-line no-shadow
|
|
46
|
+
const getLink = (link, locale) => {
|
|
47
|
+
if (typeof link === 'string') {
|
|
48
|
+
// http[s] 开头的 url
|
|
49
|
+
if (isUrl(link)) {
|
|
50
|
+
const url = new URL(link);
|
|
51
|
+
url.searchParams.set('locale', locale);
|
|
52
|
+
return url.href;
|
|
53
|
+
}
|
|
54
|
+
const url = new URL(link, window.location.origin);
|
|
55
|
+
url.searchParams.set('locale', locale);
|
|
56
|
+
return url.pathname + url.search;
|
|
57
|
+
}
|
|
58
|
+
if (typeof link === 'object') {
|
|
59
|
+
return link[locale] || link?.en || link?.zh;
|
|
60
|
+
}
|
|
61
|
+
return link;
|
|
62
|
+
};
|
|
63
|
+
return mapRecursive(navigation, item => {
|
|
64
|
+
return {
|
|
65
|
+
...item,
|
|
66
|
+
title: getTitle(item.title, locale),
|
|
67
|
+
// 仅对叶结点进行处理
|
|
68
|
+
link: !item.items?.length ? getLink(item.link, locale) : item.link
|
|
69
|
+
};
|
|
70
|
+
}, 'items');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 格式化 navigation
|
|
75
|
+
*
|
|
76
|
+
* - role 统一为数组形式
|
|
77
|
+
*/
|
|
78
|
+
export const formatNavigation = navigation => {
|
|
79
|
+
return mapRecursive(navigation, item => {
|
|
80
|
+
if (item.role) {
|
|
81
|
+
return {
|
|
82
|
+
...item,
|
|
83
|
+
role: Array.isArray(item.role) ? item.role : [item.role]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return item;
|
|
87
|
+
}, 'items');
|
|
88
|
+
};
|
|
89
|
+
export const parseNavigation = navigation => {
|
|
90
|
+
if (!navigation?.length) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const formattedNav = formatNavigation(navigation);
|
|
94
|
+
const sections = {
|
|
95
|
+
header: [],
|
|
96
|
+
footer: [],
|
|
97
|
+
// 对应 footer social media
|
|
98
|
+
social: [],
|
|
99
|
+
// 对应 footer 底部 links
|
|
100
|
+
bottom: [],
|
|
101
|
+
// 对应 dashboard#sidenav 导航
|
|
102
|
+
dashboard: [],
|
|
103
|
+
// session manager menus
|
|
104
|
+
sessionManager: []
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// 对 navigation 顶层元素按 section 分组
|
|
108
|
+
formattedNav.forEach(item => {
|
|
109
|
+
// item#section 为空时, 表示只存在于 header
|
|
110
|
+
if (!item.section) {
|
|
111
|
+
sections.header.push(item);
|
|
112
|
+
// item 出现在指定几个 section 中 (array)
|
|
113
|
+
} else if (Array.isArray(item.section)) {
|
|
114
|
+
item.section.forEach(sectionKey => {
|
|
115
|
+
sections[sectionKey]?.push(item);
|
|
116
|
+
});
|
|
117
|
+
// item 出现在指定的一个 section 中 (string)
|
|
118
|
+
} else if (typeof item.section === 'string') {
|
|
119
|
+
sections[item.section]?.push(item);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return sections;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 格式化 blocklet info 数据
|
|
127
|
+
*/
|
|
128
|
+
export const formatBlockletInfo = blockletInfo => {
|
|
129
|
+
if (!blockletInfo) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const formatted = {
|
|
133
|
+
...blockletInfo
|
|
134
|
+
};
|
|
135
|
+
// theme
|
|
136
|
+
formatted.theme = formatTheme(formatted.theme);
|
|
137
|
+
// navigation
|
|
138
|
+
formatted.navigation = parseNavigation(filterValidNavItems(formatted.navigation));
|
|
139
|
+
return formatted;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 过滤掉无效结点 (无 link 且子元素为空)
|
|
144
|
+
*/
|
|
145
|
+
export const filterValidNavItems = (navigation = []) => {
|
|
146
|
+
return filterRecursive(navigation, (item, context) => !!item.link || context.filteredChildren?.length, 'items');
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 根据 role 筛选 nav, 规则:
|
|
151
|
+
* - 如果是枝结点, 必须至少存在一个符合条件的子结点
|
|
152
|
+
* - role 未定义, 符合条件
|
|
153
|
+
* - role 定义且包括当前的 userRole, 符合条件
|
|
154
|
+
*
|
|
155
|
+
* @param {object[]} nav 导航菜单数据
|
|
156
|
+
* @param {string} userRole 当前用户 role
|
|
157
|
+
* @returns 符合 role 权限的导航菜单数据
|
|
158
|
+
*/
|
|
159
|
+
export const filterNavByRole = (nav, userRole) => {
|
|
160
|
+
return filterRecursive(nav, (item, context) => {
|
|
161
|
+
const isRoleMatched = !item.role || userRole && item.role.includes(userRole);
|
|
162
|
+
if (!context.isLeaf) {
|
|
163
|
+
return isRoleMatched && context.filteredChildren?.length;
|
|
164
|
+
}
|
|
165
|
+
return isRoleMatched;
|
|
166
|
+
}, 'items');
|
|
167
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useContext, createElement } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
// FIXME: 直接从 react 中 import Fragment 可能会在 vite 下出错,先暂时从 react/jsx-runtime 导入 Fragment 来跳过这个问题
|
|
4
|
+
import { Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { SessionContext } from '@arcblock/did-connect/lib/Session';
|
|
6
|
+
import SessionManager from '@arcblock/did-connect/lib/SessionManager';
|
|
7
|
+
import LocaleSelector from '@arcblock/ux/lib/Locale/selector';
|
|
8
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
9
|
+
import { sessionManagerProps } from '../types';
|
|
10
|
+
import { getLocalizedNavigation, filterNavByRole } from '../blocklets';
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line no-shadow
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
export default function HeaderAddons({
|
|
15
|
+
formattedBlocklet,
|
|
16
|
+
addons,
|
|
17
|
+
sessionManagerProps
|
|
18
|
+
}) {
|
|
19
|
+
const sessionCtx = useContext(SessionContext);
|
|
20
|
+
const {
|
|
21
|
+
locale
|
|
22
|
+
} = useLocaleContext() || {};
|
|
23
|
+
const {
|
|
24
|
+
enableConnect = true,
|
|
25
|
+
enableLocale = true
|
|
26
|
+
} = formattedBlocklet;
|
|
27
|
+
const authenticated = !!sessionCtx?.session?.user;
|
|
28
|
+
let localizedNav = getLocalizedNavigation(formattedBlocklet?.navigation?.sessionManager, locale) || [];
|
|
29
|
+
// 根据 role 筛选 nav 数据
|
|
30
|
+
localizedNav = filterNavByRole(localizedNav, sessionCtx?.session?.user?.role);
|
|
31
|
+
const renderAddons = () => {
|
|
32
|
+
// 不关心内置的 session manager 和 locale selector, 直接覆盖 UX Header 的 addons
|
|
33
|
+
if (addons && typeof addons !== 'function') {
|
|
34
|
+
return Array.isArray(addons) ? addons : [addons];
|
|
35
|
+
}
|
|
36
|
+
let addonsArray = [];
|
|
37
|
+
// 启用了多语言并且检测到了 locale context
|
|
38
|
+
if (enableLocale && locale) {
|
|
39
|
+
addonsArray.push( /*#__PURE__*/_jsx(LocaleSelector, {
|
|
40
|
+
showText: false
|
|
41
|
+
}, "locale-selector"));
|
|
42
|
+
}
|
|
43
|
+
// 启用了连接钱包并且检测到了 session context
|
|
44
|
+
if (enableConnect && sessionCtx) {
|
|
45
|
+
const menu = [];
|
|
46
|
+
if (authenticated && localizedNav?.[0]) {
|
|
47
|
+
const firstItem = localizedNav[0];
|
|
48
|
+
menu.push({
|
|
49
|
+
label: firstItem.title,
|
|
50
|
+
icon: firstItem.icon ? /*#__PURE__*/_jsx("iconify-icon", {
|
|
51
|
+
icon: firstItem.icon,
|
|
52
|
+
height: 24,
|
|
53
|
+
style: {
|
|
54
|
+
marginRight: 16
|
|
55
|
+
}
|
|
56
|
+
}) : null,
|
|
57
|
+
component: 'a',
|
|
58
|
+
href: firstItem.link,
|
|
59
|
+
key: firstItem.link
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
addonsArray.push( /*#__PURE__*/_jsx(SessionManager, {
|
|
63
|
+
session: sessionCtx.session,
|
|
64
|
+
locale: locale,
|
|
65
|
+
menu: menu,
|
|
66
|
+
showRole: true,
|
|
67
|
+
...sessionManagerProps
|
|
68
|
+
}, "session-manager"));
|
|
69
|
+
}
|
|
70
|
+
// 在内置 addons 基础上定制 addons
|
|
71
|
+
if (typeof addons === 'function') {
|
|
72
|
+
addonsArray = addons(addonsArray) || [];
|
|
73
|
+
}
|
|
74
|
+
return addonsArray;
|
|
75
|
+
};
|
|
76
|
+
const renderedAddons = renderAddons();
|
|
77
|
+
const addonList = /*#__PURE__*/createElement(Fragment, null, ...(Array.isArray(renderedAddons) ? renderedAddons : [renderedAddons]));
|
|
78
|
+
return addonList;
|
|
79
|
+
}
|
|
80
|
+
HeaderAddons.propTypes = {
|
|
81
|
+
formattedBlocklet: PropTypes.object.isRequired,
|
|
82
|
+
// 需要考虑 定制的 addons 与内置的 连接钱包/选择语言 addons 共存的情况
|
|
83
|
+
// - PropTypes.func: 可以把自定义 addons 插在 session-manager 或 locale-selector (如果存在的话) 前/中/后
|
|
84
|
+
// - PropTypes.node: 将 addons 原样传给 UX Header 组件
|
|
85
|
+
addons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
86
|
+
sessionManagerProps
|
|
87
|
+
};
|
|
88
|
+
HeaderAddons.defaultProps = {
|
|
89
|
+
addons: null,
|
|
90
|
+
sessionManagerProps: {
|
|
91
|
+
showRole: true
|
|
92
|
+
}
|
|
93
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
function hasParentOfType(node, type) {
|
|
3
|
+
if (!node) return false;
|
|
4
|
+
if (type === node.nodeName) return true;
|
|
5
|
+
return hasParentOfType(node.parentNode, type);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 适用于 header/footer/dashboard "preview mode", 阻止内部组件中所有 link 的默认点击行为
|
|
10
|
+
*/
|
|
11
|
+
function LinkBlocker({
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
const handleOnClick = e => {
|
|
15
|
+
const isInsideLink = hasParentOfType(e.target, 'A');
|
|
16
|
+
if (isInsideLink) {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return /*#__PURE__*/_jsx("div", {
|
|
21
|
+
onClick: handleOnClick,
|
|
22
|
+
...rest
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export default LinkBlocker;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { createTheme, ThemeProvider } from '@arcblock/ux/lib/Theme';
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
const defaultTheme = createTheme();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 允许 theme 被覆盖的 ThemeProvider 组件, 默认使用 ux default theme, 可以传入 theme 进行覆盖
|
|
8
|
+
*/
|
|
9
|
+
export default function OverridableThemeProvider({
|
|
10
|
+
theme: themeOverrides,
|
|
11
|
+
children
|
|
12
|
+
}) {
|
|
13
|
+
const theme = themeOverrides ? createTheme(themeOverrides) : defaultTheme;
|
|
14
|
+
return /*#__PURE__*/_jsx(ThemeProvider, {
|
|
15
|
+
theme: theme,
|
|
16
|
+
children: children
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
OverridableThemeProvider.propTypes = {
|
|
20
|
+
children: PropTypes.any.isRequired,
|
|
21
|
+
theme: PropTypes.object
|
|
22
|
+
};
|
|
23
|
+
OverridableThemeProvider.defaultProps = {
|
|
24
|
+
theme: null
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
|
|
3
|
+
import dsbridge from 'dsbridge';
|
|
4
|
+
|
|
5
|
+
// 在 wallet webview 环境中, 隐藏 wallet topbar
|
|
6
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
7
|
+
export const useWalletHiddenTopbar = () => {
|
|
8
|
+
const browser = useBrowser();
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (browser.wallet) {
|
|
11
|
+
dsbridge.call('arcHideTopBar', '{}');
|
|
12
|
+
}
|
|
13
|
+
}, [browser]);
|
|
14
|
+
};
|
package/es/types.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
export const blockletMetaProps = PropTypes.shape({
|
|
4
|
+
appLogo: PropTypes.node,
|
|
5
|
+
appName: PropTypes.string,
|
|
6
|
+
theme: PropTypes.shape({
|
|
7
|
+
background: PropTypes.string
|
|
8
|
+
}),
|
|
9
|
+
enableConnect: PropTypes.bool,
|
|
10
|
+
enableLocale: PropTypes.bool,
|
|
11
|
+
navigation: PropTypes.arrayOf(PropTypes.shape({
|
|
12
|
+
title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
|
13
|
+
link: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
|
14
|
+
icon: PropTypes.string,
|
|
15
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
16
|
+
title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
|
17
|
+
link: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
|
|
18
|
+
}))
|
|
19
|
+
}))
|
|
20
|
+
});
|
|
21
|
+
export const sessionManagerProps = PropTypes.shape({
|
|
22
|
+
showText: PropTypes.bool,
|
|
23
|
+
showRole: PropTypes.bool,
|
|
24
|
+
switchDid: PropTypes.bool,
|
|
25
|
+
switchProfile: PropTypes.bool,
|
|
26
|
+
switchPassport: PropTypes.bool,
|
|
27
|
+
disableLogout: PropTypes.bool,
|
|
28
|
+
onLogin: PropTypes.func,
|
|
29
|
+
onLogout: PropTypes.func,
|
|
30
|
+
onSwitchDid: PropTypes.func,
|
|
31
|
+
onSwitchProfile: PropTypes.func,
|
|
32
|
+
onSwitchPassport: PropTypes.func,
|
|
33
|
+
menu: PropTypes.array,
|
|
34
|
+
menuRender: PropTypes.func,
|
|
35
|
+
dark: PropTypes.bool,
|
|
36
|
+
size: PropTypes.number
|
|
37
|
+
});
|
package/es/utils.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const mapRecursive = (array, fn, childrenKey = 'children') => {
|
|
2
|
+
return array.map(item => {
|
|
3
|
+
if (Array.isArray(item[childrenKey])) {
|
|
4
|
+
return fn({
|
|
5
|
+
...item,
|
|
6
|
+
[childrenKey]: mapRecursive(item[childrenKey], fn, childrenKey)
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
return fn(item);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// 展平有层级结构的 array
|
|
14
|
+
export const flatRecursive = (array, childrenKey = 'children') => {
|
|
15
|
+
const result = [];
|
|
16
|
+
mapRecursive(array, item => result.push(item), childrenKey);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 对有层级结构的 array 元素计数
|
|
21
|
+
export const countRecursive = (array, childrenKey = 'children') => {
|
|
22
|
+
let counter = 0;
|
|
23
|
+
mapRecursive(array, () => counter++, childrenKey);
|
|
24
|
+
return counter;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// 对有层级结构的 array 进行 filter 处理
|
|
28
|
+
// 因为是 DFS 遍历, 可以借助 context.filteredChildren 在过滤/保留子结的同时保持父子结构 (即使父结点不满足筛选条件)
|
|
29
|
+
export const filterRecursive = (array, predicate, childrenKey = 'children') => {
|
|
30
|
+
return array.map(item => ({
|
|
31
|
+
...item
|
|
32
|
+
})).filter(item => {
|
|
33
|
+
const children = item[childrenKey];
|
|
34
|
+
if (Array.isArray(children)) {
|
|
35
|
+
const filtered = filterRecursive(children, predicate, childrenKey);
|
|
36
|
+
item[childrenKey] = filtered?.length ? filtered : undefined;
|
|
37
|
+
}
|
|
38
|
+
const context = {
|
|
39
|
+
filteredChildren: item[childrenKey],
|
|
40
|
+
isLeaf: !children?.length
|
|
41
|
+
};
|
|
42
|
+
return predicate(item, context);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// "http://", "https://" 2 种情况
|
|
47
|
+
export const isUrl = str => {
|
|
48
|
+
return /^https?:\/\//.test(str);
|
|
49
|
+
};
|
|
50
|
+
export const isIconifyString = str => {
|
|
51
|
+
return /^[\w-]+:[\w-]+$/.test(str);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 检测 path 是否匹配当前 location, path 只考虑 "/" 开头的相对路径
|
|
56
|
+
*/
|
|
57
|
+
export const matchPath = path => {
|
|
58
|
+
if (!path || !path?.startsWith('/')) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const ensureTrailingSlash = str => str.endsWith('/') ? str : `${str}/`;
|
|
62
|
+
const pathname = ensureTrailingSlash(window.location.pathname);
|
|
63
|
+
const normalizedPath = ensureTrailingSlash(new URL(path, window.location.origin).pathname);
|
|
64
|
+
return pathname.startsWith(normalizedPath);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 从一组 paths 中, 找到匹配当前 location 的 path, 返回序号
|
|
69
|
+
*/
|
|
70
|
+
export const matchPaths = (paths = []) => {
|
|
71
|
+
const matched = paths.map((item, index) => ({
|
|
72
|
+
path: item,
|
|
73
|
+
index
|
|
74
|
+
})).filter(item => matchPath(item.path));
|
|
75
|
+
if (!matched?.length) {
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
// 多个 path 都匹配时, 取一个最具体 (最长的) path
|
|
79
|
+
const mostSpecific = matched.slice(1).reduce((prev, cur) => {
|
|
80
|
+
return prev.path.length >= cur.path.length ? prev : cur;
|
|
81
|
+
}, matched[0]);
|
|
82
|
+
return mostSpecific.index;
|
|
83
|
+
};
|
package/lib/Dashboard/index.js
CHANGED
|
@@ -69,9 +69,8 @@ function Dashboard(_ref) {
|
|
|
69
69
|
localizedNav = (0, _utils.mapRecursive)(localizedNav, item => ({
|
|
70
70
|
title: item.title,
|
|
71
71
|
url: item.link,
|
|
72
|
-
icon: item.icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)("
|
|
73
|
-
|
|
74
|
-
"data-icon": item.icon
|
|
72
|
+
icon: item.icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)("iconify-icon", {
|
|
73
|
+
icon: item.icon
|
|
75
74
|
}) : null,
|
|
76
75
|
// https://github.com/ArcBlock/ux/issues/755#issuecomment-1208692620
|
|
77
76
|
external: true,
|
package/lib/Icon/index.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = Icon;
|
|
7
7
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
8
8
|
var _Avatar = _interopRequireDefault(require("@mui/material/Avatar"));
|
|
9
|
-
require("
|
|
9
|
+
require("iconify-icon");
|
|
10
10
|
var _utils = require("../utils");
|
|
11
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
12
|
const _excluded = ["icon", "size", "sx"];
|
|
@@ -61,17 +61,15 @@ function Icon(_ref) {
|
|
|
61
61
|
}
|
|
62
62
|
if ((0, _utils.isIconifyString)(icon)) {
|
|
63
63
|
// y = 0.6 * x + 4
|
|
64
|
-
const
|
|
64
|
+
const height = size ? 0.6 * size + 4 : 0;
|
|
65
65
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, _objectSpread(_objectSpread({
|
|
66
66
|
as: "span"
|
|
67
67
|
}, rest), {}, {
|
|
68
68
|
sx: _sx,
|
|
69
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
'data-height': iconHeight
|
|
74
|
-
}))
|
|
69
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("iconify-icon", {
|
|
70
|
+
icon: icon,
|
|
71
|
+
height: height || undefined
|
|
72
|
+
})
|
|
75
73
|
}));
|
|
76
74
|
}
|
|
77
75
|
// letter avatar
|
|
@@ -59,10 +59,9 @@ function HeaderAddons(_ref) {
|
|
|
59
59
|
const firstItem = localizedNav[0];
|
|
60
60
|
menu.push({
|
|
61
61
|
label: firstItem.title,
|
|
62
|
-
icon: firstItem.icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)("
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"data-height": "24",
|
|
62
|
+
icon: firstItem.icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)("iconify-icon", {
|
|
63
|
+
icon: firstItem.icon,
|
|
64
|
+
height: 24,
|
|
66
65
|
style: {
|
|
67
66
|
marginRight: 16
|
|
68
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/ui-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Some useful front-end web components that can be used in Blocklets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"homepage": "https://github.com/ArcBlock/ux#readme",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"main": "lib/index.js",
|
|
15
|
+
"module": "es/index.js",
|
|
15
16
|
"repository": {
|
|
16
17
|
"type": "git",
|
|
17
18
|
"url": "git+https://github.com/ArcBlock/ux.git"
|
|
@@ -19,7 +20,10 @@
|
|
|
19
20
|
"scripts": {
|
|
20
21
|
"lint": "eslint src tests --ext js --ext jsx",
|
|
21
22
|
"lint:fix": "npm run lint -- --fix",
|
|
22
|
-
"build": "
|
|
23
|
+
"build": "npm run build:lib && npm run build:es && npm run autoexports",
|
|
24
|
+
"build:lib": "babel src --out-dir lib --copy-files --no-copy-ignored",
|
|
25
|
+
"build:es": "babel --config-file ./babel.config.es.js src --out-dir es --copy-files --no-copy-ignored",
|
|
26
|
+
"autoexports": "node tools/auto-exports.js",
|
|
23
27
|
"watch": "babel src --out-dir lib -w --copy-files --no-copy-ignored",
|
|
24
28
|
"precommit": "CI=1 npm run lint",
|
|
25
29
|
"prepush": "CI=1 npm run lint",
|
|
@@ -30,14 +34,40 @@
|
|
|
30
34
|
"bugs": {
|
|
31
35
|
"url": "https://github.com/ArcBlock/ux/issues"
|
|
32
36
|
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": "./es/index.js",
|
|
40
|
+
"require": "./lib/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./lib/": {
|
|
43
|
+
"import": "./es/",
|
|
44
|
+
"require": "./lib/"
|
|
45
|
+
},
|
|
46
|
+
"./lib/Dashboard": {
|
|
47
|
+
"import": "./es/Dashboard/index.js",
|
|
48
|
+
"require": "./lib/Dashboard/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./lib/Footer": {
|
|
51
|
+
"import": "./es/Footer/index.js",
|
|
52
|
+
"require": "./lib/Footer/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./lib/Header": {
|
|
55
|
+
"import": "./es/Header/index.js",
|
|
56
|
+
"require": "./lib/Header/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./lib/Icon": {
|
|
59
|
+
"import": "./es/Icon/index.js",
|
|
60
|
+
"require": "./lib/Icon/index.js"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
33
63
|
"dependencies": {
|
|
34
|
-
"@arcblock/did-connect": "^2.
|
|
35
|
-
"@arcblock/ux": "^2.
|
|
64
|
+
"@arcblock/did-connect": "^2.7.0",
|
|
65
|
+
"@arcblock/ux": "^2.7.0",
|
|
36
66
|
"@emotion/react": "^11.10.4",
|
|
37
67
|
"@emotion/styled": "^11.10.4",
|
|
38
|
-
"@iconify/iconify": "^2.2.1",
|
|
39
68
|
"@mui/material": "^5.10.8",
|
|
40
69
|
"core-js": "^3.25.5",
|
|
70
|
+
"iconify-icon": "^1.0.8",
|
|
41
71
|
"react-error-boundary": "^3.1.4"
|
|
42
72
|
},
|
|
43
73
|
"peerDependencies": {
|
|
@@ -52,7 +82,8 @@
|
|
|
52
82
|
"@babel/preset-env": "^7.19.3",
|
|
53
83
|
"@babel/preset-react": "^7.18.6",
|
|
54
84
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
85
|
+
"glob": "^10.3.3",
|
|
55
86
|
"jest": "^28.1.3"
|
|
56
87
|
},
|
|
57
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "6ad0ce9f2f929e737be1a271c09d3a1bce1315e1"
|
|
58
89
|
}
|
package/src/Dashboard/index.js
CHANGED
|
@@ -39,7 +39,7 @@ function Dashboard({ meta, fallbackUrl, invalidPathFallback, headerAddons, sessi
|
|
|
39
39
|
(item) => ({
|
|
40
40
|
title: item.title,
|
|
41
41
|
url: item.link,
|
|
42
|
-
icon: item.icon ? <
|
|
42
|
+
icon: item.icon ? <iconify-icon icon={item.icon} /> : null,
|
|
43
43
|
// https://github.com/ArcBlock/ux/issues/755#issuecomment-1208692620
|
|
44
44
|
external: true,
|
|
45
45
|
children: item.items,
|
package/src/Icon/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import Avatar from '@mui/material/Avatar';
|
|
3
|
-
import '
|
|
3
|
+
import 'iconify-icon';
|
|
4
4
|
import { isUrl, isIconifyString } from '../utils';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -32,10 +32,10 @@ export default function Icon({ icon, size, sx, ...rest }) {
|
|
|
32
32
|
}
|
|
33
33
|
if (isIconifyString(icon)) {
|
|
34
34
|
// y = 0.6 * x + 4
|
|
35
|
-
const
|
|
35
|
+
const height = size ? 0.6 * size + 4 : 0;
|
|
36
36
|
return (
|
|
37
37
|
<Avatar as="span" {...rest} sx={_sx}>
|
|
38
|
-
<
|
|
38
|
+
<iconify-icon icon={icon} height={height || undefined} />
|
|
39
39
|
</Avatar>
|
|
40
40
|
);
|
|
41
41
|
}
|
|
@@ -36,9 +36,7 @@ export default function HeaderAddons({ formattedBlocklet, addons, sessionManager
|
|
|
36
36
|
const firstItem = localizedNav[0];
|
|
37
37
|
menu.push({
|
|
38
38
|
label: firstItem.title,
|
|
39
|
-
icon: firstItem.icon ?
|
|
40
|
-
<span className="iconify" data-icon={firstItem.icon} data-height="24" style={{ marginRight: 16 }} />
|
|
41
|
-
) : null,
|
|
39
|
+
icon: firstItem.icon ? <iconify-icon icon={firstItem.icon} height={24} style={{ marginRight: 16 }} /> : null,
|
|
42
40
|
component: 'a',
|
|
43
41
|
href: firstItem.link,
|
|
44
42
|
key: firstItem.link,
|