@onpe/ui 1.0.25 → 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 +1313 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,31 +13,115 @@ Librería completa de componentes de interfaz de usuario para aplicaciones de la
|
|
|
13
13
|
|
|
14
14
|
## 🚀 Instalación
|
|
15
15
|
|
|
16
|
+
### Instalación Completa de la Librería
|
|
16
17
|
```bash
|
|
17
18
|
npm install @onpe/ui
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
### Instalación Individual de Componentes (CLI)
|
|
22
|
+
```bash
|
|
23
|
+
# Instalar la CLI globalmente
|
|
24
|
+
npm install -g @onpe/ui
|
|
25
|
+
|
|
26
|
+
# O usar directamente con npx
|
|
27
|
+
npx onpe-ui add <componente>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## ⚠️ Importante sobre Estilos
|
|
21
31
|
|
|
22
|
-
|
|
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:
|
|
23
37
|
|
|
24
38
|
```css
|
|
25
|
-
|
|
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
|
+
}
|
|
26
99
|
```
|
|
27
100
|
|
|
28
|
-
|
|
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';
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Opción 2: Componentes Individuales
|
|
109
|
+
Si instalas componentes individualmente con la CLI, **SÍ necesitas** instalar y configurar TailwindCSS en tu proyecto.
|
|
29
110
|
|
|
30
111
|
## 📖 Uso Básico
|
|
31
112
|
|
|
32
|
-
###
|
|
113
|
+
### Opción 1: Usar la Librería Completa
|
|
33
114
|
|
|
34
|
-
|
|
35
|
-
|
|
115
|
+
#### Importar estilos
|
|
116
|
+
```tsx
|
|
117
|
+
// Importar tu archivo CSS personalizado
|
|
118
|
+
import './onpe-ui.css';
|
|
119
|
+
import { Button } from '@onpe/ui/components';
|
|
36
120
|
```
|
|
37
121
|
|
|
38
|
-
|
|
39
|
-
|
|
122
|
+
#### Usar componentes
|
|
40
123
|
```tsx
|
|
124
|
+
import './onpe-ui.css';
|
|
41
125
|
import { Button } from '@onpe/ui/components';
|
|
42
126
|
|
|
43
127
|
function App() {
|
|
@@ -50,6 +134,122 @@ function App() {
|
|
|
50
134
|
}
|
|
51
135
|
```
|
|
52
136
|
|
|
137
|
+
### Opción 2: Instalar Componentes Individualmente con CLI
|
|
138
|
+
|
|
139
|
+
#### Instalar un componente específico
|
|
140
|
+
```bash
|
|
141
|
+
# Instalar Button
|
|
142
|
+
npx onpe-ui add button
|
|
143
|
+
|
|
144
|
+
# Instalar Modal (instala automáticamente Portal y Overlay)
|
|
145
|
+
npx onpe-ui add modal
|
|
146
|
+
|
|
147
|
+
# Instalar Portal
|
|
148
|
+
npx onpe-ui add portal
|
|
149
|
+
|
|
150
|
+
# Instalar Overlay
|
|
151
|
+
npx onpe-ui add overlay
|
|
152
|
+
|
|
153
|
+
# Instalar Show
|
|
154
|
+
npx onpe-ui add show
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Usar componentes instalados individualmente
|
|
158
|
+
```tsx
|
|
159
|
+
// Después de instalar con CLI
|
|
160
|
+
import { Button } from './components/ui/Button';
|
|
161
|
+
import { Modal } from './components/ui/Modal';
|
|
162
|
+
import { useState } from 'react';
|
|
163
|
+
|
|
164
|
+
function App() {
|
|
165
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
166
|
+
|
|
167
|
+
return (
|
|
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>
|
|
190
|
+
</Modal>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Configuración requerida para componentes individuales
|
|
197
|
+
|
|
198
|
+
**1. Instalar Tailwind CSS:**
|
|
199
|
+
```bash
|
|
200
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
201
|
+
npx tailwindcss init -p
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**2. Configurar tailwind.config.js:**
|
|
205
|
+
```javascript
|
|
206
|
+
/** @type {import('tailwindcss').Config} */
|
|
207
|
+
module.exports = {
|
|
208
|
+
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
209
|
+
theme: {
|
|
210
|
+
extend: {
|
|
211
|
+
colors: {
|
|
212
|
+
'onpe-ui-blue': '#003770',
|
|
213
|
+
'onpe-ui-skyblue': '#0073cf',
|
|
214
|
+
'onpe-ui-skyblue-light': '#69b2e8',
|
|
215
|
+
'onpe-ui-yellow': '#ffb81c',
|
|
216
|
+
'onpe-ui-light-skyblue': '#aaeff6',
|
|
217
|
+
'onpe-ui-gray': '#bcbcbc',
|
|
218
|
+
'onpe-ui-gray-light': '#bdbdbd',
|
|
219
|
+
'onpe-ui-gray-extra-light': '#f2f2f2',
|
|
220
|
+
'onpe-ui-red': '#e3002b',
|
|
221
|
+
'onpe-ui-dark-gray': '#4f4f4f',
|
|
222
|
+
'onpe-ui-green': '#76bd43',
|
|
223
|
+
'onpe-ui-yellow-light': '#FFF1D2',
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
plugins: [],
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**3. Agregar estilos base en index.css:**
|
|
232
|
+
```css
|
|
233
|
+
@tailwind base;
|
|
234
|
+
@tailwind components;
|
|
235
|
+
@tailwind utilities;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**4. Para componentes que usan Portal, agregar en public/index.html:**
|
|
239
|
+
```html
|
|
240
|
+
<!DOCTYPE html>
|
|
241
|
+
<html lang="es">
|
|
242
|
+
<head>
|
|
243
|
+
<meta charset="utf-8" />
|
|
244
|
+
<title>Mi App</title>
|
|
245
|
+
</head>
|
|
246
|
+
<body>
|
|
247
|
+
<div id="root"></div>
|
|
248
|
+
<div id="portal"></div>
|
|
249
|
+
</body>
|
|
250
|
+
</html>
|
|
251
|
+
```
|
|
252
|
+
|
|
53
253
|
## 🎨 Paleta de Colores ONPE
|
|
54
254
|
|
|
55
255
|
### Colores Principales
|
|
@@ -69,32 +269,156 @@ function App() {
|
|
|
69
269
|
- **Gray Light**: `#bdbdbd` - Texto terciario
|
|
70
270
|
- **Gray Extra Light**: `#f2f2f2` - Fondos suaves
|
|
71
271
|
|
|
272
|
+
## 🔗 Dependencias entre Componentes
|
|
273
|
+
|
|
274
|
+
### Mapa de Dependencias
|
|
275
|
+
```
|
|
276
|
+
Modal
|
|
277
|
+
├── Portal (requerido)
|
|
278
|
+
├── Overlay (requerido)
|
|
279
|
+
└── IconClose (requerido)
|
|
280
|
+
|
|
281
|
+
Portal
|
|
282
|
+
└── react-dom (createPortal)
|
|
283
|
+
|
|
284
|
+
Overlay
|
|
285
|
+
└── (sin dependencias externas)
|
|
286
|
+
|
|
287
|
+
Button
|
|
288
|
+
└── (sin dependencias externas)
|
|
289
|
+
|
|
290
|
+
Show
|
|
291
|
+
└── (sin dependencias externas)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Instalación Automática de Dependencias
|
|
295
|
+
|
|
296
|
+
**Modal** - Instala automáticamente sus dependencias:
|
|
297
|
+
```bash
|
|
298
|
+
npx onpe-ui add modal
|
|
299
|
+
# Esto instalará automáticamente:
|
|
300
|
+
# - Portal.tsx
|
|
301
|
+
# - Overlay.tsx
|
|
302
|
+
# - IconClose.tsx (si está disponible)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Otros componentes** - Instalación independiente:
|
|
306
|
+
```bash
|
|
307
|
+
npx onpe-ui add button # Sin dependencias
|
|
308
|
+
npx onpe-ui add portal # Sin dependencias
|
|
309
|
+
npx onpe-ui add overlay # Sin dependencias
|
|
310
|
+
npx onpe-ui add show # Sin dependencias
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Estructura de Archivos Después de la Instalación
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
src/
|
|
317
|
+
└── components/
|
|
318
|
+
└── ui/
|
|
319
|
+
├── Button.tsx
|
|
320
|
+
├── Modal.tsx
|
|
321
|
+
├── Overlay.tsx
|
|
322
|
+
├── Portal.tsx
|
|
323
|
+
└── Show.tsx
|
|
324
|
+
```
|
|
325
|
+
|
|
72
326
|
## 🧩 Componentes Disponibles
|
|
73
327
|
|
|
74
328
|
### Button
|
|
75
329
|
|
|
76
330
|
Botón versátil con múltiples colores y tamaños.
|
|
77
331
|
|
|
78
|
-
|
|
332
|
+
#### Ejemplo Básico
|
|
333
|
+
```tsx
|
|
334
|
+
import { Button } from '@onpe/ui/components';
|
|
79
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
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Ejemplo con Funcionalidad
|
|
80
378
|
```tsx
|
|
81
379
|
import { Button } from '@onpe/ui/components';
|
|
380
|
+
import { useState } from 'react';
|
|
82
381
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<Button color="skyblue" title="Sky Blue" />
|
|
87
|
-
<Button color="green" title="Verde" />
|
|
88
|
-
<Button color="yellow" title="Amarillo" />
|
|
89
|
-
<Button color="red" title="Rojo" />
|
|
382
|
+
function VotingApp() {
|
|
383
|
+
const [voted, setVoted] = useState(false);
|
|
384
|
+
const [loading, setLoading] = useState(false);
|
|
90
385
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
};
|
|
95
393
|
|
|
96
|
-
|
|
97
|
-
<
|
|
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
|
+
}
|
|
98
422
|
```
|
|
99
423
|
|
|
100
424
|
### Props del Button
|
|
@@ -111,17 +435,223 @@ import { Button } from '@onpe/ui/components';
|
|
|
111
435
|
|
|
112
436
|
Componente modal para mostrar contenido en overlay.
|
|
113
437
|
|
|
438
|
+
#### Ejemplo Básico
|
|
114
439
|
```tsx
|
|
115
440
|
import { Modal } from '@onpe/ui/components';
|
|
441
|
+
import { useState } from 'react';
|
|
116
442
|
|
|
117
443
|
function App() {
|
|
118
444
|
const [isOpen, setIsOpen] = useState(false);
|
|
119
445
|
|
|
120
446
|
return (
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
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>
|
|
125
655
|
);
|
|
126
656
|
}
|
|
127
657
|
```
|
|
@@ -420,6 +950,762 @@ MIT © ONPE - Oficina Nacional de Procesos Electorales
|
|
|
420
950
|
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
|
|
421
951
|
5. Abre un Pull Request
|
|
422
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
|
+
|
|
1400
|
+
## 🐛 Solución de Problemas
|
|
1401
|
+
|
|
1402
|
+
### Problemas con la CLI
|
|
1403
|
+
|
|
1404
|
+
**Error: "Componente no encontrado"**
|
|
1405
|
+
```bash
|
|
1406
|
+
# Verificar componentes disponibles
|
|
1407
|
+
npx onpe-ui add --help
|
|
1408
|
+
|
|
1409
|
+
# Componentes válidos:
|
|
1410
|
+
# button, modal, overlay, portal, show
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
**Error: "No se pudo descargar el componente"**
|
|
1414
|
+
```bash
|
|
1415
|
+
# Verificar conexión a internet
|
|
1416
|
+
ping github.com
|
|
1417
|
+
|
|
1418
|
+
# Verificar que el repositorio esté disponible
|
|
1419
|
+
curl https://raw.githubusercontent.com/ricardosv46/onpe-ui/main/src/components/Button/Button.tsx
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
**Los estilos no se aplican**
|
|
1423
|
+
```bash
|
|
1424
|
+
# Verificar que Tailwind esté instalado
|
|
1425
|
+
npm list tailwindcss
|
|
1426
|
+
|
|
1427
|
+
# Verificar configuración
|
|
1428
|
+
cat tailwind.config.js
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
**Portal no funciona**
|
|
1432
|
+
```bash
|
|
1433
|
+
# Verificar que tengas el elemento portal en HTML
|
|
1434
|
+
grep -r "id=\"portal\"" public/
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
### Problemas con la Librería Completa
|
|
1438
|
+
|
|
1439
|
+
**Error: "Module not found"**
|
|
1440
|
+
```bash
|
|
1441
|
+
# Verificar instalación
|
|
1442
|
+
npm list @onpe/ui
|
|
1443
|
+
|
|
1444
|
+
# Reinstalar si es necesario
|
|
1445
|
+
npm uninstall @onpe/ui
|
|
1446
|
+
npm install @onpe/ui
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
**Estilos no se cargan**
|
|
1450
|
+
```tsx
|
|
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 */
|
|
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
|
+
}
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
### Problemas con Storybook
|
|
1559
|
+
|
|
1560
|
+
**Error: "Failed to fetch dynamically imported module"**
|
|
1561
|
+
- Verificar que el archivo `preview.ts` importe correctamente `../src/styles.css`
|
|
1562
|
+
- Asegurar que Tailwind CSS esté configurado correctamente
|
|
1563
|
+
- Verificar que PostCSS esté configurado
|
|
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
|
+
|
|
423
1709
|
## 📞 Soporte
|
|
424
1710
|
|
|
425
1711
|
- 📧 Email: desarrollo@onpe.gob.pe
|