@onpe/ui 1.0.26 → 1.0.27

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.
Files changed (2) hide show
  1. package/README.md +1092 -29
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -27,27 +27,101 @@ npm install -g @onpe/ui
27
27
  npx onpe-ui add <componente>
28
28
  ```
29
29
 
30
- ## ⚠️ Importante sobre TailwindCSS
30
+ ## ⚠️ Importante sobre Estilos
31
31
 
32
- Esta librería **NO requiere** que instales TailwindCSS en tu proyecto. Los estilos ya están compilados y optimizados. Solo necesitas importar los estilos:
32
+ ### Opción 1: Librería Completa (Recomendado)
33
+ Esta librería **NO requiere** que instales TailwindCSS en tu proyecto. Los estilos ya están compilados y optimizados. Solo necesitas crear tu propio archivo CSS con las variables personalizadas:
34
+
35
+ #### Crear archivo CSS personalizado
36
+ Crea un archivo llamado `onpe-ui.css` en la raíz de tu proyecto:
33
37
 
34
38
  ```css
35
- @import "@onpe/ui/styles";
39
+ /* Solo definimos nuestras variables y estilos personalizados */
40
+
41
+
42
+
43
+ :root {
44
+ --blue: #003770;
45
+ --skyblue: #0073cf;
46
+ --skyblue-light: #69b2e8;
47
+ --yellow: #ffb81c;
48
+ --light-skyblue: #aaeff6;
49
+ --gray: #bcbcbc;
50
+ --gray-light: #bdbdbd;
51
+ --gray-extra-light: #f2f2f2;
52
+ --red: #e3002b;
53
+ --dark-gray: #4f4f4f;
54
+ --green: #76bd43;
55
+ --yellow-light: #FFF1D2;
56
+ }
57
+
58
+ @theme {
59
+ --color-onpe-ui-blue: var(--blue);
60
+ --color-onpe-ui-skyblue: var(--skyblue);
61
+ --color-onpe-ui-skyblue-light: var(--skyblue-light);
62
+ --color-onpe-ui-yellow: var(--yellow);
63
+ --color-onpe-ui-light-skyblue: var(--light-skyblue);
64
+ --color-onpe-ui-gray: var(--gray);
65
+ --color-onpe-ui-gray-light: var(--gray-light);
66
+ --color-onpe-ui-gray-extra-light: var(--gray-extra-light);
67
+ --color-onpe-ui-red: var(--red);
68
+ --color-onpe-ui-dark-gray: var(--dark-gray);
69
+ --color-onpe-ui-green: var(--green);
70
+ --color-onpe-ui-yellow-light: var(--yellow-light);
71
+ }
72
+
73
+
74
+ button{
75
+ cursor: pointer;
76
+ }
77
+
78
+
79
+ @keyframes fastBlink {
80
+ 0% {
81
+ opacity: 1;
82
+ }
83
+ 25% {
84
+ opacity: 0.8;
85
+ }
86
+ 50% {
87
+ opacity: 0.4;
88
+ }
89
+ 75% {
90
+ opacity: 0.8;
91
+ }
92
+ 100% {
93
+ opacity: 1;
94
+ }
95
+ }
96
+ .fast-blink {
97
+ animation: fastBlink 0.8s ease-in-out infinite;
98
+ }
99
+ ```
100
+
101
+ #### Importar el archivo CSS en tu proyecto
102
+ ```tsx
103
+ // En tu archivo principal (index.tsx, App.tsx, etc.)
104
+ import './onpe-ui.css';
105
+ import { Button } from '@onpe/ui/components';
36
106
  ```
37
107
 
38
- Si quieres extender los estilos con tus propias clases de TailwindCSS, entonces sí necesitarías instalar TailwindCSS en tu proyecto.
108
+ ### Opción 2: Componentes Individuales
109
+ Si instalas componentes individualmente con la CLI, **SÍ necesitas** instalar y configurar TailwindCSS en tu proyecto.
39
110
 
40
111
  ## 📖 Uso Básico
41
112
 
42
113
  ### Opción 1: Usar la Librería Completa
43
114
 
44
115
  #### Importar estilos
45
- ```css
46
- @import "@onpe/ui/styles";
116
+ ```tsx
117
+ // Importar tu archivo CSS personalizado
118
+ import './onpe-ui.css';
119
+ import { Button } from '@onpe/ui/components';
47
120
  ```
48
121
 
49
122
  #### Usar componentes
