Les boîtes de dialogue dans l'expérience utilisateur
Autant que je me souvienne, ce défi a été le plus persistant depuis que j'ai commencé le développement web. Récemment, j'ai découvert une méthode pour construire une bibliothèque de composants, ce qui a été un long processus. Cependant, résoudre les subtilités de la gestion des boîtes de dialogue s'est avéré encore plus vaste, complexe, et crucial en raison de leur présence omniprésente dans chaque application.
Aujourd'hui, je suis ravi de partager la solution que j'ai trouvée, je ne l'ai pas développée, pour gérer efficacement les boîtes de dialogue au sein de vos applications. Cette solution découle de plusieurs années d'expérience, et j'espère qu'elle vous sera aussi bénéfique qu'elle l'a été pour moi.
Comprendre le problème
Avant de plonger dans le problème, alignons-nous sur ce dont nous parlons. Cet article se concentre sur les boîtes de dialogue, modaux, et slideover. Chacun sert à afficher des informations ou à solliciter une confirmation, différant principalement en termes de présentation et d'utilisation.
Essayez d'ouvrir la boîte de dialogue en cliquant sur le bouton ci-dessous.
De même, vous pouvez ouvrir un slideover en cliquant sur le bouton.
Le design et les types de dialogues peuvent sembler secondaires ; l'aspect important réside dans le code sous-jacent. Examinons-le :
<template>
<Modal
title="Boîte de Dialogue"
description="Ceci est une boîte de dialogue"
>
<Button label="Ouvrir la Boîte de Dialogue" />
</Modal>
</template>
<template>
<Slideover
title="Slideover"
description="Ceci est un slideover"
>
<Button label="Ouvrir le Slideover" />
</Slideover>
</template>
Ils sont très similaires, reposant sur le même composant en coulisses. La véritable intrigue réside dans la manière dont ce code est utilisé. Ces exemples illustrent que les boîtes de dialogue s'intègrent parfaitement comme tout élément HTML, ce qui est le cœur du problème.
Les boîtes de dialogue sont un type particulier de composant dans une application :
Ils peuvent être invoqués de n'importe où dans l'application.
Ils s'ouvrent par divers moyens, tels que :
- Clics de bouton
- Pressions de touches
- Navigations dans les routes
La nature omniprésente rend des applications sans boîtes de dialogue presque impraticables sans sacrifier l'expérience utilisateur :
- Création d'éléments
- Modification d'éléments
- Confirmations d'action
- Visualisations d'éléments
- Etc.
Chaque action peut mener à des conséquences immédiates ou attendre une réponse du serveur, ce qui complique les possibilités d'utilisation des boîtes de dialogue.
En raison de ces facteurs, les boîtes de dialogue nécessitent une attention minutieuse lors du développement et de l'utilisation ; sinon, ils et leur logique peuvent rapidement devenir chaotiques.
De plus, j'ai toujours eu une préférence personnelle pour permettre l'empilement de boîtes de dialogue avec des animations fluides, comme ceci :
Boîte de dialogue en ligne
Une première tentative pour relever ce défi est via des boîtes de dialogue en ligne. C'est une approche répandue et qui convient aux cas simples. La stratégie consiste à incorporer un composant modal directement dans le template nécessitant celui-ci, comme vu précédemment.
<template>
<Modal
title="Dialogue"
description="Ceci est un dialogue"
>
<Button label="Ouvrir le Dialogue" />
</Modal>
</template>
Bien que l'implémentation soit simple, cette approche présente au moins deux inconvénients significatifs :
- Les composants de boîte de dialogue deviennent étroitement couplés avec le composant qui les utilise, compliquant la compréhension et le refactoring.
- Il devient coûteux, en terme de code, d'ajouter une nouvelle boîte de dialogue, surtout lorsqu'on ouvre les ouvre sans boutons ou en découplant les templates d'une boîte de dialogue. Un exemple d'ouverture d'une boîte dialogue par pression de touche suit :
<script lang="ts" setup>
import { ref } from 'vue'
const isDialogOpen = ref(false)
defineShortcuts({
meta_k: () => {
isDialogOpen.value = !isDialogOpen.value
}
})
</script>
<template>
<div>
<Modal
v-model:open="isDialogOpen"
title="Boîte de Dialogue"
description="Ceci est une boîte de dialogue"
/>
</div>
</template>
Cette méthode devient rapidement encombrante et difficile à maintenir au fil du temps. Plusieurs boîtes de dialogue intensifient le code répétitif, nécessitant des ajouts répétés dans chaque composant utilisant la boîte de dialogue.
Boîtes de dialogue globales
Améliorer la gestion des boîtes de dialogue implique d'en ouvrir programatiquement de manière universelle, découplant leur invocation de toute relation directe avec un composant. Ce modèle facilite le refactoring du code en séparant physiquement les boîtes de dialogue des composants, améliorant la réutilisabilité à travers plusieurs composants sans incorporations redondantes.
Un fichier App.vue
pourrait ressembler à ceci :
<template>
<div>
<Layout>
<RouterView />
</Layout>
<ModalFeature1 />
<ModalFeature2 />
<ModalFeature3 />
<!-- Et ainsi de suite... -->
</div>
</template>
Atteindre cette configuration exige de partager une variable entre le composant ouvrant la boîte de dialogue et la boîte de dialogue ell-même. Dans Vue, une approche simple—bien que pas idéale—est de créer une composable externalisant son état.
import { ref } from 'vue'
const isOpen = ref(false)
export function useDialog() {
function open() {
isOpen.value = true
}
function close() {
isOpen.value = false
}
return {
isOpen,
open,
close
}
}
Utilisez ce composable pour le composant ouvrant la boîte de dialogue et la boîte de dialogue elle-même.
<script lang="ts" setup>
import { useDialog } from '@/composables/useDialog'
const { open } = useDialog()
</script>
<template>
<Button label="Ouvrir la Boîte de Dialogue" @click="open" />
</template>
<script lang="ts" setup>
import { useDialog } from '@/composables/useDialog'
const { isOpen, close } = useDialog()
</script>
<template>
<Modal
v-model:open="isOpen"
title="Boîte de Dialogue"
description="Ceci est une boîte de dialogue"
>
<Button label="Fermer" @click="close" />
</Modal>
</template>
Cette approche est certainement supérieure à la méthode en ligne mais présente tout de même des inconvénients :
L'inclusion de boîtes de dialogue au niveau de l'application (ou de la route) crée une redondance de code.
La gestion des boîtes de dialogue devient complexe, particulièrement avec de nombreuses boîtes de dialogue ou propriétés, chacune nécessitant un passage à travers la composable.
tsimport { ref } from 'vue' const isOpen = ref(false) const title = ref('') const description = ref('') export function useDialog() { function open(newTitle: string, newDescription: string) { title.value = newTitle description.value = newDescription isOpen.value = true } function close() { isOpen.value = false } return { isOpen, title, description, open, close } }
Note
Simplifiez l'écriture des composables avec createSharedComposable
de @vueuse/core
pour créer un composable singleton, partageant automatiquement le même état entre tous les composants qui l'utilisent.
Dialogues programmatiques
La méthode la plus récente et profondément efficace que j'ai découverte implique la gestion programmatique des boîtes de dialogue. Cette méthode résout les difficultés mentionnées plus tôt en permettant un contrôle complet des boîtes de dialogue par le biais du code plutôt que des templates.
Ayant utilisé des boîtes de dialogue programmatiques dans Angular avec Angular Material, j'ai apprécié sa capacité à simplifier le code.
const dialogRef = dialog.open(UserProfileComponent, {
height: '400px',
width: '600px',
})
dialogRef.afterClosed().subscribe((result) => {
console.log(`Résultat du dialogue : ${result}`) // Pizza !
})
dialogRef.close('Pizza !')
Pendant longtemps, j'ai espéré d'une fonctionnalité similaire dans Vue.
Ce temps est désormais arrivé, lorsque Nuxt a sorti la version 3 de Nuxt UI avec la composable useOverlay
. Ce composable partagé par l'application permet d'ouvrir des boîtes de dialogue de n'importe quel endroit, imitant la fonctionnalité d'Angular Material.
<script setup lang="ts">
const overlay = useOverlay()
async function openModal() {
overlay
.create(MyModal)
.open()
}
</script>
Magnifique, n’est-ce pas ? 😍
Sous le capot, cette méthode utilise la directive component
avec une simple boucle v-for
pour rendre les boîtes de dialogue.
<script lang="ts" setup>
const { overlays, close } = useOverlay()
</script>
<template>
<component
:is="overlay.component"
v-for="overlay in overlays"
:key="overlay.id"
v-bind="overlay.props"
v-model:open="overlay.modelValue"
/>
</template>
Note
Il s'agit d'une version simplifiée du code.
Avec cette méthode, les coûts de la gestion d'état sont éliminés, ce qui supprime le besoin de composables partagés ou de boîtes de dialogue globales. Cela améliore considérablement la lisibilité, la maintenabilité et la création d'expériences UI interactives.
Empilement des boîtes de dialogue sans effort
Le point culminant de cette approche est l'empilement de boîtes de dialogue sans effort. Il suffit de rendre un tableau de boîtes de dialogue via une boucle pour permettre l'ouverture simultanée de boîtes de dialogue, facilitant un empilement élégant avec des animations fluides.
<script lang="ts" setup>
import DialogFeature1 from './components/DialogFeature1.vue'
const overlay = useOverlay()
function openOverlay() {
overlay
.create(DialogFeature1, {
props: {
title: 'Premier Modal',
description: 'Ceci est le premier modal'
},
destroyOnClose: true
})
.open()
}
</script>
<template>
<div>
<Button label="Ouvrir le Premier Overlay" @click="openOverlay" />
<OverlayProvider />
</div>
</template>
<script lang="ts" setup>
import DialogFeature2 from './DialogFeature2.vue'
const props = defineProps<{
title: string
description: string
}>()
const overlay = useOverlay()
function openOverlay() {
overlay
.create(DialogFeature2, {
props: {
title: 'Deuxième Modal',
description: 'Ceci est le deuxième modal'
},
destroyOnClose: true
})
.open()
}
</script>
<template>
<Modal
:title="props.title"
:description="props.description"
>
<template #body>
<Button
label="Ouvrir le Deuxième Modal"
@click="openOverlay"
/>
</template>
</Modal>
</template>
<script lang="ts" setup>
const props = defineProps<{
title: string
description: string
}>()
</script>
<template>
<Modal
:title="props.title"
:description="props.description"
/>
</template>
Explorez le code complet sur vue-dialog-stacking et même une démo en direct.
Conclusion
Après des années de lutte avec les boîtes de dialogue et les modaux et d'essai de plusieurs approches, j'ai enfin trouvé une méthode qui fonctionne pour moi et qui peut fonctionner pour vous.
J'ai d'abord rencontré cette approche avec Angular Material et j'ai espéré quelque chose de similaire dans Vue. Anthony Fu a proposé un début prometteur avec useTemplatePromise
, mais cela est resté complexe uniquement pour l'ouverture de boîtes de dialogue.
Enfin, je remercie zernonia pour Reka UI et Eugen Istoc pour la composable useOverlay
dans Nuxt UI, je peux désormais gérer les boîtes de dialogue de manière appropriée dans Vue.
Je suis convaincu que cette approche est un changement majeur pour les expériences UI interactives dans Vue. N'hésitez pas à l'essayer !
Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web.
Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.
Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.
J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous—cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !
Discussions
Ajouter un commentaire
Vous devez être connecté pour accéder à cette fonctionnalité.