@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.
- package/README.md +1092 -29
- 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
|
|
30
|
+
## ⚠️ Importante sobre Estilos
|
|
31
31
|
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
46
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
258
|
-
<
|
|
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
|
-
<
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
```
|
|
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
|