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-tailwindY 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 rawLuego instalé Angular Material de la siguiente manera:
ng add @angular/materialHe 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-600como color de fondo para mostrar los mensajes que tienen éxito como tipo. bg-red-600se utiliza como color de fondo para mostrar los mensajes que tienen error como tipo.bg-gray-600se utiliza como color de fondo para mostrar los mensajes que tienen info como tipo.text-whitese 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.
appearancepodría tener como valorfill,outline, osofty el valor por defecto esfill.typepodría tener como valorinfo,success, oerrory el por defecto esinfo.
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)