@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.
- package/README.md +1034 -29
- 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
|
|
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
|
-
```
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
46
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" />
|
|
315
|
+
function VotingApp() {
|
|
316
|
+
const [voted, setVoted] = useState(false);
|
|
317
|
+
const [loading, setLoading] = useState(false);
|
|
251
318
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
258
|
-
<
|
|
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
|
-
<
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
```
|
|
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
|