diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js new file mode 100644 index 00000000000..202a8c18c22 --- /dev/null +++ b/app/assets/javascripts/fly_out_nav.js @@ -0,0 +1,35 @@ +export const calculateTop = (boundingRect, outerHeight) => { + const windowHeight = window.innerHeight; + const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); + + return bottomOverflow < 0 ? boundingRect.top - Math.abs(bottomOverflow) : boundingRect.top; +}; + +export const createArrowStyles = (boundingRect, top) => `.sidebar-sub-level-items::before { transform: translate3d(0, ${boundingRect.top - top}px, 0); }`; + +export default () => { + const style = document.createElement('style'); + document.head.appendChild(style); + + $('.sidebar-top-level-items > li:not(.active)').on('mouseover', (e) => { + const $this = e.currentTarget; + const $subitems = $('.sidebar-sub-level-items', $this).show(); + + if ($subitems.length) { + const boundingRect = $this.getBoundingClientRect(); + const top = calculateTop(boundingRect, $subitems.outerHeight()); + + $subitems.css({ + transform: `translate3d(0, ${top}px, 0)`, + }); + + style.sheet.insertRule(createArrowStyles(boundingRect, top), 0); + } + }).on('mouseout', (e) => { + $('.sidebar-sub-level-items', e.currentTarget).hide(); + + if (style.sheet.rules.length) { + style.sheet.deleteRule(0); + } + }); +}; diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 71064ccc539..e0363e091a7 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,5 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ +import Cookies from 'js-cookie'; import _ from 'underscore'; +import initFlyOutNav from './fly_out_nav'; (function() { var hideEndFade; @@ -54,5 +56,9 @@ import _ from 'underscore'; $(() => { $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); + + if (Cookies.get('new_nav') === 'true') { + initFlyOutNav(); + } }); }).call(window); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 95c0082e8dd..26c67fb721c 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -347,31 +347,4 @@ $(function () { gl.utils.renderTimeago(); $(document).trigger('init.scrolling-tabs'); - - const style = document.createElement("style"); - document.head.appendChild(style); - - $('.sidebar-top-level-items > li:not(.active)').on('mouseover', (e) => { - const windowHeight = window.innerHeight; - const $this = e.currentTarget; - const $subitems = $('.sidebar-sub-level-items', $this).show(); - - if ($subitems.length) { - const boundingRect = $this.getBoundingClientRect(); - const bottomOverflow = windowHeight - (boundingRect.top + $subitems.outerHeight()); - const top = bottomOverflow < 0 ? boundingRect.top - Math.abs(bottomOverflow) : boundingRect.top; - - $subitems.css({ - transform: `translate3d(0, ${top}px, 0)`, - }); - - style.sheet.insertRule(`.sidebar-sub-level-items::before { transform: translate3d(0, ${boundingRect.top - top}px, 0); }`, 0); - } - }).on('mouseout', (e) => { - $('.sidebar-sub-level-items', e.currentTarget).hide(); - - if (style.sheet.rules.length) { - style.sheet.deleteRule(0); - } - }); }); diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js new file mode 100644 index 00000000000..bbf3eb6f582 --- /dev/null +++ b/spec/javascripts/fly_out_nav_spec.js @@ -0,0 +1,37 @@ +import { calculateTop, createArrowStyles } from '~/fly_out_nav'; + +describe('Fly out sidebar navigation', () => { + describe('calculateTop', () => { + it('returns boundingRect top', () => { + const boundingRect = { + top: 100, + }; + + expect( + calculateTop(boundingRect, 100), + ).toBe(100); + }); + + it('returns boundingRect - bottomOverflow', () => { + const boundingRect = { + top: window.innerHeight, + }; + + expect( + calculateTop(boundingRect, 100), + ).toBe(window.innerHeight - 100); + }); + }); + + describe('createArrowStyles', () => { + it('returns translate3d styles', () => { + const boundingRect = { + top: 100, + }; + + expect( + createArrowStyles(boundingRect, 50), + ).toContain('translate3d(0, 50px, 0)'); + }); + }); +});