BigW Consortium Gitlab

fly_out_nav.js 4.9 KB
Newer Older
1
import Cookies from 'js-cookie';
2
import bp from './breakpoints';
3

4
const HIDE_INTERVAL_TIMEOUT = 300;
5 6 7 8 9 10 11 12 13
const IS_OVER_CLASS = 'is-over';
const IS_ABOVE_CLASS = 'is-above';
const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out';
let currentOpenMenu = null;
let menuCornerLocs;
let timeoutId;

export const mousePos = [];

14
export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
15 16 17

export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);

18 19 20 21
let headerHeight = 50;

export const getHeaderHeight = () => headerHeight;

22 23 24 25 26 27 28 29 30
export const canShowActiveSubItems = (el) => {
  const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';

  if (el.classList.contains('active') && !isHiddenByMedia) {
    return Cookies.get('sidebar_collapsed') === 'true';
  }

  return true;
};
31

32
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
33

34 35 36 37 38 39 40 41 42 43 44 45 46
export const getHideSubItemsInterval = () => {
  if (!currentOpenMenu) return 0;

  const currentMousePos = mousePos[mousePos.length - 1];
  const prevMousePos = mousePos[0];
  const currentMousePosY = currentMousePos.y;
  const [menuTop, menuBottom] = menuCornerLocs;

  if (currentMousePosY < menuTop.y ||
      currentMousePosY > menuBottom.y) return 0;

  if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
    slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) {
47
    return HIDE_INTERVAL_TIMEOUT;
48 49 50 51 52
  }

  return 0;
};

53 54 55 56
export const calculateTop = (boundingRect, outerHeight) => {
  const windowHeight = window.innerHeight;
  const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);

57 58
  return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height :
    boundingRect.top;
59 60
};

61 62
export const hideMenu = (el) => {
  if (!el) return;
63

64
  const parentEl = el.parentNode;
65

66 67 68 69 70 71
  el.style.display = ''; // eslint-disable-line no-param-reassign
  el.style.transform = ''; // eslint-disable-line no-param-reassign
  el.classList.remove(IS_ABOVE_CLASS);
  parentEl.classList.remove(IS_OVER_CLASS);
  parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);

72
  setOpenMenu();
73
};
74

75
export const moveSubItemsToPosition = (el, subItems) => {
76
  const boundingRect = el.getBoundingClientRect();
Phil Hughes committed
77
  const top = calculateTop(boundingRect, subItems.offsetHeight);
78 79
  const isAbove = top < boundingRect.top;

80
  subItems.classList.add('fly-out-list');
81
  subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
82 83 84 85 86 87 88 89 90 91 92 93 94

  const subItemsRect = subItems.getBoundingClientRect();

  menuCornerLocs = [
    {
      x: subItemsRect.left, // left position of the sub items
      y: subItemsRect.top, // top position of the sub items
    },
    {
      x: subItemsRect.left, // left position of the sub items
      y: subItemsRect.top + subItemsRect.height, // bottom position of the sub items
    },
  ];
95 96

  if (isAbove) {
97
    subItems.classList.add(IS_ABOVE_CLASS);
98 99 100
  }
};

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
export const showSubLevelItems = (el) => {
  const subItems = el.querySelector('.sidebar-sub-level-items');

  if (!canShowSubItems() || !canShowActiveSubItems(el)) return;

  el.classList.add(IS_OVER_CLASS);

  if (!subItems) return;

  subItems.style.display = 'block';
  el.classList.add(IS_SHOWING_FLY_OUT_CLASS);

  setOpenMenu(subItems);
  moveSubItemsToPosition(el, subItems);
};

export const mouseEnterTopItems = (el) => {
  clearTimeout(timeoutId);

  timeoutId = setTimeout(() => {
    if (currentOpenMenu) hideMenu(currentOpenMenu);

    showSubLevelItems(el);
  }, getHideSubItemsInterval());
};

export const mouseLeaveTopItem = (el) => {
Phil Hughes committed
128
  const subItems = el.querySelector('.sidebar-sub-level-items');
129

130 131
  if (!canShowSubItems() || !canShowActiveSubItems(el) ||
      (subItems && subItems === currentOpenMenu)) return;
132

133 134 135 136
  el.classList.remove(IS_OVER_CLASS);
};

export const documentMouseMove = (e) => {
137 138 139 140
  mousePos.push({
    x: e.clientX,
    y: e.clientY,
  });
141 142

  if (mousePos.length > 6) mousePos.shift();
143 144
};

145
export default () => {
146
  const sidebar = document.querySelector('.sidebar-top-level-items');
147 148 149

  if (!sidebar) return;

150 151 152 153 154 155 156 157 158
  const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];

  sidebar.addEventListener('mouseleave', () => {
    clearTimeout(timeoutId);

    timeoutId = setTimeout(() => {
      if (currentOpenMenu) hideMenu(currentOpenMenu);
    }, getHideSubItemsInterval());
  });
159

160 161
  headerHeight = document.querySelector('.nav-sidebar').offsetTop;

162
  items.forEach((el) => {
163 164 165 166 167 168 169 170 171 172 173
    const subItems = el.querySelector('.sidebar-sub-level-items');

    if (subItems) {
      subItems.addEventListener('mouseleave', () => {
        clearTimeout(timeoutId);
        hideMenu(currentOpenMenu);
      });
    }

    el.addEventListener('mouseenter', e => mouseEnterTopItems(e.currentTarget));
    el.addEventListener('mouseleave', e => mouseLeaveTopItem(e.currentTarget));
174
  });
175 176

  document.addEventListener('mousemove', documentMouseMove);
177
};