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

ten-minds-ui-kit

v0.0.2

Published

Librería de componentes UI para proyectos Angular de Ten Minds

Readme

ten-minds-ui-kit

Librería de componentes UI para Angular, construida con Tailwind CSS v4 y Lucide Icons.


Índice


1. Instalación

1.1 Instalar la librería

npm install ten-minds-ui-kit

1.2 Instalar dependencia de íconos

npm install lucide-angular

1.3 Agregar los estilos

Importá los estilos en el styles.css de tu proyecto:

@import 'ten-minds-ui-kit/styles/styles.css';

1.4 Importar componentes

Los componentes son standalone — importá solo los que necesités directamente en tu componente:

import { TmButton, TmChip, TmCheckbox } from 'ten-minds-ui-kit';

@Component({
  imports: [TmButton, TmChip, TmCheckbox],
})

1.5 Usar íconos

Los íconos se importan directamente desde lucide-angular como objetos. No se requiere configuración global en app.config.ts.

import { Download, Trash2, Check } from 'lucide-angular';

export class MiComponente {
  download = Download;
  trash = Trash2;
  check = Check;
}
<tm-button [iconLeft]="download">Exportar</tm-button> <tm-chip [iconLeft]="check">Activo</tm-chip>

1.6 Dark Mode

La librería soporta dark mode mediante la clase .dark en el elemento raíz:

