diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-09 18:10:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-09 18:10:05 +0300 |
commit | c0f42c6d662b776777afbf79ba72d8e833b8de48 (patch) | |
tree | d94d38bccd5297f59522090fd3c814d9264a1cc9 /app/assets/javascripts/nav | |
parent | f4d6d3ec77286fa64810bd6a25c58671e0deedaf (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/nav')
8 files changed, 265 insertions, 8 deletions
diff --git a/app/assets/javascripts/nav/components/responsive_app.vue b/app/assets/javascripts/nav/components/responsive_app.vue index 01b0fa370b5..cef1a14df19 100644 --- a/app/assets/javascripts/nav/components/responsive_app.vue +++ b/app/assets/javascripts/nav/components/responsive_app.vue @@ -1,30 +1,101 @@ <script> +import { FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS } from '~/frequent_items/constants'; +import { BV_DROPDOWN_SHOW, BV_DROPDOWN_HIDE } from '~/lib/utils/constants'; +import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue'; import eventHub, { EVENT_RESPONSIVE_TOGGLE } from '../event_hub'; - -const TEMPORARY_PLACEHOLDER = 'Placeholder for responsive top nav'; +import { resetMenuItemsActive } from '../utils/reset_menu_items_active'; +import ResponsiveHeader from './responsive_header.vue'; +import ResponsiveHome from './responsive_home.vue'; +import TopNavContainerView from './top_nav_container_view.vue'; export default { + components: { + KeepAliveSlots, + ResponsiveHeader, + ResponsiveHome, + TopNavContainerView, + }, props: { navData: { type: Object, required: true, }, }, + data() { + return { + activeView: 'home', + hasMobileOverlay: false, + }; + }, + computed: { + nav() { + return resetMenuItemsActive(this.navData); + }, + }, created() { eventHub.$on(EVENT_RESPONSIVE_TOGGLE, this.onToggle); + this.$root.$on(BV_DROPDOWN_SHOW, this.showMobileOverlay); + this.$root.$on(BV_DROPDOWN_HIDE, this.hideMobileOverlay); }, beforeDestroy() { eventHub.$off(EVENT_RESPONSIVE_TOGGLE, this.onToggle); + this.$root.$off(BV_DROPDOWN_SHOW, this.showMobileOverlay); + this.$root.$off(BV_DROPDOWN_HIDE, this.hideMobileOverlay); }, methods: { onToggle() { document.body.classList.toggle('top-nav-responsive-open'); }, + onMenuItemClick({ view }) { + if (view) { + this.activeView = view; + } + }, + showMobileOverlay() { + this.hasMobileOverlay = true; + }, + hideMobileOverlay() { + this.hasMobileOverlay = false; + }, }, - TEMPORARY_PLACEHOLDER, + FREQUENT_ITEMS_PROJECTS, + FREQUENT_ITEMS_GROUPS, }; </script> <template> - <p>{{ $options.TEMPORARY_PLACEHOLDER }}</p> + <div> + <div + class="mobile-overlay" + :class="{ 'mobile-nav-open': hasMobileOverlay }" + data-testid="mobile-overlay" + ></div> + <keep-alive-slots :slot-key="activeView"> + <template #home> + <responsive-home :nav-data="nav" @menu-item-click="onMenuItemClick" /> + </template> + <template #projects> + <responsive-header @menu-item-click="onMenuItemClick"> + {{ __('Projects') }} + </responsive-header> + <top-nav-container-view + :frequent-items-dropdown-type="$options.FREQUENT_ITEMS_PROJECTS.namespace" + :frequent-items-vuex-module="$options.FREQUENT_ITEMS_PROJECTS.vuexModule" + container-class="gl-px-3" + v-bind="nav.views.projects" + /> + </template> + <template #groups> + <responsive-header @menu-item-click="onMenuItemClick"> + {{ __('Groups') }} + </responsive-header> + <top-nav-container-view + :frequent-items-dropdown-type="$options.FREQUENT_ITEMS_GROUPS.namespace" + :frequent-items-vuex-module="$options.FREQUENT_ITEMS_GROUPS.vuexModule" + container-class="gl-px-3" + v-bind="nav.views.groups" + /> + </template> + </keep-alive-slots> + </div> </template> diff --git a/app/assets/javascripts/nav/components/responsive_header.vue b/app/assets/javascripts/nav/components/responsive_header.vue new file mode 100644 index 00000000000..8a1d21993b7 --- /dev/null +++ b/app/assets/javascripts/nav/components/responsive_header.vue @@ -0,0 +1,37 @@ +<script> +import { GlTooltipDirective } from '@gitlab/ui'; +import TopNavMenuItem from './top_nav_menu_item.vue'; + +export default { + components: { + TopNavMenuItem, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + computed: { + menuItem() { + return { + id: 'home', + view: 'home', + icon: 'angle-left', + }; + }, + }, +}; +</script> + +<template> + <header class="gl-py-4 gl-display-flex gl-align-items-center"> + <top-nav-menu-item + v-gl-tooltip="{ title: s__('TopNav|Go back') }" + class="gl-p-3!" + :menu-item="menuItem" + icon-only + @click="$emit('menu-item-click', menuItem)" + /> + <span class="gl-font-size-h2 gl-font-weight-bold gl-ml-2"> + <slot></slot> + </span> + </header> +</template> diff --git a/app/assets/javascripts/nav/components/responsive_home.vue b/app/assets/javascripts/nav/components/responsive_home.vue new file mode 100644 index 00000000000..c8f2f0bfb10 --- /dev/null +++ b/app/assets/javascripts/nav/components/responsive_home.vue @@ -0,0 +1,62 @@ +<script> +import { GlTooltipDirective } from '@gitlab/ui'; +import TopNavMenuItem from './top_nav_menu_item.vue'; +import TopNavMenuSections from './top_nav_menu_sections.vue'; +import TopNavNewDropdown from './top_nav_new_dropdown.vue'; + +const NEW_VIEW = 'new'; +const SEARCH_VIEW = 'search'; + +export default { + components: { + TopNavMenuItem, + TopNavMenuSections, + TopNavNewDropdown, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + navData: { + type: Object, + required: true, + }, + }, + computed: { + menuSections() { + return [ + { id: 'primary', menuItems: this.navData.primary }, + { id: 'secondary', menuItems: this.navData.secondary }, + ].filter((x) => x.menuItems?.length); + }, + newDropdownViewModel() { + return this.navData.views[NEW_VIEW]; + }, + searchMenuItem() { + return this.navData.views[SEARCH_VIEW]; + }, + }, +}; +</script> + +<template> + <div> + <header class="gl-display-flex gl-align-items-center gl-py-4 gl-pl-4"> + <h1 class="gl-m-0 gl-font-size-h2 gl-reset-color gl-mr-auto">{{ __('Menu') }}</h1> + <top-nav-menu-item + v-if="searchMenuItem" + v-gl-tooltip="{ title: searchMenuItem.title }" + class="gl-ml-3" + :menu-item="searchMenuItem" + icon-only + /> + <top-nav-new-dropdown + v-if="newDropdownViewModel" + v-gl-tooltip="{ title: newDropdownViewModel.title }" + :view-model="newDropdownViewModel" + class="gl-ml-3" + /> + </header> + <top-nav-menu-sections class="gl-h-full" :sections="menuSections" v-on="$listeners" /> + </div> +</template> diff --git a/app/assets/javascripts/nav/components/top_nav_container_view.vue b/app/assets/javascripts/nav/components/top_nav_container_view.vue index 4043ef1ae5b..6f98f85ff90 100644 --- a/app/assets/javascripts/nav/components/top_nav_container_view.vue +++ b/app/assets/javascripts/nav/components/top_nav_container_view.vue @@ -20,6 +20,11 @@ export default { type: String, required: true, }, + containerClass: { + type: String, + required: false, + default: '', + }, linksPrimary: { type: Array, required: false, @@ -50,7 +55,11 @@ export default { <template> <div class="top-nav-container-view gl-display-flex gl-flex-direction-column"> - <div class="frequent-items-dropdown-container gl-w-auto"> + <div + class="frequent-items-dropdown-container gl-w-auto" + :class="containerClass" + data-testid="frequent-items-container" + > <div class="frequent-items-dropdown-content gl-w-full! gl-pt-0!"> <vuex-module-provider :vuex-module="frequentItemsVuexModule"> <frequent-items-app v-bind="$attrs" /> diff --git a/app/assets/javascripts/nav/components/top_nav_menu_item.vue b/app/assets/javascripts/nav/components/top_nav_menu_item.vue index 3675ee50fdf..08b2fbf2ed1 100644 --- a/app/assets/javascripts/nav/components/top_nav_menu_item.vue +++ b/app/assets/javascripts/nav/components/top_nav_menu_item.vue @@ -16,6 +16,11 @@ export default { type: Object, required: true, }, + iconOnly: { + type: Boolean, + required: false, + default: false, + }, }, computed: { dataAttrs() { @@ -32,13 +37,16 @@ export default { :href="menuItem.href" class="top-nav-menu-item gl-display-block" :class="[menuItem.css_class, { [$options.ACTIVE_CLASS]: menuItem.active }]" + :aria-label="menuItem.title" v-bind="dataAttrs" v-on="$listeners" > <span class="gl-display-flex"> - <gl-icon v-if="menuItem.icon" :name="menuItem.icon" class="gl-mr-2!" /> - {{ menuItem.title }} - <gl-icon v-if="menuItem.view" name="chevron-right" class="gl-ml-auto" /> + <gl-icon v-if="menuItem.icon" :name="menuItem.icon" :class="{ 'gl-mr-2!': !iconOnly }" /> + <template v-if="!iconOnly"> + {{ menuItem.title }} + <gl-icon v-if="menuItem.view" name="chevron-right" class="gl-ml-auto" /> + </template> </span> </gl-button> </template> diff --git a/app/assets/javascripts/nav/components/top_nav_menu_sections.vue b/app/assets/javascripts/nav/components/top_nav_menu_sections.vue index 407c79b1f42..442af512350 100644 --- a/app/assets/javascripts/nav/components/top_nav_menu_sections.vue +++ b/app/assets/javascripts/nav/components/top_nav_menu_sections.vue @@ -54,6 +54,7 @@ export default { :key="menuItem.id" :menu-item="menuItem" data-testid="menu-item" + class="gl-w-full" :class="{ 'gl-mt-1': menuItemIndex > 0 }" @click="onClick(menuItem)" /> diff --git a/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue b/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue new file mode 100644 index 00000000000..154bed81854 --- /dev/null +++ b/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue @@ -0,0 +1,55 @@ +<script> +import { GlDropdown, GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui'; + +export default { + components: { + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlDropdownSectionHeader, + }, + props: { + viewModel: { + type: Object, + required: true, + }, + }, + computed: { + sections() { + return this.viewModel.menu_sections || []; + }, + showHeaders() { + return this.sections.length > 1; + }, + }, +}; +</script> + +<template> + <gl-dropdown + toggle-class="top-nav-menu-item" + icon="plus" + :text="viewModel.title" + category="tertiary" + text-sr-only + no-caret + right + > + <template v-for="({ title, menu_items }, index) in sections"> + <gl-dropdown-divider v-if="index > 0" :key="`${index}_divider`" data-testid="divider" /> + <gl-dropdown-section-header v-if="showHeaders" :key="`${index}_header`" data-testid="header"> + {{ title }} + </gl-dropdown-section-header> + <template v-for="menuItem in menu_items"> + <gl-dropdown-item + :key="`${index}_item_${menuItem.id}`" + link-class="top-nav-menu-item" + :href="menuItem.href" + data-testid="item" + > + {{ menuItem.title }} + </gl-dropdown-item> + </template> + </template> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/nav/utils/reset_menu_items_active.js b/app/assets/javascripts/nav/utils/reset_menu_items_active.js new file mode 100644 index 00000000000..9b5d8e97c9c --- /dev/null +++ b/app/assets/javascripts/nav/utils/reset_menu_items_active.js @@ -0,0 +1,14 @@ +const resetActiveInArray = (arr) => arr?.map((menuItem) => ({ ...menuItem, active: false })); + +/** + * This method sets `active: false` for the menu items within the given nav data. + * + * @returns navData with the menu items updated with `active: false` + */ +export const resetMenuItemsActive = ({ primary, secondary, ...navData }) => { + return { + ...navData, + primary: resetActiveInArray(primary), + secondary: resetActiveInArray(secondary), + }; +}; |