Una de las cosas notables que hice este año es aprender tailwind CSS y utilizarlo en algunos de mis proyectos.

Me encantó la amplia gama de clases CSS que ofrece a los desarrolladores para satisfacer sus necesidades y la hermosa interfaz de usuario que podemos crear utilizando el framework correctamente.

En este artículo, te mostraré cómo optimizar la experiencia de usuario de tu aplicación web mostrando mensajes en un elegante snack bar Angular Material optimizada con tailwind CSS.

Así que para empezar, déjame mostrarte el resultado final después de los retoques en la hoja de estilo por defecto mat-snackbar.

  • En la parte superior, puedes ver el aspecto del snack bar de Angular Material con su hoja de estilos predeterminada.
  • En la parte inferior, puedes ver tres apariencias - relleno, contorno y suave - para tres tipos de notificaciones: información, éxito y error.

Configuración del proyecto


Para construir estos snack bars, he creado una nueva aplicación Angular ng-snackbar-tailwind ejecutando el comando ng new:

ng new ng-snackbar-tailwind

Y le añadí el componente heroes del tutorial de Angular tour of heroes.

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;
  selectedHero?: Hero;

  constructor() { }

  ngOnInit(): void {
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
view raw

Luego instalé Angular Material de la siguiente manera:

ng add @angular/material

He elegido Indigo/pink como tema precompilado y he respondido con un "sí" a las dos preguntas sobre la configuración de estilos tipográficos globales de Angular Material y animaciones de navegador para Angular Material:

Esto actualizará su archivo package.json con las dependencias recién añadidas:

Si echamos un vistazo ahora a lo que tenemos en el componente de héroes, nos damos cuenta de que simplemente muestra una lista de héroes, y cada vez que el usuario hace clic en uno de ellos, puede ver los detalles (id y nombre) en la parte inferior:

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span>
      <span class="name">{{hero.name}}</span>
    </button>
  </li>
</ul>

<div *ngIf="selectedHero">
  <div>{{selectedHero.name | uppercase}} Details</div>
  <div>id: {{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>
</div>

Si despliego la aplicación ejecutando ng serve, puedo comprobar cómo se ve en el navegador:

Abrir un Snack Bar


Bien. Volvamos al código fuente y ajustemos el heroes.components.ts como se indica a continuación:

import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;
  selectedHero?: Hero;

  constructor(private snackBar: MatSnackBar) { }

  ngOnInit(): void {
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    const message = 'Hero name: ' + hero.name;
    this.snackBar.open(message, '', { duration: 5000, verticalPosition: 'top', horizontalPosition: 'center' });
  }
}

Aquí he inyectado MatSnackBar en el constructor y he llamado a snackbar.open(..) con el nombre del héroe como mensaje a mostrar cada vez que el usuario selecciona un héroe.

Para que esto funcione, no olvides añadir MatSnackBarModule al bloque imports del AppModule:

import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;
  selectedHero?: Hero;

  constructor(private snackBar: MatSnackBar) { }

  ngOnInit(): void {
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    const message = 'Hero name: ' + hero.name;
    this.snackBar.open(message, '', { duration: 5000, verticalPosition: 'top', horizontalPosition: 'center' });
  }
}

Como puedes observar, he pasado algunos parámetros de configuración al método open(..) del objeto snackbar:

this.snackBar.open(message, '', { duration: 5000, verticalPosition: 'top', horizontalPosition: 'center' });

duración: 5000 significa que quiero que snack bar  espere 5.000 milisegundos antes de ser descartada automáticamente.

verticalPosition: ‘top’ y horizontalPosition: ‘center’ significa que quiero que el  snack bar se muestre en la parte superior central de la pantalla.

Volvamos a ejecutar ng serve y comprobemos en el navegador qué resultado tenemos ahora:

Añadir CSS de Tailwind


El siguiente paso sería añadir tailwind CSS al proyecto siguiendo las instrucciones de tailwind docs: Instalar Tailwind CSS con Angular:

Primero, necesitamos instalar tailwindcss y sus dos dependencias postcss y autoprefixer ejecutando npm install -D tailwindcss postcss autoprefixer. También puedes usar yarn en lugar de npm.
Luego necesitamos generar un archivo tailwind.config.js ejecutando npx tailwindcss init.
Una vez generado el archivo config, necesitamos actualizar su bloque de contenido añadiéndole "./src/**/*.{html,ts}":

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{html,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

También tenemos que añadir tres directivas al archivo /src/styles.scss del proyecto:

@tailwind base;
@tailwind components;
@tailwind utilities;

Y ya estamos listos.

Cambiar la hoja de estilos por defecto de mat-snackbar
Para usar las clases de utilidad de tailwind para dar estilo al snack bar de Angular Material, añadiré a /src/styles.scss las siguientes clases CSS personalizadas:

@tailwind base;
@tailwind components;
@tailwind utilities;

/* ------------------------------------------------------------------------- */
/*  Snackbar
/* ------------------------------------------------------------------------- */

/* Info */
.snackbar-type-fill-info {
  @apply bg-gray-600;
}

/* Success */
.snackbar-type-fill-success {
  @apply bg-green-600;
}

/* Error */
.snackbar-type-fill-error {
  @apply bg-red-600;
}

.snackbar-type-fill-info, .snackbar-type-fill-success, .snackbar-type-fill-error {
  @apply text-white;
}

/* Appearance Outline: */
/* Info */
.snackbar-type-outline-info {
  @apply bg-gray-100 ring-1 ring-gray-400 ring-inset;
  @apply text-gray-700;
}

/* Success */
.snackbar-type-outline-success {
  @apply bg-green-50 ring-1 ring-green-400 ring-inset;
  @apply text-green-700;
}

/* Error */
.snackbar-type-outline-error {
  @apply bg-red-50 ring-1 ring-red-400 ring-inset;
  @apply text-red-700;
}

/* Appearance Soft: */
/* Info */
.snackbar-type-soft-info {
  @apply bg-gray-100;
  @apply text-gray-700;
}

/* Success */
.snackbar-type-soft-success {
  @apply bg-green-50;
  @apply text-green-700;
}

/* Error */
.snackbar-type-soft-error {
  @apply bg-red-50;
  @apply text-red-700;
}

Quiero tener tres tipos de barras de bocadillo: info, éxito y error, y tres apariencias: relleno, contorno y suave.

En la apariencia fill:

  • Estoy usando bg-verde-600 como color de fondo para mostrar los mensajes que tienen éxito como tipo.
  • bg-red-600 se utiliza como color de fondo para mostrar los mensajes que tienen error como tipo.
  • bg-gray-600 se utiliza como color de fondo para mostrar los mensajes que tienen info como tipo.
  • text-white se utiliza como color de fuente.

Volvamos al heroes.component.ts y creemos una variable que contenga los parámetros de configuración. Añadiré el parámetro panelClass a la configuración, que me permite especificar una hoja de estilos personalizada para aplicarla al contenedor del snack bar. Cambiaré la duración de 5.000 a 50.000 milisegundos para poder ajustar el CSS en DevTools en el navegador.

El último paso es optimizar el diseño del código y crear un método más reutilizable para abrir el snack bar:

onSelect(hero: Hero): void {
    this.selectedHero = hero;
    const message = 'Hero name: ' + hero.name;
    this.openSnackBar(message, 5000, 'soft', 'info');
  }

  openSnackBar(message: string,
               duration: number = 5000,
               appearance: 'fill' | 'outline' | 'soft' = 'fill',
               type: 'info' | 'success' | 'error' = 'info'): void {

    const config: MatSnackBarConfig = {
      duration: duration,
      verticalPosition: 'top',
      horizontalPosition: 'center',
      panelClass: [`alert-type-${appearance}-${type}`]
    };
    this.snackBar.open(message, '', config);
  }

He llamado al nuevo método openSnackBar(), y tiene como parámetros: mensaje, duración, apariencia y tipo.

  • appearance podría tener como valor fill, outline, o soft y el valor por defecto es fill.
  • type podría tener como valor info, success, o error y el por defecto es info.

También he sustituido la última línea de onSelect() por this.openSnackBar( message, 5000  soft, info).

Ahora podemos comprobar en el navegador el resultado de los tipos info, success, y error, con las tres apariciones.

Actualización a Angular 15


(Actualización del 23 de diciembre de 2022)

Si pruebas la hoja de estilos anterior en un proyecto que utiliza la versión 15 de Angular y Angular Material, notarás que tu CSS personalizado no funciona. Tu snack bar no cambiará su color de fondo si se trata de un mensaje de éxito, error o información.

La razón de esta regresión es que Angular Material 15 utiliza Componentes basados en Material Design (MDC):

Y tiene un color de fondo que anula el que hemos definido:

Pero que no cunda el pánico. Vamos a arreglarlo.

Todo lo que tienes que hacer es añadir .mat-mdc-snack-bar-container como prefijo a snackbar-type-(appearance)-(messageType) y después añadir .mdc-snackbar__surface.

Si también ha añadido un botón a su snackbar, no olvide cambiar .mat-button o .mat-button-wrapper por .mat-mdc-button.

También debes anular el valor de la propiedad de color para .mat-mdc-snack-bar-container .mdc-snackbar__label utilizando @text-white #{'!important'}:

/* Appearance Fill: */
/* Info */
.mat-mdc-snack-bar-container.snackbar-type-fill-info .mdc-snackbar__surface {
    @apply bg-gray-600 #{'!important'};
}

/* Success */
.mat-mdc-snack-bar-container.snackbar-type-fill-success .mdc-snackbar__surface {
    @apply bg-green-600 #{'!important'};
}

/* Error */
.mat-mdc-snack-bar-container.snackbar-type-fill-error .mdc-snackbar__surface {
    @apply bg-red-600 #{'!important'};
}

.mat-mdc-snack-bar-container.snackbar-type-fill-info .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-fill-success .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-fill-error .mdc-snackbar__surface,
.mat-mdc-snack-bar-container .mdc-snackbar__label {
    @apply text-white #{'!important'};
    .mat-mdc-button {
        @apply text-white #{'!important'};
    }
}

/* Appearance Outline: */
/* Info */
.mat-mdc-snack-bar-container.snackbar-type-outline-info .mdc-snackbar__surface {
    @apply bg-gray-100 ring-1 ring-gray-400 ring-inset #{'!important'};
    @apply text-gray-700 #{'!important'};
}

/* Success */
.mat-mdc-snack-bar-container.snackbar-type-outline-success .mdc-snackbar__surface {
    @apply bg-green-50 ring-1 ring-green-400 ring-inset #{'!important'};
    @apply text-green-700 #{'!important'};
}

/* Error */
.mat-mdc-snack-bar-container.snackbar-type-outline-error .mdc-snackbar__surface {
    @apply bg-red-50 ring-1 ring-red-400 ring-inset #{'!important'};
    @apply text-red-700 #{'!important'};
}

/* Appearance Soft: */
/* Info */
.mat-mdc-snack-bar-container.snackbar-type-soft-info .mdc-snackbar__surface {
    @apply bg-gray-100 #{'!important'};
    @apply text-gray-700 #{'!important'};
}

/* Success */
.mat-mdc-snack-bar-container.snackbar-type-soft-success .mdc-snackbar__surface {
    @apply bg-green-50 #{'!important'};
    @apply text-green-700 #{'!important'};
}

/* Error */
.mat-mdc-snack-bar-container.snackbar-type-soft-error .mdc-snackbar__surface {
    @apply bg-red-50 #{'!important'};
    @apply text-red-700 #{'!important'};
}

/* Common */
.mat-mdc-snack-bar-container.snackbar-type-outline-info .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-outline-success .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-outline-error .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-soft-info .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-soft-success .mdc-snackbar__surface,
.mat-mdc-snack-bar-container.snackbar-type-soft-error .mdc-snackbar__surface {
    .mat-mdc-button {
        @apply text-black #{'!important'};
    }
}

Gracias por llegar hasta el final de este blog quiero recordarte que todo esto es gratis y posible gracias a que tu compartes. Un fuerte abrazo y recuerda que el conocimiento es poder.

Invertir en conocimientos produce siempre los mejores beneficios. (Benjamín Franklin)

Fuente

Plataforma de cursos gratis sobre programación