onThemeChange(mode: 'light' | 'dark') {
  if (mode === 'dark') {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

2. Personalización de estilos

La librería ofrece tres niveles de personalización según el alcance que necesités.

| Método | Alcance | Tailwind | Cuándo usarlo | | -------------------------- | ------------- | -------- | ----------------------------------------- | | Token CSS en :root | Toda la app | ✗ | Cambiar la paleta de colores del proyecto | | CSS scoped con clase padre | Una sección | ✗ | Variante especial en una página o sección | | customClass input | Un componente | ✓ | Ajuste puntual en un solo componente |


Nivel 1 — Global: tokens CSS

Afecta todos los componentes que usen ese token en toda la app. Agregá esto en el styles.css de tu proyecto:

:root {
  --color-tm-brand: #your-brand-color;
  --color-tm-primary: #your-text-color;
}

Tokens disponibles

| Token | Descripción | Default (light) | | -------------------------------- | --------------------------- | --------------- | | --color-tm-brand | Color principal de la marca | #141414 | | --color-tm-primary | Texto principal | #141414 | | --color-tm-secondary | Texto secundario | #737373 | | --color-tm-tertiary | Texto terciario | #A0A0A0 | | --color-tm-inverse | Texto sobre fondos oscuros | #FFFFFF | | --color-tm-canvas | Fondo general de la app | #FAFAFA | | --color-tm-surface | Fondo de tarjetas y paneles | #FFFFFF | | --color-tm-subtle | Fondo sutil | #F0F0F0 | | --color-tm-muted | Fondo atenuado | #E4E4E4 | | --color-tm-brand-hover-default | Hover del botón brand | #363636 | | --color-tm-brand-hover-subtle | Hover sutil del botón brand | #F0F0F0 | | --color-tm-default | Borde por defecto | #E4E4E4 | | --color-tm-strong | Borde fuerte | #C8C8C8 |


Nivel 2 — Scoped: una sección

Afecta solo los componentes dentro de un contenedor específico. Se usa CSS puro — no Tailwind.

/* styles.css del proyecto consumidor */
.mi-seccion tm-button button {
  background-color: #your-color !important;
  font-size: 1.25rem !important;
  border-radius: 999px !important;
}
<div class="mi-seccion">
  <tm-button>Solo este botón cambia</tm-button>
</div>

<tm-button>Este queda igual</tm-button>

⚠️ CSS puro únicamente. Tailwind no funciona aquí porque se compila en build time y las clases de la librería no están disponibles en el proyecto consumidor.


Nivel 3 — Puntual: customClass

Afecta únicamente el componente donde lo aplicás. Aquí sí podés usar clases de Tailwind porque se compilan en el proyecto consumidor.

<!-- Tailwind funciona aquí -->
<tm-button customClass="!bg-orange-500 !text-black">Botón personalizado</tm-button>

<!-- CSS también funciona con clase propia -->
<tm-button customClass="mi-boton-especial">Botón personalizado</tm-button>
/* styles.css */
.mi-boton-especial {
  background-color: orange !important;
  font-size: 1.25rem !important;
}

ℹ️ El modificador ! de Tailwind es necesario para sobreescribir estilos ya definidos por el componente.


3. Componentes

| Componente | Selector | Export | Descripción | | --------------- | ------------------ | ---------------- | --------------------------------------- | | TmAvatar | tm-avatar | TmAvatar | Avatar con iniciales y estado | | TmButton | tm-button | TmButton | Botón principal con múltiples variantes | | TmButtonIcon | tm-button-icon | TmButtonIcon | Botón cuadrado solo con ícono | | TmButtonTheme | tm-button-theme | TmButtonTheme | Botón para alternar dark/light mode | | TmCartButton | tm-cart-button | TmCartButton | Botón de carrito con badge numérico | | TmCheckbox | tm-checkbox | TmCheckbox | Checkbox personalizado | | TmChip | tm-chip | TmChip | Etiqueta de estado o categoría | | TmCloseDialog | tm-close-dialog | TmCloseDialog | Botón de cierre para modales y paneles | | TmInput | tm-input | TmInput | Campo de texto | | TmMenu | tm-menu | TmMenu | Menú desplegable con trigger flexible | | TmRadio | tm-radio | TmRadio | Botón de selección única | | TmSelect | tm-select | TmSelect | Combobox con búsqueda y grupos | | TmSnackbar | tm-snackbar-host | TmSnackbarHost | Contenedor de notificaciones globales | | TmToggle | tm-toggle | TmToggle | Switch on/off |


TmAvatar

Componente TmAvatar

Componente de avatar de la librería ten-minds-ui-kit. Soporta imagen, iniciales como fallback, múltiples tamaños e indicador de estado.


Importación

import { TmAvatar } from 'ten-minds-ui-kit';

@Component({
  imports: [TmAvatar],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ---------- | ---------------------- | ---------- | ---------------------------------------------------------- | | src | string | '' | URL de la imagen. Si está vacío, se muestran las iniciales | | initials | string | '' | Texto de iniciales, se muestra cuando no hay imagen | | alt | string | 'Avatar' | Texto alternativo para accesibilidad | | size | AvatarSize | 'md' | Tamaño del avatar | | status | AvatarStatus \| null | null | Indicador de estado visible sobre el avatar |

Tipos

type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
type AvatarStatus = 'online' | 'busy' | 'disconnect';

Tamaños de referencia

| Valor | Tamaño | | ----- | ------ | | xs | 28px | | sm | 40px | | md | 48px | | lg | 60px | | xl | 80px |


Ejemplos

Con imagen

<tm-avatar src="https://ejemplo.com/foto.jpg" />
<tm-avatar src="https://ejemplo.com/foto.jpg" alt="Foto de Juan" />

Con iniciales (fallback sin imagen)

<tm-avatar initials="JP" /> <tm-avatar initials="AB" />

Si src está vacío e initials tiene valor, se muestran las iniciales sobre el fondo de marca.

Tamaños

<tm-avatar src="..." size="xs" />
<tm-avatar src="..." size="sm" />
<tm-avatar src="..." size="md" />
<tm-avatar src="..." size="lg" />
<tm-avatar src="..." size="xl" />

Indicador de estado

<tm-avatar src="..." status="online" />
<tm-avatar src="..." status="busy" />
<tm-avatar src="..." status="disconnect" />

Sin estado (por defecto)

<tm-avatar src="..." />
<!-- status es null por defecto, no se muestra el indicador -->

Combinaciones comunes

<!-- Avatar con iniciales y estado online -->
<tm-avatar initials="MG" status="online" />

<!-- Avatar grande con imagen y estado ocupado -->
<tm-avatar src="https://ejemplo.com/foto.jpg" alt="María García" size="lg" status="busy" />

<!-- Avatar pequeño desconectado -->
<tm-avatar src="https://ejemplo.com/foto.jpg" size="sm" status="disconnect" />

<!-- Avatar extra grande solo con iniciales -->
<tm-avatar initials="TM" size="xl" />

Notas

  • Si src tiene valor, se muestra la imagen. Si no, se recurre a las initials. Si tampoco hay iniciales, el avatar queda vacío con el fondo de marca.
  • El indicador de status escala automáticamente según el size del avatar y se posiciona en la esquina inferior derecha.
  • El indicador incluye role="status" y aria-label con el valor del estado para accesibilidad.

TmButton

Componente TmButton

Componente de botón principal de la librería ten-minds-ui-kit. Soporta múltiples colores, variantes, tamaños, bordes redondeados, íconos, estado activo para navegación y personalización mediante clases CSS.


Importación

import { TmButton } from 'ten-minds-ui-kit';

@Component({
  imports: [TmButton],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ------------- | ---------------- | ----------- | ---------------------------------------------------------------------------------------- | | color | ButtonColor | 'primary' | Color del botón | | size | ButtonSize | 'md' | Tamaño del botón | | type | ButtonType | 'filled' | Variante visual | | rounded | ButtonRounded | 'default' | Forma del borde | | disabled | boolean | false | Desactiva el botón | | active | boolean | false | Aplica fondo activo, útil para navegación en sidebar | | iconLeft | LucideIconData | — | Ícono a la izquierda del texto (opcional) | | iconRight | LucideIconData | — | Ícono a la derecha del texto (opcional) | | iconSize | number | auto | Sobreescribe el tamaño del ícono. Si no se pasa, se calcula automáticamente según size | | customClass | string | '' | Clases CSS adicionales al botón |

Outputs

| Evento | Tipo | Descripción | | ----------- | ------------ | --------------------------------------------------- | | (clicked) | MouseEvent | Se emite al hacer click (no emite si está disabled) |

Tipos

type ButtonColor = 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
type ButtonSize = 'lg' | 'md' | 'sm';
type ButtonType = 'filled' | 'outlined' | 'ghost';
type ButtonRounded = 'default' | 'full';

Uso básico

<tm-button>Guardar</tm-button>

Colores

<tm-button color="primary">Primary</tm-button>
<tm-button color="secondary">Secondary</tm-button>
<tm-button color="danger">Danger</tm-button>
<tm-button color="success">Success</tm-button>
<tm-button color="warning">Warning</tm-button>

Variantes

<tm-button type="filled">Filled</tm-button>
<tm-button type="outlined">Outlined</tm-button>
<tm-button type="ghost">Ghost</tm-button>

Tamaños

<tm-button size="lg">Large</tm-button>
<tm-button size="md">Medium</tm-button>
<tm-button size="sm">Small</tm-button>

Bordes redondeados

<tm-button rounded="default">Default</tm-button> <tm-button rounded="full">Pill</tm-button>

Disabled

<tm-button [disabled]="true">No disponible</tm-button>
<tm-button color="danger" type="outlined" [disabled]="true">Eliminar</tm-button>

Íconos

Los íconos se importan desde lucide-angular como objetos y se asignan a propiedades del componente:

import { Download, Trash2, Check } from 'lucide-angular';

export class MiComponente {
  download = Download;
  trash = Trash2;
  check = Check;
}
<tm-button [iconLeft]="download">Exportar</tm-button>
<tm-button color="success" [iconRight]="check">Confirmar</tm-button>
<tm-button color="danger" type="outlined" [iconLeft]="trash" [iconRight]="check"
  >Eliminar</tm-button
>

Los íconos deben importarse desde lucide-angular como objetos. Pasar strings directamente no funciona.


Tamaño de ícono

El tamaño del ícono se calcula automáticamente según el size del botón:

| size | Tamaño ícono | | ------ | ------------ | | lg | 20px | | md | 20px | | sm | 16px |

Si necesitás sobreescribirlo en un caso específico, usá iconSize:

<!-- Tamaño automático según size -->
<tm-button size="sm" [iconLeft]="trashIcon">Eliminar</tm-button>

<!-- Tamaño forzado -->
<tm-button size="md" [iconLeft]="trashIcon" [iconSize]="12">Eliminar</tm-button>

Estado activo para navegación

El input active aplica un fondo sutil (bg-tm-subtle) al botón, ideal para marcar la página actual en un sidebar. Se usa principalmente con type="ghost":

paginaActiva = 'plugins';
<tm-button
  type="ghost"
  color="primary"
  [iconLeft]="userIcon"
  customClass="w-full justify-start"
  [active]="paginaActiva === 'perfil'"
  (clicked)="paginaActiva = 'perfil'"
>
  Mi perfil
</tm-button>

<tm-button
  type="ghost"
  color="primary"
  [iconLeft]="pluginIcon"
  customClass="w-full justify-start"
  [active]="paginaActiva === 'plugins'"
  (clicked)="paginaActiva = 'plugins'"
>
  My Plugins
</tm-button>

<tm-button
  type="ghost"
  color="danger"
  [iconLeft]="logoutIcon"
  customClass="w-full justify-start"
  (clicked)="logout()"
>
  Log out
</tm-button>

active es opcional y no tiene efecto visual significativo en variantes filled u outlined. Está diseñado para usarse con type="ghost".


Personalización con customClass

El input customClass permite agregar clases Tailwind o CSS propias directamente al botón, sin afectar los estilos base:

<tm-button customClass="w-full justify-start">Ancho completo</tm-button>
<tm-button customClass="!bg-orange-500 !text-black">Botón naranja</tm-button>
<tm-button customClass="mi-boton-especial">Botón personalizado</tm-button>
/* styles.css del proyecto consumidor */
.mi-boton-especial {
  background-color: purple !important;
  border-radius: 4px !important;
}

Usá el modificador ! de Tailwind para sobreescribir estilos que ya vienen definidos en el componente.


Manejo de eventos

<tm-button color="success" (clicked)="onGuardar($event)">Guardar</tm-button>
onGuardar(event: MouseEvent) {
  console.log('Guardando...', event);
}

Casos de uso reales

Botón de acción principal

<tm-button color="primary" size="md" [iconLeft]="download">Exportar</tm-button>

Botón destructivo

<tm-button color="danger" type="outlined" size="md" [iconLeft]="trash">Eliminar</tm-button>

Botón pill

<tm-button color="primary" rounded="full" [iconLeft]="check">Confirmar</tm-button>

Sidebar de navegación completo

paginaActiva = 'plugins';

pages = [
  { id: 'perfil', label: 'Mi perfil', icon: UserIcon },
  { id: 'password', label: 'Password', icon: ShieldIcon },
  { id: 'plugins', label: 'My Plugins', icon: PluginIcon },
  { id: 'billing', label: 'Billing', icon: CardIcon },
];
@for (page of pages; track page.id) {
<tm-button
  type="ghost"
  color="primary"
  [iconLeft]="page.icon"
  customClass="w-full justify-start"
  [active]="paginaActiva === page.id"
  (clicked)="paginaActiva = page.id"
>
  {{ page.label }}
</tm-button>
}

<tm-button
  type="ghost"
  color="danger"
  [iconLeft]="logoutIcon"
  customClass="w-full justify-start"
  (clicked)="logout()"
>
  Log out
</tm-button>

Con Angular Router

import { Router, NavigationEnd } from '@angular/router';

paginaActiva = signal('');

constructor(private router: Router) {
  router.events.subscribe(e => {
    if (e instanceof NavigationEnd) {
      this.paginaActiva.set(e.urlAfterRedirects);
    }
  });
}
<tm-button
  type="ghost"
  color="primary"
  [iconLeft]="pluginIcon"
  customClass="w-full justify-start"
  [active]="paginaActiva() === '/plugins'"
  (clicked)="router.navigate(['/plugins'])"
>
  My Plugins
</tm-button>

Notas importantes

  • El evento (clicked) no se emite cuando disabled es true, no es necesario manejarlo manualmente.
  • Los íconos deben importarse desde lucide-angular como objetos y asignarse a propiedades del componente.
  • customClass se aplica al final de la cadena de clases; usá ! de Tailwind para sobreescribir estilos base.
  • active está diseñado para type="ghost". En filled u outlined el fondo activo puede no verse bien visualmente.
  • iconSize es opcional. Solo usarlo cuando el tamaño automático no se adapte al caso específico.

TmButtonIcon

Componente TmButtonIcon

Componente de botón de ícono de la librería ten-minds-ui-kit. Versión cuadrada del botón principal, diseñado para acciones que se representan únicamente con un ícono. Soporta los mismos colores, variantes, tamaños y bordes redondeados que TmButtonPrimary.


Importación

import { TmButtonIcon } from 'ten-minds-ui-kit';

@Component({
  imports: [TmButtonIcon],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ------------------ | ---------------- | ----------- | --------------------------------------------------------------------------------- | | icon | LucideIconData | — | Requerido. Ícono a mostrar dentro del botón | | color | ButtonColor | 'primary' | Color del botón | | size | ButtonSize | 'md' | Tamaño del botón | | type | ButtonType | 'filled' | Variante visual | | rounded | ButtonRounded | 'default' | Forma del borde | | active | boolean | false | Aplica estado activo (brightness) | | disabled | boolean | false | Desactiva el botón | | ariaLabel | string | '' | Texto accesible del botón. Recomendado siempre que no haya texto visible | | iconCustomSize | number | — | Tamaño del ícono en px. Si no se pasa, se usa el tamaño correspondiente al size | | iconCustomStroke | number | — | Grosor del trazo del ícono. Si no se pasa, se usa 1.75 por defecto |

Outputs

| Evento | Tipo | Descripción | | --------- | ------------ | --------------------------------------------------- | | clicked | MouseEvent | Se emite al hacer click (no emite si está disabled) |

Tipos

type ButtonColor = 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
type ButtonSize = 'lg' | 'md' | 'sm';
type ButtonType = 'filled' | 'outlined' | 'ghost';
type ButtonRounded = 'default' | 'full';

Tamaños de referencia

| Valor | Tamaño | Ícono por defecto | | ----- | ------ | ----------------- | | lg | 44px | 24px | | md | 40px | 20px | | sm | 36px | 16px |

Estos valores se sobreescriben con iconCustomSize si se proporciona.


Ejemplos

Uso básico

El prop icon es requerido. Siempre importa el ícono desde lucide-angular como objeto.

import { Trash2, Edit, Plus } from 'lucide-angular';

export class App {
  trash = Trash2;
  edit = Edit;
  plus = Plus;
}
<tm-button-icon [icon]="edit" ariaLabel="Editar" />

Colores

<tm-button-icon [icon]="edit" color="primary" ariaLabel="Editar" />
<tm-button-icon [icon]="edit" color="secondary" ariaLabel="Secundario" />
<tm-button-icon [icon]="trash" color="danger" ariaLabel="Eliminar" />
<tm-button-icon [icon]="plus" color="success" ariaLabel="Agregar" />
<tm-button-icon [icon]="edit" color="warning" ariaLabel="Advertencia" />

Variantes

<tm-button-icon [icon]="edit" type="filled" ariaLabel="Editar" />
<tm-button-icon [icon]="edit" type="outlined" ariaLabel="Editar" />
<tm-button-icon [icon]="edit" type="ghost" ariaLabel="Editar" />

Tamaños

<tm-button-icon [icon]="edit" size="lg" ariaLabel="Editar" />
<tm-button-icon [icon]="edit" size="md" ariaLabel="Editar" />
<tm-button-icon [icon]="edit" size="sm" ariaLabel="Editar" />

Tamaño de ícono personalizado

Usa iconCustomSize para sobreescribir el tamaño del ícono independientemente del size del botón.

<!-- Ícono más grande que el default para md -->
<tm-button-icon [icon]="edit" size="md" [iconCustomSize]="24" ariaLabel="Editar" />

<!-- Ícono más pequeño que el default para lg -->
<tm-button-icon [icon]="plus" size="lg" [iconCustomSize]="14" ariaLabel="Agregar" />

Grosor de trazo personalizado

Usa iconCustomStroke para ajustar el grosor del trazo del ícono. El valor por defecto es 1.75.

<!-- Trazo más grueso -->
<tm-button-icon [icon]="edit" [iconCustomStroke]="2.5" ariaLabel="Editar" />

<!-- Trazo más fino -->
<tm-button-icon [icon]="edit" [iconCustomStroke]="1" ariaLabel="Editar" />

Combinando tamaño y grosor custom

<tm-button-icon
  [icon]="edit"
  size="md"
  [iconCustomSize]="24"
  [iconCustomStroke]="2"
  ariaLabel="Editar"
/>

Bordes redondeados

<tm-button-icon [icon]="plus" rounded="default" ariaLabel="Agregar" />
<tm-button-icon [icon]="plus" rounded="full" ariaLabel="Agregar" />

Disabled

<tm-button-icon [icon]="trash" [disabled]="true" ariaLabel="Eliminar" />
<tm-button-icon
  [icon]="trash"
  color="danger"
  type="outlined"
  [disabled]="true"
  ariaLabel="Eliminar"
/>

Manejo de eventos

<tm-button-icon [icon]="trash" color="danger" ariaLabel="Eliminar" (clicked)="onEliminar($event)" />
onEliminar(event: MouseEvent) {
  console.log('Eliminando...', event);
}

Combinaciones comunes

<!-- Botón de acción flotante (FAB) -->
<tm-button-icon
  [icon]="plus"
  color="primary"
  size="lg"
  rounded="full"
  ariaLabel="Crear nuevo"
  (clicked)="onCreate($event)"
/>

<!-- Botón destructivo ghost -->
<tm-button-icon
  [icon]="trash"
  color="danger"
  type="ghost"
  size="sm"
  ariaLabel="Eliminar fila"
  (clicked)="onDelete($event)"
/>

<!-- Botón de edición outlined -->
<tm-button-icon
  [icon]="edit"
  color="primary"
  type="outlined"
  ariaLabel="Editar registro"
  (clicked)="onEdit($event)"
/>

<!-- Ícono con tamaño y grosor personalizados -->
<tm-button-icon
  [icon]="edit"
  color="primary"
  type="ghost"
  size="md"
  [iconCustomSize]="22"
  [iconCustomStroke]="2"
  ariaLabel="Editar"
  (clicked)="onEdit($event)"
/>

Notas

  • El prop icon es requerido. El componente no renderiza nada sin él.
  • El evento clicked no se emite cuando disabled es true, no es necesario manejarlo manualmente.
  • Se recomienda proporcionar siempre un ariaLabel descriptivo ya que el botón no contiene texto visible, lo cual es importante para accesibilidad.
  • Los íconos deben importarse desde lucide-angular como objetos y asignarse a propiedades del componente. Pasar strings directamente no funciona.
  • El tamaño del ícono se ajusta automáticamente según el size del botón. Usa iconCustomSize solo cuando necesites un tamaño específico distinto al default.
  • iconCustomStroke sobreescribe el grosor 1.75 que se aplica por defecto a todos los íconos.

TmButtonTheme

Componente TmButtonTheme

Componente de botón para alternar entre modo claro y oscuro. Cambia el icono según el modo actual y emite el nuevo modo al padre.


Instalación

import { TmButtonTheme } from 'ten-minds-ui-kit';

@Component({
  imports: [TmButtonTheme],
})

Uso básico

<tm-button-theme [mode]="currentTheme" (modeChange)="onThemeChange($event)" />
import { ThemeMode } from 'ten-minds-ui-kit';

currentTheme: ThemeMode = 'light';

onThemeChange(mode: ThemeMode) {
  this.currentTheme = mode;
  if (mode === 'dark') {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

Inputs y Outputs

| Input | Tipo | Default | Descripción | | ---------- | ----------- | --------- | ------------------------ | | mode | ThemeMode | 'light' | Modo actual del tema | | disabled | boolean | false | Desactiva la interacción |

| Output | Tipo | Descripción | | ------------ | ----------- | ---------------------------------- | | modeChange | ThemeMode | Emite el nuevo modo al hacer click |


Tipos

type ThemeMode = 'light' | 'dark';

Estados

Light (modo claro)

<tm-button-theme mode="light" (modeChange)="onThemeChange($event)" />

Dark (modo oscuro)

<tm-button-theme mode="dark" (modeChange)="onThemeChange($event)" />

Disabled

<tm-button-theme [disabled]="true" />

Casos de uso reales

Toggle global en el navbar

<nav class="flex items-center justify-between px-6 py-4">
  <span>Mi App</span>
  <tm-button-theme [mode]="currentTheme" (modeChange)="onThemeChange($event)" />
</nav>
currentTheme: ThemeMode = 'light';

onThemeChange(mode: ThemeMode) {
  this.currentTheme = mode;
  if (mode === 'dark') {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

Persistir en localStorage

currentTheme: ThemeMode = 'light';

ngOnInit() {
  const saved = localStorage.getItem('theme') as ThemeMode;
  if (saved) {
    this.currentTheme = saved;
    document.documentElement.classList.toggle('dark', saved === 'dark');
  }
}

onThemeChange(mode: ThemeMode) {
  this.currentTheme = mode;
  localStorage.setItem('theme', mode);
  document.documentElement.classList.toggle('dark', mode === 'dark');
}

TmCartButton

Componente TmCartButton

Componente de botón de carrito de compras de la librería ten-minds-ui-kit. Muestra un ícono de carrito con un badge numérico opcional según la cantidad de ítems.


Importación

import { TmCartButton } from 'ten-minds-ui-kit';

@Component({
  imports: [TmCartButton],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ----------- | --------- | ---------------------- | -------------------------------------------------------------------------------------- | | count | number | 0 | Cantidad de ítems en el carrito. Si es 0, no se muestra el badge | | disabled | boolean | false | Desactiva el botón | | ariaLabel | string | 'Carrito de compras' | Texto accesible del botón. Se complementa automáticamente con la cantidad si hay ítems |

Outputs

| Evento | Tipo | Descripción | | --------- | ------ | --------------------------------------------------- | | clicked | void | Se emite al hacer click (no emite si está disabled) |


Ejemplos

Uso básico

<tm-cart-button />

Con ítems en el carrito

<tm-cart-button [count]="3" /> <tm-cart-button [count]="9" />

Cuando count supera 9, el badge muestra 9+ en lugar del número exacto.

<tm-cart-button [count]="15" />
<!-- Badge muestra: 9+ -->

Deshabilitado

<tm-cart-button [disabled]="true" /> <tm-cart-button [count]="4" [disabled]="true" />

Manejo de eventos

<tm-cart-button [count]="itemCount" (clicked)="onCartClick()" />
itemCount = 2;

onCartClick() {
  console.log('Abriendo carrito...');
}

Con aria-label personalizado

<tm-cart-button ariaLabel="Ver mi pedido" [count]="2" />
<!-- aria-label resultante: "Ver mi pedido, 2 items" -->

Combinaciones comunes

<!-- Carrito vacío -->
<tm-cart-button (clicked)="abrirCarrito()" />

<!-- Carrito con items -->
<tm-cart-button [count]="totalItems" (clicked)="abrirCarrito()" />

<!-- Carrito deshabilitado durante carga -->
<tm-cart-button [count]="totalItems" [disabled]="cargando" (clicked)="abrirCarrito()" />

Notas

  • El badge solo es visible cuando count es mayor a 0.
  • Cuando count supera 9, el badge muestra 9+ para evitar desbordamiento visual.
  • El evento clicked no se emite cuando disabled es true, no es necesario manejarlo manualmente.
  • El aria-label se construye dinámicamente: si hay ítems, añade la cantidad al texto base (ej. "Carrito de compras, 3 items"). El badge tiene aria-hidden="true" para evitar duplicados en lectores de pantalla.
  • El ícono del carrito es fijo (ShoppingCart de lucide-angular) y no es configurable.

📄 Documentación detallada: tm-cart-button.md


TmCheckbox

TmCheckbox

Componente de selección para formularios. Permite marcar o desmarcar una opción individual. Semánticamente correcto y accesible.


Importación

import { TmCheckbox } from 'ten-minds-ui-kit';

@Component({
  imports: [TmCheckbox],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ---------- | --------- | ------------- | ------------------------------------------------- | | checked | boolean | false | Estado actual del checkbox | | disabled | boolean | false | Desactiva la interacción | | label | string | — | Texto descriptivo al lado del checkbox (opcional) | | id | string | auto-generado | ID del input, útil para asociar labels externos |

Outputs

| Output | Tipo | Descripción | | ----------- | --------- | ------------------------------------ | | (changed) | boolean | Emite el nuevo estado al hacer click |


Uso básico

<tm-checkbox [checked]="isChecked" (changed)="isChecked = $event" />

Estados

Default (desmarcado)

<tm-checkbox [checked]="false" (changed)="isChecked = $event" />

Checked (marcado)

<tm-checkbox [checked]="true" (changed)="isChecked = $event" />

Disabled desmarcado

<tm-checkbox [checked]="false" [disabled]="true" />

Disabled marcado

<tm-checkbox [checked]="true" [disabled]="true" />

Con label

<tm-checkbox
  [checked]="isChecked"
  label="Aceptar términos y condiciones"
  (changed)="isChecked = $event"
/>

Manejo del estado

El checkbox no guarda estado internamente. El padre es responsable de actualizar el valor:

isChecked = false;

onChanged(value: boolean) {
  this.isChecked = value;
}
<tm-checkbox [checked]="isChecked" (changed)="onChanged($event)" />

O de forma inline:

<tm-checkbox [checked]="isChecked" (changed)="isChecked = $event" />

Casos de uso reales

Aceptar términos

<tm-checkbox
  [checked]="aceptaTerminos"
  label="Acepto los términos y condiciones"
  (changed)="aceptaTerminos = $event"
/>
<btn-primary [disabled]="!aceptaTerminos" type="filled" (clicked)="continuar()">
  Continuar
</btn-primary>

Lista de permisos

@for(permiso of permisos; track permiso.id) {
<tm-checkbox
  [checked]="permiso.activo"
  [label]="permiso.nombre"
  (changed)="togglePermiso(permiso.id, $event)"
/>
}
togglePermiso(id: string, value: boolean) {
  const permiso = this.permisos.find(p => p.id === id);
  if (permiso) permiso.activo = value;
}

Con disabled según permisos

<tm-checkbox
  [checked]="opcion.activa"
  [disabled]="!tienePermisos"
  [label]="opcion.nombre"
  (changed)="opcion.activa = $event"
/>

TmChip

Componente TmChip

Componente para mostrar estados, etiquetas o valores visuales. Puede ser estático o clickeable.


Importación

import { TmChip } from 'ten-minds-ui-kit';

@Component({
  imports: [TmChip],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ----------- | ---------------- | ----------- | ----------------------------------------------------- | | status | ChipStatus | 'brand' | Color del chip | | type | ChipType | 'filled' | Estilo visual del chip | | rounded | ChipRounded | 'default' | Forma del borde | | iconLeft | LucideIconData | — | Ícono a la izquierda | | iconRight | LucideIconData | — | Ícono a la derecha | | clickable | boolean | false | Habilita cursor pointer y emite evento al hacer click |

Outputs

| Output | Tipo | Descripción | | ----------- | ------ | ------------------------------------------------ | | (clicked) | void | Se emite al hacer click si clickable es true |

Tipos

type ChipStatus = 'success' | 'warning' | 'indigo' | 'danger' | 'info' | 'brand';
type ChipType = 'filled' | 'subtle' | 'outlined';
type ChipRounded = 'default' | 'full';

Status (colores)

| Status | Uso recomendado | | --------- | --------------------------------- | | success | Activo, completado, aprobado | | warning | Pendiente, por vencer, precaución | | danger | Inactivo, error, rechazado | | info | Informativo, en proceso | | indigo | Categorías, etiquetas neutras | | brand | Destacado, principal |

<tm-chip status="success">Activo</tm-chip>
<tm-chip status="warning">Pendiente</tm-chip>
<tm-chip status="danger">Inactivo</tm-chip>
<tm-chip status="info">En proceso</tm-chip>
<tm-chip status="indigo">Categoría</tm-chip>
<tm-chip status="brand">Destacado</tm-chip>

Type (estilo visual)

| Type | Descripción | | ---------- | --------------------------------------- | | filled | Fondo sólido con texto blanco | | subtle | Fondo suave con texto del color | | outlined | Sin fondo, solo borde y texto del color |

<tm-chip status="success" type="filled">Filled</tm-chip>
<tm-chip status="success" type="subtle">Subtle</tm-chip>
<tm-chip status="success" type="outlined">Outlined</tm-chip>

Rounded (forma)

| Rounded | Uso recomendado | | --------- | ---------------------------- | | default | Estados, badges informativos | | full | Filtros, tags interactivos |

<tm-chip status="info" rounded="default">Estado</tm-chip>
<tm-chip status="info" rounded="full">Etiqueta</tm-chip>

Íconos

Los íconos se importan desde lucide-angular como objetos y se asignan a propiedades del componente:

import { Check, TriangleAlert, X, CircleX, Info } from 'lucide-angular';

export class MiComponente {
  check = Check;
  triangleAlert = TriangleAlert;
  x = X;
  circleX = CircleX;
  info = Info;
}
<!-- Ícono izquierdo -->
<tm-chip status="success" [iconLeft]="check">Activo</tm-chip>

<!-- Ícono derecho -->
<tm-chip status="info" [iconRight]="x">Etiqueta</tm-chip>

<!-- Ambos íconos -->
<tm-chip status="warning" [iconLeft]="triangleAlert" [iconRight]="x">Advertencia</tm-chip>

Los íconos deben importarse desde lucide-angular como objetos. Pasar strings directamente no funciona.


Clickeable

Por defecto el chip es estático. Para hacerlo interactivo usá [clickable]="true" y escuchá el evento (clicked):

<tm-chip
  status="danger"
  type="filled"
  rounded="full"
  [iconLeft]="circleX"
  [clickable]="true"
  (clicked)="onDesactivar()"
>
  Desactivar
</tm-chip>
onDesactivar() {
  console.log('chip clickeado');
}

Si clickable es false (default), el chip no emite eventos ni muestra cursor pointer.


Combinaciones comunes

<!-- Estado de un producto -->
<tm-chip status="success" type="subtle" rounded="full" [iconLeft]="check">Activo</tm-chip>

<!-- Licencia por vencer -->
<tm-chip status="warning" type="filled" rounded="default" [iconLeft]="triangleAlert"
  >Por vencer</tm-chip
>

<!-- Filtro clickeable -->
<tm-chip status="indigo" type="outlined" rounded="full" [clickable]="true" (clicked)="filtrar()">
  Electrónica
</tm-chip>

<!-- Notificación de error -->
<tm-chip status="danger" type="subtle" rounded="default" [iconLeft]="circleX"
  >Pago rechazado</tm-chip
>

<!-- Chip informativo con ícono derecho -->
<tm-chip status="info" type="subtle" rounded="full" [iconRight]="x">En proceso</tm-chip>

Notas

  • Si clickable es false, el evento (clicked) no se emite aunque se haga click.
  • Los íconos deben importarse desde lucide-angular como objetos y asignarse a propiedades del componente.
  • El tamaño del ícono es fijo en 14px con strokeWidth de 1.75.

TmCloseDialog

Componente TmCloseDialog

Componente de botón de cierre de la librería ten-minds-ui-kit. Diseñado para cerrar diálogos, modales o paneles. Muestra un ícono de X con fondo circular al hacer hover.


Importación

import { TmCloseDialog } from 'ten-minds-ui-kit';

@Component({
  imports: [TmCloseDialog],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ----------- | --------- | ---------- | ------------------------- | | disabled | boolean | false | Desactiva el botón | | ariaLabel | string | 'Cerrar' | Texto accesible del botón |

Outputs

| Evento | Tipo | Descripción | | -------- | ------ | --------------------------------------------------- | | closed | void | Se emite al hacer click (no emite si está disabled) |


Ejemplos

Uso básico

<tm-close-dialog (closed)="onClose()" />

Deshabilitado

<tm-close-dialog [disabled]="true" />

Con aria-label personalizado

<tm-close-dialog ariaLabel="Cerrar modal de confirmación" (closed)="onClose()" />

Combinaciones comunes

<!-- En la cabecera de un modal -->
<div class="flex items-center justify-between">
  <h2>Título del modal</h2>
  <tm-close-dialog (closed)="cerrarModal()" />
</div>

<!-- Deshabilitado durante procesamiento -->
<tm-close-dialog [disabled]="procesando" (closed)="cerrarModal()" />
procesando = false;

cerrarModal() {
  console.log('Cerrando modal...');
}

Notas

  • El evento closed no se emite cuando disabled es true, no es necesario manejarlo manualmente.
  • El ícono de cierre es fijo (X de lucide-angular) y no es configurable.
  • El botón tiene forma circular y aplica un fondo sutil (hover:bg-tm-subtle) al pasar el cursor, sin afectar el layout del contenedor.

TmInput

Componente TmInput

Componente de campo de texto de la librería ten-minds-ui-kit. Soporta múltiples tipos de input, tamaños, íconos, estados de error, hints y labels.


Uso

Importa el componente en tu módulo o componente standalone:

import { TmInput } from 'ten-minds-ui-kit';

@Component({
  imports: [TmInput],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ------------- | ---------------- | --------------- | ----------------------------------------------------------------------------- | | value | string | '' | Valor del input (two-way binding con model) | | size | InputSize | 'md' | Tamaño del input | | type | InputType | 'text' | Tipo de input nativo | | placeholder | string | 'placeholder' | Texto de placeholder | | label | string | '' | Etiqueta visible sobre el input | | hint | string | '' | Texto de ayuda debajo del input (no se muestra si hay error) | | disabled | boolean | false | Desactiva el input | | required | boolean | false | Marca el campo como requerido (agrega * al label) | | error | string | '' | Mensaje de error (activa el estado visual de error) | | iconLeft | LucideIconData | — | Ícono a la izquierda del input | | iconRight | LucideIconData | — | Ícono a la derecha del input | | customClass | string | '' | Clases de Tailwind adicionales aplicadas al wrapper (ej: ancho, sombra, etc.) |

Outputs

| Evento | Tipo | Descripción | | --------- | ------------ | --------------------------------------- | | focused | FocusEvent | Se emite cuando el input recibe el foco | | blurred | FocusEvent | Se emite cuando el input pierde el foco |

Tipos

type InputSize = 'sm' | 'md';
type InputType = 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url';

Tamaños y tipografía

El prop size controla las dimensiones del wrapper y el tamaño del texto escrito. El placeholder siempre se muestra un tamaño más pequeño que el texto real para diferenciarse visualmente.

| Size | Alto | Texto escrito | Placeholder | | ---- | ------ | ------------- | ----------- | | md | h-11 | text-sm | text-xs | | sm | h-10 | text-xs | text-xs |


Ejemplos

Uso básico

<tm-input placeholder="Escribe algo..." />

Con label y hint

<tm-input
  label="Correo electrónico"
  placeholder="[email protected]"
  hint="Nunca compartiremos tu correo."
/>

Campo requerido

<tm-input label="Nombre completo" placeholder="Juan Pérez" [required]="true" />

Tamaños

<tm-input size="md" placeholder="Medium (default)" /> <tm-input size="sm" placeholder="Small" />

Ancho personalizado con customClass

Por defecto el input tiene un ancho fijo de 273px. Usa customClass para sobreescribirlo o agregar cualquier estilo adicional al wrapper.

<!-- Ancho completo -->
<tm-input customClass="w-full" placeholder="Ancho completo" />

<!-- Ancho fijo distinto -->
<tm-input customClass="w-96" placeholder="Ancho fijo" />

<!-- Múltiples clases -->
<tm-input customClass="w-full max-w-md shadow-lg" placeholder="Con sombra" />

customClass se aplica al div wrapper del input, por lo que es ideal para controlar dimensiones, márgenes o sombras externas. No afecta el texto ni los íconos internos.

Tipos de input

<tm-input type="text" placeholder="Texto" />
<tm-input type="email" placeholder="Correo" />
<tm-input type="password" placeholder="Contraseña" />
<tm-input type="number" placeholder="Número" />
<tm-input type="search" placeholder="Buscar..." />
<tm-input type="tel" placeholder="Teléfono" />
<tm-input type="url" placeholder="https://..." />

Estado deshabilitado

<tm-input [disabled]="true" placeholder="No disponible" />
<tm-input [disabled]="true" label="Usuario" placeholder="admin" />

Estado de error

<tm-input
  label="Correo electrónico"
  placeholder="[email protected]"
  error="El correo ingresado no es válido."
/>

Cuando error tiene valor, el hint queda oculto y el borde cambia a rojo.

Con íconos

Los íconos se pasan como objetos LucideIconData, no como strings.

// app.ts
import { Mail, Lock, Search, Eye } from 'lucide-angular';

export class App {
  mail = Mail;
  lock = Lock;
  search = Search;
  eye = Eye;
}
<!-- Ícono izquierdo -->
<tm-input [iconLeft]="mail" placeholder="Correo electrónico" />

<!-- Ícono derecho -->
<tm-input [iconRight]="eye" type="password" placeholder="Contraseña" />

<!-- Ambos íconos -->
<tm-input [iconLeft]="search" [iconRight]="eye" placeholder="Buscar..." />

Two-way binding

<tm-input [(value)]="correo" placeholder="Correo electrónico" />
<p>Valor actual: {{ correo }}</p>
correo = '';

Manejo de eventos de foco

<tm-input placeholder="Nombre" (focused)="onFocus($event)" (blurred)="onBlur($event)" />
onFocus(event: FocusEvent) {
  console.log('Input enfocado', event);
}

onBlur(event: FocusEvent) {
  console.log('Input desenfocado', event);
}

Combinaciones comunes

<!-- Campo de búsqueda -->
<tm-input type="search" size="sm" [iconLeft]="search" placeholder="Buscar usuarios..." />

<!-- Campo de contraseña con label y error -->
<tm-input
  type="password"
  label="Contraseña"
  placeholder="Mínimo 8 caracteres"
  [iconLeft]="lock"
  error="La contraseña es demasiado corta."
/>

<!-- Campo de email requerido con hint -->
<tm-input
  type="email"
  label="Correo electrónico"
  placeholder="[email protected]"
  [iconLeft]="mail"
  hint="Te enviaremos un enlace de confirmación."
  [required]="true"
  [(value)]="email"
/>

<!-- Campo de ancho completo con ícono -->
<tm-input
  type="search"
  [iconLeft]="search"
  placeholder="Buscar en el sistema..."
  customClass="w-full"
/>

Notas

  • El prop value usa model() de Angular Signals, lo que permite two-way binding con la sintaxis [(value)].
  • Cuando error tiene contenido, el hint no se muestra, independientemente de su valor.
  • El estado visual del wrapper cambia automáticamente entre default, active, error y disabled sin configuración adicional.
  • Los íconos deben importarse desde lucide-angular como objetos y asignarse a propiedades del componente. Pasar strings directamente no funciona.
  • El placeholder siempre se renderiza un tamaño de fuente más pequeño que el texto escrito, independientemente del size seleccionado.
  • customClass se agrega al final de las clases del wrapper, por lo que tiene prioridad sobre los estilos base en caso de conflicto.
  • No se requiere ninguna configuración global de íconos en app.config.ts.

TmMenu

Componente TmMenu

Sistema de menú desplegable con posicionamiento automático via Angular CDK Overlay. Soporta trigger personalizado, header opcional, items con íconos, divisores y opciones peligrosas. El chevron de apertura/cierre está incluido y se anima automáticamente.


Importación

import { Tm_Menu } from 'ten-minds-ui-kit';

@Component({
  imports: [Tm_Menu],
})

O pieza por pieza:

import { TmMenu, TmMenuItem, TmMenuTrigger } from 'ten-minds-ui-kit';

@Component({
  imports: [TmMenu, TmMenuItem, TmMenuTrigger],
})

API

TmMenu

| Prop | Tipo | Default | Descripción | | ----------- | --------------- | -------------- | -------------------------------------- | | placement | MenuPlacement | 'bottom-end' | Posición del panel respecto al trigger | | triggerOn | MenuTriggerOn | 'click' | Cómo se abre el menú | | fullWidth | boolean | false | El panel hereda el ancho del trigger |

| Output | Tipo | Descripción | | ---------- | ------ | --------------------------------- | | (opened) | void | Se emite cuando el menú se abre | | (closed) | void | Se emite cuando el menú se cierra |

TmMenuItem

| Prop | Tipo | Default | Descripción | | ---------- | ---------------- | ------- | ----------------------------------------- | | label | string | '' | Texto del item | | icon | LucideIconData | — | Ícono a la izquierda del label (opcional) | | danger | boolean | false | Estilo rojo para acciones destructivas | | disabled | boolean | false | Desactiva el item | | divider | boolean | false | Renderiza un separador en lugar de item |

| Output | Tipo | Descripción | | ------------ | ------ | ---------------------------------- | | (selected) | void | Se emite al hacer click en el item |

Tipos

type MenuPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
type MenuTriggerOn = 'click' | 'hover' | 'both';

Uso básico

El trigger es cualquier elemento marcado con tmMenuTrigger. El chevron se inyecta automáticamente:

<tm-menu>
  <button tmMenuTrigger class="px-4 py-2 rounded-lg border">Opciones</button>

  <tm-menu-item label="Editar" [icon]="editIcon" (selected)="editar()" />
  <tm-menu-item label="Duplicar" [icon]="copyIcon" (selected)="duplicar()" />
  <tm-menu-item [divider]="true" />
  <tm-menu-item label="Eliminar" [icon]="trashIcon" [danger]="true" (selected)="eliminar()" />
</tm-menu>

Posiciones

<tm-menu placement="bottom-end">...</tm-menu>
<!-- default -->
<tm-menu placement="bottom-start">...</tm-menu>
<tm-menu placement="top-end">...</tm-menu>
<tm-menu placement="top-start">...</tm-menu>

Modos de apertura

<!-- Abre y cierra con click (default) -->
<tm-menu triggerOn="click">...</tm-menu>

<!-- Abre con hover -->
<tm-menu triggerOn="hover">...</tm-menu>

<!-- Ambos -->
<tm-menu triggerOn="both">...</tm-menu>

Full width

El panel hereda el ancho del trigger:

<tm-menu [fullWidth]="true">
  <button tmMenuTrigger class="w-full px-4 py-2 rounded-lg border">Seleccionar acción</button>
  <tm-menu-item label="Exportar" (selected)="exportar()" />
  <tm-menu-item label="Importar" (selected)="importar()" />
</tm-menu>

Con header de perfil

El slot menuHeader renderiza contenido fijo en la parte superior del panel, antes de los items:

<tm-menu placement="bottom-start">
  <img tmMenuTrigger src="avatar.png" class="rounded-full w-10 h-10 cursor-pointer" />

  <div menuHeader class="px-3 py-3 flex items-center gap-3 border-b border-tm-subtle w-full">
    <img src="avatar.png" class="rounded-full w-10 h-10" />
    <div>
      <p class="font-medium text-sm">Jhon Doe</p>
      <p class="text-xs text-tm-secondary">Super Admin</p>
    </div>
  </div>

  <tm-menu-item label="Mi perfil" [icon]="userIcon" (selected)="verPerfil()" />
  <tm-menu-item label="Mis plugins" [icon]="pluginIcon" (selected)="verPlugins()" />
  <tm-menu-item
    label="Cerrar sesión"
    [icon]="logoutIcon"
    [danger]="true"
    [divider]="true"
    (selected)="cerrarSesion()"
  />
</tm-menu>

Casos de uso reales

Menú de acciones sobre una fila de tabla

<tm-menu placement="bottom-end">
  <button tmMenuTrigger class="p-1 rounded hover:bg-tm-canvas">
    <lucide-icon [img]="moreVertical" [size]="16" />
  </button>

  <tm-menu-item label="Ver detalle" [icon]="eyeIcon" (selected)="verDetalle(item)" />
  <tm-menu-item label="Editar" [icon]="editIcon" (selected)="editar(item)" />
  <tm-menu-item [divider]="true" />
  <tm-menu-item label="Eliminar" [icon]="trashIcon" [danger]="true" (selected)="eliminar(item)" />
</tm-menu>

Menú de usuario en sidebar

<tm-menu placement="top-start" [fullWidth]="true">
  <div tmMenuTrigger class="flex items-center gap-3 p-3 rounded-lg hover:bg-tm-canvas w-full">
    <img src="avatar.png" class="rounded-full w-8 h-8" />
    <div class="flex-1 min-w-0">
      <p class="text-sm-semibold text-tm-primary truncate">Jhon Doe</p>
      <p class="text-xs-regular text-tm-secondary truncate">Super Admin</p>
    </div>
  </div>

  <tm-menu-item label="Mi perfil" [icon]="userIcon" (selected)="verPerfil()" />
  <tm-menu-item label="Configuración" [icon]="settingsIcon" (selected)="configuracion()" />
  <tm-menu-item [divider]="true" />
  <tm-menu-item
    label="Cerrar sesión"
    [icon]="logoutIcon"
    [danger]="true"
    (selected)="cerrarSesion()"
  />
</tm-menu>

Menú con hover para navegación

<tm-menu triggerOn="hover" placement="bottom-start">
  <button tmMenuTrigger class="px-4 py-2 text-sm-medium text-tm-primary">Productos</button>

  <tm-menu-item label="Plan Starter" (selected)="navegar('/starter')" />
  <tm-menu-item label="Plan Professional" (selected)="navegar('/professional')" />
  <tm-menu-item label="Plan Enterprise" (selected)="navegar('/enterprise')" />
</tm-menu>

Escuchar apertura y cierre

menuAbierto = false;
<tm-menu (opened)="menuAbierto = true" (closed)="menuAbierto = false">
  <button tmMenuTrigger>Opciones</button>
  <tm-menu-item label="Acción" (selected)="accion()" />
</tm-menu>

Notas importantes

  • El chevron está incluido — TmMenu inyecta automáticamente el ícono ChevronDown/ChevronUp junto al trigger. No es necesario importar ni declarar los íconos manualmente.
  • tmMenuTrigger puede aplicarse a cualquier elemento HTML: <button>, <div>, <img>, etc.
  • menuHeader es un slot opcional para contenido fijo en la parte superior del panel (perfil, avatar, etc.).
  • [divider]="true" en un tm-menu-item renderiza únicamente un separador visual, ignorando label, icon y demás props.
  • El posicionamiento usa Angular CDK Overlay, por lo que el panel siempre queda visible dentro del viewport.
  • Tm_Menu requiere que @angular/cdk esté instalado en el proyecto.

TmRadio

Componente TmRadio

Componente de selección única dentro de un grupo. Semánticamente correcto con name compartido entre opciones y accesible.


Importación

import { TmRadio } from 'ten-minds-ui-kit';

@Component({
  imports: [TmRadio],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ---------- | --------- | --------------- | -------------------------------------------------------- | | value | string | requerido | Valor que emite el radio al seleccionarse | | checked | boolean | false | Estado actual del radio | | disabled | boolean | false | Desactiva la interacción | | label | string | — | Texto descriptivo al lado del radio (opcional) | | name | string | 'radio-group' | Agrupa radios relacionados para comportamiento exclusivo | | id | string | auto-generado | ID del input, útil para asociar labels externos |

Outputs

| Output | Tipo | Descripción | | ----------- | -------- | ------------------------------------------- | | (changed) | string | Emite el value del radio al seleccionarse |


Uso básico

<tm-radio value="opcion1" [checked]="seleccion === 'opcion1'" (changed)="seleccion = $event" />

Estados

Default (no seleccionado)

<tm-radio value="a" [checked]="false" (changed)="seleccion = $event" />

Checked (seleccionado)

<tm-radio value="a" [checked]="true" (changed)="seleccion = $event" />

Disabled no seleccionado

<tm-radio value="a" [checked]="false" [disabled]="true" />

Disabled seleccionado

<tm-radio value="a" [checked]="true" [disabled]="true" />

Con label

<tm-radio
  value="mensual"
  label="Facturación mensual"
  [checked]="plan === 'mensual'"
  (changed)="plan = $event"
/>

Grupo de radios

Los radios se agrupan mediante el input name. Solo uno puede estar seleccionado a la vez. El padre controla cuál está activo comparando su estado con el value de cada radio:

seleccion = 'mensual';

onChanged(value: string) {
  this.seleccion = value;
}
<tm-radio
  name="plan"
  value="mensual"
  label="Mensual"
  [checked]="seleccion === 'mensual'"
  (changed)="onChanged($event)"
/>
<tm-radio
  name="plan"
  value="anual"
  label="Anual"
  [checked]="seleccion === 'anual'"
  (changed)="onChanged($event)"
/>

O de forma inline:

<tm-radio
  name="plan"
  value="mensual"
  label="Mensual"
  [checked]="seleccion === 'mensual'"
  (changed)="seleccion = $event"
/>

Casos de uso reales

Selector de plan

planSeleccionado = 'mensual';

planes = [
  { id: 'mensual', nombre: 'Mensual - $9.99' },
  { id: 'anual', nombre: 'Anual - $99.99' },
];
@for(plan of planes; track plan.id) {
<tm-radio
  name="plan"
  [value]="plan.id"
  [label]="plan.nombre"
  [checked]="planSeleccionado === plan.id"
  (changed)="planSeleccionado = $event"
/>
}

Con opción deshabilitada según disponibilidad

@for(opcion of opciones; track opcion.id) {
<tm-radio
  name="envio"
  [value]="opcion.id"
  [label]="opcion.nombre"
  [checked]="envioSeleccionado === opcion.id"
  [disabled]="!opcion.disponible"
  (changed)="envioSeleccionado = $event"
/>
}

Dentro de un formulario reactivo

form = new FormGroup({
  rol: new FormControl('viewer'),
});
@for(rol of roles; track rol.value) {
<tm-radio
  name="rol"
  [value]="rol.value"
  [label]="rol.label"
  [checked]="form.get('rol')?.value === rol.value"
  (changed)="form.get('rol')?.setValue($event)"
/>
}

Nota importante

tm-radio no guarda estado internamente. El padre es responsable de comparar el valor seleccionado con el value de cada radio para determinar cuál está activo con [checked]="seleccion === value".


TmSelect

Componente TmSelect

Componente de selección desplegable con búsqueda integrada, soporte de íconos y avatares por opción, manejo de errores y dos tamaños. El dropdown se posiciona automáticamente usando Angular CDK Overlay.


Importación

import { TmSelect } from 'ten-minds-ui-kit';

@Component({
  imports: [TmSelect],
})

API

Inputs

| Prop | Tipo | Default | Descripción | | ------------- | -------------------------- | ------------------ | ------------------------------------------------ | | options | SelectOption[] | [] | Lista de opciones disponibles | | value | string \| number \| null | null | Valor seleccionado actualmente (two-way binding) | | placeholder | string | 'Seleccionar...' | Texto cuando no hay opción seleccionada | | size | SelectSize | 'md' | Tamaño del componente: 'sm' o 'md' | | disabled | boolean | false | Desactiva la interacción | | error | string | '' | Mensaje de error visible debajo del select | | label | string | '' | Etiqueta visible encima del select |

Outputs

| Output | Tipo | Descripción | | ----------- | ---------------------- | ------------------------------------------------- | | (changed) | SelectOption \| null | Emite la opción seleccionada, o null al limpiar |

Two-way binding

value soporta [(value)] para sincronización bidireccional:

<tm-select [(value)]="seleccion" [options]="opciones" />

Tipos

SelectOption

interface SelectOption {
  label: string;
  value: string | number;
  avatar?: string; // URL de imagen circular
  icon?: LucideIconData; // Ícono de lucide-angular
  group?: string; // Agrupación (referencia futura)
  disabled?: boolean; // Desactiva la opción individualmente
}

SelectSize

type SelectSize = 'sm' | 'md';

Uso básico

opciones: SelectOption[] = [
  { label: 'Argentina', value: 'ar' },
  { label: 'Bolivia',   value: 'bo' },
  { label: 'Chile',     value: 'cl' },
];

pais: string | null = null;
<tm-select [(value)]="pais" [options]="opciones" placeholder="Seleccioná un país" />

Tamaños

md (default)

<tm-select size="md" [(value)]="seleccion" [options]="opciones" />

sm

<tm-select size="sm" [(value)]="seleccion" [options]="opciones" />

Estados

Con label

<tm-select label="País de residencia" [(value)]="pais" [options]="opciones" />

Con error

<tm-select [(value)]="pais" [options]="opciones" [error]="'Debés seleccionar un país'" />

Disabled

<tm-select [(value)]="pais" [options]="opciones" [disabled]="true" />

Opciones con ícono

import { CircleUser, Globe, Settings } from 'lucide-angular';

opciones: SelectOption[] = [
  { label: 'Perfil',        value: 'profile',  icon: CircleUser },
  { label: 'Configuración', value: 'settings', icon: Settings   },
  { label: 'Región',        value: 'region',   icon: Globe      },
];
<tm-select [(value)]="opcion" [options]="opciones" />

Opciones con avatar

opciones: SelectOption[] = [
  { label: 'Ana García',   value: 'ana',   avatar: 'https://i.pravatar.cc/32?u=ana'   },
  { label: 'Luis Pérez',   value: 'luis',  avatar: 'https://i.pravatar.cc/32?u=luis'  },
  { label: 'María Torres', value: 'maria', avatar: 'https://i.pravatar.cc/32?u=maria' },
];
<tm-select label="Asignar a" [(