@doyourjob/gravity-ui-page-constructor-addons 2.1.56 → 2.1.57
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/build/cjs/components/Navigation/components/popups/components/Search/Search.css +21 -0
- package/build/cjs/components/NewHeader/components/NHLoginButton/NHLoginButton.js +23 -19
- package/build/cjs/components/NewHeader/components/NHNavigation/NHNavigation.d.ts +1 -1
- package/build/cjs/components/NewHeader/components/NHNavigation/NHNavigation.js +33 -30
- package/build/cjs/components/NewHeader/components/NHNavigationItem/NHNavigationItem.js +1 -6
- package/build/esm/components/Navigation/components/popups/components/Search/Search.css +21 -0
- package/build/esm/components/NewHeader/components/NHLoginButton/NHLoginButton.js +24 -20
- package/build/esm/components/NewHeader/components/NHNavigation/NHNavigation.d.ts +1 -1
- package/build/esm/components/NewHeader/components/NHNavigation/NHNavigation.js +34 -31
- package/build/esm/components/NewHeader/components/NHNavigationItem/NHNavigationItem.js +2 -7
- package/package.json +1 -1
|
@@ -27,12 +27,33 @@ unpredictable css rules order in build */
|
|
|
27
27
|
width: 100%;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
.pc-addons-popup-search__search-suggest .g-text-input__control,
|
|
30
31
|
.pc-addons-popup-search__search-suggest .yc-text-input__control {
|
|
31
32
|
padding-right: 32px;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
@media (max-width: 768px) {
|
|
36
|
+
.pc-addons-popup-search__search-suggest .g-text-input,
|
|
37
|
+
.pc-addons-popup-search__search-suggest .yc-text-input {
|
|
38
|
+
--g-text-input-height: 44px;
|
|
39
|
+
}
|
|
40
|
+
.pc-addons-popup-search__search-suggest .g-text-input__control,
|
|
41
|
+
.pc-addons-popup-search__search-suggest .yc-text-input__control {
|
|
42
|
+
font-size: 16px;
|
|
43
|
+
line-height: 20px;
|
|
44
|
+
height: 42px;
|
|
45
|
+
padding-top: 10px;
|
|
46
|
+
padding-bottom: 10px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
.pc-addons-popup-search_size_s {
|
|
35
50
|
height: 36px;
|
|
36
51
|
width: 352px;
|
|
37
52
|
max-width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@media (max-width: 768px) {
|
|
56
|
+
.pc-addons-popup-search_size_s {
|
|
57
|
+
height: 44px;
|
|
58
|
+
}
|
|
38
59
|
}
|
|
@@ -13,28 +13,36 @@ const NHNavigationPopup_1 = require("../NHNavigationPopup/NHNavigationPopup");
|
|
|
13
13
|
const b = (0, cn_1.block)('nh-login-button');
|
|
14
14
|
const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
15
15
|
const [isActive, setIsActive] = (0, react_1.useState)(false);
|
|
16
|
-
const [pretendentActiveTab, setPretendentAciveTab] = (0, react_1.useState)(false);
|
|
17
16
|
const [previouslyFocusedElement, setPreviouslyFocusedElement] = (0, react_1.useState)(null);
|
|
18
17
|
const popupId = (0, uikit_1.useUniqId)();
|
|
18
|
+
const hoverTimerRef = (0, react_1.useRef)(null);
|
|
19
|
+
const clearHoverTimer = (0, react_1.useCallback)(() => {
|
|
20
|
+
if (hoverTimerRef.current) {
|
|
21
|
+
clearTimeout(hoverTimerRef.current);
|
|
22
|
+
hoverTimerRef.current = null;
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
// Open/close run through a single cancelable timer so moving from the trigger into the
|
|
26
|
+
// portaled popup (across the gap) cannot force the menu closed.
|
|
19
27
|
const handleActiveTab = (0, react_1.useCallback)((val) => {
|
|
20
28
|
setPreviouslyFocusedElement(document.activeElement);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
setIsActive(
|
|
24
|
-
}
|
|
25
|
-
}, [
|
|
29
|
+
clearHoverTimer();
|
|
30
|
+
hoverTimerRef.current = setTimeout(() => {
|
|
31
|
+
setIsActive(val);
|
|
32
|
+
}, constants_1.SWITCH_MENU_TAB_TIMEOUT);
|
|
33
|
+
}, [clearHoverTimer]);
|
|
26
34
|
const onEscapeKeyDown = (0, react_1.useCallback)((event) => {
|
|
27
35
|
if (event.key === 'Escape' && isActive) {
|
|
36
|
+
clearHoverTimer();
|
|
28
37
|
setIsActive(false);
|
|
29
|
-
setPretendentAciveTab(false);
|
|
30
38
|
previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
|
|
31
39
|
}
|
|
32
|
-
}, [isActive, previouslyFocusedElement]);
|
|
40
|
+
}, [clearHoverTimer, isActive, previouslyFocusedElement]);
|
|
33
41
|
const handleMouseEnter = (0, react_1.useCallback)(() => handleActiveTab(true), [handleActiveTab]);
|
|
34
42
|
const handleMouseLeave = (0, react_1.useCallback)((event) => {
|
|
35
43
|
const popup = document.getElementById(popupId);
|
|
36
44
|
const nextHoveredElement = (event === null || event === void 0 ? void 0 : event.relatedTarget) || (event === null || event === void 0 ? void 0 : event.nativeEvent.relatedTarget);
|
|
37
|
-
if (nextHoveredElement && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
45
|
+
if (nextHoveredElement instanceof Node && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
38
46
|
return;
|
|
39
47
|
}
|
|
40
48
|
handleActiveTab(false);
|
|
@@ -45,25 +53,20 @@ const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
|
45
53
|
const handleToggle = (0, react_1.useCallback)(() => {
|
|
46
54
|
const focusedElement = document.activeElement;
|
|
47
55
|
const nextIsActive = !isActive;
|
|
56
|
+
clearHoverTimer();
|
|
48
57
|
setPreviouslyFocusedElement(focusedElement);
|
|
49
|
-
setPretendentAciveTab(nextIsActive);
|
|
50
58
|
setIsActive(nextIsActive);
|
|
51
59
|
if (!nextIsActive) {
|
|
52
60
|
focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
|
|
53
61
|
}
|
|
54
|
-
}, [isActive]);
|
|
62
|
+
}, [clearHoverTimer, isActive]);
|
|
55
63
|
const handleKeyDown = (0, react_1.useCallback)((event) => {
|
|
56
64
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
57
65
|
event.preventDefault();
|
|
58
66
|
handleToggle();
|
|
59
67
|
}
|
|
60
68
|
}, [handleToggle]);
|
|
61
|
-
(0, react_1.useEffect)(() =>
|
|
62
|
-
const timerId = setTimeout(() => {
|
|
63
|
-
setIsActive(pretendentActiveTab);
|
|
64
|
-
}, constants_1.SWITCH_MENU_TAB_TIMEOUT);
|
|
65
|
-
return () => clearTimeout(timerId);
|
|
66
|
-
}, [pretendentActiveTab]);
|
|
69
|
+
(0, react_1.useEffect)(() => () => clearHoverTimer(), [clearHoverTimer]);
|
|
67
70
|
(0, react_1.useEffect)(() => {
|
|
68
71
|
document.addEventListener('keydown', onEscapeKeyDown);
|
|
69
72
|
return () => {
|
|
@@ -71,8 +74,9 @@ const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
|
71
74
|
};
|
|
72
75
|
}, [onEscapeKeyDown]);
|
|
73
76
|
(0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
clearHoverTimer();
|
|
78
|
+
setIsActive(false);
|
|
79
|
+
}), [clearHoverTimer, setupRouteChangeHandler]);
|
|
76
80
|
return (react_1.default.createElement("div", { className: b('wrapper'), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
77
81
|
react_1.default.createElement("button", { className: b(), type: "button", onMouseDown: handleMouseDown, onKeyDown: handleKeyDown, "aria-expanded": isActive, "aria-controls": popupId },
|
|
78
82
|
data.text,
|
|
@@ -7,7 +7,7 @@ interface NavigationProps {
|
|
|
7
7
|
headerRef?: RefObject<HTMLDivElement>;
|
|
8
8
|
navigationId: string;
|
|
9
9
|
setupRouteChangeHandler?: SetupRouteChangeHandler;
|
|
10
|
-
setActiveNavigationId:
|
|
10
|
+
setActiveNavigationId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
11
11
|
}
|
|
12
12
|
export declare const NHNavigation: ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }: NavigationProps) => React.JSX.Element;
|
|
13
13
|
export {};
|
|
@@ -22,50 +22,62 @@ const getPopupContent = (sectionData) => {
|
|
|
22
22
|
};
|
|
23
23
|
const NHNavigation = ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }) => {
|
|
24
24
|
const [activeTab, setActiveTab] = (0, react_1.useState)(constants_1.NO_MENU_TAB_SELECTED);
|
|
25
|
-
const [pretendentActiveTab, setPretendentAciveTab] = (0, react_1.useState)(constants_1.NO_MENU_TAB_SELECTED);
|
|
26
25
|
const [previouslyFocusedElement, setPreviouslyFocusedElement] = (0, react_1.useState)(null);
|
|
27
26
|
const [popupTabToFocus, setPopupTabToFocus] = (0, react_1.useState)(null);
|
|
27
|
+
const hoverTimerRef = (0, react_1.useRef)(null);
|
|
28
|
+
const clearHoverTimer = (0, react_1.useCallback)(() => {
|
|
29
|
+
if (hoverTimerRef.current) {
|
|
30
|
+
clearTimeout(hoverTimerRef.current);
|
|
31
|
+
hoverTimerRef.current = null;
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
28
34
|
const setNavigationTab = (0, react_1.useCallback)((currentIndex) => {
|
|
29
|
-
setPretendentAciveTab(currentIndex);
|
|
30
35
|
setActiveTab(currentIndex);
|
|
31
36
|
setPopupTabToFocus(null);
|
|
32
|
-
setActiveNavigationId(currentIndex === constants_1.NO_MENU_TAB_SELECTED
|
|
37
|
+
setActiveNavigationId(currentIndex === constants_1.NO_MENU_TAB_SELECTED
|
|
38
|
+
? (prev) => (prev === navigationId ? null : prev)
|
|
39
|
+
: navigationId);
|
|
33
40
|
}, [navigationId, setActiveNavigationId]);
|
|
41
|
+
// Hover open/switch/close all run through a single cancelable timer: every new pointer
|
|
42
|
+
// event cancels the pending one, so a brief transit across the trigger->popup gap (or
|
|
43
|
+
// rapid movement between items) can no longer force the popup closed.
|
|
34
44
|
const handleActiveTab = (0, react_1.useCallback)((currentIndex) => {
|
|
35
45
|
setPreviouslyFocusedElement(document.activeElement);
|
|
36
|
-
|
|
46
|
+
clearHoverTimer();
|
|
47
|
+
hoverTimerRef.current = setTimeout(() => {
|
|
37
48
|
setNavigationTab(currentIndex);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setPretendentAciveTab(currentIndex);
|
|
41
|
-
}, [activeTab, setNavigationTab]);
|
|
49
|
+
}, constants_1.SWITCH_MENU_TAB_TIMEOUT);
|
|
50
|
+
}, [clearHoverTimer, setNavigationTab]);
|
|
42
51
|
const handleToggleTab = (0, react_1.useCallback)((currentIndex) => {
|
|
43
52
|
const focusedElement = document.activeElement;
|
|
44
53
|
const nextActiveTab = activeTab === currentIndex ? constants_1.NO_MENU_TAB_SELECTED : currentIndex;
|
|
54
|
+
clearHoverTimer();
|
|
45
55
|
setPreviouslyFocusedElement(focusedElement);
|
|
46
56
|
setNavigationTab(nextActiveTab);
|
|
47
57
|
if (nextActiveTab === constants_1.NO_MENU_TAB_SELECTED) {
|
|
48
58
|
focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
|
|
49
59
|
}
|
|
50
|
-
}, [activeTab, setNavigationTab]);
|
|
60
|
+
}, [activeTab, clearHoverTimer, setNavigationTab]);
|
|
51
61
|
const handleFocusTabPopup = (0, react_1.useCallback)((currentIndex) => {
|
|
62
|
+
clearHoverTimer();
|
|
52
63
|
setPreviouslyFocusedElement(document.activeElement);
|
|
53
|
-
setPretendentAciveTab(currentIndex);
|
|
54
64
|
setActiveTab(currentIndex);
|
|
55
65
|
setPopupTabToFocus(currentIndex);
|
|
56
66
|
setActiveNavigationId(navigationId);
|
|
57
|
-
}, [navigationId, setActiveNavigationId]);
|
|
67
|
+
}, [clearHoverTimer, navigationId, setActiveNavigationId]);
|
|
58
68
|
const handleFocusTab = (0, react_1.useCallback)((currentIndex) => {
|
|
59
69
|
if (activeNavigationId === null) {
|
|
60
70
|
return;
|
|
61
71
|
}
|
|
72
|
+
clearHoverTimer();
|
|
62
73
|
setPreviouslyFocusedElement(document.activeElement);
|
|
63
74
|
setNavigationTab(currentIndex);
|
|
64
|
-
}, [activeNavigationId, setNavigationTab]);
|
|
75
|
+
}, [activeNavigationId, clearHoverTimer, setNavigationTab]);
|
|
65
76
|
const handleCloseTab = (0, react_1.useCallback)(() => {
|
|
77
|
+
clearHoverTimer();
|
|
66
78
|
setNavigationTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
67
79
|
previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
|
|
68
|
-
}, [previouslyFocusedElement, setNavigationTab]);
|
|
80
|
+
}, [clearHoverTimer, previouslyFocusedElement, setNavigationTab]);
|
|
69
81
|
const handleBlur = (0, react_1.useCallback)((event) => {
|
|
70
82
|
if (activeTab === constants_1.NO_MENU_TAB_SELECTED) {
|
|
71
83
|
return;
|
|
@@ -77,35 +89,26 @@ const NHNavigation = ({ activeNavigationId, data, headerRef, navigationId, setup
|
|
|
77
89
|
(popup === null || popup === void 0 ? void 0 : popup.contains(nextFocusedElement)))) {
|
|
78
90
|
return;
|
|
79
91
|
}
|
|
92
|
+
clearHoverTimer();
|
|
80
93
|
setNavigationTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
81
|
-
}, [activeTab, setNavigationTab]);
|
|
94
|
+
}, [activeTab, clearHoverTimer, setNavigationTab]);
|
|
82
95
|
const onEscapeKeyDown = (0, react_1.useCallback)((event) => {
|
|
83
96
|
if (event.key === 'Escape' && activeTab !== constants_1.NO_MENU_TAB_SELECTED) {
|
|
84
97
|
handleCloseTab();
|
|
85
98
|
}
|
|
86
99
|
}, [activeTab, handleCloseTab]);
|
|
87
100
|
(0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const timerId = setTimeout(() => {
|
|
92
|
-
setActiveTab(pretendentActiveTab);
|
|
93
|
-
if (pretendentActiveTab !== constants_1.NO_MENU_TAB_SELECTED) {
|
|
94
|
-
setActiveNavigationId(navigationId);
|
|
95
|
-
}
|
|
96
|
-
else if (activeNavigationId === navigationId) {
|
|
97
|
-
setActiveNavigationId(null);
|
|
98
|
-
}
|
|
99
|
-
}, constants_1.SWITCH_MENU_TAB_TIMEOUT);
|
|
100
|
-
return () => clearTimeout(timerId);
|
|
101
|
-
}, [activeNavigationId, navigationId, pretendentActiveTab, setActiveNavigationId]);
|
|
101
|
+
clearHoverTimer();
|
|
102
|
+
setNavigationTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
103
|
+
}), [setupRouteChangeHandler, clearHoverTimer, setNavigationTab]);
|
|
102
104
|
(0, react_1.useEffect)(() => {
|
|
103
105
|
if (activeNavigationId !== navigationId && activeTab !== constants_1.NO_MENU_TAB_SELECTED) {
|
|
106
|
+
clearHoverTimer();
|
|
104
107
|
setActiveTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
105
|
-
setPretendentAciveTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
106
108
|
setPopupTabToFocus(null);
|
|
107
109
|
}
|
|
108
|
-
}, [activeNavigationId, activeTab, navigationId]);
|
|
110
|
+
}, [activeNavigationId, activeTab, navigationId, clearHoverTimer]);
|
|
111
|
+
(0, react_1.useEffect)(() => () => clearHoverTimer(), [clearHoverTimer]);
|
|
109
112
|
(0, react_1.useEffect)(() => {
|
|
110
113
|
if (popupTabToFocus === null || activeTab !== popupTabToFocus) {
|
|
111
114
|
return;
|
|
@@ -7,19 +7,17 @@ const gravity_ui_page_constructor_1 = require("@doyourjob/gravity-ui-page-constr
|
|
|
7
7
|
const uikit_1 = require("@gravity-ui/uikit");
|
|
8
8
|
const cn_1 = require("../../../../utils/cn");
|
|
9
9
|
const constants_1 = require("../../constants");
|
|
10
|
-
const route_change_1 = require("../../contexts/route-change");
|
|
11
10
|
const models_1 = require("../../models");
|
|
12
11
|
const ChevronDown_1 = require("../ChevronDown");
|
|
13
12
|
const ChevronUp_1 = require("../ChevronUp");
|
|
14
13
|
const b = (0, cn_1.block)('nh-navigation-item');
|
|
15
14
|
const NHNavigationItem = ({ item, isActive, handleActiveTab, handleFocusTab, handleFocusTabPopup, handleToggleTab, index, children, tooltipId, }) => {
|
|
16
15
|
var _a, _b, _c, _d;
|
|
17
|
-
const setupRouteChangeHandler = (0, react_1.useContext)(route_change_1.RouteChangeHandlerContext);
|
|
18
16
|
const handleMouseEnter = (0, react_1.useCallback)(() => handleActiveTab(index), [handleActiveTab, index]);
|
|
19
17
|
const handleMouseLeave = (0, react_1.useCallback)((event) => {
|
|
20
18
|
const popup = tooltipId ? document.getElementById(tooltipId) : null;
|
|
21
19
|
const nextHoveredElement = (event === null || event === void 0 ? void 0 : event.relatedTarget) || (event === null || event === void 0 ? void 0 : event.nativeEvent.relatedTarget);
|
|
22
|
-
if (nextHoveredElement && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
20
|
+
if (nextHoveredElement instanceof Node && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
25
23
|
handleActiveTab(constants_1.NO_MENU_TAB_SELECTED);
|
|
@@ -39,9 +37,6 @@ const NHNavigationItem = ({ item, isActive, handleActiveTab, handleFocusTab, han
|
|
|
39
37
|
handleToggleTab(index);
|
|
40
38
|
}
|
|
41
39
|
}, [handleFocusTabPopup, handleToggleTab, index]);
|
|
42
|
-
(0, react_1.useEffect)(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
43
|
-
handleMouseLeave();
|
|
44
|
-
}), [handleMouseLeave, setupRouteChangeHandler]);
|
|
45
40
|
if (item.type === models_1.NHNavigationItemType.Link) {
|
|
46
41
|
return (react_1.default.createElement("li", { className: b({ disable: !((_a = item.data) === null || _a === void 0 ? void 0 : _a.url) }), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
47
42
|
react_1.default.createElement("a", Object.assign({ className: b('text', { active: isActive }), href: (_b = item.data) === null || _b === void 0 ? void 0 : _b.url, onFocus: handleLinkFocus }, (0, gravity_ui_page_constructor_1.getLinkProps)(((_c = item.data) === null || _c === void 0 ? void 0 : _c.url) || '', undefined, (_d = item.data) === null || _d === void 0 ? void 0 : _d.target)), item.title),
|
|
@@ -27,12 +27,33 @@ unpredictable css rules order in build */
|
|
|
27
27
|
width: 100%;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
.pc-addons-popup-search__search-suggest .g-text-input__control,
|
|
30
31
|
.pc-addons-popup-search__search-suggest .yc-text-input__control {
|
|
31
32
|
padding-right: 32px;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
@media (max-width: 768px) {
|
|
36
|
+
.pc-addons-popup-search__search-suggest .g-text-input,
|
|
37
|
+
.pc-addons-popup-search__search-suggest .yc-text-input {
|
|
38
|
+
--g-text-input-height: 44px;
|
|
39
|
+
}
|
|
40
|
+
.pc-addons-popup-search__search-suggest .g-text-input__control,
|
|
41
|
+
.pc-addons-popup-search__search-suggest .yc-text-input__control {
|
|
42
|
+
font-size: 16px;
|
|
43
|
+
line-height: 20px;
|
|
44
|
+
height: 42px;
|
|
45
|
+
padding-top: 10px;
|
|
46
|
+
padding-bottom: 10px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
.pc-addons-popup-search_size_s {
|
|
35
50
|
height: 36px;
|
|
36
51
|
width: 352px;
|
|
37
52
|
max-width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@media (max-width: 768px) {
|
|
56
|
+
.pc-addons-popup-search_size_s {
|
|
57
|
+
height: 44px;
|
|
58
|
+
}
|
|
38
59
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { Icon, useUniqId } from '@gravity-ui/uikit';
|
|
3
3
|
import { block } from '../../../../utils/cn';
|
|
4
4
|
import { SWITCH_MENU_TAB_TIMEOUT } from '../../constants';
|
|
@@ -10,28 +10,36 @@ import './NHLoginButton.css';
|
|
|
10
10
|
const b = block('nh-login-button');
|
|
11
11
|
export const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
12
12
|
const [isActive, setIsActive] = useState(false);
|
|
13
|
-
const [pretendentActiveTab, setPretendentAciveTab] = useState(false);
|
|
14
13
|
const [previouslyFocusedElement, setPreviouslyFocusedElement] = useState(null);
|
|
15
14
|
const popupId = useUniqId();
|
|
15
|
+
const hoverTimerRef = useRef(null);
|
|
16
|
+
const clearHoverTimer = useCallback(() => {
|
|
17
|
+
if (hoverTimerRef.current) {
|
|
18
|
+
clearTimeout(hoverTimerRef.current);
|
|
19
|
+
hoverTimerRef.current = null;
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
// Open/close run through a single cancelable timer so moving from the trigger into the
|
|
23
|
+
// portaled popup (across the gap) cannot force the menu closed.
|
|
16
24
|
const handleActiveTab = useCallback((val) => {
|
|
17
25
|
setPreviouslyFocusedElement(document.activeElement);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setIsActive(
|
|
21
|
-
}
|
|
22
|
-
}, [
|
|
26
|
+
clearHoverTimer();
|
|
27
|
+
hoverTimerRef.current = setTimeout(() => {
|
|
28
|
+
setIsActive(val);
|
|
29
|
+
}, SWITCH_MENU_TAB_TIMEOUT);
|
|
30
|
+
}, [clearHoverTimer]);
|
|
23
31
|
const onEscapeKeyDown = useCallback((event) => {
|
|
24
32
|
if (event.key === 'Escape' && isActive) {
|
|
33
|
+
clearHoverTimer();
|
|
25
34
|
setIsActive(false);
|
|
26
|
-
setPretendentAciveTab(false);
|
|
27
35
|
previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
|
|
28
36
|
}
|
|
29
|
-
}, [isActive, previouslyFocusedElement]);
|
|
37
|
+
}, [clearHoverTimer, isActive, previouslyFocusedElement]);
|
|
30
38
|
const handleMouseEnter = useCallback(() => handleActiveTab(true), [handleActiveTab]);
|
|
31
39
|
const handleMouseLeave = useCallback((event) => {
|
|
32
40
|
const popup = document.getElementById(popupId);
|
|
33
41
|
const nextHoveredElement = (event === null || event === void 0 ? void 0 : event.relatedTarget) || (event === null || event === void 0 ? void 0 : event.nativeEvent.relatedTarget);
|
|
34
|
-
if (nextHoveredElement && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
42
|
+
if (nextHoveredElement instanceof Node && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
35
43
|
return;
|
|
36
44
|
}
|
|
37
45
|
handleActiveTab(false);
|
|
@@ -42,25 +50,20 @@ export const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
|
42
50
|
const handleToggle = useCallback(() => {
|
|
43
51
|
const focusedElement = document.activeElement;
|
|
44
52
|
const nextIsActive = !isActive;
|
|
53
|
+
clearHoverTimer();
|
|
45
54
|
setPreviouslyFocusedElement(focusedElement);
|
|
46
|
-
setPretendentAciveTab(nextIsActive);
|
|
47
55
|
setIsActive(nextIsActive);
|
|
48
56
|
if (!nextIsActive) {
|
|
49
57
|
focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
|
|
50
58
|
}
|
|
51
|
-
}, [isActive]);
|
|
59
|
+
}, [clearHoverTimer, isActive]);
|
|
52
60
|
const handleKeyDown = useCallback((event) => {
|
|
53
61
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
54
62
|
event.preventDefault();
|
|
55
63
|
handleToggle();
|
|
56
64
|
}
|
|
57
65
|
}, [handleToggle]);
|
|
58
|
-
useEffect(() =>
|
|
59
|
-
const timerId = setTimeout(() => {
|
|
60
|
-
setIsActive(pretendentActiveTab);
|
|
61
|
-
}, SWITCH_MENU_TAB_TIMEOUT);
|
|
62
|
-
return () => clearTimeout(timerId);
|
|
63
|
-
}, [pretendentActiveTab]);
|
|
66
|
+
useEffect(() => () => clearHoverTimer(), [clearHoverTimer]);
|
|
64
67
|
useEffect(() => {
|
|
65
68
|
document.addEventListener('keydown', onEscapeKeyDown);
|
|
66
69
|
return () => {
|
|
@@ -68,8 +71,9 @@ export const NHLoginButton = ({ data, headerRef, setupRouteChangeHandler }) => {
|
|
|
68
71
|
};
|
|
69
72
|
}, [onEscapeKeyDown]);
|
|
70
73
|
useEffect(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
clearHoverTimer();
|
|
75
|
+
setIsActive(false);
|
|
76
|
+
}), [clearHoverTimer, setupRouteChangeHandler]);
|
|
73
77
|
return (React.createElement("div", { className: b('wrapper'), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
74
78
|
React.createElement("button", { className: b(), type: "button", onMouseDown: handleMouseDown, onKeyDown: handleKeyDown, "aria-expanded": isActive, "aria-controls": popupId },
|
|
75
79
|
data.text,
|
|
@@ -8,7 +8,7 @@ interface NavigationProps {
|
|
|
8
8
|
headerRef?: RefObject<HTMLDivElement>;
|
|
9
9
|
navigationId: string;
|
|
10
10
|
setupRouteChangeHandler?: SetupRouteChangeHandler;
|
|
11
|
-
setActiveNavigationId:
|
|
11
|
+
setActiveNavigationId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
12
12
|
}
|
|
13
13
|
export declare const NHNavigation: ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }: NavigationProps) => React.JSX.Element;
|
|
14
14
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { block } from '../../../../utils/cn';
|
|
3
3
|
import { NO_MENU_TAB_SELECTED, SWITCH_MENU_TAB_TIMEOUT } from '../../constants';
|
|
4
4
|
import { NHNavigationItemType } from '../../models';
|
|
@@ -19,50 +19,62 @@ const getPopupContent = (sectionData) => {
|
|
|
19
19
|
};
|
|
20
20
|
export const NHNavigation = ({ activeNavigationId, data, headerRef, navigationId, setupRouteChangeHandler, setActiveNavigationId, }) => {
|
|
21
21
|
const [activeTab, setActiveTab] = useState(NO_MENU_TAB_SELECTED);
|
|
22
|
-
const [pretendentActiveTab, setPretendentAciveTab] = useState(NO_MENU_TAB_SELECTED);
|
|
23
22
|
const [previouslyFocusedElement, setPreviouslyFocusedElement] = useState(null);
|
|
24
23
|
const [popupTabToFocus, setPopupTabToFocus] = useState(null);
|
|
24
|
+
const hoverTimerRef = useRef(null);
|
|
25
|
+
const clearHoverTimer = useCallback(() => {
|
|
26
|
+
if (hoverTimerRef.current) {
|
|
27
|
+
clearTimeout(hoverTimerRef.current);
|
|
28
|
+
hoverTimerRef.current = null;
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
25
31
|
const setNavigationTab = useCallback((currentIndex) => {
|
|
26
|
-
setPretendentAciveTab(currentIndex);
|
|
27
32
|
setActiveTab(currentIndex);
|
|
28
33
|
setPopupTabToFocus(null);
|
|
29
|
-
setActiveNavigationId(currentIndex === NO_MENU_TAB_SELECTED
|
|
34
|
+
setActiveNavigationId(currentIndex === NO_MENU_TAB_SELECTED
|
|
35
|
+
? (prev) => (prev === navigationId ? null : prev)
|
|
36
|
+
: navigationId);
|
|
30
37
|
}, [navigationId, setActiveNavigationId]);
|
|
38
|
+
// Hover open/switch/close all run through a single cancelable timer: every new pointer
|
|
39
|
+
// event cancels the pending one, so a brief transit across the trigger->popup gap (or
|
|
40
|
+
// rapid movement between items) can no longer force the popup closed.
|
|
31
41
|
const handleActiveTab = useCallback((currentIndex) => {
|
|
32
42
|
setPreviouslyFocusedElement(document.activeElement);
|
|
33
|
-
|
|
43
|
+
clearHoverTimer();
|
|
44
|
+
hoverTimerRef.current = setTimeout(() => {
|
|
34
45
|
setNavigationTab(currentIndex);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
setPretendentAciveTab(currentIndex);
|
|
38
|
-
}, [activeTab, setNavigationTab]);
|
|
46
|
+
}, SWITCH_MENU_TAB_TIMEOUT);
|
|
47
|
+
}, [clearHoverTimer, setNavigationTab]);
|
|
39
48
|
const handleToggleTab = useCallback((currentIndex) => {
|
|
40
49
|
const focusedElement = document.activeElement;
|
|
41
50
|
const nextActiveTab = activeTab === currentIndex ? NO_MENU_TAB_SELECTED : currentIndex;
|
|
51
|
+
clearHoverTimer();
|
|
42
52
|
setPreviouslyFocusedElement(focusedElement);
|
|
43
53
|
setNavigationTab(nextActiveTab);
|
|
44
54
|
if (nextActiveTab === NO_MENU_TAB_SELECTED) {
|
|
45
55
|
focusedElement === null || focusedElement === void 0 ? void 0 : focusedElement.focus({ preventScroll: true });
|
|
46
56
|
}
|
|
47
|
-
}, [activeTab, setNavigationTab]);
|
|
57
|
+
}, [activeTab, clearHoverTimer, setNavigationTab]);
|
|
48
58
|
const handleFocusTabPopup = useCallback((currentIndex) => {
|
|
59
|
+
clearHoverTimer();
|
|
49
60
|
setPreviouslyFocusedElement(document.activeElement);
|
|
50
|
-
setPretendentAciveTab(currentIndex);
|
|
51
61
|
setActiveTab(currentIndex);
|
|
52
62
|
setPopupTabToFocus(currentIndex);
|
|
53
63
|
setActiveNavigationId(navigationId);
|
|
54
|
-
}, [navigationId, setActiveNavigationId]);
|
|
64
|
+
}, [clearHoverTimer, navigationId, setActiveNavigationId]);
|
|
55
65
|
const handleFocusTab = useCallback((currentIndex) => {
|
|
56
66
|
if (activeNavigationId === null) {
|
|
57
67
|
return;
|
|
58
68
|
}
|
|
69
|
+
clearHoverTimer();
|
|
59
70
|
setPreviouslyFocusedElement(document.activeElement);
|
|
60
71
|
setNavigationTab(currentIndex);
|
|
61
|
-
}, [activeNavigationId, setNavigationTab]);
|
|
72
|
+
}, [activeNavigationId, clearHoverTimer, setNavigationTab]);
|
|
62
73
|
const handleCloseTab = useCallback(() => {
|
|
74
|
+
clearHoverTimer();
|
|
63
75
|
setNavigationTab(NO_MENU_TAB_SELECTED);
|
|
64
76
|
previouslyFocusedElement === null || previouslyFocusedElement === void 0 ? void 0 : previouslyFocusedElement.focus({ preventScroll: true });
|
|
65
|
-
}, [previouslyFocusedElement, setNavigationTab]);
|
|
77
|
+
}, [clearHoverTimer, previouslyFocusedElement, setNavigationTab]);
|
|
66
78
|
const handleBlur = useCallback((event) => {
|
|
67
79
|
if (activeTab === NO_MENU_TAB_SELECTED) {
|
|
68
80
|
return;
|
|
@@ -74,35 +86,26 @@ export const NHNavigation = ({ activeNavigationId, data, headerRef, navigationId
|
|
|
74
86
|
(popup === null || popup === void 0 ? void 0 : popup.contains(nextFocusedElement)))) {
|
|
75
87
|
return;
|
|
76
88
|
}
|
|
89
|
+
clearHoverTimer();
|
|
77
90
|
setNavigationTab(NO_MENU_TAB_SELECTED);
|
|
78
|
-
}, [activeTab, setNavigationTab]);
|
|
91
|
+
}, [activeTab, clearHoverTimer, setNavigationTab]);
|
|
79
92
|
const onEscapeKeyDown = useCallback((event) => {
|
|
80
93
|
if (event.key === 'Escape' && activeTab !== NO_MENU_TAB_SELECTED) {
|
|
81
94
|
handleCloseTab();
|
|
82
95
|
}
|
|
83
96
|
}, [activeTab, handleCloseTab]);
|
|
84
97
|
useEffect(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const timerId = setTimeout(() => {
|
|
89
|
-
setActiveTab(pretendentActiveTab);
|
|
90
|
-
if (pretendentActiveTab !== NO_MENU_TAB_SELECTED) {
|
|
91
|
-
setActiveNavigationId(navigationId);
|
|
92
|
-
}
|
|
93
|
-
else if (activeNavigationId === navigationId) {
|
|
94
|
-
setActiveNavigationId(null);
|
|
95
|
-
}
|
|
96
|
-
}, SWITCH_MENU_TAB_TIMEOUT);
|
|
97
|
-
return () => clearTimeout(timerId);
|
|
98
|
-
}, [activeNavigationId, navigationId, pretendentActiveTab, setActiveNavigationId]);
|
|
98
|
+
clearHoverTimer();
|
|
99
|
+
setNavigationTab(NO_MENU_TAB_SELECTED);
|
|
100
|
+
}), [setupRouteChangeHandler, clearHoverTimer, setNavigationTab]);
|
|
99
101
|
useEffect(() => {
|
|
100
102
|
if (activeNavigationId !== navigationId && activeTab !== NO_MENU_TAB_SELECTED) {
|
|
103
|
+
clearHoverTimer();
|
|
101
104
|
setActiveTab(NO_MENU_TAB_SELECTED);
|
|
102
|
-
setPretendentAciveTab(NO_MENU_TAB_SELECTED);
|
|
103
105
|
setPopupTabToFocus(null);
|
|
104
106
|
}
|
|
105
|
-
}, [activeNavigationId, activeTab, navigationId]);
|
|
107
|
+
}, [activeNavigationId, activeTab, navigationId, clearHoverTimer]);
|
|
108
|
+
useEffect(() => () => clearHoverTimer(), [clearHoverTimer]);
|
|
106
109
|
useEffect(() => {
|
|
107
110
|
if (popupTabToFocus === null || activeTab !== popupTabToFocus) {
|
|
108
111
|
return;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React, { useCallback
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
import { getLinkProps } from '@doyourjob/gravity-ui-page-constructor';
|
|
3
3
|
import { Icon } from '@gravity-ui/uikit';
|
|
4
4
|
import { block } from '../../../../utils/cn';
|
|
5
5
|
import { NO_MENU_TAB_SELECTED } from '../../constants';
|
|
6
|
-
import { RouteChangeHandlerContext } from '../../contexts/route-change';
|
|
7
6
|
import { NHNavigationItemType } from '../../models';
|
|
8
7
|
import { ChevronDown } from '../ChevronDown';
|
|
9
8
|
import { ChevronUp } from '../ChevronUp';
|
|
@@ -11,12 +10,11 @@ import './NHNavigationItem.css';
|
|
|
11
10
|
const b = block('nh-navigation-item');
|
|
12
11
|
export const NHNavigationItem = ({ item, isActive, handleActiveTab, handleFocusTab, handleFocusTabPopup, handleToggleTab, index, children, tooltipId, }) => {
|
|
13
12
|
var _a, _b, _c, _d;
|
|
14
|
-
const setupRouteChangeHandler = useContext(RouteChangeHandlerContext);
|
|
15
13
|
const handleMouseEnter = useCallback(() => handleActiveTab(index), [handleActiveTab, index]);
|
|
16
14
|
const handleMouseLeave = useCallback((event) => {
|
|
17
15
|
const popup = tooltipId ? document.getElementById(tooltipId) : null;
|
|
18
16
|
const nextHoveredElement = (event === null || event === void 0 ? void 0 : event.relatedTarget) || (event === null || event === void 0 ? void 0 : event.nativeEvent.relatedTarget);
|
|
19
|
-
if (nextHoveredElement && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
17
|
+
if (nextHoveredElement instanceof Node && (popup === null || popup === void 0 ? void 0 : popup.contains(nextHoveredElement))) {
|
|
20
18
|
return;
|
|
21
19
|
}
|
|
22
20
|
handleActiveTab(NO_MENU_TAB_SELECTED);
|
|
@@ -36,9 +34,6 @@ export const NHNavigationItem = ({ item, isActive, handleActiveTab, handleFocusT
|
|
|
36
34
|
handleToggleTab(index);
|
|
37
35
|
}
|
|
38
36
|
}, [handleFocusTabPopup, handleToggleTab, index]);
|
|
39
|
-
useEffect(() => setupRouteChangeHandler === null || setupRouteChangeHandler === void 0 ? void 0 : setupRouteChangeHandler(() => {
|
|
40
|
-
handleMouseLeave();
|
|
41
|
-
}), [handleMouseLeave, setupRouteChangeHandler]);
|
|
42
37
|
if (item.type === NHNavigationItemType.Link) {
|
|
43
38
|
return (React.createElement("li", { className: b({ disable: !((_a = item.data) === null || _a === void 0 ? void 0 : _a.url) }), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
44
39
|
React.createElement("a", Object.assign({ className: b('text', { active: isActive }), href: (_b = item.data) === null || _b === void 0 ? void 0 : _b.url, onFocus: handleLinkFocus }, getLinkProps(((_c = item.data) === null || _c === void 0 ? void 0 : _c.url) || '', undefined, (_d = item.data) === null || _d === void 0 ? void 0 : _d.target)), item.title),
|