@arcblock/ux 2.12.3 → 2.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/NavMenu/nav-menu.d.ts +1 -1
- package/lib/NavMenu/nav-menu.js +22 -12
- package/lib/NavMenu/products.js +74 -63
- package/lib/NavMenu/style.d.ts +2 -2
- package/lib/NavMenu/style.js +46 -25
- package/lib/NavMenu/sub-container.d.ts +6 -0
- package/lib/NavMenu/sub-container.js +83 -0
- package/package.json +5 -5
- package/src/NavMenu/nav-menu.tsx +26 -15
- package/src/NavMenu/products.tsx +140 -78
- package/src/NavMenu/style.ts +40 -22
- package/src/NavMenu/sub-container.tsx +96 -0
package/src/NavMenu/style.ts
CHANGED
@@ -5,11 +5,12 @@ type NavMenuProps = {
|
|
5
5
|
$textColor: string;
|
6
6
|
};
|
7
7
|
|
8
|
-
// .navmenu
|
9
|
-
export const
|
8
|
+
// .navmenu
|
9
|
+
export const NavMenuStyled = styled('nav', {
|
10
10
|
shouldForwardProp: (prop) => prop !== '$bgColor' && prop !== '$textColor',
|
11
11
|
})<NavMenuProps>(({ $bgColor, $textColor }) => ({
|
12
|
-
|
12
|
+
position: 'relative',
|
13
|
+
padding: '0 16px',
|
13
14
|
minWidth: '50px',
|
14
15
|
// FIXME: @zhanghan 这个只是临时的解决方案,会导致 header align right 不能真正的右对齐,需要修改 header 才能真正解决这个问题
|
15
16
|
flexGrow: 100,
|
@@ -18,15 +19,15 @@ export const NavMenuRoot = styled('nav', {
|
|
18
19
|
fontSize: '16px',
|
19
20
|
}));
|
20
21
|
|
21
|
-
// .navmenu-
|
22
|
-
export const
|
22
|
+
// .navmenu-root
|
23
|
+
export const NavMenuRoot = styled('ul')(() => ({
|
23
24
|
listStyle: 'none',
|
24
25
|
margin: 0,
|
25
26
|
padding: 0,
|
26
27
|
display: 'flex',
|
27
28
|
alignItems: 'center',
|
28
29
|
// inline 布局
|
29
|
-
'&.navmenu-
|
30
|
+
'&.navmenu-root--inline ': {
|
30
31
|
flexDirection: 'column',
|
31
32
|
alignItems: 'stretch',
|
32
33
|
},
|
@@ -38,14 +39,16 @@ type NavMenuItemProps = {
|
|
38
39
|
// 菜单项 .navmenu-item
|
39
40
|
export const NavMenuItem = styled('li', {
|
40
41
|
shouldForwardProp: (prop) => prop !== '$activeTextColor',
|
41
|
-
})<NavMenuItemProps>(({ $activeTextColor }) => ({
|
42
|
+
})<NavMenuItemProps>(({ $activeTextColor, theme }) => ({
|
42
43
|
display: 'flex',
|
43
44
|
alignItems: 'center',
|
44
45
|
position: 'relative',
|
45
|
-
padding: '
|
46
|
+
padding: '8px 12px',
|
46
47
|
whiteSpace: 'nowrap',
|
47
48
|
cursor: 'pointer',
|
48
|
-
transition: 'color
|
49
|
+
transition: theme.transitions.create('color', {
|
50
|
+
duration: theme.transitions.duration.standard,
|
51
|
+
}),
|
49
52
|
// 间距调整
|
50
53
|
'&:first-of-type': {
|
51
54
|
paddingLeft: 0,
|
@@ -98,7 +101,9 @@ export const NavMenuItem = styled('li', {
|
|
98
101
|
marginLeft: '6px',
|
99
102
|
fontSize: '14px',
|
100
103
|
opacity: 0,
|
101
|
-
transition: 'opacity
|
104
|
+
transition: theme.transitions.create('opacity', {
|
105
|
+
duration: theme.transitions.duration.standard,
|
106
|
+
}),
|
102
107
|
},
|
103
108
|
},
|
104
109
|
'.navmenu-item__desc': {
|
@@ -136,13 +141,17 @@ export const NavMenuItem = styled('li', {
|
|
136
141
|
},
|
137
142
|
'&:hover': {
|
138
143
|
background: '#f9f9fb',
|
139
|
-
transition: 'background
|
144
|
+
transition: theme.transitions.create('background', {
|
145
|
+
duration: theme.transitions.duration.standard,
|
146
|
+
}),
|
140
147
|
'.navmenu-item__label-arrow': {
|
141
148
|
opacity: 1,
|
142
149
|
},
|
143
150
|
'.navmenu-item__desc': {
|
144
151
|
color: '#26292e',
|
145
|
-
transition: 'color
|
152
|
+
transition: theme.transitions.create('color', {
|
153
|
+
duration: theme.transitions.duration.standard,
|
154
|
+
}),
|
146
155
|
},
|
147
156
|
},
|
148
157
|
'&.navmenu-item--active': {
|
@@ -160,24 +169,26 @@ export const NavMenuItem = styled('li', {
|
|
160
169
|
}));
|
161
170
|
|
162
171
|
// 包含子菜单的导航项 .navmenu-item .navmenu-sub
|
163
|
-
export const NavMenuSub = styled(NavMenuItem)(() => ({
|
172
|
+
export const NavMenuSub = styled(NavMenuItem)(({ theme }) => ({
|
164
173
|
'.navmenu-item': {
|
165
174
|
marginLeft: 0,
|
175
|
+
overflow: 'hidden',
|
166
176
|
},
|
167
177
|
'& .navmenu-sub__container': {
|
168
|
-
|
178
|
+
pointerEvents: 'none',
|
169
179
|
opacity: 0,
|
170
180
|
position: 'absolute',
|
171
181
|
top: '100%',
|
172
|
-
left:
|
182
|
+
left: 0,
|
173
183
|
zIndex: 1,
|
174
|
-
transform: 'translateX(-50%)',
|
175
|
-
paddingTop: '16px',
|
176
|
-
transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
|
177
184
|
},
|
178
185
|
'&.navmenu-sub--opened > .navmenu-sub__container': {
|
179
|
-
|
186
|
+
pointerEvents: 'auto',
|
180
187
|
opacity: 1,
|
188
|
+
transition: theme.transitions.create('opacity', {
|
189
|
+
duration: theme.transitions.duration.standard,
|
190
|
+
easing: theme.transitions.easing.easeOut,
|
191
|
+
}),
|
181
192
|
},
|
182
193
|
// 扩展图标
|
183
194
|
'.navmenu-sub__expand-icon': {
|
@@ -188,7 +199,9 @@ export const NavMenuSub = styled(NavMenuItem)(() => ({
|
|
188
199
|
'& > *': {
|
189
200
|
width: '0.8em',
|
190
201
|
height: '0.8em',
|
191
|
-
transition: 'transform
|
202
|
+
transition: theme.transitions.create('transform', {
|
203
|
+
duration: theme.transitions.duration.standard,
|
204
|
+
}),
|
192
205
|
},
|
193
206
|
'.navmenu-item--inline &': {
|
194
207
|
marginLeft: 'auto',
|
@@ -204,7 +217,9 @@ export const NavMenuSub = styled(NavMenuItem)(() => ({
|
|
204
217
|
padding: 0,
|
205
218
|
height: 0,
|
206
219
|
overflow: 'hidden',
|
207
|
-
transition: 'height
|
220
|
+
transition: theme.transitions.create('height', {
|
221
|
+
duration: theme.transitions.duration.standard,
|
222
|
+
}),
|
208
223
|
},
|
209
224
|
'&.navmenu-sub--opened > .navmenu-sub__container': {
|
210
225
|
height: 'auto',
|
@@ -214,10 +229,13 @@ export const NavMenuSub = styled(NavMenuItem)(() => ({
|
|
214
229
|
paddingLeft: '16px',
|
215
230
|
boxShadow: 'none',
|
216
231
|
},
|
232
|
+
'.navmenu-item__content': {
|
233
|
+
height: '48px',
|
234
|
+
},
|
217
235
|
},
|
218
236
|
}));
|
219
237
|
|
220
|
-
// 下拉子菜单 .navmenu-
|
238
|
+
// 下拉子菜单 .navmenu-sub__list
|
221
239
|
export const NavMenuSubList = styled('ul')(() => ({
|
222
240
|
margin: 0,
|
223
241
|
padding: '16px',
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
2
|
+
|
3
|
+
interface SubContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
4
|
+
children: React.ReactNode;
|
5
|
+
}
|
6
|
+
|
7
|
+
const paddingTop = 8;
|
8
|
+
|
9
|
+
// 下拉子菜单容器
|
10
|
+
export function SubContainer({ children, ...props }: SubContainerProps) {
|
11
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
12
|
+
const [position, setPosition] = useState(0); // 只需要保存水平偏移量
|
13
|
+
|
14
|
+
const updatePosition = () => {
|
15
|
+
if (!rootRef.current) return;
|
16
|
+
const anchor = rootRef.current.parentElement; // 以父容器作为参照
|
17
|
+
if (!anchor) return;
|
18
|
+
|
19
|
+
const anchorRect = anchor.getBoundingClientRect();
|
20
|
+
const containerRect = rootRef.current.getBoundingClientRect();
|
21
|
+
const windowWidth = window.innerWidth;
|
22
|
+
const padding = 32;
|
23
|
+
|
24
|
+
// 1. 计算相对于父元素的水平居中位置
|
25
|
+
let left = (anchorRect.width - containerRect.width) / 2;
|
26
|
+
|
27
|
+
// 2. 检查容器在视口中的绝对位置
|
28
|
+
const absoluteLeft = anchorRect.left + left;
|
29
|
+
|
30
|
+
// 3. 调整水平位置确保不超出窗口
|
31
|
+
if (absoluteLeft < 0) {
|
32
|
+
// 如果超出左边界,向右偏移
|
33
|
+
left = left - absoluteLeft + padding;
|
34
|
+
} else if (absoluteLeft + containerRect.width > windowWidth) {
|
35
|
+
// 如果超出右边界,向左偏移
|
36
|
+
left -= absoluteLeft + containerRect.width - windowWidth + padding;
|
37
|
+
}
|
38
|
+
|
39
|
+
setPosition(left);
|
40
|
+
};
|
41
|
+
|
42
|
+
// 监听自身大小变化
|
43
|
+
useEffect(() => {
|
44
|
+
const resizeObserver = new ResizeObserver(() => {
|
45
|
+
updatePosition();
|
46
|
+
});
|
47
|
+
|
48
|
+
resizeObserver.observe(rootRef.current!);
|
49
|
+
|
50
|
+
return () => resizeObserver.disconnect();
|
51
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
52
|
+
}, []);
|
53
|
+
|
54
|
+
// 监听 anchor 的大小变化
|
55
|
+
useEffect(() => {
|
56
|
+
const anchor = rootRef.current?.parentElement;
|
57
|
+
let resizeObserver: ResizeObserver | null = null;
|
58
|
+
|
59
|
+
if (anchor) {
|
60
|
+
resizeObserver = new ResizeObserver(() => {
|
61
|
+
updatePosition();
|
62
|
+
});
|
63
|
+
|
64
|
+
resizeObserver.observe(anchor);
|
65
|
+
}
|
66
|
+
|
67
|
+
return () => resizeObserver?.disconnect();
|
68
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
69
|
+
}, []);
|
70
|
+
|
71
|
+
// 监听窗口大小变化
|
72
|
+
useEffect(() => {
|
73
|
+
const handleResize = () => {
|
74
|
+
updatePosition();
|
75
|
+
};
|
76
|
+
|
77
|
+
window.addEventListener('resize', handleResize);
|
78
|
+
return () => window.removeEventListener('resize', handleResize);
|
79
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
80
|
+
}, []);
|
81
|
+
|
82
|
+
return (
|
83
|
+
<div
|
84
|
+
ref={rootRef}
|
85
|
+
className="navmenu-sub__container"
|
86
|
+
style={{
|
87
|
+
paddingTop: `${paddingTop}px`,
|
88
|
+
transform: `translateX(${position}px)`,
|
89
|
+
}}
|
90
|
+
{...props}>
|
91
|
+
{children}
|
92
|
+
</div>
|
93
|
+
);
|
94
|
+
}
|
95
|
+
|
96
|
+
export default SubContainer;
|