@arcblock/ux 2.2.2 → 2.3.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.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = DashboardWrapper;
7
7
 
8
8
  var _react = require("react");
9
9
 
@@ -23,6 +23,8 @@ var _Hidden = _interopRequireDefault(require("@mui/material/Hidden"));
23
23
 
24
24
  var _Box = _interopRequireDefault(require("@mui/material/Box"));
25
25
 
26
+ var _clsx = _interopRequireDefault(require("clsx"));
27
+
26
28
  var _dashboardLegacy = _interopRequireDefault(require("../dashboard-legacy"));
27
29
 
28
30
  var _Header = require("../../Header");
@@ -33,10 +35,12 @@ var _Footer = _interopRequireDefault(require("../../Footer"));
33
35
 
34
36
  var _sidebar = _interopRequireDefault(require("./sidebar"));
35
37
 
38
+ var _withExternalLink = require("./with-external-link");
39
+
36
40
  var _jsxRuntime = require("react/jsx-runtime");
37
41
 
38
42
  const _excluded = ["closeMenu"],
39
- _excluded2 = ["children", "title", "headerProps", "links", "fullWidth"],
43
+ _excluded2 = ["children", "title", "headerProps", "links", "fullWidth", "dense"],
40
44
  _excluded3 = ["legacy"];
41
45
 
42
46
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -67,30 +71,49 @@ function NavMenuWrapper(_ref) {
67
71
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavMenu.default, _objectSpread({}, rest));
68
72
  }
69
73
 
