@arcblock/ux 2.8.24 → 2.8.26
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/es/Header/header.js +14 -2
- package/es/NavMenu/nav-menu.js +93 -22
- package/es/NavMenu/style.js +5 -0
- package/lib/Header/header.js +14 -1
- package/lib/NavMenu/nav-menu.js +93 -20
- package/lib/NavMenu/style.js +1 -1
- package/package.json +4 -4
- package/src/Header/header.jsx +20 -4
- package/src/NavMenu/nav-menu.js +130 -14
- package/src/NavMenu/style.js +5 -0
package/es/Header/header.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import Box from '@mui/material/Box';
|
|
3
3
|
import Container from '@mui/material/Container';
|
|
4
|
+
import { useRef, useState, useEffect } from 'react';
|
|
4
5
|
import AutoHidden from './auto-hidden';
|
|
5
6
|
import { styled } from '../Theme';
|
|
6
7
|
|
|
@@ -24,10 +25,21 @@ function Header({
|
|
|
24
25
|
homeLink,
|
|
25
26
|
...rest
|
|
26
27
|
}) {
|
|
28
|
+
const logoRef = useRef();
|
|
29
|
+
const [brandWrapperMinWidth, setBrandWrapperMinWidth] = useState('0px');
|
|
30
|
+
const style = {
|
|
31
|
+
minWidth: brandWrapperMinWidth
|
|
32
|
+
};
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (logoRef.current) {
|
|
35
|
+
setBrandWrapperMinWidth(`${logoRef.current.offsetWidth}px`);
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
27
38
|
const renderBrand = () => {
|
|
28
39
|
const brandContent = /*#__PURE__*/_jsxs(_Fragment, {
|
|
29
40
|
children: [logo && /*#__PURE__*/_jsx("div", {
|
|
30
41
|
className: "header-logo",
|
|
42
|
+
ref: logoRef,
|
|
31
43
|
children: logo
|
|
32
44
|
}), brand && /*#__PURE__*/_jsx(AutoHidden, {
|
|
33
45
|
height: 44,
|
|
@@ -65,6 +77,7 @@ function Header({
|
|
|
65
77
|
className: "header-container",
|
|
66
78
|
children: [prepend, /*#__PURE__*/_jsx("div", {
|
|
67
79
|
className: "header-brand-wrapper",
|
|
80
|
+
style: style,
|
|
68
81
|
children: renderBrand()
|
|
69
82
|
}), /*#__PURE__*/_jsx("div", {
|
|
70
83
|
className: "header-brand-addon",
|
|
@@ -122,8 +135,7 @@ const Root = styled('div')`
|
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
.header-brand-wrapper {
|
|
125
|
-
flex-shrink:
|
|
126
|
-
min-width: 0;
|
|
138
|
+
flex-shrink: 2;
|
|
127
139
|
> a {
|
|
128
140
|
display: flex;
|
|
129
141
|
align-items: center;
|
package/es/NavMenu/nav-menu.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Children, useEffect, createContext, useContext, useMemo, useState, useRef, useCallback } from 'react';
|
|
1
|
+
import { Children, cloneElement, useEffect, createContext, useContext, useMemo, useState, useRef, useCallback, forwardRef } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
+
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
4
5
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
6
|
+
import MenuIcon from '@mui/icons-material/Menu';
|
|
5
7
|
import { HorizontalStyle, InlineStyle } from './style';
|
|
6
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
9
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -70,6 +72,59 @@ function NavMenu({
|
|
|
70
72
|
close
|
|
71
73
|
};
|
|
72
74
|
}, [state, mode, activate, open, close]);
|
|
75
|
+
const [hiddenItemCount, setHiddenItemCount] = useState(0);
|
|
76
|
+
const navMenuRef = useRef();
|
|
77
|
+
const itemRefs = useRef([]);
|
|
78
|
+
const moreIconRef = useRef();
|
|
79
|
+
const isAllItemsHidden = hiddenItemCount === itemRefs.current?.length;
|
|
80
|
+
const icon = isAllItemsHidden ? /*#__PURE__*/_jsx(MenuIcon, {}) : /*#__PURE__*/_jsx(MoreHorizIcon, {});
|
|
81
|
+
const style = isAllItemsHidden ? {
|
|
82
|
+
marginLeft: '0px'
|
|
83
|
+
} : undefined;
|
|
84
|
+
const renderChildrenWithRef = childrenElement => {
|
|
85
|
+
return Children.map(childrenElement, (child, index) => {
|
|
86
|
+
return /*#__PURE__*/cloneElement(child, {
|
|
87
|
+
ref: el => {
|
|
88
|
+
itemRefs.current[index] = el;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const checkItemsFit = () => {
|
|
94
|
+
let totalWidthUsed = 0;
|
|
95
|
+
let newHiddenCount = 0;
|
|
96
|
+
let leftAllHidden = false;
|
|
97
|
+
const containerWidth = navMenuRef.current?.offsetWidth || 0;
|
|
98
|
+
const moreIconWidth = moreIconRef.current ? moreIconRef.current.offsetWidth + parseFloat(window.getComputedStyle(moreIconRef.current).marginLeft) : 0;
|
|
99
|
+
itemRefs.current.forEach((item, index) => {
|
|
100
|
+
if (item) {
|
|
101
|
+
item.style.display = 'flex';
|
|
102
|
+
const marginLeft = index > 0 ? parseFloat(window.getComputedStyle(item).marginLeft) : 0;
|
|
103
|
+
const currentItemWidth = item.offsetWidth + marginLeft;
|
|
104
|
+
if (containerWidth - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
|
|
105
|
+
totalWidthUsed += currentItemWidth;
|
|
106
|
+
} else {
|
|
107
|
+
item.style.display = 'none';
|
|
108
|
+
leftAllHidden = true;
|
|
109
|
+
newHiddenCount++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (newHiddenCount !== hiddenItemCount) {
|
|
114
|
+
setHiddenItemCount(newHiddenCount);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (mode === 'horizontal') {
|
|
119
|
+
checkItemsFit();
|
|
120
|
+
window.addEventListener('resize', checkItemsFit);
|
|
121
|
+
return () => {
|
|
122
|
+
window.removeEventListener('resize', checkItemsFit);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
127
|
+
}, [mode, hiddenItemCount]);
|
|
73
128
|
useEffect(() => {
|
|
74
129
|
// NavMenu#activeId 和 Item#active prop 都可以用来控制激活状态 (一般不会混用这两种方式)
|
|
75
130
|
// 如果未传入 NavMenu#activeId, 应该避免设置一个空值的 activeId 状态 (会与 Item#active 冲突)
|
|
@@ -81,26 +136,32 @@ function NavMenu({
|
|
|
81
136
|
}
|
|
82
137
|
}, [activeId]);
|
|
83
138
|
const classes = clsx('navmenu', `navmenu--${mode}`, rest.className);
|
|
84
|
-
const renderItem = (item, index) => {
|
|
85
|
-
if (item
|
|
139
|
+
const renderItem = (item, index, isTopLevel = false) => {
|
|
140
|
+
if (item?.children) {
|
|
141
|
+
// 对于 Sub 组件,如果它是顶级组件,则包含 ref
|
|
86
142
|
return /*#__PURE__*/_jsx(Sub, {
|
|
87
143
|
id: item.id,
|
|
88
144
|
icon: item.icon,
|
|
89
145
|
label: item.label,
|
|
90
|
-
|
|
146
|
+
ref: isTopLevel ? el => {
|
|
147
|
+
itemRefs.current[index] = el;
|
|
148
|
+
} : undefined,
|
|
149
|
+
children: item.children.map((childItem, childIndex) => renderItem(childItem, childIndex, false))
|
|
91
150
|
}, index);
|
|
92
151
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
|
|
153
|
+
// 顶级 Item 组件总是包含 ref
|
|
154
|
+
return /*#__PURE__*/_jsx(Item, {
|
|
155
|
+
id: item.id,
|
|
156
|
+
icon: item.icon,
|
|
157
|
+
label: item.label,
|
|
158
|
+
active: item.active,
|
|
159
|
+
ref: isTopLevel ? el => {
|
|
160
|
+
itemRefs.current[index] = el;
|
|
161
|
+
} : undefined
|
|
162
|
+
}, index);
|
|
103
163
|
};
|
|
164
|
+
const content = items ? items?.slice(-hiddenItemCount).map((item, index) => renderItem(item, index)) : children?.slice(-hiddenItemCount);
|
|
104
165
|
const StyledRoot = mode === 'inline' ? InlineStyle : HorizontalStyle;
|
|
105
166
|
return /*#__PURE__*/_jsx(NavMenuContext.Provider, {
|
|
106
167
|
value: contextValue,
|
|
@@ -110,9 +171,17 @@ function NavMenu({
|
|
|
110
171
|
$textColor: textColor,
|
|
111
172
|
$activeTextColor: activeTextColor,
|
|
112
173
|
$bgColor: bgColor,
|
|
113
|
-
children: /*#__PURE__*/
|
|
174
|
+
children: /*#__PURE__*/_jsxs("ul", {
|
|
114
175
|
className: "navmenu-root",
|
|
115
|
-
|
|
176
|
+
ref: navMenuRef,
|
|
177
|
+
children: [items ? items.map((item, index) => renderItem(item, index, true)) : renderChildrenWithRef(children), hiddenItemCount > 0 && /*#__PURE__*/_jsx(Sub, {
|
|
178
|
+
expandIcon: false,
|
|
179
|
+
icon: icon,
|
|
180
|
+
label: "",
|
|
181
|
+
ref: moreIconRef,
|
|
182
|
+
style: style,
|
|
183
|
+
children: content
|
|
184
|
+
})]
|
|
116
185
|
})
|
|
117
186
|
})
|
|
118
187
|
});
|
|
@@ -143,14 +212,14 @@ NavMenu.defaultProps = {
|
|
|
143
212
|
/**
|
|
144
213
|
* Item
|
|
145
214
|
*/
|
|
146
|
-
|
|
215
|
+
const Item = /*#__PURE__*/forwardRef(({
|
|
147
216
|
id: _id,
|
|
148
217
|
icon,
|
|
149
218
|
label,
|
|
150
219
|
active,
|
|
151
220
|
onClick,
|
|
152
221
|
...rest
|
|
153
|
-
}) {
|
|
222
|
+
}, ref) => {
|
|
154
223
|
const id = useUniqueId(_id);
|
|
155
224
|
const {
|
|
156
225
|
activeId,
|
|
@@ -176,6 +245,7 @@ function Item({
|
|
|
176
245
|
...rest,
|
|
177
246
|
className: classes,
|
|
178
247
|
onClick: handleClick,
|
|
248
|
+
ref: ref,
|
|
179
249
|
children: [icon && /*#__PURE__*/_jsx("span", {
|
|
180
250
|
className: "navmenu-item-icon",
|
|
181
251
|
children: icon
|
|
@@ -185,7 +255,7 @@ function Item({
|
|
|
185
255
|
})]
|
|
186
256
|
})
|
|
187
257
|
);
|
|
188
|
-
}
|
|
258
|
+
});
|
|
189
259
|
Item.propTypes = {
|
|
190
260
|
id: PropTypes.string,
|
|
191
261
|
icon: PropTypes.element,
|
|
@@ -204,14 +274,14 @@ Item.defaultProps = {
|
|
|
204
274
|
/**
|
|
205
275
|
* Sub
|
|
206
276
|
*/
|
|
207
|
-
|
|
277
|
+
const Sub = /*#__PURE__*/forwardRef(({
|
|
208
278
|
id: _id,
|
|
209
279
|
icon,
|
|
210
280
|
label,
|
|
211
281
|
children,
|
|
212
282
|
expandIcon,
|
|
213
283
|
...rest
|
|
214
|
-
}) {
|
|
284
|
+
}, ref) => {
|
|
215
285
|
const id = useUniqueId(_id);
|
|
216
286
|
const {
|
|
217
287
|
openedIds,
|
|
@@ -239,6 +309,7 @@ function Sub({
|
|
|
239
309
|
...rest,
|
|
240
310
|
className: classes,
|
|
241
311
|
...props,
|
|
312
|
+
ref: ref,
|
|
242
313
|
children: [icon && /*#__PURE__*/_jsx("span", {
|
|
243
314
|
className: "navmenu-sub-icon",
|
|
244
315
|
children: icon
|
|
@@ -259,7 +330,7 @@ function Sub({
|
|
|
259
330
|
})
|
|
260
331
|
})]
|
|
261
332
|
});
|
|
262
|
-
}
|
|
333
|
+
});
|
|
263
334
|
Sub.propTypes = {
|
|
264
335
|
id: PropTypes.string,
|
|
265
336
|
icon: PropTypes.element,
|
package/es/NavMenu/style.js
CHANGED
|
@@ -78,6 +78,9 @@ const NavMenuBase = styled('nav')`
|
|
|
78
78
|
`;
|
|
79
79
|
export const HorizontalStyle = styled(NavMenuBase)`
|
|
80
80
|
padding: 8px 16px;
|
|
81
|
+
min-width: 50px;
|
|
82
|
+
flex-grow: 1;
|
|
83
|
+
|
|
81
84
|
.navmenu-root {
|
|
82
85
|
display: flex;
|
|
83
86
|
align-items: center;
|
|
@@ -86,6 +89,7 @@ export const HorizontalStyle = styled(NavMenuBase)`
|
|
|
86
89
|
.navmenu-root > .navmenu-item,
|
|
87
90
|
.navmenu-root > .navmenu-sub {
|
|
88
91
|
margin-left: 24px;
|
|
92
|
+
white-space: nowrap;
|
|
89
93
|
}
|
|
90
94
|
.navmenu-root > .navmenu-item:first-of-type,
|
|
91
95
|
.navmenu-root > .navmenu-sub:first-of-type {
|
|
@@ -109,6 +113,7 @@ export const HorizontalStyle = styled(NavMenuBase)`
|
|
|
109
113
|
}
|
|
110
114
|
/* 二级 sub menu */
|
|
111
115
|
.navmenu-root > .navmenu-sub {
|
|
116
|
+
white-space: nowrap;
|
|
112
117
|
> .navmenu-sub-container {
|
|
113
118
|
left: 50%;
|
|
114
119
|
transform: translateX(-50%);
|
package/lib/Header/header.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.default = void 0;
|
|
|
7
7
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
8
8
|
var _Box = _interopRequireDefault(require("@mui/material/Box"));
|
|
9
9
|
var _Container = _interopRequireDefault(require("@mui/material/Container"));
|
|
10
|
+
var _react = require("react");
|
|
10
11
|
var _autoHidden = _interopRequireDefault(require("./auto-hidden"));
|
|
11
12
|
var _Theme = require("../Theme");
|
|
12
13
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -39,10 +40,21 @@ function Header(_ref) {
|
|
|
39
40
|
homeLink
|
|
40
41
|
} = _ref,
|
|
41
42
|
rest = _objectWithoutProperties(_ref, _excluded);
|
|
43
|
+
const logoRef = (0, _react.useRef)();
|
|
44
|
+
const [brandWrapperMinWidth, setBrandWrapperMinWidth] = (0, _react.useState)('0px');
|
|
45
|
+
const style = {
|
|
46
|
+
minWidth: brandWrapperMinWidth
|
|
47
|
+
};
|
|
48
|
+
(0, _react.useEffect)(() => {
|
|
49
|
+
if (logoRef.current) {
|
|
50
|
+
setBrandWrapperMinWidth("".concat(logoRef.current.offsetWidth, "px"));
|
|
51
|
+
}
|
|
52
|
+
}, []);
|
|
42
53
|
const renderBrand = () => {
|
|
43
54
|
const brandContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
44
55
|
children: [logo && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
45
56
|
className: "header-logo",
|
|
57
|
+
ref: logoRef,
|
|
46
58
|
children: logo
|
|
47
59
|
}), brand && /*#__PURE__*/(0, _jsxRuntime.jsx)(_autoHidden.default, {
|
|
48
60
|
height: 44,
|
|
@@ -79,6 +91,7 @@ function Header(_ref) {
|
|
|
79
91
|
className: "header-container",
|
|
80
92
|
children: [prepend, /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
81
93
|
className: "header-brand-wrapper",
|
|
94
|
+
style: style,
|
|
82
95
|
children: renderBrand()
|
|
83
96
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
84
97
|
className: "header-brand-addon",
|
|
@@ -124,6 +137,6 @@ Header.defaultProps = {
|
|
|
124
137
|
maxWidth: undefined,
|
|
125
138
|
homeLink: '/'
|
|
126
139
|
};
|
|
127
|
-
const Root = (0, _Theme.styled)('div')(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n position: relative;\n z-index: ", ";\n font-size: 14px;\n background: ", ";\n .header-container {\n display: flex;\n align-items: center;\n height: 64px;\n }\n\n .header-brand-wrapper {\n flex-shrink:
|
|
140
|
+
const Root = (0, _Theme.styled)('div')(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n position: relative;\n z-index: ", ";\n font-size: 14px;\n background: ", ";\n .header-container {\n display: flex;\n align-items: center;\n height: 64px;\n }\n\n .header-brand-wrapper {\n flex-shrink: 2;\n > a {\n display: flex;\n align-items: center;\n height: 100%;\n line-height: 1;\n color: inherit;\n text-decoration: none;\n }\n }\n .header-brand-wrapper .header-logo {\n display: inline-flex;\n position: relative;\n height: 44px;\n margin-right: 16px;\n img,\n svg {\n width: auto;\n height: 100%;\n max-height: 100%;\n }\n }\n .header-brand {\n display: flex;\n flex-direction: column;\n justify-content: center;\n height: 44px;\n margin-right: 16px;\n line-height: 1;\n a {\n color: inherit;\n text-decoration: none;\n }\n .header-brand-title {\n display: flex;\n align-items: center;\n h2 {\n margin: 0;\n font-size: 16px;\n }\n }\n .header-brand-desc {\n margin-top: 4px;\n color: #9397a1;\n }\n }\n .header-brand-addon {\n margin-right: 16px;\n }\n .header-addons {\n display: flex;\n align-items: center;\n }\n ", " {\n .header-brand {\n margin-right: 12px;\n .header-brand-title {\n h2 {\n font-size: 14px;\n }\n }\n }\n .header-brand-addon {\n display: none;\n }\n }\n ", " {\n .header-menu {\n display: inline-block;\n }\n .header-logo {\n height: 32px;\n }\n .header-brand {\n .header-brand-title {\n h2 {\n font-size: 13px;\n }\n }\n .header-brand-desc {\n font-size: 12px;\n }\n }\n }\n"])), props => props.theme.zIndex.appBar, props => props.theme.palette.common.white, props => props.theme.breakpoints.down('lg'), props => props.theme.breakpoints.down('md'));
|
|
128
141
|
var _default = Header;
|
|
129
142
|
exports.default = _default;
|
package/lib/NavMenu/nav-menu.js
CHANGED
|
@@ -7,7 +7,9 @@ exports.default = void 0;
|
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
9
|
var _clsx = _interopRequireDefault(require("clsx"));
|
|
10
|
+
var _MoreHoriz = _interopRequireDefault(require("@mui/icons-material/MoreHoriz"));
|
|
10
11
|
var _ExpandMore = _interopRequireDefault(require("@mui/icons-material/ExpandMore"));
|
|
12
|
+
var _Menu = _interopRequireDefault(require("@mui/icons-material/Menu"));
|
|
11
13
|
var _style = require("./style");
|
|
12
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
15
|
const _excluded = ["items", "mode", "children", "activeId", "textColor", "activeTextColor", "bgColor", "onSelected"],
|
|
@@ -39,7 +41,7 @@ function useUniqueId(id) {
|
|
|
39
41
|
* NavMenu, 导航组件, 可用于 header/footer/sidebar
|
|
40
42
|
*/
|
|
41
43
|
function NavMenu(_ref) {
|
|
42
|
-
var _children;
|
|
44
|
+
var _children, _itemRefs$current, _children2;
|
|
43
45
|
let {
|
|
44
46
|
items,
|
|
45
47
|
mode,
|
|
@@ -84,6 +86,60 @@ function NavMenu(_ref) {
|
|
|
84
86
|
close
|
|
85
87
|
});
|
|
86
88
|
}, [state, mode, activate, open, close]);
|
|
89
|
+
const [hiddenItemCount, setHiddenItemCount] = (0, _react.useState)(0);
|
|
90
|
+
const navMenuRef = (0, _react.useRef)();
|
|
91
|
+
const itemRefs = (0, _react.useRef)([]);
|
|
92
|
+
const moreIconRef = (0, _react.useRef)();
|
|
93
|
+
const isAllItemsHidden = hiddenItemCount === ((_itemRefs$current = itemRefs.current) === null || _itemRefs$current === void 0 ? void 0 : _itemRefs$current.length);
|
|
94
|
+
const icon = isAllItemsHidden ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Menu.default, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_MoreHoriz.default, {});
|
|
95
|
+
const style = isAllItemsHidden ? {
|
|
96
|
+
marginLeft: '0px'
|
|
97
|
+
} : undefined;
|
|
98
|
+
const renderChildrenWithRef = childrenElement => {
|
|
99
|
+
return _react.Children.map(childrenElement, (child, index) => {
|
|
100
|
+
return /*#__PURE__*/(0, _react.cloneElement)(child, {
|
|
101
|
+
ref: el => {
|
|
102
|
+
itemRefs.current[index] = el;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const checkItemsFit = () => {
|
|
108
|
+
var _navMenuRef$current;
|
|
109
|
+
let totalWidthUsed = 0;
|
|
110
|
+
let newHiddenCount = 0;
|
|
111
|
+
let leftAllHidden = false;
|
|
112
|
+
const containerWidth = ((_navMenuRef$current = navMenuRef.current) === null || _navMenuRef$current === void 0 ? void 0 : _navMenuRef$current.offsetWidth) || 0;
|
|
113
|
+
const moreIconWidth = moreIconRef.current ? moreIconRef.current.offsetWidth + parseFloat(window.getComputedStyle(moreIconRef.current).marginLeft) : 0;
|
|
114
|
+
itemRefs.current.forEach((item, index) => {
|
|
115
|
+
if (item) {
|
|
116
|
+
item.style.display = 'flex';
|
|
117
|
+
const marginLeft = index > 0 ? parseFloat(window.getComputedStyle(item).marginLeft) : 0;
|
|
118
|
+
const currentItemWidth = item.offsetWidth + marginLeft;
|
|
119
|
+
if (containerWidth - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
|
|
120
|
+
totalWidthUsed += currentItemWidth;
|
|
121
|
+
} else {
|
|
122
|
+
item.style.display = 'none';
|
|
123
|
+
leftAllHidden = true;
|
|
124
|
+
newHiddenCount++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
if (newHiddenCount !== hiddenItemCount) {
|
|
129
|
+
setHiddenItemCount(newHiddenCount);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
(0, _react.useEffect)(() => {
|
|
133
|
+
if (mode === 'horizontal') {
|
|
134
|
+
checkItemsFit();
|
|
135
|
+
window.addEventListener('resize', checkItemsFit);
|
|
136
|
+
return () => {
|
|
137
|
+
window.removeEventListener('resize', checkItemsFit);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
142
|
+
}, [mode, hiddenItemCount]);
|
|
87
143
|
(0, _react.useEffect)(() => {
|
|
88
144
|
// NavMenu#activeId 和 Item#active prop 都可以用来控制激活状态 (一般不会混用这两种方式)
|
|
89
145
|
// 如果未传入 NavMenu#activeId, 应该避免设置一个空值的 activeId 状态 (会与 Item#active 冲突)
|
|
@@ -94,26 +150,33 @@ function NavMenu(_ref) {
|
|
|
94
150
|
}
|
|
95
151
|
}, [activeId]);
|
|
96
152
|
const classes = (0, _clsx.default)('navmenu', "navmenu--".concat(mode), rest.className);
|
|
97
|
-
const renderItem = (item, index)
|
|
98
|
-
|
|
153
|
+
const renderItem = function renderItem(item, index) {
|
|
154
|
+
let isTopLevel = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
155
|
+
if (item !== null && item !== void 0 && item.children) {
|
|
156
|
+
// 对于 Sub 组件,如果它是顶级组件,则包含 ref
|
|
99
157
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Sub, {
|
|
100
158
|
id: item.id,
|
|
101
159
|
icon: item.icon,
|
|
102
160
|
label: item.label,
|
|
103
|
-
|
|
161
|
+
ref: isTopLevel ? el => {
|
|
162
|
+
itemRefs.current[index] = el;
|
|
163
|
+
} : undefined,
|
|
164
|
+
children: item.children.map((childItem, childIndex) => renderItem(childItem, childIndex, false))
|
|
104
165
|
}, index);
|
|
105
166
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
167
|
+
|
|
168
|
+
// 顶级 Item 组件总是包含 ref
|
|
169
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Item, {
|
|
170
|
+
id: item.id,
|
|
171
|
+
icon: item.icon,
|
|
172
|
+
label: item.label,
|
|
173
|
+
active: item.active,
|
|
174
|
+
ref: isTopLevel ? el => {
|
|
175
|
+
itemRefs.current[index] = el;
|
|
176
|
+
} : undefined
|
|
177
|
+
}, index);
|
|
116
178
|
};
|
|
179
|
+
const content = items ? items === null || items === void 0 ? void 0 : items.slice(-hiddenItemCount).map((item, index) => renderItem(item, index)) : (_children2 = children) === null || _children2 === void 0 ? void 0 : _children2.slice(-hiddenItemCount);
|
|
117
180
|
const StyledRoot = mode === 'inline' ? _style.InlineStyle : _style.HorizontalStyle;
|
|
118
181
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NavMenuContext.Provider, {
|
|
119
182
|
value: contextValue,
|
|
@@ -122,9 +185,17 @@ function NavMenu(_ref) {
|
|
|
122
185
|
$textColor: textColor,
|
|
123
186
|
$activeTextColor: activeTextColor,
|
|
124
187
|
$bgColor: bgColor,
|
|
125
|
-
children: /*#__PURE__*/(0, _jsxRuntime.
|
|
188
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("ul", {
|
|
126
189
|
className: "navmenu-root",
|
|
127
|
-
|
|
190
|
+
ref: navMenuRef,
|
|
191
|
+
children: [items ? items.map((item, index) => renderItem(item, index, true)) : renderChildrenWithRef(children), hiddenItemCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(Sub, {
|
|
192
|
+
expandIcon: false,
|
|
193
|
+
icon: icon,
|
|
194
|
+
label: "",
|
|
195
|
+
ref: moreIconRef,
|
|
196
|
+
style: style,
|
|
197
|
+
children: content
|
|
198
|
+
})]
|
|
128
199
|
})
|
|
129
200
|
}))
|
|
130
201
|
});
|
|
@@ -155,7 +226,7 @@ NavMenu.defaultProps = {
|
|
|
155
226
|
/**
|
|
156
227
|
* Item
|
|
157
228
|
*/
|
|
158
|
-
|
|
229
|
+
const Item = /*#__PURE__*/(0, _react.forwardRef)((_ref2, ref) => {
|
|
159
230
|
let {
|
|
160
231
|
id: _id,
|
|
161
232
|
icon,
|
|
@@ -188,6 +259,7 @@ function Item(_ref2) {
|
|
|
188
259
|
(0, _jsxRuntime.jsxs)("li", _objectSpread(_objectSpread({}, rest), {}, {
|
|
189
260
|
className: classes,
|
|
190
261
|
onClick: handleClick,
|
|
262
|
+
ref: ref,
|
|
191
263
|
children: [icon && /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
192
264
|
className: "navmenu-item-icon",
|
|
193
265
|
children: icon
|
|
@@ -197,7 +269,7 @@ function Item(_ref2) {
|
|
|
197
269
|
})]
|
|
198
270
|
}))
|
|
199
271
|
);
|
|
200
|
-
}
|
|
272
|
+
});
|
|
201
273
|
Item.propTypes = {
|
|
202
274
|
id: _propTypes.default.string,
|
|
203
275
|
icon: _propTypes.default.element,
|
|
@@ -216,7 +288,7 @@ Item.defaultProps = {
|
|
|
216
288
|
/**
|
|
217
289
|
* Sub
|
|
218
290
|
*/
|
|
219
|
-
|
|
291
|
+
const Sub = /*#__PURE__*/(0, _react.forwardRef)((_ref3, ref) => {
|
|
220
292
|
let {
|
|
221
293
|
id: _id,
|
|
222
294
|
icon,
|
|
@@ -251,6 +323,7 @@ function Sub(_ref3) {
|
|
|
251
323
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("li", _objectSpread(_objectSpread(_objectSpread({}, rest), {}, {
|
|
252
324
|
className: classes
|
|
253
325
|
}, props), {}, {
|
|
326
|
+
ref: ref,
|
|
254
327
|
children: [icon && /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
255
328
|
className: "navmenu-sub-icon",
|
|
256
329
|
children: icon
|
|
@@ -271,7 +344,7 @@ function Sub(_ref3) {
|
|
|
271
344
|
})
|
|
272
345
|
}))]
|
|
273
346
|
}));
|
|
274
|
-
}
|
|
347
|
+
});
|
|
275
348
|
Sub.propTypes = {
|
|
276
349
|
id: _propTypes.default.string,
|
|
277
350
|
icon: _propTypes.default.element,
|
package/lib/NavMenu/style.js
CHANGED
|
@@ -8,7 +8,7 @@ var _Theme = require("../Theme");
|
|
|
8
8
|
var _templateObject, _templateObject2, _templateObject3;
|
|
9
9
|
function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
|
|
10
10
|
const NavMenuBase = (0, _Theme.styled)('nav')(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n background-color: ", ";\n font-size: 16px;\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n .navmenu-item,\n .navmenu-sub {\n display: flex;\n align-items: center;\n }\n a {\n color: inherit;\n text-decoration: none;\n }\n /* active/hover */\n .navmenu-item,\n .navmenu-sub {\n color: ", ";\n }\n .navmenu-item--active,\n .navmenu-item:hover,\n .navmenu-sub--opened {\n color: ", ";\n }\n\n .navmenu-item {\n position: relative;\n cursor: pointer;\n transition: color 0.2s ease-in-out;\n a {\n white-space: nowrap;\n }\n a::before {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: transparent;\n content: '';\n }\n }\n\n .navmenu-sub {\n position: relative;\n cursor: pointer;\n }\n /* icon & expand icon */\n .navmenu-item-icon,\n .navmenu-sub-icon,\n .navmenu-sub-expand-icon {\n display: flex;\n line-height: 1;\n }\n .navmenu-item-icon,\n .navmenu-sub-icon {\n margin-right: 4px;\n }\n .navmenu-item-icon > *,\n .navmenu-sub-icon > * {\n width: auto;\n height: 22px;\n max-height: 22px;\n font-size: 1.5em;\n }\n .navmenu-sub-expand-icon {\n margin-left: 8px;\n > * {\n width: 0.8em;\n height: 0.8em;\n transition: transform 0.2s ease-in-out;\n }\n }\n"])), props => props.$bgColor, props => props.$textColor, props => props.$activeTextColor);
|
|
11
|
-
const HorizontalStyle = (0, _Theme.styled)(NavMenuBase)(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n padding: 8px 16px;\n .navmenu-root {\n display: flex;\n align-items: center;\n }\n /* \u9876\u7EA7\u83DC\u5355\u95F4\u9694 */\n .navmenu-root > .navmenu-item,\n .navmenu-root > .navmenu-sub {\n margin-left: 24px;\n }\n .navmenu-root > .navmenu-item:first-of-type,\n .navmenu-root > .navmenu-sub:first-of-type {\n margin-left: 0;\n }\n\n /* \u5B50\u7EA7\u5217\u8868 */\n .navmenu-sub-container {\n display: none;\n position: absolute;\n top: 100%;\n }\n .navmenu-sub-list {\n padding: 16px;\n border-radius: 4px;\n background: #fff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n .navmenu-item + .navmenu-item {\n margin-top: 8px;\n }\n }\n /* \u4E8C\u7EA7 sub menu */\n .navmenu-root > .navmenu-sub {\n > .navmenu-sub-container {\n left: 50%;\n transform: translateX(-50%);\n padding-top: 16px;\n }\n &.navmenu-sub--opened > .navmenu-sub-container {\n display: block;\n }\n }\n"])));
|
|
11
|
+
const HorizontalStyle = (0, _Theme.styled)(NavMenuBase)(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n padding: 8px 16px;\n min-width: 50px;\n flex-grow: 1;\n\n .navmenu-root {\n display: flex;\n align-items: center;\n }\n /* \u9876\u7EA7\u83DC\u5355\u95F4\u9694 */\n .navmenu-root > .navmenu-item,\n .navmenu-root > .navmenu-sub {\n margin-left: 24px;\n white-space: nowrap;\n }\n .navmenu-root > .navmenu-item:first-of-type,\n .navmenu-root > .navmenu-sub:first-of-type {\n margin-left: 0;\n }\n\n /* \u5B50\u7EA7\u5217\u8868 */\n .navmenu-sub-container {\n display: none;\n position: absolute;\n top: 100%;\n }\n .navmenu-sub-list {\n padding: 16px;\n border-radius: 4px;\n background: #fff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n .navmenu-item + .navmenu-item {\n margin-top: 8px;\n }\n }\n /* \u4E8C\u7EA7 sub menu */\n .navmenu-root > .navmenu-sub {\n white-space: nowrap;\n > .navmenu-sub-container {\n left: 50%;\n transform: translateX(-50%);\n padding-top: 16px;\n }\n &.navmenu-sub--opened > .navmenu-sub-container {\n display: block;\n }\n }\n"])));
|
|
12
12
|
|
|
13
13
|
/* inline mode */
|
|
14
14
|
exports.HorizontalStyle = HorizontalStyle;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/ux",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.26",
|
|
4
4
|
"description": "Common used react components for arcblock products",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -324,11 +324,11 @@
|
|
|
324
324
|
"peerDependencies": {
|
|
325
325
|
"react": ">=18.1.0"
|
|
326
326
|
},
|
|
327
|
-
"gitHead": "
|
|
327
|
+
"gitHead": "05d8234199a6770e3a26a8d11b72d0956ff24f4a",
|
|
328
328
|
"dependencies": {
|
|
329
329
|
"@arcblock/did-motif": "^1.1.13",
|
|
330
|
-
"@arcblock/icons": "^2.8.
|
|
331
|
-
"@arcblock/react-hooks": "^2.8.
|
|
330
|
+
"@arcblock/icons": "^2.8.26",
|
|
331
|
+
"@arcblock/react-hooks": "^2.8.26",
|
|
332
332
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
333
333
|
"@emotion/react": "^11.10.4",
|
|
334
334
|
"@emotion/styled": "^11.10.4",
|
package/src/Header/header.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import Box from '@mui/material/Box';
|
|
3
3
|
import Container from '@mui/material/Container';
|
|
4
|
+
import { useRef, useState, useEffect } from 'react';
|
|
4
5
|
import AutoHidden from './auto-hidden';
|
|
5
6
|
import { styled } from '../Theme';
|
|
6
7
|
|
|
@@ -21,10 +22,24 @@ function Header({
|
|
|
21
22
|
homeLink,
|
|
22
23
|
...rest
|
|
23
24
|
}) {
|
|
25
|
+
const logoRef = useRef();
|
|
26
|
+
const [brandWrapperMinWidth, setBrandWrapperMinWidth] = useState('0px');
|
|
27
|
+
const style = { minWidth: brandWrapperMinWidth };
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (logoRef.current) {
|
|
31
|
+
setBrandWrapperMinWidth(`${logoRef.current.offsetWidth}px`);
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
24
35
|
const renderBrand = () => {
|
|
25
36
|
const brandContent = (
|
|
26
37
|
<>
|
|
27
|
-
{logo &&
|
|
38
|
+
{logo && (
|
|
39
|
+
<div className="header-logo" ref={logoRef}>
|
|
40
|
+
{logo}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
28
43
|
{brand && (
|
|
29
44
|
<AutoHidden height={44} sx={{ flexShrink: { xs: 1 } }}>
|
|
30
45
|
<div className="header-brand">
|
|
@@ -46,7 +61,9 @@ function Header({
|
|
|
46
61
|
<Root {...rest}>
|
|
47
62
|
<Container maxWidth={maxWidth} className="header-container">
|
|
48
63
|
{prepend}
|
|
49
|
-
<div className="header-brand-wrapper"
|
|
64
|
+
<div className="header-brand-wrapper" style={style}>
|
|
65
|
+
{renderBrand()}
|
|
66
|
+
</div>
|
|
50
67
|
<div className="header-brand-addon">{brandAddon}</div>
|
|
51
68
|
{align === 'right' && <Box flexGrow={1} />}
|
|
52
69
|
{children}
|
|
@@ -101,8 +118,7 @@ const Root = styled('div')`
|
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
.header-brand-wrapper {
|
|
104
|
-
flex-shrink:
|
|
105
|
-
min-width: 0;
|
|
121
|
+
flex-shrink: 2;
|
|
106
122
|
> a {
|
|
107
123
|
display: flex;
|
|
108
124
|
align-items: center;
|
package/src/NavMenu/nav-menu.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
createContext,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
useRef,
|
|
10
|
+
useCallback,
|
|
11
|
+
forwardRef,
|
|
12
|
+
} from 'react';
|
|
2
13
|
import PropTypes from 'prop-types';
|
|
3
14
|
import clsx from 'clsx';
|
|
15
|
+
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
4
16
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
17
|
+
import MenuIcon from '@mui/icons-material/Menu';
|
|
5
18
|
import { HorizontalStyle, InlineStyle } from './style';
|
|
6
19
|
|
|
7
20
|
const NavMenuContext = createContext();
|
|
@@ -52,6 +65,65 @@ function NavMenu({ items, mode, children, activeId, textColor, activeTextColor,
|
|
|
52
65
|
close,
|
|
53
66
|
};
|
|
54
67
|
}, [state, mode, activate, open, close]);
|
|
68
|
+
const [hiddenItemCount, setHiddenItemCount] = useState(0);
|
|
69
|
+
const navMenuRef = useRef();
|
|
70
|
+
const itemRefs = useRef([]);
|
|
71
|
+
const moreIconRef = useRef();
|
|
72
|
+
const isAllItemsHidden = hiddenItemCount === itemRefs.current?.length;
|
|
73
|
+
const icon = isAllItemsHidden ? <MenuIcon /> : <MoreHorizIcon />;
|
|
74
|
+
const style = isAllItemsHidden ? { marginLeft: '0px' } : undefined;
|
|
75
|
+
|
|
76
|
+
const renderChildrenWithRef = (childrenElement) => {
|
|
77
|
+
return Children.map(childrenElement, (child, index) => {
|
|
78
|
+
return cloneElement(child, {
|
|
79
|
+
ref: (el) => {
|
|
80
|
+
itemRefs.current[index] = el;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const checkItemsFit = () => {
|
|
86
|
+
let totalWidthUsed = 0;
|
|
87
|
+
let newHiddenCount = 0;
|
|
88
|
+
let leftAllHidden = false;
|
|
89
|
+
const containerWidth = navMenuRef.current?.offsetWidth || 0;
|
|
90
|
+
const moreIconWidth = moreIconRef.current
|
|
91
|
+
? moreIconRef.current.offsetWidth + parseFloat(window.getComputedStyle(moreIconRef.current).marginLeft)
|
|
92
|
+
: 0;
|
|
93
|
+
|
|
94
|
+
itemRefs.current.forEach((item, index) => {
|
|
95
|
+
if (item) {
|
|
96
|
+
item.style.display = 'flex';
|
|
97
|
+
const marginLeft = index > 0 ? parseFloat(window.getComputedStyle(item).marginLeft) : 0;
|
|
98
|
+
const currentItemWidth = item.offsetWidth + marginLeft;
|
|
99
|
+
|
|
100
|
+
if (containerWidth - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
|
|
101
|
+
totalWidthUsed += currentItemWidth;
|
|
102
|
+
} else {
|
|
103
|
+
item.style.display = 'none';
|
|
104
|
+
leftAllHidden = true;
|
|
105
|
+
newHiddenCount++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (newHiddenCount !== hiddenItemCount) {
|
|
111
|
+
setHiddenItemCount(newHiddenCount);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (mode === 'horizontal') {
|
|
117
|
+
checkItemsFit();
|
|
118
|
+
window.addEventListener('resize', checkItemsFit);
|
|
119
|
+
|
|
120
|
+
return () => {
|
|
121
|
+
window.removeEventListener('resize', checkItemsFit);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
|
+
}, [mode, hiddenItemCount]);
|
|
55
127
|
|
|
56
128
|
useEffect(() => {
|
|
57
129
|
// NavMenu#activeId 和 Item#active prop 都可以用来控制激活状态 (一般不会混用这两种方式)
|
|
@@ -62,20 +134,53 @@ function NavMenu({ items, mode, children, activeId, textColor, activeTextColor,
|
|
|
62
134
|
}, [activeId]);
|
|
63
135
|
|
|
64
136
|
const classes = clsx('navmenu', `navmenu--${mode}`, rest.className);
|
|
65
|
-
|
|
66
|
-
|
|
137
|
+
|
|
138
|
+
const renderItem = (item, index, isTopLevel = false) => {
|
|
139
|
+
if (item?.children) {
|
|
140
|
+
// 对于 Sub 组件,如果它是顶级组件,则包含 ref
|
|
67
141
|
return (
|
|
68
|
-
<Sub
|
|
69
|
-
{
|
|
142
|
+
<Sub
|
|
143
|
+
key={index}
|
|
144
|
+
id={item.id}
|
|
145
|
+
icon={item.icon}
|
|
146
|
+
label={item.label}
|
|
147
|
+
ref={
|
|
148
|
+
isTopLevel
|
|
149
|
+
? (el) => {
|
|
150
|
+
itemRefs.current[index] = el;
|
|
151
|
+
}
|
|
152
|
+
: undefined
|
|
153
|
+
}>
|
|
154
|
+
{item.children.map((childItem, childIndex) => renderItem(childItem, childIndex, false))}
|
|
70
155
|
</Sub>
|
|
71
156
|
);
|
|
72
157
|
}
|
|
158
|
+
|
|
159
|
+
// 顶级 Item 组件总是包含 ref
|
|
73
160
|
return (
|
|
74
|
-
|
|
75
|
-
|
|
161
|
+
<Item
|
|
162
|
+
key={index}
|
|
163
|
+
id={item.id}
|
|
164
|
+
icon={item.icon}
|
|
165
|
+
label={item.label}
|
|
166
|
+
active={item.active}
|
|
167
|
+
ref={
|
|
168
|
+
isTopLevel
|
|
169
|
+
? (el) => {
|
|
170
|
+
itemRefs.current[index] = el;
|
|
171
|
+
}
|
|
172
|
+
: undefined
|
|
173
|
+
}
|
|
174
|
+
/>
|
|
76
175
|
);
|
|
77
176
|
};
|
|
177
|
+
|
|
178
|
+
const content = items
|
|
179
|
+
? items?.slice(-hiddenItemCount).map((item, index) => renderItem(item, index))
|
|
180
|
+
: children?.slice(-hiddenItemCount);
|
|
181
|
+
|
|
78
182
|
const StyledRoot = mode === 'inline' ? InlineStyle : HorizontalStyle;
|
|
183
|
+
|
|
79
184
|
return (
|
|
80
185
|
<NavMenuContext.Provider value={contextValue}>
|
|
81
186
|
<StyledRoot
|
|
@@ -84,7 +189,14 @@ function NavMenu({ items, mode, children, activeId, textColor, activeTextColor,
|
|
|
84
189
|
$textColor={textColor}
|
|
85
190
|
$activeTextColor={activeTextColor}
|
|
86
191
|
$bgColor={bgColor}>
|
|
87
|
-
<ul className="navmenu-root"
|
|
192
|
+
<ul className="navmenu-root" ref={navMenuRef}>
|
|
193
|
+
{items ? items.map((item, index) => renderItem(item, index, true)) : renderChildrenWithRef(children)}
|
|
194
|
+
{hiddenItemCount > 0 && (
|
|
195
|
+
<Sub expandIcon={false} icon={icon} label="" ref={moreIconRef} style={style}>
|
|
196
|
+
{content}
|
|
197
|
+
</Sub>
|
|
198
|
+
)}
|
|
199
|
+
</ul>
|
|
88
200
|
</StyledRoot>
|
|
89
201
|
</NavMenuContext.Provider>
|
|
90
202
|
);
|
|
@@ -116,28 +228,31 @@ NavMenu.defaultProps = {
|
|
|
116
228
|
/**
|
|
117
229
|
* Item
|
|
118
230
|
*/
|
|
119
|
-
|
|
231
|
+
const Item = forwardRef(({ id: _id, icon, label, active, onClick, ...rest }, ref) => {
|
|
120
232
|
const id = useUniqueId(_id);
|
|
121
233
|
const { activeId, activate } = useContext(NavMenuContext);
|
|
122
234
|
const classes = clsx('navmenu-item', { 'navmenu-item--active': activeId === id }, rest.className);
|
|
235
|
+
|
|
123
236
|
useEffect(() => {
|
|
124
237
|
if (active) {
|
|
125
238
|
activate(id);
|
|
126
239
|
}
|
|
127
240
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
128
241
|
}, [active]);
|
|
242
|
+
|
|
129
243
|
const handleClick = () => {
|
|
130
244
|
onClick?.();
|
|
131
245
|
activate(id);
|
|
132
246
|
};
|
|
247
|
+
|
|
133
248
|
return (
|
|
134
249
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
135
|
-
<li {...rest} className={classes} onClick={handleClick}>
|
|
250
|
+
<li {...rest} className={classes} onClick={handleClick} ref={ref}>
|
|
136
251
|
{icon && <span className="navmenu-item-icon">{icon}</span>}
|
|
137
252
|
<span className="navmenu-item-label">{label}</span>
|
|
138
253
|
</li>
|
|
139
254
|
);
|
|
140
|
-
}
|
|
255
|
+
});
|
|
141
256
|
|
|
142
257
|
Item.propTypes = {
|
|
143
258
|
id: PropTypes.string,
|
|
@@ -158,7 +273,7 @@ Item.defaultProps = {
|
|
|
158
273
|
/**
|
|
159
274
|
* Sub
|
|
160
275
|
*/
|
|
161
|
-
|
|
276
|
+
const Sub = forwardRef(({ id: _id, icon, label, children, expandIcon, ...rest }, ref) => {
|
|
162
277
|
const id = useUniqueId(_id);
|
|
163
278
|
const { openedIds, open, close, mode } = useContext(NavMenuContext);
|
|
164
279
|
const isOpen = openedIds.includes(id);
|
|
@@ -177,8 +292,9 @@ function Sub({ id: _id, icon, label, children, expandIcon, ...rest }) {
|
|
|
177
292
|
onClick: (e) => e.stopPropagation(),
|
|
178
293
|
}
|
|
179
294
|
: {};
|
|
295
|
+
|
|
180
296
|
return (
|
|
181
|
-
<li {...rest} className={classes} {...props}>
|
|
297
|
+
<li {...rest} className={classes} {...props} ref={ref}>
|
|
182
298
|
{icon && <span className="navmenu-sub-icon">{icon}</span>}
|
|
183
299
|
<span className="navmenu-sub-label">{label}</span>
|
|
184
300
|
{expandIcon && <span className="navmenu-sub-expand-icon">{expandIcon?.({ isOpen }) || expandIcon}</span>}
|
|
@@ -187,7 +303,7 @@ function Sub({ id: _id, icon, label, children, expandIcon, ...rest }) {
|
|
|
187
303
|
</div>
|
|
188
304
|
</li>
|
|
189
305
|
);
|
|
190
|
-
}
|
|
306
|
+
});
|
|
191
307
|
|
|
192
308
|
Sub.propTypes = {
|
|
193
309
|
id: PropTypes.string,
|
package/src/NavMenu/style.js
CHANGED
|
@@ -80,6 +80,9 @@ const NavMenuBase = styled('nav')`
|
|
|
80
80
|
|
|
81
81
|
export const HorizontalStyle = styled(NavMenuBase)`
|
|
82
82
|
padding: 8px 16px;
|
|
83
|
+
min-width: 50px;
|
|
84
|
+
flex-grow: 1;
|
|
85
|
+
|
|
83
86
|
.navmenu-root {
|
|
84
87
|
display: flex;
|
|
85
88
|
align-items: center;
|
|
@@ -88,6 +91,7 @@ export const HorizontalStyle = styled(NavMenuBase)`
|
|
|
88
91
|
.navmenu-root > .navmenu-item,
|
|
89
92
|
.navmenu-root > .navmenu-sub {
|
|
90
93
|
margin-left: 24px;
|
|
94
|
+
white-space: nowrap;
|
|
91
95
|
}
|
|
92
96
|
.navmenu-root > .navmenu-item:first-of-type,
|
|
93
97
|
.navmenu-root > .navmenu-sub:first-of-type {
|
|
@@ -111,6 +115,7 @@ export const HorizontalStyle = styled(NavMenuBase)`
|
|
|
111
115
|
}
|
|
112
116
|
/* 二级 sub menu */
|
|
113
117
|
.navmenu-root > .navmenu-sub {
|
|
118
|
+
white-space: nowrap;
|
|
114
119
|
> .navmenu-sub-container {
|
|
115
120
|
left: 50%;
|
|
116
121
|
transform: translateX(-50%);
|