11import _ from 'lodash' ;
22import block from 'bem-cn-lite' ;
3- import React , { RefObject , createRef , Fragment , MouseEventHandler } from 'react' ;
4- import OverflowScroller from '../../../OverflowScroller/OverflowScroller' ;
5- // import {LocationContext} from '../../context/locationContext';
3+ import React , {
4+ Fragment ,
5+ MouseEventHandler ,
6+ useState ,
7+ useEffect ,
8+ useCallback ,
9+ useContext ,
10+ useRef ,
11+ } from 'react' ;
612
13+ import OverflowScroller from '../../../OverflowScroller/OverflowScroller' ;
714import {
815 NavigationDropdownItem ,
916 NavigationItem as NavigationItemModel ,
1017 NavigationItemType ,
1118} from '../../../../models/navigation' ;
1219import NavigationPopup from '../NavigationPopup/NavigationPopup' ;
1320import NavigationItem from '../NavigationItem/NavigationItem' ;
21+ import { LocationContext } from '../../../../context/locationContext' ;
1422
1523import './Navigation.scss' ;
1624
1725const b = block ( 'navigation' ) ;
1826
19- export interface NavigationOwnProps {
27+ export interface NavigationProps {
2028 links : NavigationItemModel [ ] ;
2129 activeItemIndex : number ;
2230 onActiveItemChange : ( index : number ) => void ;
2331 className ?: string ;
2432 highlightActiveItem ?: boolean ;
2533}
2634
27- // interface WithRouterProps {
28- // router: NextRouter;
29- // }
30-
31- // export type NavigationProps = NavigationOwnProps & WithRouterProps;
32- export type NavigationProps = NavigationOwnProps ;
33-
34- interface NavigationState {
35- itemPositions : number [ ] ;
36- }
37-
38- class Navigation extends React . Component < NavigationProps , NavigationState > {
39- itemRefs : RefObject < HTMLLIElement > [ ] = [ ] ;
40- state = {
41- itemPositions : [ ] ,
42- } ;
43- lastLeftScroll = 0 ;
44-
45- componentDidMount ( ) {
46- this . calculateItemPositions ( ) ;
47- this . lastLeftScroll = window . pageXOffset ;
48-
49- window . addEventListener ( 'resize' , this . calculateItemPositions ) ;
50- window . addEventListener ( 'scroll' , this . calculateOnScroll ) ;
51- }
52-
53- // componentDidUpdate(prevProps: NavigationProps) {
54- // //use locationContext
55- // const {router} = this.props;
56- //
57- // if (router.asPath !== prevProps.router.asPath) {
58- // this.hidePopup();
59- // }
60- // }
61-
62- componentWillUnmount ( ) {
63- window . removeEventListener ( 'resize' , this . calculateItemPositions ) ;
64- window . removeEventListener ( 'scroll' , this . calculateOnScroll ) ;
65- }
66-
67- render ( ) {
68- const { className} = this . props ;
69-
70- return (
71- < OverflowScroller
72- className = { b ( null , className ) }
73- onScrollStart = { this . hidePopup }
74- onScrollEnd = { this . calculateItemPositions }
75- >
76- { this . renderContent ( ) }
77- </ OverflowScroller >
78- ) ;
79- }
80-
81- renderContent ( ) {
82- const { links, activeItemIndex, highlightActiveItem} = this . props ;
83- const { itemPositions} = this . state ;
84-
85- return (
86- < nav >
87- < ul className = { b ( 'links' ) } >
88- { links . map ( ( link , index ) => {
89- const isActive = index === activeItemIndex ;
90- const onClick = this . getItemClickHandler ( index ) ;
91-
92- if ( ! this . itemRefs [ index ] ) {
93- this . itemRefs [ index ] = createRef ( ) ;
94- }
95-
96- return (
97- < li ref = { this . itemRefs [ index ] } key = { index } className = { b ( 'links-item' ) } >
98- { link . type === NavigationItemType . Dropdown ? (
99- this . renderNavDropdown (
100- link ,
101- onClick ,
102- isActive ,
103- itemPositions [ index ] ,
104- )
105- ) : (
106- < NavigationItem data = { link } onClick = { onClick } />
107- ) }
108- { highlightActiveItem && isActive && this . renderSlider ( ) }
109- </ li >
110- ) ;
111- } ) }
112- </ ul >
113- </ nav >
114- ) ;
115- }
116-
117- private renderNavDropdown = (
35+ const Navigation : React . FC < NavigationProps > = ( {
36+ className,
37+ onActiveItemChange,
38+ links,
39+ activeItemIndex,
40+ highlightActiveItem,
41+ } ) => {
42+ const { asPath, pathname} = useContext ( LocationContext ) ;
43+ const itemRefs = useRef < ( HTMLLIElement | null ) [ ] > ( [ ] ) ;
44+ const [ itemPositions , setItemPosition ] = useState < number [ ] > ( [ ] ) ;
45+
46+ const [ lastLeftScroll , setLastLeftScroll ] = useState ( 0 ) ;
47+
48+ const hidePopup = useCallback ( ( ) => {
49+ onActiveItemChange ( - 1 ) ;
50+ } , [ onActiveItemChange ] ) ;
51+
52+ const getItemClickHandler = useCallback < ( index : number ) => MouseEventHandler > (
53+ ( index ) => ( e ) => {
54+ e . stopPropagation ( ) ;
55+ onActiveItemChange ( index === activeItemIndex ? - 1 : index ) ;
56+ } ,
57+ [ activeItemIndex , onActiveItemChange ] ,
58+ ) ;
59+
60+ const renderNavDropdown = (
11861 data : NavigationDropdownItem ,
11962 onClick : MouseEventHandler ,
12063 isActive : boolean ,
@@ -133,7 +76,7 @@ class Navigation extends React.Component<NavigationProps, NavigationState> {
13376 { isActive && (
13477 < NavigationPopup
13578 left = { position }
136- onClose = { this . hidePopup }
79+ onClose = { hidePopup }
13780 items = { items }
13881 { ...popupProps }
13982 />
@@ -142,44 +85,84 @@ class Navigation extends React.Component<NavigationProps, NavigationState> {
14285 ) ;
14386 } ;
14487
145- private renderSlider ( ) {
146- return (
147- < div className = { b ( 'slider-container' ) } >
148- < div className = { b ( 'slider' ) } />
149- </ div >
150- ) ;
151- }
152-
153- private getItemClickHandler : ( index : number ) => MouseEventHandler = ( index ) => ( e ) => {
154- e . stopPropagation ( ) ;
155- const { activeItemIndex, onActiveItemChange} = this . props ;
156- onActiveItemChange ( index === activeItemIndex ? - 1 : index ) ;
157- } ;
158-
159- private hidePopup = ( ) => {
160- this . props . onActiveItemChange ( - 1 ) ;
161- } ;
162-
163- // eslint-disable-next-line @typescript-eslint/member-ordering
164- private calculateItemPositions = _ . debounce ( ( ) => {
165- if ( this . itemRefs . length ) {
166- const itemPositions = this . itemRefs . map (
167- ( itemRef ) => ( itemRef . current && itemRef . current . getBoundingClientRect ( ) . left ) || 0 ,
88+ const slider = (
89+ < div className = { b ( 'slider-container' ) } >
90+ < div className = { b ( 'slider' ) } />
91+ </ div >
92+ ) ;
93+
94+ const content = (
95+ < nav >
96+ < ul className = { b ( 'links' ) } >
97+ { links . map ( ( link , index ) => {
98+ const isActive = index === activeItemIndex ;
99+ const onClick = getItemClickHandler ( index ) ;
100+
101+ return (
102+ < li
103+ ref = { ( el ) => itemRefs . current . push ( el ) }
104+ key = { index }
105+ className = { b ( 'links-item' ) }
106+ >
107+ { link . type === NavigationItemType . Dropdown ? (
108+ renderNavDropdown ( link , onClick , isActive , itemPositions [ index ] )
109+ ) : (
110+ < NavigationItem data = { link } onClick = { onClick } />
111+ ) }
112+ { highlightActiveItem && isActive && slider }
113+ </ li >
114+ ) ;
115+ } ) }
116+ </ ul >
117+ </ nav >
118+ ) ;
119+
120+ const calculateItemPositions = useCallback ( ( ) => {
121+ if ( itemRefs . current . length ) {
122+ const currentItemPositions = itemRefs . current . map (
123+ ( itemRef ) => ( itemRef && itemRef . getBoundingClientRect ( ) . left ) || 0 ,
168124 ) ;
169- this . setState ( { itemPositions} ) ;
170- }
171- } , 100 ) ;
172-
173- // eslint-disable-next-line @typescript-eslint/member-ordering
174- private calculateOnScroll = _ . debounce ( ( ) => {
175- const curLeftScroll = window . pageXOffset ;
176125
177- if ( curLeftScroll !== this . lastLeftScroll ) {
178- this . lastLeftScroll = curLeftScroll ;
179- this . calculateItemPositions ( ) ;
126+ setItemPosition ( currentItemPositions ) ;
180127 }
181- } , 100 ) ;
182- }
128+ } , [ ] ) ;
129+
130+ useEffect ( ( ) => {
131+ const debouncedCalculateItemPositions = _ . debounce ( calculateItemPositions , 100 ) ;
132+ const calculateOnScroll = _ . debounce ( ( ) => {
133+ const curLeftScroll = window . pageXOffset ;
134+
135+ if ( curLeftScroll !== lastLeftScroll ) {
136+ setLastLeftScroll ( window . pageXOffset ) ;
137+ calculateItemPositions ( ) ;
138+ }
139+ } , 100 ) ;
140+
141+ calculateItemPositions ( ) ;
142+ setLastLeftScroll ( window . pageXOffset ) ;
143+
144+ window . addEventListener ( 'resize' , debouncedCalculateItemPositions ) ;
145+ window . addEventListener ( 'scroll' , calculateOnScroll ) ;
146+
147+ return ( ) => {
148+ window . removeEventListener ( `resize` , calculateItemPositions ) ;
149+ window . removeEventListener ( 'scroll' , calculateOnScroll ) ;
150+ } ;
151+ } , [ calculateItemPositions , itemRefs , lastLeftScroll ] ) ;
152+
153+ useEffect ( ( ) => {
154+ hidePopup ( ) ;
155+ } , [ hidePopup , asPath , pathname ] ) ;
156+
157+ return (
158+ < OverflowScroller
159+ className = { b ( null , className ) }
160+ onScrollStart = { hidePopup }
161+ onScrollEnd = { calculateItemPositions }
162+ >
163+ { content }
164+ </ OverflowScroller >
165+ ) ;
166+ } ;
183167
184- // export default withRouter(Navigation);
185168export default Navigation ;
0 commit comments