@arcblock/ux 2.6.8 → 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 +12 -0
- package/es/ActionButton/index.js +99 -0
- package/es/ActivityIndicator/index.js +180 -0
- package/es/Address/compact-text.js +105 -0
- package/es/Address/did-address.js +211 -0
- package/es/Address/index.js +23 -0
- package/es/Address/responsive-did-address.js +88 -0
- package/es/Alert/index.js +134 -0
- package/es/AnimationWaiter/default-animation.json +1 -0
- package/es/AnimationWaiter/index.js +239 -0
- package/es/Async/index.js +38 -0
- package/es/Avatar/did-motif.js +64 -0
- package/es/Avatar/etherscan-blockies.js +83 -0
- package/es/Avatar/index.js +176 -0
- package/es/Badge/index.js +98 -0
- package/es/Blocklet/blocklet.js +298 -0
- package/es/Blocklet/index.js +4 -0
- package/es/Blocklet/utils.js +52 -0
- package/es/BlockletNFT/index.js +412 -0
- package/es/Button/index.js +8 -0
- package/es/Button/wrap.js +140 -0
- package/es/ButtonGroup/index.js +6 -0
- package/es/CardSelector/index.js +131 -0
- package/es/Center/index.js +41 -0
- package/es/ClickToCopy/copy-button.js +72 -0
- package/es/ClickToCopy/hook.js +39 -0
- package/es/ClickToCopy/index.js +93 -0
- package/es/CodeBlock/LightBox.js +85 -0
- package/es/CodeBlock/index.js +226 -0
- package/es/Colors/index.js +2 -0
- package/es/Colors/themes/default.js +78 -0
- package/es/ContactForm/index.js +230 -0
- package/es/CookieConsent/index.js +113 -0
- package/es/CountDown/index.js +178 -0
- package/es/DID/index.js +105 -0
- package/es/Datatable/CustomToolbar.js +396 -0
- package/es/Datatable/DatatableContext.js +34 -0
- package/es/Datatable/TableSearch.js +165 -0
- package/es/Datatable/index.js +627 -0
- package/es/Datatable/utils.js +132 -0
- package/es/Dialog/confirm.js +90 -0
- package/es/Dialog/dialog.js +192 -0
- package/es/Dialog/index.js +3 -0
- package/es/DidLogo/index.js +31 -0
- package/es/DriftBot/index.js +70 -0
- package/es/Earth/countries.json +8057 -0
- package/es/Earth/index.js +521 -0
- package/es/Earth/util.js +51 -0
- package/es/Empty/index.js +64 -0
- package/es/ErrorBoundary/fallback.js +73 -0
- package/es/ErrorBoundary/index.js +1 -0
- package/es/Footer/index.js +172 -0
- package/es/Header/auto-hidden.js +35 -0
- package/es/Header/header.js +211 -0
- package/es/Header/index.js +2 -0
- package/es/Header/responsive-header.js +111 -0
- package/es/Icon/image.js +65 -0
- package/es/Icon/index.js +84 -0
- package/es/Img/index.js +217 -0
- package/es/InfoRow/index.js +87 -0
- package/es/Layout/dashboard/external-link.js +58 -0
- package/es/Layout/dashboard/full-page.js +53 -0
- package/es/Layout/dashboard/index.js +275 -0
- package/es/Layout/dashboard/sidebar.js +209 -0
- package/es/Layout/dashboard-legacy/header.js +174 -0
- package/es/Layout/dashboard-legacy/index.js +149 -0
- package/es/Layout/dashboard-legacy/sidebar.js +129 -0
- package/es/Layout/index.js +335 -0
- package/es/Locale/browser-lang.js +52 -0
- package/es/Locale/context.js +114 -0
- package/es/Locale/languages.js +60 -0
- package/es/Locale/selector.js +180 -0
- package/es/Locale/util.js +13 -0
- package/es/Logo/images/logo-dark-text.svg +3 -0
- package/es/Logo/images/logo-dark-top.svg +6 -0
- package/es/Logo/images/logo-light-text.svg +3 -0
- package/es/Logo/images/logo-light-top.svg +6 -0
- package/es/Logo/index.js +136 -0
- package/es/Metric/index.js +132 -0
- package/es/NFTDisplay/README.md +59 -0
- package/es/NFTDisplay/aspect-ratio-container.js +39 -0
- package/es/NFTDisplay/broken.js +18 -0
- package/es/NFTDisplay/demo/data/asset-state-display-url.json +7 -0
- package/es/NFTDisplay/demo/data/asset-state-gzipped-svg-1-1.json +10 -0
- package/es/NFTDisplay/demo/data/asset-state-gzipped-svg-374-130.json +10 -0
- package/es/NFTDisplay/demo/data/asset-state-gzipped-svg-with-foreign-object.json +20 -0
- package/es/NFTDisplay/demo/data/asset-state-svg.json +29 -0
- package/es/NFTDisplay/demo/data/asset-state-url.json +10 -0
- package/es/NFTDisplay/index.js +323 -0
- package/es/NFTDisplay/loading.js +18 -0
- package/es/NFTDisplay/svg-embedder/img.js +45 -0
- package/es/NFTDisplay/svg-embedder/inline-svg.js +39 -0
- package/es/NavMenu/index.js +2 -0
- package/es/NavMenu/nav-menu.js +286 -0
- package/es/NavMenu/style.js +176 -0
- package/es/PageScroller/index.js +286 -0
- package/es/PageScroller/story/FifthComponent.js +9 -0
- package/es/PageScroller/story/FirstComponent.js +9 -0
- package/es/PageScroller/story/FourthComponent.js +12 -0
- package/es/PageScroller/story/FullPage.js +47 -0
- package/es/PageScroller/story/PageContain.js +59 -0
- package/es/PageScroller/story/SecondComponent.js +9 -0
- package/es/PageScroller/story/ThirdComponent.js +9 -0
- package/es/PageScroller/story/index.css +115 -0
- package/es/PageScroller/usePrevValue.js +9 -0
- package/es/PricingTable/PricingPlan.js +124 -0
- package/es/PricingTable/index.js +53 -0
- package/es/QRCode/index.js +72 -0
- package/es/RelativeTime/index.js +98 -0
- package/es/Result/common.js +140 -0
- package/es/Result/demo/fixtures/result-image-404.svg +1 -0
- package/es/Result/index.js +33 -0
- package/es/Result/result.js +59 -0
- package/es/Result/translations.js +54 -0
- package/es/Screenshot/BaseScreenshot/index.js +91 -0
- package/es/Screenshot/BaseScreenshot/shells/Macbook.js +51 -0
- package/es/Screenshot/BaseScreenshot/shells/Phone.js +36 -0
- package/es/Screenshot/demo/images/bg-00.jpg +0 -0
- package/es/Screenshot/demo/images/bg-01.jpg +0 -0
- package/es/Screenshot/demo/images/bg-02.jpg +0 -0
- package/es/Screenshot/demo/images/bg-03.jpg +0 -0
- package/es/Screenshot/demo/images/bg-04.jpg +0 -0
- package/es/Screenshot/demo/images/bg-05.jpg +0 -0
- package/es/Screenshot/demo/images/bg-06.jpg +0 -0
- package/es/Screenshot/demo/images/bg-07.jpg +0 -0
- package/es/Screenshot/demo/images/bg-08.jpg +0 -0
- package/es/Screenshot/demo/images/bg-09.jpg +0 -0
- package/es/Screenshot/devices.css +1366 -0
- package/es/Screenshot/index.js +299 -0
- package/es/SessionManager/federated-login-detecter.js +166 -0
- package/es/SessionManager/index.js +468 -0
- package/es/SessionManager/user-popper.js +132 -0
- package/es/Sparkline/index.js +193 -0
- package/es/Spinner/index.js +28 -0
- package/es/SplitButton/index.js +144 -0
- package/es/Switch/index.js +96 -0
- package/es/Tabs/index.js +48 -0
- package/es/Tag/index.js +108 -0
- package/es/TextCollapse/index.js +92 -0
- package/es/Theme/index.js +16 -0
- package/es/Theme/theme-provider.js +39 -0
- package/es/Theme/theme.js +133 -0
- package/es/Toast/index.js +95 -0
- package/es/Util/deprecate.js +28 -0
- package/es/Util/index.js +298 -0
- package/es/Util/wallet.js +32 -0
- package/es/Video/index.js +89 -0
- package/es/Wallet/Action.js +119 -0
- package/es/Wallet/Download.js +331 -0
- package/es/Wallet/Open.js +45 -0
- package/es/Wallet/images/abtwallet.png +0 -0
- package/es/Wallet/images/android_download.svg +23 -0
- package/es/Wallet/images/app-store.svg +20 -0
- package/es/Wallet/images/google-play.svg +70 -0
- package/es/WebWalletSWKeeper/index.js +115 -0
- package/es/WechatPrompt/images/android.png +0 -0
- package/es/WechatPrompt/images/ios.png +0 -0
- package/es/WechatPrompt/index.js +88 -0
- package/es/index.js +38 -0
- package/es/withTheme/index.js +69 -0
- package/es/withTracker/README.md +34 -0
- package/es/withTracker/error_boundary.js +34 -0
- package/es/withTracker/index.js +56 -0
- package/package.json +272 -5
package/es/Img/index.js
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import SvgIcon from '@mui/material/SvgIcon';
|
4
|
+
import { useInView } from 'react-intersection-observer';
|
5
|
+
import Alert from 'mdi-material-ui/Alert';
|
6
|
+
import ImageIcon from 'mdi-material-ui/Image';
|
7
|
+
import { styled } from '../Theme';
|
8
|
+
|
9
|
+
/**
|
10
|
+
* @typedef {Object} ImgExProps
|
11
|
+
* @property {string} src - required
|
12
|
+
* @property {string} [alt]
|
13
|
+
* @property {string} [size='cover']
|
14
|
+
* @property {string} [position='top center']
|
15
|
+
* @property {object} [style]
|
16
|
+
* @property {number} [ratio=1]
|
17
|
+
* @property {string} [repeat='no-repeat']
|
18
|
+
* @property {number} [width]
|
19
|
+
* @property {number} [height]
|
20
|
+
* @property {boolean} [lazy=true]
|
21
|
+
* @property {string} [placeholder]
|
22
|
+
* @property {string} [fallback]
|
23
|
+
* @property {string} [className='']
|
24
|
+
* @property {function} [onError=() => {}]
|
25
|
+
* @property {function} [onSuccess=() => {}]
|
26
|
+
*/
|
27
|
+
|
28
|
+
/**
|
29
|
+
* @typedef {ImgExProps & import('react').ComponentPropsWithoutRef<"div">} ImgProps
|
30
|
+
*/
|
31
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
32
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
33
|
+
const PREFIX = 'Img';
|
34
|
+
const classes = {
|
35
|
+
root: `${PREFIX}-root`
|
36
|
+
};
|
37
|
+
|
38
|
+
/** @type {import('@emotion/styled').StyledComponent<import('react').ComponentPropsWithoutRef<"div">, {}, { ref?: React.Ref<any> | undefined;}>} */
|
39
|
+
const Root = styled('div')(() => ({
|
40
|
+
[`& .${classes.root}`]: {
|
41
|
+
position: 'relative',
|
42
|
+
overflow: 'hidden',
|
43
|
+
'& .image--state, & .image--img': {
|
44
|
+
minWidth: '100%',
|
45
|
+
minHeight: '100%',
|
46
|
+
position: 'absolute',
|
47
|
+
top: 0,
|
48
|
+
left: '50%',
|
49
|
+
transform: 'translateX(-50%)'
|
50
|
+
},
|
51
|
+
'& .image--state': {
|
52
|
+
display: 'flex',
|
53
|
+
flexDirection: 'column',
|
54
|
+
justifyContent: 'center',
|
55
|
+
alignItems: 'center',
|
56
|
+
color: '#999',
|
57
|
+
background: '#eee',
|
58
|
+
width: '100%',
|
59
|
+
height: '100%'
|
60
|
+
},
|
61
|
+
'& .image--img': {
|
62
|
+
opacity: 0,
|
63
|
+
visibility: 'hidden'
|
64
|
+
},
|
65
|
+
'& .image--icon': {
|
66
|
+
fontSize: 30,
|
67
|
+
maxWidth: '80%',
|
68
|
+
maxHeight: '80%'
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}));
|
72
|
+
|
73
|
+
/**
|
74
|
+
*
|
75
|
+
* @param {ImgProps} props
|
76
|
+
* @returns {React.ReactComponentElement}
|
77
|
+
*/
|
78
|
+
function Img({
|
79
|
+
lazy,
|
80
|
+
width,
|
81
|
+
height,
|
82
|
+
repeat,
|
83
|
+
ratio,
|
84
|
+
alt,
|
85
|
+
size,
|
86
|
+
position,
|
87
|
+
src,
|
88
|
+
placeholder,
|
89
|
+
fallback,
|
90
|
+
style,
|
91
|
+
className,
|
92
|
+
onError,
|
93
|
+
onSuccess,
|
94
|
+
...rest
|
95
|
+
}) {
|
96
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
97
|
+
const [ref, inView] = lazy ? useInView({
|
98
|
+
threshold: 0,
|
99
|
+
triggerOnce: true
|
100
|
+
}) : [null, true];
|
101
|
+
const [imgState, setImgState] = useState('init');
|
102
|
+
const actualSrc = useMemo(() => {
|
103
|
+
switch (imgState) {
|
104
|
+
case 'init':
|
105
|
+
case 'loading':
|
106
|
+
return placeholder;
|
107
|
+
case 'error':
|
108
|
+
return fallback;
|
109
|
+
case 'loaded':
|
110
|
+
return src;
|
111
|
+
default:
|
112
|
+
return null;
|
113
|
+
}
|
114
|
+
}, [placeholder, fallback, src, imgState]);
|
115
|
+
const actualRatio = width && height ? 100 * height / width : ratio * 100;
|
116
|
+
const mergedStyle = useMemo(() => ({
|
117
|
+
backgroundImage: actualSrc ? `url(${actualSrc})` : '',
|
118
|
+
backgroundPosition: position,
|
119
|
+
backgroundSize: size,
|
120
|
+
backgroundRepeat: repeat,
|
121
|
+
paddingTop: `${actualRatio}%`
|
122
|
+
}),
|
123
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
124
|
+
[actualSrc, position, size, imgState]);
|
125
|
+
|
126
|
+
/**
|
127
|
+
* @type {CSSStyleDeclaration}
|
128
|
+
*/
|
129
|
+
const outerStyle = {
|
130
|
+
...style,
|
131
|
+
display: 'inline-block',
|
132
|
+
width: width ? `${width}px` : '100%'
|
133
|
+
};
|
134
|
+
function loadImg() {
|
135
|
+
const img = new Image();
|
136
|
+
img.src = src;
|
137
|
+
setImgState('loading');
|
138
|
+
img.onload = () => {
|
139
|
+
setImgState('loaded');
|
140
|
+
onSuccess();
|
141
|
+
};
|
142
|
+
img.onerror = err => {
|
143
|
+
setImgState('error');
|
144
|
+
onError(err);
|
145
|
+
};
|
146
|
+
}
|
147
|
+
useEffect(() => {
|
148
|
+
if (inView) loadImg();
|
149
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
150
|
+
}, [inView]);
|
151
|
+
return (
|
152
|
+
/*#__PURE__*/
|
153
|
+
// paddingTop 要求元素本身的宽度为 100%,所以只能加一个外层元素去限制宽度
|
154
|
+
_jsx(Root, {
|
155
|
+
ref: ref,
|
156
|
+
style: outerStyle,
|
157
|
+
...rest,
|
158
|
+
children: /*#__PURE__*/_jsxs("div", {
|
159
|
+
className: `image ${className} ${classes.root}`,
|
160
|
+
style: mergedStyle,
|
161
|
+
children: [!fallback && imgState === 'error' && /*#__PURE__*/_jsx("div", {
|
162
|
+
className: "image--state",
|
163
|
+
title: "loading image",
|
164
|
+
children: /*#__PURE__*/_jsx(SvgIcon, {
|
165
|
+
component: Alert,
|
166
|
+
className: "image--icon"
|
167
|
+
})
|
168
|
+
}), !placeholder && imgState === 'loading' && /*#__PURE__*/_jsx("div", {
|
169
|
+
className: "image--state",
|
170
|
+
title: "Image load error",
|
171
|
+
children: /*#__PURE__*/_jsx(SvgIcon, {
|
172
|
+
component: ImageIcon,
|
173
|
+
className: "image--icon"
|
174
|
+
})
|
175
|
+
}), imgState === 'loaded' && /*#__PURE__*/_jsx("img", {
|
176
|
+
className: "image--img",
|
177
|
+
src: src,
|
178
|
+
alt: alt
|
179
|
+
})]
|
180
|
+
})
|
181
|
+
})
|
182
|
+
);
|
183
|
+
}
|
184
|
+
Img.propTypes = {
|
185
|
+
src: PropTypes.string.isRequired,
|
186
|
+
alt: PropTypes.string,
|
187
|
+
size: PropTypes.string,
|
188
|
+
position: PropTypes.string,
|
189
|
+
style: PropTypes.object,
|
190
|
+
ratio: PropTypes.number,
|
191
|
+
repeat: PropTypes.string,
|
192
|
+
width: PropTypes.number,
|
193
|
+
height: PropTypes.number,
|
194
|
+
lazy: PropTypes.bool,
|
195
|
+
placeholder: PropTypes.string,
|
196
|
+
fallback: PropTypes.string,
|
197
|
+
className: PropTypes.string,
|
198
|
+
onError: PropTypes.func,
|
199
|
+
onSuccess: PropTypes.func
|
200
|
+
};
|
201
|
+
Img.defaultProps = {
|
202
|
+
alt: null,
|
203
|
+
size: 'cover',
|
204
|
+
position: 'top center',
|
205
|
+
repeat: 'no-repeat',
|
206
|
+
style: null,
|
207
|
+
ratio: 1,
|
208
|
+
width: null,
|
209
|
+
height: null,
|
210
|
+
lazy: true,
|
211
|
+
placeholder: null,
|
212
|
+
fallback: null,
|
213
|
+
className: '',
|
214
|
+
onError: () => {},
|
215
|
+
onSuccess: () => {}
|
216
|
+
};
|
217
|
+
export default Img;
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import camelCase from 'lodash/camelCase';
|
3
|
+
import upperFirst from 'lodash/upperFirst';
|
4
|
+
import Typography from '@mui/material/Typography';
|
5
|
+
import { styled } from '../Theme';
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
7
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
8
|
+
function InfoRow({
|
9
|
+
name,
|
10
|
+
nameFormatter,
|
11
|
+
layout,
|
12
|
+
children,
|
13
|
+
valueComponent,
|
14
|
+
nameWidth,
|
15
|
+
...rest
|
16
|
+
}) {
|
17
|
+
return /*#__PURE__*/_jsxs(Container, {
|
18
|
+
layout: layout,
|
19
|
+
width: nameWidth,
|
20
|
+
...rest,
|
21
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
22
|
+
color: "textSecondary",
|
23
|
+
className: "info-row__name",
|
24
|
+
children: nameFormatter(name)
|
25
|
+
}), children && /*#__PURE__*/_jsx(Typography, {
|
26
|
+
color: "textPrimary",
|
27
|
+
component: valueComponent,
|
28
|
+
className: "info-row__value",
|
29
|
+
children: children
|
30
|
+
})]
|
31
|
+
});
|
32
|
+
}
|
33
|
+
InfoRow.propTypes = {
|
34
|
+
name: PropTypes.any.isRequired,
|
35
|
+
layout: PropTypes.oneOf(['horizontal', 'vertical']),
|
36
|
+
children: PropTypes.any,
|
37
|
+
nameFormatter: PropTypes.func,
|
38
|
+
valueComponent: PropTypes.string,
|
39
|
+
nameWidth: PropTypes.number
|
40
|
+
};
|
41
|
+
InfoRow.defaultProps = {
|
42
|
+
children: null,
|
43
|
+
nameWidth: 90,
|
44
|
+
nameFormatter: name => {
|
45
|
+
const resetName = name.split(' ').map(x => upperFirst(camelCase(x))).join(' ');
|
46
|
+
return typeof name === 'string' ? resetName : name;
|
47
|
+
},
|
48
|
+
valueComponent: 'div',
|
49
|
+
layout: 'horizontal'
|
50
|
+
};
|
51
|
+
const Container = styled('div')`
|
52
|
+
display: flex;
|
53
|
+
flex-direction: ${props => props.layout === 'vertical' ? 'column' : 'row'};
|
54
|
+
justify-content: flex-start;
|
55
|
+
align-items: ${props => props.layout === 'vertical' ? 'flex-start' : 'center'};
|
56
|
+
margin-bottom: 16px;
|
57
|
+
|
58
|
+
.info-row__name {
|
59
|
+
width: ${props => props.width}px;
|
60
|
+
margin-right: ${props => props.layout === 'vertical' ? '0' : '8px'};
|
61
|
+
margin-bottom: ${props => props.layout === 'vertical' ? '8px' : '0'};
|
62
|
+
text-transform: capitalize;
|
63
|
+
}
|
64
|
+
|
65
|
+
.info-row__value {
|
66
|
+
flex: 1;
|
67
|
+
overflow: auto;
|
68
|
+
word-wrap: break-word;
|
69
|
+
font-weight: 500;
|
70
|
+
}
|
71
|
+
|
72
|
+
@media (max-width: ${props => props.theme.breakpoints.values.md}px) {
|
73
|
+
flex-direction: column;
|
74
|
+
justify-content: flex-start;
|
75
|
+
align-items: flex-start;
|
76
|
+
.info-row__name {
|
77
|
+
font-weight: bold;
|
78
|
+
}
|
79
|
+
|
80
|
+
.info-row__value {
|
81
|
+
width: 100%;
|
82
|
+
font-size: 12px;
|
83
|
+
margin-top: 8px;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
`;
|
87
|
+
export default InfoRow;
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import { Link as RouterLink, NavLink as RouterNavLink } from 'react-router-dom';
|
3
|
+
|
4
|
+
// 包裹 router Link/NavLink 组件, 支持 external link (external 为 true 时渲染为 a 标签)
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
6
|
+
function ExternalLink({
|
7
|
+
children,
|
8
|
+
routerLinkComponent: RouterLinkComponent,
|
9
|
+
to,
|
10
|
+
external,
|
11
|
+
...rest
|
12
|
+
}) {
|
13
|
+
if (external) {
|
14
|
+
return /*#__PURE__*/_jsx("a", {
|
15
|
+
href: to,
|
16
|
+
...rest,
|
17
|
+
children: children
|
18
|
+
});
|
19
|
+
}
|
20
|
+
return /*#__PURE__*/_jsx(RouterLinkComponent, {
|
21
|
+
to: to,
|
22
|
+
...rest,
|
23
|
+
children: children
|
24
|
+
});
|
25
|
+
}
|
26
|
+
ExternalLink.propTypes = {
|
27
|
+
children: PropTypes.any.isRequired,
|
28
|
+
routerLinkComponent: PropTypes.elementType.isRequired,
|
29
|
+
external: PropTypes.bool,
|
30
|
+
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
|
31
|
+
};
|
32
|
+
ExternalLink.defaultProps = {
|
33
|
+
external: false
|
34
|
+
};
|
35
|
+
export function Link(props) {
|
36
|
+
return /*#__PURE__*/_jsx(ExternalLink, {
|
37
|
+
routerLinkComponent: RouterLink,
|
38
|
+
...props
|
39
|
+
});
|
40
|
+
}
|
41
|
+
export function NavLink({
|
42
|
+
className,
|
43
|
+
...rest
|
44
|
+
}) {
|
45
|
+
// NavLink#className 支持 function
|
46
|
+
const classes = rest.external && typeof className === 'function' ? className({}) : className;
|
47
|
+
return /*#__PURE__*/_jsx(ExternalLink, {
|
48
|
+
routerLinkComponent: RouterNavLink,
|
49
|
+
className: classes,
|
50
|
+
...rest
|
51
|
+
});
|
52
|
+
}
|
53
|
+
NavLink.propTypes = {
|
54
|
+
className: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
|
55
|
+
};
|
56
|
+
NavLink.defaultProps = {
|
57
|
+
className: ''
|
58
|
+
};
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { useState, useContext, createContext, useMemo, useLayoutEffect } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
4
|
+
export const FullPageContext = /*#__PURE__*/createContext();
|
5
|
+
export const useFullPage = initialState => {
|
6
|
+
const ctx = useContext(FullPageContext);
|
7
|
+
useLayoutEffect(() => {
|
8
|
+
if (initialState) {
|
9
|
+
ctx.configure(initialState);
|
10
|
+
}
|
11
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
12
|
+
}, []);
|
13
|
+
return ctx;
|
14
|
+
};
|
15
|
+
export function FullPageProvider({
|
16
|
+
children,
|
17
|
+
...rest
|
18
|
+
}) {
|
19
|
+
const [state, setState] = useState({
|
20
|
+
inFullPage: false,
|
21
|
+
showToggleButton: false,
|
22
|
+
headerVisibleInFullPage: true,
|
23
|
+
footerVisibleInFullPage: false,
|
24
|
+
sidebarVisibleInFullPage: false
|
25
|
+
});
|
26
|
+
const toggleFullPage = () => {
|
27
|
+
setState(prev => ({
|
28
|
+
...prev,
|
29
|
+
inFullPage: !prev.inFullPage
|
30
|
+
}));
|
31
|
+
};
|
32
|
+
const value = useMemo(() => {
|
33
|
+
return {
|
34
|
+
...state,
|
35
|
+
headerVisible: !state.inFullPage || state.headerVisibleInFullPage,
|
36
|
+
footerVisible: !state.inFullPage || state.footerVisibleInFullPage,
|
37
|
+
sidebarVisible: !state.inFullPage || state.sidebarVisibleInFullPage,
|
38
|
+
toggleFullPage,
|
39
|
+
configure: params => setState(prev => ({
|
40
|
+
...prev,
|
41
|
+
...params
|
42
|
+
}))
|
43
|
+
};
|
44
|
+
}, [state]);
|
45
|
+
return /*#__PURE__*/_jsx(FullPageContext.Provider, {
|
46
|
+
value: value,
|
47
|
+
...rest,
|
48
|
+
children: children
|
49
|
+
});
|
50
|
+
}
|
51
|
+
FullPageProvider.propTypes = {
|
52
|
+
children: PropTypes.node.isRequired
|
53
|
+
};
|
@@ -0,0 +1,275 @@
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { useLocation, matchPath } from 'react-router-dom';
|
4
|
+
import Helmet from 'react-helmet';
|
5
|
+
import Container from '@mui/material/Container';
|
6
|
+
import Hidden from '@mui/material/Hidden';
|
7
|
+
import Box from '@mui/material/Box';
|
8
|
+
import clsx from 'clsx';
|
9
|
+
import OpenInFullIcon from '@mui/icons-material/OpenInFull';
|
10
|
+
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
11
|
+
import DashboardLegacy from '../dashboard-legacy';
|
12
|
+
import { ResponsiveHeader } from '../../Header';
|
13
|
+
import NavMenu from '../../NavMenu';
|
14
|
+
import Footer from '../../Footer';
|
15
|
+
import Sidebar from './sidebar';
|
16
|
+
import { styled, useTheme } from '../../Theme';
|
17
|
+
import { Link } from './external-link';
|
18
|
+
import { FullPageProvider, useFullPage } from './full-page';
|
19
|
+
|
20
|
+
// 监听 location 变化并关闭 header 中的 menu (确保 drawer - disablePortal:false, keepMounted:true)
|
21
|
+
// 直接监听 menu 中 link 点击来触发 closeMenu 会有问题 (Suspense & lazy)
|
22
|
+
// eslint-disable-next-line react/prop-types
|
23
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
24
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
25
|
+
function NavMenuWrapper({
|
26
|
+
closeMenu,
|
27
|
+
...rest
|
28
|
+
}) {
|
29
|
+
const location = useLocation();
|
30
|
+
useEffect(() => {
|
31
|
+
closeMenu();
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
33
|
+
}, [location]);
|
34
|
+
return /*#__PURE__*/_jsx(NavMenu, {
|
35
|
+
...rest
|
36
|
+
});
|
37
|
+
}
|
38
|
+
function formatLinks(links, location) {
|
39
|
+
return links.map(link => {
|
40
|
+
if (link.children?.length) {
|
41
|
+
return {
|
42
|
+
...link,
|
43
|
+
label: link.title,
|
44
|
+
children: formatLinks(link.children, location)
|
45
|
+
};
|
46
|
+
}
|
47
|
+
return {
|
48
|
+
...link,
|
49
|
+
label: /*#__PURE__*/_jsx(Link, {
|
50
|
+
to: link.url,
|
51
|
+
external: link.external,
|
52
|
+
children: link.title
|
53
|
+
}),
|
54
|
+
// external = true 时 active 状态由传入 links 的调用方决定 (适用于 blocklet ui dashboard 的情况)
|
55
|
+
active: link.external ? link.active : !!matchPath({
|
56
|
+
path: link.url,
|
57
|
+
end: false
|
58
|
+
}, location.pathname)
|
59
|
+
};
|
60
|
+
});
|
61
|
+
}
|
62
|
+
function Dashboard({
|
63
|
+
children,
|
64
|
+
title,
|
65
|
+
headerProps,
|
66
|
+
links = [],
|
67
|
+
fullWidth,
|
68
|
+
dense,
|
69
|
+
footerProps,
|
70
|
+
...rest
|
71
|
+
}) {
|
72
|
+
const theme = useTheme();
|
73
|
+
const {
|
74
|
+
inFullPage,
|
75
|
+
showToggleButton,
|
76
|
+
headerVisible,
|
77
|
+
footerVisible,
|
78
|
+
sidebarVisible,
|
79
|
+
toggleFullPage
|
80
|
+
} = useFullPage();
|
81
|
+
const location = useLocation();
|
82
|
+
const navItems = useMemo(() => formatLinks(links, location), [location, links]);
|
83
|
+
const isGroupedMode = navItems.some(item => !!item.children?.length);
|
84
|
+
// 一级菜单数量 > 8 或都分组模式, 都启用 dense 布局
|
85
|
+
const _dense = dense === 'auto' ? navItems.length >= 8 || isGroupedMode : dense;
|
86
|
+
const classes = clsx('dashboard', {
|
87
|
+
'dashboard-dense': _dense
|
88
|
+
}, rest.className);
|
89
|
+
const defaultHomeLink = content => /*#__PURE__*/_jsx(Link, {
|
90
|
+
to: window.blocklet?.prefix || '/',
|
91
|
+
children: content
|
92
|
+
});
|
93
|
+
const _headerProps = {
|
94
|
+
homeLink: defaultHomeLink,
|
95
|
+
...headerProps
|
96
|
+
};
|
97
|
+
return /*#__PURE__*/_jsxs(Wrapper, {
|
98
|
+
...rest,
|
99
|
+
className: classes,
|
100
|
+
children: [/*#__PURE__*/_jsx(Helmet, {
|
101
|
+
title: title
|
102
|
+
}, title), headerVisible && /*#__PURE__*/_jsx(StyledUxHeader, {
|
103
|
+
..._headerProps,
|
104
|
+
className: "dashboard-header",
|
105
|
+
children: links?.length ? ({
|
106
|
+
isMobile,
|
107
|
+
closeMenu
|
108
|
+
}) => {
|
109
|
+
if (isMobile) {
|
110
|
+
return /*#__PURE__*/_jsx(NavMenuWrapper, {
|
111
|
+
mode: "inline",
|
112
|
+
items: navItems,
|
113
|
+
closeMenu: closeMenu,
|
114
|
+
bgColor: "transparent",
|
115
|
+
activeTextColor: theme.palette.primary.main
|
116
|
+
});
|
117
|
+
}
|
118
|
+
return null;
|
119
|
+
} : null
|
120
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
121
|
+
display: "flex",
|
122
|
+
className: "dashboard-body",
|
123
|
+
children: [/*#__PURE__*/_jsx(Hidden, {
|
124
|
+
mdDown: true,
|
125
|
+
children: !!links?.length && sidebarVisible && /*#__PURE__*/_jsx(Box, {
|
126
|
+
className: "dashboard-sidebar",
|
127
|
+
children: /*#__PURE__*/_jsx(Sidebar, {
|
128
|
+
links: links,
|
129
|
+
dense: _dense
|
130
|
+
})
|
131
|
+
})
|
132
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
133
|
+
className: "dashboard-main",
|
134
|
+
children: [showToggleButton && /*#__PURE__*/_jsx(Box, {
|
135
|
+
sx: {
|
136
|
+
position: 'absolute',
|
137
|
+
top: 4,
|
138
|
+
right: 4,
|
139
|
+
display: 'flex',
|
140
|
+
justifyContent: 'center',
|
141
|
+
alignItems: 'center',
|
142
|
+
width: 24,
|
143
|
+
height: 24,
|
144
|
+
color: '#fff',
|
145
|
+
bgcolor: 'rgba(158,158,158, 0.6);',
|
146
|
+
borderRadius: 1,
|
147
|
+
cursor: 'pointer'
|
148
|
+
},
|
149
|
+
onClick: toggleFullPage,
|
150
|
+
children: inFullPage ? /*#__PURE__*/_jsx(CloseFullscreenIcon, {
|
151
|
+
style: {
|
152
|
+
fontSize: 18
|
153
|
+
}
|
154
|
+
}) : /*#__PURE__*/_jsx(OpenInFullIcon, {
|
155
|
+
style: {
|
156
|
+
fontSize: 18
|
157
|
+
}
|
158
|
+
})
|
159
|
+
}), /*#__PURE__*/_jsx(Container, {
|
160
|
+
className: "dashboard-content",
|
161
|
+
...(fullWidth && {
|
162
|
+
maxWidth: false
|
163
|
+
}),
|
164
|
+
children: children
|
165
|
+
}), footerVisible && /*#__PURE__*/_jsx(Footer, {
|
166
|
+
...footerProps
|
167
|
+
})]
|
168
|
+
})]
|
169
|
+
})]
|
170
|
+
});
|
171
|
+
}
|
172
|
+
Dashboard.propTypes = {
|
173
|
+
children: PropTypes.any.isRequired,
|
174
|
+
title: PropTypes.string,
|
175
|
+
// 支持分组, links item 如果是数组, 则视为一个 group
|
176
|
+
links: PropTypes.array.isRequired,
|
177
|
+
headerProps: PropTypes.object,
|
178
|
+
fullWidth: PropTypes.bool,
|
179
|
+
sidebarWidth: PropTypes.number,
|
180
|
+
// sidenav 稠密一些的布局, 纵向空间占用较少, 默认为 auto, 当 links 个数 > 8 时自动启用
|
181
|
+
dense: PropTypes.oneOf([true, false, 'auto'])
|
182
|
+
};
|
183
|
+
Dashboard.defaultProps = {
|
184
|
+
title: 'Home',
|
185
|
+
headerProps: {},
|
186
|
+
fullWidth: false,
|
187
|
+
sidebarWidth: 120,
|
188
|
+
dense: 'auto'
|
189
|
+
};
|
190
|
+
const Wrapper = styled('div', {
|
191
|
+
shouldForwardProp: prop => prop !== 'sidebarWidth'
|
192
|
+
})`
|
193
|
+
&.dashboard {
|
194
|
+
display: flex;
|
195
|
+
flex-direction: column;
|
196
|
+
height: 100vh;
|
197
|
+
}
|
198
|
+
.dashboard-body {
|
199
|
+
overflow: hidden;
|
200
|
+
flex: 1;
|
201
|
+
}
|
202
|
+
.dashboard-sidebar {
|
203
|
+
box-sizing: border-box;
|
204
|
+
flex: 0 0 auto;
|
205
|
+
width: ${props => props.sidebarWidth}px;
|
206
|
+
overflow: hidden;
|
207
|
+
&:hover {
|
208
|
+
overflow-y: auto;
|
209
|
+
}
|
210
|
+
}
|
211
|
+
.dashboard-main {
|
212
|
+
display: flex;
|
213
|
+
flex-direction: column;
|
214
|
+
overflow: auto;
|
215
|
+
flex: 1;
|
216
|
+
position: relative;
|
217
|
+
}
|
218
|
+
.dashboard-content {
|
219
|
+
flex: 1;
|
220
|
+
}
|
221
|
+
&.dashboard-dense {
|
222
|
+
.dashboard-header {
|
223
|
+
border-bottom: 1px solid #eee;
|
224
|
+
}
|
225
|
+
.dashboard-sidebar {
|
226
|
+
width: 256px;
|
227
|
+
border-right: 1px solid #eee;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
${props => props.theme.breakpoints.up('md')} {
|
231
|
+
.header-logo {
|
232
|
+
display: flex;
|
233
|
+
justify-content: center;
|
234
|
+
/* logo 与 sidebar 中的 icon 垂直对齐, sidebarWidth - 24 * 2 */
|
235
|
+
width: ${props => props.sidebarWidth - 24 * 2}px;
|
236
|
+
}
|
237
|
+
&.dashboard-dense {
|
238
|
+
.header-logo {
|
239
|
+
/* dense = true 时 logo 与 sidenav icons 不需要对齐 */
|
240
|
+
width: auto;
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
`;
|
245
|
+
const StyledUxHeader = styled(ResponsiveHeader)`
|
246
|
+
.header-container {
|
247
|
+
max-width: 100%;
|
248
|
+
}
|
249
|
+
`;
|
250
|
+
|
251
|
+
// 兼容旧版 dashboard
|
252
|
+
export default function DashboardWrapper({
|
253
|
+
legacy,
|
254
|
+
...rest
|
255
|
+
}) {
|
256
|
+
if (legacy) {
|
257
|
+
return /*#__PURE__*/_jsx(DashboardLegacy, {
|
258
|
+
...rest
|
259
|
+
});
|
260
|
+
}
|
261
|
+
return /*#__PURE__*/_jsx(FullPageProvider, {
|
262
|
+
children: /*#__PURE__*/_jsx(Dashboard, {
|
263
|
+
...rest
|
264
|
+
})
|
265
|
+
});
|
266
|
+
}
|
267
|
+
DashboardWrapper.propTypes = {
|
268
|
+
...Dashboard.propTypes,
|
269
|
+
legacy: PropTypes.bool,
|
270
|
+
footerProps: PropTypes.shape(Footer.propTypes)
|
271
|
+
};
|
272
|
+
DashboardWrapper.defaultProps = {
|
273
|
+
...Dashboard.defaultProps,
|
274
|
+
legacy: true
|
275
|
+
};
|