ten-minds-beta 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,914 @@
1
+ # TenMindsBeta
2
+
3
+ This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
4
+
5
+ ## Code scaffolding
6
+
7
+ Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
8
+
9
+ ```bash
10
+ ng generate component component-name
11
+ ```
12
+
13
+ For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
+
15
+ ```bash
16
+ ng generate --help
17
+ ```
18
+
19
+ ## Building
20
+
21
+ To build the library, run:
22
+
23
+ ```bash
24
+ ng build ten-minds-beta
25
+ ```
26
+
27
+ This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
+
29
+ ### Publishing the Library
30
+
31
+ Once the project is built, you can publish your library by following these steps:
32
+
33
+ 1. Navigate to the `dist` directory:
34
+
35
+ ```bash
36
+ cd dist/ten-minds-beta
37
+ ```
38
+
39
+ 2. Run the `npm publish` command to publish your library to the npm registry:
40
+ ```bash
41
+ npm publish
42
+ ```
43
+
44
+ ## Running unit tests
45
+
46
+ To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
47
+
48
+ ```bash
49
+ ng test
50
+ ```
51
+
52
+ ## Running end-to-end tests
53
+
54
+ For end-to-end (e2e) testing, run:
55
+
56
+ ```bash
57
+ ng e2e
58
+ ```
59
+
60
+ Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
61
+
62
+ ## Additional Resources
63
+
64
+ # ten-minds-beta
65
+
66
+ Librería de componentes UI para Angular, construida con Tailwind CSS v4 y Lucide Icons.
67
+
68
+ ---
69
+
70
+ ## Instalación
71
+
72
+ ```bash
73
+ npm install ten-minds-beta lucide-angular
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Configuración inicial
79
+
80
+ En el `app.config.ts` de tu proyecto, registrá los íconos de Lucide que vas a usar:
81
+
82
+ ```typescript
83
+ import { ApplicationConfig, importProvidersFrom } from '@angular/core';
84
+ import { LucideAngularModule, Download, Trash2, Check, Plus, X, Pencil, Search } from 'lucide-angular';
85
+
86
+ export const appConfig: ApplicationConfig = {
87
+ providers: [
88
+ importProvidersFrom(
89
+ LucideAngularModule.pick({ Download, Trash2, Check, Plus, X, Pencil, Search })
90
+ )
91
+ ]
92
+ };
93
+ ```
94
+
95
+ > Registrá solo los íconos que uses — eso mantiene el bundle liviano.
96
+
97
+ ---
98
+
99
+ ## Estilos
100
+
101
+ Importá los estilos de la librería en el `styles.css` de tu proyecto:
102
+
103
+ ```css
104
+ @import 'ten-minds-beta/styles/styles.css';
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Componentes
110
+
111
+ ### `<btn-primary>`
112
+
113
+ Botón con soporte para múltiples variantes, tamaños, estados y íconos.
114
+
115
+ #### Importar
116
+
117
+ ```typescript
118
+ import { BtnPrimary } from 'ten-minds-beta';
119
+
120
+ @Component({
121
+ imports: [BtnPrimary],
122
+ })
123
+ ```
124
+
125
+ #### Inputs
126
+
127
+ | Input | Tipo | Default | Descripción |
128
+ |------------|-----------------------------------------------|-------------|------------------------------------|
129
+ | `color` | `'primary' \| 'danger' \| 'success' \| 'warning'` | `'primary'` | Color del botón |
130
+ | `size` | `'lg' \| 'md' \| 'sm'` | `'md'` | Tamaño del botón |
131
+ | `type` | `'filled' \| 'outlined' \| 'ghost'` | `'filled'` | Estilo visual del botón |
132
+ | `disabled` | `boolean` | `false` | Deshabilita el botón |
133
+ | `active` | `boolean` | `false` | Aplica estado activo |
134
+ | `iconLeft` | `string` | — | Nombre del ícono Lucide a la izquierda |
135
+ | `iconRight`| `string` | — | Nombre del ícono Lucide a la derecha |
136
+
137
+ #### Outputs
138
+
139
+ | Output | Tipo | Descripción |
140
+ |-----------|--------------|------------------------------------|
141
+ | `clicked` | `MouseEvent` | Se emite al hacer click en el botón |
142
+
143
+ ---
144
+
145
+ #### Ejemplos de uso
146
+
147
+ **Variantes de color:**
148
+
149
+ ```html
150
+ <btn-primary color="primary" type="filled">Primary</btn-primary>
151
+ <btn-primary color="danger" type="filled">Danger</btn-primary>
152
+ <btn-primary color="success" type="filled">Success</btn-primary>
153
+ <btn-primary color="warning" type="filled">Warning</btn-primary>
154
+ ```
155
+
156
+ **Variantes de tipo:**
157
+
158
+ ```html
159
+ <btn-primary type="filled" >Filled</btn-primary>
160
+ <btn-primary type="outlined">Outlined</btn-primary>
161
+ <btn-primary type="ghost" >Ghost</btn-primary>
162
+ ```
163
+
164
+ **Tamaños:**
165
+
166
+ ```html
167
+ <btn-primary size="lg">Large</btn-primary>
168
+ <btn-primary size="md">Medium</btn-primary>
169
+ <btn-primary size="sm">Small</btn-primary>
170
+ ```
171
+
172
+ **Estado disabled:**
173
+
174
+ ```html
175
+ <btn-primary [disabled]="true">Deshabilitado</btn-primary>
176
+ ```
177
+
178
+ **Con íconos:**
179
+
180
+ ```html
181
+ <!-- Ícono izquierdo -->
182
+ <btn-primary iconLeft="download">Exportar</btn-primary>
183
+
184
+ <!-- Ícono derecho -->
185
+ <btn-primary iconRight="arrow-right">Siguiente</btn-primary>
186
+
187
+ <!-- Ambos íconos -->
188
+ <btn-primary iconLeft="upload" iconRight="check">Subir archivo</btn-primary>
189
+ ```
190
+
191
+ > Los nombres de íconos van en kebab-case exactamente como aparecen en [lucide.dev](https://lucide.dev).
192
+ > Recordá registrar cada ícono que uses en el `app.config.ts`.
193
+
194
+ **Escuchando eventos:**
195
+
196
+ ```html
197
+ <!-- Output propio -->
198
+ <btn-primary (clicked)="guardar($event)">Guardar</btn-primary>
199
+
200
+ <!-- Eventos nativos -->
201
+ <btn-primary
202
+ (click)="guardar()"
203
+ (dblclick)="abrirModal()"
204
+ (focus)="onFocus()"
205
+ (blur)="onBlur()"
206
+ >
207
+ Guardar
208
+ </btn-primary>
209
+ ```
210
+
211
+ **Combinado:**
212
+
213
+ ```html
214
+ <btn-primary
215
+ color="primary"
216
+ type="filled"
217
+ size="md"
218
+ iconLeft="download"
219
+ (clicked)="exportar($event)"
220
+ >
221
+ Exportar registros
222
+ </btn-primary>
223
+
224
+ <btn-primary
225
+ color="danger"
226
+ type="outlined"
227
+ size="sm"
228
+ iconLeft="trash-2"
229
+ [disabled]="cargando"
230
+ (clicked)="eliminar($event)"
231
+ >
232
+ Eliminar
233
+ </btn-primary>
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Íconos disponibles en Lucide
239
+
240
+ Podés ver el catálogo completo en [lucide.dev](https://lucide.dev). Algunos íconos comunes:
241
+
242
+ | Nombre | Uso sugerido |
243
+ |------------------|----------------------|
244
+ | `download` | Exportar, descargar |
245
+ | `upload` | Importar, subir |
246
+ | `trash-2` | Eliminar |
247
+ | `pencil` | Editar |
248
+ | `check` | Confirmar, guardar |
249
+ | `x` | Cancelar, cerrar |
250
+ | `plus` | Agregar, nuevo |
251
+ | `search` | Buscar |
252
+ | `arrow-right` | Siguiente, continuar |
253
+ | `arrow-left` | Volver, anterior |
254
+ | `chevron-down` | Expandir |
255
+ | `settings` | Configuración |
256
+ | `user` | Perfil, usuario |
257
+ | `eye` | Ver, mostrar |
258
+ | `eye-off` | Ocultar |
259
+ | `alert-triangle` | Advertencia |
260
+ | `info` | Información |
261
+ | `filter` | Filtrar |
262
+ | `refresh-cw` | Recargar |
263
+
264
+ ---
265
+
266
+ ## Desarrollo
267
+
268
+ ```bash
269
+ # Buildear la librería en modo watch
270
+ ng build ten-minds-beta --watch
271
+
272
+ # Servir la app de prueba
273
+ ng serve consumer-app
274
+ ```
275
+ ## SnackbarService
276
+
277
+ Sistema de notificaciones globales para mostrar feedback visual al usuario.
278
+ Reemplaza los `console.log` con mensajes visibles en pantalla.
279
+
280
+ ### Configuración (una sola vez)
281
+
282
+ **`app.html`:**
283
+
284
+ ```html
285
+ <tm-snackbar-host />
286
+ <router-outlet />
287
+ ```
288
+
289
+ **`app.ts`:**
290
+
291
+ ```typescript
292
+ import { SnackbarHostComponent } from 'ten-minds-beta';
293
+
294
+ @Component({
295
+ imports: [SnackbarHostComponent],
296
+ })
297
+ ```
298
+
299
+ ### Uso
300
+ ```typescript
301
+ import { SnackbarService } from 'ten-minds-beta';
302
+
303
+ export class MiComponente {
304
+ snackbar = inject(SnackbarService);
305
+
306
+ async onGuardar() {
307
+ try {
308
+ await this.service.guardar(data);
309
+ this.snackbar.success('Guardado', 'Los cambios fueron guardados correctamente');
310
+ } catch {
311
+ this.snackbar.error('Error', 'No pudimos guardar los cambios');
312
+ }
313
+ }
314
+ }
315
+ ```
316
+
317
+ ### Métodos
318
+
319
+ | Método | Tipo | Descripción |
320
+ |--------|------|-------------|
321
+ | `success(title, description?)` | alert | Desaparece en 5s |
322
+ | `error(title, description?)` | alert | Desaparece en 5s |
323
+ | `warning(title, description?)` | alert | Desaparece en 5s |
324
+ | `info(title, description?)` | alert | Desaparece en 5s |
325
+ | `notify(color{success,error,warning,info}, title, description?)` | notification | El usuario la cierra manualmente |
326
+ ---
327
+
328
+
329
+ ## Chip
330
+
331
+ Componente para mostrar estados, etiquetas o valores visuales. Puede ser estático o clickeable.
332
+
333
+ ---
334
+
335
+ ## Instalación
336
+
337
+ ```typescript
338
+ import { Chip } from 'ten-minds-beta';
339
+
340
+ @Component({
341
+ imports: [Chip],
342
+ })
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Status (colores)
348
+
349
+ | Status | Uso recomendado |
350
+ |-----------|----------------------------------------|
351
+ | `success` | Activo, completado, aprobado |
352
+ | `warning` | Pendiente, por vencer, precaución |
353
+ | `danger` | Inactivo, error, rechazado |
354
+ | `info` | Informativo, en proceso |
355
+ | `indigo` | Categorías, etiquetas neutras |
356
+ | `brand` | Destacado, principal |
357
+
358
+ ```html
359
+ <chip status="success">Activo</chip>
360
+ <chip status="warning">Pendiente</chip>
361
+ <chip status="danger">Inactivo</chip>
362
+ <chip status="info">En proceso</chip>
363
+ <chip status="indigo">Categoría</chip>
364
+ <chip status="brand">Destacado</chip>
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Type (estilo visual)
370
+
371
+ | Type | Descripción |
372
+ |------------|------------------------------------------|
373
+ | `filled` | Fondo sólido con texto blanco |
374
+ | `subtle` | Fondo suave con texto del color |
375
+ | `outlined` | Sin fondo, solo borde y texto del color |
376
+
377
+ ```html
378
+ <chip status="success" type="filled">Filled</chip>
379
+ <chip status="success" type="subtle">Subtle</chip>
380
+ <chip status="success" type="outlined">Outlined</chip>
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Rounded (forma)
386
+
387
+ | Rounded | Uso recomendado |
388
+ |-----------|-----------------------------------------------------|
389
+ | `default` | Valores estáticos, estados, badges informativos |
390
+ | `full` | Filtros, tags interactivos, estados de productos |
391
+
392
+ ```html
393
+ <chip status="info" rounded="default">Estado</chip>
394
+ <chip status="info" rounded="full">Etiqueta</chip>
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Iconos
400
+
401
+ Acepta cualquier nombre de icono de [Lucide](https://lucide.dev/icons/) en `iconLeft` o `iconRight`.
402
+
403
+ ```html
404
+ <!-- Solo icono izquierdo -->
405
+ <chip status="success" iconLeft="check">Activo</chip>
406
+
407
+ <!-- Solo icono derecho -->
408
+ <chip status="info" iconRight="x">Etiqueta</chip>
409
+
410
+ <!-- Ambos iconos -->
411
+ <chip status="warning" iconLeft="triangle-alert" iconRight="x">Advertencia</chip>
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Clickeable
417
+
418
+ Por defecto el chip es estático. Para hacerlo interactivo usa `[clickable]="true"` y escucha el evento `(clicked)`.
419
+
420
+ ```html
421
+ <chip
422
+ status="danger"
423
+ type="filled"
424
+ rounded="full"
425
+ iconLeft="circle-x"
426
+ [clickable]="true"
427
+ (clicked)="onDesactivar()"
428
+ >
429
+ Desactivar
430
+ </chip>
431
+ ```
432
+
433
+ ```typescript
434
+ onDesactivar() {
435
+ console.log('chip clickeado');
436
+ }
437
+ ```
438
+
439
+ > Si `clickable` es `false` (default), el chip no emite eventos ni muestra cursor pointer.
440
+
441
+ ---
442
+
443
+ ## Referencia de inputs y outputs
444
+
445
+ | Input | Tipo | Default | Descripción |
446
+ |-------------|---------------|-------------|-------------------------------------------------------|
447
+ | `status` | `ChipStatus` | `'brand'` | Color del chip |
448
+ | `type` | `ChipType` | `'filled'` | Estilo visual del chip |
449
+ | `rounded` | `ChipRounded` | `'default'` | Forma del borde |
450
+ | `iconLeft` | `string` | — | Nombre del icono Lucide a la izquierda (opcional) |
451
+ | `iconRight` | `string` | — | Nombre del icono Lucide a la derecha (opcional) |
452
+ | `clickable` | `boolean` | `false` | Habilita cursor pointer y emite evento al hacer click |
453
+
454
+ | Output | Tipo | Descripción |
455
+ |-----------|--------|-----------------------------------------------|
456
+ | `clicked` | `void` | Se emite al hacer click si `clickable="true"` |
457
+
458
+ ---
459
+
460
+ ## Casos de uso reales
461
+
462
+ ```html
463
+ <!-- Estado de un producto -->
464
+ <chip status="success" type="subtle" rounded="full" iconLeft="check">
465
+ Activo
466
+ </chip>
467
+
468
+ <!-- Licencia por vencer -->
469
+ <chip status="warning" type="filled" rounded="default" iconLeft="triangle-alert">
470
+ Por vencer
471
+ </chip>
472
+
473
+ <!-- Filtro clickeable -->
474
+ <chip
475
+ status="indigo"
476
+ type="outlined"
477
+ rounded="full"
478
+ [clickable]="true"
479
+ (clicked)="filtrar()"
480
+ >
481
+ Electrónica
482
+ </chip>
483
+
484
+ <!-- Notificación de error -->
485
+ <chip status="danger" type="subtle" rounded="default" iconLeft="circle-x">
486
+ Pago rechazado
487
+ </chip>
488
+ ```
489
+
490
+ ## Componente Toggle
491
+
492
+ Componente de interruptor para activar o desactivar estados. Emite el nuevo valor al padre para que maneje la lógica correspondiente.
493
+
494
+ ---
495
+
496
+ ## Instalación
497
+
498
+ ```typescript
499
+ import { Toggle } from 'ten-minds-beta';
500
+
501
+ @Component({
502
+ imports: [Toggle],
503
+ })
504
+ ```
505
+
506
+ ---
507
+
508
+ ## Uso básico
509
+
510
+ ```html
511
+ <toggle [value]="isActive" (changed)="isActive = $event" />
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Inputs y Outputs
517
+
518
+ | Input | Tipo | Default | Descripción |
519
+ |------------|-----------|---------|------------------------------------------|
520
+ | `value` | `boolean` | `false` | Estado actual del toggle |
521
+ | `disabled` | `boolean` | `false` | Desactiva la interacción |
522
+
523
+ | Output | Tipo | Descripción |
524
+ |-----------|-----------|----------------------------------------|
525
+ | `changed` | `boolean` | Emite el nuevo estado al hacer click |
526
+
527
+ ---
528
+
529
+ ## Estados
530
+
531
+ ### Default (apagado)
532
+ ```html
533
+ <toggle [value]="false" (changed)="onChanged($event)" />
534
+ ```
535
+
536
+ ### Active (encendido)
537
+ ```html
538
+ <toggle [value]="true" (changed)="onChanged($event)" />
539
+ ```
540
+
541
+ ### Disabled
542
+ ```html
543
+ <toggle [value]="false" [disabled]="true" />
544
+ <toggle [value]="true" [disabled]="true" />
545
+ ```
546
+
547
+ ---
548
+
549
+ ## Manejo del estado
550
+
551
+ El toggle no guarda estado internamente. El padre es responsable de actualizar el valor:
552
+
553
+ ```typescript
554
+ isActive = false;
555
+
556
+ onChanged(value: boolean) {
557
+ this.isActive = value;
558
+ }
559
+ ```
560
+
561
+ ```html
562
+ <toggle [value]="isActive" (changed)="onChanged($event)" />
563
+ ```
564
+
565
+ O de forma inline:
566
+
567
+ ```html
568
+ <toggle [value]="isActive" (changed)="isActive = $event" />
569
+ ```
570
+
571
+ ---
572
+
573
+ ## Casos de uso reales
574
+
575
+ ### Activar notificaciones
576
+ ```html
577
+ <div class="flex items-center gap-3">
578
+ <toggle [value]="notificaciones" (changed)="notificaciones = $event" />
579
+ <span>Notificaciones</span>
580
+ </div>
581
+
582
+ @if(notificaciones) {
583
+ <p>Recibirás notificaciones de nuevos eventos.</p>
584
+ } @else {
585
+ <p>Las notificaciones están desactivadas.</p>
586
+ }
587
+ ```
588
+
589
+ ### Activar/desactivar un producto
590
+ ```html
591
+ <div class="flex items-center gap-3">
592
+ <toggle [value]="producto.activo" (changed)="toggleProducto($event)" />
593
+ <span>{{ producto.activo ? 'Activo' : 'Inactivo' }}</span>
594
+ </div>
595
+ ```
596
+
597
+ ```typescript
598
+ toggleProducto(value: boolean) {
599
+ this.producto.activo = value;
600
+ this.productoService.actualizarEstado(this.producto.id, value);
601
+ }
602
+ ```
603
+
604
+ ### Con disabled según permisos
605
+ ```html
606
+ <toggle
607
+ [value]="configuracion.habilitado"
608
+ [disabled]="!tienePermisos"
609
+ (changed)="configuracion.habilitado = $event"
610
+ />
611
+ ```
612
+
613
+
614
+ # Checkbox
615
+
616
+ Componente de selección para formularios. Permite marcar o desmarcar una opción individual. Semánticamente correcto y accesible.
617
+
618
+ ---
619
+
620
+ ## Instalación
621
+
622
+ ```typescript
623
+ import { CheckboxButton } from 'ten-minds-beta';
624
+
625
+ @Component({
626
+ imports: [CheckboxButton],
627
+ })
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Uso básico
633
+
634
+ ```html
635
+ <checkbox-button [checked]="isChecked" (changed)="isChecked = $event" />
636
+ ```
637
+
638
+ ---
639
+
640
+ ## Inputs y Outputs
641
+
642
+ | Input | Tipo | Default | Descripción |
643
+ |------------|-----------|---------------|----------------------------------------------------|
644
+ | `checked` | `boolean` | `false` | Estado actual del checkbox |
645
+ | `disabled` | `boolean` | `false` | Desactiva la interacción |
646
+ | `label` | `string` | — | Texto descriptivo al lado del checkbox (opcional) |
647
+ | `id` | `string` | auto-generado | ID del input, útil para asociar labels externos |
648
+
649
+ | Output | Tipo | Descripción |
650
+ |-----------|-----------|--------------------------------------|
651
+ | `changed` | `boolean` | Emite el nuevo estado al hacer click |
652
+
653
+ ---
654
+
655
+ ## Estados
656
+
657
+ ### Default (desmarcado)
658
+ ```html
659
+ <checkbox-button [checked]="false" (changed)="isChecked = $event" />
660
+ ```
661
+
662
+ ### Checked (marcado)
663
+ ```html
664
+ <checkbox-button [checked]="true" (changed)="isChecked = $event" />
665
+ ```
666
+
667
+ ### Disabled desmarcado
668
+ ```html
669
+ <checkbox-button [checked]="false" [disabled]="true" />
670
+ ```
671
+
672
+ ### Disabled marcado
673
+ ```html
674
+ <checkbox-button [checked]="true" [disabled]="true" />
675
+ ```
676
+
677
+ ---
678
+
679
+ ## Con label
680
+
681
+ ```html
682
+ <checkbox-button
683
+ [checked]="isChecked"
684
+ label="Aceptar términos y condiciones"
685
+ (changed)="isChecked = $event"
686
+ />
687
+ ```
688
+
689
+ ---
690
+
691
+ ## Manejo del estado
692
+
693
+ El checkbox no guarda estado internamente. El padre es responsable de actualizar el valor:
694
+
695
+ ```typescript
696
+ isChecked = false;
697
+
698
+ onChanged(value: boolean) {
699
+ this.isChecked = value;
700
+ }
701
+ ```
702
+
703
+ ```html
704
+ <checkbox-button [checked]="isChecked" (changed)="onChanged($event)" />
705
+ ```
706
+
707
+ O de forma inline:
708
+
709
+ ```html
710
+ <checkbox-button [checked]="isChecked" (changed)="isChecked = $event" />
711
+ ```
712
+
713
+ ---
714
+
715
+ ## Casos de uso reales
716
+
717
+ ### Aceptar términos
718
+ ```html
719
+ <checkbox-button
720
+ [checked]="aceptaTerminos"
721
+ label="Acepto los términos y condiciones"
722
+ (changed)="aceptaTerminos = $event"
723
+ />
724
+
725
+ <btn-primary
726
+ [disabled]="!aceptaTerminos"
727
+ type="filled"
728
+ (clicked)="continuar()"
729
+ >
730
+ Continuar
731
+ </btn-primary>
732
+ ```
733
+
734
+ ### Lista de permisos
735
+ ```html
736
+ @for(permiso of permisos; track permiso.id) {
737
+ <checkbox-button
738
+ [checked]="permiso.activo"
739
+ [label]="permiso.nombre"
740
+ (changed)="togglePermiso(permiso.id, $event)"
741
+ />
742
+ }
743
+ ```
744
+
745
+ ```typescript
746
+ togglePermiso(id: string, value: boolean) {
747
+ const permiso = this.permisos.find(p => p.id === id);
748
+ if (permiso) permiso.activo = value;
749
+ }
750
+ ```
751
+
752
+ ### Con disabled según permisos
753
+ ```html
754
+ <checkbox-button
755
+ [checked]="opcion.activa"
756
+ [disabled]="!tienePermisos"
757
+ [label]="opcion.nombre"
758
+ (changed)="opcion.activa = $event"
759
+ />
760
+ ```
761
+
762
+
763
+ # Radio Button
764
+
765
+ Componente de selección única para formularios. Se usa en grupos donde solo una opción puede estar seleccionada a la vez.
766
+
767
+ ---
768
+
769
+ ## Instalación
770
+
771
+ ```typescript
772
+ import { RadioButton } from 'ten-minds-beta';
773
+
774
+ @Component({
775
+ imports: [RadioButton],
776
+ })
777
+ ```
778
+
779
+ ---
780
+
781
+ ## Uso básico
782
+
783
+ ```html
784
+ <radio-button
785
+ value="opcion1"
786
+ name="mi-grupo"
787
+ [checked]="selected === 'opcion1'"
788
+ (changed)="selected = $event"
789
+ />
790
+ ```
791
+
792
+ ---
793
+
794
+ ## Inputs y Outputs
795
+
796
+ | Input | Tipo | Default | Descripción |
797
+ |------------|-----------|-----------------|----------------------------------------------------------|
798
+ | `value` | `string` | requerido | Valor que emite cuando se selecciona |
799
+ | `name` | `string` | `'radio-group'` | Nombre del grupo, debe ser igual en todos los del grupo |
800
+ | `checked` | `boolean` | `false` | Estado actual del radio |
801
+ | `disabled` | `boolean` | `false` | Desactiva la interacción |
802
+ | `label` | `string` | — | Texto descriptivo al lado del radio (opcional) |
803
+ | `id` | `string` | auto-generado | ID del input, útil para asociar labels externos |
804
+
805
+ | Output | Tipo | Descripción |
806
+ |-----------|----------|------------------------------------------|
807
+ | `changed` | `string` | Emite el `value` del radio seleccionado |
808
+
809
+ ---
810
+
811
+ ## Estados
812
+
813
+ ### Default (sin seleccionar)
814
+ ```html
815
+ <radio-button value="opcion1" name="grupo" [checked]="false" (changed)="onChanged($event)" />
816
+ ```
817
+
818
+ ### Checked (seleccionado)
819
+ ```html
820
+ <radio-button value="opcion1" name="grupo" [checked]="true" (changed)="onChanged($event)" />
821
+ ```
822
+
823
+ ### Disabled desmarcado
824
+ ```html
825
+ <radio-button value="opcion1" name="grupo" [checked]="false" [disabled]="true" />
826
+ ```
827
+
828
+ ### Disabled marcado
829
+ ```html
830
+ <radio-button value="opcion1" name="grupo" [checked]="true" [disabled]="true" />
831
+ ```
832
+
833
+ ---
834
+
835
+ ## Uso en grupo
836
+
837
+ 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:
838
+
839
+ ```typescript
840
+ selected = 'mensual';
841
+
842
+ onChanged(value: string) {
843
+ this.selected = value;
844
+ }
845
+ ```
846
+
847
+ ```html
848
+ <div class="flex flex-col gap-3">
849
+ <radio-button
850
+ value="mensual"
851
+ name="plan"
852
+ label="Mensual"
853
+ [checked]="selected === 'mensual'"
854
+ (changed)="onChanged($event)"
855
+ />
856
+ <radio-button
857
+ value="anual"
858
+ name="plan"
859
+ label="Anual"
860
+ [checked]="selected === 'anual'"
861
+ (changed)="onChanged($event)"
862
+ />
863
+ <radio-button
864
+ value="lifetime"
865
+ name="plan"
866
+ label="De por vida"
867
+ [checked]="selected === 'lifetime'"
868
+ (changed)="onChanged($event)"
869
+ />
870
+ </div>
871
+ ```
872
+
873
+ ---
874
+
875
+ ## Casos de uso reales
876
+
877
+ ### Selección de plan
878
+ ```html
879
+ <div class="flex flex-col gap-3">
880
+ @for(plan of planes; track plan.id) {
881
+ <radio-button
882
+ [value]="plan.id"
883
+ name="planes"
884
+ [label]="plan.nombre"
885
+ [checked]="selectedPlan === plan.id"
886
+ (changed)="selectedPlan = $event"
887
+ />
888
+ }
889
+ </div>
890
+ ```
891
+
892
+ ### Con opción desactivada
893
+ ```html
894
+ <radio-button
895
+ value="premium"
896
+ name="plan"
897
+ label="Premium (próximamente)"
898
+ [checked]="false"
899
+ [disabled]="true"
900
+ />
901
+ ```
902
+
903
+ ### Selección de rol en formulario de usuario
904
+ ```html
905
+ <div class="flex flex-col gap-3">
906
+ <radio-button value="admin" name="rol" label="Administrador" [checked]="rol === 'admin'" (changed)="rol = $event" />
907
+ <radio-button value="editor" name="rol" label="Editor" [checked]="rol === 'editor'" (changed)="rol = $event" />
908
+ <radio-button value="viewer" name="rol" label="Solo lectura" [checked]="rol === 'viewer'" (changed)="rol = $event" />
909
+ </div>
910
+ ```
911
+
912
+ ```typescript
913
+ rol = 'viewer';
914
+ ```