diff options
author | Sarah German <sgerman@gitlab.com> | 2022-07-27 19:40:59 +0300 |
---|---|---|
committer | Sarah German <sgerman@gitlab.com> | 2022-07-27 19:40:59 +0300 |
commit | 816bb25394206abacabd46295b30cb1c9a0bd7ab (patch) | |
tree | 336a99f1be22c0edeeb4d8bb18f1a5de78748a6a | |
parent | b3f13cc29373981c2a5ebb17b396a891e7cb4e31 (diff) |
Toggle nav on browser resize1082-responsive-navbar
-rw-r--r-- | content/assets/stylesheets/stylesheet.scss | 44 | ||||
-rw-r--r-- | content/frontend/default/components/navigation_toggle.vue | 66 | ||||
-rw-r--r-- | spec/frontend/__mocks__/match_media_mock.js | 15 | ||||
-rw-r--r-- | spec/frontend/default/components/navigation_toggle_spec.js | 27 |
4 files changed, 98 insertions, 54 deletions
diff --git a/content/assets/stylesheets/stylesheet.scss b/content/assets/stylesheets/stylesheet.scss index 5866f6b6..74350e35 100644 --- a/content/assets/stylesheets/stylesheet.scss +++ b/content/assets/stylesheets/stylesheet.scss @@ -88,27 +88,23 @@ ol { &.active { width: $sidebar-width; .nav-toggle { - .arrow { - left: 19px; - transform: rotate(0); - &:nth-child(2) { - left: 25px; - } - } .label { display: block; pointer-events: none; font-size: 0.875rem; color: $gds-gray-700; - margin-left: 30px; flex-shrink: 0; } + svg { + transform: scaleX(-1); + } } .global-nav { visibility: visible; } } .nav-toggle { + display: flex; width: 100%; height: 50px; flex-shrink: 0; @@ -118,34 +114,8 @@ ol { .label { display: none; } - .arrow, - .arrow::before, - .arrow::after { - cursor: pointer; - pointer-events: none; - border-radius: 1px; - height: 2px; - width: 9px; - background: $gds-gray-700; - position: absolute; - display: block; - content: ''; - } - .arrow { - transform: rotate(180deg); - background-color: transparent; - &:nth-child(2) { - left: 19px; - } - } - .arrow::before { - top: 0; - transform: rotate(45deg) translateY(4px); - } - .arrow::after { - top: 0; - bottom: -7px; - transform: rotate(-45deg) translateY(-4px); + svg { + fill: $gds-gray-700; } } .gl-button.nav-toggle { @@ -165,7 +135,7 @@ ol { width: $sidebar-mobile-width; .nav-toggle { - display: block; + display: flex; } } } diff --git a/content/frontend/default/components/navigation_toggle.vue b/content/frontend/default/components/navigation_toggle.vue index 0d50894d..8aaf8bec 100644 --- a/content/frontend/default/components/navigation_toggle.vue +++ b/content/frontend/default/components/navigation_toggle.vue @@ -1,6 +1,8 @@ <script> import GlButton from '@gitlab/ui/src/components/base/button/button.vue'; +const mediaQuery = window.matchMedia(`(max-width: 1199px`); + export default { components: { GlButton, @@ -11,8 +13,21 @@ export default { required: true, }, }, + data() { + return { + width: null, + open: null, + }; + }, + created() { + this.width = window.innerWidth; + mediaQuery.addEventListener('change', this.responsiveToggle); + }, + beforeDestroy() { + mediaQuery.addEventListener('change', this.responsiveToggle); + }, methods: { - toggle() { + toggle(direction = '') { this.targetSelector.forEach((el) => { const target = document.querySelector(el); @@ -20,24 +35,49 @@ export default { return; } - target.classList.toggle('active'); + switch (direction) { + case 'open': + target.classList.add('active'); + this.open = true; + break; + case 'close': + target.classList.remove('active'); + this.open = false; + break; + default: + target.classList.toggle('active'); + this.open = !this.open; + } }); }, + /** + * Toggle the menu visibility based on a change event. + * + * @param {*} e + * A media query change event. + * In this method, we use the "matches" property to check + * if the media query returns true or false. + */ + responsiveToggle(e) { + const newWidth = window.innerWidth; + + // Browser is less wide than 1199px and has decreased in width. + if (e.matches && newWidth < this.width) { + this.toggle('close'); + } + // Browser is wider than 1199px and has increased in width. + if (!e.matches && newWidth > this.width) { + this.toggle('open'); + } + + this.width = newWidth; + }, }, }; </script> <template> - <gl-button class="nav-toggle border-right-0" @click="toggle"> - <!-- - TODO: Replace arrows with 'angle-double-right' icon using the icon component from gitlab-ui - We'll do this once https://gitlab.com/gitlab-org/gitlab-ui/issues/98 is complete. - Issue to add gitlab-ui to this project: https://gitlab.com/gitlab-org/gitlab-docs/issues/443 - --> - <div class="d-flex align-items-center"> - <span class="arrow"></span> - <span class="arrow"></span> - <div class="label">Collapse sidebar</div> - </div> + <gl-button class="nav-toggle gl-border-none gl-pl-5!" icon="angle-double-right" @click="toggle"> + <span class="label gl-ml-2">Collapse sidebar</span> </gl-button> </template> diff --git a/spec/frontend/__mocks__/match_media_mock.js b/spec/frontend/__mocks__/match_media_mock.js new file mode 100644 index 00000000..0f2d0192 --- /dev/null +++ b/spec/frontend/__mocks__/match_media_mock.js @@ -0,0 +1,15 @@ +/** + * Mock matchMedia since it is not available in JSDOM. + * https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + */ +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); diff --git a/spec/frontend/default/components/navigation_toggle_spec.js b/spec/frontend/default/components/navigation_toggle_spec.js index 67dd0338..5eacf0d8 100644 --- a/spec/frontend/default/components/navigation_toggle_spec.js +++ b/spec/frontend/default/components/navigation_toggle_spec.js @@ -3,6 +3,7 @@ */ import { mount } from '@vue/test-utils'; +import '../../__mocks__/match_media_mock'; import NavigationToggle from '../../../../content/frontend/default/components/navigation_toggle.vue'; describe('component: Navigation Toggle', () => { @@ -24,10 +25,6 @@ describe('component: Navigation Toggle', () => { expect(wrapper.find('.label').text()).toEqual('Collapse sidebar'); }); - it('renders two arrow icons', () => { - expect(wrapper.findAll('.arrow').length).toEqual(2); - }); - it('toggles the navigation when the navigation toggle is clicked', () => { const findMenu = () => document.querySelector(`.${className}`); jest.spyOn(findMenu().classList, 'toggle'); @@ -35,4 +32,26 @@ describe('component: Navigation Toggle', () => { wrapper.find('.nav-toggle').trigger('click'); expect(findMenu().classList.toggle).toHaveBeenCalledWith('active'); }); + + it('toggles the navigation when changing breakpoints', () => { + // Mock an event of the media query returning negative. + // This represents the browser not matching "max-width: 1199px," + // meaning we have rezised up to a large window. + wrapper.setData({ width: 500 }); // Mock the starting width. + let mockChangeMatchMediaEvent = { + matches: false, + }; + // Expect an open menu for large windows. + wrapper.vm.responsiveToggle(mockChangeMatchMediaEvent); + expect(wrapper.vm.open).toBe(true); + + // Mock resizing down to a small window, where max-width:1199px is true. + wrapper.setData({ width: 1200 }); // Mock starting width. + mockChangeMatchMediaEvent = { + matches: true, + }; + // The menu should be closed. + wrapper.vm.responsiveToggle(mockChangeMatchMediaEvent); + expect(wrapper.vm.open).toBe(false); + }); }); |