button-icon-cover
Paquete: @tiendana/ngx-core/ui
Componente de animación con canvas para botones con icono. Dibuja un cuadrado redondeado que se expande y se contrae con efecto spring cuando el botón padre recibe hover o está marcado como activo.
Características
✅ Animación con spring physics (masa, rigidez, amortiguamiento)
✅ Renderizado con canvas HTML5 y ajuste al devicePixelRatio
✅ En dispositivos touch permanece siempre expandido
✅ Soporte para botones pequeños (.bttn--small)
✅ Compatible con Material Icons (matIcon) y SVG (svgIcon)
✅ Se repinta al cambiar la clase scheme-* del <body> (por ejemplo .scheme-dark)
✅ Puede ser la raíz de un bloque @if/@else dentro del botón, cediendo su lugar a tdn-busy-loader
API
Inputs
| Input | Tipo | Default | Descripción |
|---|---|---|---|
svgIcon | InputSignal<string> | '' | Nombre de un icono registrado en el sprite de MatIconRegistry |
matIcon | InputSignal<string> | '' | Ligadura de Material, por ejemplo 'check'. Se dibuja antes que svgIcon si se pasan ambos |
active | InputSignal<boolean> | false | Mantiene el cuadrado expandido como si hubiera hover (por ejemplo mientras un menú abierto por el botón sigue visible) |
Signals
| Signal | Tipo | Descripción |
|---|---|---|
isExpanded | Signal<boolean> | true si es touch, hay hover en el botón padre o active está activo |
canvas | Signal<ElementRef<HTMLCanvasElement>> | Referencia al canvas de dibujo (viewChild.required) |
Comportamiento
- El cuadrado crece (32px, 16px en botones pequeños) mientras el botón padre tiene hover o
activeestá activo, y reposa pequeño (10px, 5px en botones pequeños) en cualquier otro caso. En un dispositivo touch siempre está expandido. - El hover se lee de
mouseenterymouseleavedel elemento padre, así que el componente debe ser hijo directo del botón. - La detección del hover se conecta después del primer render. Por eso también funciona como raíz de un bloque de control de flujo dentro del botón.
- Si el componente vuelve a insertarse bajo un puntero en reposo, el estado inicial se toma de
:hover, sin esperar unmouseenter. - Al destruirse, el componente retira sus listeners y cancela la animación en curso: un botón que lo recrea en cada petición no los acumula.
private springParams = { mass: 1, stiffness: 300, damping: 20 };
targetSize = expanded ? (small ? 16 : 32) : (small ? 5 : 10);
.bttn--small se lee una sola vezLa clase .bttn--small del padre se lee una sola vez, después del primer render. Si la añades o la quitas en un botón ya renderizado, el CSS del host cambia, pero el canvas conserva el tamaño y el dibujo iniciales, así que el cuadrado queda descuadrado.
Variables CSS
| Variable | Descripción | Default |
|---|---|---|
--bttn-icon-canvas-color | Relleno del cuadrado. Se lee del estilo computado del componente en el momento de dibujar | --color-dark-forrest de la raíz |
--bttn-icon-color | Color del icono (mat-icon) | var(--color-trust) |
button-icon-cover {
--bttn-icon-canvas-color: var(--color-natura);
}
Tamaños
| Variante | Host | Canvas | Cuadrado expandido | Cuadrado en reposo | Radio | Icono |
|---|---|---|---|---|---|---|
| Normal | 21x32px | 32x32px | 32px | 10px | 10px | 20px |
.bttn--small | 16x16px | 16x16px | 16px | 5px | 5px | 12px |
Uso
<button class="bttn bttn--icon">
<button-icon-cover matIcon="delete" />
</button>
<button class="bttn bttn--icon bttn--small">
<button-icon-cover matIcon="add" />
</button>
Con un menú o estado activo
<button class="bttn" (click)="isOpen.set(!isOpen())">
<button-icon-cover svgIcon="arrow_right" [active]="isOpen()" />
</button>
Cediendo su lugar al loader de ocupado
Mientras corre una petición, el icono se reemplaza por tdn-busy-loader con place="cover", que conserva la huella del cover para que el botón no cambie de tamaño ni mueva su texto.
<button type="button" class="bttn bttn--primary" [attr.aria-busy]="saving()" (click)="save()">
Guardar
@if (saving()) {
<tdn-busy-loader place="cover" ground="natura" />
} @else {
<button-icon-cover matIcon="check" />
}
</button>
Material Icon o SVG
<!-- Material Icon -->
<button-icon-cover matIcon="favorite" />
<!-- SVG registrado en MatIconRegistry -->
<button-icon-cover svgIcon="custom-icon" />
Host Classes
host: {
'[class.expanded]': 'isExpanded()',
'[class.active]': 'active()'
}
Accesibilidad
- El canvas y el icono son decorativos: el nombre accesible del botón debe venir de su texto o de un
aria-label. - No afecta la accesibilidad del botón padre.
- La animación no consulta
prefers-reduced-motion. Si tu producto lo exige, evita el componente o sustitúyelo para esos usuarios.