tdn-tabs
Paquete: @tiendana/ngx-core/ui
Tira horizontal de pestañas operable con teclado, construida sobre los ítems .nav del sistema de diseño. Sirve para cualquier fila de opciones excluyentes que cambian lo que muestra una pantalla: secciones de un formulario, el alcance de una búsqueda, un filtro, los modos de un editor. Para el avance por pasos use tdn-stepper; para navegar entre rutas use enlaces.
Disponible desde @tiendana/ngx-core 0.8.0. El slot trailing, iconPosition y la forma del host llegaron en 0.8.1.
Características
✅ Controlado: no guarda la selección; dibuja activeId, emite selected y el consumidor lo escribe de vuelta
✅ Semántica a elección: radio (por defecto), tab o none
✅ Teclado completo: una sola parada con Tab y flechas, Inicio y Fin, la selección sigue al foco
✅ Desbordamiento: scroll horizontal con puntos de ajuste y flechas que aparecen sobre los extremos
✅ Variantes: compact (sin píldora) y floating (glassmorphism)
✅ Slot trailing para dibujar un marcador propio después de la etiqueta
Todos los colores salen de los estilos .nav de @tiendana/web-foundation, que responden a .scheme-dark. Sin esa hoja de estilos los ítems se ven como botones sin estilo.
Selector
tdn-tabs
API
Inputs
| Input | Tipo | Default | Descripción |
|---|---|---|---|
items | TdnTab[] | requerido | Las pestañas, en el orden en que se dibujan |
activeId | string | '' | id de la pestaña actual. Un id que no coincide con ningún ítem deja la tira sin selección y sin ítems alcanzables con Tab |
semantics | 'tab' | 'radio' | 'none' | 'radio' | Qué es la tira para la tecnología asistiva |
ariaLabel | string | '' | Nombre accesible del grupo, ya traducido. Vacío no escribe aria-label |
compact | boolean | false | Quita la píldora de fondo. Booleano simple: se enlaza [compact]="true" |
floating | boolean | false | Aplica el glassmorphism flotante (.nav--is-floating) a la pista. Booleano simple |
iconPosition | 'before' | 'after' | 'after' | Lado de la etiqueta en el que va el icono del ítem |
trailing | TemplateRef<{ $implicit: TdnTab }> | null | null | Reemplaza lo que se dibuja después de la etiqueta; recibe el ítem como contexto |
Son entradas booleanas simples, sin booleanAttribute: escribir <tdn-tabs compact> no las activa. Enlácelas: [compact]="true".
Outputs
| Output | Tipo | Descripción |
|---|---|---|
selected | OutputEmitterRef<string> | Emite el id de la pestaña elegida, por clic o por flecha. Se emite incluso si ya era la activa |
Interfaz TdnTab
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador estable, único en la tira. Lo emite selected y se compara con activeId |
label | string | Texto visible, ya traducido: la librería nunca traduce |
icon? | string | Ligadura de Material. Se ignora si hay svgIcon |
svgIcon? | string | Símbolo registrado en el MatIconRegistry de la app. Tiene prioridad sobre icon |
title? | string | Tooltip nativo, para etiquetas abreviadas o iconos que cargan el significado |
dot? | boolean | Dibuja el punto de estado del sistema de diseño tras la etiqueta; --dot-color es transparente en reposo, --color-forrest al pasar el cursor y --color-natura cuando el ítem está activo. No se dibuja si hay trailing |
onError? | boolean | Aplica el tratamiento de error (.nav__item--on-error) al icono del ítem |
panelId? | string | id del panel que controla; se escribe como aria-controls. Solo tiene sentido con semantics="tab" |
data? | unknown | Lo que necesite su plantilla trailing; la librería nunca lo lee |
Tipos TdnTab y TdnTabsSemantics
export interface TdnTab {
id: string;
label: string;
icon?: string;
svgIcon?: string;
title?: string;
dot?: boolean;
onError?: boolean;
panelId?: string;
data?: unknown;
}
export type TdnTabsSemantics = 'tab' | 'radio' | 'none';
Miembros públicos
canScrollLeft y canScrollRight (signals que el componente actualiza al medir y que muestran las flechas; no los escriba desde fuera), containerRole e itemRole (computed), y los métodos select(id), onKey(event), onScroll() y scrollBy(-1 | 1). select(id) emite selected como lo hace un clic y no cambia activeId por sí mismo; el resto son manejadores de la plantilla que no están pensados para llamarse desde fuera.
Contenido proyectado
No proyecta contenido. El único punto de extensión es el input trailing, una ng-template cuyo contexto implícito (let-item) es el TdnTab, con su data incluido.
<tdn-tabs [items]="sections()" [activeId]="section()" (selected)="section.set($event)" [trailing]="state" />
<ng-template #state let-item>
@if (item.data?.pending) {
<tdn-busy-loader style="--busy-loader-size: 16px" />
}
</ng-template>
Con trailing presente se reemplazan el icono (cuando iconPosition es 'after') y el punto; un icono con iconPosition="before" se sigue dibujando.
Semántica y accesibilidad
semantics | Contenedor | Ítem | Úselo cuando |
|---|---|---|---|
'radio' (defecto) | radiogroup | radio con aria-checked | Hay una sola zona de resultados que la tira filtra o acota |
'tab' | tablist | tab con aria-selected y aria-controls (desde panelId) | Cada ítem revela su propio panel |
'none' | sin rol | botón sin rol ni estado | Enlaces dentro de la página o scroll-spy |
- Nombre el grupo con
ariaLabelen'radio'y'tab'. Déjelo vacío con'none': ARIA no permite nombre en un elemento sin rol. - Teclado: una sola parada de Tab (tabindex itinerante: solo el ítem activo tiene
0). Flecha derecha o abajo y flecha izquierda o arriba pasan al siguiente o anterior y dan la vuelta; Inicio y Fin van al primero y al último. La selección sigue al foco, de modo que cada flecha emiteselected. - Las flechas de desbordamiento son
aria-hidden, quedan fuera del orden de Tab y no roban el foco al pulsarse, así que un campo de búsqueda junto a la tira conserva el cursor. - El ítem enfocado dibuja un contorno de 2px con
--color-forrest. - Con
semantics="tab"los ítems no llevanid: dé a cadarole="tabpanel"su propioaria-label.
- Un
activeIdque no coincide con ningún ítem deja todos los ítems contabindex="-1": no se llega con Tab y las flechas no hacen nada. Empiece siempre con un id válido. - No escribir
selectedde vuelta enactiveIddeja la tira pegada a la pestaña anterior. - Los degradados de desbordamiento se pintan desde
--bg-1dp; sobre otra superficie se ven como una banda.
Desbordamiento
Cuando los ítems no caben, la pista hace scroll horizontal con puntos de ajuste y aparecen chevrones sobre los extremos; cada pulsación avanza cerca del 60% del ancho visible. Las flechas se miden al hacer scroll, al cambiar items y al redimensionarse la tira. Cuando cambia activeId, la pestaña activa se desliza al centro.
Estilos
- Clases del host:
tdn-tabs--compactcuandocompactestá activo. El host toma el radio de la pista (--border-radius-xl,0en compact), de modo que una sombra o contorno que el consumidor ponga sobretdn-tabssigue la píldora. - Clases internas de web-foundation:
.nav,.nav__item,.nav__item--active,.nav__item--on-error,.nav--is-floating; locales:.nav--compact,.nav-arrow,.nav-arrow--left,.nav-arrow--right,.nav-arrow--hidden. compactusa peso normal y semibold solo en el ítem activo.- El componente no declara propiedades CSS propias; el color y el relleno vienen de los tokens del sistema de diseño (
--bg-1dp,--color-forrest,--color-text-primary,--spacing-*,--font-*).
Ejemplos y Demos
Uso básico
Ver código
Compacto dentro de un diálogo
Ver código
Desbordamiento
Ver código
Alcance de una búsqueda
<tdn-tabs
[items]="scopes()"
[activeId]="scope()"
semantics="radio"
[ariaLabel]="'search_scope' | translate"
(selected)="scope.set($event)" />