@telcomdev/ui
Telcomdev UI
Input outline
<td-input
label="Nombre completo"
icon="person"
[clearable]="true"
[(ngModel)]="nombre"
/>TdInput es compatible con ngModel y formularios reactivos. La etiqueta y
el borde outline permanecen visibles en todos los estados.
Selects
<td-select
label="Estado"
[options]="estados"
[(ngModel)]="estado"
/>
<td-autocomplete-select
label="Ciudad"
[options]="ciudades"
[(ngModel)]="ciudad"
/>Ambos controles usan TdSelectOption<T>, son compatibles con formularios
Angular y muestran su panel mediante Angular CDK Overlay.
El comportamiento predeterminado durante el scroll es reposition, por lo que
el panel permanece unido al control. Puede cambiarse con
scrollBehavior="close".
Tabs
<td-tabs [(selectedIndex)]="indice">
<td-tab label="General" icon="settings">
<app-general />
</td-tab>
<td-tab label="Usuarios" icon="people" [badge]="12">
<app-usuarios />
</td-tab>
</td-tabs>Incluye las variantes linea, pastilla y contenida, además de navegación
con flechas, Home y End.
Para usar componentes basados en Overlay, como TdDialog, agrega el estilo
estructural del CDK en angular.json:
"styles": [
"node_modules/@angular/cdk/overlay-prebuilt.css",
"src/styles.scss"
]Los diálogos pueden abrir cualquier componente standalone. El contenido decide libremente sus acciones:
<td-dialog-actions>
<button type="button" [tdDialogClose]="false">Cancelar</button>
<button type="button" (click)="guardarBorrador()">Borrador</button>
<button type="submit" tdDialogPrimary>Guardar</button>
</td-dialog-actions>Iconos
import { TdIcon, TdIconoRegistry } from '@telcomdev/ui';
@Component({
imports: [TdIcon],
})
export class App {
constructor(registry: TdIconoRegistry) {
registry.registrar({
estrella: 'M12 2 15 9 22 9 17 14 19 22 12 17 5 22 7 14 2 9 9 9Z',
});
}
}<td-icon nombre="security" tamano="24px" />
<td-icon nombre="estrella" titulo="Favorito" />El icono hereda el color CSS mediante currentColor. Si el nombre no existe, se muestra
folder.
Data table
TdDataTable usa @angular/cdk/scrolling para virtualizar las filas. La aplicación
consumidora mantiene la responsabilidad de consultar, paginar y modificar datos.
import {
TdDataTable,
TdDataTableAccion,
TdDataTableAccionEvento,
TdDataTableBoton,
TdDataTableColumna,
} from '@telcomdev/ui';
@Component({
imports: [TdDataTable],
})
export class Establecimientos {
columnas: TdDataTableColumna<Establecimiento>[] = [
{ clave: 'nombre', encabezado: 'Nombre', ancho: '1fr' },
{ clave: 'codigoSunat', encabezado: 'Código Sunat', ancho: '8rem' },
{ clave: 'direccion', encabezado: 'Dirección', ancho: '1.5fr' },
{ clave: 'esPrincipal', encabezado: 'Principal', tipo: 'booleano' },
{ clave: 'activo', encabezado: 'Estado', tipo: 'estado' },
];
acciones: TdDataTableAccion<Establecimiento>[] = [
{ id: 'editar', etiqueta: 'Editar', icono: 'edit' },
{ id: 'eliminar', etiqueta: 'Eliminar', icono: 'delete', tono: 'peligro' },
];
botones: TdDataTableBoton[] = [
{ id: 'agregar', etiqueta: 'Agregar establecimiento', icono: 'add', tono: 'primario' },
{ id: 'pdf', etiqueta: 'Exportar PDF', icono: 'pdf', tono: 'peligro' },
{ id: 'excel', etiqueta: 'Exportar Excel', icono: 'description', tono: 'exito' },
];
ejecutar(evento: TdDataTableAccionEvento<Establecimiento>) {
// evento.accion.id, evento.fila y evento.indice
}
ejecutarBoton(boton: TdDataTableBoton) {
if (boton.id === 'agregar') this.abrirModal(0);
if (boton.id === 'pdf') this.exportarPdf();
}
}<td-data-table
campoId="establecimientoId"
[datos]="lista()"
[columnas]="columnas"
[acciones]="acciones"
[botones]="botones"
[total]="totalEstablecimientos()"
[cargando]="cargando()"
[filasAntesDeCargar]="5"
[puedeCargarMas]="paginaActual() < totalPaginas()"
(busquedaChange)="buscar($event)"
(accion)="ejecutar($event)"
(botonClick)="ejecutarBoton($event)"
(cargarMas)="paginaSiguiente()"
>
<select tdDataTableFilters [(ngModel)]="estado">
<option value="">Todos los estados</option>
<option value="activo">Activos</option>
<option value="inactivo">Inactivos</option>
</select>
<input tdDataTableFilters type="date" [(ngModel)]="fecha" />
<button tdDataTableActions type="button">Acción personalizada</button>
</td-data-table>Incluye búsqueda con debounce, scroll virtual, carga incremental, chips de estado,
acciones condicionales, tema oscuro, estado vacío y skeleton de carga. Los elementos
marcados con tdDataTableFilters se colocan junto al buscador. Los elementos
tdDataTableActions aparecen junto al contador y al botón Agregar.
Header
import { TdHeader } from '@telcomdev/ui';
@Component({
imports: [TdHeader],
})
export class App {
sidebarAbierto = true;
}<td-header
[menuAbierto]="sidebarAbierto"
[oscuro]="oscuro"
usuario="Andrea Torres"
cargo="Administradora"
[notificaciones]="notificaciones"
(menuToggle)="sidebarAbierto = !sidebarAbierto"
(notificacionSeleccionada)="abrirNotificacion($event)"
(verTodasNotificaciones)="verNotificaciones()"
(accionPerfil)="ejecutarAccionPerfil($event)"
>
<button tdHeaderActions type="button">Acción adicional</button>
</td-header>El header incluye dropdowns funcionales de notificaciones y perfil, avatar por iniciales,
modo responsive y tema oscuro. Las acciones tdHeaderActions aparecen a la derecha.
Footer
import { TdFooter } from '@telcomdev/ui';
@Component({
imports: [TdFooter],
})
export class App {}<td-footer
empresa="TelcomDev"
texto="Todos los derechos reservados."
version="1.0.0"
[oscuro]="oscuro"
>
<a tdFooterLinks href="/privacidad">Privacidad</a>
<span tdFooterActions>Angular 21</span>
</td-footer>El footer calcula el año actual por defecto y admite el modo compacto.
Sidebar
TdSidebar es standalone y no depende de Angular Material ni Tailwind.
import { MenuSidebar, TdSidebar } from '@telcomdev/ui';
@Component({
imports: [TdSidebar],
})
export class App {
abierto = true;
oscuro = false;
menu: MenuSidebar[] = [
{
nombre: 'Inicio',
ruta: '/',
icono: 'home',
orden: 1,
subMenus: [],
},
];
}<td-sidebar
titulo="Gestión Corporativa"
subtitulo="Central Platform"
[menu]="menu"
[(abierto)]="abierto"
modoCerrado="oculto"
[oscuro]="oscuro"
[cargando]="false"
error=""
(seleccionar)="onSeleccionar($event)"
/>El botón para abrir o cerrar debe vivir en el header de la aplicación:
<button type="button" (click)="abierto = !abierto">
<td-icon [nombre]="abierto ? 'close' : 'menu'" />
</button>modoCerrado acepta compacto para conservar una barra de iconos u oculto para cerrar
completamente el sidebar.
La aplicación consumidora obtiene el menú desde su propia API. El componente ordena cada
nivel por orden, usa folder cuando no recibe icono y renderiza los submenús
recursivamente.
Iconos incluidos: home, dashboard, folder, settings, people, person,
security, inventory, shopping_cart, receipt, assessment, description y
business.
Signal Forms
TdInput, TdSelect, TdAutocompleteSelect y TdDatePicker implementan
FormValueControl y conservan soporte para ngModel y Reactive Forms.
Cuando existe [formField], el field pasa a ser la fuente principal del valor y
del estado. Los controles reciben automáticamente errors, invalid,
required, readonly, disabled, min, max, minLength, maxLength y
pattern cuando corresponda. Las propiedades tradicionales (value,
valueChange, error, disabled, etc.) continúan disponibles para no exigir
una migración completa.
TdInput acepta fields string, number y sus variantes anulables. Un valor
null se presenta como un input vacío. Con type="number" y Signal Forms, el
control entrega number | null, evitando números almacenados como texto.
La política de vaciado se resuelve automáticamente y puede fijarse cuando sea necesario:
<td-input type="number" [formField]="fields.cantidad" />
<td-input [formField]="fields.correo" emptyValue="null" />emptyValue acepta auto (predeterminado), null y empty-string. En modo
tradicional se conserva el comportamiento histórico: type="number" continúa
emitiendo texto mediante CVA para no romper formularios existentes.
Cuando el control recibe maxlength —de forma directa o mediante la metadata
maxLength de Signal Forms— muestra automáticamente un contador actual/max.
Puede ocultarse sin quitar la validación:
<td-input [maxlength]="100" [(ngModel)]="nombre" />
<td-input [formField]="fields.descripcion" [showCounter]="false" />minlength, min, max y pattern no activan el contador.
Checkbox
TdCheckbox implementa el contrato FormCheckboxControl de Angular y mantiene
compatibilidad con ControlValueAccessor. Su valor siempre es booleano:
<td-checkbox
label="Establecimiento principal"
description="Se utilizará como establecimiento predeterminado."
[formField]="establecimientoForm.esPrincipal"
/>También funciona con formularios tradicionales:
<td-checkbox label="Activo" [(ngModel)]="activo" />
<td-checkbox label="Principal" [formControl]="controlPrincipal" />Con Signal Forms no se necesita una variable intermedia ni eventos manuales. El
modelo debe inicializar el campo como true o false, nunca como null.
import { signal } from '@angular/core';
import { form, FormField } from '@angular/forms/signals';
readonly model = signal({
nombre: '',
periodo: null as TdDatePickerValue,
});
readonly fields = form(this.model);Agrega FormField a los imports del componente. La librería no registra un
segundo selector [formField], evitando conflictos con la directiva oficial y
con la transformación especial del compilador de Angular.
<td-input [formField]="fields.nombre" />
<td-date-picker mode="rango" [formField]="fields.periodo" />El uso clásico sigue siendo válido:
<td-input [(ngModel)]="nombre" />
<td-select [formControl]="form.controls.estado" [options]="estados" />Date Picker
<td-date-picker
label="Fecha de emisión"
min="2026-01-01"
max="2027-12-31"
[(ngModel)]="fecha"
/>
<td-date-picker
label="Periodo"
mode="rango"
[(ngModel)]="rango"
/>Las fechas se almacenan como YYYY-MM-DD. En modo rango el valor tiene la forma
{ inicio: string, fin: string | null }.
Build
ng build telcomdev-ui