Commit 0fb58cf6 authored by Clement Ho's avatar Clement Ho

Merge branch...

Merge branch '3181-no-clues-to-indicate-the-list-in-go-to-board-dropdown-is-scrollable' into 'master'

Resolve "No clues to indicate the list in Go to board dropdown is scrollable"

Closes #3181

See merge request gitlab-org/gitlab-ee!3384
parents 79350ba8 abba4521
import Vue from 'vue';
import { throttle } from 'underscore';
import BoardForm from './board_form.vue';
(() => {
......@@ -10,6 +11,7 @@ import BoardForm from './board_form.vue';
Store.createNewListDropdownData();
gl.issueBoards.BoardsSelector = Vue.extend({
name: 'boards-selector',
components: {
BoardForm,
},
......@@ -22,13 +24,22 @@ import BoardForm from './board_form.vue';
type: String,
required: true,
},
throttleDuration: {
type: Number,
default: 200,
},
},
data() {
return {
open: false,
loading: true,
hasScrollFade: false,
scrollFadeInitialized: false,
boards: [],
state: Store.state,
throttledSetScrollFade: throttle(this.setScrollFade, this.throttleDuration),
contentClientHeight: 0,
maxPosition: 0,
};
},
watch: {
......@@ -60,6 +71,11 @@ import BoardForm from './board_form.vue';
showDelete() {
return this.boards.length > 1;
},
scrollFadeClass() {
return {
'fade-out': !this.hasScrollFade,
};
},
},
methods: {
showPage(page) {
......@@ -81,11 +97,32 @@ import BoardForm from './board_form.vue';
this.loading = false;
this.boards = json;
})
.then(() => this.$nextTick()) // Wait for boards list in DOM
.then(this.setScrollFade)
.catch(() => {
this.loading = false;
});
}
},
isScrolledUp() {
const { content } = this.$refs;
const currentPosition = this.contentClientHeight + content.scrollTop;
return content && currentPosition < this.maxPosition;
},
initScrollFade() {
this.scrollFadeInitialized = true;
const { content } = this.$refs;
this.contentClientHeight = content.clientHeight;
this.maxPosition = content.scrollHeight;
},
setScrollFade() {
if (!this.scrollFadeInitialized) this.initScrollFade();
this.hasScrollFade = this.isScrolledUp();
},
},
created() {
this.state.currentBoard = this.currentBoard;
......
......@@ -608,7 +608,7 @@
}
.dropdown-content {
max-height: 215px;
max-height: $dropdown-max-height;
overflow-y: auto;
}
......@@ -1059,3 +1059,28 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
margin: 0;
}
}
.dropdown-content-faded-mask {
position: relative;
.dropdown-list {
max-height: $dropdown-max-height;
overflow-y: auto;
position: relative;
}
&::after {
height: $dropdown-fade-mask-height;
width: 100%;
position: absolute;
bottom: 0;
background: linear-gradient(to top, $white-light 0, rgba($white-light, 0));
transition: opacity $fade-mask-transition-duration $fade-mask-transition-curve;
content: '';
pointer-events: none;
}
&.fade-out::after {
opacity: 0;
}
}
......@@ -344,6 +344,7 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San
* Dropdowns
*/
$dropdown-width: 300px;
$dropdown-max-height: 215px;
$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
......@@ -360,6 +361,7 @@ $dropdown-loading-bg: rgba(#fff, .6);
$dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%);
$dropdown-item-hover-bg: $gray-darker;
$dropdown-fade-mask-height: 32px;
/*
* Filtered Search
......@@ -571,6 +573,8 @@ $label-border-radius: 100px;
* Animation
*/
$fade-in-duration: 200ms;
$fade-mask-transition-duration: .1s;
$fade-mask-transition-curve: ease-in-out;
/*
* Lint
......
---
title: Add fade mask to the bottom of the boards selector dropdown list if it can
be scrolled down
merge_request: 3384
author:
type: other
......@@ -2,25 +2,26 @@
- milestone_filter_opts = { format: :json }
- milestone_filter_opts = milestone_filter_opts.merge(only_group_milestones: true) if board.group_board?
%boards-selector{ "inline-template" => true,
%boards-selector.js-boards-selector{ "inline-template" => true,
":current-board" => current_board_json,
"milestone-path" => milestones_filter_path(milestone_filter_opts) }
%span.boards-selector-wrapper
%span.boards-selector-wrapper.js-boards-selector-wrapper
.dropdown
%button.dropdown-menu-toggle{ "v-on:click" => "loadBoards",
%button.dropdown-menu-toggle.js-dropdown-toggle{ "v-on:click" => "loadBoards",
data: { toggle: "dropdown" } }
{{ board.name }}
= icon("chevron-down")
.dropdown-menu{ ":class" => "{ 'is-loading': loading }" }
.dropdown-content
%ul{ "v-if" => "!loading" }
%li{ "v-for" => "board in boards" }
.dropdown-content-faded-mask.js-scroll-fade{ ":class" => "scrollFadeClass" }
%ul.dropdown-list.js-dropdown-list{ "v-if" => "!loading", "v-on:scroll.passive" => "throttledSetScrollFade", ref: "content" }
%li.dropdown-item.js-dropdown-item{ "v-for" => "board in boards" }
%a{ ":href" => "'#{board_base_url}/' + board.id" }
{{ board.name }}
- if !multiple_boards_available? && current_board_parent.boards.size > 1
%li
.small.unclickable
Some of your boards are hidden, activate a license to see them again.
.dropdown-loading{ "v-if" => "loading" }
= icon("spin spinner")
......
/* global BoardService */
import Vue from 'vue';
import BoardService from '~/boards/services/board_service';
import '~/boards/components/boards_selector';
import setTimeoutPromiseHelper from '../../helpers/set_timeout_promise_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
const throttleDuration = 1;
function waitForScroll() {
return Vue.nextTick()
.then(() => setTimeoutPromiseHelper(throttleDuration))
.then(() => Vue.nextTick());
}
describe('BoardsSelector', () => {
let vm;
let scrollContainer;
let scrollFade;
let boardServiceResponse;
const boards = new Array(20).fill()
.map((board, id) => {
const name = `board${id}`;
return {
id,
name,
};
});
beforeEach((done) => {
loadFixtures('boards/show.html.raw');
window.gl = window.gl || {};
window.gl.boardService = new BoardService({
boardsEndpoint: '',
listsEndpoint: '',
bulkUpdatePath: '',
boardId: '',
});
boardServiceResponse = Promise.resolve({
json() {
return boards;
},
});
spyOn(BoardService.prototype, 'allBoards').and.returnValue(boardServiceResponse);
vm = mountComponent(gl.issueBoards.BoardsSelector, {
throttleDuration,
currentBoard: {},
milestonePath: '',
}, document.querySelector('.js-boards-selector'));
vm.$el.querySelector('.js-dropdown-toggle').click();
boardServiceResponse
.then(() => vm.$nextTick())
.then(() => {
scrollFade = vm.$el.querySelector('.js-scroll-fade');
scrollContainer = scrollFade.querySelector('.js-dropdown-list');
scrollContainer.style.maxHeight = '100px';
scrollContainer.style.overflowY = 'scroll';
})
.then(done)
.catch(done.fail);
});
afterEach(() => {
vm.$destroy();
window.gl.boardService = undefined;
});
it('shows the scroll fade if isScrolledUp', (done) => {
scrollContainer.scrollTop = 0;
waitForScroll()
.then(() => {
expect(scrollFade.classList.contains('fade-out')).toEqual(false);
})
.then(done)
.catch(done.fail);
});
it('hides the scroll fade if not isScrolledUp', (done) => {
scrollContainer.scrollTop = scrollContainer.scrollHeight;
waitForScroll()
.then(() => {
expect(scrollFade.classList.contains('fade-out')).toEqual(true);
})
.then(done)
.catch(done.fail);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment