ten-minds-beta
v1.0.1
Published
Librería de componentes UI para proyectos Angular de Ten Minds
Downloads
211
Maintainers
Readme
ten-minds-beta
Librería de componentes UI para Angular, construida con Tailwind CSS v4 y Lucide Icons.
Instalación
npm install ten-minds-betaConfiguración inicial
1.- Instalar la sgte libreria lucide-angular
npm i lucide-angular
1. Agregar los estilos de la librería
Importá los estilos de la librería en el styles.css de tu proyecto:
@import 'ten-minds-beta/styles/styles.css';Customización de Estilos
La librería ten-minds-beta ofrece tres niveles de personalización según el alcance que necesites.
Nivel 1 — Global: sobreescribir tokens CSS
Afecta todos los componentes que usen ese token en toda la app.
Agrega esto en el styles.css de tu proyecto:
:root {
--tm-gray-900: #your-brand-color;
}O directamente el token semántico:
: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: cambiar un componente en 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-btn-primary button {
background-color: #your-color !important;
font-size: 1.25rem !important;
border-radius: 999px !important;
}<div class="mi-seccion">
<tm-btn-primary>Solo este botón cambia</tm-btn-primary>
</div>
<tm-btn-primary>Este queda igual</tm-btn-primary>⚠️ 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: un solo componente con customClass
Afecta únicamente el componente donde lo aplicas. Aquí sí puedes usar clases de Tailwind porque se compilan en el proyecto consumidor.
<!-- Tailwind funciona aquí -->
<tm-btn-primary customClass="!bg-orange-500 !text-black">
Botón personalizado
</tm-btn-primary>
<!-- CSS también funciona con clase propia -->
<tm-btn-primary customClass="mi-boton-especial">
Botón personalizado
</tm-btn-primary>/* 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.
Resumen
| 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 |
2. Importar los componentes
Los componentes son standalone — solo importa los que necesites directamente en tu componente o módulo:
import { BtnPrimary, Chip, CheckboxButton } from 'ten-minds-beta';
@Component({
imports: [BtnPrimary, Chip, CheckboxButton],
})3. Usar íconos
Los íconos se importan directamente desde lucide-angular como objetos. No se requiere ninguna configuración global en app.config.ts.
import { Download, Trash2, Check } from 'lucide-angular';
export class MiComponente {
download = Download;
trash = Trash2;
check = Check;
}<tm-btn-primary [iconLeft]="download">Exportar</tm-btn-primary>
<tm-chip [iconLeft]="check">Activo</tm-chip>Componentes disponibles
| Componente | Selector | Descripción |
|---|---|---|
| BtnPrimary | tm-btn-primary | Botón principal con múltiples variantes |
| Chip | tm-chip | Etiqueta de estado o categoría |
| CheckboxButton | tm-checkbox | Checkbox personalizado |
| Toggle | tm-toggle | Switch on/off |
| RadioButton | tm-radio-button | Botón de selección única |
| InputTen | tm-input | Campo de texto |
| Avatar | tm-avatar | Avatar con iniciales y estado |
| SnackbarHost | tm-snackbar-host | Contenedor de notificaciones |
Dark Mode
La librería soporta dark mode mediante la clase .dark en el elemento raíz. Agrégala o quítala desde tu componente:
onThemeChange(mode: 'light' | 'dark') {
if (mode === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}Componentes
BtnPrimary
Componente de botón principal de la librería ten-minds-beta. Soporta múltiples colores, variantes, tamaños, bordes redondeados e íconos.
Instalación
npm install ten-minds-betaImporta el componente en tu módulo o componente standalone:
import { BtnPrimary } from 'ten-minds-beta';
@Component({
imports: [BtnPrimary],
})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 estado activo (brightness) |
| iconLeft | LucideIconData | — | Ícono a la izquierda del texto |
| iconRight | LucideIconData | — | Ícono a la derecha del texto |
Outputs
| Evento | Tipo | Descripción |
|---|---|---|
| clicked | MouseEvent | Se emite al hacer click (no emite si está disabled) |
Tipos
type ButtonColor = 'primary' | 'danger' | 'success' | 'warning';
type ButtonSize = 'lg' | 'md' | 'sm';
type ButtonType = 'filled' | 'outlined' | 'ghost';
type ButtonRounded = 'default' | 'full';Ejemplos
Colores
<btn-primary color="primary">Primary</btn-primary>
<btn-primary color="danger">Danger</btn-primary>
<btn-primary color="success">Success</btn-primary>
<btn-primary color="warning">Warning</btn-primary>Variantes
<btn-primary type="filled">Filled</btn-primary>
<btn-primary type="outlined">Outlined</btn-primary>
<btn-primary type="ghost">Ghost</btn-primary>Tamaños
<btn-primary size="lg">Large</btn-primary>
<btn-primary size="md">Medium</btn-primary>
<btn-primary size="sm">Small</btn-primary>Bordes redondeados
<btn-primary rounded="default">Default</btn-primary>
<btn-primary rounded="full">Pill</btn-primary>Disabled
<btn-primary [disabled]="true">No disponible</btn-primary>
<btn-primary color="danger" type="outlined" [disabled]="true">Eliminar</btn-primary>Con íconos
Los íconos se pasan como objetos LucideIconData, no como strings.
// app.ts
import { Download, Trash2, Check } from 'lucide-angular';
export class App {
download = Download;
trash = Trash2;
check = Check;
}<!-- Ícono izquierdo -->
<btn-primary [iconLeft]="download">Exportar</btn-primary>
<!-- Ícono derecho -->
<btn-primary color="success" [iconRight]="check">Confirmar</btn-primary>
<!-- Ambos íconos -->
<btn-primary color="danger" type="outlined" [iconLeft]="trash" [iconRight]="check">
Eliminar y confirmar
</btn-primary>Combinaciones comunes
<!-- Botón de acción principal -->
<btn-primary color="primary" type="filled" size="md" [iconLeft]="download">
Exportar
</btn-primary>
<!-- Botón destructivo -->
<btn-primary color="danger" type="outlined" size="md" [iconLeft]="trash">
Eliminar
</btn-primary>
<!-- Botón redondeado estilo pill -->
<btn-primary color="primary" type="filled" rounded="full" [iconLeft]="check">
Confirmar
</btn-primary>
<!-- Botón pequeño fantasma -->
<btn-primary color="primary" type="ghost" size="sm">
Ver más
</btn-primary>Manejo de eventos
<btn-primary color="success" (clicked)="onGuardar($event)">
Guardar
</btn-primary>onGuardar(event: MouseEvent) {
console.log('Guardando...', event);
}Notas
- El evento
clickedno se emite cuandodisabledestrue, no es necesario manejarlo manualmente. - Los íconos deben importarse desde
lucide-angularcomo objetos y asignarse a propiedades del componente. Pasar strings directamente no funciona. - No se requiere ninguna configuración global de íconos en
app.config.ts.
Desarrollo
# Buildear la librería en modo watch
ng build ten-minds-beta --watch
# Servir la app de prueba
ng serve consumer-appSnackbarService
Sistema de notificaciones globales para mostrar feedback visual al usuario.
Configuración (una sola vez)
app.html:
<snackbar-host /> <router-outlet />app.ts:
import { SnackbarHost } from 'borealis-ui';
@Component({
imports: [SnackbarHost],
})Uso
import { SnackbarService } from 'borealis-ui';
export class MiComponente {
snackbar = inject(SnackbarService);
async onGuardar() {
try {
await this.service.guardar(data);
this.snackbar.alert('success', 'Guardado', 'Los cambios fueron guardados correctamente');
} catch {
this.snackbar.alert('error', 'Error', 'No pudimos guardar los cambios');
}
}
}Métodos
| Método | Descripción |
| --------------------------------------------------------- | -------------------------------- |
| alert(color, title, description?, duration?, position?) | Desaparece automáticamente |
| notify(color, title, description?, position?) | El usuario la cierra manualmente |
Parámetros
| Parámetro | Tipo | Default | Valores |
| ------------- | ------------------ | ----------- | -------------------------------------------------------------------------------- |
| color | SnackbarColor | — | success error warning info |
| title | string | — | — |
| description | string | undefined | — |
| duration | number | 5000 | Milisegundos |
| position | SnackbarPosition | top-right | top-left top-center top-right bottom-left bottom-center bottom-right |
Ejemplos
// Mínimo
this.snackbar.alert('success', 'Guardado');
// Con descripción
this.snackbar.alert('error', 'Error de pago', 'No pudimos procesar tu método de pago');
// Con duración personalizada
this.snackbar.alert('warning', 'Licencia por expirar', 'Expira en 7 días', 2000);
// Con posición personalizada
this.snackbar.alert('info', 'Nueva versión', 'Yana 2.0 disponible', 5000, 'bottom-center');
// Notificación persistente
this.snackbar.notify('success', 'Compra exitosa', 'Tu licencia fue activada');
// Notificación con posición
this.snackbar.notify('error', 'Sin conexión', undefined, 'bottom-right');Chip
Componente para mostrar estados, etiquetas o valores visuales. Puede ser estático o clickeable.
Instalación
import { Chip } from 'ten-minds-beta';
@Component({
imports: [Chip],
})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 |
<chip status="success">Activo</chip>
<chip status="warning">Pendiente</chip>
<chip status="danger">Inactivo</chip>
<chip status="info">En proceso</chip>
<chip status="indigo">Categoría</chip>
<chip status="brand">Destacado</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 |
<chip status="success" type="filled">Filled</chip>
<chip status="success" type="subtle">Subtle</chip>
<chip status="success" type="outlined">Outlined</chip>Rounded (forma)
| Rounded | Uso recomendado |
| --------- | ------------------------------------------------ |
| default | Valores estáticos, estados, badges informativos |
| full | Filtros, tags interactivos, estados de productos |
<chip status="info" rounded="default">Estado</chip>
<chip status="info" rounded="full">Etiqueta</chip>Iconos
Acepta cualquier nombre de icono de Lucide en iconLeft o iconRight.
<!-- Solo icono izquierdo -->
<chip status="success" iconLeft="check">Activo</chip>
<!-- Solo icono derecho -->
<chip status="info" iconRight="x">Etiqueta</chip>
<!-- Ambos iconos -->
<chip status="warning" iconLeft="triangle-alert" iconRight="x">Advertencia</chip>Clickeable
Por defecto el chip es estático. Para hacerlo interactivo usa [clickable]="true" y escucha el evento (clicked).
<chip
status="danger"
type="filled"
rounded="full"
iconLeft="circle-x"
[clickable]="true"
(clicked)="onDesactivar()"
>
Desactivar
</chip>onDesactivar() {
console.log('chip clickeado');
}Si
clickableesfalse(default), el chip no emite eventos ni muestra cursor pointer.
Referencia de inputs y outputs
| Input | Tipo | Default | Descripción |
| ----------- | ------------- | ----------- | ----------------------------------------------------- |
| status | ChipStatus | 'brand' | Color del chip |
| type | ChipType | 'filled' | Estilo visual del chip |
| rounded | ChipRounded | 'default' | Forma del borde |
| iconLeft | string | — | Nombre del icono Lucide a la izquierda (opcional) |
| iconRight | string | — | Nombre del icono Lucide a la derecha (opcional) |
| clickable | boolean | false | Habilita cursor pointer y emite evento al hacer click |
| Output | Tipo | Descripción |
| --------- | ------ | --------------------------------------------- |
| clicked | void | Se emite al hacer click si clickable="true" |
Casos de uso reales
<!-- Estado de un producto -->
<chip status="success" type="subtle" rounded="full" iconLeft="check"> Activo </chip>
<!-- Licencia por vencer -->
<chip status="warning" type="filled" rounded="default" iconLeft="triangle-alert"> Por vencer </chip>
<!-- Filtro clickeable -->
<chip status="indigo" type="outlined" rounded="full" [clickable]="true" (clicked)="filtrar()">
Electrónica
</chip>
<!-- Notificación de error -->
<chip status="danger" type="subtle" rounded="default" iconLeft="circle-x"> Pago rechazado </chip>Componente Toggle
Componente de interruptor para activar o desactivar estados. Emite el nuevo valor al padre para que maneje la lógica correspondiente.
Instalación
import { Toggle } from 'ten-minds-beta';
@Component({
imports: [Toggle],
})Uso básico
<toggle [value]="isActive" (changed)="isActive = $event" />Inputs y Outputs
| Input | Tipo | Default | Descripción |
| ---------- | --------- | ------- | ------------------------ |
| value | boolean | false | Estado actual del toggle |
| disabled | boolean | false | Desactiva la interacción |
| Output | Tipo | Descripción |
| --------- | --------- | ------------------------------------ |
| changed | boolean | Emite el nuevo estado al hacer click |
Estados
Default (apagado)
<toggle [value]="false" (changed)="onChanged($event)" />Active (encendido)
<toggle [value]="true" (changed)="onChanged($event)" />Disabled
<toggle [value]="false" [disabled]="true" /> <toggle [value]="true" [disabled]="true" />Manejo del estado
El toggle no guarda estado internamente. El padre es responsable de actualizar el valor:
isActive = false;
onChanged(value: boolean) {
this.isActive = value;
}<toggle [value]="isActive" (changed)="onChanged($event)" />O de forma inline:
<toggle [value]="isActive" (changed)="isActive = $event" />Casos de uso reales
Activar notificaciones
<div class="flex items-center gap-3">
<toggle [value]="notificaciones" (changed)="notificaciones = $event" />
<span>Notificaciones</span>
</div>
@if(notificaciones) {
<p>Recibirás notificaciones de nuevos eventos.</p>
} @else {
<p>Las notificaciones están desactivadas.</p>
}Activar/desactivar un producto
<div class="flex items-center gap-3">
<toggle [value]="producto.activo" (changed)="toggleProducto($event)" />
<span>{{ producto.activo ? 'Activo' : 'Inactivo' }}</span>
</div>toggleProducto(value: boolean) {
this.producto.activo = value;
this.productoService.actualizarEstado(this.producto.id, value);
}Con disabled según permisos
<toggle
[value]="configuracion.habilitado"
[disabled]="!tienePermisos"
(changed)="configuracion.habilitado = $event"
/>Checkbox
Componente de selección para formularios. Permite marcar o desmarcar una opción individual. Semánticamente correcto y accesible.
Instalación
import { CheckboxButton } from 'ten-minds-beta';
@Component({
imports: [CheckboxButton],
})Uso básico
<checkbox-button [checked]="isChecked" (changed)="isChecked = $event" />Inputs y Outputs
| Input | 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 |
| Output | Tipo | Descripción |
| --------- | --------- | ------------------------------------ |
| changed | boolean | Emite el nuevo estado al hacer click |
Estados
Default (desmarcado)
<checkbox-button [checked]="false" (changed)="isChecked = $event" />Checked (marcado)
<checkbox-button [checked]="true" (changed)="isChecked = $event" />Disabled desmarcado
<checkbox-button [checked]="false" [disabled]="true" />Disabled marcado
<checkbox-button [checked]="true" [disabled]="true" />Con label
<checkbox-button
[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;
}<checkbox-button [checked]="isChecked" (changed)="onChanged($event)" />O de forma inline:
<checkbox-button [checked]="isChecked" (changed)="isChecked = $event" />Casos de uso reales
Aceptar términos
<checkbox-button
[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) {
<checkbox-button
[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
<checkbox-button
[checked]="opcion.activa"
[disabled]="!tienePermisos"
[label]="opcion.nombre"
(changed)="opcion.activa = $event"
/>Radio Button
Componente de selección única para formularios. Se usa en grupos donde solo una opción puede estar seleccionada a la vez.
Instalación
import { RadioButton } from 'ten-minds-beta';
@Component({
imports: [RadioButton],
})Uso básico
<radio-button
value="opcion1"
name="mi-grupo"
[checked]="selected === 'opcion1'"
(changed)="selected = $event"
/>Inputs y Outputs
| Input | Tipo | Default | Descripción |
| ---------- | --------- | --------------- | ------------------------------------------------------- |
| value | string | requerido | Valor que emite cuando se selecciona |
| name | string | 'radio-group' | Nombre del grupo, debe ser igual en todos los del grupo |
| checked | boolean | false | Estado actual del radio |
| disabled | boolean | false | Desactiva la interacción |
| label | string | — | Texto descriptivo al lado del radio (opcional) |
| id | string | auto-generado | ID del input, útil para asociar labels externos |
| Output | Tipo | Descripción |
| --------- | -------- | --------------------------------------- |
| changed | string | Emite el value del radio seleccionado |
Estados
Default (sin seleccionar)
<radio-button value="opcion1" name="grupo" [checked]="false" (changed)="onChanged($event)" />Checked (seleccionado)
<radio-button value="opcion1" name="grupo" [checked]="true" (changed)="onChanged($event)" />Disabled desmarcado
<radio-button value="opcion1" name="grupo" [checked]="false" [disabled]="true" />Disabled marcado
<radio-button value="opcion1" name="grupo" [checked]="true" [disabled]="true" />Uso en grupo
El name debe ser el mismo en todos los radio del grupo. El padre controla cuál está seleccionado comparando value con el valor actual:
selected = 'mensual';
onChanged(value: string) {
this.selected = value;
}<div class="flex flex-col gap-3">
<radio-button
value="mensual"
name="plan"
label="Mensual"
[checked]="selected === 'mensual'"
(changed)="onChanged($event)"
/>
<radio-button
value="anual"
name="plan"
label="Anual"
[checked]="selected === 'anual'"
(changed)="onChanged($event)"
/>
<radio-button
value="lifetime"
name="plan"
label="De por vida"
[checked]="selected === 'lifetime'"
(changed)="onChanged($event)"
/>
</div>Casos de uso reales
Selección de plan
<div class="flex flex-col gap-3">
@for(plan of planes; track plan.id) {
<radio-button
[value]="plan.id"
name="planes"
[label]="plan.nombre"
[checked]="selectedPlan === plan.id"
(changed)="selectedPlan = $event"
/>
}
</div>Con opción desactivada
<radio-button
value="premium"
name="plan"
label="Premium (próximamente)"
[checked]="false"
[disabled]="true"
/>Selección de rol en formulario de usuario
<div class="flex flex-col gap-3">
<radio-button
value="admin"
name="rol"
label="Administrador"
[checked]="rol === 'admin'"
(changed)="rol = $event"
/>
<radio-button
value="editor"
name="rol"
label="Editor"
[checked]="rol === 'editor'"
(changed)="rol = $event"
/>
<radio-button
value="viewer"
name="rol"
label="Solo lectura"
[checked]="rol === 'viewer'"
(changed)="rol = $event"
/>
</div>rol = 'viewer';