DEV Community

Cover image for Crafting my Portfolio - Nav and Footer
Hardeep Kumar
Hardeep Kumar

Posted on

Crafting my Portfolio - Nav and Footer

This is a Series of Posts where I'm sharing my journey (sort of) while I craft my Portfolio.

Now, since I prepared base in previous post, Time to add a layout, as per Nuxt terms. It is going to be the base template, sort of, that will hold the UI elements that are going to be shared by all the pages and the UI elements that are going to be injected into it. The shared UI element won't change much and even if they do, that won't be big of a change. The injected UI elements will change, like on Home, About etc. they'll have different content.

Layout

Hmm... as per Nuxt Docs on Layouts, they say

If you only have a single layout in your application, we recommend using app.vue instead.

Now currently this portfolio of mine is gonna have only 1 layout, yes. This is the base one to view all pages, sharing the Nav, Footer etc. But going further, I might want to add a Blog in there with a bit different layout. Like for Blog with a sidebar section showing recent posts. So I want to be prepared for it, and that's why I'll go with layouts approach instead of app.vue approach.

Now the first thing to do is add a layout, which I will name Default and add an index page, the entry page of my Portfolio and delete the initial app.vue page since I'm gonna use pages now. To do that, I'll use nuxi to generate a layout and page for me.

npx nuxi add layout default
Enter fullscreen mode Exit fullscreen mode

which will give me

<script lang="ts" setup></script>

<template>
  <div>
    Layout: default
    <slot />
  </div>
</template>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode

and for index page

npx nuxi add page index
Enter fullscreen mode Exit fullscreen mode

and I'll get

<script lang="ts" setup></script>

<template>
  <div>
    Page: foo
  </div>
</template>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode

and delete the app.vue. Btw, adding a page activated the router functionality in Nuxt 3. In case you don't know, Nuxt takes care of everything behind the scene and allows the developer to focus on building the application rather than configuring, the majority of it not everything. Like activating store only if store is needed, activating router only if pages are defined, auto import plugins and also auto importing components. End of all Headaches.

Doing all this gave me this Astronomical UI which is.... empty... very.

Best UI in the World

Well, for now, I'll add some pointers to it, so I can tell what and where I'm gonna put them. A 'Navbar', a 'Footer' and a 'to up' floating button.

UI Update

Icons

I'll be using Icons at many places like Menu, Social, etc. So I'm gonna add some icons to my project first. I love Remix Icons. I always use them in the majority of my things. But many times there is a need to use some other icons which are not in one Icon provider, like, say an 'x' icon is not in Remix Icons but is in Box Icons. So either copy SVG file or install them as well. Quite cumbersome.

Solution? Oh, Vue Icons!. (No, I mean, that's literally the name. Come on I not joking 😑) It come with 20 Icon packs including Bootstrap, Font Awesome and even Remix Icons. I can import only the icons i will use of just dump all of them, I'll use the first approach btw. (no Box Icons, sadge but soon will) Check their docs for more. To install, I'll just

yarn add --dev oh-vue-icons
Enter fullscreen mode Exit fullscreen mode

and all set. For now at least.

Nav/Drawer

Since I'm building a responsive Site (of course, who doesn't do so in year 2022) well do I need to even say? Of course first Mobile UI then Desktop. I decided to go with Hamburger Menu with full screen Menu panel on Mobile. And used this as an inspiration material. As for desktop, I'll just keep it simple, 3-4 links in a line as usual.

I'm going to use the Drawer component from DaisyUI for creating my mobile Navigation. and as for Desktop, I'll just use the typical links in a strip on top as usual. This Drawer markup is closer to what I want, I'll use this one then.

Hmm... so I'll take the Boiler then add it all in layouts/default.vue Refactor it and separate portions into components. Then update it as per need. Good. I'll create few components before hand. I'll use a folder approach, or so I call it. Basically I'll create a folder with the name NavBar then add an index.vue file as components/NavBar/index.vue. This file will be the entry point of the NavBar component. Then I'll create a new component in it as named Drawer as components/NavBar/Drawer.vue where I'll keep drawer sidebar code. I'll also add ability to switch themes. I'll make it such that It'll be in loading state intially so that I can better track theme change and what theme is currently active. Chop-Chop, Time to wear Developer Cap on.