74
+ function formatLinks(links, location) {
75
+ return links.map(link => {
76
+ if (Array.isArray(link)) {
77
+ return formatLinks(link, location);
78
+ }
79
+
80
+ return _objectSpread(_objectSpread({}, link), {}, {
81
+ label: /*#__PURE__*/(0, _jsxRuntime.jsx)(_withExternalLink.Link, {
82
+ external: link.external,
83
+ to: link.url,
84
+ children: link.title
85
+ }),
86
+ active: !!(0, _reactRouterDom.matchPath)({
87
+ path: link.url,
88
+ end: false
89
+ }, location.pathname)
90
+ });
91
+ });
92
+ }
93
+
70
94
  function Dashboard(_ref2) {
71
95
  let {
72
96
  children,
73
97
  title,
74
98
  headerProps,
75
99
  links,
76
- fullWidth
100
+ fullWidth,
101
+ dense
77
102
  } = _ref2,
78
103
  rest = _objectWithoutProperties(_ref2, _excluded2);
79
104
 
80
105
  const theme = (0, _styles.useTheme)();
81
106
  const location = (0, _reactRouterDom.useLocation)();
82
- const navItems = (0, _react.useMemo)(() => links.map(link => _objectSpread(_objectSpread({}, link), {}, {
83
- label: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.Link, {
84
- to: link.url,
85
- children: link.title
86
- }),
87
- active: !!(0, _reactRouterDom.matchPath)(location.pathname, {
88
- path: link.url,
89
- exact: false
90
- })
91
- })), [location, links]);
107
+ const navItems = (0, _react.useMemo)(() => formatLinks(links, location), [location, links]);
108
+ const flattendNavItems = navItems.flat();
109
+
110
+ const _dense = dense === 'auto' ? flattendNavItems.length >= 8 : dense;
111
+
112
+ const classes = (0, _clsx.default)('dashboard', {
113
+ 'dashboard-dense': _dense
114
+ }, rest.className);
92
115
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(Wrapper, _objectSpread(_objectSpread({}, rest), {}, {
93
- className: "dashboard ".concat(rest.className),
116
+ className: classes,
94
117
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactHelmet.default, {
95
118
  title: title
96
119
  }, title), /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledUxHeader, _objectSpread(_objectSpread({}, headerProps), {}, {
@@ -104,7 +127,7 @@ function Dashboard(_ref2) {
104
127
  if (isMobile) {
105
128
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(NavMenuWrapper, {
106
129
  mode: "inline",
107
- items: navItems,
130
+ items: flattendNavItems,
108
131
  closeMenu: closeMenu,
109
132
  bgColor: "transparent",
110
133
  activeTextColor: theme.palette.primary.main
@@ -121,7 +144,8 @@ function Dashboard(_ref2) {
121
144
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Box.default, {
122
145
  className: "dashboard-sidebar",
123
146
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sidebar.default, {
124
- links: links
147
+ links: links,
148
+ dense: _dense
125
149
  })
126
150
  })
127
151
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Box.default, {
@@ -141,25 +165,31 @@ function Dashboard(_ref2) {
141
165
  Dashboard.propTypes = {
142
166
  children: _propTypes.default.any.isRequired,
143
167
  title: _propTypes.default.string,
168
+ // 支持分组, links item 如果是数组, 则视为一个 group
144
169
  links: _propTypes.default.array.isRequired,
145
170
  headerProps: _propTypes.default.object,
146
- fullWidth: _propTypes.default.bool
171
+ fullWidth: _propTypes.default.bool,
172
+ sidebarWidth: _propTypes.default.number,
173
+ // sidenav 稠密一些的布局, 纵向空间占用较少, 默认为 auto, 当 links 个数 > 8 时自动启用
174
+ dense: _propTypes.default.oneOf([true, false, 'auto'])
147
175
  };
148
176
  Dashboard.defaultProps = {
149
177
  title: 'Home',
150
178
  headerProps: {},
151
- fullWidth: false
179
+ fullWidth: false,
180
+ sidebarWidth: 120,
181
+ dense: 'auto'
152
182
  };
153
183
 
154
184
  const Wrapper = _styledComponents.default.div.withConfig({
155
185
  displayName: "dashboard__Wrapper",
156
186
  componentId: "sc-arvc7q-0"
157
- })(["&.dashboard{display:flex;flex-direction:column;height:100vh;}.dashboard-body{overflow:hidden;flex:1;}.dashboard-sidebar{box-sizing:border-box;flex:0 0 auto;width:104px;&:hover{overflow-y:auto;}}.dashboard-main{display:flex;flex-direction:column;overflow:auto;flex:1;}.dashboard-content{flex:1;}"]);
187
+ })(["&.dashboard{display:flex;flex-direction:column;height:100vh;}.dashboard-body{overflow:hidden;flex:1;}.dashboard-sidebar{box-sizing:border-box;flex:0 0 auto;width:", "px;&:hover{overflow-y:auto;}}.dashboard-main{display:flex;flex-direction:column;overflow:auto;flex:1;}.dashboard-content{flex:1;}&.dashboard-dense{.dashboard-sidebar{width:auto;}}", "{.header-logo{display:flex;justify-content:center;width:", "px;}&.dashboard-dense{.header-logo{width:auto;}}}"], props => props.sidebarWidth, props => props.theme.breakpoints.up('md'), props => props.sidebarWidth - 24 * 2);
158
188
 
159
189
  const StyledUxHeader = (0, _styledComponents.default)(_Header.ResponsiveHeader).withConfig({
160
190
  displayName: "dashboard__StyledUxHeader",
161
191
  componentId: "sc-arvc7q-1"
162
- })([".header-container{max-width:100%;}", "{.header-logo{display:flex;justify-content:center;width:56px;}}"], props => props.$theme.breakpoints.up('md')); // 兼容旧版 dashboard
192
+ })([".header-container{max-width:100%;}"]); // 兼容旧版 dashboard
163
193
 
164
194
  function DashboardWrapper(_ref4) {
165
195
  let {
@@ -179,8 +209,4 @@ DashboardWrapper.propTypes = _objectSpread(_objectSpread({}, Dashboard.propTypes
179
209
  });
180
210
  DashboardWrapper.defaultProps = _objectSpread(_objectSpread({}, Dashboard.defaultProps), {}, {
181
211
  legacy: true
182
- });
183
-
184
- var _default = (0, _reactRouterDom.withRouter)(DashboardWrapper);
185
-
186
- exports.default = _default;
212
+ });
@@ -9,15 +9,17 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
9
9
 
10
10
  var _styledComponents = _interopRequireDefault(require("styled-components"));
11
11
 
12
- var _reactRouterDom = require("react-router-dom");
13
-
14
12
  var _Typography = _interopRequireDefault(require("@mui/material/Typography"));
15
13
 
16
14
  var _colors = require("@mui/material/colors");
17
15
 
16
+ var _clsx = _interopRequireDefault(require("clsx"));
17
+
18
+ var _withExternalLink = require("./with-external-link");
19
+
18
20
  var _jsxRuntime = require("react/jsx-runtime");
19
21
 
20
- const _excluded = ["links", "addons"];
22
+ const _excluded = ["links", "addons", "dense"];
21
23
 
22
24
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
25
 
@@ -31,38 +33,71 @@ function _objectWithoutProperties(source, excluded) { if (source == null) return
31
33
 
32
34
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
33
35
 
34
- function Sidebar(_ref) {
36
+ // 渲染 links, 为 group links 添加分隔线, 返回展平后的、包含分隔线元素的 links 数组
37
+ function renderLinks(links) {
38
+ const result = [];
39
+ links.forEach((item, index) => {
40
+ const prev = links[index - 1]; // 当当前元素和前一个元素仅有一个为 group (array) 时, 在当前元素前面位置添加一个分隔线元素
41
+
42
+ if (index > 0 && (Array.isArray(prev) && !Array.isArray(item) || Array.isArray(item) && !Array.isArray(prev))) {
43
+ result.push( /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
44
+ className: "layout-sidebar-divider"
45
+ }, "divider-after-".concat(index)));
46
+ }
47
+
48
+ result.push(renderLinksItem(item));
49
+ });
50
+ return result.flat();
51
+ }
52
+
53
+ function renderLinksItem(item) {
54
+ if (Array.isArray(item)) {
55
+ return item.map(renderLinksItem);
56
+ }
57
+
58
+ const {
59
+ url,
60
+ icon,
61
+ title,
62
+ showBadge,
63
+ external
64
+ } = item;
65
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
66
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_withExternalLink.NavLink, {
67
+ to: url,
68
+ external: external,
69
+ className: _ref => {
70
+ let {
71
+ isActive
72
+ } = _ref;
73
+ return isActive ? 'layout-sidebar-link layout-sidebar-link--active' : 'layout-sidebar-link';
74
+ },
75
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
76
+ className: "layout-sidebar-icon ".concat(showBadge ? 'layout-sidebar-badge' : ''),
77
+ children: icon
78
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Typography.default, {
79
+ component: "span",
80
+ className: "layout-sidebar-link-text",
81
+ children: title
82
+ })]
83
+ })
84
+ }, url);
85
+ }
86
+
87
+ function Sidebar(_ref2) {
35
88
  let {
36
89
  links,
37
- addons
38
- } = _ref,
39
- rest = _objectWithoutProperties(_ref, _excluded);
90
+ addons,
91
+ dense
92
+ } = _ref2,
93
+ rest = _objectWithoutProperties(_ref2, _excluded);
40
94
 
