Design System - Naive UI
Dans ce chapitre, on reprend le Show Watchlist du chapitre précédent et on remplace chaque élément HTML brut par son équivalent Naive UI.
Pourquoi utiliser une librairie UI ?
Sans librairie UI, chaque bouton, formulaire ou mise en page doit être conçu et stylisé manuellement. Une librairie comme Naive UI offre :
- Des composants préconçus (boutons, inputs, cartes, grilles…)
- Un design cohérent appliqué uniformément
- Des composants accessibles et testés
- Un gain de temps pour se concentrer sur la logique métier
Installation et configuration
npm install naive-ui
Enregistrement global dans main.ts — tous les composants sont disponibles partout sans import supplémentaire :
// main.ts
import naive from 'naive-ui'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.use(naive)
app.mount('#app')
Remarque : Dans Naive UI, le
v-modeldes inputs s'écritv-model:valueau lieu dev-model. C'est une particularité de la librairie.
Composants essentiels
NButton
Remplace les <button> HTML. Plusieurs types visuels disponibles.
<template>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
Types disponibles : default, primary, info, success, warning, error.
NInput
Remplace les <input> HTML. Utilise v-model:value au lieu de v-model.
<template>
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
</template>
NTag
Remplace les <span> de statut. Idéal pour afficher l'état vu/à voir d'une série.
<template>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
NCard
Remplace les <li> ou <div> de carte. Propose des slots #header-extra et #footer.
<template>
<NCard :title="show.title">
<template #header-extra>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
<p>{{ show.genre }} — {{ show.year }}</p>
<template #footer>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
</NCard>
</template>
NGrid et NGi
Remplace la <ul> / <li> pour une mise en page en grille responsive.
<template>
<NGrid :cols="3" :x-gap="16" :y-gap="16">
<NGi v-for="show in shows" :key="show.id">
<ShowCard :show="show" @toggle-seen="emit('toggle-seen', $event)"/>
</NGi>
</NGrid>
</template>
NSpace
Gère l'espacement et l'alignement entre éléments, en remplacement des <div> de layout.
<template>
<NSpace justify="space-between" align="center">
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
<NTag type="success">{{ seenShows.length }} vu(s) sur {{ shows.length }}</NTag>
</NSpace>
</template>
Exemple complet
Le Show Watchlist du chapitre précédent, avec tous les éléments HTML remplacés par Naive UI.
src/components/ShowCard.component.vue
<template>
<NCard :title="show.title">
<template #header-extra>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
<p>{{ show.genre }} — {{ show.year }}</p>
<template #footer>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
</NCard>
</template>
<script setup lang="ts">
import type { Show } from '@/types'
interface ShowCardProps {
show: Show
}
defineProps<ShowCardProps>()
const emit = defineEmits<{
'toggle-seen': [show: Show]
}>()
</script>
src/components/ShowList.component.vue
<template>
<NSpace vertical :size="16">
<slot></slot>
<p v-if="shows.length === 0">Aucune série pour l'instant.</p>
<NGrid :cols="3" :x-gap="16" :y-gap="16">
<NGi v-for="show in shows" :key="show.id">
<ShowCard :show="show" @toggle-seen="emit('toggle-seen', $event)"/>
</NGi>
</NGrid>
</NSpace>
</template>
<script setup lang="ts">
import type { Show } from '@/types'
import ShowCard from './ShowCard.component.vue'
interface ShowListProps {
shows: Show[]
}
defineProps<ShowListProps>()
const emit = defineEmits<{
'toggle-seen': [show: Show]
}>()
</script>
src/pages/HomePage.vue
<template>
<div>
<ShowList :shows="shows" @toggle-seen="onToggleSeen">
<NSpace justify="space-between" align="center">
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
<NTag type="success">{{ seenShows.length }} vu(s) sur {{ shows.length }}</NTag>
</NSpace>
</ShowList>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { Show } from '@/types'
import ShowList from '../components/ShowList.component.vue'
const search = ref<string>('')
const shows = ref<Show[]>([
{ id: 1, title: 'Inception', genre: 'Sci-Fi', year: 2010, seen: true },
{ id: 2, title: 'The Dark Knight', genre: 'Action', year: 2008, seen: false },
{ id: 3, title: 'Interstellar', genre: 'Sci-Fi', year: 2014, seen: false },
{ id: 4, title: 'Parasite', genre: 'Thriller', year: 2019, seen: true },
{ id: 5, title: 'Dune', genre: 'Sci-Fi', year: 2021, seen: false },
{ id: 6, title: 'Everything Everywhere All at Once', genre: 'Action', year: 2022, seen: true },
])
const seenShows = computed(() =>
shows.value.filter((m) => m.seen)
)
watch(search, (newVal) => {
console.log('Recherche :', newVal)
})
onMounted(() => {
console.log('Application prête')
})
const onToggleSeen = (show: Show) => {
show.seen = !show.seen
}
</script>