npx nuxi add component NavBar/index
npx nuxi add component NavBar/Drawer
Enter fullscreen mode Exit fullscreen mode
# layouts/default.vue
<script lang="ts" setup></script>

<template>
  <div>
    <div class="drawer drawer-end">
      <input id="mainDrawer" type="checkbox" class="drawer-toggle" />
      <div class="drawer-content flex flex-col">
        <!-- Navbar -->
        <NavBar />

        <!-- content -->
        <slot />
        <!-- content -->
      </div>
      <NavBarDrawer />
    </div>
  </div>
</template>

<style scoped></style>

Enter fullscreen mode Exit fullscreen mode
# components/NavBar/index.vue
<script lang="ts" setup>
import { OhVueIcon, addIcons } from 'oh-vue-icons';
import { RiMenu2Fill, RiSunFill, RiMoonFill } from 'oh-vue-icons/icons';
import { themeChange } from 'theme-change';

const themeLoaderSet = ref(false);

onMounted(() => {
  themeChange(false);
  themeLoaderSet.value = true;
});

addIcons(RiMenu2Fill, RiSunFill, RiMoonFill);
</script>

<template>
  <div class="w-full navbar bg-base-100">
    <!-- Brand -->
    <div class="flex-1">
      <NuxtLink to="/" class="normal-case text-3xl text-primary">HK</NuxtLink>
    </div>

    <div class="flex-1 hidden lg:block">
      <ul class="menu menu-horizontal menu-compact gap-1 p-1">
        <!-- Navbar menu content here -->
        <li>
          <NuxtLink to="/" active-class="active">Home</NuxtLink>
        </li>
        <li>
          <NuxtLink to="/" active-class="active">About</NuxtLink>
        </li>
      </ul>
    </div>

    <!-- start:Theme Toggle -->
    <div class="flex-none">
      <div
        class="btn btn-circle bg-transparent border-none loading disabled text-primary"
        v-show="!themeLoaderSet"
      ></div>

      <div
        class="btn-circle swap swap-rotate"
        data-toggle-theme="pastel,dracula"
        data-act-class="swap-active"
        v-show="themeLoaderSet"
      >
        <!-- sun icon -->
        <OhVueIcon
          name="ri-sun-fill"
          class="swap-on fill-primary"
          scale="1.2"
        />

        <!-- moon icon -->
        <OhVueIcon
          name="ri-moon-fill"
          class="swap-off fill-primary"
          scale="1.2"
        />
      </div>
    </div>
    <!-- end:Theme Toggle -->

    <div class="flex-none lg:hidden">
      <label for="mainDrawer" class="btn btn-primary"
        ><OhVueIcon name="ri-menu-2-fill" class="text-white"
      /></label>
    </div>
  </div>
</template>

<style scoped></style>

Enter fullscreen mode Exit fullscreen mode
# components/NavBar/Drawer.vue
<script lang="ts" setup>
import { OhVueIcon, addIcons } from 'oh-vue-icons';
import { RiCloseFill } from 'oh-vue-icons/icons';

addIcons(RiCloseFill);
</script>

<template>
  <div class="drawer-side">
    <label for="mainDrawer" class="drawer-overlay"></label>
    <div class="w-full bg-base-100 md:w-80">
      <div class="flex justify-end m-2 lg:hidden">
        <label
          for="mainDrawer"
          class="m-2 p-1 border border-primary rounded-full cursor-pointer"
        >
          <OhVueIcon name="ri-close-fill" scale="1.2" class="text-primary" />
        </label>
      </div>

      <ul class="menu p-4 gap-2 overflow-y-auto">
        <!-- Sidebar content here -->
        <li>
          <NuxtLink
            to="/"
            class="flex justify-center items-center"
            active-class="active"
            >Home</NuxtLink
          >
        </li>
        <li>
          <NuxtLink
            to="/"
            class="flex justify-center items-center"
            active-class="active"
            >About</NuxtLink
          >
        </li>
      </ul>
    </div>
  </div>
</template>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode

Results

Nav

Viewport Height Issue

