@blocklet/ui-react 3.2.17 → 3.2.19
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/lib/Dashboard/app-shell/app-badge.d.ts +24 -0
- package/lib/Dashboard/app-shell/app-badge.js +48 -0
- package/lib/Dashboard/app-shell/app-header.d.ts +5 -0
- package/lib/Dashboard/app-shell/app-header.js +72 -0
- package/lib/Dashboard/app-shell/app-info-context.d.ts +42 -0
- package/lib/Dashboard/app-shell/app-info-context.js +83 -0
- package/lib/Dashboard/app-shell/badges/app-badge-default.d.ts +20 -0
- package/lib/Dashboard/app-shell/badges/app-badge-default.js +84 -0
- package/lib/Dashboard/app-shell/badges/app-badge-did.d.ts +5 -0
- package/lib/Dashboard/app-shell/badges/app-badge-did.js +16 -0
- package/lib/Dashboard/app-shell/badges/app-badge-state.d.ts +6 -0
- package/lib/Dashboard/app-shell/badges/app-badge-state.js +34 -0
- package/lib/Dashboard/app-shell/badges/app-badge-switch.d.ts +8 -0
- package/lib/Dashboard/app-shell/badges/app-badge-switch.js +66 -0
- package/lib/Dashboard/app-shell/badges/app-badge-version.d.ts +14 -0
- package/lib/Dashboard/app-shell/badges/app-badge-version.js +50 -0
- package/lib/Dashboard/app-shell/index.d.ts +4 -0
- package/lib/Dashboard/app-shell/index.js +9 -0
- package/lib/Dashboard/index.d.ts +12 -2
- package/lib/Dashboard/index.js +83 -63
- package/lib/Footer/internal-footer.js +11 -11
- package/lib/Footer/links.d.ts +5 -3
- package/lib/Footer/links.js +63 -61
- package/lib/utils.js +28 -28
- package/package.json +12 -6
- package/src/Dashboard/app-shell/app-badge.stories.tsx +64 -0
- package/src/Dashboard/app-shell/app-badge.tsx +94 -0
- package/src/Dashboard/app-shell/app-header.tsx +104 -0
- package/src/Dashboard/app-shell/app-info-context.tsx +182 -0
- package/src/Dashboard/app-shell/badges/app-badge-default.tsx +131 -0
- package/src/Dashboard/app-shell/badges/app-badge-did.tsx +28 -0
- package/src/Dashboard/app-shell/badges/app-badge-state.tsx +40 -0
- package/src/Dashboard/app-shell/badges/app-badge-switch.tsx +72 -0
- package/src/Dashboard/app-shell/badges/app-badge-version.tsx +60 -0
- package/src/Dashboard/app-shell/index.ts +5 -0
- package/src/Dashboard/index.jsx +17 -3
- package/src/Footer/internal-footer.jsx +1 -1
- package/src/Footer/links.jsx +11 -7
- package/src/utils.js +6 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Box, Typography, useTheme } from '@mui/material';
|
|
2
|
+
import { useMemoizedFn } from 'ahooks';
|
|
3
|
+
import { AppBadgeDefaultProps, BadgeContainer } from './app-badge-default';
|
|
4
|
+
|
|
5
|
+
export interface AppBadgeSwitchProps extends Omit<AppBadgeDefaultProps, 'value' | 'onChange'> {
|
|
6
|
+
value?: unknown;
|
|
7
|
+
trueValue?: unknown;
|
|
8
|
+
falseValue?: unknown;
|
|
9
|
+
onChange?: (checked: boolean) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function AppBadgeSwitch({
|
|
13
|
+
label = '',
|
|
14
|
+
value = undefined,
|
|
15
|
+
trueValue = true,
|
|
16
|
+
falseValue = false,
|
|
17
|
+
loading = false,
|
|
18
|
+
onChange = undefined,
|
|
19
|
+
...rest
|
|
20
|
+
}: AppBadgeSwitchProps) {
|
|
21
|
+
const theme = useTheme();
|
|
22
|
+
let checked = false;
|
|
23
|
+
const handleClick = useMemoizedFn(() => onChange?.(!checked));
|
|
24
|
+
|
|
25
|
+
if (value === trueValue) {
|
|
26
|
+
checked = true;
|
|
27
|
+
} else if (value === falseValue) {
|
|
28
|
+
checked = false;
|
|
29
|
+
} else {
|
|
30
|
+
checked = Boolean(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<BadgeContainer
|
|
35
|
+
loading={loading}
|
|
36
|
+
sx={{
|
|
37
|
+
cursor: 'pointer',
|
|
38
|
+
backgroundColor: checked ? 'primary.main' : 'grey.100',
|
|
39
|
+
borderColor: checked ? 'primary.main' : 'divider',
|
|
40
|
+
transition: 'background-color 0.3s ease',
|
|
41
|
+
}}
|
|
42
|
+
onClick={handleClick}
|
|
43
|
+
{...rest}>
|
|
44
|
+
{/* 内容 */}
|
|
45
|
+
<Typography
|
|
46
|
+
sx={{
|
|
47
|
+
fontWeight: 400,
|
|
48
|
+
paddingLeft: checked ? 0 : theme.spacing(2.5),
|
|
49
|
+
paddingRight: checked ? theme.spacing(2.5) : 0,
|
|
50
|
+
color: checked ? 'common.white' : 'text.secondary',
|
|
51
|
+
transition: 'color 0.3s ease',
|
|
52
|
+
}}>
|
|
53
|
+
{label}
|
|
54
|
+
</Typography>
|
|
55
|
+
{/* 滑块 */}
|
|
56
|
+
<Box
|
|
57
|
+
sx={{
|
|
58
|
+
width: theme.spacing(2.5),
|
|
59
|
+
height: theme.spacing(2.5),
|
|
60
|
+
borderRadius: `${(theme.shape.borderRadius as number) * 0.5}px`,
|
|
61
|
+
backgroundColor: 'common.white',
|
|
62
|
+
position: 'absolute',
|
|
63
|
+
top: '50%',
|
|
64
|
+
left: checked ? `calc(100% - ${theme.spacing(3)})` : theme.spacing(0.5),
|
|
65
|
+
transform: 'translateY(-50%)',
|
|
66
|
+
transition: 'left 0.3s ease',
|
|
67
|
+
boxShadow: 1,
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</BadgeContainer>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { LiteralUnion } from 'type-fest';
|
|
2
|
+
import { alpha, Theme, Typography, useTheme } from '@mui/material';
|
|
3
|
+
import { Icon } from '@iconify/react';
|
|
4
|
+
import { AppBadgeDefaultProps, BadgeContainer } from './app-badge-default';
|
|
5
|
+
|
|
6
|
+
export type ColorKey = 'primary' | 'info' | 'success' | 'error' | 'warning';
|
|
7
|
+
export type BadgeColor = LiteralUnion<ColorKey, string>;
|
|
8
|
+
export const colorMap: Record<ColorKey, (theme: Theme) => { main: string }> = {
|
|
9
|
+
primary: (theme) => theme.palette.primary,
|
|
10
|
+
info: (theme) => theme.palette.info,
|
|
11
|
+
success: (theme) => theme.palette.success,
|
|
12
|
+
error: (theme) => theme.palette.error,
|
|
13
|
+
warning: (theme) => theme.palette.warning,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getBgColor = (theme: Theme, color: BadgeColor): string => {
|
|
17
|
+
if (colorMap[color as ColorKey]) {
|
|
18
|
+
const c = colorMap[color as ColorKey](theme).main;
|
|
19
|
+
return alpha(c, 0.1);
|
|
20
|
+
}
|
|
21
|
+
return alpha(color, 0.1);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getTextColor = (theme: Theme, color: BadgeColor): string => {
|
|
25
|
+
if (colorMap[color as ColorKey]) {
|
|
26
|
+
return colorMap[color as ColorKey](theme).main;
|
|
27
|
+
}
|
|
28
|
+
return color;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface AppBadgeVersionProps extends AppBadgeDefaultProps {
|
|
32
|
+
color?: string | ColorKey;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AppBadgeVersion({
|
|
36
|
+
icon = '',
|
|
37
|
+
value = '',
|
|
38
|
+
color = 'info',
|
|
39
|
+
loading = false,
|
|
40
|
+
...rest
|
|
41
|
+
}: AppBadgeVersionProps) {
|
|
42
|
+
const theme = useTheme();
|
|
43
|
+
const txtcolor = getTextColor(theme, color);
|
|
44
|
+
const bgcolor = getBgColor(theme, color);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<BadgeContainer
|
|
48
|
+
loading={loading}
|
|
49
|
+
sx={{
|
|
50
|
+
borderColor: alpha(txtcolor, 0.2),
|
|
51
|
+
bgcolor,
|
|
52
|
+
}}
|
|
53
|
+
{...rest}>
|
|
54
|
+
<Icon icon={icon || 'lucide:orbit'} style={{ marginRight: 6 }} />
|
|
55
|
+
<Typography className="app-badge-value" style={{ color: txtcolor }}>
|
|
56
|
+
v {value}
|
|
57
|
+
</Typography>
|
|
58
|
+
</BadgeContainer>
|
|
59
|
+
);
|
|
60
|
+
}
|
package/src/Dashboard/index.jsx
CHANGED
|
@@ -13,12 +13,13 @@ import { mapRecursive, flatRecursive, matchPaths } from '../utils';
|
|
|
13
13
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation, filterNavByRole } from '../blocklets';
|
|
14
14
|
import HeaderAddons from '../common/header-addons';
|
|
15
15
|
import { useWalletHiddenTopbar } from '../common/wallet-hidden-topbar';
|
|
16
|
+
import { AppHeader, AppBadge, AppInfoProvider, useAppInfo } from './app-shell';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* 专门用于 (composable) blocklet 的 Dashboard 组件, 解析 blocklet meta 中 section 为 dashboard 的 navigation 数据, 渲染一个 UX Dashboard
|
|
19
20
|
*/
|
|
20
21
|
function Dashboard({
|
|
21
|
-
meta =
|
|
22
|
+
meta = undefined,
|
|
22
23
|
fallbackUrl = publicPath,
|
|
23
24
|
invalidPathFallback = null,
|
|
24
25
|
headerAddons = undefined,
|
|
@@ -32,6 +33,10 @@ function Dashboard({
|
|
|
32
33
|
},
|
|
33
34
|
links = [],
|
|
34
35
|
showDomainWarningDialog = true,
|
|
36
|
+
appPath = undefined,
|
|
37
|
+
appTab = undefined,
|
|
38
|
+
onAppTabChange = undefined,
|
|
39
|
+
children = undefined,
|
|
35
40
|
...rest
|
|
36
41
|
}) {
|
|
37
42
|
useWalletHiddenTopbar();
|
|
@@ -150,8 +155,12 @@ function Dashboard({
|
|
|
150
155
|
logo: <img src={appLogo} alt="logo" />,
|
|
151
156
|
addons: _headerAddons,
|
|
152
157
|
...rest.headerProps,
|
|
153
|
-
}}
|
|
154
|
-
|
|
158
|
+
}}>
|
|
159
|
+
<AppInfoProvider path={appPath} currentTab={appTab} meta={meta}>
|
|
160
|
+
<AppHeader onTabChange={onAppTabChange} />
|
|
161
|
+
{children}
|
|
162
|
+
</AppInfoProvider>
|
|
163
|
+
</UxDashboard>
|
|
155
164
|
);
|
|
156
165
|
}
|
|
157
166
|
|
|
@@ -165,6 +174,11 @@ Dashboard.propTypes = {
|
|
|
165
174
|
sessionManagerProps: SessionManagerProps,
|
|
166
175
|
links: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
|
|
167
176
|
showDomainWarningDialog: PropTypes.bool,
|
|
177
|
+
appPath: PropTypes.string,
|
|
178
|
+
appTab: PropTypes.string,
|
|
179
|
+
onAppTabChange: PropTypes.func,
|
|
180
|
+
children: PropTypes.node,
|
|
168
181
|
};
|
|
169
182
|
|
|
183
|
+
export { AppHeader, AppBadge, AppInfoProvider, useAppInfo };
|
|
170
184
|
export default Dashboard;
|
|
@@ -43,7 +43,7 @@ function InternalFooter({ ...props }) {
|
|
|
43
43
|
return brand ? <Brand {...brand} /> : null;
|
|
44
44
|
};
|
|
45
45
|
const renderNavigation = () => {
|
|
46
|
-
return navigation?.length ? <Links links={navigation}
|
|
46
|
+
return navigation?.length ? <Links links={navigation} minColumns={3} /> : null;
|
|
47
47
|
};
|
|
48
48
|
const renderSocialMedia = () => {
|
|
49
49
|
return socialMedia?.length ? <SocialMedia items={socialMedia} /> : null;
|
package/src/Footer/links.jsx
CHANGED
|
@@ -14,13 +14,15 @@ import { splitNavColumns, isMailProtocol } from '../utils';
|
|
|
14
14
|
/**
|
|
15
15
|
* footer 中的 links (支持分组, 最多支持 2 级)
|
|
16
16
|
*/
|
|
17
|
-
export default function Links({ links = [], flowLayout = false,
|
|
17
|
+
export default function Links({ links = [], flowLayout = false, minColumns = 1, maxColumns = 4, ...rest }) {
|
|
18
18
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
19
19
|
const isMobile = useMobile({ key: 'md' });
|
|
20
20
|
// 只要发现一项元素有子元素, 就认为是分组 (大字号突出 group title)
|
|
21
21
|
const isGroupMode = links.some((item) => item.items?.length);
|
|
22
|
-
//
|
|
23
|
-
const columnsLayout = !isMobile && isGroupMode && isInteger(
|
|
22
|
+
// 是否启用分列布局
|
|
23
|
+
const columnsLayout = !isMobile && isGroupMode && isInteger(minColumns) && minColumns > 1 && maxColumns >= minColumns;
|
|
24
|
+
// Clamp columns
|
|
25
|
+
const columns = columnsLayout ? Math.min(Math.max(links.length, minColumns), maxColumns) : 1;
|
|
24
26
|
const renderItem = ({ label, link, icon, render, props }) => {
|
|
25
27
|
let result = label;
|
|
26
28
|
|
|
@@ -139,6 +141,7 @@ export default function Links({ links = [], flowLayout = false, columns, ...rest
|
|
|
139
141
|
|
|
140
142
|
return (
|
|
141
143
|
<Root
|
|
144
|
+
columns={columns}
|
|
142
145
|
{...rest}
|
|
143
146
|
className={clsx(rest.className, {
|
|
144
147
|
'footer-links--grouped': isGroupMode,
|
|
@@ -161,11 +164,12 @@ Links.propTypes = {
|
|
|
161
164
|
),
|
|
162
165
|
// 流动布局, 简单的从左到右排列
|
|
163
166
|
flowLayout: PropTypes.bool,
|
|
164
|
-
//
|
|
165
|
-
|
|
167
|
+
// 列布局,最小列数,最大列数
|
|
168
|
+
minColumns: PropTypes.number,
|
|
169
|
+
maxColumns: PropTypes.number,
|
|
166
170
|
};
|
|
167
171
|
|
|
168
|
-
const Root = styled('div')`
|
|
172
|
+
const Root = styled('div', { shouldForwardProp: (prop) => prop !== 'columns' })`
|
|
169
173
|
overflow: hidden;
|
|
170
174
|
color: ${({ theme }) => theme.palette.text.secondary};
|
|
171
175
|
.footer-links-inner {
|
|
@@ -230,7 +234,7 @@ const Root = styled('div')`
|
|
|
230
234
|
/* columns 布局 */
|
|
231
235
|
&.footer-links--columns {
|
|
232
236
|
.footer-links-inner {
|
|
233
|
-
gap:
|
|
237
|
+
gap: ${({ columns }) => `${288 / columns}px`};
|
|
234
238
|
}
|
|
235
239
|
.footer-links-column {
|
|
236
240
|
display: flex;
|
package/src/utils.js
CHANGED
|
@@ -89,6 +89,8 @@ export const matchPaths = (paths = []) => {
|
|
|
89
89
|
/** 导航列表分列 */
|
|
90
90
|
export const splitNavColumns = (items, options = {}) => {
|
|
91
91
|
const { columns = 1, breakInside = false, groupHeight = 48, itemHeight = 24, childrenKey = 'items' } = options;
|
|
92
|
+
// 分组数
|
|
93
|
+
const groups = items.length;
|
|
92
94
|
|
|
93
95
|
// 高度预估
|
|
94
96
|
const totalHeight = items.reduce((height, group) => {
|
|
@@ -114,16 +116,17 @@ export const splitNavColumns = (items, options = {}) => {
|
|
|
114
116
|
};
|
|
115
117
|
|
|
116
118
|
items.forEach((group) => {
|
|
119
|
+
// 当前分组的预估高度
|
|
117
120
|
const groupTotalHeight = groupHeight + (group[childrenKey]?.length || 0) * itemHeight;
|
|
118
121
|
|
|
119
|
-
//
|
|
122
|
+
// 允许截断分组,可以在任何子项处换列,尽可能利用当前列的剩余空间
|
|
120
123
|
if (breakInside && shouldBreakColumn(groupHeight)) {
|
|
121
124
|
currentColumn++;
|
|
122
125
|
currentHeight = 0;
|
|
123
126
|
result[currentColumn] = [];
|
|
124
127
|
}
|
|
125
|
-
//
|
|
126
|
-
if (!breakInside && currentHeight > 0 && shouldBreakColumn(groupTotalHeight)) {
|
|
128
|
+
// 不允许截断分组,只能在分组边界换列,优先分列,列不够用再考虑充分利用剩余空间
|
|
129
|
+
if (!breakInside && currentHeight > 0 && (groups <= columns || shouldBreakColumn(groupTotalHeight))) {
|
|
127
130
|
currentColumn++;
|
|
128
131
|
currentHeight = 0;
|
|
129
132
|
result[currentColumn] = [];
|