1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
const isBrowser = typeof window !== `undefined`;
function useSidebar() {
const sidebar = useRef(null);
const [open, setOpen] = useState(false);
const sidebarToggler = useRef(null);
const [windowHeight, setWindowHeight] = useState(
isBrowser ? window.innerHeight : 0
);
const store = useRef({
resizeTimer: null,
top: false,
bottom: false,
topOffset: 0,
lastWindowPos: 0
});
const onResizeHandler = useCallback(() => {
clearTimeout(store.current.resizeTimer);
store.current.resizeTimer = setTimeout(() => {
setWindowHeight(window.innerHeight);
}, 250);
}, []);
const onScrollHandler = useCallback(() => {
const { top, bottom, lastWindowPos } = store.current;
const windowPos = window.scrollY;
const bodyHeight = document.body.offsetHeight;
const sidebarHeight = sidebar.current.offsetHeight;
const sidebarOffsetTop = Math.round(
windowPos + sidebar.current.getBoundingClientRect().top
);
if (sidebarHeight > windowHeight) {
if (windowPos > lastWindowPos) {
if (top) {
store.current.top = false;
store.current.topOffset = sidebarOffsetTop > 0 ? sidebarOffsetTop : 0;
sidebar.current.setAttribute(
"style",
`top: ${store.current.topOffset}px;`
);
} else if (
!bottom &&
windowPos + windowHeight > sidebarHeight + sidebarOffsetTop &&
sidebarHeight < bodyHeight
) {
store.current.bottom = true;
sidebar.current.setAttribute("style", "position: fixed; bottom: 0;");
}
} else if (windowPos < lastWindowPos) {
if (bottom) {
store.current.bottom = false;
store.current.topOffset = sidebarOffsetTop > 0 ? sidebarOffsetTop : 0;
sidebar.current.setAttribute(
"style",
`top: ${store.current.topOffset}px;`
);
} else if (!top && windowPos < sidebarOffsetTop) {
store.current.top = true;
sidebar.current.setAttribute("style", "position: fixed;");
}
} else {
store.current.top = store.current.bottom = false;
store.current.topOffset = sidebarOffsetTop ? sidebarOffsetTop : 0;
sidebar.current.setAttribute(
"style",
`top: ${store.current.topOffset}px;`
);
}
} else if (!top) {
store.current.top = true;
sidebar.current.setAttribute("style", "position: fixed;");
}
store.current.lastWindowPos = windowPos;
}, [windowHeight]);
useLayoutEffect(() => {
if (isBrowser) {
onResizeHandler();
onScrollHandler();
window.addEventListener("resize", onResizeHandler);
window.addEventListener("scroll", onScrollHandler);
return () => {
window.removeEventListener("resize", onResizeHandler);
window.removeEventListener("scroll", onScrollHandler);
};
}
}, [onResizeHandler, onScrollHandler]);
const bundle = useMemo(() => [sidebar, open, setOpen, sidebarToggler], [
open
]);
return bundle;
}
export default useSidebar;
|