50
123
  ```tsx
124
+ import './onpe-ui.css';
51
125
  import { Button } from '@onpe/ui/components';
52
126
 
53
127
  function App() {
@@ -85,15 +159,34 @@ npx onpe-ui add show
85
159
  // Después de instalar con CLI
86
160
  import { Button } from './components/ui/Button';
87
161
  import { Modal } from './components/ui/Modal';
162
+ import { useState } from 'react';
88
163
 
89
164
  function App() {
90
165
  const [isOpen, setIsOpen] = useState(false);
91
166
 
92
167
  return (
93
- <div>
94
- <Button color="primary" title="Abrir Modal" onClick={() => setIsOpen(true)} />
95
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
96
- <h2>Contenido del Modal</h2>
168
+ <div className="p-4">
169
+ <Button
170
+ color="primary"
171
+ title="Abrir Modal"
172
+ onClick={() => setIsOpen(true)}
173
+ />
174
+
175
+ <Modal
176
+ isOpen={isOpen}
177
+ onClose={() => setIsOpen(false)}
178
+ closeButton={true}
179
+ overlayColor="blue"
180
+ >
181
+ <div className="p-6">
182
+ <h2 className="text-xl font-bold mb-4">Contenido del Modal</h2>
183
+ <p className="mb-4">Este es un ejemplo de modal con contenido.</p>
184
+ <Button
185
+ color="green"
186
+ title="Cerrar"
187
+ onClick={() => setIsOpen(false)}
188
+ />
189
+ </div>
97
190
  </Modal>
98
191
  </div>
99
192
  );
@@ -236,26 +329,96 @@ src/
236
329
 
237
330
  Botón versátil con múltiples colores y tamaños.
238
331
 
239
- Botón versátil con múltiples variantes y tamaños.
332
+ #### Ejemplo Básico
333
+ ```tsx
334
+ import { Button } from '@onpe/ui/components';
335
+
336
+ function App() {
337
+ return (
338
+ <div className="space-y-4 p-4">
339
+ <h2 className="text-2xl font-bold">Botones ONPE</h2>
340
+
341
+ {/* Colores disponibles */}
342
+ <div className="space-y-2">
343
+ <h3 className="text-lg font-semibold">Colores:</h3>
344
+ <div className="flex flex-wrap gap-2">
345
+ <Button color="primary" title="Primario" />
346
+ <Button color="blue" title="Azul" />
347
+ <Button color="skyblue" title="Sky Blue" />
348
+ <Button color="green" title="Verde" />
349
+ <Button color="yellow" title="Amarillo" />
350
+ <Button color="red" title="Rojo" />
351
+ </div>
352
+ </div>
353
+
354
+ {/* Tamaños */}
355
+ <div className="space-y-2">
356
+ <h3 className="text-lg font-semibold">Tamaños:</h3>
357
+ <div className="flex items-center gap-2">
358
+ <Button color="primary" title="Pequeño" size="small" />
359
+ <Button color="primary" title="Mediano" size="normal" />
360
+ <Button color="primary" title="Grande" size="large" />
361
+ </div>
362
+ </div>
363
+
364
+ {/* Estados */}
365
+ <div className="space-y-2">
366
+ <h3 className="text-lg font-semibold">Estados:</h3>
367
+ <div className="flex gap-2">
368
+ <Button color="primary" title="Normal" />
369
+ <Button color="primary" title="Deshabilitado" disabled />
370
+ </div>
371
+ </div>
372
+ </div>
373
+ );
374
+ }
375
+ ```
240
376
 
377
+ #### Ejemplo con Funcionalidad
241
378
  ```tsx
242
379
  import { Button } from '@onpe/ui/components';
380
+ import { useState } from 'react';
243
381
 
244
- // Colores disponibles
245
- <Button color="primary" title="Primario" />
246
- <Button color="blue" title="Azul" />
247
- <Button color="skyblue" title="Sky Blue" />
248
- <Button color="green" title="Verde" />
249
- <Button color="yellow" title="Amarillo" />
250
- <Button color="red" title="Rojo" />
382
+ function VotingApp() {
383
+ const [voted, setVoted] = useState(false);
384
+ const [loading, setLoading] = useState(false);
251
385
 
252
- // Tamaños
253
- <Button color="primary" title="Pequeño" size="small" />
254
- <Button color="primary" title="Mediano" size="normal" />
255
- <Button color="primary" title="Grande" size="large" />
386
+ const handleVote = async () => {
387
+ setLoading(true);
388
+ // Simular llamada a API
389
+ await new Promise(resolve => setTimeout(resolve, 2000));
390
+ setVoted(true);
391
+ setLoading(false);
392
+ };
256
393
 
257
- // Estados
258
- <Button color="primary" title="Deshabilitado" disabled />
394
+ return (
395
+ <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
396
+ <h2 className="text-xl font-bold mb-4">Sistema de Votación</h2>
397
+
398
+ {!voted ? (
399
+ <div className="space-y-4">
400
+ <p className="text-gray-600">¿Desea votar por esta opción?</p>
401
+ <Button
402
+ color="primary"
403
+ title={loading ? "Procesando..." : "Votar Ahora"}
404
+ onClick={handleVote}
405
+ disabled={loading}
406
+ size="large"
407
+ />
408
+ </div>
409
+ ) : (
410
+ <div className="text-center">
411
+ <p className="text-green-600 font-semibold mb-4">¡Voto registrado exitosamente!</p>
412
+ <Button
413
+ color="green"
414
+ title="Ver Resultados"
415
+ onClick={() => setVoted(false)}
416
+ />
417
+ </div>
418
+ )}
419
+ </div>
420
+ );
421
+ }
259
422
  ```
260
423
 
261
424
  ### Props del Button
@@ -272,17 +435,223 @@ import { Button } from '@onpe/ui/components';
272
435
 
273
436
  Componente modal para mostrar contenido en overlay.
274
437
 
438
+ #### Ejemplo Básico
275
439
  ```tsx
276
440
  import { Modal } from '@onpe/ui/components';
441
+ import { useState } from 'react';
277
442
 
