import cn from 'classnames' ; import { find , get , some , throttle } from 'lodash' ; import React from 'react' ; import { Affix , IAffixProps , ITab , ITabsProps , Tabs } from 'zent' ; interface IAffixTab < T > extends ITab < T > { target : string ; hidden ? : boolean ; } interface IAffixTabsProps < T > extends ITabsProps < T > { defaultActiveId : T ; tabs : Array < IAffixTab < T > > ; } interface IProps < TId extends React . ReactText > { offset ? : number ; pinClassName ? : string ; affixProps ? : IAffixProps ; tabsProps : Partial < IAffixTabsProps < TId > > ; onActiveIdChange ? : ( key : string ) => void ; } param target param offset const scrollTo = ( target , offset = 0 ) => { if ( ! target ) return ; let $target ; if ( typeof target === 'string' ) { $target = document . querySelector ( target ) ; } else { $target = target ; } if ( $target ) { const targetTop = $target . getBoundingClientRect ( ) . top + ( document . documentElement || document . body ) . scrollTop ; const offsetTop = targetTop - offset ; window . scrollTo ( { top : offsetTop ! , behavior : 'smooth' } ) ; } } ; param props function AffixTabsNav < T extends React . ReactText > ( props ) { const { affixProps , tabsProps , offset , pinClassName = 'rc__affix-tabs-nav__wrap--pined' , onActiveIdChange } = props ; const defaultActiveId = React . useMemo ( ( ) => { return tabsProps . defaultActiveId || get ( tabsProps , 'tabs[0]key' ) ; } , [ tabsProps ] ) ; const [ activeId , setActiveId ] = React . useState < T > ( defaultActiveId ) ; const handleChange = React . useCallback ( ( activeId ) => { const tab = find ( tabsProps . tabs , ( tab ) => tab . key === activeId ) ; if ( tab ?. key ) { scrollTo ( tab . target , offset ) ; } setActiveId ( activeId ) ; onActiveIdChange ?. ( activeId ) ; } , [ tabsProps , offset , onActiveIdChange ] ) ; React . useEffect ( ( ) => { const timeout = 100 ; const scrollEndFix = ( ) => { let minDistance = 9999 ; let targetId = null ; some ( tabsProps . tabs , ( tab ) => { const dis = Math . abs ( document . querySelector ( tab . target ) ?. getBoundingClientRect ( ) ?. top ?? 9999 ) ; if ( dis > minDistance ) { return true ; } if ( dis <= minDistance ) { targetId = tab . key ; minDistance = dis ; return false ; } } ) ; if ( targetId !== null ) { setActiveId ( targetId ) ; } } ; scrollEndFix . timer = ( 0 as unknown ) as NodeJS . Timeout ; const scrollHandler = ( ) => { some ( tabsProps . tabs , ( tab ) => { const $target = document . querySelector ( tab . target ) ; if ( ! $target ) return ; const rect = $target . getBoundingClientRect ( ) ; const distance = Math . abs ( rect . top ) ; const distanceOffset = Math . max ( offset || 0 , 48 ) ; if ( distance <= distanceOffset ) { setActiveId ( tab . key ) ; return true ; } } ) ; clearTimeout ( scrollEndFix . timer ) ; scrollEndFix . timer = setTimeout ( ( ) => { scrollEndFix ( ) ; } , timeout * 2 ) ; } ; const throttledHandler = throttle ( scrollHandler , timeout ) ; window . addEventListener ( 'scroll' , throttledHandler ) ; return ( ) => { window . removeEventListener ( 'scroll' , throttledHandler ) ; } ; } , [ setActiveId , tabsProps , offset ] ) ; const { activeId : propsActiveId , onChange : propsOnChange , tabs , ... rest } = tabsProps ; const passTabsProps = { activeId : propsActiveId ?? activeId , onChange : propsOnChange ?? handleChange , tabs : tabs ?. filter ( ( tab ) => ! tab . hidden ) , ... rest , } ; const { onPin , onUnpin , ... passAffixProps } = affixProps || { } ; const [ pined , setPined ] = React . useState ( false ) ; const handlePin = React . useCallback ( ( ) => { setPined ( true ) ; onPin && onPin ( ) ; } , [ onPin ] ) ; const handleUnPin = React . useCallback ( ( ) => { setPined ( false ) ; onUnpin && onUnpin ( ) ; } , [ onUnpin ] ) ; const pinCls = React . useMemo ( ( ) => { return pined ? pinClassName || '' : '' ; } , [ pined , pinClassName ] ) ; return ( < Affix className = { cn ( 'rc__affix-tabs-nav__wrap' , pinCls ) } offsetTop = { offset } { ... passAffixProps } onPin = { handlePin } onUnpin = { handleUnPin } > < Tabs { ... passTabsProps } / > < /Affix>); } AffixTabsNav . defaultProps = { affixProps : { } , } ; export default AffixTabsNav ;