Sviluppo fullstack con AdonisJS parte 6: Personalizzazione del tema

Sviluppo fullstack con AdonisJS parte 6: Personalizzazione del tema

Personalizzare 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.

Commenti