278
443
  function App() {
279
444
  const [isOpen, setIsOpen] = useState(false);
280
445
 
281
446
  return (
282
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
283
- <h2>Contenido del Modal</h2>
284
- <p>Este es el contenido del modal.</p>
285
- </Modal>
447
+ <div className="p-4">
448
+ <button
449
+ onClick={() => setIsOpen(true)}
450
+ className="bg-blue-500 text-white px-4 py-2 rounded"
451
+ >
452
+ Abrir Modal
453
+ </button>
454
+
455
+ <Modal
456
+ isOpen={isOpen}
457
+ onClose={() => setIsOpen(false)}
458
+ closeButton={true}
459
+ overlayColor="blue"
460
+ >
461
+ <div className="p-6">
462
+ <h2 className="text-xl font-bold mb-4">Título del Modal</h2>
463
+ <p className="mb-4">Este es el contenido del modal.</p>
464
+ <button
465
+ onClick={() => setIsOpen(false)}
466
+ className="bg-gray-500 text-white px-4 py-2 rounded"
467
+ >
468
+ Cerrar
469
+ </button>
470
+ </div>
471
+ </Modal>
472
+ </div>
473
+ );
474
+ }
475
+ ```
476
+
477
+ #### Ejemplo Avanzado - Modal de Confirmación
478
+ ```tsx
479
+ import { Modal } from '@onpe/ui/components';
480
+ import { useState } from 'react';
481
+
482
+ function DeleteConfirmation() {
483
+ const [showModal, setShowModal] = useState(false);
484
+ const [itemToDelete, setItemToDelete] = useState('');
485
+
486
+ const handleDelete = (itemName) => {
487
+ setItemToDelete(itemName);
488
+ setShowModal(true);
489
+ };
490
+
491
+ const confirmDelete = () => {
492
+ // Lógica para eliminar el elemento
493
+ console.log(`Eliminando: ${itemToDelete}`);
494
+ setShowModal(false);
495
+ setItemToDelete('');
496
+ };
497
+
498
+ return (
499
+ <div className="p-4">
500
+ <div className="space-y-2">
501
+ <button
502
+ onClick={() => handleDelete('Usuario 1')}
503
+ className="bg-red-500 text-white px-4 py-2 rounded mr-2"
504
+ >
505
+ Eliminar Usuario 1
506
+ </button>
507
+ <button
508
+ onClick={() => handleDelete('Documento 2')}
509
+ className="bg-red-500 text-white px-4 py-2 rounded mr-2"
510
+ >
511
+ Eliminar Documento 2
512
+ </button>
513
+ </div>
514
+
515
+ <Modal
516
+ isOpen={showModal}
517
+ onClose={() => setShowModal(false)}
518
+ closeButton={true}
519
+ overlayColor="red"
520
+ closeDisabled={false}
521
+ >
522
+ <div className="p-6 text-center">
523
+ <div className="mb-4">
524
+ <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
525
+ <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
526
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
527
+ </svg>
528
+ </div>
529
+ <h3 className="text-lg font-medium text-gray-900 mb-2">
530
+ Confirmar Eliminación
531
+ </h3>
532
+ <p className="text-sm text-gray-500">
533
+ ¿Está seguro de que desea eliminar <strong>{itemToDelete}</strong>?
534
+ Esta acción no se puede deshacer.
535
+ </p>
536
+ </div>
537
+
538
+ <div className="flex justify-center space-x-3">
539
+ <button
540
+ onClick={() => setShowModal(false)}
541
+ className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium"
542
+ >
543
+ Cancelar
544
+ </button>
545
+ <button
546
+ onClick={confirmDelete}
547
+ className="bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium"
548
+ >
549
+ Eliminar
550
+ </button>
551
+ </div>
552
+ </div>
553
+ </Modal>
554
+ </div>
555
+ );
556
+ }
557
+ ```
558
+
559
+ #### Ejemplo - Modal de Formulario
560
+ ```tsx
561
+ import { Modal } from '@onpe/ui/components';
562
+ import { useState } from 'react';
563
+
564
+ function UserForm() {
565
+ const [isOpen, setIsOpen] = useState(false);
566
+ const [formData, setFormData] = useState({
567
+ name: '',
568
+ email: '',
569
+ phone: ''
570
+ });
571
+
572
+ const handleSubmit = (e) => {
573
+ e.preventDefault();
574
+ console.log('Datos del formulario:', formData);
575
+ setIsOpen(false);
576
+ setFormData({ name: '', email: '', phone: '' });
577
+ };
578
+
579
+ return (
580
+ <div className="p-4">
581
+ <button
582
+ onClick={() => setIsOpen(true)}
583
+ className="bg-green-500 text-white px-4 py-2 rounded"
584
+ >
585
+ Agregar Usuario
586
+ </button>
587
+
588
+ <Modal
589
+ isOpen={isOpen}
590
+ onClose={() => setIsOpen(false)}
591
+ closeButton={true}
592
+ overlayColor="skyblue"
593
+ >
594
+ <div className="p-6">
595
+ <h2 className="text-xl font-bold mb-4">Nuevo Usuario</h2>
596
+
597
+ <form onSubmit={handleSubmit} className="space-y-4">
598
+ <div>
599
+ <label className="block text-sm font-medium text-gray-700 mb-1">
600
+ Nombre Completo
601
+ </label>
602
+ <input
603
+ type="text"
604
+ value={formData.name}
605
+ onChange={(e) => setFormData({...formData, name: e.target.value})}
606
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
607
+ required
608
+ />
609
+ </div>
610
+
611
+ <div>
612
+ <label className="block text-sm font-medium text-gray-700 mb-1">
613
+ Correo Electrónico
614
+ </label>
615
+ <input
616
+ type="email"
617
+ value={formData.email}
618
+ onChange={(e) => setFormData({...formData, email: e.target.value})}
619
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
620
+ required
621
+ />
622
+ </div>
623
+
624
+ <div>
625
+ <label className="block text-sm font-medium text-gray-700 mb-1">
626
+ Teléfono
627
+ </label>
628
+ <input
629
+ type="tel"
630
+ value={formData.phone}
631
+ onChange={(e) => setFormData({...formData, phone: e.target.value})}
632
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
633
+ />
634
+ </div>
635
+
636
+ <div className="flex justify-end space-x-3 pt-4">
637
+ <button
638
+ type="button"
639
+ onClick={() => setIsOpen(false)}
640
+ className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md"
641
+ >
642
+ Cancelar
643
+ </button>
644
+ <button
645
+ type="submit"
646
+ className="bg-blue-600 text-white px-4 py-2 rounded-md"
647
+ >
648
+ Guardar Usuario
649
+ </button>
650
+ </div>
651
+ </form>
652
+ </div>
653
+ </Modal>
654
+ </div>
286
655
  );
287
656
  }
