kodadot/nft-gallery

View on GitHub
components/Navbar.vue

Summary

Maintainability
Test Coverage
<template>
  <nav
    role="navigation"
    aria-label="main navigation"
    class="navbar is-fixed-top is-spaced"
    :class="{
      'is-active': isMobileNavbarOpen,
    }"
  >
    <div
      class="container items-center max-lg:!px-0"
      :class="{ 'is-fluid': !isTouch }"
    >
      <!-- BRAND -->
      <div class="navbar-brand">
        <nuxt-link
          v-if="!isMobile || !openMobileSearchBar"
          to="/"
          class="navbar-item logo nuxt-link-active"
        >
          <img
            :src="logoSrc"
            alt="First NFT market explorer on Kusama and Polkadot"
          >
        </nuxt-link>
        <div
          class="lg:!hidden flex flex-grow items-center justify-end"
          @click="closeBurgerMenu"
        >
          <NeoButton
            v-show="isMobile && !openMobileSearchBar"
            class="square-40 mr-2"
            icon="magnifying-glass"
            @click="showMobileSearchBar"
          />

          <div
            v-show="!isMobile || openMobileSearchBar"
            class="w-full"
          >
            <div
              class="is-fixed-top z-10 flex items-center justify-between p-2"
            >
              <Search
                ref="mobilSearchRef"
                hide-filter
                class="flex-grow"
              />
              <NeoButton
                variant="text"
                class="p-3 is-shadowless border-0 capitalize sm:hidden"
                @click="hideMobileSearchBar"
              >
                {{ $t('cancel') }}
              </NeoButton>
            </div>
          </div>
        </div>

        <!-- BURGER MENU -->
        <a
          role="button"
          class="navbar-burger lg:hidden"
          :class="{ 'is-active': isMobileNavbarOpen }"
          aria-label="menu"
          aria-expanded="false"
          data-target="MainNavbar"
          @click="showMobileNavbar"
        >
          <span aria-hidden="true" />
          <span aria-hidden="true" />
          <span aria-hidden="true" />
        </a>
        <!-- END BURGER MENU -->
      </div>
      <!-- END BRAND -->

      <!-- MENU -->
      <div
        id="MainNavbar"
        class="navbar-menu py-0"
        :class="{ 'is-active': isMobileNavbarOpen }"
      >
        <!-- NAV START -->
        <div class="navbar-start">
          <div class="navbar-item is-expanded flex justify-center">
            <Search
              v-if="!isTouch"
              class="search-navbar flex-grow pb-0 max-lg:!hidden"
              hide-filter
              search-column-class="flex-grow"
            />
          </div>
        </div>
        <!-- END NAV START -->

        <!-- NAV END -->
        <div class="navbar-end">
          <nuxt-link
            :to="dropsPath"
            rel="nofollow"
          >
            <div
              class="navbar-item"
              data-testid="drops"
            >
              {{ $t('drops.drops') }}

              <NeoIcon
                class="ml-1"
                icon="fire-flame-curved"
                pack="fass"
                variant="primary"
              />
            </div>
          </nuxt-link>

          <MobileExpandableSection
            v-if="isExplorerVisible"
            v-slot="{ onCloseMobileSubMenu }"
            class="lg:!hidden"
            :title="$t('explore')"
          >
            <NavbarExploreOptions
              @select="
                () => {
                  showMobileNavbar()
                  onCloseMobileSubMenu()
                }
              "
            />
          </MobileExpandableSection>
          <NavbarExploreDropdown
            v-if="isExplorerVisible"
            class="navbar-explore custom-navbar-item max-lg:!hidden"
            data-testid="explore"
          />

          <a
            href="https://hello.kodadot.xyz"
            rel="nofollow noopener noreferrer"
            target="_blank"
            class="navbar-item"
            data-testid="learn"
          >
            {{ $t('learn') }}
          </a>
          <CreateDropdown
            v-show="isCreateVisible"
            class="navbar-create custom-navbar-item ml-0"
            data-testid="create"
            :chain="urlPrefix"
            @select="showMobileNavbar"
          />

          <MobileExpandableSection
            v-slot="{ onCloseMobileSubMenu }"
            class="lg:!hidden"
            no-padding
            :title="$t('chainSelect', [chainName])"
          >
            <NavbarChainOptions
              @select="
                () => {
                  handleMobileChainSelect()
                  onCloseMobileSubMenu()
                }
              "
            />
          </MobileExpandableSection>
          <ChainSelectDropdown
            id="NavChainSelect"
            class="navbar-chain custom-navbar-item max-lg:!hidden"
            data-testid="chain-select"
          />

          <ShoppingCartButton
            data-testid="navbar-button-cart"
            :show-label="isTouch"
            @close-burger-menu="showMobileNavbar"
          />

          <div class="lg:!hidden">
            <template v-if="!account">
              <MobileExpandableSection
                v-slot="{ onCloseMobileSubMenu }"
                class="mobile-language"
                :no-padding="true"
                :title="$t('profileMenu.language')"
                icon="globe"
              >
                <MobileLanguageOption
                  @select="
                    () => {
                      showMobileNavbar()
                      onCloseMobileSubMenu()
                    }
                  "
                />
              </MobileExpandableSection>
              <ColorModeButton class="navbar-item" />
              <NavbarCookiesButton @select="showMobileNavbar" />
            </template>
            <div
              v-else
              class="navbar-item"
              @click.stop="openWalletConnectModal"
            >
              <span>
                {{ $t('profile.page') }}
                <NeoIcon
                  icon="user-circle"
                  class="w-4 h-4 ml-2 lg:!ml-0"
                  size="medium"
                />
              </span>
              <NeoIcon
                class="icon--right"
                icon="chevron-right"
              />
            </div>

            <div
              v-if="!account"
              id="NavProfile"
            >
              <ConnectWalletButton
                class="button-connect-wallet"
                data-testid="navbar-button-connect-wallet"
                variant="connect"
                @close-burger-menu="showMobileNavbar"
              />
            </div>
          </div>

          <NavbarProfileDropdown
            id="NavProfile"
            class="max-lg:!hidden"
            :chain="urlPrefix"
            data-testid="navbar-profile-dropdown"
            @close-burger-menu="closeBurgerMenu"
          />
        </div>
        <!-- END NAV END -->
      </div>
      <!-- END MENU -->
    </div>
  </nav>
