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-13 00:10:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-13 00:10:52 +0300
commita60e53c7671c299432f0c255ffaf0e0c9fa9eeab (patch)
tree9682f6acc0c40bd80beb79b9feec645f6252e8e0 /app/assets/javascripts/super_sidebar/components
parent753eb533e509464184ad267fb894d2c08d0d1ba6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/super_sidebar/components')
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_hover_peek_behavior.vue126
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_peek_behavior.vue14
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue40
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar_toggle.vue20
4 files changed, 177 insertions, 23 deletions
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_hover_peek_behavior.vue b/app/assets/javascripts/super_sidebar/components/sidebar_hover_peek_behavior.vue
new file mode 100644
index 00000000000..df432a1928a
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_hover_peek_behavior.vue
@@ -0,0 +1,126 @@
+<script>
+import { getCssClassDimensions } from '~/lib/utils/css_utils';
+import Tracking from '~/tracking';
+import {
+ JS_TOGGLE_EXPAND_CLASS,
+ SUPER_SIDEBAR_PEEK_OPEN_DELAY,
+ SUPER_SIDEBAR_PEEK_CLOSE_DELAY,
+ SUPER_SIDEBAR_PEEK_STATE_CLOSED as STATE_CLOSED,
+ SUPER_SIDEBAR_PEEK_STATE_WILL_OPEN as STATE_WILL_OPEN,
+ SUPER_SIDEBAR_PEEK_STATE_OPEN as STATE_OPEN,
+ SUPER_SIDEBAR_PEEK_STATE_WILL_CLOSE as STATE_WILL_CLOSE,
+} from '../constants';
+
+export default {
+ name: 'SidebarHoverPeek',
+ mixins: [Tracking.mixin()],
+ props: {
+ isMouseOverSidebar: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ created() {
+ // Nothing needs to observe these properties, so they are not reactive.
+ this.state = null;
+ this.openTimer = null;
+ this.closeTimer = null;
+ this.xSidebarEdge = null;
+ this.isMouseWithinSidebarArea = false;
+ },
+ async mounted() {
+ await this.$nextTick();
+ this.xSidebarEdge = getCssClassDimensions('super-sidebar').width;
+ document.addEventListener('mousemove', this.onMouseMove);
+ document.documentElement.addEventListener('mouseleave', this.onDocumentLeave);
+ document
+ .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
+ .addEventListener('mouseenter', this.onMouseEnter);
+ document
+ .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
+ .addEventListener('mouseleave', this.onMouseLeave);
+ this.changeState(STATE_CLOSED);
+ },
+ beforeDestroy() {
+ document.removeEventListener('mousemove', this.onMouseMove);
+ document.documentElement.removeEventListener('mouseleave', this.onDocumentLeave);
+ document
+ .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
+ .removeEventListener('mouseenter', this.onMouseEnter);
+ document
+ .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
+ .removeEventListener('mouseleave', this.onMouseLeave);
+ this.clearTimers();
+ },
+ methods: {
+ onMouseMove({ clientX }) {
+ if (clientX < this.xSidebarEdge) {
+ this.isMouseWithinSidebarArea = true;
+ } else {
+ this.isMouseWithinSidebarArea = false;
+ if (!this.isMouseOverSidebar && this.state === STATE_OPEN) {
+ this.willClose();
+ }
+ }
+ },
+ onDocumentLeave() {
+ this.isMouseWithinSidebarArea = false;
+ if (this.state === STATE_OPEN) {
+ this.willClose();
+ } else if (this.state === STATE_WILL_OPEN) {
+ this.close();
+ }
+ },
+ onMouseEnter() {
+ clearTimeout(this.closeTimer);
+ this.willOpen();
+ },
+ onMouseLeave() {
+ clearTimeout(this.openTimer);
+ if (this.isMouseWithinSidebarArea || this.isMouseOverSidebar) return;
+ this.willClose();
+ },
+ willClose() {
+ this.changeState(STATE_WILL_CLOSE);
+ this.closeTimer = setTimeout(this.close, SUPER_SIDEBAR_PEEK_CLOSE_DELAY);
+ },
+ willOpen() {
+ this.changeState(STATE_WILL_OPEN);
+ this.openTimer = setTimeout(this.open, SUPER_SIDEBAR_PEEK_OPEN_DELAY);
+ },
+ open() {
+ this.changeState(STATE_OPEN);
+ this.clearTimers();
+ this.track('nav_hover_peek', {
+ label: 'nav_sidebar_toggle',
+ property: 'nav_sidebar',
+ });
+ },
+ close() {
+ if (this.isMouseWithinSidebarArea) return;
+ this.changeState(STATE_CLOSED);
+ this.clearTimers();
+ },
+ clearTimers() {
+ clearTimeout(this.closeTimer);
+ clearTimeout(this.openTimer);
+ },
+ /**
+ * Switches to the new state, and emits a change event.
+ *
+ * If the given state is the current state, do nothing.
+ *
+ * @param {string} state The state to transition to.
+ */
+ changeState(state) {
+ if (this.state === state) return;
+ this.state = state;
+ this.$emit('change', state);
+ },
+ },
+ render() {
+ return null;
+ },
+};
+</script>
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_peek_behavior.vue b/app/assets/javascripts/super_sidebar/components/sidebar_peek_behavior.vue
index ec728b4af9e..a20e37b945a 100644
--- a/app/assets/javascripts/super_sidebar/components/sidebar_peek_behavior.vue
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_peek_behavior.vue
@@ -1,12 +1,14 @@
<script>
import { getCssClassDimensions } from '~/lib/utils/css_utils';
import Tracking from '~/tracking';
-import { SUPER_SIDEBAR_PEEK_OPEN_DELAY, SUPER_SIDEBAR_PEEK_CLOSE_DELAY } from '../constants';
-
-export const STATE_CLOSED = 'closed';
-export const STATE_WILL_OPEN = 'will-open';
-export const STATE_OPEN = 'open';
-export const STATE_WILL_CLOSE = 'will-close';
+import {
+ SUPER_SIDEBAR_PEEK_OPEN_DELAY,
+ SUPER_SIDEBAR_PEEK_CLOSE_DELAY,
+ SUPER_SIDEBAR_PEEK_STATE_CLOSED as STATE_CLOSED,
+ SUPER_SIDEBAR_PEEK_STATE_WILL_OPEN as STATE_WILL_OPEN,
+ SUPER_SIDEBAR_PEEK_STATE_OPEN as STATE_OPEN,
+ SUPER_SIDEBAR_PEEK_STATE_WILL_CLOSE as STATE_WILL_CLOSE,
+} from '../constants';
export default {
name: 'SidebarPeek',
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index fa366deeac8..2c939487784 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -4,14 +4,20 @@ import { Mousetrap } from '~/lib/mousetrap';
import { keysFor, TOGGLE_SUPER_SIDEBAR } from '~/behaviors/shortcuts/keybindings';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
-import { sidebarState } from '../constants';
+import {
+ sidebarState,
+ SUPER_SIDEBAR_PEEK_STATE_CLOSED as STATE_CLOSED,
+ SUPER_SIDEBAR_PEEK_STATE_WILL_OPEN as STATE_WILL_OPEN,
+ SUPER_SIDEBAR_PEEK_STATE_OPEN as STATE_OPEN,
+} from '../constants';
import { isCollapsed, toggleSuperSidebarCollapsed } from '../super_sidebar_collapsed_state_manager';
import { trackContextAccess } from '../utils';
import UserBar from './user_bar.vue';
import SidebarPortalTarget from './sidebar_portal_target.vue';
import HelpCenter from './help_center.vue';
import SidebarMenu from './sidebar_menu.vue';
-import SidebarPeekBehavior, { STATE_CLOSED, STATE_WILL_OPEN } from './sidebar_peek_behavior.vue';
+import SidebarPeekBehavior from './sidebar_peek_behavior.vue';
+import SidebarHoverPeekBehavior from './sidebar_hover_peek_behavior.vue';
export default {
components: {
@@ -20,6 +26,7 @@ export default {
HelpCenter,
SidebarMenu,
SidebarPeekBehavior,
+ SidebarHoverPeekBehavior,
SidebarPortalTarget,
TrialStatusWidget: () =>
import('ee_component/contextual_sidebar/components/trial_status_widget.vue'),
@@ -43,16 +50,21 @@ export default {
sidebarState,
showPeekHint: false,
isMouseover: false,
+ breakpoint: null,
};
},
computed: {
+ showOverlay() {
+ return this.sidebarState.isPeek || this.sidebarState.isHoverPeek;
+ },
menuItems() {
return this.sidebarData.current_menu_items || [];
},
peekClasses() {
return {
'super-sidebar-peek-hint': this.showPeekHint,
- 'super-sidebar-peek': this.sidebarState.isPeek,
+ 'super-sidebar-peek': this.showOverlay,
+ 'super-sidebar-has-peeked': this.sidebarState.hasPeeked,
};
},
},
@@ -90,6 +102,7 @@ export default {
this.sidebarState.isCollapsed = true;
this.showPeekHint = false;
} else if (state === STATE_WILL_OPEN) {
+ this.sidebarState.hasPeeked = true;
this.sidebarState.isPeek = false;
this.sidebarState.isCollapsed = true;
this.showPeekHint = true;
@@ -99,6 +112,16 @@ export default {
this.showPeekHint = false;
}
},
+ onHoverPeekChange(state) {
+ if (state === STATE_OPEN) {
+ this.sidebarState.hasPeeked = true;
+ this.sidebarState.isHoverPeek = true;
+ this.sidebarState.isCollapsed = false;
+ } else if (state === STATE_CLOSED) {
+ this.sidebarState.isHoverPeek = false;
+ this.sidebarState.isCollapsed = true;
+ }
+ },
},
};
</script>
@@ -124,7 +147,7 @@ export default {
@mouseenter="isMouseover = true"
@mouseleave="isMouseover = false"
>
- <user-bar :has-collapse-button="!sidebarState.isPeek" :sidebar-data="sidebarData" />
+ <user-bar :has-collapse-button="!showOverlay" :sidebar-data="sidebarData" />
<div v-if="showTrialStatusWidget" class="gl-px-2 gl-py-2">
<trial-status-widget
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-3 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! nav-item-link gl-py-3"
@@ -165,13 +188,18 @@ export default {
</a>
<!--
- Only mount SidebarPeekBehavior if the sidebar is peekable, to avoid
+ Only mount peek behavior components if the sidebar is peekable, to avoid
setting up event listeners unnecessarily.
-->
<sidebar-peek-behavior
- v-if="sidebarState.isPeekable"
+ v-if="sidebarState.isPeekable && !sidebarState.isHoverPeek"
:is-mouse-over-sidebar="isMouseover"
@change="onPeekChange"
/>
+ <sidebar-hover-peek-behavior
+ v-if="sidebarState.isPeekable && !sidebarState.isPeek"
+ :is-mouse-over-sidebar="isMouseover"
+ @change="onHoverPeekChange"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar_toggle.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar_toggle.vue
index 49435310793..f3f7dd587db 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar_toggle.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar_toggle.vue
@@ -27,19 +27,18 @@ export default {
},
i18n: {
collapseSidebar: __('Hide sidebar'),
- expandSidebar: __('Show sidebar'),
+ expandSidebar: __('Keep sidebar visible'),
primaryNavigationSidebar: __('Primary navigation sidebar'),
},
data() {
return sidebarState;
},
computed: {
+ canOpen() {
+ return this.isCollapsed || this.isPeek || this.isHoverPeek;
+ },
tooltipTitle() {
- if (this.isPeek) return '';
-
- return this.isCollapsed
- ? this.$options.i18n.expandSidebar
- : this.$options.i18n.collapseSidebar;
+ return this.canOpen ? this.$options.i18n.expandSidebar : this.$options.i18n.collapseSidebar;
},
tooltip() {
return {
@@ -49,21 +48,21 @@ export default {
};
},
ariaExpanded() {
- return String(!this.isCollapsed);
+ return String(!this.canOpen);
},
},
methods: {
toggle() {
- this.track(this.isCollapsed ? 'nav_show' : 'nav_hide', {
+ this.track(this.canOpen ? 'nav_show' : 'nav_hide', {
label: 'nav_toggle',
property: 'nav_sidebar',
});
- toggleSuperSidebarCollapsed(!this.isCollapsed, true);
+ toggleSuperSidebarCollapsed(!this.canOpen, true);
this.focusOtherToggle();
},
focusOtherToggle() {
this.$nextTick(() => {
- const classSelector = this.isCollapsed ? JS_TOGGLE_EXPAND_CLASS : JS_TOGGLE_COLLAPSE_CLASS;
+ const classSelector = this.canOpen ? JS_TOGGLE_EXPAND_CLASS : JS_TOGGLE_COLLAPSE_CLASS;
const otherToggle = document.querySelector(`.${classSelector}`);
otherToggle?.focus();
});
@@ -80,7 +79,6 @@ export default {
:aria-label="$options.i18n.primaryNavigationSidebar"
icon="sidebar"
category="tertiary"
- :disabled="isPeek"
@click="toggle"
/>
</template>