npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@telcomdev/ui

v0.1.40

Published

Librería UI reutilizable para los sistemas TelcomDev.

Readme

Telcomdev UI

Theming

La librería usa tokens CSS --td-* con fallbacks internos. Si no importas ningún tema, los componentes conservan su apariencia por defecto.

Para usar los tokens oficiales:

@import '@telcomdev/ui/lib/theme/td-theme.css';

Puedes sobrescribirlos desde tu aplicación sin cambiar APIs:

:root {
  --td-font-family: var(--app-font-family, Inter, system-ui, sans-serif);
  --td-color-primary: #3157a4;
  --td-color-primary-hover: #254680;
  --td-color-primary-soft: #eaf0fb;
  --td-color-surface: #ffffff;
  --td-color-surface-soft: #f6f8fb;
  --td-color-surface-muted: #edf1f6;
  --td-color-border: #dde3ee;
  --td-color-border-strong: #c8d1df;
  --td-color-text: #202b3d;
  --td-color-text-muted: #66758b;
  --td-color-danger: #b93a3a;
  --td-color-success: #16784a;
  --td-focus-ring: 0 0 0 3px color-mix(in srgb, var(--td-color-primary) 18%, transparent);
  --td-radius-md: 0.75rem;
  --td-shadow-md: 0 18px 46px rgba(32, 43, 61, 0.14);
}

Si tu aplicación ya usa Angular Material con system tokens, puedes mapearlos automáticamente importando el bridge opcional:

@import '@telcomdev/ui/lib/theme/td-material-theme.css';

Ese archivo conecta --td-* con tokens como --mat-sys-primary, --mat-sys-surface, --mat-sys-on-surface, --mat-sys-outline y --mat-sys-error. No reemplaza TdThemeService; solo permite convivir con un tema externo.

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 son compatibles con formularios Angular y muestran su panel mediante Angular CDK Overlay.

td-autocomplete-select soporta dos formas de trabajo:

Modo estándar

Usa TdSelectOption<T> cuando ya tienes opciones normalizadas:

readonly proveedores = [
  { value: 1, label: 'AMERICA MOVIL PERU S.A.C.', description: '20467534026' },
];

proveedorId: number | null = 1;
<td-autocomplete-select
  label="Proveedor"
  [options]="proveedores"
  [(ngModel)]="proveedorId"
/>

Modo mapeado

Usa valueKey, labelKey y, opcionalmente, descriptionKey, iconKey o disabledKey cuando quieres consumir DTOs reales sin mapearlos manualmente:

readonly proveedores = [
  {
    proveedorId: 1,
    tipoDocumentoIdentidadId: 4,
    numeroDocumento: '20467534026',
    nombre: 'AMERICA MOVIL PERU S.A.C.',
  },
];

proveedorId: number | null = 1;
<td-autocomplete-select
  label="Proveedor"
  [options]="proveedores"
  valueKey="proveedorId"
  labelKey="nombre"
  descriptionKey="numeroDocumento"
  [(ngModel)]="proveedorId"
/>

Si el valor llega antes que las opciones, o las opciones llegan antes que el valor, el texto visible se sincroniza automáticamente. Esta sincronización no emite selectionChange; ese evento se reserva para selección o limpieza realizada por el usuario.

El comportamiento predeterminado durante el scroll es reposition, por lo que el panel permanece unido al control. Puede cambiarse con scrollBehavior="close".

File Upload

Selector de archivos con soporte para formularios Angular, drag and drop, validación de formatos y previsualización de imágenes/PDF mediante el visor de archivos de la librería.

Variante input

<td-file-upload
  label="Logo del proveedor"
  variant="input"
  accept="image/*"
  [maxSizeMb]="2"
  [(ngModel)]="logo"
/>
logo: File | null = null;

Variante dropzone con preview

<td-file-upload
  label="Adjuntos del expediente"
  description="Arrastra contrato, evidencia o documentos."
  accept="image/*,.pdf,.xml,.xlsx,.zip"
  [multiple]="true"
  [maxFiles]="5"
  [maxSizeMb]="8"
  [(ngModel)]="adjuntos"
  (filesChange)="actualizarArchivos($event)"
  (rejected)="mostrarRechazos($event)"
/>
adjuntos: File[] = [];

Cuando multiple está desactivado el valor del formulario es File | null. Cuando multiple está activado el valor es File[].

Badge

Indicadores compactos para estados, conteos y etiquetas de producto.

<td-badge tone="success" icon="check">Activo</td-badge>
<td-badge tone="warning" icon="alert">Pendiente</td-badge>
<td-badge tone="danger" variant="solid">Vencido</td-badge>
<td-badge tone="danger" dot aria-label="Tiene alertas" />

Soporta tone, variant, size, icon, dot y pill.

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" />

Radio Button

TdRadioGroup permite selección única tipada y mantiene compatibilidad con Signal Forms, ngModel y Reactive Forms.

<td-radio-group
  label="Tipo de documento"
  [options]="tiposDocumento"
  [(ngModel)]="tipoDocumento"
/>
readonly tiposDocumento = [
  { value: 'dni', label: 'DNI' },
  { value: 'ruc', label: 'RUC' },
  { value: 'ce', label: 'Carné de extranjería' },
];

tipoDocumento: 'dni' | 'ruc' | 'ce' | null = 'ruc';

También cuenta con variante tipo tarjeta:

<td-radio-group
  label="Canal de notificación"
  variant="card"
  direction="horizontal"
  [options]="canales"
  [formField]="fields.canal"
/>

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