Sviluppo fullstack con AdonisJS parte 6: Personalizzazione del tema
lunedì, 10 ottobre 2022Personalizzare il tema grafico con NaiveUI
Foto di Pavel Nekoranec su Unsplash
Premessa
Questo articolo fa parte di una serie che illustra come costruire un’applicazione con AdonisJS e VueJS. Se ti fossi perso la prima parte inizia da qui.
Introduzione
In questo articolo andremo a introdurre alcune personalizzazioni grafiche colorando barra laterale e menù di navigazione e inserendo il contenuto principale in una card.
Il tema di Naive UI
Il modo più semplice di personalizzare la grafica è intervenire sul tema di Naive UI come abbiamo già visto. Per ordinare meglio il codice spostiamo tutte le personalizzazioni in un nuovo file resources/js/theme.js
:
// resources/js/theme.js
const white = '#ffffff'
const grey1 = '#F8F9FF'
const grey2 = '#303E52'
const grey3 = '#0D1118'
const red1 = '#F5505E'
const red2 = '#BA1C31'
const blue1 = '#53E8C9'
const blue2 = '#4FDDBF'
/**
* Use this for type hints under js file
* @type import('naive-ui').GlobalThemeOverrides
*/
const theme = {
common: {
primaryColor: red1,
primaryColorHover: red1,
},
Layout: {
color: 'transparent',
headerColor: 'transparent',
siderColor: 'transparent',
},
Menu: {
itemTextColor: white,
itemIconColor: white,
itemIconColorCollapsed: white,
itemColorActive: blue1,
itemIconColorHover: blue1,
itemTextColorHover: blue1,
itemColorActiveCollapsed: blue1,
itemIconColorChildActive: blue1,
itemTextColorChildActive: blue1,
},
Card: {
borderRadius: '10px',
},
}
export default theme
Abbiamo isolato i colori dentro a delle costanti richiamabili più volte e personalizzato alcuni componenti base.
Includiamo il nuovo file in App.vue
:
<!-- resources/js/components/App.vue -->
<script>
import { defineComponent } from 'vue'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import theme from '../theme'
import Layout from './Layout.vue'
export default defineComponent({
components: {
NMessageProvider,
NConfigProvider,
Layout,
},
setup() {
return {
theme,
}
},
})
</script>
<template>
<n-config-provider :theme-overrides="theme">
<n-message-provider>
<router-view />
</n-message-provider>
</n-config-provider>
</template>
CSS globali
Alcune personalizzazioni come gli sfondi con gradienti non si riescono ad applicare direttamente dal tema, andiamo quindi ad impostarli tramite un file css globale che creiamo in resources/css/app.css
/* resources/css/app.css */
html,
body {
height: 100vh;
}
body {
background: linear-gradient(0deg, #0d1118 85%, #303e52 100%);
}
.n-layout-sider {
background: linear-gradient(0deg, #ba1c31 0%, #f5505e 100%);
}
.main-card {
max-width: 600px;
margin-top: -3.2rem;
box-shadow: 0 0 60px rgba(0, 0, 0, 0.2);
}
Importiamo il nuovo file in resources/js/app.js
:
// resources/js/app.js
// ...
import '../css/app.css'
// ...
Il componente Sidebar
Per una migliore organizzazione del codice isoliamo la Sidebar in un nuovo componente:
<!-- resources/js/components/Sidebar.vue -->
<script>
import { defineComponent, h, ref } from 'vue'
import { NButton, NMenu, NIcon } from 'naive-ui'
import { RouterLink } from 'vue-router'
import {
Information as WorkIcon,
Home as HomeIcon,
Logout as LogoutIcon,
Bookmark as BookmarkIcon,
Add as AddIcon,
List as ListIcon,
} from '@vicons/carbon'
function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) })
}
export default {
name: 'Sidebar',
components: { NButton, NMenu },
data() {
const menuOptions = [
{
label: 'Categorie file',
key: 'file-categories',
icon: renderIcon(BookmarkIcon),
children: [
{
label: () =>
h(
RouterLink,
{
to: {
path: '/file-categories',
},
},
{ default: () => 'Elenco categorie' }
),
key: 'file-categories-list',
icon: renderIcon(ListIcon),
},
{
label: () =>
h(
RouterLink,
{
to: {
path: '/file-categories/create',
},
},
{ default: () => 'Crea categoria' }
),
key: 'file-categories-create',
icon: renderIcon(AddIcon),
},
],
}
]
return {
menuOptions,
}
},
}
</script>
<template>
<n-menu :collapsed-width="64" :collapsed-icon-size="22" :options="menuOptions" />
</template>
Che andiamo ad importare in Layout.vue
applicando anche qualche personalizzazione grafica allo sfondo:
<script>
import { defineComponent } from 'vue'
import { NSpace, NLayout, NLayoutHeader, NLayoutContent, NLayoutSider } from 'naive-ui'
import Navbar from './Navbar.vue'
import Sidebar from './Sidebar.vue'
export default defineComponent({
components: {
NSpace,
Navbar,
NLayout,
NLayoutHeader,
NLayoutContent,
NLayoutSider,
Sidebar,
},
})
</script>
<template>
<n-space vertical size="large">
<n-layout>
<n-layout has-sider>
<n-layout-sider
show-trigger
collapse-mode="width"
:collapsed-width="64"
:width="240"
default-collapsed
style="height: 100vh"
>
<sidebar />
</n-layout-sider>
<n-layout>
<n-layout-header>
<navbar />
</n-layout-header>
<n-layout-content style="height: 100vh">
<div class="wrapper">
<router-view></router-view>
</div>
</n-layout-content>
</n-layout>
</n-layout>
</n-layout>
</n-space>
</template>
<style lang="scss" scoped>
.wrapper {
margin-top: 4rem;
padding: 1.8rem;
background: #f8f9ff;
min-height: 80vh;
}
</style>
Ripuliamo poi la Navbar
dalle voci che abbiamo spostato nella Sidebar
:
<!-- resources/js/components/Navbar.vue -->
<script>
import { defineComponent, h, ref } from 'vue'
import { NButton, NMenu, NIcon } from 'naive-ui'
import { RouterLink } from 'vue-router'
import {
Information as WorkIcon,
Home as HomeIcon,
Logout as LogoutIcon,
Bookmark as BookmarkIcon,
Add as AddIcon,
List as ListIcon,
} from '@vicons/carbon'
function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) })
}
export default {
name: 'Navbar',
components: {
NButton,
NMenu,
},
data() {
const menuOptions = [
{
label: () =>
h(
RouterLink,
{
class: 'logo',
to: {
name: 'home',
},
},
{ default: () => 'Unity' }
),
key: 'logo',
},
{
label: () =>
h(
RouterLink,
{
to: {
name: 'home',
},
},
{ default: () => 'Home' }
),
key: 'home',
icon: renderIcon(HomeIcon),
},
{
label: () =>
h(
RouterLink,
{
to: {
path: '/about',
},
},
{ default: () => 'About' }
),
key: 'about',
icon: renderIcon(WorkIcon),
},
{
label: () =>
h(
'a',
{
href: '#',
onClick: (e) => {
e.preventDefault()
this.$http.get('/logout').then((response) => {
if (response.status === 200) {
this.$router.push('/login')
}
})
},
},
'Logout'
),
key: 'logout',
icon: renderIcon(LogoutIcon),
},
]
return {
menuOptions,
activeKey: ref(null),
}
},
}
</script>
<template>
<n-menu v-model:value="activeKey" mode="horizontal" :options="menuOptions" />
</template>
La card principale nelle pagine
Infine inseriamo nelle pagine una card con la classe speciale “main-card” per isolarne il contenuto:
<!-- resources/js/pages/Home.vue -->
<!-- ... -->
<template>
<n-card title="Home" size="large" class="main-card">
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium repudiandae dolorem
temporibus deleniti libero! Voluptate quibusdam laboriosam nulla veniam, fuga quae!
Praesentium mollitia quaerat doloribus sit inventore. Doloremque eius sequi fugit omnis magnam
repellat sit corrupti excepturi repellendus! Aut error adipisci rerum delectus, necessitatibus
beatae, consequuntur, nemo quibusdam corrupti incidunt ut vero quam labore amet nulla quos.
</p>
</n-card>
</template>
<!-- resources/js/pages/CreateAndUpdate.vue -->
<!-- ... -->
<template>
<n-card class="main-card" :title="`${actionText} una nuova categoria di file`">
<n-form ref="formRef" :model="formValue" :rules="rules" size="medium">
<n-form-item label="Nome" path="name">
<n-input v-model:value="formValue.name" />
</n-form-item>
<n-form-item>
<n-button @click="submit" attr-type="submit" type="primary"> {{ actionText }} </n-button>
</n-form-item>
</n-form>
</n-card>
</template>
<!-- resources/js/pages/List.vue -->
<!-- ... -->
<template>
<template>
<n-card title="Elenco categorie file" class="main-card">
<n-list bordered>
<n-list-item v-for="category in categories" :key="category.id">
<template #prefix>
<n-button quaternary circle @click="goToUpdateCategory(category.id)">
<template #icon>
<n-icon><edit-icon /></n-icon>
</template>
</n-button>
</template>
<template #suffix>
<n-button quaternary circle @click="deleteCategory(category.id)">
<template #icon>
<n-icon><trash-can-icon /></n-icon>
</template>
</n-button>
</template>
<n-thing :title="category.name" />
</n-list-item>
</n-list>
</n-card>
</template>
Il prossimo passo
Nel prossimo articolo torneremo ad occuparci del backend applicando delle personalizzazioni alla gestione dei privilegi in modo che solo gli utenti amministratori possano compiere certe azioni.