logomark
Paquete: @tiendana/ngx-core/logomark
TdnLogomarkComponent es el logomark de Tiendana con inputs de tipo signal. El componente es el custom element tdn-logomark de @tiendana/utils/logomark (misma etiqueta, se registra en el primer uso), así que una página simple, React y Angular dibujan exactamente lo mismo. Las reglas de la marca están en Fundamentos: Logomark.
Características
✅ Componente standalone con ChangeDetectionStrategy.OnPush
✅ Inputs signal: clip, still, loop, colourway, size y label
✅ Output clipEnd al terminar un clip que no se repite
✅ Métodos play() y refresh() para control imperativo
✅ Decorativo por defecto; con label se anuncia como imagen
✅ Respeta prefers-reduced-motion y se pausa fuera de pantalla
Instalación
El componente vive en un entry point propio de @tiendana/ngx-core, disponible desde la versión 0.9.0:
import { TdnLogomarkComponent } from '@tiendana/ngx-core/logomark';
Requiere @tiendana/utils >=0.4.0 como peer dependency, que aporta el elemento, los clips y los tipos (ClipName, StillName, LogomarkColourway).
Selector
<tdn-logomark />
API
Inputs
| Input | Tipo | Default | Descripción |
|---|---|---|---|
clip | InputSignal<ClipName | null> | null | Clip a reproducir, desde su inicio cada vez que cambia. Con null mantiene still |
still | InputSignal<StillName | null> | null | Pose que se mantiene mientras no se reproduce ningún clip; SMILE si es null |
loop | InputSignal<boolean> | false | Reinicia el clip cuando termina |
colourway | InputSignal<LogomarkColourway> | 'primary' | Tonos: 'primary', 'reverse', 'neon' o 'mono' |
size | InputSignal<number | null> | null | Alto de la marca en px. Con null lo decide el CSS (64 px si la página no dimensiona el elemento) |
label | InputSignal<string | null> | null | Se anuncia como imagen. Sin él, el logomark es decorativo y queda oculto a tecnologías de apoyo |
Outputs
| Output | Tipo | Descripción |
|---|---|---|
clipEnd | OutputEmitterRef<ClipName> | Un clip que no se repite llegó a su fin (o habría llegado, con movimiento reducido) |
El output se llama clipEnd y no ended: ended es el evento DOM del propio elemento, y un binding (ended) en la plantilla lo recibiría una segunda vez. Si escuchas el evento DOM directamente, detail.clip lleva el nombre del clip.
Métodos
| Método | Firma | Descripción |
|---|---|---|
play | play(clip: ClipName, options?: { readonly loop?: boolean }): void | Reproduce un clip desde el inicio, incluso el que ya se está reproduciendo (un segundo envío fallido vuelve a reproducir form-error) |
refresh | refresh(): void | Relee las propiedades --logomark-* y repinta, tras un cambio de tema que solo intercambia esas propiedades |
Nombres de clips y stills
Los nombres válidos de clip y still dependen de la versión instalada de @tiendana/utils. Con 0.5.0:
- Clips (30):
hello,blink,double-blink,play,congrat,mini-congrat,away,peek,squint,error,form-error,ah,doze,wake,thinking,draw,sale,conduct,heartbeat,celebrate,wow,doubt,confused,crash, y para estados de páginaloading,updating,offline,empty,no-resultsywarning. - Stills (17):
smile,congrat,play,error,asleep,squint,peek,away,pop,wow,doubt,confused,downcast,draw-25,draw-50,draw-75,draw-100.
form-error es un clip sostenido: termina en ERROR y se queda ahí hasta que se reproduce otro. No se repite.
Los clips desde celebrate y los stills wow, doubt, confused y downcast llegan con 0.5.0: con 0.4.0 instalado no existen. Consulta Clips.
Ejemplos
Marca viva
import { Component } from '@angular/core';
import { TdnLogomarkComponent } from '@tiendana/ngx-core/logomark';
@Component({
selector: 'app-brand',
imports: [TdnLogomarkComponent],
template: `<tdn-logomark clip="hello" [loop]="true" [size]="48" label="Tiendana" />`
})
export class BrandComponent {}
Error de formulario con play('form-error')
ERROR se sacude y se queda hasta que el formulario reproduce otra cosa. Al usar play(), un segundo fallo repite la animación.
import { Component, viewChild } from '@angular/core';
import { TdnLogomarkComponent } from '@tiendana/ngx-core/logomark';
@Component({
selector: 'app-login-form',
imports: [TdnLogomarkComponent],
template: `
<tdn-logomark [size]="40" />
<button type="button" (click)="submit()">Guardar</button>
`
})
export class LoginFormComponent {
private readonly mark = viewChild.required(TdnLogomarkComponent);
async submit(): Promise<void> {
try {
await this.save();
this.mark().play('congrat');
} catch {
this.mark().play('form-error');
}
}
private async save(): Promise<void> {
// llamada al API
}
}
Estado de carga y éxito
clip se reproduce desde su inicio cada vez que cambia, y clipEnd avisa cuando un clip sin loop termina. Aquí thinking se repite mientras se carga y congrat se reproduce una vez al terminar.
import { Component, signal } from '@angular/core';
import { TdnLogomarkComponent } from '@tiendana/ngx-core/logomark';
@Component({
selector: 'app-sync-status',
imports: [TdnLogomarkComponent],
template: `
<tdn-logomark
[clip]="clip()"
[loop]="loading()"
[size]="56"
label="Estado de sincronización"
(clipEnd)="onClipEnd($event)" />
`
})
export class SyncStatusComponent {
readonly loading = signal(true);
readonly clip = signal<'thinking' | 'congrat'>('thinking');
finish(): void {
this.loading.set(false);
this.clip.set('congrat');
}
onClipEnd(clip: string): void {
console.log('terminó', clip);
}
}
Para una pose fija, usa still en lugar de clip:
<tdn-logomark still="error" colourway="reverse" label="Error" />
Colourways en el host de un componente
colourway elige entre los tonos de la marca. Dentro de nuestras apps, el mixin colourway de @tiendana/utils/logomark/colourways fija las propiedades --logomark-* desde los tokens de web-foundation, para que la marca siga al tema de la página:
@use '@tiendana/utils/logomark/colourways' as logomark;
:host {
@include logomark.colourway(primary);
}
:host-context(.scheme-dark) {
@include logomark.colourway(reverse);
}
El segundo parámetro, $track, sustituye el tono de la parte aún no dibujada cuando el fondo no es blanco ni forrest. Los nombres válidos son primary, reverse, neon y mono; cualquier otro lanza un error de Sass.
Si el tema cambia solo intercambiando propiedades CSS, llama a refresh() en el componente para que repinte con los nuevos tonos. Consulta Colourways.
Accesibilidad
- Sin
label, el componente es decorativo (aria-hidden). - Con
label, se anuncia como imagen (role="img") con ese texto. - Con
prefers-reduced-motion: reduceno se reproduce ningún clip; se mantiene la pose que representa yclipEndse emite de inmediato. - Se pausa fuera de pantalla y en pestañas ocultas.