Skip to content

Commit f0189a7

Browse files
refactor(Navigation): use Popup from UIKit to reduce custom logic (#268)
* refactor(Navigation): use Popup from UIKit to reduce custom logic * fix(NavigationItem): lint error
1 parent e3d074c commit f0189a7

File tree

11 files changed

+106
-188
lines changed

11 files changed

+106
-188
lines changed

src/navigation/components/Header/Header.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,6 @@ $block: '.#{$ns}header';
8989
&__buttons-item {
9090
position: relative;
9191

92-
height: var(--header-height);
93-
94-
line-height: var(--header-height);
95-
@include navigation-link();
96-
9792
&:not(:last-child) {
9893
margin-right: $indentS;
9994
}

src/navigation/components/Navigation/Navigation.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ $block: '.#{$ns}navigation';
1717
&__links-item {
1818
position: relative;
1919

20-
height: var(--header-height);
21-
22-
line-height: var(--header-height);
23-
@include navigation-link();
24-
2520
&:not(:last-child) {
2621
margin-right: $indentS;
2722
}

src/navigation/components/Navigation/Navigation.tsx

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
1+
import React, {useCallback, useContext, useEffect} from 'react';
22

33
import _ from 'lodash';
44

@@ -12,7 +12,6 @@ import {NavigationListItem} from '../NavigationListItem/NavigationListItem';
1212
import './Navigation.scss';
1313

1414
const b = block('navigation');
15-
const EVENT_HANDLE_DELAY = 100;
1615

1716
export interface NavigationProps {
1817
links: NavigationItemModel[];
@@ -27,77 +26,29 @@ const Navigation: React.FC<NavigationProps> = ({
2726
onActiveItemChange,
2827
links,
2928
activeItemId,
30-
highlightActiveItem,
3129
}) => {
3230
const {asPath, pathname} = useContext(LocationContext);
33-
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
34-
const [itemPositions, setItemPosition] = useState<number[]>([]);
35-
36-
const [lastLeftScroll, setLastLeftScroll] = useState(0);
3731

3832
const hidePopup = useCallback(() => {
3933
onActiveItemChange();
4034
}, [onActiveItemChange]);
4135

42-
const calculateItemPositions = useCallback(() => {
43-
if (itemRefs.current.length) {
44-
const currentItemPositions = itemRefs.current.map(
45-
(itemRef) => (itemRef && itemRef.getBoundingClientRect().left) || 0,
46-
);
47-
48-
setItemPosition(currentItemPositions);
49-
}
50-
}, []);
51-
52-
useEffect(() => {
53-
const debouncedCalculateItemPositions = _.debounce(
54-
calculateItemPositions,
55-
EVENT_HANDLE_DELAY,
56-
);
57-
const debouncedCalculateOnScroll = _.debounce(() => {
58-
const curLeftScroll = window.pageXOffset;
59-
60-
if (curLeftScroll !== lastLeftScroll) {
61-
setLastLeftScroll(window.pageXOffset);
62-
calculateItemPositions();
63-
}
64-
}, EVENT_HANDLE_DELAY);
65-
66-
calculateItemPositions();
67-
setLastLeftScroll(window.pageXOffset);
68-
69-
window.addEventListener('resize', debouncedCalculateItemPositions);
70-
window.addEventListener('scroll', debouncedCalculateOnScroll);
71-
72-
return () => {
73-
window.removeEventListener(`resize`, debouncedCalculateItemPositions);
74-
window.removeEventListener('scroll', debouncedCalculateOnScroll);
75-
};
76-
}, [calculateItemPositions, itemRefs, lastLeftScroll]);
77-
7836
useEffect(() => {
7937
hidePopup();
8038
}, [hidePopup, asPath, pathname]);
8139

8240
return (
83-
<OverflowScroller
84-
className={b(null, className)}
85-
onScrollStart={hidePopup}
86-
onScrollEnd={calculateItemPositions}
87-
>
41+
<OverflowScroller className={b(null, className)} onScrollStart={hidePopup}>
8842
<nav>
8943
<ul className={b('links')}>
9044
{links.map((link, index) => (
9145
<NavigationListItem
9246
key={index}
9347
className={b('links-item')}
9448
item={link}
95-
itemRefs={itemRefs}
9649
index={index}
9750
activeItemId={activeItemId}
98-
highlightActiveItem={highlightActiveItem}
9951
hidePopup={hidePopup}
100-
itemPositions={itemPositions}
10152
column={ItemColumnName.Left}
10253
onActiveItemChange={onActiveItemChange}
10354
/>

src/navigation/components/NavigationDropdownItem/NavigationDropdownItem.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
1-
import React, {Fragment, MouseEventHandler} from 'react';
1+
import React, {Fragment, MouseEventHandler, useRef} from 'react';
22

33
import {NavigationDropdownItem, NavigationItemType} from '../../../models';
44
import NavigationItem from '../NavigationItem/NavigationItem';
55
import NavigationPopup from '../NavigationPopup/NavigationPopup';
66

77
export interface NavigationDropdownProps {
8+
className?: string;
89
data: NavigationDropdownItem;
910
onClick: MouseEventHandler;
1011
isActive: boolean;
11-
position: number;
1212
hidePopup: () => void;
1313
}
1414

1515
const NavigationDropdown: React.FC<NavigationDropdownProps> = ({
16+
className,
1617
data,
1718
isActive,
18-
position,
1919
hidePopup,
2020
onClick,
2121
}) => {
22+
const anchorRef = useRef<HTMLElement>(null);
2223
const {text, icon, items, ...popupProps} = data;
2324

2425
return (
2526
<Fragment>
2627
<NavigationItem
28+
className={className}
29+
ref={anchorRef}
2730
onClick={onClick}
2831
isOpened={isActive}
2932
data={{text, type: NavigationItemType.Dropdown, icon}}
3033
/>
31-
{isActive && (
32-
<NavigationPopup
33-
left={position}
34-
onClose={hidePopup}
35-
items={items}
36-
{...popupProps}
37-
/>
38-
)}
34+
<NavigationPopup
35+
open={isActive}
36+
onClose={hidePopup}
37+
items={items}
38+
anchorRef={anchorRef}
39+
{...popupProps}
40+
/>
3941
</Fragment>
4042
);
4143
};

src/navigation/components/NavigationItem/NavigationItem.tsx

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,27 @@ const NavigationItemsMap: Record<NavigationItemType, React.ComponentType<any>> =
2727
[NavigationItemType.GithubButton]: GithubButton,
2828
};
2929

30-
const NavigationItem: React.FC<NavigationItemProps> = ({data, className, ...props}) => {
31-
const {type = NavigationItemType.Link} = data;
32-
const Component = NavigationItemsMap[type];
33-
const componentProps = useMemo(
34-
() => ({
35-
className,
36-
...data,
37-
...props,
38-
}),
39-
[className, data, props],
40-
);
30+
const NavigationItem = React.forwardRef<HTMLElement, NavigationItemProps>(
31+
({data, className, ...props}, ref) => {
32+
const {type = NavigationItemType.Link} = data;
33+
const Component = NavigationItemsMap[type];
34+
const componentProps = useMemo(
35+
() => ({
36+
className,
37+
...data,
38+
...props,
39+
ref,
40+
}),
41+
[className, data, props, ref],
42+
);
4143

42-
return (
43-
<BlockIdContext.Provider value={ANALYTICS_ID}>
44-
<Component {...componentProps} />
45-
</BlockIdContext.Provider>
46-
);
47-
};
44+
return (
45+
<BlockIdContext.Provider value={ANALYTICS_ID}>
46+
<Component {...componentProps} />
47+
</BlockIdContext.Provider>
48+
);
49+
},
50+
);
51+
NavigationItem.displayName = 'NavigationItem';
4852

4953
export default NavigationItem;

src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,22 @@ const TOGGLE_ARROW_SIZE = 12;
1515

1616
type NavigationDropdownProps = NavigationItemProps & DropdownItemData;
1717

18-
export const NavigationDropdown: React.FC<NavigationDropdownProps> = ({
19-
text,
20-
icon,
21-
isOpened,
22-
className,
23-
...props
24-
}) => {
25-
const iconData = icon && getMediaImage(icon);
18+
export const NavigationDropdown = React.forwardRef<HTMLElement, NavigationDropdownProps>(
19+
({text, icon, isOpened, className, ...props}, ref) => {
20+
const iconData = icon && getMediaImage(icon);
2621

27-
return (
28-
<span {...props} className={b(null, className)}>
29-
<ContentWrapper text={text} icon={iconData} />
30-
<ToggleArrow
31-
className={b('arrow')}
32-
size={TOGGLE_ARROW_SIZE}
33-
type={'vertical'}
34-
iconType="navigation"
35-
open={isOpened}
36-
/>
37-
</span>
38-
);
39-
};
22+
return (
23+
<span ref={ref} {...props} className={b(null, className)}>
24+
<ContentWrapper text={text} icon={iconData} />
25+
<ToggleArrow
26+
className={b('arrow')}
27+
size={TOGGLE_ARROW_SIZE}
28+
type={'vertical'}
29+
iconType="navigation"
30+
open={isOpened}
31+
/>
32+
</span>
33+
);
34+
},
35+
);
36+
NavigationDropdown.displayName = 'NavigationDropdown';

src/navigation/components/NavigationListItem/NavigationListItem.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
@import '../../../../styles/variables';
2+
@import '../../../../styles/mixins';
23

34
$block: '.#{$ns}navigation-list-item';
45

56
#{$block} {
7+
height: var(--header-height);
8+
line-height: var(--header-height);
9+
cursor: pointer;
10+
11+
@include islands-focus();
12+
@include reset-link-style();
13+
14+
&__content {
15+
&:hover,
16+
&:active {
17+
color: var(--yc-color-text-link);
18+
}
19+
}
20+
621
&__slider-container {
722
position: absolute;
823
right: 0;

src/navigation/components/NavigationListItem/NavigationListItem.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useRef} from 'react';
1+
import React from 'react';
22

33
import {ClassNameProps, NavigationItemModel, NavigationItemType} from '../../../models';
44
import {block} from '../../../utils';
@@ -9,33 +9,28 @@ import NavigationItem from '../NavigationItem/NavigationItem';
99

1010
import './NavigationListItem.scss';
1111

12-
const b = block('navigation');
12+
const b = block('navigation-list-item');
1313

1414
type NavigationListItemProps = {
1515
item: NavigationItemModel;
1616
index: number;
1717
column: ItemColumnName;
1818
activeItemId?: string;
19-
itemPositions?: number[];
20-
itemRefs?: React.MutableRefObject<(HTMLLIElement | null)[]>;
2119
highlightActiveItem?: boolean;
2220
hidePopup: () => void;
2321
onActiveItemChange: (id?: string) => void;
2422
} & ClassNameProps;
2523

2624
export const NavigationListItem = ({
2725
item,
28-
itemRefs,
2926
className,
3027
index,
3128
activeItemId,
3229
highlightActiveItem,
3330
hidePopup,
34-
itemPositions,
3531
column,
3632
onActiveItemChange,
3733
}: NavigationListItemProps) => {
38-
const ref = useRef<HTMLLIElement | null>(null);
3934
const id = `${column}-${index}`;
4035
const isActive = id === activeItemId;
4136
const onClick = getItemClickHandler({
@@ -46,19 +41,17 @@ export const NavigationListItem = ({
4641
});
4742

4843
return (
49-
<li ref={itemRefs ? (el) => itemRefs.current.push(el) : ref} className={className}>
44+
<li className={b(null, className)}>
5045
{item.type === NavigationItemType.Dropdown ? (
5146
<NavigationDropdownItem
47+
className={b('content')}
5248
data={item}
5349
onClick={onClick}
5450
isActive={isActive}
55-
position={
56-
itemPositions?.[index] || ref.current?.getBoundingClientRect().left || 0
57-
}
5851
hidePopup={hidePopup}
5952
/>
6053
) : (
61-
<NavigationItem data={item} onClick={onClick} />
54+
<NavigationItem className={b('content')} data={item} onClick={onClick} />
6255
)}
6356
{highlightActiveItem && isActive && (
6457
<div className={b('slider-container')}>

src/navigation/components/NavigationPopup/NavigationPopup.scss

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44
$block: '.#{$ns}navigation-popup';
55

66
#{$block} {
7-
$offset: calc(#{$gridGutter} * 2);
7+
$offset: calc($gridGutter * 2 * -1);
88

9-
position: fixed;
10-
top: calc(var(--header-height) - #{$indentXS});
9+
margin-left: $offset;
10+
margin-top: -#{$indentXS};
1111

12-
padding-right: 4px;
13-
padding-left: 4px;
14-
15-
transform: translateX(calc(#{$offset} * -1));
1612
@include desktop-only();
1713
@include navigation-popup();
1814

0 commit comments

Comments
 (0)