@onpe/ui 1.0.26 → 1.0.28

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 +1034 -29
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -27,27 +27,34 @@ 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
+ ### Opción 1: Librería Completa (Recomendado)
32
33
  Esta librería **NO requiere** que instales TailwindCSS en tu proyecto. Los estilos ya están compilados y optimizados. Solo necesitas importar los estilos:
33
34
 
34
- ```css
35
- @import "@onpe/ui/styles";
35
+ ```tsx
36
+ // En tu archivo principal (index.tsx, App.tsx, etc.)
37
+ import '@onpe/ui/styles';
38
+ import { Button } from '@onpe/ui/components';
36
39
  ```
37
40
 
38
- Si quieres extender los estilos con tus propias clases de TailwindCSS, entonces sí necesitarías instalar TailwindCSS en tu proyecto.
41
+ ### Opción 2: Componentes Individuales (CLI)
42
+ Si instalas componentes individualmente con la CLI, **SÍ necesitas** instalar y configurar TailwindCSS en tu proyecto, y crear un archivo CSS con las variables personalizadas.
39
43
 
40
44
  ## 📖 Uso Básico
41
45
 
42
46
  ### Opción 1: Usar la Librería Completa
43
47
 
44
48
  #### Importar estilos
45
- ```css
46
- @import "@onpe/ui/styles";
49
+ ```tsx
50
+ // Importar estilos de la librería
51
+ import '@onpe/ui/styles';
52
+ import { Button } from '@onpe/ui/components';
47
53
  ```
48
54
 
49
55
  #### Usar componentes
50
56
  ```tsx
57
+ import '@onpe/ui/styles';
51
58
  import { Button } from '@onpe/ui/components';
52
59
 
53
60
  function App() {
@@ -85,15 +92,34 @@ npx onpe-ui add show
85
92
  // Después de instalar con CLI
86
93
  import { Button } from './components/ui/Button';
87
94
  import { Modal } from './components/ui/Modal';
95
+ import { useState } from 'react';
88
96
 
89
97
  function App() {
90
98
  const [isOpen, setIsOpen] = useState(false);
91
99
 
92
100
  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>
101
+ <div className="p-4">
102
+ <Button
103
+ color="primary"
104
+ title="Abrir Modal"
105
+ onClick={() => setIsOpen(true)}
106
+ />
107
+
108
+ <Modal
109
+ isOpen={isOpen}
110
+ onClose={() => setIsOpen(false)}
111
+ closeButton={true}
112
+ overlayColor="blue"
113
+ >
114
+ <div className="p-6">
115
+ <h2 className="text-xl font-bold mb-4">Contenido del Modal</h2>
116
+ <p className="mb-4">Este es un ejemplo de modal con contenido.</p>
117
+ <Button
118
+ color="green"
119
+ title="Cerrar"
120
+ onClick={() => setIsOpen(false)}
121
+ />
122
+ </div>
97
123
  </Modal>
98
124
  </div>
99
125
  );
@@ -236,26 +262,96 @@ src/
236
262
 
237
263
  Botón versátil con múltiples colores y tamaños.
238
264
 
239
- Botón versátil con múltiples variantes y tamaños.
265
+ #### Ejemplo Básico
266
+ ```tsx
267
+ import { Button } from '@onpe/ui/components';
240
268
 
269
+ function App() {
270
+ return (
271
+ <div className="space-y-4 p-4">
272
+ <h2 className="text-2xl font-bold">Botones ONPE</h2>
273
+
274
+ {/* Colores disponibles */}
275
+ <div className="space-y-2">
276
+ <h3 className="text-lg font-semibold">Colores:</h3>
277
+ <div className="flex flex-wrap gap-2">
278
+ <Button color="primary" title="Primario" />
279
+ <Button color="blue" title="Azul" />
280
+ <Button color="skyblue" title="Sky Blue" />
281
+ <Button color="green" title="Verde" />
282
+ <Button color="yellow" title="Amarillo" />
283
+ <Button color="red" title="Rojo" />
284
+ </div>
285
+ </div>
286
+
287
+ {/* Tamaños */}
288
+ <div className="space-y-2">
289
+ <h3 className="text-lg font-semibold">Tamaños:</h3>
290
+ <div className="flex items-center gap-2">
291
+ <Button color="primary" title="Pequeño" size="small" />
292
+ <Button color="primary" title="Mediano" size="normal" />
293
+ <Button color="primary" title="Grande" size="large" />
294
+ </div>
295
+ </div>
296
+
297
+ {/* Estados */}
298
+ <div className="space-y-2">
299
+ <h3 className="text-lg font-semibold">Estados:</h3>
300
+ <div className="flex gap-2">
301
+ <Button color="primary" title="Normal" />
302
+ <Button color="primary" title="Deshabilitado" disabled />
303
+ </div>
304
+ </div>
305
+ </div>
306
+ );
307
+ }
308
+ ```
309
+
310
+ #### Ejemplo con Funcionalidad
241
311
  ```tsx
