graphen 2.0.8 → 2.0.9
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/dist/css.js +1 -1
- package/dist/scripts.js +1 -1
- package/package.json +1 -1
- package/src/components/Dropdown/integration/dropdown.spec.js +1 -1
- package/src/components/Navigation/styles/_submenu.scss +1 -1
- package/src/components/NavigationOption/index.tsx +50 -2
- package/src/components/NavigationOption/integration/navigation-option.spec.js +30 -0
- package/src/example.tsx +37 -0
- package/src/variables/integration/colors.spec.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React, { useCallback, useState } from "react";
|
|
1
|
+
import React, { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import classNames from "classnames";
|
|
3
3
|
|
|
4
|
+
const SUBMENU_VIEWPORT_MARGIN = 10;
|
|
5
|
+
|
|
4
6
|
type Props = {
|
|
5
7
|
className?: string;
|
|
6
8
|
label: React.ReactNode;
|
|
@@ -17,11 +19,52 @@ function NavigationOption({
|
|
|
17
19
|
children = null,
|
|
18
20
|
}: Props) {
|
|
19
21
|
const [isOpened, setIsOpened] = useState(false);
|
|
22
|
+
const [submenuPosition, setSubmenuPosition] = useState({ top: 0, left: 0 });
|
|
23
|
+
const triggerReference = useRef<HTMLButtonElement>(null);
|
|
24
|
+
const submenuReference = useRef<HTMLDivElement>(null);
|
|
20
25
|
|
|
21
26
|
const toggleSubmenu = useCallback(() => {
|
|
22
27
|
setIsOpened((previousIsOpened) => !previousIsOpened);
|
|
23
28
|
}, [setIsOpened]);
|
|
24
29
|
|
|
30
|
+
const updateSubmenuPosition = useCallback(() => {
|
|
31
|
+
const trigger = triggerReference.current;
|
|
32
|
+
const submenu = submenuReference.current;
|
|
33
|
+
|
|
34
|
+
if (!trigger || !submenu) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
39
|
+
const maximumLeft =
|
|
40
|
+
window.innerWidth - submenu.offsetWidth - SUBMENU_VIEWPORT_MARGIN;
|
|
41
|
+
|
|
42
|
+
setSubmenuPosition({
|
|
43
|
+
top: triggerRect.bottom,
|
|
44
|
+
left: Math.max(
|
|
45
|
+
SUBMENU_VIEWPORT_MARGIN,
|
|
46
|
+
Math.min(triggerRect.left, maximumLeft)
|
|
47
|
+
),
|
|
48
|
+
});
|
|
49
|
+
}, [setSubmenuPosition]);
|
|
50
|
+
|
|
51
|
+
useLayoutEffect(() => {
|
|
52
|
+
if (!isOpened) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
updateSubmenuPosition();
|
|
57
|
+
|
|
58
|
+
// capture phase catches scrolling inside consumer overflow containers
|
|
59
|
+
window.addEventListener("scroll", updateSubmenuPosition, true);
|
|
60
|
+
window.addEventListener("resize", updateSubmenuPosition);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
window.removeEventListener("scroll", updateSubmenuPosition, true);
|
|
64
|
+
window.removeEventListener("resize", updateSubmenuPosition);
|
|
65
|
+
};
|
|
66
|
+
}, [isOpened, updateSubmenuPosition]);
|
|
67
|
+
|
|
25
68
|
const closeSubmenuOnBlur = useCallback(
|
|
26
69
|
(event: React.FocusEvent<HTMLLIElement>) => {
|
|
27
70
|
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
|
@@ -65,6 +108,7 @@ function NavigationOption({
|
|
|
65
108
|
onKeyDown={closeSubmenuOnEscape}
|
|
66
109
|
>
|
|
67
110
|
<button
|
|
111
|
+
ref={triggerReference}
|
|
68
112
|
type="button"
|
|
69
113
|
className="gc-navigation__link"
|
|
70
114
|
aria-haspopup="true"
|
|
@@ -73,7 +117,11 @@ function NavigationOption({
|
|
|
73
117
|
>
|
|
74
118
|
{label}
|
|
75
119
|
</button>
|
|
76
|
-
<div
|
|
120
|
+
<div
|
|
121
|
+
ref={submenuReference}
|
|
122
|
+
className={submenuClasses}
|
|
123
|
+
style={{ top: submenuPosition.top, left: submenuPosition.left }}
|
|
124
|
+
>
|
|
77
125
|
<div className="gc-submenu__content">{children}</div>
|
|
78
126
|
</div>
|
|
79
127
|
</li>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
describe("navigation submenu", () => {
|
|
2
|
+
it("should anchor submenu to its parent option in a scrolled navigation", () => {
|
|
3
|
+
cy.visit("/");
|
|
4
|
+
|
|
5
|
+
cy.get("#navigation").scrollIntoView();
|
|
6
|
+
|
|
7
|
+
cy.get("#navigation .gc-navigation")
|
|
8
|
+
.eq(1)
|
|
9
|
+
.parent()
|
|
10
|
+
.scrollTo("right")
|
|
11
|
+
.within(() => {
|
|
12
|
+
cy.contains("button.gc-navigation__link", "Platform").click();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
cy.window().then((win) => {
|
|
16
|
+
cy.contains("button.gc-navigation__link", "Platform").then(($trigger) => {
|
|
17
|
+
const triggerRect = $trigger[0].getBoundingClientRect();
|
|
18
|
+
|
|
19
|
+
cy.get(".gc-submenu--opened").then(($submenu) => {
|
|
20
|
+
const submenuRect = $submenu[0].getBoundingClientRect();
|
|
21
|
+
|
|
22
|
+
expect(submenuRect.top).to.be.closeTo(triggerRect.bottom, 1);
|
|
23
|
+
expect(submenuRect.left).to.be.at.most(triggerRect.left + 1);
|
|
24
|
+
expect(submenuRect.left).to.be.at.least(0);
|
|
25
|
+
expect(submenuRect.right).to.be.at.most(win.innerWidth);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
package/src/example.tsx
CHANGED
|
@@ -1190,6 +1190,43 @@ function App() {
|
|
|
1190
1190
|
<NavigationOption label="Resources" href="#" />
|
|
1191
1191
|
</Navigation>
|
|
1192
1192
|
</Demo>
|
|
1193
|
+
<p className="docs-section-desc">
|
|
1194
|
+
Inside a narrow container the navigation scrolls horizontally. The
|
|
1195
|
+
submenu follows its scrolled parent option and stays within the
|
|
1196
|
+
viewport.
|
|
1197
|
+
</p>
|
|
1198
|
+
<Demo
|
|
1199
|
+
stageClass="column"
|
|
1200
|
+
code={`<div style={{ maxWidth: 280, overflowX: "auto" }}>
|
|
1201
|
+
<Navigation>
|
|
1202
|
+
<NavigationOption label="Overview" href="#" isActive />
|
|
1203
|
+
<NavigationOption label="Admin" href="#" />
|
|
1204
|
+
<NavigationOption label="Platform">
|
|
1205
|
+
<a className="gc-submenu__item" href="#">Users</a>
|
|
1206
|
+
<a className="gc-submenu__item" href="#">Events</a>
|
|
1207
|
+
</NavigationOption>
|
|
1208
|
+
<NavigationOption label="e-Shop" href="#" />
|
|
1209
|
+
<NavigationOption label="Account" href="#" />
|
|
1210
|
+
</Navigation>
|
|
1211
|
+
</div>`}
|
|
1212
|
+
>
|
|
1213
|
+
<div style={{ maxWidth: 280, overflowX: "auto" }}>
|
|
1214
|
+
<Navigation>
|
|
1215
|
+
<NavigationOption label="Overview" href="#" isActive />
|
|
1216
|
+
<NavigationOption label="Admin" href="#" />
|
|
1217
|
+
<NavigationOption label="Platform">
|
|
1218
|
+
<a className="gc-submenu__item" href="#">
|
|
1219
|
+
Users
|
|
1220
|
+
</a>
|
|
1221
|
+
<a className="gc-submenu__item" href="#">
|
|
1222
|
+
Events
|
|
1223
|
+
</a>
|
|
1224
|
+
</NavigationOption>
|
|
1225
|
+
<NavigationOption label="e-Shop" href="#" />
|
|
1226
|
+
<NavigationOption label="Account" href="#" />
|
|
1227
|
+
</Navigation>
|
|
1228
|
+
</div>
|
|
1229
|
+
</Demo>
|
|
1193
1230
|
</section>
|
|
1194
1231
|
|
|
1195
1232
|
<section className="docs-section" id="segmented-control">
|