41
95
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(Root, _objectSpread(_objectSpread({}, rest), {}, {
96
+ className: (0, _clsx.default)({
97
+ 'layout-sidebar-dense': dense
98
+ }),
42
99
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("ul", {
43
- children: links.map(_ref2 => {
44
- let {
45
- url,
46
- icon,
47
- title,
48
- showBadge
49
- } = _ref2;
50
- return /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
51
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactRouterDom.NavLink, {
52
- to: url,
53
- className: "layout-sidebar-link",
54
- activeClassName: "layout-sidebar-link--active",
55
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
56
- className: "layout-sidebar-icon ".concat(showBadge ? 'layout-sidebar-badge' : ''),
57
- children: icon
58
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Typography.default, {
59
- component: "span",
60
- className: "layout-sidebar-link-text",
61
- children: title
62
- })]
63
- })
64
- }, url);
65
- })
100
+ children: renderLinks(links)
66
101
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
67
102
  style: {
68
103
  flex: 1
@@ -73,17 +108,19 @@ function Sidebar(_ref) {
73
108
 
74
109
  Sidebar.propTypes = {
75
110
  links: _propTypes.default.array.isRequired,
76
- addons: _propTypes.default.any
111
+ addons: _propTypes.default.any,
112
+ dense: _propTypes.default.bool
77
113
  };
78
114
  Sidebar.defaultProps = {
79
- addons: null
115
+ addons: null,
116
+ dense: false
80
117
  };
81
118
  const gradient = 'linear-gradient(32deg, rgba(144, 255, 230, 0.1), rgba(144, 255, 230, 0))';
82
119
 
83
120
  const Root = _styledComponents.default.div.withConfig({
84
121
  displayName: "sidebar__Root",
85
122
  componentId: "sc-gaosgy-0"
86
- })(["display:flex;flex-direction:column;ul{list-style:none;margin:0;padding:0;}.layout-sidebar-link{display:flex;flex-direction:column;align-items:center;padding:22px 24px;color:", ";text-decoration:none;&:hover,&.layout-sidebar-link--active{color:", ";background:", ";border-left-color:", ";}}.layout-sidebar-icon{display:inline-block;width:32px;height:32px;> img,> svg{width:32px;height:32px;}}.layout-sidebar-badge{position:relative;&:after{content:'';position:absolute;width:10px;height:10px;border-radius:10px;background-color:#fe4e44;right:-2px;top:0;box-shadow:0px 1px 4px rgba(0,0,0,0.3),0px 0px 20px rgba(0,0,0,0.1);}}.layout-sidebar-link-text{margin-top:8px;font-size:12px;font-weight:500;text-align:center;text-transform:capitalize;letter-spacing:normal;}"], props => props.theme.palette.text.secondary, props => props.theme.palette.primary.main, gradient, _colors.teal.A700);
123
+ })(["display:flex;flex-direction:column;ul{list-style:none;margin:0;padding:0;}.layout-sidebar-link{display:flex;flex-direction:column;align-items:center;padding:22px 24px;color:", ";text-decoration:none;&:hover,&.layout-sidebar-link--active{color:", ";background:", ";border-left-color:", ";}}.layout-sidebar-icon{display:inline-block;width:32px;height:32px;> img,> svg{width:32px;height:32px;}}.layout-sidebar-badge{position:relative;&:after{content:'';position:absolute;width:10px;height:10px;border-radius:10px;background-color:#fe4e44;right:-2px;top:0;box-shadow:0px 1px 4px rgba(0,0,0,0.3),0px 0px 20px rgba(0,0,0,0.1);}}.layout-sidebar-link-text{margin-top:8px;font-size:12px;font-weight:500;text-align:center;text-transform:capitalize;letter-spacing:normal;}.layout-sidebar-divider{display:flex;justify-content:center;align-items:center;&::after{content:'';display:inline-block;width:80px;max-width:100%;border-bottom:1px solid #ddd;}}&.layout-sidebar-dense{box-sizing:border-box;height:100%;padding-top:28px;border-right:1px solid #ddd;.layout-sidebar-link{flex-direction:row;align-items:center;padding:10px 36px 10px 24px;}.layout-sidebar-icon{display:inline-block;width:20px;height:20px;margin-right:12px;> img,> svg{width:20px;height:20px;}}.layout-sidebar-badge{&:after{width:6px;height:6px;border-radius:6px;right:-2px;top:0;}}.layout-sidebar-link-text{margin-top:0;font-size:14px;}.layout-sidebar-divider{&::after{content:'';display:inline-block;width:100%;margin:12px 0;}}}"], props => props.theme.palette.text.secondary, props => props.theme.palette.primary.main, gradient, _colors.teal.A700);
87
124
 
88
125
  var _default = Sidebar;
89
126
  exports.default = _default;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NavLink = exports.Link = void 0;
