summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/fly_out_nav.js22
-rw-r--r--app/assets/javascripts/layout_nav.js3
-rw-r--r--app/assets/stylesheets/new_sidebar.scss41
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml2
-rw-r--r--spec/javascripts/fly_out_nav_spec.js25
5 files changed, 92 insertions, 1 deletions
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
new file mode 100644
index 00000000000..1ae2e72410f
--- /dev/null
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -0,0 +1,22 @@
+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 default () => {
+ $('.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)`,
+ });
+ }
+ }).on('mouseout', e => $('.sidebar-sub-level-items', e.currentTarget).hide());
+};
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 6186ffe20b3..5c1ba416a03 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -2,6 +2,7 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import NewNavSidebar from './new_sidebar';
+import initFlyOutNav from './fly_out_nav';
(function() {
var hideEndFade;
@@ -58,6 +59,8 @@ import NewNavSidebar from './new_sidebar';
if (Cookies.get('new_nav') === 'true') {
const newNavSidebar = new NewNavSidebar();
newNavSidebar.bindEvents();
+
+ initFlyOutNav();
}
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index ae43197a1a6..78f278c1669 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -193,6 +193,46 @@ $new-sidebar-width: 220px;
.sidebar-top-level-items {
> li {
+ > a {
+ @media (min-width: $screen-sm-min) {
+ margin-right: 2px;
+ }
+ }
+
+ &:not(.active) {
+ .sidebar-sub-level-items {
+ @media (min-width: $screen-sm-min) {
+ position: fixed;
+ top: 0;
+ left: 220px;
+ width: 150px;
+ background-color: $hover-background;
+ box-shadow: 2px 1px 3px $dropdown-shadow-color;
+ border-radius: 0 3px 3px 0;
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 44px;
+ left: -30px;
+ right: 0;
+ bottom: 0;
+ z-index: -1;
+ }
+
+ a {
+ color: rgba($white-light, .9);
+
+ &:hover,
+ &:focus {
+ color: $white-light;
+ background-color: rgba($black, .12);
+ }
+ }
+ }
+ }
+ }
+
.badge {
float: right;
background-color: $inactive-badge-background;
@@ -212,6 +252,7 @@ $new-sidebar-width: 220px;
}
}
+ &:not(.active):hover > a,
> a:hover {
background-color: $hover-background;
color: $hover-color;
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index 21f175291fa..15327847b17 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -177,7 +177,7 @@
%ul.sidebar-sub-level-items
- can_edit = can?(current_user, :admin_project, @project)
- if can_edit
- = nav_link(controller: :projects) do
+ = nav_link(path: %w[projects#edit]) do
= link_to edit_project_path(@project), title: 'General' do
%span
General
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
new file mode 100644
index 00000000000..0e71e2a87e5
--- /dev/null
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -0,0 +1,25 @@
+import { calculateTop } 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);
+ });
+ });
+});