Réaliser un Side Drawer avec Astro et Vue JS

Réaliser un Side Drawer avec Astro et Vue JS

Astro Vue Tailwind

Introduction

Dans cet article nous allons implémenter un Side Drawer avec Astro et Vue JS.

Pour rappel Astro est un framework web ayant pour but de générer une MPA (Multi Page Application) et focus de base le SSG (Static Site Generation).

A l’inverse, Next JS ou Nuxt JS permettent de générer des SPA (Single Page Application) via SSG (Static Site Generation) ou SSR (Server Side Rendering).

Le composant Backdrop Vue JS

Commençons par créer un composant Backdrop avec Vue JS qui permettra d’obtenir une ombre portée lorsque le Side Drawer sera ouvert. Nous utiliserons également Tailwind pour simplifier l’écriture du code css.

<!-- src/components/ui/Backdrop.vue -->

<template>
  <div class="z-20 fixed inset-0 transition-opacity">
    <div class="absolute inset-0 bg-black opacity-70"></div>
  </div>
</template>

Pour le moment rien de bien compliqué dans le composant Backdrop, il n’y a que du code HTML 🙂.

Les nanos stores avec Astro

Ensuite, il va être nécessaire d’avoir un mécanisme qui nous permettre de gérer un état dans notre application Astro. Pour ce faire, nous allons utiliser les nano stores.

// src/store/drawerStore.ts 

import { atom } from 'nanostores';

export const isDrawerOpen = atom(false); 

Le code est très simple, l’atom dispose d’un état de type boolean et nous l’initialisons à false par défaut, pour que notre Drawer soit fermé.

Le composable Vue JS

Nous allons maintenant créer un composable avec Vue JS, qui exploitera le nano store. Pour rappel, un composable est une fonction qui peut contenir un état et être réutilisable entre plusieurs composants.

// src/composables/useDrawer.ts

import { useStore } from "@nanostores/vue";
import { isDrawerOpen } from "../store/drawerStore";

export function useDrawer() {
  const $isDrawerOpen = useStore(isDrawerOpen);

  function openDrawer() {
    isDrawerOpen.set(true);
  }

  function closeDrawer() {
    isDrawerOpen.set(false);
  }

  function toggleDrawer() {
    isDrawerOpen.set(!$isDrawerOpen.value);
  }

  return { isDrawerOpen: $isDrawerOpen, openDrawer, closeDrawer, toggleDrawer };
}

Le composable est relativement simple, il expose des fonctions permettant d’ouvrir ou fermer le Drawer tout en mettant l’état du nano store à jour.

Le composant Drawer Vue JS

Passons maintenant au composant Drawer, qui utilisera l’ensemble de ce que nous avons précédemment créer.

<!-- src/components/ui/Drawer.vue --> 

<template>
    <Backdrop v-if="isDrawerOpen" @click="closeDrawer" />
    <aside
        class="drawer transform top-0 left-0 w-3/4 bg-white fixed h-screen overflow-auto ease-in-out transition-all duration-150 z-30"
        :class="{'translate-x-0': isDrawerOpen, '-translate-x-full': !isDrawerOpen}">
        <div class="bg-zinc-700 h-16 flex items-center">
            <h2 class="ml-4 text-xl text-white">Menu</h2>
        </div>
        <ul class="ml-8">
            <slot />
        </ul>
    </aside>
</template>
  
<script lang="ts" setup>
import { useDrawer } from '../../composables/useDrawer';
import Backdrop from './Backdrop.vue';

const { isDrawerOpen, closeDrawer } = useDrawer();
</script>

Le Backdrop est caché ou affiché en fonction de l’état du Drawer. Des classes tailwind sont également utilisées pour animer l’ouverture du Drawer en fonction de l’état ouvert ou fermé.

Vous noterez également que nous avons utilisé l’instruction <slot /> qui permet d’injecter du contenu dynamique à l’intérieur du Drawer. Nous nous en sevirons pour afficher la liste des liens.

Afin de pouvoir correctemnet fermer le Drawer il est nécessaire de définir un nouveau composant que nous allons appeler DrawerLink.

Le composant utilisera notre composable useDrawer, afin de pouvoir fermer le Drawer, lorsque l’utilisateur cliquera sur le lien.

<!-- src/components/ui/DrawerLink.vue -->

<template>
    <li class="mt-8 flex" @click="closeDrawer">
        <slot />
    </li>
</template>

<script lang="ts" setup>
import { useDrawer } from '../../composables/useDrawer';

const { closeDrawer } = useDrawer();
</script>

Un click event est bindé sur la balise <li>, à l’aide de @click="closeDrawer". L’event appelera la fonction closeDrawer fournie par notre composable.

Un slot est aussi utilisé pour personaliser le contenu du lien. Nous utiliserons dans le composant parant un icône Astro avec un label.

Le composant Header avec Astro

Pour terminer, créeons notre composant Header en Astro afin d’afficher notre Drawer. Vous remaquerez la directive client:load qui est très importante, c’est grâce à cette instruction que Astro va contrôler l’hydratation javascript pour nos composants. Sans cette directive Astro considèrerait nos composants comme du HTML à 100%.

Nous utiliserons aussi un Array d'objet pour lister les liens du Drawer (const links).

---
// src/components/Header.astro

import { Icon } from "astro-icon";
import Drawer from "./Drawer.vue";

const links = [
  {
    href: "/",
    label: "Accueil",
    icon: "home",
  },
  {
    href: "/articles",
    label: "Mes articles",
    icon: "post",
  },
];
---
<header>
  <Drawer client:load>
    {
      links.map((link) => (
        <DrawerLink client:load>
          <div class="mr-4">
            <Icon pack="mdi" name={link.icon} class="w-6 h-6" />
          </div>
          <a
            class:list={[
              { active: Astro.url.pathname === link.href },
              "hover:underline",
              "ml-2",
            ]}
            href={link.href}
          >
            {link.label}
          </a>
        </DrawerLink>
      ))
    }
  </Drawer>
</header>

Nous itérons sur la liste de liens et la tranformons en composants DrawerLink, grâce à la fonction map.

La directive class:list est utilisée pour conditionner le lien actif, en fonction de l’url courante.

Vous noterez également que nous utilisons la librairie astro-icon pour afficher des icônes mdi.

Voilà, nous arrivons à la fin de l’article, j’espère que cet exemple avec Astro et Vue JS vous sera utile 😀.