aboutsummaryrefslogtreecommitdiff
path: root/src/hooks/useSidebar.js
blob: 519f48e4f007c022d92b073c5a963c5ec30dec1c (plain) (blame)
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;