242
312
  import { Button } from '@onpe/ui/components';
313
+ import { useState } from 'react';
243
314
 
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" />
315
+ function VotingApp() {
316
+ const [voted, setVoted] = useState(false);
317
+ const [loading, setLoading] = useState(false);
251
318
 
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" />
319
+ const handleVote = async () => {
320
+ setLoading(true);
321
+ // Simular llamada a API
322
+ await new Promise(resolve => setTimeout(resolve, 2000));
323
+ setVoted(true);
324
+ setLoading(false);
325
+ };
256
326
 
257
- // Estados
258
- <Button color="primary" title="Deshabilitado" disabled />
327
+ return (
328
+ <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
329
+ <h2 className="text-xl font-bold mb-4">Sistema de Votación</h2>
330
+
331
+ {!voted ? (
332
+ <div className="space-y-4">
333
+ <p className="text-gray-600">¿Desea votar por esta opción?</p>
334
+ <Button
335
+ color="primary"
336
+ title={loading ? "Procesando..." : "Votar Ahora"}
337
+ onClick={handleVote}
338
+ disabled={loading}
339
+ size="large"
340
+ />
341
+ </div>
342
+ ) : (
343
+ <div className="text-center">
344
+ <p className="text-green-600 font-semibold mb-4">¡Voto registrado exitosamente!</p>
345
+ <Button
346
+ color="green"
347
+ title="Ver Resultados"
348
+ onClick={() => setVoted(false)}
349
+ />
350
+ </div>
351
+ )}
352
+ </div>
353
+ );
354
+ }
259
355
  ```
260
356
 
261
357
  ### Props del Button
@@ -272,17 +368,223 @@ import { Button } from '@onpe/ui/components';
272
368
 
273
369
  Componente modal para mostrar contenido en overlay.
274
370
 
371
+ #### Ejemplo Básico
275
372
  ```tsx
276
373
  import { Modal } from '@onpe/ui/components';
374
+ import { useState } from 'react';
277
375
 
