diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-20 14:18:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-20 14:18:08 +0300 |
commit | 5afcbe03ead9ada87621888a31a62652b10a7e4f (patch) | |
tree | 9918b67a0d0f0bafa6542e839a8be37adf73102d /app/assets/javascripts/super_sidebar/components/nav_item.vue | |
parent | c97c0201564848c1f53226fe19d71fdcc472f7d0 (diff) |
Add latest changes from gitlab-org/gitlab@16-4-stable-eev16.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/super_sidebar/components/nav_item.vue')
-rw-r--r-- | app/assets/javascripts/super_sidebar/components/nav_item.vue | 148 |
1 files changed, 119 insertions, 29 deletions
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 36803a885e7..5e0f8fffb0e 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -1,6 +1,6 @@ <script> -import { GlButton, GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { GlAvatar, GlButton, GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; import { CLICK_MENU_ITEM_ACTION, CLICK_PINNED_MENU_ITEM_ACTION, @@ -12,11 +12,14 @@ import NavItemRouterLink from './nav_item_router_link.vue'; export default { i18n: { + pin: s__('Navigation|Pin %{title}'), pinItem: s__('Navigation|Pin item'), + unpin: s__('Navigation|Unpin %{title}'), unpinItem: s__('Navigation|Unpin item'), }, name: 'NavItem', components: { + GlAvatar, GlButton, GlIcon, GlBadge, @@ -62,6 +65,12 @@ export default { default: false, }, }, + data() { + return { + isMouseIn: false, + canClickPinButton: false, + }; + }, computed: { pillData() { return this.item.pill_count; @@ -96,12 +105,27 @@ export default { ...extraData, }; }, + /** + * Some QA specs rely on a stable "Project overview"/"Group overview" nav + * item data-qa-submenu-item attribute value. + * + * This computed ensures that those particular nav items use the `id` of + * the item rather than its title for that QA attribute. + * + * In future, probably all nav items should do this, for consistency. + * See https://gitlab.com/gitlab-org/gitlab/-/issues/422925. + */ + qaSubMenuItem() { + const { id } = this.item; + if (id === 'project_overview' || id === 'group_overview') return id.replace(/_/g, '-'); + return this.item.title; + }, linkProps() { return { ...this.$attrs, ...this.trackingProps, item: this.item, - 'data-qa-submenu-item': this.item.title, + 'data-qa-submenu-item': this.qaSubMenuItem, 'data-method': this.item.data_method ?? null, }; }, @@ -118,26 +142,73 @@ export default { navItemLinkComponent() { return this.item.to ? NavItemRouterLink : NavItemLink; }, + hasAvatar() { + return Boolean(this.item.entity_id); + }, + avatarShape() { + return this.item.avatar_shape || 'rect'; + }, + pinAriaLabel() { + return sprintf(this.$options.i18n.pin, { + title: this.item.title, + }); + }, + unpinAriaLabel() { + return sprintf(this.$options.i18n.unpin, { + title: this.item.title, + }); + }, + activeIndicatorStyle() { + const style = { + width: '3px', + borderRadius: '3px', + marginRight: '1px', + }; + + // The active indicator is too close to the avatar for items with one, so shift + // it left by 1px. + // + // The indicator is absolutely positioned using rem units. This tweak for this + // edge case is in pixel units, so that it does not scale with root font size. + if (this.hasAvatar) style.transform = 'translateX(-1px)'; + + return style; + }, + }, + mounted() { + if (this.item.is_active) { + this.$el.scrollIntoView(false); + } + }, + methods: { + togglePointerEvents() { + this.canClickPinButton = this.isMouseIn; + }, }, }; </script> <template> - <li> + <li + class="gl-relative show-on-focus-or-hover--context hide-on-focus-or-hover--context transition-opacity-on-hover--context" + data-testid="nav-item" + @mouseenter="isMouseIn = true" + @mouseleave="isMouseIn = false" + > <component :is="navItemLinkComponent" #default="{ isActive }" v-bind="linkProps" - class="nav-item-link gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus show-on-focus-or-hover--context" + class="gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus show-on-focus-or-hover--control hide-on-focus-or-hover--control" :class="computedLinkClasses" - data-qa-selector="nav_item_link" data-testid="nav-item-link" + data-qa-selector="nav_item_link" > <div :class="[isActive ? 'gl-opacity-10' : 'gl-opacity-0']" class="active-indicator gl-bg-blue-500 gl-absolute gl-left-2 gl-top-2 gl-bottom-2 gl-transition-slow" aria-hidden="true" - style="width: 3px; border-radius: 3px; margin-right: 1px" + :style="activeIndicatorStyle" data-testid="active-indicator" ></div> <div v-if="!isFlyout" class="gl-flex-shrink-0 gl-w-6 gl-display-flex"> @@ -148,6 +219,14 @@ export default { name="grip" class="gl-m-auto gl-text-gray-400 js-draggable-icon gl-cursor-grab show-on-focus-or-hover--target" /> + <gl-avatar + v-else-if="hasAvatar" + :size="24" + :shape="avatarShape" + :entity-name="item.title" + :entity-id="item.entity_id" + :src="item.avatar" + /> </slot> </div> <div class="gl-flex-grow-1 gl-text-gray-900 gl-truncate-end"> @@ -157,36 +236,47 @@ export default { </div> </div> <slot name="actions"></slot> - <span v-if="hasPill || isPinnable" class="gl-text-right gl-relative"> + <span v-if="hasPill || isPinnable" class="gl-text-right gl-relative gl-min-w-8"> <gl-badge v-if="hasPill" size="sm" variant="neutral" - :class="{ 'nav-item-badge gl-absolute gl-right-0 gl-top-2': isPinnable }" + class="gl-bg-t-gray-a-08!" + :class="{ + 'hide-on-focus-or-hover--target transition-opacity-on-hover--target': isPinnable, + }" > {{ pillData }} </gl-badge> - <gl-button - v-if="isPinnable && !isPinned" - v-gl-tooltip.noninteractive.ds500.right.viewport="$options.i18n.pinItem" - size="small" - category="tertiary" - icon="thumbtack" - class="show-on-focus-or-hover--target" - :aria-label="$options.i18n.pinItem" - @click.prevent="$emit('pin-add', item.id)" - /> - <gl-button - v-else-if="isPinnable && isPinned" - v-gl-tooltip.noninteractive.ds500.right.viewport="$options.i18n.unpinItem" - size="small" - category="tertiary" - :aria-label="$options.i18n.unpinItem" - icon="thumbtack-solid" - class="show-on-focus-or-hover--target" - @click.prevent="$emit('pin-remove', item.id)" - /> </span> </component> + <template v-if="isPinnable"> + <gl-button + v-if="isPinned" + v-gl-tooltip.noninteractive.right.viewport="$options.i18n.unpinItem" + :aria-label="unpinAriaLabel" + category="tertiary" + class="show-on-focus-or-hover--target transition-opacity-on-hover--target always-animate gl-absolute gl-right-3 gl-top-2" + :class="{ 'gl-pointer-events-none': !canClickPinButton }" + data-testid="nav-item-unpin" + icon="thumbtack-solid" + size="small" + @click="$emit('pin-remove', item.id)" + @transitionend="togglePointerEvents" + /> + <gl-button + v-else + v-gl-tooltip.noninteractive.right.viewport="$options.i18n.pinItem" + :aria-label="pinAriaLabel" + category="tertiary" + class="show-on-focus-or-hover--target transition-opacity-on-hover--target always-animate gl-absolute gl-right-3 gl-top-2" + :class="{ 'gl-pointer-events-none': !canClickPinButton }" + data-testid="nav-item-pin" + icon="thumbtack" + size="small" + @click="$emit('pin-add', item.id)" + @transitionend="togglePointerEvents" + /> + </template> </li> </template> |