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 valorfill
,outline
, osoft
y el valor por defecto esfill.
type
podría tener como valorinfo
,success
, oerror
y 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)