7
+ exports.withExternalLink = withExternalLink;
8
+
9
+ var _reactRouterDom = require("react-router-dom");
10
+
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+
13
+ const _excluded = ["to", "external"];
14
+
15
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
16
+
17
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
18
+
19
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20
+
21
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
22
+
23
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
24
+
25
+ // 包裹 router Link/NavLink 组件, 支持 external link (external 为 true 时通过 click 事件跳转)
26
+ function withExternalLink(RouterLinkComponent) {
27
+ // eslint-disable-next-line react/prop-types
28
+ return function WithExternalLink(_ref) {
29
+ let {
30
+ to,
31
+ external
32
+ } = _ref,
33
+ rest = _objectWithoutProperties(_ref, _excluded);
34
+
35
+ const handleClick = e => {
36
+ e.preventDefault();
37
+ window.location.href = to;
38
+ };
39
+
40
+ if (external) {
41
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(RouterLinkComponent, _objectSpread({
42
+ onClick: handleClick,
43
+ to: to
44
+ }, rest));
45
+ }
46
+
47
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(RouterLinkComponent, _objectSpread({
48
+ to: to
49
+ }, rest));
50
+ };
51
+ }
52
+
53
+ const Link = withExternalLink(_reactRouterDom.Link);
54
+ exports.Link = Link;
55
+ const NavLink = withExternalLink(_reactRouterDom.NavLink);
56
+ exports.NavLink = NavLink;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = Sidebar;
7
7
 
8
8
  var _react = require("react");
9
9
 
@@ -27,7 +27,7 @@ var _Logo = _interopRequireDefault(require("../../Logo"));
27
27
 
28
28
  var _jsxRuntime = require("react/jsx-runtime");
29
29
 
30
- const _excluded = ["location", "images", "links", "prefix", "addons", "logo"];
30
+ const _excluded = ["images", "links", "prefix", "addons", "logo"];
31
31
 
32
32
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
33
33
 
@@ -43,7 +43,6 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
43
43
 
44
44
  function Sidebar(_ref) {
45
45
  let {
46
- location,
47
46
  images,
48
47
  links,
49
48
  prefix,
@@ -53,6 +52,7 @@ function Sidebar(_ref) {
53
52
  rest = _objectWithoutProperties(_ref, _excluded);
54
53
 
55
54
  const theme = (0, _styles.useTheme)();
55
+ const location = (0, _reactRouterDom.useLocation)();
56
56
 
57
57
  const isSelected = (url, name) => {
58
58
  const pattern = new RegExp("/".concat(name));
@@ -100,7 +100,6 @@ function Sidebar(_ref) {
100
100
  }
101
101
 
102
102
  Sidebar.propTypes = {
103
- location: _propTypes.default.object.isRequired,
104
103
  images: _propTypes.default.object.isRequired,
105
104
  links: _propTypes.default.array.isRequired,
106
105
  prefix: _propTypes.default.string,
@@ -120,8 +119,4 @@ const gradient = 'linear-gradient(32deg, rgba(144, 255, 230, 0.1), rgba(144, 255
120
119
  const MenuItem = (0, _styledComponents.default)(_Button.default).withConfig({
121
120
  displayName: "sidebar__MenuItem",
122
121
  componentId: "sc-gtwxx4-1"
123
- })(["&&{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;transition:all 200ms ease-in-out;background:", ";padding:24px 0;border-left:2px solid ", ";border-radius:0;&:hover{background:", ";border-left-color:", ";}.menu-title{margin-top:8px;font-size:12px;font-weight:500;text-align:center;text-transform:capitalize;letter-spacing:normal;width:80%;color:", ";}}"], props => props.selected ? gradient : '', props => props.selected ? _colors.teal.A700 : 'transparent', gradient, _colors.teal.A700, props => props.theme.palette.text.primary);
124
-
125
- var _default = (0, _reactRouterDom.withRouter)(Sidebar);
126
-
127
- exports.default = _default;
122
+ })(["&&{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;transition:all 200ms ease-in-out;background:", ";padding:24px 0;border-left:2px solid ", ";border-radius:0;&:hover{background:", ";border-left-color:", ";}.menu-title{margin-top:8px;font-size:12px;font-weight:500;text-align:center;text-transform:capitalize;letter-spacing:normal;width:80%;color:", ";}}"], props => props.selected ? gradient : '', props => props.selected ? _colors.teal.A700 : 'transparent', gradient, _colors.teal.A700, props => props.theme.palette.text.primary);
@@ -5,14 +5,14 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
- var _react = require("react");
9
-
10
- var _propTypes = _interopRequireDefault(require("prop-types"));
11
-
12
8
  var _reactGa = _interopRequireDefault(require("react-ga"));
13
9
 
14
10
  var Sentry = _interopRequireWildcard(require("@sentry/browser"));
15
11
 
12
+ var _useDeepCompareEffect = _interopRequireDefault(require("react-use/lib/useDeepCompareEffect"));
13
+
14
+ var _useMount = _interopRequireDefault(require("react-use/lib/useMount"));
15
+
16
16
  var _reactRouterDom = require("react-router-dom");
17
17
 
18
18
  var _error_boundary = _interopRequireDefault(require("./error_boundary"));
@@ -65,38 +65,23 @@ var _default = function _default(WrappedComponent) {
65
65
  }
66
66
  };
67
67
 
68
- class TrackedComponent extends _react.Component {
69
- componentDidMount() {
70
- const page = this.props.location.pathname;
71
- trackPage(page);
72
- }
73
-
74
- componentWillReceiveProps(nextProps) {
75
- const currentPage = this.props.location.pathname;
76
- const nextPage = nextProps.location.pathname;
77
-
78
- if (currentPage !== nextPage) {
79
- trackPage(nextPage);
80
- }
81
- }
82
-
83
- render() {
84
- if (process.env.NODE_ENV === 'production') {
85
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_error_boundary.default, {
86
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedComponent, _objectSpread({}, this.props))
87
- });
88
- }
89
-
90
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedComponent, _objectSpread({}, this.props));
68
+ return function TrackedComponent(props) {
69
+ const location = (0, _reactRouterDom.useLocation)();
70
+ (0, _useMount.default)(() => {
71
+ trackPage(location.pathname);
72
+ });
73
+ (0, _useDeepCompareEffect.default)(() => {
74
+ trackPage(location.pathname);
75
+ }, [location.pathname]);
76
+
77
+ if (process.env.NODE_ENV === 'production') {
78
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_error_boundary.default, {
79
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedComponent, _objectSpread({}, props))
80
+ });
91
81
  }