</template>

<script lang="ts" setup>
import { NeoButton, NeoIcon } from '@kodadot1/brick'
import { nextTick } from 'vue'
import ShoppingCartButton from './navbar/ShoppingCartButton.vue'
import { openConnectWalletModal } from '@/components/common/ConnectWallet/useConnectWallet'
import ChainSelectDropdown from '@/components/navbar/ChainSelectDropdown.vue'
import CreateDropdown from '@/components/navbar/CreateDropdown.vue'
import MobileExpandableSection from '@/components/navbar/MobileExpandableSection.vue'
import MobileLanguageOption from '@/components/navbar/MobileLanguageOption.vue'
import NavbarChainOptions from '@/components/navbar/NavbarChainOptions.vue'
import NavbarExploreOptions from '@/components/navbar/NavbarExploreOptions.vue'
import Search from '@/components/search/Search.vue'
import ConnectWalletButton from '@/components/shared/ConnectWalletButton.vue'
import { explorerVisible, createVisible } from '@/utils/config/permission.config'
import { useIdentityStore } from '@/stores/identity'
import { getChainNameByPrefix } from '@/utils/chain'

const { neoModal } = useProgrammatic()
const openMobileSearchBar = ref(false)
const lastScrollPosition = ref(0)
const isBurgerMenuOpened = ref(false)
const { isMobile, isMobileOrTablet: isTouch } = useDevice()
const { urlPrefix } = usePrefix()
const { isDarkMode } = useTheme()
const identityStore = useIdentityStore()
const isMobileNavbarOpen = ref(false)
const updateAuthBalanceTimer = ref()

const mobilSearchRef = ref<{ focusInput: () => void } | null>(null)

const account = computed(() => identityStore.getAuthAddress)

const isCreateVisible = computed(() => createVisible(urlPrefix.value))
const isExplorerVisible = computed(() => explorerVisible(urlPrefix.value))

const logoSrc = computed(() => {
  const variant = isTouch ? 'Koda' : 'Koda_Beta'
  const color = isDarkMode.value ? '_dark' : ''
  return `/${variant}${color}.svg`
})

const dropsPath = computed(() => {
  const prefix = pickByVm({
    SUB: 'ahp',
    EVM: urlPrefix.value,
  })
  return `/${prefix}/drops`
})

const handleMobileChainSelect = () => {
  showMobileNavbar()
}

const closeAllModals = () => neoModal.closeAll()

const openWalletConnectModal = (): void => {
  closeAllModals()
  openConnectWalletModal()
}

const showMobileNavbar = () => {
  closeAllModals()

  document.body.classList.toggle('is-clipped')
  isMobileNavbarOpen.value = !isMobileNavbarOpen.value
  if (!isMobileNavbarOpen.value) {
    document.documentElement.scrollTop = lastScrollPosition.value
  }
}

const closeBurgerMenu = () => {
  isBurgerMenuOpened.value = false
}

watch([isBurgerMenuOpened], () => {
  setBodyScroll(!isBurgerMenuOpened.value)
})

const setBodyScroll = (allowScroll: boolean) => {
  nextTick(() => {
    const body = document.querySelector('body') as HTMLBodyElement
    if (allowScroll) {
      body.classList.remove('is-clipped')
    }
    else {
      body.classList.add('is-clipped')
    }
  })
}

const showMobileSearchBar = () => {
  openMobileSearchBar.value = true
  nextTick(() => {
    mobilSearchRef.value?.focusInput()
  })
  setBodyScroll(false)
}

const hideMobileSearchBar = () => {
  openMobileSearchBar.value = false
  setBodyScroll(true)
}

const chainName = computed(() => getChainNameByPrefix(urlPrefix.value))

const updateAuthBalance = () => {
  account.value && identityStore.fetchBalance({ address: account.value })
}

onMounted(() => {
  document.body.style.overflowY = 'initial'
  document.body.className = 'has-navbar-fixed-top has-spaced-navbar-fixed-top'
  updateAuthBalanceTimer.value = setInterval(updateAuthBalance, 30000)
})

onBeforeUnmount(() => {
  setBodyScroll(true)
  document.documentElement.classList.remove('is-clipped-touch')
  clearInterval(updateAuthBalanceTimer.value)
})
</script>

<style lang="scss" scoped>
.square-40 {
  width: 40px;
  height: 40px;
}

:deep(.navbar-explore) {
  .navbar-item {
    height: 4.5rem;
  }
  .o-drop__menu {
    margin: 0;
  }
  .o-drop__item {
    padding: 1.5rem 2rem;
    min-width: 18.75rem;

    &:hover {
      background-color: unset;
    }
  }
}
</style>