Introduction
Dans le développement web moderne, l’utilisation de frameworks comme Nuxt.js 3 facilite la création d’applications réactives et performantes. Un élément clé pour assurer une expérience utilisateur optimale est la conception d’un layout par défaut, qui sert de base à toutes les pages de l’application. Ce layout doit intégrer des éléments essentiels tels qu’une navbar et une sidebar dynamiques, permettant une navigation fluide et intuitive. Pour optimiser le développement, il est crucial d’utiliser des ressources telles que des snippets de code et des packages comme Vue Sidebar Menu, qui offrent des solutions efficaces pour construire une interface utilisateur conforme aux attentes des utilisateurs et aux exigences de design.
Prérequis
Avant de commencer, il est essentiel d’avoir un projet Nuxt.js déjà initialisé, avec Pinia configuré pour la gestion de l’état. Si besoin, référez-vous à nos articles sur l’initialisation d’un projet Nuxt.js 3 optimisé avec Bootstrap et la gestion d’état avec Pinia.
Use Case
Imaginons un scénario dans lequel une application nécessite un layout par défaut structuré pour accueillir diverses fonctionnalités. Supposons que nous développons une application de gestion de projet qui permet aux utilisateurs d’accéder rapidement à différentes sections, telles que le tableau de bord, les tâches, les équipes et les rapports.
Pour ce faire, la création d’une navbar en haut de l’écran facilitera la navigation principale entre ces sections. Cette barre de navigation comportera un formulaire de recherche, des icônes de fonctionnalités, ainsi que le nom et la photo de profil de l’utilisateur connecté, assurant ainsi une expérience fluide et personnalisée.
Parallèlement, une sidebar dynamique, intégrée grâce au package Vue Sidebar Menu, permettra un accès rapide à des options contextuelles spécifiques à chaque section. Par exemple, lorsque l’utilisateur se trouve dans la section des tâches, la sidebar peut afficher des sous-catégories telles que “Tâches assignées”, “Tâches complètes” ou “Créer une nouvelle tâche”.
Pour concevoir ce layout de manière efficace, la recherche de snippets de code facilitera l’implémentation de ces éléments, garantissant ainsi une interface utilisateur conforme aux attentes et optimisée pour le développement.
Recherche de Snippets pour Conception UI
Dans le développement d’un layout qui respecte les normes de design, la recherche de snippets de code devient une étape essentielle. Selon les projets, les exigences des clients peuvent varier considérablement. Pour certains projets, le client impose un design bien défini qu’il souhaite voir intégré dans l’application, tandis que pour d’autres, il laisse une certaine liberté au développeur en fournissant uniquement un prototype. Dans ce dernier cas, la recherche de snippets peut jouer un rôle crucial pour concevoir une interface utilisateur qui ressemble au prototype reçu.
Des sites comme Bootsnipp, Bootdey, CodePen et GitHub Gists offrent une vaste bibliothèque de composants prêts à l’emploi. Ces ressources sont particulièrement utiles pour gagner du temps lors de la création d’interfaces complexes. En utilisant des snippets, vous pouvez intégrer rapidement des éléments de navigation, des formulaires et d’autres composants UI sans avoir à les coder à partir de zéro.
L’utilisation de ces snippets ne se limite pas à la simple rapidité d’implémentation. Elle permet également de garantir que votre interface respecte les spécifications du prototype. Les snippets, souvent conçus par des développeurs expérimentés, sont généralement éprouvés en termes de fiabilité et de cohérence, ce qui peut améliorer la qualité globale de votre application. De plus, la réutilisation de composants standards contribue à créer une interface plus harmonieuse, tout en minimisant le risque d’erreurs.
Ainsi, la recherche de snippets devient un outil précieux pour les développeurs souhaitant équilibrer créativité et conformité avec les exigences de design, quel que soit le niveau de liberté accordé par le client. En facilitant l’intégration d’éléments de design cohérents et fonctionnels, les snippets aident à transformer rapidement une idée en une interface utilisateur efficace et attrayante.
Création du Layout par Défaut
Le layout par défaut est configuré dans un fichier default.vue
situé dans le répertoire layouts
. Ce fichier sert de base aux pages sans layout spécifique, assurant une cohérence visuelle dans toute l’application. Il comprend deux composants essentiels, une navbar (Header
) et une sidebar (Sidebar
), qui seront détaillés ultérieurement. Ensemble, ils facilitent une navigation fluide grâce à des options dynamiques et contextuelles.
<template> <div id="page-top"> <div id="wrapper"> <div class="d-flex flex-column " :class="{'maxBody': storeSidebar.isCollapsed, 'minBody': !storeSidebar.isCollapsed}" id="content-wrapper"> <Header /> <div id="content"> <div class="my-5 pt-5 mx-2"> <slot /> </div> </div> </div> <Sidebar/> <a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a> </div> </div> </template> <script setup > import { useCollapsedSidebar } from '/stores/sidebar' const storeSidebar = useCollapsedSidebar() </script> <style > .container-fluid > h3:first-child{ text-transform: uppercase; font-weight: 600; font-size: 16px!important; color:#495057!important; } .maxBody{ margin-inline-start: 65px; } .minBody{ margin-inline-start: 250px; } @media screen and (max-width: 768px) { .minBody{ margin-inline-start: 65px } } </style>
Ce code de layout organise le contenu principal en utilisant une structure flexible, intégrant un slot qui permet d’insérer du contenu spécifique à chaque page, assurant ainsi une personnalisation tout en maintenant une cohérence visuelle. Il intègre également le store useCollapsedSidebar
pour ajuster dynamiquement la marge de la page en fonction de l’état de la sidebar. La marge (classe maxBody
ou minBody
) s’adapte automatiquement selon que la sidebar est élargie ou réduite, rendant ainsi le layout responsive.
import { defineStore } from 'pinia' export const useCollapsedSidebar = defineStore('collapsed', () => { const isCollapsed = ref(false) function toggleSidebar() { isCollapsed.value = !isCollapsed.value; } return { isCollapsed, toggleSidebar } })
Ce store Pinia, importé et utilisé dans le fichier default.vue
, gère l’état de la sidebar via la propriété isCollapsed
et la fonction toggleSidebar
, permettant de basculer entre les états réduit et élargi de la sidebar.
Il est nécessaire de configurer le fichier app.vue
afin que Nuxt.js applique ce layout par défaut à toutes les pages. En intégrant les balises <NuxtLayout>
et <NuxtPage>
, Nuxt peut ainsi sélectionner dynamiquement le layout approprié et afficher le contenu spécifique de chaque page au sein de ce layout.
<template> <NuxtLayout> <NuxtPage /> </NuxtLayout> </template>
Mise en Place de la Navbar
Pour ajouter une barre de navigation (navbar) fonctionnelle, nous nous appuyons sur un snippet tiré du site Bootsnipp. Ce code de base présente une structure simplifiée, intégrant des éléments tels que le logo de l’application, un formulaire de recherche, des icônes pour les fonctionnalités essentielles, et des informations sur l’utilisateur connecté. En adaptant ce snippet, vous pouvez ajuster les styles et les liens pour qu’ils correspondent au design souhaité de votre projet.
Pour structurer cette navigation dans votre application, nous allons créer un composant Header
dans le dossier components
et le diviser en sous-composants pour une organisation claire. Par exemple, components/Header/logo.vue
sera un composant autonome pour gérer le logo, facilitant ainsi sa modification et réutilisation, tandis que components/Header/index.vue
coordonnera la structure et la logique de la barre de navigation dans son ensemble.
<template> <a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#"> <div class="sidebar-brand-icon mx-5"> <img v-if="!storeSidebar.isCollapsed" src="@/assets/image/logoApplize.png" alt="logo" class="minLogo"> <img v-if="storeSidebar.isCollapsed" src="@/assets/image/iconApplize.png" alt="logo small" class="maxLogo" /> </div> </a> </template> <script setup> import { useCollapsedSidebar } from '/stores/sidebar' const storeSidebar = useCollapsedSidebar() </script> <style> .maxLogo{ width: 65px; height: 70px; vertical-align: middle; padding-block: 24px; padding-inline: 5px; } .minLogo{ width: 95%; height: 70px; vertical-align: middle; padding-block: 10px; } .maxNavLogo{ width: 65px; position: fixed; top: 0; left: 0; z-index: 200; background: #2a3042; } .minNavLogo{ width: 250px; position: fixed; top: 0; left: 0; z-index: 200; background: #2a3042; } @media screen and (max-width: 768px) { /* #wrapper #content-wrapper #content { background: yellow; position: inherit; } */ .minNavLogo{ width: 65px; position: fixed; top: 0; left: 0; z-index: 200; background: #2a3042; } .minLogo{ width: 65px; height: 70px; vertical-align: middle; padding-block: 24px; padding-inline: 5px; } } </style>
<template> <div :class="{'maxNavLogo': storeSidebar.isCollapsed, 'minNavLogo': !storeSidebar.isCollapsed}"> <HeaderLogo /> </div> <nav class="navbar navbar-expand bg-white shadow topbar static-top navbar-light mb-4 fixed-top " :class="{'maxNav': storeSidebar.isCollapsed, 'minNav': !storeSidebar.isCollapsed}"> <div class="container-fluid "> <button class="btn btn-link rounded-circle me-3" id="sidebarToggleTop" type="button" @click="storeSidebar.toggleSidebar"> <i class="fas fa-bars"></i> </button> <form class="app-search d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 "> <div class="position-relative"> <i class="fa-solid fa-magnifying-glass position-absolute top-50 start-0 translate-middle-y ms-2"></i> <input type="text" class="form-control " :placeholder="'Search'"> </div> </form> <ul class="navbar-nav flex-nowrap ms-auto"> <li class="nav-item dropdown d-sm-none no-arrow"> <a class=" nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"> <i class="fas fa-search"></i> </a> <div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown"> <form class="me-auto navbar-search w-100"> <div class="input-group"> <input class="bg-light form-control border-0 small" type="text" placeholder="Search for ..." /> <div class="input-group-append"> <button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button> </div> </div> </form> </div> </li> <li class="nav-item dropdown no-arrow mx-2"> <div class="nav-item dropdown no-arrow"> <a class="nav-link btn header-item " href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <img v-if="$i18n.locale === 'en'" id="header-lang-img" src="@/assets/image/en.jfif" alt="Header Language" height="16"> <img v-if="$i18n.locale === 'fr'" id="header-lang-img" src="@/assets/image/fr.jfif" alt="Header Language" height="16"> </a> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown"> <li> <a class="dropdown-item notify-item language" href="#" target="_self" @click="changeLocale('en')"> <img src="@/assets/image/en.jfif" alt="en-image" class="me-1" height="16"> <span class="align-middle">en</span> </a> </li> <li> <a class="dropdown-item notify-item language" href="#" target="_self" @click="changeLocale('fr')"> <img src="@/assets/image/fr.jfif" alt="fr-image" class="me-1" height="16"> <span class="align-middle">fr</span> </a> </li> </ul> </div> </li> <li class="nav-item dropdown no-arrow mx-2"> <div class="nav-item dropdown no-arrow"> <!-- Dropdown toggle without the arrow --> <a class="nav-link position-relative" aria-expanded="false" data-bs-toggle="dropdown" href="#"> <!-- Icône de la cloche FontAwesome --> <i class="fa-solid fa-bell fa-fw " ></i> <!-- Badge "3+" positionné en haut à droite de l'icône --> <span class="position-absolute top-45 translate-middle badge bg-danger" > 3+ </span> </a> <!-- Dropdown menu --> <div class="dropdown-menu dropdown-menu-end dropdown-list animated--grow-in"> <h6 class="dropdown-header">Alerts center</h6> <a class="dropdown-item d-flex align-items-center" href="#"> <div class="me-3"> <div class="bg-primary icon-circle"><i class="fas fa-file-alt text-white"></i></div> </div> <div><span class="small text-gray-500">December 12, 2019</span> <p>A new monthly report is ready to download!</p> </div> </a> <a class="dropdown-item d-flex align-items-center" href="#"> <div class="me-3"> <div class="bg-success icon-circle"><i class="fas fa-donate text-white"></i></div> </div> <div><span class="small text-gray-500">December 7, 2019</span> <p>$290.29 has been deposited into your account!</p> </div> </a> <a class="dropdown-item d-flex align-items-center" href="#"> <div class="me-3"> <div class="bg-warning icon-circle"><i class="fas fa-exclamation-triangle text-white"></i></div> </div> <div><span class="small text-gray-500">December 2, 2019</span> <p>Spending Alert: We've noticed unusually high spending for your account.</p> </div> </a> <NuxtLink class="dropdown-item text-center small text-gray-500" to="/notification">Show All Alerts</NuxtLink> </div> </div> </li> <div class="d-none d-sm-block topbar-divider"></div> <li class="nav-item dropdown no-arrow mx-2"> <div class="nav-item dropdown no-arrow"> <NuxtLink class="nav-link" aria-expanded="false" data-bs-toggle="dropdown" to="#"> <span class="d-none d-lg-inline me-2 text-gray-600 small">John Doe</span> <img class="border rounded-circle img-profile" src="@/assets/image/profile01.jpeg" height="32px"/> </NuxtLink> <div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"> <NuxtLink class="dropdown-item text-account" to="/user-account"> <i class="fa-solid fa-user font-size-16 align-middle me-1"></i> <span class="align-middle ">My account</span> </NuxtLink> <div class="dropdown-divider"></div> <NuxtLink class="dropdown-item text-danger" to="/signup"> <i class="fa-solid fa-power-off font-size-16 align-middle me-1 text-danger"></i> <span class=" align-middle">Logout</span> </NuxtLink> </div> </div> </li> </ul> </div> </nav> </template> <script setup > import { useCollapsedSidebar } from '/stores/sidebar' const storeSidebar = useCollapsedSidebar() const changeLocale = (newLocale) => { if (availableLocales.includes(newLocale)) { locale.value = newLocale } } </script> <style > .app-search .form-control { border: none; height: 38px; padding-left: 40px; padding-right: 20px; background-color: #f3f3f9; box-shadow: none; border-radius: 30px; font-family: "Poppins", sans-serif; } .app-search span { position: absolute; z-index: 10; font-size: 16px; line-height: 38px; left: 13px; top: 0; color: #74788d; } .text-account{ color:#212529; } .text-danger { --bs-text-opacity: 1; color: rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important; } .font-size-16 { font-size: 16px!important; } .minNav{ z-index: 100; margin-inline-start: 250px; } .maxNav{ z-index: 100; margin-inline-start: 65px; } .topbar .topbar-divider { width: 0; border-right: 1px solid #e3e6f0; height: calc(4.375rem - 2rem); margin: auto 1rem; } @media screen and (max-width: 768px) { .minNav{ margin-inline-start: 65px } } /* Styles for the locale dropdown button */ .locale-dropdown { display: flex; text-align: inherit; align-items: center; text-decoration: none; --bs-dropdown-link-active-bg: none; } /* Styles for the select element */ .locale-select { padding: 5px; border: 1px solid #ccc; border-radius: 4px; margin-left: 10px; } </style>
Intégration du Vue Sidebar Menu
Pour ajouter une sidebar dynamique à notre application, nous allons intégrer le package Vue Sidebar Menu. Ce composant nous permettra de créer une interface de navigation intuitive et adaptable. Commençons par installer le package vue-sidebar-menu
en utilisant la commande suivante :
yarn add vue-sidebar-menu
Ensuite, nous devons créer un fichier VueSidebarMenu.js
dans le dossier plugins
pour intégrer le package Vue Sidebar Menu dans notre application.
import VueSidebarMenu from 'vue-sidebar-menu' export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use(VueSidebarMenu); });
Dans le fichier nuxt.config.ts
, pour garantir que la sidebar ait le style approprié, incluez le fichier CSS du package et chargez le fichier VueSidebarMenu.js
en tant que plugin, en précisant que ce dernier doit être exécuté uniquement dans le navigateur avec mode: 'client'
, ce qui est essentiel pour les fonctionnalités qui manipulent le DOM.
export default defineNuxtConfig({ css: [ 'vue-sidebar-menu/dist/vue-sidebar-menu.css', ], plugins: [ { src: '~/plugins/VueSidebarMenu.js', mode: 'client' }, ], });
Mise en Place de la Sidebar
Une fois le package installé et configuré, vous pouvez utiliser le composant Vue Sidebar Menu dans un fichier Sidebar.vue
que vous allez créer dans le dossier components
. Voici un exemple de base :
<template> <sidebar-menu :menu="menu" :collapsed="storeSidebar.isCollapsed" :hide-toggle="true" :width="'250px'" :showOneChild="true" > </sidebar-menu> </template> <script setup > import { useCollapsedSidebar } from '/stores/sidebar' const storeSidebar = useCollapsedSidebar() const menu = [ { href: '/home', title: 'Dashboard', icon: 'fa-solid fa-house fs-6', }, { href: '#', title: 'Task', icon: 'fa-solid fa-list-check fs-6', child: [ { href: '/assignedTasks', title: 'Assigned Tasks', }, { href: '/completedTasks', title: 'Completed Tasks', }, { href: '/newTask', title: 'New Task', }, ], }, { href: '/team', title: 'Team', icon: 'fa-solid fa-user-plus fs-6', }, { href: '/report', title: 'Report', icon: 'fa-solid fa-bug fs-6', }, ]; </script> <style > .v-sidebar-menu { position: fixed; top: 0; left: 0; height: 100vh; z-index: 200; color: #a6b0cf; background: #2a3042; margin-block-start: 70px; transition: width 0.3s ease; } .v-sidebar-menu .vsm--link_level-1 .vsm--icon { background-color:#2a3042; margin-right: 0.11rem; padding-bottom: 5px; color: #79829c; } .v-sidebar-menu .vsm--link_level-1.vsm--link_active .vsm--icon, .v-sidebar-menu .vsm--link_level-1.vsm--link_hover .vsm--icon { color: #fff; background-color: transparent; } .v-sidebar-menu.vsm_expanded .vsm--link_level-1{ /* background-color:#2a3042; */ color: #a6b0cf; padding-block: 0.1rem; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-1.vsm--link_open { background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-1.vsm--link_hover { background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu .vsm--link_level-1.vsm--link_active { -webkit-box-shadow: none; box-shadow: none ; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-2{ color: #79829c; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-2.vsm--link_open { /* background-color:#2a3042; */ color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-2.vsm--link_hover { background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_expanded .vsm--link_level-2{ padding-inline-start: 2.9rem; padding-block-end: 1px; padding-block: 1px; font-size: 13px; color: #79829c; /* background-color:#2a3042; */ } .v-sidebar-menu .vsm--dropdown { background-color:#2a3042; overflow: hidden !important; /* background-color: red; */ } .v-sidebar-menu.vsm_collapsed .vsm--dropdown { background-color:#2a3042; overflow: hidden !important; overflow-y: hidden !important; /* background-color: red; */ } .v-sidebar-menu.vsm_collapsed .vsm--child.vsm--child_mobile{ overflow-y: hidden !important; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-1 .vsm--icon { /* background-color: #2a3042; */ margin: 0 auto; display: flex; justify-content: center; align-items: center; } .v-sidebar-menu.vsm_collapsed .vsm--arrow_default { display: none; } .v-sidebar-menu.vsm_collapsed .vsm--mobile-bg{ background-color:#2e3548; color:#fff; } .v-sidebar-menu.vsm_collapsed .vsm--link.vsm--link_level-1.vsm--link_hover .vsm--icon{ background-color:#2e3548; font-size: 13px; } .v-sidebar-menu.vsm_collapsed .vsm--link.vsm--link_level-1 .vsm--title { background-color:#2e3548; font-size: 13px; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-1{ background-color:#2a3042; color: #a6b0cf; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-1.vsm--link_hover{ background-color:#2a3042; color: #fff; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-1.vsm--link_active{ background-color:#2a3042; color: #fff; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-1.vsm--link_open{ background-color:#2a3042; color: #fff; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-2{ /* background-color:#2a3042; */ color: #79829c; font-size: 13px; padding-block: 0rem; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-2.vsm--link_hover{ background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-2.vsm--link_active{ background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_collapsed .vsm--link_level-2.vsm--link_open{ background-color:#2a3042; color: #fff; font-size: 13px; } .v-sidebar-menu.vsm_collapsed .vsm--menu { transition: width 0.0001s ease; } @media screen and (max-width: 768px) { .v-sidebar-menu { position: fixed; top: 0; left: 0; height: 100vh; z-index: 200; background: #2a3042; margin-block-start: 70px; } } </style>
Dans ce code, le composant <sidebar-menu>
est configuré avec des propriétés telles que :menu
, qui contient la structure de la barre latérale, et :collapsed
, qui détermine si la barre est réduite ou non, en se basant sur un store de gestion d’état. Le script utilise useCollapsedSidebar
pour récupérer l’état de la barre latérale, et l’objet menu
, représentant les éléments de la sidebar, est composé d’un titre, d’une route, d’une icône et de sous-menus, ce qui permet de l’étendre ou de le modifier facilement en fonction des besoins de votre application. Les styles CSS définissent l’apparence et le comportement de la barre latérale, y compris des transitions et des ajustements pour les écrans de petite taille.
Démonstration du Layout par Défaut
Voici la structure finale de notre interface illustrant le layout par défaut, comprenant la sidebar et la navbar, ainsi qu’un peu de contenu ajouté dans la page index.vue
pour offrir une expérience utilisateur intuitive.

Conclusion
Pour créer un layout par défaut performant et ergonomique dans Nuxt.js 3, nous avons intégré une barre de navigation (navbar) et une barre latérale (sidebar) dynamiques, toutes deux essentielles pour une expérience utilisateur fluide et intuitive. Grâce à l’utilisation de snippets et du package Vue Sidebar Menu, nous avons structuré un environnement modulaire et réactif, où chaque composant a un rôle distinct et précis. La navbar inclut un logo, un formulaire de recherche, des icônes de fonctionnalités, ainsi que des informations utilisateur, tandis que la sidebar est enrichie de liens contextuels, permettant une navigation efficace dans l’application. Ces éléments, combinés à une gestion d’état personnalisée avec Pinia, garantissent une interface harmonieuse et réutilisable, adaptable aux besoins de chaque utilisateur.
Développeuse Full-Stack avec une spécialisation dans les technologies web et une solide compréhension des enjeux climatiques. Diplômée de l'université Joseph Ki-Zerbo, Ouagadougou, Burkina en Informatique et Changement Climatique. Passionné par la création de solutions innovantes pour lutter contre le changement climatique. Compétences avancées en programmation et en analyse de données, associées à une expérience approfondie dans la modélisation climatique et la recherche scientifique.