Creando un sencillo sistema de notificaciones en Vue3 con TailwindCSS (MerakiUI)
Tags: vue, tailwindcss, spanish
Published: 2021-10-25 20:42:15

Hola a todos, dicen por ahí que es bueno escribir artículos técnicos porque así reafirmas lo aprendido, pues, en esta ocasión, voy a seguir con Vue3 y hoy particularmente con TailwindCSS y MerakiUI, creando un sencillo sistema de notificaciones para tu app.

###TailwindCSS
Es una potente librería de utilidades para CSS, te ahorrará mucho tiempo a la hora de escribir tus estilos, aunque, NO reemplaza al CSS puro. 

###MerakiUI
Una librería de componentes responsivos y limpios, basada en Flexbox y Grid, completamente responsiva. 

###Motivación
En mi tiempo libre por las noches, he estado ¨rehaciendo¨ una app que tengo online y en producción desde principios de año, he querido hacerla lo mas ligera posible y por eso la estoy rediseñando y reescribiendo desde 0 con Vue3 y TailwindCSS, actualmente esta con Vue2 y Vuetify + otras deps, y no por hecharle tierra a Vuetify, pero está bastante pesadita. 

###Notificar o no notificar?
Una de las cosas mas importantes en tu app y UX es la interacción con el usuario, las buenas prácticas dictan de informar al usuario qué esta pasando con sus acciones, si se completaron o si terminaron en un error, aquí entran las alertas, hay N+1 librerías open source que te permiten gestionarlas, pero quiero depender lo menos de 3ros, asi que me puse a intentar construir mi propio sistema.

###Manos a la obra
Lo primero que tuve en mente fue armar un composable que gestionara todo lo de las alertas

Archivo: `useToast.ts`
```typescript
import { ref } from 'vue';

// Este arreglo contendrá las notificaciones
const messages = ref<ToastOptions[]>([]);

const useToast = () => {
    // Empuja una nueva alerta al arreglo
    const addAlert = (options: ToastOptions) => {
        messages.value.push({
            id: options.id,
            message: options.message,
            type: options.type || 'success',
        });
       // A los 3 segundos, que se disipe.
        setTimeout(() => {
            messages.value.length > 0 ? removeAlert(messages.value.findIndex((item) => item.id === options.id)) : null;
        }, 3000);
    };
    // Remueve el elemento del arreglo de mensajes
    const removeAlert = (index: number) => {
        if (index > -1) {
            messages.value.splice(index, 1);
        }
    };

    return {
        messages,
        addAlert,
        removeAlert,
    };
};

export default useToast;
```
De esa forma podemos traernos `useToast()` a cualquier parte de nuestra app.

Continuamos con el componente:
Archivo: `alert.vue`
```vue
<template>
  <div
    v-for="alert, index in messages"
    :key="index"
    ref="item"
    class="absolute right-5 top-5 flex w-full max-w-sm mx-auto overflow-hidden bg-white rounded-lg shadow-lg dark:bg-gray-800 animate-fadeIn"
  >
    <div
      :class="`flex items-center justify-center w-12  ${alert.type === 'error' ? 'bg-red-500' : 'bg-green-500'}`"
    >
      <svg
        class="w-6 h-6 text-white fill-current"
        viewBox="0 0 40 40"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M20 3.36667C10.8167 3.36667 3.3667 10.8167 3.3667 20C3.3667 29.1833 10.8167 36.6333 20 36.6333C29.1834 36.6333 36.6334 29.1833 36.6334 20C36.6334 10.8167 29.1834 3.36667 20 3.36667ZM19.1334 33.3333V22.9H13.3334L21.6667 6.66667V17.1H27.25L19.1334 33.3333Z"
        />
      </svg>
    </div>

    <div class="px-4 py-2 -mx-3">
      <div class="mx-3 w-11/12">
        <span
          :class="`font-semibold ${alert.type === 'error' ? 'text-red-500' : 'text-green-500'} dark:text-red-400`"
        >{{ alert.type === 'error' ? 'Error' : 'Completado' }}</span>
        <p class="text-sm text-gray-600 dark:text-gray-200 w-full">{{ alert.message }}</p>
      </div>
    </div>
  </div>
</template>


<script lang="ts">
import { defineComponent, PropType } from "vue"; export default defineComponent({
  props: {

    messages: {
      type: [] as PropType<ToastOptions[] | null>,
      default: []
    }
  },
})
</script>
```
El componente recibirá como props el arreglo de mensajes que habíamos definido en `useToast()`, ya luego solo queda usarlo en `App.vue`

Archivo: `App.vue`
```vue
<template>
  <Alert :messages="messages" />
  <default-vue />
</template>

<script lang="ts">
import DefaultVue from "./layouts/Default.vue";
import Alert from "./components/common/alert.vue"
import { defineComponent } from "vue";
import useToast from "./utils/useToast";

export default defineComponent({
  components: {
    DefaultVue, Alert
  },
  name: "App",
  setup() {
    const { messages } = useToast()
    return {
      messages,
    }
  }
})
</script>
```
Para llamar a la alerta desde cualquier lado:

```typescript
import useToast from './useToast';
const { addAlert } = useToast();

addAlert({id: generateGuid(),
message: error.message,
type: 'error',
});
```

Resultando en:
![Alertas con MerakUI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njrmrjebke9v1m5344a6.gif)


Y hasta aquí este sencillo artículo, espero les haya servido de algo

¿Para mejorarlo?:
- Hacer que una alerta este por debajo de la otra y se acumulen
- Adicionar un boton de cerrar la alerta.

Cualquier comentario y/o crítica es bienvenida!