1
1
import React , { useEffect , useRef , useState } from 'react' ;
2
2
3
+ import { cn } from '@/utils/styles' ;
4
+
3
5
import type { ReactNode } from 'react' ;
4
6
5
7
interface Props {
6
8
children : ReactNode ;
7
9
}
8
10
9
11
const fixedClasses = [ 'opacity-1' , 'translate-y-0' ] ;
10
- const hiddenClasses = [ 'opacity-0' , 'translate-y-full ' ] ;
12
+ const hiddenClasses = [ 'opacity-0' , 'translate-y-20 ' ] ;
11
13
12
14
const showLink = ( linkRef : React . RefObject < HTMLAnchorElement > ) : void => {
13
15
linkRef . current ?. classList . add ( ...fixedClasses ) ;
@@ -29,13 +31,25 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
29
31
const [ height , setHeight ] = useState ( getHalfViewportHeight ( window ) ) ;
30
32
31
33
useEffect ( ( ) => {
34
+ // ! track them both independently at same time
35
+ // ! important: must be in this scope, outside of callback()
36
+ let isAtTop = false ;
37
+ let isAtBottom = false ;
38
+
32
39
const callback : IntersectionObserverCallback = ( entries ) => {
33
- const isAtTopOrBottom = entries . some ( ( entry ) => entry . isIntersecting ) ;
40
+ // entries.length === 1 || 2, count changes when exits viewport
41
+ entries . forEach ( ( entry ) => {
42
+ if ( entry . target === topRef . current ) {
43
+ isAtTop = entry . isIntersecting ;
44
+ }
34
45
35
- console . log ( 'isAtTopOrBottom' , isAtTopOrBottom ) ;
46
+ if ( entry . target === bottomRef . current ) {
47
+ isAtBottom = entry . isIntersecting ;
48
+ }
49
+ } ) ;
36
50
37
51
if ( linkRef . current ) {
38
- isAtTopOrBottom ? hideLink ( linkRef ) : showLink ( linkRef ) ;
52
+ isAtTop || isAtBottom ? hideLink ( linkRef ) : showLink ( linkRef ) ;
39
53
}
40
54
} ;
41
55
@@ -69,6 +83,7 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
69
83
className = "pointer-events-none absolute top-0 w-10 bg-red-500"
70
84
style = { { height : `${ height } px` } }
71
85
/>
86
+ { /* mounted in <body /> in Base layout */ }
72
87
< div
73
88
ref = { bottomRef }
74
89
className = "pointer-events-none absolute bottom-0 w-10 bg-blue-500"
@@ -78,7 +93,14 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
78
93
ref = { linkRef }
79
94
id = "to-top"
80
95
href = "#top"
81
- className = "z-10 fixed bottom-6 right-6 rounded bg-base-200 border border-base-300"
96
+ className = { cn (
97
+ // default styles
98
+ 'z-10 fixed bottom-6 right-6 rounded bg-base-200 border border-base-300' ,
99
+ // initial state
100
+ hiddenClasses ,
101
+ // transition classes
102
+ 'transition-all duration-300'
103
+ ) }
82
104
aria-label = "Scroll to top"
83
105
>
84
106
{ /* astro-icon must be passed as slot */ }
0 commit comments