92
82
 
93
- }
94
-
95
- _defineProperty(TrackedComponent, "propTypes", {
96
- location: _propTypes.default.object.isRequired
97
- });
98
-
99
- return (0, _reactRouterDom.withRouter)(TrackedComponent);
83
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedComponent, _objectSpread({}, props));
84
+ };
100
85
  };
101
86
 
102
87
  exports.default = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -48,20 +48,20 @@
48
48
  "react": ">=18.1.0",
49
49
  "react-ga": "^2.7.0"
50
50
  },
51
- "gitHead": "3329ac7ede9cdb8b65c925bb9359ee0e23ad1aa6",
51
+ "gitHead": "1d17ca422c95117bbf74c199b21692e478aa5003",
52
52
  "dependencies": {
53
- "@arcblock/icons": "^2.2.2",
54
- "@arcblock/react-hooks": "^2.2.2",
53
+ "@arcblock/icons": "^2.3.0",
54
+ "@arcblock/react-hooks": "^2.3.0",
55
55
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
56
56
  "@emotion/react": "^11.10.0",
57
57
  "@emotion/styled": "^11.10.0",
58
- "@fontsource/lato": "^4.5.8",
58
+ "@fontsource/lato": "^4.5.9",
59
59
  "@mui/icons-material": "^5.8.4",
60
60
  "@mui/material": "^5.9.3",
61
61
  "@solana/qr-code-styling": "^1.6.0-beta.0",
62
62
  "axios": "^0.21.4",
63
63
  "base64-url": "^2.3.3",
64
- "copy-to-clipboard": "^3.3.1",
64
+ "copy-to-clipboard": "^3.3.2",
65
65
  "core-js": "^3.24.1",
66
66
  "d3-geo": "^1.12.1",
67
67
  "dayjs": "^1.11.4",
@@ -70,7 +70,7 @@
70
70
  "is-svg": "^4.3.2",
71
71
  "js-cookie": "^2.2.1",
72
72
  "lodash": "^4.17.21",
73
- "mdi-material-ui": "^7.4.0",
73
+ "mdi-material-ui": "^7.5.0",
74
74
  "mui-datatables": "^4.2.2",
75
75
  "notistack": "^2.0.5",
76
76
  "react-cookie-consent": "^6.4.1",
@@ -79,7 +79,7 @@
79
79
  "react-intersection-observer": "^8.34.0",
80
80
  "react-lottie-player": "^1.4.3",
81
81
  "react-player": "^1.15.3",
82
- "react-router-dom": "^5.3.3",
82
+ "react-router-dom": "^6.3.0",
83
83
  "react-shadow": "^19.0.3",
84
84
  "react-use": "^17.4.0",
85
85
  "rebound": "^0.1.0",
@@ -1,17 +1,19 @@
1
1
  import { useEffect, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { withRouter, Link, useLocation, matchPath } from 'react-router-dom';
3
+ import { useLocation, matchPath } from 'react-router-dom';
4
4
  import Helmet from 'react-helmet';
5
5
  import styled from 'styled-components';
6
6
  import { useTheme } from '@mui/material/styles';
7
7
  import Container from '@mui/material/Container';
8
8
  import Hidden from '@mui/material/Hidden';
9
9
  import Box from '@mui/material/Box';
10
+ import clsx from 'clsx';
10
11
  import DashboardLegacy from '../dashboard-legacy';
11
12
  import { ResponsiveHeader } from '../../Header';
12
13
  import NavMenu from '../../NavMenu';
13
14
  import Footer from '../../Footer';
14
15
  import Sidebar from './sidebar';
16
+ import { Link } from './with-external-link';
15
17
 
16
18
  // 监听 location 变化并关闭 header 中的 menu (确保 drawer - disablePortal:false, keepMounted:true)
17
19
  // 直接监听 menu 中 link 点击来触发 closeMenu 会有问题 (Suspense & lazy)
@@ -25,21 +27,33 @@ function NavMenuWrapper({ closeMenu, ...rest }) {
25
27
  return <NavMenu {...rest} />;
26
28
  }
27
29
 
28
- function Dashboard({ children, title, headerProps, links, fullWidth, ...rest }) {
30
+ function formatLinks(links, location) {
31
+ return links.map((link) => {
32
+ if (Array.isArray(link)) {
33
+ return formatLinks(link, location);
34
+ }
35
+ return {
36
+ ...link,
37
+ label: (
38
+ <Link external={link.external} to={link.url}>
39
+ {link.title}
40
+ </Link>
41
+ ),
42
+ active: !!matchPath({ path: link.url, end: false }, location.pathname),
43
+ };
44
+ });
45
+ }
46
+
47
+ function Dashboard({ children, title, headerProps, links, fullWidth, dense, ...rest }) {
29
48
  const theme = useTheme();
30
49
  const location = useLocation();
31
- const navItems = useMemo(
32
- () =>
33
- links.map((link) => ({
34
- ...link,
35
- label: <Link to={link.url}>{link.title}</Link>,
36
- active: !!matchPath(location.pathname, { path: link.url, exact: false }),
37
- })),
38
- [location, links]
39
- );
50
+ const navItems = useMemo(() => formatLinks(links, location), [location, links]);
51
+ const flattendNavItems = navItems.flat();
52
+ const _dense = dense === 'auto' ? flattendNavItems.length >= 8 : dense;
53
+ const classes = clsx('dashboard', { 'dashboard-dense': _dense }, rest.className);
40
54
 
41
55
  return (
42
- <Wrapper {...rest} className={`dashboard ${rest.className}`}>
56
+ <Wrapper {...rest} className={classes}>
43
57
  <Helmet title={title} key={title} />
44
58
 
45
59
  <StyledUxHeader {...headerProps} $theme={theme}>
@@ -48,7 +62,7 @@ function Dashboard({ children, title, headerProps, links, fullWidth, ...rest })
48
62
  return (
49
63
  <NavMenuWrapper
50
64
  mode="inline"
51
- items={navItems}
65
+ items={flattendNavItems}
52
66
  closeMenu={closeMenu}
53
67
  bgColor="transparent"
54
68
  activeTextColor={theme.palette.primary.main}
@@ -62,7 +76,7 @@ function Dashboard({ children, title, headerProps, links, fullWidth, ...rest })
62
76
  <Box display="flex" className="dashboard-body">
63
77
  <Hidden mdDown>
64
78
  <Box className="dashboard-sidebar">
65
- <Sidebar links={links} />
79
+ <Sidebar links={links} dense={_dense} />
66
80
  </Box>
67
81
  </Hidden>
68
82
  <Box className="dashboard-main">
@@ -79,15 +93,21 @@ function Dashboard({ children, title, headerProps, links, fullWidth, ...rest })
79
93
  Dashboard.propTypes = {
80
94
  children: PropTypes.any.isRequired,
81
95
  title: PropTypes.string,
96
+ // 支持分组, links item 如果是数组, 则视为一个 group
82
97
  links: PropTypes.array.isRequired,
83
98
  headerProps: PropTypes.object,
84
99
  fullWidth: PropTypes.bool,
100
+ sidebarWidth: PropTypes.number,
101
+ // sidenav 稠密一些的布局, 纵向空间占用较少, 默认为 auto, 当 links 个数 > 8 时自动启用
102
+ dense: PropTypes.oneOf([true, false, 'auto']),
85
103
  };
86
104
 
87
105
  Dashboard.defaultProps = {
88
106
  title: 'Home',
89
107
  headerProps: {},
90
108
  fullWidth: false,
109
+ sidebarWidth: 120,
110
+ dense: 'auto',
91
111
  };
92
112
 
93
113
  const Wrapper = styled.div`
@@ -103,7 +123,7 @@ const Wrapper = styled.div`
103
123
  .dashboard-sidebar {
104
124
  box-sizing: border-box;
105
125
  flex: 0 0 auto;
106
- width: 104px;
126
+ width: ${(props) => props.sidebarWidth}px;
107
127
  &:hover {
108
128
  overflow-y: auto;
109
129
  }
@@ -117,24 +137,35 @@ const Wrapper = styled.div`
117
137
  .dashboard-content {
118
138
  flex: 1;
119
139
  }
120
- `;
121
-
122
- const StyledUxHeader = styled(ResponsiveHeader)`
123
- .header-container {
124
- max-width: 100%;
140
+ &.dashboard-dense {
141
+ .dashboard-sidebar {
142
+ width: auto;
143
+ }
125
144
  }
126
- ${(props) => props.$theme.breakpoints.up('md')} {
145
+ ${(props) => props.theme.breakpoints.up('md')} {
127
146
  .header-logo {
128
147
  display: flex;
129
148
  justify-content: center;
130
- /* logo 与 sidebar 中的 icon 垂直对齐, 104 - 24 * 2 = 56 */
131
- width: 56px;
149
+ /* logo 与 sidebar 中的 icon 垂直对齐, sidebarWidth - 24 * 2 */
150
+ width: ${(props) => props.sidebarWidth - 24 * 2}px;
151
+ }
152
+ &.dashboard-dense {
153
+ .header-logo {
154
+ /* dense = true 时 logo 与 sidenav icons 不需要对齐 */
155
+ width: auto;
156
+ }
132
157
  }
133
158
  }
134
159
  `;
135
160
 
161
+ const StyledUxHeader = styled(ResponsiveHeader)`
162
+ .header-container {
163
+ max-width: 100%;
164
+ }
165
+ `;
166
+
136
167
  // 兼容旧版 dashboard
137
- function DashboardWrapper({ legacy, ...rest }) {
168
+ export default function DashboardWrapper({ legacy, ...rest }) {
138
169
  if (legacy) {
139
170
  return <DashboardLegacy {...rest} />;
140
171
  }
@@ -150,5 +181,3 @@ DashboardWrapper.defaultProps = {
150
181
  ...Dashboard.defaultProps,
151
182
  legacy: true,
152
183
  };
153
-
154
- export default withRouter(DashboardWrapper);
@@ -1,27 +1,52 @@
1
+ /* eslint-disable react/no-array-index-key */
1
2
  import PropTypes from 'prop-types';
2
3
  import styled from 'styled-components';
3
4
 
4
- import { NavLink } from 'react-router-dom';
5
5
  import Typography from '@mui/material/Typography';
6
6
  import { teal } from '@mui/material/colors';
7
+ import clsx from 'clsx';
8
+ import { NavLink } from './with-external-link';
7
9
 
8
- function Sidebar({ links, addons, ...rest }) {
10
+ // 渲染 links, group links 添加分隔线, 返回展平后的、包含分隔线元素的 links 数组
11
+ function renderLinks(links) {
12
+ const result = [];
13
+ links.forEach((item, index) => {
14
+ const prev = links[index - 1];
15
+ // 当当前元素和前一个元素仅有一个为 group (array) 时, 在当前元素前面位置添加一个分隔线元素
16
+ if (index > 0 && ((Array.isArray(prev) && !Array.isArray(item)) || (Array.isArray(item) && !Array.isArray(prev)))) {
17
+ result.push(<li className="layout-sidebar-divider" key={`divider-after-${index}`} />);
18
+ }
19
+ result.push(renderLinksItem(item));
20
+ });
21
+ return result.flat();
22
+ }
23
+
24
+ function renderLinksItem(item) {
25
+ if (Array.isArray(item)) {
26
+ return item.map(renderLinksItem);
27
+ }
28
+ const { url, icon, title, showBadge, external } = item;
29
+ return (
30
+ <li key={url}>
31
+ <NavLink
32
+ to={url}
33
+ external={external}
34
+ className={({ isActive }) =>
35
+ isActive ? 'layout-sidebar-link layout-sidebar-link--active' : 'layout-sidebar-link'
36
+ }>
37
+ <span className={`layout-sidebar-icon ${showBadge ? 'layout-sidebar-badge' : ''}`}>{icon}</span>
38
+ <Typography component="span" className="layout-sidebar-link-text">
39
+ {title}
40
+ </Typography>
41
+ </NavLink>
42
+ </li>
43
+ );
44
+ }
45
+
46
+ function Sidebar({ links, addons, dense, ...rest }) {
9
47
  return (
10
- <Root {...rest}>
11
- <ul>
12
- {links.map(({ url, icon, title, showBadge }) => {
13
- return (
14
- <li key={url}>
15
- <NavLink to={url} className="layout-sidebar-link" activeClassName="layout-sidebar-link--active">
16
- <span className={`layout-sidebar-icon ${showBadge ? 'layout-sidebar-badge' : ''}`}>{icon}</span>
17
- <Typography component="span" className="layout-sidebar-link-text">
18
- {title}
19
- </Typography>
20
- </NavLink>
21
- </li>
22
- );
23
- })}
24
- </ul>
48
+ <Root {...rest} className={clsx({ 'layout-sidebar-dense': dense })}>
49
+ <ul>{renderLinks(links)}</ul>
25
50
  <div style={{ flex: 1 }} />
26
51
  {addons}
27
52
  </Root>
@@ -31,10 +56,12 @@ function Sidebar({ links, addons, ...rest }) {
31
56
  Sidebar.propTypes = {
32
57
  links: PropTypes.array.isRequired,
33
58
  addons: PropTypes.any,
59
+ dense: PropTypes.bool,
34
60
  };
35
61
 
36
62
  Sidebar.defaultProps = {
37
63
  addons: null,
64
+ dense: false,
38
65
  };
39
66
 
40
67
  const gradient = 'linear-gradient(32deg, rgba(144, 255, 230, 0.1), rgba(144, 255, 230, 0))';
@@ -94,6 +121,61 @@ const Root = styled.div`
94
121
  text-transform: capitalize;
95
122
  letter-spacing: normal;
96
123
  }
124
+ .layout-sidebar-divider {
125
+ display: flex;
126
+ justify-content: center;
127
+ align-items: center;
128
+ &::after {
129
+ content: '';
130
+ display: inline-block;
131
+ width: 80px;
132
+ max-width: 100%;
133
+ border-bottom: 1px solid #ddd;
134
+ }
135
+ }
136
+ &.layout-sidebar-dense {
137
+ box-sizing: border-box;
138
+ height: 100%;
139
+ padding-top: 28px;
140
+ border-right: 1px solid #ddd;
141
+ .layout-sidebar-link {
142
+ flex-direction: row;
143
+ align-items: center;
144
+ padding: 10px 36px 10px 24px;
145
+ }
146
+ .layout-sidebar-icon {
147
+ display: inline-block;
148
+ width: 20px;
149
+ height: 20px;
150
+ margin-right: 12px;
151
+ > img,
152
+ > svg {
153
+ width: 20px;
154
+ height: 20px;
155
+ }
156
+ }
157
+ .layout-sidebar-badge {
158
+ &:after {
159
+ width: 6px;
160
+ height: 6px;
161
+ border-radius: 6px;
162
+ right: -2px;
163
+ top: 0;
164
+ }
165
+ }
166
+ .layout-sidebar-link-text {
167
+ margin-top: 0;
168
+ font-size: 14px;
169
+ }
170
+ .layout-sidebar-divider {
171
+ &::after {
172
+ content: '';
173
+ display: inline-block;
174
+ width: 100%;
175
+ margin: 12px 0;
176
+ }
177
+ }
178
+ }
97
179
  `;
98
180
 
99
181
  export default Sidebar;
@@ -0,0 +1,20 @@
1
+ import { Link as RouterLink, NavLink as RouterNavLink } from 'react-router-dom';
2
+
3
+ // 包裹 router Link/NavLink 组件, 支持 external link (external 为 true 时通过 click 事件跳转)
4
+ export function withExternalLink(RouterLinkComponent) {
5
+ // eslint-disable-next-line react/prop-types
6
+ return function WithExternalLink({ to, external, ...rest }) {
7
+ const handleClick = (e) => {
8
+ e.preventDefault();
9
+ window.location.href = to;
10
+ };
11
+ if (external) {
12
+ return <RouterLinkComponent onClick={handleClick} to={to} {...rest} />;
13
+ }
14
+ return <RouterLinkComponent to={to} {...rest} />;
15
+ };
16
+ }
17
+
18
+ export const Link = withExternalLink(RouterLink);
19
+
20
+ export const NavLink = withExternalLink(RouterNavLink);
@@ -2,7 +2,7 @@ import { memo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import styled from 'styled-components';
4
4
 
5
- import { withRouter, Link } from 'react-router-dom';
5
+ import { Link, useLocation } from 'react-router-dom';
6
6
  import { useTheme } from '@mui/material/styles';
7
7
  import Button from '@mui/material/Button';
8
8
  import Typography from '@mui/material/Typography';
@@ -11,8 +11,9 @@ import { teal } from '@mui/material/colors';
11
11
  import ImageIcon from '../../Icon/image';
12
12
  import Logo from '../../Logo';
13
13
 
14
- function Sidebar({ location, images, links, prefix, addons, logo, ...rest }) {
14
+ export default function Sidebar({ images, links, prefix, addons, logo, ...rest }) {
15
15
  const theme = useTheme();
16
+ const location = useLocation();
16
17
  const isSelected = (url, name) => {
17
18
  const pattern = new RegExp(`/${name}`);
18
19
  return pattern.test(location.pathname);
@@ -47,7 +48,6 @@ function Sidebar({ location, images, links, prefix, addons, logo, ...rest }) {
47
48
  }
48
49
 
49
50
  Sidebar.propTypes = {
50
- location: PropTypes.object.isRequired,
51
51
  images: PropTypes.object.isRequired,
52
52
  links: PropTypes.array.isRequired,
53
53
  prefix: PropTypes.string,
@@ -116,5 +116,3 @@ const MenuItem = styled(Button)`
116
116
  }
117
117
  }
118
118
  `;
119
-
120
- export default withRouter(Sidebar);
@@ -1,11 +1,10 @@
1
- /* eslint-disable react/no-deprecated */
2
- /* eslint-disable react/static-property-placement */
3
1
  /* eslint-disable import/no-unresolved */
4
- import { Component } from 'react';
5
- import PropTypes from 'prop-types';
6
2
  import ReactGA from 'react-ga';
7
3
  import * as Sentry from '@sentry/browser';
8
- import { withRouter } from 'react-router-dom';
4
+
5
+ import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
6
+ import useMount from 'react-use/lib/useMount';
7
+ import { useLocation } from 'react-router-dom';
9
8
 
10
9
  import ErrorBoundary from './error_boundary';
11
10
 
@@ -34,37 +33,25 @@ export default (WrappedComponent, options = {}) => {
34
33
  }
35
34
  };
36
35
 
37
- class TrackedComponent extends Component {
38
- static propTypes = {
39
- location: PropTypes.object.isRequired,
40
- };
41
-
42
- componentDidMount() {
43
- const page = this.props.location.pathname;
44
- trackPage(page);
45
- }
46
-
47
- componentWillReceiveProps(nextProps) {
48
- const currentPage = this.props.location.pathname;
49
- const nextPage = nextProps.location.pathname;
36
+ return function TrackedComponent(props) {
37
+ const location = useLocation();
50
38
 
51
- if (currentPage !== nextPage) {
52
- trackPage(nextPage);
53
- }
54
- }
39
+ useMount(() => {
40
+ trackPage(location.pathname);
41
+ });
55
42
 
56
- render() {
57
- if (process.env.NODE_ENV === 'production') {
58
- return (
59
- <ErrorBoundary>
60
- <WrappedComponent {...this.props} />
61
- </ErrorBoundary>
62
- );
63
- }
43
+ useDeepCompareEffect(() => {
44
+ trackPage(location.pathname);
45
+ }, [location.pathname]);
64
46
 
65
- return <WrappedComponent {...this.props} />;
47
+ if (process.env.NODE_ENV === 'production') {
48
+ return (
49
+ <ErrorBoundary>
50
+ <WrappedComponent {...props} />
51
+ </ErrorBoundary>
52
+ );
66
53
  }
67
- }
68
54
 
69
- return withRouter(TrackedComponent);
55
+ return <WrappedComponent {...props} />;
56
+ };
70
57
  };