288
657
  ```
@@ -581,6 +950,453 @@ MIT © ONPE - Oficina Nacional de Procesos Electorales
581
950
  4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
582
951
  5. Abre un Pull Request
583
952
 
953
+ ## 🚀 Ejemplos Completos de Aplicaciones
954
+
955
+ ### Aplicación de Votación Electrónica
956
+ ```tsx
957
+ import { Button, Modal, Show } from '@onpe/ui/components';
958
+ import { useState } from 'react';
959
+
960
+ function VotingApp() {
961
+ const [currentStep, setCurrentStep] = useState(1);
962
+ const [selectedCandidate, setSelectedCandidate] = useState('');
963
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
964
+ const [voted, setVoted] = useState(false);
965
+
966
+ const candidates = [
967
+ { id: '1', name: 'Juan Pérez', party: 'Partido Democrático' },
968
+ { id: '2', name: 'María García', party: 'Partido Progresista' },
969
+ { id: '3', name: 'Carlos López', party: 'Partido Independiente' }
970
+ ];
971
+
972
+ const handleVote = () => {
973
+ setVoted(true);
974
+ setShowConfirmModal(false);
975
+ setCurrentStep(4);
976
+ };
977
+
978
+ return (
979
+ <div className="min-h-screen bg-gray-50 py-8">
980
+ <div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
981
+ <div className="text-center mb-8">
982
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">
983
+ Sistema de Votación ONPE
984
+ </h1>
985
+ <p className="text-gray-600">
986
+ Seleccione su candidato preferido
987
+ </p>
988
+ </div>
989
+
990
+ <Show when={currentStep === 1}>
991
+ <div className="space-y-4">
992
+ <h2 className="text-xl font-semibold mb-4">Candidatos Disponibles:</h2>
993
+ {candidates.map((candidate) => (
994
+ <div
995
+ key={candidate.id}
996
+ className={`p-4 border-2 rounded-lg cursor-pointer transition-colors ${
997
+ selectedCandidate === candidate.id
998
+ ? 'border-blue-500 bg-blue-50'
999
+ : 'border-gray-200 hover:border-gray-300'
1000
+ }`}
1001
+ onClick={() => setSelectedCandidate(candidate.id)}
1002
+ >
1003
+ <div className="flex items-center justify-between">
1004
+ <div>
1005
+ <h3 className="font-semibold text-lg">{candidate.name}</h3>
1006
+ <p className="text-gray-600">{candidate.party}</p>
1007
+ </div>
1008
+ {selectedCandidate === candidate.id && (
1009
+ <div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
1010
+ <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
1011
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
1012
+ </svg>
1013
+ </div>
1014
+ )}
1015
+ </div>
1016
+ </div>
1017
+ ))}
1018
+
1019
+ <div className="pt-4">
1020
+ <Button
1021
+ color="primary"
1022
+ title="Continuar"
1023
+ size="large"
1024
+ disabled={!selectedCandidate}
1025
+ onClick={() => setCurrentStep(2)}
1026
+ />
1027
+ </div>
1028
+ </div>
1029
+ </Show>
1030
+
1031
+ <Show when={currentStep === 2}>
1032
+ <div className="text-center">
1033
+ <h2 className="text-xl font-semibold mb-4">Confirmar Voto</h2>
1034
+ <div className="bg-gray-50 p-4 rounded-lg mb-6">
1035
+ <p className="text-gray-600 mb-2">Ha seleccionado:</p>
1036
+ <p className="font-semibold text-lg">
1037
+ {candidates.find(c => c.id === selectedCandidate)?.name}
1038
+ </p>
1039
+ <p className="text-gray-600">
1040
+ {candidates.find(c => c.id === selectedCandidate)?.party}
1041
+ </p>
1042
+ </div>
1043
+
1044
+ <div className="flex justify-center space-x-4">
1045
+ <Button
1046
+ color="gray"
1047
+ title="Volver"
1048
+ onClick={() => setCurrentStep(1)}
1049
+ />
1050
+ <Button
1051
+ color="primary"
1052
+ title="Confirmar Voto"
1053
+ onClick={() => setShowConfirmModal(true)}
1054
+ />
1055
+ </div>
1056
+ </div>
1057
+ </Show>
1058
+
1059
+ <Show when={currentStep === 4}>
1060
+ <div className="text-center">
1061
+ <div className="mb-6">
1062
+ <div className="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4">
1063
+ <svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1064
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
1065
+ </svg>
1066
+ </div>
1067
+ <h2 className="text-2xl font-bold text-green-600 mb-2">
1068
+ ¡Voto Registrado Exitosamente!
1069
+ </h2>
1070
+ <p className="text-gray-600">
1071
+ Su voto ha sido procesado correctamente. Gracias por participar en la democracia.
1072
+ </p>
1073
+ </div>
1074
+
1075
+ <Button
1076
+ color="green"
1077
+ title="Ver Resultados"
1078
+ size="large"
1079
+ onClick={() => window.location.reload()}
1080
+ />
1081
+ </div>
1082
+ </Show>
1083
+
1084
+ {/* Modal de Confirmación */}
1085
+ <Modal
1086
+ isOpen={showConfirmModal}
1087
+ onClose={() => setShowConfirmModal(false)}
1088
+ closeButton={true}
1089
+ overlayColor="blue"
1090
+ >
1091
+ <div className="p-6 text-center">
1092
+ <div className="mb-4">
1093
+ <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 mb-4">
1094
+ <svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1095
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
1096
+ </svg>
1097
+ </div>
1098
+ <h3 className="text-lg font-medium text-gray-900 mb-2">
1099
+ Confirmar Voto
1100
+ </h3>
1101
+ <p className="text-sm text-gray-500">
1102
+ ¿Está seguro de que desea votar por{' '}
1103
+ <strong>{candidates.find(c => c.id === selectedCandidate)?.name}</strong>?
1104
+ Esta acción no se puede deshacer.
1105
+ </p>
1106
+ </div>
1107
+
1108
+ <div className="flex justify-center space-x-3">
1109
+ <Button
1110
+ color="gray"
1111
+ title="Cancelar"
1112
+ onClick={() => setShowConfirmModal(false)}
1113
+ />
1114
+ <Button
1115
+ color="primary"
1116
+ title="Confirmar Voto"
1117
+ onClick={handleVote}
1118
+ />
1119
+ </div>
1120
+ </div>
1121
+ </Modal>
1122
+ </div>
1123
+ </div>
1124
+ );
1125
+ }
1126
+
1127
+ export default VotingApp;
1128
+ ```
1129
+
1130
+ ### Dashboard de Administración
1131
+ ```tsx
1132
+ import { Button, Modal, Show } from '@onpe/ui/components';
1133
+ import { useState } from 'react';
1134
+
1135
+ function AdminDashboard() {
1136
+ const [activeTab, setActiveTab] = useState('users');
1137
+ const [showUserModal, setShowUserModal] = useState(false);
1138
+ const [users, setUsers] = useState([
1139
+ { id: 1, name: 'Juan Pérez', email: 'juan@onpe.gob.pe', role: 'Admin' },
1140
+ { id: 2, name: 'María García', email: 'maria@onpe.gob.pe', role: 'Usuario' },
1141
+ { id: 3, name: 'Carlos López', email: 'carlos@onpe.gob.pe', role: 'Usuario' }
1142
+ ]);
1143
+
1144
+ const [newUser, setNewUser] = useState({
1145
+ name: '',
1146
+ email: '',
1147
+ role: 'Usuario'
1148
+ });
1149
+
1150
+ const addUser = () => {
1151
+ const user = {
1152
+ id: users.length + 1,
1153
+ ...newUser
1154
+ };
1155
+ setUsers([...users, user]);
1156
+ setNewUser({ name: '', email: '', role: 'Usuario' });
1157
+ setShowUserModal(false);
1158
+ };
1159
+
1160
+ return (
1161
+ <div className="min-h-screen bg-gray-100">
1162
+ {/* Header */}
1163
+ <header className="bg-white shadow-sm border-b">
1164
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
1165
+ <div className="flex justify-between items-center py-4">
1166
+ <div>
1167
+ <h1 className="text-2xl font-bold text-gray-900">Dashboard ONPE</h1>
1168
+ <p className="text-gray-600">Panel de administración del sistema electoral</p>
1169
+ </div>
1170
+ <div className="flex items-center space-x-4">
1171
+ <span className="text-sm text-gray-500">Administrador</span>
1172
+ <Button color="primary" title="Cerrar Sesión" size="small" />
1173
+ </div>
1174
+ </div>
1175
+ </div>
1176
+ </header>
1177
+
1178
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
1179
+ {/* Navigation Tabs */}
1180
+ <div className="mb-8">
1181
+ <nav className="flex space-x-8">
1182
+ <button
1183
+ onClick={() => setActiveTab('users')}
1184
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1185
+ activeTab === 'users'
1186
+ ? 'border-blue-500 text-blue-600'
1187
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1188
+ }`}
1189
+ >
1190
+ Usuarios
1191
+ </button>
1192
+ <button
1193
+ onClick={() => setActiveTab('elections')}
1194
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1195
+ activeTab === 'elections'
1196
+ ? 'border-blue-500 text-blue-600'
1197
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1198
+ }`}
1199
+ >
1200
+ Elecciones
1201
+ </button>
1202
+ <button
1203
+ onClick={() => setActiveTab('reports')}
1204
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1205
+ activeTab === 'reports'
1206
+ ? 'border-blue-500 text-blue-600'
1207
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1208
+ }`}
1209
+ >
1210
+ Reportes
1211
+ </button>
1212
+ </nav>
1213
+ </div>
1214
+
1215
+ {/* Users Tab */}
1216
+ <Show when={activeTab === 'users'}>
1217
+ <div className="bg-white rounded-lg shadow">
1218
+ <div className="px-6 py-4 border-b border-gray-200">
1219
+ <div className="flex justify-between items-center">
1220
+ <h2 className="text-lg font-medium text-gray-900">Gestión de Usuarios</h2>
1221
+ <Button
1222
+ color="green"
1223
+ title="Agregar Usuario"
1224
+ onClick={() => setShowUserModal(true)}
1225
+ />
1226
+ </div>
1227
+ </div>
1228
+
1229
+ <div className="overflow-x-auto">
1230
+ <table className="min-w-full divide-y divide-gray-200">
1231
+ <thead className="bg-gray-50">
1232
+ <tr>
1233
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1234
+ Nombre
1235
+ </th>
1236
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1237
+ Email
1238
+ </th>
1239
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1240
+ Rol
1241
+ </th>
1242
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1243
+ Acciones
1244
+ </th>
1245
+ </tr>
1246
+ </thead>
1247
+ <tbody className="bg-white divide-y divide-gray-200">
1248
+ {users.map((user) => (
1249
+ <tr key={user.id}>
1250
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
1251
+ {user.name}
1252
+ </td>
1253
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
1254
+ {user.email}
1255
+ </td>
1256
+ <td className="px-6 py-4 whitespace-nowrap">
1257
+ <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
1258
+ user.role === 'Admin'
1259
+ ? 'bg-red-100 text-red-800'
1260
+ : 'bg-green-100 text-green-800'
1261
+ }`}>
1262
+ {user.role}
1263
+ </span>
1264
+ </td>
1265
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
1266
+ <div className="flex space-x-2">
1267
+ <Button color="skyblue" title="Editar" size="small" />
1268
+ <Button color="red" title="Eliminar" size="small" />
1269
+ </div>
1270
+ </td>
1271
+ </tr>
1272
+ ))}
1273
+ </tbody>
1274
+ </table>
1275
+ </div>
1276
+ </div>
1277
+ </Show>
1278
+
1279
+ {/* Elections Tab */}
1280
+ <Show when={activeTab === 'elections'}>
1281
+ <div className="bg-white rounded-lg shadow p-6">
1282
+ <h2 className="text-lg font-medium text-gray-900 mb-4">Gestión de Elecciones</h2>
1283
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
1284
+ <div className="bg-blue-50 p-4 rounded-lg">
1285
+ <h3 className="font-semibold text-blue-900">Elecciones Generales</h3>
1286
+ <p className="text-blue-700 text-sm mt-1">14 de abril, 2024</p>
1287
+ <Button color="blue" title="Ver Detalles" size="small" />
1288
+ </div>
1289
+ <div className="bg-green-50 p-4 rounded-lg">
1290
+ <h3 className="font-semibold text-green-900">Elecciones Regionales</h3>
1291
+ <p className="text-green-700 text-sm mt-1">28 de octubre, 2024</p>
1292
+ <Button color="green" title="Ver Detalles" size="small" />
1293
+ </div>
1294
+ <div className="bg-yellow-50 p-4 rounded-lg">
1295
+ <h3 className="font-semibold text-yellow-900">Elecciones Municipales</h3>
1296
+ <p className="text-yellow-700 text-sm mt-1">15 de diciembre, 2024</p>
1297
+ <Button color="yellow" title="Ver Detalles" size="small" />
1298
+ </div>
1299
+ </div>
1300
+ </div>
1301
+ </Show>
1302
+
1303
+ {/* Reports Tab */}
1304
+ <Show when={activeTab === 'reports'}>
1305
+ <div className="bg-white rounded-lg shadow p-6">
1306
+ <h2 className="text-lg font-medium text-gray-900 mb-4">Reportes del Sistema</h2>
1307
+ <div className="space-y-4">
1308
+ <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
1309
+ <div>
1310
+ <h3 className="font-medium">Reporte de Participación Electoral</h3>
1311
+ <p className="text-sm text-gray-600">Generado el 15 de abril, 2024</p>
1312
+ </div>
1313
+ <Button color="primary" title="Descargar PDF" size="small" />
1314
+ </div>
1315
+ <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
1316
+ <div>
1317
+ <h3 className="font-medium">Estadísticas de Usuarios</h3>
1318
+ <p className="text-sm text-gray-600">Generado el 14 de abril, 2024</p>
1319
+ </div>
1320
+ <Button color="primary" title="Descargar PDF" size="small" />
1321
+ </div>
1322
+ </div>
1323
+ </div>
1324
+ </Show>
1325
+
1326
+ {/* Add User Modal */}
1327
+ <Modal
1328
+ isOpen={showUserModal}
1329
+ onClose={() => setShowUserModal(false)}
1330
+ closeButton={true}
1331
+ overlayColor="skyblue"
1332
+ >
1333
+ <div className="p-6">
1334
+ <h2 className="text-xl font-bold mb-4">Agregar Nuevo Usuario</h2>
1335
+
1336
+ <div className="space-y-4">
1337
+ <div>
1338
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1339
+ Nombre Completo
1340
+ </label>
1341
+ <input
1342
+ type="text"
1343
+ value={newUser.name}
1344
+ onChange={(e) => setNewUser({...newUser, name: e.target.value})}
1345
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1346
+ placeholder="Ingrese el nombre completo"
1347
+ />
1348
+ </div>
1349
+
1350
+ <div>
1351
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1352
+ Correo Electrónico
1353
+ </label>
1354
+ <input
1355
+ type="email"
1356
+ value={newUser.email}
1357
+ onChange={(e) => setNewUser({...newUser, email: e.target.value})}
1358
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1359
+ placeholder="usuario@onpe.gob.pe"
1360
+ />
1361
+ </div>
1362
+
1363
+ <div>
1364
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1365
+ Rol
1366
+ </label>
1367
+ <select
1368
+ value={newUser.role}
1369
+ onChange={(e) => setNewUser({...newUser, role: e.target.value})}
1370
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1371
+ >
1372
+ <option value="Usuario">Usuario</option>
1373
+ <option value="Admin">Administrador</option>
1374
+ </select>
1375
+ </div>
1376
+
1377
+ <div className="flex justify-end space-x-3 pt-4">
1378
+ <Button
1379
+ color="gray"
1380
+ title="Cancelar"
1381
+ onClick={() => setShowUserModal(false)}
1382
+ />
1383
+ <Button
1384
+ color="green"
1385
+ title="Agregar Usuario"
1386
+ onClick={addUser}
1387
+ />
1388
+ </div>
1389
+ </div>
1390
+ </div>
1391
+ </Modal>
1392
+ </div>
1393
+ </div>
1394
+ );
1395
+ }
1396
+
1397
+ export default AdminDashboard;
1398
+ ```
1399
+
584
1400
  ## 🐛 Solución de Problemas
585
1401
 
586
1402
  ### Problemas con la CLI
@@ -631,9 +1447,112 @@ npm install @onpe/ui
631
1447
  ```