ViewPort Height is kinda funky on mobile devices. And as it is now , I don't like it. 'Cuz it's messing up my drawer plans. Now why is it funky and what is funky? Well.. Instead of me explaining it, just read this CSS Tricks article. It also gives the solution, which I'm gonna use actually.

The Issue.

VH Error

To fix this funkiness, I'll reassign the viewport height to be the window height minus any browser UI. i.e, the inner height of the window. So first I'll get the innerHeight of window, then I'll assign the height to be the new viewport height. I'll also add an event listener for resize.

....
function setVH() {
  let vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}

onMounted(() => {
  setVH();
  window.addEventListener('resize', () => {
    setVH();
  });
});
....
Enter fullscreen mode Exit fullscreen mode

Now I need to tell TailwindCss to use my way of setting viewport height. I honestly don't know if this is the correct way to overide the default classes. I just wanted to overide only one, but doing so removed others so i used this default config file in their github repo to get the default values and add mine in combination. (Do tell me if there is a better way to do this.)

....
// overide height
height: (theme) => ({
  auto: 'auto',
  ...theme('spacing'),
  '1/2': '50%',
  '1/3': '33.333333%',
  '2/3': '66.666667%',
  '1/4': '25%',
  '2/4': '50%',
  '3/4': '75%',
  '1/5': '20%',
  '2/5': '40%',
  '3/5': '60%',
  '4/5': '80%',
  '1/6': '16.666667%',
  '2/6': '33.333333%',
  '3/6': '50%',
  '4/6': '66.666667%',
  '5/6': '83.333333%',
  full: '100%',
  screen: 'calc(var(--vh) * 100)',
  min: 'min-content',
  max: 'max-content',
  fit: 'fit-content',
}),

// overide min-height
minHeight: {
  0: '0px',
  full: '100%',
  screen: 'calc(var(--vh) * 100)',
  min: 'min-content',
  max: 'max-content',
  fit: 'fit-content',
},

// overide max-height
maxHeight: (theme) => ({
  ...theme('spacing'),
  full: '100%',
  screen: 'calc(var(--vh) * 100)',
  min: 'min-content',
  max: 'max-content',
  fit: 'fit-content',
}),
....
Enter fullscreen mode Exit fullscreen mode

Result

vh fix

Footer

Next up is Footer. There is nothing fancy to add here so one of the components from Daisy UI will work. I decided to use this one. Its got the Favicon, copyright text and some social icons too. Good enough for me. So I'll create a new component with nuxi and then update it as per my preference

npx nuxi add component Footer
Enter fullscreen mode Exit fullscreen mode
# components/Footer.vue
<script lang="ts" setup>
import { OhVueIcon, addIcons } from 'oh-vue-icons';
import { RiGithubFill, RiTwitterFill, SiDevdotto } from 'oh-vue-icons/icons';

addIcons(RiGithubFill, RiTwitterFill, SiDevdotto);
</script>

<template>
  <footer class="footer items-center p-4 bg-neutral text-neutral-content">
    <div class="items-center grid-flow-col">
      <!-- Brand -->
      <NuxtLink to="/" class="normal-case text-3xl text-primary">HK</NuxtLink>
      <p>Copyright © {{ new Date().getFullYear() }} - All right reserved</p>
    </div>
    <div class="grid-flow-col gap-4 md:place-self-center md:justify-self-end">
      <a href="https://twitter.com/wrench1815" target="_blank">
        <OhVueIcon name="ri-twitter-fill" scale="1.2" />
      </a>
      <a href="https://dev.to/wrench1815/" target="_blank">
        <OhVueIcon name="si-devdotto" scale="1.5" />
      </a>
      <a href="https://github.com/wrench1815/" target="_blank">
        <OhVueIcon name="ri-github-fill" scale="1.2" />
      </a>
    </div>
  </footer>
</template>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode
# layouts/default.vue
....
<!-- Footer -->
<Footer />
....
Enter fullscreen mode Exit fullscreen mode

Yep, good enough for me.

After Footer

Well Nav and Footer are done, for now. I'll start creating Home Page in next part.


Cover Credits: Jean-Frederic Fortier


Top comments (0)