278
376
  function App() {
279
377
  const [isOpen, setIsOpen] = useState(false);
280
378
 
281
379
  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>
380
+ <div className="p-4">
381
+ <button
382
+ onClick={() => setIsOpen(true)}
383
+ className="bg-blue-500 text-white px-4 py-2 rounded"
384
+ >
385
+ Abrir Modal
386
+ </button>
387
+
388
+ <Modal
389
+ isOpen={isOpen}
390
+ onClose={() => setIsOpen(false)}
391
+ closeButton={true}
392
+ overlayColor="blue"
393
+ >
394
+ <div className="p-6">
395
+ <h2 className="text-xl font-bold mb-4">Título del Modal</h2>
396
+ <p className="mb-4">Este es el contenido del modal.</p>
397
+ <button
398
+ onClick={() => setIsOpen(false)}
399
+ className="bg-gray-500 text-white px-4 py-2 rounded"
400
+ >
401
+ Cerrar
402
+ </button>
403
+ </div>
404
+ </Modal>
405
+ </div>
406
+ );
407
+ }
408
+ ```
409
+
410
+ #### Ejemplo Avanzado - Modal de Confirmación
411
+ ```tsx
412
+ import { Modal } from '@onpe/ui/components';
413
+ import { useState } from 'react';
414
+
415
+ function DeleteConfirmation() {
416
+ const [showModal, setShowModal] = useState(false);
417
+ const [itemToDelete, setItemToDelete] = useState('');
418
+
419
+ const handleDelete = (itemName) => {
420
+ setItemToDelete(itemName);
421
+ setShowModal(true);
422
+ };
423
+
424
+ const confirmDelete = () => {
425
+ // Lógica para eliminar el elemento
426
+ console.log(`Eliminando: ${itemToDelete}`);
427
+ setShowModal(false);
428
+ setItemToDelete('');
429
+ };
430
+
431
+ return (
432
+ <div className="p-4">
433
+ <div className="space-y-2">
434
+ <button
435
+ onClick={() => handleDelete('Usuario 1')}
436
+ className="bg-red-500 text-white px-4 py-2 rounded mr-2"
437
+ >
438
+ Eliminar Usuario 1
439
+ </button>
440
+ <button
441
+ onClick={() => handleDelete('Documento 2')}
442
+ className="bg-red-500 text-white px-4 py-2 rounded mr-2"
443
+ >
444
+ Eliminar Documento 2
445
+ </button>
446
+ </div>
447
+
448
+ <Modal
449
+ isOpen={showModal}
450
+ onClose={() => setShowModal(false)}
451
+ closeButton={true}
452
+ overlayColor="red"
453
+ closeDisabled={false}
454
+ >
455
+ <div className="p-6 text-center">
456
+ <div className="mb-4">
457
+ <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
458
+ <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
459
+ <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" />
460
+ </svg>
461
+ </div>
462
+ <h3 className="text-lg font-medium text-gray-900 mb-2">
463
+ Confirmar Eliminación
464
+ </h3>
465
+ <p className="text-sm text-gray-500">
466
+ ¿Está seguro de que desea eliminar <strong>{itemToDelete}</strong>?
467
+ Esta acción no se puede deshacer.
468
+ </p>
469
+ </div>
470
+
471
+ <div className="flex justify-center space-x-3">
472
+ <button
473
+ onClick={() => setShowModal(false)}
474
+ className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium"
475
+ >
476
+ Cancelar
477
+ </button>
478
+ <button
479
+ onClick={confirmDelete}
480
+ className="bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium"
481
+ >
482
+ Eliminar
483
+ </button>
484
+ </div>
485
+ </div>
486
+ </Modal>
487
+ </div>
488
+ );
489
+ }
490
+ ```
491
+
492
+ #### Ejemplo - Modal de Formulario
493
+ ```tsx
494
+ import { Modal } from '@onpe/ui/components';
495
+ import { useState } from 'react';
496
+
497
+ function UserForm() {
498
+ const [isOpen, setIsOpen] = useState(false);
499
+ const [formData, setFormData] = useState({
500
+ name: '',
501
+ email: '',
502
+ phone: ''
503
+ });
504
+
505
+ const handleSubmit = (e) => {
506
+ e.preventDefault();
507
+ console.log('Datos del formulario:', formData);
508
+ setIsOpen(false);
509
+ setFormData({ name: '', email: '', phone: '' });
510
+ };
511
+
512
+ return (
513
+ <div className="p-4">
514
+ <button
515
+ onClick={() => setIsOpen(true)}
516
+ className="bg-green-500 text-white px-4 py-2 rounded"
517
+ >
518
+ Agregar Usuario
519
+ </button>
520
+
521
+ <Modal
522
+ isOpen={isOpen}
523
+ onClose={() => setIsOpen(false)}
524
+ closeButton={true}
525
+ overlayColor="skyblue"
526
+ >
527
+ <div className="p-6">
528
+ <h2 className="text-xl font-bold mb-4">Nuevo Usuario</h2>
529
+
530
+ <form onSubmit={handleSubmit} className="space-y-4">
531
+ <div>
532
+ <label className="block text-sm font-medium text-gray-700 mb-1">
533
+ Nombre Completo
534
+ </label>
535
+ <input
536
+ type="text"
537
+ value={formData.name}
538
+ onChange={(e) => setFormData({...formData, name: e.target.value})}
539
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
540
+ required
541
+ />
542
+ </div>
543
+
544
+ <div>
545
+ <label className="block text-sm font-medium text-gray-700 mb-1">
546
+ Correo Electrónico
547
+ </label>
548
+ <input
549
+ type="email"
550
+ value={formData.email}
551
+ onChange={(e) => setFormData({...formData, email: e.target.value})}
552
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
553
+ required
554
+ />
555
+ </div>
556
+
557
+ <div>
558
+ <label className="block text-sm font-medium text-gray-700 mb-1">
559
+ Teléfono
560
+ </label>
561
+ <input
562
+ type="tel"
563
+ value={formData.phone}
564
+ onChange={(e) => setFormData({...formData, phone: e.target.value})}
565
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
566
+ />
567
+ </div>
568
+
569
+ <div className="flex justify-end space-x-3 pt-4">
570
+ <button
571
+ type="button"
572
+ onClick={() => setIsOpen(false)}
573
+ className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md"
574
+ >
575
+ Cancelar
576
+ </button>
577
+ <button
578
+ type="submit"
579
+ className="bg-blue-600 text-white px-4 py-2 rounded-md"
580
+ >
581
+ Guardar Usuario
582
+ </button>
583
+ </div>
584
+ </form>
585
+ </div>
586
+ </Modal>
587
+ </div>
286
588
  );
287
589
  }