632
1448
 
633
1449
  **Estilos no se cargan**
634
- ```css
1450
+ ```tsx
635
1451
  /* Verificar que tengas la importación correcta */
1452
+ import './onpe-ui.css';
1453
+ import { Button } from '@onpe/ui/components';
1454
+ ```
1455
+
1456
+ **Solución: Verificar rutas de importación**
1457
+ ```tsx
1458
+ // ✅ CORRECTO: Importar tu archivo CSS personalizado
1459
+ import './onpe-ui.css';
1460
+ import { Button } from '@onpe/ui/components';
1461
+
1462
+ // ❌ INCORRECTO: No importar el archivo CSS
1463
+ import { Button } from '@onpe/ui/components';
1464
+ ```
1465
+
1466
+ **Solución: Verificar configuración de bundler**
1467
+ ```javascript
1468
+ // webpack.config.js
1469
+ module.exports = {
1470
+ resolve: {
1471
+ alias: {
1472
+ '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
1473
+ }
1474
+ }
1475
+ };
1476
+
1477
+ // vite.config.js
1478
+ export default {
1479
+ resolve: {
1480
+ alias: {
1481
+ '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
1482
+ }
1483
+ }
1484
+ };
1485
+ ```
1486
+
1487
+ **Solución: Verificar orden de importación**
1488
+ ```tsx
1489
+ // ✅ CORRECTO - Importar estilos ANTES de los componentes
1490
+ import '@onpe/ui/styles';
1491
+ import { Button } from '@onpe/ui/components';
1492
+
1493
+ // ❌ INCORRECTO - Importar estilos DESPUÉS de los componentes
1494
+ import { Button } from '@onpe/ui/components';
1495
+ import '@onpe/ui/styles';
1496
+ ```
1497
+
1498
+ ### Problemas con Estilos
1499
+
1500
+ **Los componentes se ven sin estilos**
1501
+ ```bash
1502
+ # Verificar que la librería esté instalada
1503
+ npm list @onpe/ui
1504
+
1505
+ # Verificar que los estilos estén en node_modules
1506
+ ls node_modules/@onpe/ui/dist/
1507
+ ```
1508
+
1509
+ **Solución: Verificar importación de estilos**
1510
+ ```tsx
1511
+ // En tu archivo principal (index.tsx o App.tsx)
1512
+ import React from 'react';
1513
+ import ReactDOM from 'react-dom/client';
1514
+ import '@onpe/ui/styles'; // ← IMPORTANTE: Importar estilos primero
1515
+ import App from './App';
1516
+
1517
+ const root = ReactDOM.createRoot(document.getElementById('root'));
1518
+ root.render(<App />);
1519
+ ```
1520
+
1521
+ **Solución: Verificar configuración de CSS**
1522
+ ```css
1523
+ /* En tu archivo CSS principal */
636
1524
  @import "@onpe/ui/styles";
