Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-01 15:09:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-01 15:09:50 +0300
commit02f6aecd47c847bb2a7d101678813fe5077ae299 (patch)
tree886df070fd7a3b3f0d364e879b89ea5eb31aeada /app/assets/javascripts/super_sidebar
parent5ffb2b7bcde1c76f939c5dca2ff65bac3404a88f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/super_sidebar')
-rw-r--r--app/assets/javascripts/super_sidebar/components/create_menu.vue78
-rw-r--r--app/assets/javascripts/super_sidebar/components/flyout_menu.vue129
-rw-r--r--app/assets/javascripts/super_sidebar/components/menu_section.vue16
-rw-r--r--app/assets/javascripts/super_sidebar/components/user_bar.vue5
4 files changed, 178 insertions, 50 deletions
diff --git a/app/assets/javascripts/super_sidebar/components/create_menu.vue b/app/assets/javascripts/super_sidebar/components/create_menu.vue
index cea87a70878..97d11e74482 100644
--- a/app/assets/javascripts/super_sidebar/components/create_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/create_menu.vue
@@ -1,7 +1,7 @@
<script>
import {
GlDisclosureDropdown,
- GlTooltip,
+ GlTooltipDirective,
GlDisclosureDropdownGroup,
GlDisclosureDropdownItem,
} from '@gitlab/ui';
@@ -22,9 +22,11 @@ export default {
GlDisclosureDropdown,
GlDisclosureDropdownGroup,
GlDisclosureDropdownItem,
- GlTooltip,
InviteMembersTrigger,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
i18n: {
createNew: __('Create new...'),
},
@@ -59,45 +61,37 @@ export default {
</script>
<template>
- <div>
- <gl-disclosure-dropdown
- category="tertiary"
- icon="plus"
- no-caret
- text-sr-only
- :toggle-text="$options.i18n.createNew"
- :toggle-id="$options.toggleId"
- :dropdown-offset="dropdownOffset"
- data-qa-selector="new_menu_toggle"
- data-testid="new-menu-toggle"
- @shown="dropdownOpen = true"
- @hidden="dropdownOpen = false"
- >
- <gl-disclosure-dropdown-group
- v-for="(group, index) in groups"
- :key="group.name"
- :bordered="index !== 0"
- :group="group"
- >
- <template v-for="groupItem in group.items">
- <invite-members-trigger
- v-if="isInvitedMembers(groupItem)"
- :key="`${groupItem.text}-trigger`"
- trigger-source="top_nav"
- :trigger-element="$options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN"
- />
- <gl-disclosure-dropdown-item v-else :key="groupItem.text" :item="groupItem" />
- </template>
- </gl-disclosure-dropdown-group>
- </gl-disclosure-dropdown>
- <gl-tooltip
- v-if="!dropdownOpen"
- :target="`#${$options.toggleId}`"
- placement="bottom"
- container="#super-sidebar"
- noninteractive
+ <gl-disclosure-dropdown
+ v-gl-tooltip:super-sidebar.hover.noninteractive.ds500.bottom="
+ dropdownOpen ? '' : $options.i18n.createNew
+ "
+ category="tertiary"
+ icon="plus"
+ no-caret
+ text-sr-only
+ :toggle-text="$options.i18n.createNew"
+ :toggle-id="$options.toggleId"
+ :dropdown-offset="dropdownOffset"
+ data-qa-selector="new_menu_toggle"
+ data-testid="new-menu-toggle"
+ @shown="dropdownOpen = true"
+ @hidden="dropdownOpen = false"
+ >
+ <gl-disclosure-dropdown-group
+ v-for="(group, index) in groups"
+ :key="group.name"
+ :bordered="index !== 0"
+ :group="group"
>
- {{ $options.i18n.createNew }}
- </gl-tooltip>
- </div>
+ <template v-for="groupItem in group.items">
+ <invite-members-trigger
+ v-if="isInvitedMembers(groupItem)"
+ :key="`${groupItem.text}-trigger`"
+ trigger-source="top_nav"
+ :trigger-element="$options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN"
+ />
+ <gl-disclosure-dropdown-item v-else :key="groupItem.text" :item="groupItem" />
+ </template>
+ </gl-disclosure-dropdown-group>
+ </gl-disclosure-dropdown>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/flyout_menu.vue b/app/assets/javascripts/super_sidebar/components/flyout_menu.vue
index fa7960da2f4..441b19cfe23 100644
--- a/app/assets/javascripts/super_sidebar/components/flyout_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/flyout_menu.vue
@@ -2,6 +2,23 @@
import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/dom';
import NavItem from './nav_item.vue';
+// Flyout menus are shown when the MenuSection's title is hovered with the mouse.
+// Their position is dynamically calculated with floating-ui.
+//
+// Since flyout menus show all NavItems of a section, they can be very long and
+// a user might want to move their mouse diagonally from the section title down
+// to last nav item in the flyout. But this mouse movement over other sections
+// would loose hover and close the flyout, opening another section's flyout.
+// To avoid this annoyance, our flyouts come with a "diagonal tolerance". This
+// is an area between the current mouse position and the top- and bottom-left
+// corner of the flyout itself. While the mouse stays within this area and
+// reaches the flyout before a timer expires, the native browser hover stays
+// within the component.
+// This is done with an transparent SVG positioned left of the flyout menu,
+// overlapping the sidebar. The SVG itself ignores pointer events but its two
+// triangles, one above the section title, one below, do listen to events,
+// keeping hover.
+
export default {
name: 'FlyoutMenu',
components: { NavItem },
@@ -15,13 +32,45 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ currentMouseX: 0,
+ flyoutX: 0,
+ flyoutY: 0,
+ flyoutHeight: 0,
+ hoverTimeoutId: null,
+ showSVG: true,
+ targetRect: null,
+ };
+ },
cleanupFunction: undefined,
+ computed: {
+ topSVGPoints() {
+ const x = (this.currentMouseX / this.targetRect.width) * 100;
+ let y = ((this.targetRect.top - this.flyoutY) / this.flyoutHeight) * 100;
+ y += 1; // overlap title to not loose hover
+
+ return `${x}, ${y} 100, 0 100, ${y}`;
+ },
+ bottomSVGPoints() {
+ const x = (this.currentMouseX / this.targetRect.width) * 100;
+ let y = ((this.targetRect.bottom - this.flyoutY) / this.flyoutHeight) * 100;
+ y -= 1; // overlap title to not loose hover
+
+ return `${x}, ${y} 100, ${y} 100, 100`;
+ },
+ },
+ created() {
+ const target = document.querySelector(`#${this.targetId}`);
+ target.addEventListener('mousemove', this.onMouseMove);
+ },
mounted() {
const target = document.querySelector(`#${this.targetId}`);
const flyout = document.querySelector(`#${this.targetId}-flyout`);
+ const sidebar = document.querySelector('#super-sidebar');
- function updatePosition() {
- return computePosition(target, flyout, {
+ const updatePosition = () =>
+ computePosition(target, flyout, {
middleware: [offset({ alignmentAxis: -12 }), flip(), shift()],
placement: 'right-start',
strategy: 'fixed',
@@ -30,13 +79,46 @@ export default {
left: `${x}px`,
top: `${y}px`,
});
+ this.flyoutX = x;
+ this.flyoutY = y;
+ this.flyoutHeight = flyout.clientHeight;
+
+ // Flyout coordinates are relative to the sidebar which can be
+ // shifted down by the performance-bar etc.
+ // Adjust viewport coordinates from getBoundingClientRect:
+ const targetRect = target.getBoundingClientRect();
+ const sidebarRect = sidebar.getBoundingClientRect();
+ this.targetRect = {
+ top: targetRect.top - sidebarRect.top,
+ bottom: targetRect.bottom - sidebarRect.top,
+ width: targetRect.width,
+ };
});
- }
this.$options.cleanupFunction = autoUpdate(target, flyout, updatePosition);
},
beforeUnmount() {
this.$options.cleanupFunction();
+ clearTimeout(this.hoverTimeoutId);
+ },
+ beforeDestroy() {
+ const target = document.querySelector(`#${this.targetId}`);
+ target.removeEventListener('mousemove', this.onMouseMove);
+ },
+ methods: {
+ startHoverTimeout() {
+ this.hoverTimeoutId = setTimeout(() => {
+ this.showSVG = false;
+ this.$emit('mouseleave');
+ }, 1000);
+ },
+ stopHoverTimeout() {
+ clearTimeout(this.hoverTimeoutId);
+ },
+ onMouseMove(e) {
+ // add some wiggle room to the left of mouse cursor
+ this.currentMouseX = Math.max(0, e.clientX - 20);
+ },
},
};
</script>
@@ -49,8 +131,8 @@ export default {
@mouseleave="$emit('mouseleave')"
>
<ul
- v-if="items.length > 0"
class="gl-min-w-20 gl-max-w-34 gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-100 gl-shadow-md gl-bg-white gl-p-2 gl-pb-1 gl-list-style-none"
+ @mouseenter="showSVG = false"
>
<nav-item
v-for="item of items"
@@ -61,5 +143,44 @@ export default {
@pin-remove="(itemId) => $emit('pin-remove', itemId)"
/>
</ul>
+ <svg
+ v-if="targetRect && showSVG"
+ :width="flyoutX"
+ :height="flyoutHeight"
+ viewBox="0 0 100 100"
+ preserveAspectRatio="none"
+ :style="{
+ top: flyoutY + 'px',
+ }"
+ >
+ <polygon
+ ref="topSVG"
+ :points="topSVGPoints"
+ fill="transparent"
+ @mouseenter="startHoverTimeout"
+ @mouseleave="stopHoverTimeout"
+ />
+ <polygon
+ ref="bottomSVG"
+ :points="bottomSVGPoints"
+ fill="transparent"
+ @mouseenter="startHoverTimeout"
+ @mouseleave="stopHoverTimeout"
+ />
+ </svg>
</div>
</template>
+
+<style scoped>
+svg {
+ pointer-events: none;
+
+ position: fixed;
+ right: 0;
+}
+
+svg polygon,
+svg rect {
+ pointer-events: auto;
+}
+</style>
diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue
index d2d45ca7b6e..6b5002e1aa8 100644
--- a/app/assets/javascripts/super_sidebar/components/menu_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue
@@ -79,15 +79,26 @@ export default {
isExpanded(newIsExpanded) {
this.$emit('collapse-toggle', newIsExpanded);
this.keepFlyoutClosed = !this.newIsExpanded;
+ if (!newIsExpanded) {
+ this.isMouseOverFlyout = false;
+ }
},
},
methods: {
handlePointerover(e) {
+ if (!this.hasFlyout) return;
+
this.isMouseOverSection = e.pointerType === 'mouse';
},
handlePointerleave() {
- this.isMouseOverSection = false;
+ if (!this.hasFlyout) return;
+
this.keepFlyoutClosed = false;
+ // delay state change. otherwise the flyout menu gets removed before it
+ // has a chance to emit its mouseover event.
+ setTimeout(() => {
+ this.isMouseOverSection = false;
+ }, 5);
},
},
};
@@ -129,8 +140,7 @@ export default {
</button>
<flyout-menu
- v-if="hasFlyout"
- v-show="isMouseOver && !isExpanded && !keepFlyoutClosed"
+ v-if="hasFlyout && isMouseOver && !isExpanded && !keepFlyoutClosed && item.items.length > 0"
:target-id="`menu-section-button-${itemId}`"
:items="item.items"
@mouseover="isMouseOverFlyout = true"
diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue
index ca4412fc504..8112b479c19 100644
--- a/app/assets/javascripts/super_sidebar/components/user_bar.vue
+++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue
@@ -127,7 +127,10 @@ export default {
tooltip-container="super-sidebar"
data-testid="super-sidebar-collapse-button"
/>
- <create-menu v-if="sidebarData.is_logged_in" :groups="sidebarData.create_new_menu_groups" />
+ <create-menu
+ v-if="sidebarData.is_logged_in && sidebarData.create_new_menu_groups.length > 0"
+ :groups="sidebarData.create_new_menu_groups"
+ />
<user-menu v-if="sidebarData.is_logged_in" :data="sidebarData" />