288
590
  ```
@@ -581,6 +883,453 @@ MIT © ONPE - Oficina Nacional de Procesos Electorales
581
883
  4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
582
884
  5. Abre un Pull Request
583
885
 
886
+ ## 🚀 Ejemplos Completos de Aplicaciones
887
+
888
+ ### Aplicación de Votación Electrónica
889
+ ```tsx
890
+ import { Button, Modal, Show } from '@onpe/ui/components';
891
+ import { useState } from 'react';
892
+
893
+ function VotingApp() {
894
+ const [currentStep, setCurrentStep] = useState(1);
895
+ const [selectedCandidate, setSelectedCandidate] = useState('');
896
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
897
+ const [voted, setVoted] = useState(false);
898
+
899
+ const candidates = [
900
+ { id: '1', name: 'Juan Pérez', party: 'Partido Democrático' },
901
+ { id: '2', name: 'María García', party: 'Partido Progresista' },
902
+ { id: '3', name: 'Carlos López', party: 'Partido Independiente' }
903
+ ];
904
+
905
+ const handleVote = () => {
906
+ setVoted(true);
907
+ setShowConfirmModal(false);
908
+ setCurrentStep(4);
909
+ };
910
+
911
+ return (
912
+ <div className="min-h-screen bg-gray-50 py-8">
913
+ <div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
914
+ <div className="text-center mb-8">
915
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">
916
+ Sistema de Votación ONPE
917
+ </h1>
918
+ <p className="text-gray-600">
919
+ Seleccione su candidato preferido
920
+ </p>
921
+ </div>
922
+
923
+ <Show when={currentStep === 1}>
924
+ <div className="space-y-4">
925
+ <h2 className="text-xl font-semibold mb-4">Candidatos Disponibles:</h2>
926
+ {candidates.map((candidate) => (
927
+ <div
928
+ key={candidate.id}
929
+ className={`p-4 border-2 rounded-lg cursor-pointer transition-colors ${
930
+ selectedCandidate === candidate.id
931
+ ? 'border-blue-500 bg-blue-50'
932
+ : 'border-gray-200 hover:border-gray-300'
933
+ }`}
934
+ onClick={() => setSelectedCandidate(candidate.id)}
935
+ >
936
+ <div className="flex items-center justify-between">
937
+ <div>
938
+ <h3 className="font-semibold text-lg">{candidate.name}</h3>
939
+ <p className="text-gray-600">{candidate.party}</p>
940
+ </div>
941
+ {selectedCandidate === candidate.id && (
942
+ <div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
943
+ <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
944
+ <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" />
945
+ </svg>
946
+ </div>
947
+ )}
948
+ </div>
949
+ </div>
950
+ ))}
951
+
952
+ <div className="pt-4">
953
+ <Button
954
+ color="primary"
955
+ title="Continuar"
956
+ size="large"
957
+ disabled={!selectedCandidate}
958
+ onClick={() => setCurrentStep(2)}
959
+ />
960
+ </div>
961
+ </div>
962
+ </Show>
963
+
964
+ <Show when={currentStep === 2}>
965
+ <div className="text-center">
966
+ <h2 className="text-xl font-semibold mb-4">Confirmar Voto</h2>
967
+ <div className="bg-gray-50 p-4 rounded-lg mb-6">
968
+ <p className="text-gray-600 mb-2">Ha seleccionado:</p>
969
+ <p className="font-semibold text-lg">
970
+ {candidates.find(c => c.id === selectedCandidate)?.name}
971
+ </p>
972
+ <p className="text-gray-600">
973
+ {candidates.find(c => c.id === selectedCandidate)?.party}
974
+ </p>
975
+ </div>
976
+
977
+ <div className="flex justify-center space-x-4">
978
+ <Button
979
+ color="gray"
980
+ title="Volver"
981
+ onClick={() => setCurrentStep(1)}
982
+ />
983
+ <Button
984
+ color="primary"
985
+ title="Confirmar Voto"
986
+ onClick={() => setShowConfirmModal(true)}
987
+ />
988
+ </div>
989
+ </div>
990
+ </Show>
991
+
992
+ <Show when={currentStep === 4}>
993
+ <div className="text-center">
994
+ <div className="mb-6">
995
+ <div className="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4">
996
+ <svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
997
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
998
+ </svg>
999
+ </div>
1000
+ <h2 className="text-2xl font-bold text-green-600 mb-2">
1001
+ ¡Voto Registrado Exitosamente!
1002
+ </h2>
1003
+ <p className="text-gray-600">
1004
+ Su voto ha sido procesado correctamente. Gracias por participar en la democracia.
1005
+ </p>
1006
+ </div>
1007
+
1008
+ <Button
1009
+ color="green"
1010
+ title="Ver Resultados"
1011
+ size="large"
1012
+ onClick={() => window.location.reload()}
1013
+ />
1014
+ </div>
1015
+ </Show>
1016
+
1017
+ {/* Modal de Confirmación */}
1018
+ <Modal
1019
+ isOpen={showConfirmModal}
1020
+ onClose={() => setShowConfirmModal(false)}
1021
+ closeButton={true}
1022
+ overlayColor="blue"
1023
+ >
1024
+ <div className="p-6 text-center">
1025
+ <div className="mb-4">
1026
+ <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 mb-4">
1027
+ <svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1028
+ <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" />
1029
+ </svg>
1030
+ </div>
1031
+ <h3 className="text-lg font-medium text-gray-900 mb-2">
1032
+ Confirmar Voto
1033
+ </h3>
1034
+ <p className="text-sm text-gray-500">
1035
+ ¿Está seguro de que desea votar por{' '}
1036
+ <strong>{candidates.find(c => c.id === selectedCandidate)?.name}</strong>?
1037
+ Esta acción no se puede deshacer.
1038
+ </p>
1039
+ </div>
1040
+
1041
+ <div className="flex justify-center space-x-3">
1042
+ <Button
1043
+ color="gray"
1044
+ title="Cancelar"
1045
+ onClick={() => setShowConfirmModal(false)}
1046
+ />
1047
+ <Button
1048
+ color="primary"
1049
+ title="Confirmar Voto"
1050
+ onClick={handleVote}
1051
+ />
1052
+ </div>
1053
+ </div>
1054
+ </Modal>
1055
+ </div>
1056
+ </div>
1057
+ );
1058
+ }
1059
+
1060
+ export default VotingApp;
1061
+ ```
1062
+
1063
+ ### Dashboard de Administración
1064
+ ```tsx
1065
+ import { Button, Modal, Show } from '@onpe/ui/components';
1066
+ import { useState } from 'react';
1067
+
1068
+ function AdminDashboard() {
1069
+ const [activeTab, setActiveTab] = useState('users');
1070
+ const [showUserModal, setShowUserModal] = useState(false);
1071
+ const [users, setUsers] = useState([
1072
+ { id: 1, name: 'Juan Pérez', email: 'juan@onpe.gob.pe', role: 'Admin' },
1073
+ { id: 2, name: 'María García', email: 'maria@onpe.gob.pe', role: 'Usuario' },
1074
+ { id: 3, name: 'Carlos López', email: 'carlos@onpe.gob.pe', role: 'Usuario' }
1075
+ ]);
1076
+
1077
+ const [newUser, setNewUser] = useState({
1078
+ name: '',
1079
+ email: '',
1080
+ role: 'Usuario'
1081
+ });
1082
+
1083
+ const addUser = () => {
1084
+ const user = {
1085
+ id: users.length + 1,
1086
+ ...newUser
1087
+ };
1088
+ setUsers([...users, user]);
1089
+ setNewUser({ name: '', email: '', role: 'Usuario' });
1090
+ setShowUserModal(false);
1091
+ };
1092
+
1093
+ return (
1094
+ <div className="min-h-screen bg-gray-100">
1095
+ {/* Header */}
1096
+ <header className="bg-white shadow-sm border-b">
1097
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
1098
+ <div className="flex justify-between items-center py-4">
1099
+ <div>
1100
+ <h1 className="text-2xl font-bold text-gray-900">Dashboard ONPE</h1>
1101
+ <p className="text-gray-600">Panel de administración del sistema electoral</p>
1102
+ </div>
1103
+ <div className="flex items-center space-x-4">
1104
+ <span className="text-sm text-gray-500">Administrador</span>
1105
+ <Button color="primary" title="Cerrar Sesión" size="small" />
1106
+ </div>
1107
+ </div>
1108
+ </div>
1109
+ </header>
1110
+
1111
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
1112
+ {/* Navigation Tabs */}
1113
+ <div className="mb-8">
1114
+ <nav className="flex space-x-8">
1115
+ <button
1116
+ onClick={() => setActiveTab('users')}
1117
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1118
+ activeTab === 'users'
1119
+ ? 'border-blue-500 text-blue-600'
1120
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1121
+ }`}
1122
+ >
1123
+ Usuarios
1124
+ </button>
1125
+ <button
1126
+ onClick={() => setActiveTab('elections')}
1127
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1128
+ activeTab === 'elections'
1129
+ ? 'border-blue-500 text-blue-600'
1130
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1131
+ }`}
1132
+ >
1133
+ Elecciones
1134
+ </button>
1135
+ <button
1136
+ onClick={() => setActiveTab('reports')}
1137
+ className={`py-2 px-1 border-b-2 font-medium text-sm ${
1138
+ activeTab === 'reports'
1139
+ ? 'border-blue-500 text-blue-600'
1140
+ : 'border-transparent text-gray-500 hover:text-gray-700'
1141
+ }`}
1142
+ >
1143
+ Reportes
1144
+ </button>
1145
+ </nav>
1146
+ </div>
1147
+
1148
+ {/* Users Tab */}
1149
+ <Show when={activeTab === 'users'}>
1150
+ <div className="bg-white rounded-lg shadow">
1151
+ <div className="px-6 py-4 border-b border-gray-200">
1152
+ <div className="flex justify-between items-center">
1153
+ <h2 className="text-lg font-medium text-gray-900">Gestión de Usuarios</h2>
1154
+ <Button
1155
+ color="green"
1156
+ title="Agregar Usuario"
1157
+ onClick={() => setShowUserModal(true)}
1158
+ />
1159
+ </div>
1160
+ </div>
1161
+
1162
+ <div className="overflow-x-auto">
1163
+ <table className="min-w-full divide-y divide-gray-200">
1164
+ <thead className="bg-gray-50">
1165
+ <tr>
1166
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1167
+ Nombre
1168
+ </th>
1169
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1170
+ Email
1171
+ </th>
1172
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1173
+ Rol
1174
+ </th>
1175
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
1176
+ Acciones
1177
+ </th>
1178
+ </tr>
1179
+ </thead>
1180
+ <tbody className="bg-white divide-y divide-gray-200">
1181
+ {users.map((user) => (
1182
+ <tr key={user.id}>
1183
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
1184
+ {user.name}
1185
+ </td>
1186
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
1187
+ {user.email}
1188
+ </td>
1189
+ <td className="px-6 py-4 whitespace-nowrap">
1190
+ <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
1191
+ user.role === 'Admin'
1192
+ ? 'bg-red-100 text-red-800'
1193
+ : 'bg-green-100 text-green-800'
1194
+ }`}>
1195
+ {user.role}
1196
+ </span>
1197
+ </td>
1198
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
1199
+ <div className="flex space-x-2">
1200
+ <Button color="skyblue" title="Editar" size="small" />
1201
+ <Button color="red" title="Eliminar" size="small" />
1202
+ </div>
1203
+ </td>
1204
+ </tr>
1205
+ ))}
1206
+ </tbody>
1207
+ </table>
1208
+ </div>
1209
+ </div>
1210
+ </Show>
1211
+
1212
+ {/* Elections Tab */}
1213
+ <Show when={activeTab === 'elections'}>
1214
+ <div className="bg-white rounded-lg shadow p-6">
1215
+ <h2 className="text-lg font-medium text-gray-900 mb-4">Gestión de Elecciones</h2>
1216
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
1217
+ <div className="bg-blue-50 p-4 rounded-lg">
1218
+ <h3 className="font-semibold text-blue-900">Elecciones Generales</h3>
1219
+ <p className="text-blue-700 text-sm mt-1">14 de abril, 2024</p>
1220
+ <Button color="blue" title="Ver Detalles" size="small" />
1221
+ </div>
1222
+ <div className="bg-green-50 p-4 rounded-lg">
1223
+ <h3 className="font-semibold text-green-900">Elecciones Regionales</h3>
1224
+ <p className="text-green-700 text-sm mt-1">28 de octubre, 2024</p>
1225
+ <Button color="green" title="Ver Detalles" size="small" />
1226
+ </div>
1227
+ <div className="bg-yellow-50 p-4 rounded-lg">
1228
+ <h3 className="font-semibold text-yellow-900">Elecciones Municipales</h3>
1229
+ <p className="text-yellow-700 text-sm mt-1">15 de diciembre, 2024</p>
1230
+ <Button color="yellow" title="Ver Detalles" size="small" />
1231
+ </div>
1232
+ </div>
1233
+ </div>
1234
+ </Show>
1235
+
1236
+ {/* Reports Tab */}
1237
+ <Show when={activeTab === 'reports'}>
1238
+ <div className="bg-white rounded-lg shadow p-6">
1239
+ <h2 className="text-lg font-medium text-gray-900 mb-4">Reportes del Sistema</h2>
1240
+ <div className="space-y-4">
1241
+ <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
1242
+ <div>
1243
+ <h3 className="font-medium">Reporte de Participación Electoral</h3>
1244
+ <p className="text-sm text-gray-600">Generado el 15 de abril, 2024</p>
1245
+ </div>
1246
+ <Button color="primary" title="Descargar PDF" size="small" />
1247
+ </div>
1248
+ <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
1249
+ <div>
1250
+ <h3 className="font-medium">Estadísticas de Usuarios</h3>
1251
+ <p className="text-sm text-gray-600">Generado el 14 de abril, 2024</p>
1252
+ </div>
1253
+ <Button color="primary" title="Descargar PDF" size="small" />
1254
+ </div>
1255
+ </div>
1256
+ </div>
1257
+ </Show>
1258
+
1259
+ {/* Add User Modal */}
1260
+ <Modal
1261
+ isOpen={showUserModal}
1262
+ onClose={() => setShowUserModal(false)}
1263
+ closeButton={true}
1264
+ overlayColor="skyblue"
1265
+ >
1266
+ <div className="p-6">
1267
+ <h2 className="text-xl font-bold mb-4">Agregar Nuevo Usuario</h2>
1268
+
1269
+ <div className="space-y-4">
1270
+ <div>
1271
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1272
+ Nombre Completo
1273
+ </label>
1274
+ <input
1275
+ type="text"
1276
+ value={newUser.name}
1277
+ onChange={(e) => setNewUser({...newUser, name: e.target.value})}
1278
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1279
+ placeholder="Ingrese el nombre completo"
1280
+ />
1281
+ </div>
1282
+
1283
+ <div>
1284
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1285
+ Correo Electrónico
1286
+ </label>
1287
+ <input
1288
+ type="email"
1289
+ value={newUser.email}
1290
+ onChange={(e) => setNewUser({...newUser, email: e.target.value})}
1291
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1292
+ placeholder="usuario@onpe.gob.pe"
1293
+ />
1294
+ </div>
1295
+
1296
+ <div>
1297
+ <label className="block text-sm font-medium text-gray-700 mb-1">
1298
+ Rol
1299
+ </label>
1300
+ <select
1301
+ value={newUser.role}
1302
+ onChange={(e) => setNewUser({...newUser, role: e.target.value})}
1303
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
1304
+ >
1305
+ <option value="Usuario">Usuario</option>
1306
+ <option value="Admin">Administrador</option>
1307
+ </select>
1308
+ </div>
1309
+
1310
+ <div className="flex justify-end space-x-3 pt-4">
1311
+ <Button
1312
+ color="gray"
1313
+ title="Cancelar"
1314
+ onClick={() => setShowUserModal(false)}
1315
+ />
1316
+ <Button
1317
+ color="green"
1318
+ title="Agregar Usuario"
1319
+ onClick={addUser}
1320
+ />
1321
+ </div>
1322
+ </div>
1323
+ </div>
1324
+ </Modal>
1325
+ </div>
1326
+ </div>
1327
+ );
1328
+ }
1329
+
1330
+ export default AdminDashboard;
1331
+ ```
1332
+
584
1333
  ## 🐛 Solución de Problemas
585
1334
 
586
1335
  ### Problemas con la CLI
@@ -631,9 +1380,112 @@ npm install @onpe/ui
631
1380
  ```
632
1381
 
633
1382
  **Estilos no se cargan**
634
- ```css
1383
+ ```tsx
635
1384
  /* Verificar que tengas la importación correcta */
1385
+ import '@onpe/ui/styles';
1386
+ import { Button } from '@onpe/ui/components';
1387
+ ```
1388
+
1389
+ **Solución: Verificar rutas de importación**
1390
+ ```tsx
1391
+ // ✅ CORRECTO: Importar estilos de la librería
1392
+ import '@onpe/ui/styles';
1393
+ import { Button } from '@onpe/ui/components';
1394
+
1395
+ // ❌ INCORRECTO: No importar los estilos
1396
+ import { Button } from '@onpe/ui/components';
1397
+ ```
1398
+
1399
+ **Solución: Verificar configuración de bundler**
1400
+ ```javascript
1401
+ // webpack.config.js
1402
+ module.exports = {
1403
+ resolve: {
1404
+ alias: {
1405
+ '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
1406
+ }
1407
+ }
1408
+ };
1409
+
1410
+ // vite.config.js
1411
+ export default {
1412
+ resolve: {
1413
+ alias: {
1414
+ '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
1415
+ }
1416
+ }
1417
+ };
1418
+ ```
1419
+
1420
+ **Solución: Verificar orden de importación**
1421
+ ```tsx
1422
+ // ✅ CORRECTO - Importar estilos ANTES de los componentes
1423
+ import '@onpe/ui/styles';
1424
+ import { Button } from '@onpe/ui/components';
1425
+
1426
+ // ❌ INCORRECTO - Importar estilos DESPUÉS de los componentes
1427
+ import { Button } from '@onpe/ui/components';
1428
+ import '@onpe/ui/styles';
1429
+ ```
1430
+
1431
+ ### Problemas con Estilos
1432
+
1433
+ **Los componentes se ven sin estilos**
1434
+ ```bash
1435
+ # Verificar que la librería esté instalada
1436
+ npm list @onpe/ui
1437
+
1438
+ # Verificar que los estilos estén en node_modules
1439
+ ls node_modules/@onpe/ui/dist/
1440
+ ```
1441
+
1442
+ **Solución: Verificar importación de estilos**
1443
+ ```tsx
1444
+ // En tu archivo principal (index.tsx o App.tsx)
1445
+ import React from 'react';
1446
+ import ReactDOM from 'react-dom/client';
1447
+ import '@onpe/ui/styles'; // ← IMPORTANTE: Importar estilos primero
1448
+ import App from './App';
1449
+
1450
+ const root = ReactDOM.createRoot(document.getElementById('root'));
1451
+ root.render(<App />);
1452
+ ```
1453
+
1454
+ **Solución: Verificar configuración de CSS**
1455
+ ```css
1456
+ /* En tu archivo CSS principal */
636
1457
  @import "@onpe/ui/styles";
1458
+
1459
+ /* O si usas CSS modules */
1460
+ @import "~@onpe/ui/styles";
1461
+ ```
1462
+
1463
+ **Los colores personalizados no funcionan**
1464
+ ```tsx
1465
+ // Verificar que estés usando las clases correctas
1466
+ <div className="bg-onpe-ui-blue text-white p-4">
1467
+ Contenido con colores ONPE
1468
+ </div>
1469
+
1470
+ // Verificar que los estilos estén importados
1471
+ import '@onpe/ui/styles';
1472
+ ```
1473
+
1474
+ **Solución: Verificar configuración de Tailwind (para componentes individuales)**
1475
+ ```javascript
1476
+ // tailwind.config.js
1477
+ module.exports = {
1478
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
1479
+ theme: {
1480
+ extend: {
1481
+ colors: {
1482
+ 'onpe-ui-blue': '#003770',
1483
+ 'onpe-ui-skyblue': '#0073cf',
1484
+ // ... resto de colores
1485
+ }
1486
+ },
1487
+ },
1488
+ }
637
1489
  ```
638
1490
 
639
1491
  ### Problemas con Storybook
@@ -643,6 +1495,159 @@ npm install @onpe/ui
643
1495
  - Asegurar que Tailwind CSS esté configurado correctamente
644
1496
  - Verificar que PostCSS esté configurado
645
1497
 
1498
+ **Solución: Configuración completa de Storybook**
1499
+ ```typescript
1500
+ // .storybook/main.ts
1501
+ import type { StorybookConfig } from "@storybook/react-vite";
1502
+
1503
+ const config: StorybookConfig = {
1504
+ stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
1505
+ addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
1506
+ framework: {
1507
+ name: "@storybook/react-vite",
1508
+ options: {},
1509
+ },
1510
+ viteFinal: async (config) => {
1511
+ config.css = {
1512
+ ...config.css,
1513
+ postcss: {
1514
+ plugins: [require("tailwindcss"), require("autoprefixer")],
1515
+ },
1516
+ };
1517
+ return config;
1518
+ },
1519
+ };
1520
+
1521
+ export default config;
1522
+ ```
1523
+
1524
+ ```typescript
1525
+ // .storybook/preview.ts
1526
+ import type { Preview } from "@storybook/react";
1527
+ import "../src/styles.css"; // ← Importar estilos
1528
+
1529
+ const preview: Preview = {
1530
+ parameters: {
1531
+ actions: { argTypesRegex: "^on[A-Z].*" },
1532
+ controls: {
1533
+ matchers: {
1534
+ color: /(background|color)$/i,
1535
+ date: /Date$/,
1536
+ },
1537
+ },
1538
+ },
1539
+ };
1540
+
1541
+ export default preview;
1542
+ ```
1543
+
1544
+ ## 🚀 Guía Rápida de Solución
1545
+
1546
+ ### ¿Los componentes no se ven con estilos?
1547
+
1548
+ **Paso 1: Verificar instalación**
1549
+ ```bash
1550
+ npm list @onpe/ui
1551
+ ```
1552
+
1553
+ **Paso 2: Importar estilos (Librería Completa)**
1554
+ ```tsx
1555
+ // En tu archivo principal (index.tsx)
1556
+ import '@onpe/ui/styles';
1557
+ import { Button } from '@onpe/ui/components';
1558
+ ```
1559
+
1560
+ **Paso 3: Verificar orden de importación**
1561
+ ```tsx
1562
+ // ✅ CORRECTO
1563
+ import '@onpe/ui/styles';
1564
+ import { Button } from '@onpe/ui/components';
1565
+
1566
+ // ❌ INCORRECTO
1567
+ import { Button } from '@onpe/ui/components';
1568
+ // Falta importar los estilos
1569
+ ```
1570
+
1571
+ ### ¿Usas CLI para componentes individuales?
1572
+
1573
+ **Entonces SÍ necesitas configurar TailwindCSS:**
1574
+ ```bash
1575
+ # Instalar TailwindCSS
1576
+ npm install -D tailwindcss postcss autoprefixer
1577
+ npx tailwindcss init -p
1578
+ ```
1579
+
1580
+ **Y crear archivo CSS personalizado:**
1581
+ ```css
1582
+ /* Crear archivo onpe-ui.css */
1583
+ :root {
1584
+ --blue: #003770;
1585
+ --skyblue: #0073cf;
1586
+ /* ... resto de variables */
1587
+ }
1588
+
1589
+ @theme {
1590
+ --color-onpe-ui-blue: var(--blue);
1591
+ /* ... resto de colores */
1592
+ }
1593
+ ```
1594
+
1595
+ ### ¿Los colores no funcionan?
1596
+
1597
+ **Solución: Usar clases correctas**
1598
+ ```tsx
1599
+ // ✅ CORRECTO
1600
+ <div className="bg-onpe-ui-blue text-white p-4">
1601
+ Contenido
1602
+ </div>
1603
+
1604
+ // ❌ INCORRECTO
1605
+ <div className="bg-blue-500 text-white p-4">
1606
+ Contenido
1607
+ </div>
1608
+ ```
1609
+
1610
+ ### ¿Storybook no funciona?
1611
+
1612
+ **Solución: Configuración completa**
1613
+ ```typescript
1614
+ // .storybook/preview.ts
1615
+ import "../src/styles.css";
1616
+
1617
+ // .storybook/main.ts
1618
+ viteFinal: async (config) => {
1619
+ config.css = {
1620
+ ...config.css,
1621
+ postcss: {
1622
+ plugins: [require("tailwindcss"), require("autoprefixer")],
1623
+ },
1624
+ };
1625
+ return config;
1626
+ },
1627
+ ```
1628
+
1629
+ ### ¿CLI no instala componentes?
1630
+
1631
+ **Solución: Verificar comandos**
1632
+ ```bash
1633
+ # ✅ CORRECTO
1634
+ npx onpe-ui add button
1635
+ npx onpe-ui add modal
1636
+
1637
+ # ❌ INCORRECTO
1638
+ npx onpe-ui add Button
1639
+ npx onpe-ui add Modal
1640
+ ```
1641
+
1642
+ ### ¿Portal no funciona?
1643
+
1644
+ **Solución: Agregar elemento HTML**
1645
+ ```html
1646
+ <!-- En public/index.html -->
1647
+ <div id="root"></div>
1648
+ <div id="portal"></div>
1649
+ ```
1650
+
646
1651
  ## 📞 Soporte
647
1652
 
648
1653
  - 📧 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.28",
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",