1525
+
1526
+ /* O si usas CSS modules */
1527
+ @import "~@onpe/ui/styles";
1528
+ ```
1529
+
1530
+ **Los colores personalizados no funcionan**
1531
+ ```tsx
1532
+ // Verificar que estés usando las clases correctas
1533
+ <div className="bg-onpe-ui-blue text-white p-4">
1534
+ Contenido con colores ONPE
1535
+ </div>
1536
+
1537
+ // Verificar que los estilos estén importados
1538
+ import '@onpe/ui/styles';
1539
+ ```
1540
+
1541
+ **Solución: Verificar configuración de Tailwind (para componentes individuales)**
1542
+ ```javascript
1543
+ // tailwind.config.js
1544
+ module.exports = {
1545
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
1546
+ theme: {
1547
+ extend: {
1548
+ colors: {
1549
+ 'onpe-ui-blue': '#003770',
1550
+ 'onpe-ui-skyblue': '#0073cf',
1551
+ // ... resto de colores
1552
+ }
1553
+ },
1554
+ },
1555
+ }
637
1556
  ```
638
1557
 
639
1558
  ### Problemas con Storybook
@@ -643,6 +1562,150 @@ npm install @onpe/ui
643
1562
  - Asegurar que Tailwind CSS esté configurado correctamente
644
1563
  - Verificar que PostCSS esté configurado
645
1564
 
1565
+ **Solución: Configuración completa de Storybook**
1566
+ ```typescript
1567
+ // .storybook/main.ts
1568
+ import type { StorybookConfig } from "@storybook/react-vite";
1569
+
1570
+ const config: StorybookConfig = {
1571
+ stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
1572
+ addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
1573
+ framework: {
1574
+ name: "@storybook/react-vite",
1575
+ options: {},
1576
+ },
1577
+ viteFinal: async (config) => {
1578
+ config.css = {
1579
+ ...config.css,
1580
+ postcss: {
1581
+ plugins: [require("tailwindcss"), require("autoprefixer")],
1582
+ },
1583
+ };
1584
+ return config;
1585
+ },
1586
+ };
1587
+
1588
+ export default config;
1589
+ ```
1590
+
1591
+ ```typescript
1592
+ // .storybook/preview.ts
1593
+ import type { Preview } from "@storybook/react";
1594
+ import "../src/styles.css"; // ← Importar estilos
1595
+
1596
+ const preview: Preview = {
1597
+ parameters: {
1598
+ actions: { argTypesRegex: "^on[A-Z].*" },
1599
+ controls: {
1600
+ matchers: {
1601
+ color: /(background|color)$/i,
1602
+ date: /Date$/,
1603
+ },
1604
+ },
1605
+ },
1606
+ };
1607
+
1608
+ export default preview;
1609
+ ```
1610
+
1611
+ ## 🚀 Guía Rápida de Solución
1612
+
1613
+ ### ¿Los componentes no se ven con estilos?
1614
+
1615
+ **Paso 1: Verificar instalación**
1616
+ ```bash
1617
+ npm list @onpe/ui
1618
+ ```
1619
+
1620
+ **Paso 2: Crear archivo CSS personalizado**
1621
+ ```css
1622
+ /* Crear archivo onpe-ui.css en la raíz del proyecto */
1623
+ :root {
1624
+ --blue: #003770;
1625
+ --skyblue: #0073cf;
1626
+ /* ... resto de variables */
1627
+ }
1628
+
1629
+ @theme {
1630
+ --color-onpe-ui-blue: var(--blue);
1631
+ /* ... resto de colores */
1632
+ }
1633
+ ```
1634
+
1635
+ **Paso 3: Importar estilos**
1636
+ ```tsx
1637
+ // En tu archivo principal (index.tsx)
1638
+ import './onpe-ui.css';
1639
+ import { Button } from '@onpe/ui/components';
1640
+ ```
1641
+
1642
+ **Paso 4: Verificar orden de importación**
1643
+ ```tsx
1644
+ // ✅ CORRECTO
1645
+ import './onpe-ui.css';
1646
+ import { Button } from '@onpe/ui/components';
1647
+
1648
+ // ❌ INCORRECTO
1649
+ import { Button } from '@onpe/ui/components';
1650
+ // Falta importar el archivo CSS
1651
+ ```
1652
+
1653
+ ### ¿Los colores no funcionan?
1654
+
1655
+ **Solución: Usar clases correctas**
1656
+ ```tsx
1657
+ // ✅ CORRECTO
1658
+ <div className="bg-onpe-ui-blue text-white p-4">
1659
+ Contenido
1660
+ </div>
1661
+
1662
+ // ❌ INCORRECTO
1663
+ <div className="bg-blue-500 text-white p-4">
1664
+ Contenido
1665
+ </div>
1666
+ ```
1667
+
1668
+ ### ¿Storybook no funciona?
1669
+
1670
+ **Solución: Configuración completa**
1671
+ ```typescript
1672
+ // .storybook/preview.ts
1673
+ import "../src/styles.css";
1674
+
1675
+ // .storybook/main.ts
1676
+ viteFinal: async (config) => {
1677
+ config.css = {
1678
+ ...config.css,
1679
+ postcss: {
1680
+ plugins: [require("tailwindcss"), require("autoprefixer")],
1681
+ },
1682
+ };
1683
+ return config;
1684
+ },
1685
+ ```
1686
+
1687
+ ### ¿CLI no instala componentes?
1688
+
1689
+ **Solución: Verificar comandos**
1690
+ ```bash
1691
+ # ✅ CORRECTO
1692
+ npx onpe-ui add button
1693
+ npx onpe-ui add modal
1694
+
1695
+ # ❌ INCORRECTO
1696
+ npx onpe-ui add Button
1697
+ npx onpe-ui add Modal
1698
+ ```
1699
+
1700
+ ### ¿Portal no funciona?
1701
+
1702
+ **Solución: Agregar elemento HTML**
1703
+ ```html
1704
+ <!-- En public/index.html -->
1705
+ <div id="root"></div>
1706
+ <div id="portal"></div>
1707
+ ```
1708
+
646
1709
  ## 📞 Soporte
647
1710
 
648
1711
  - 📧 Email: desarrollo@onpe.gob.pe
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onpe/ui",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "type": "module",
5
5
  "description": "Librería completa de UI para ONPE - Componentes, Hooks, Utils y Librerías",
6
6
  "main": "dist/index.js",