@onpe/ui 1.3.10 → 1.3.12
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 +1965 -1965
- package/dist/components.css +113 -113
- package/dist/components.esm.css +113 -113
- package/dist/components.esm.js +1 -1
- package/dist/components.js +1 -1
- package/dist/index.css +113 -113
- package/dist/index.esm.css +113 -113
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/icons/Actions/IconMobileNfc/IconMobileNfc.backup.d.ts +0 -4
- package/dist/icons/Actions/IconMobileNfc/IconMobileNfc.d.ts +0 -3
- package/dist/icons/Actions/IconMobileNfc/index.d.ts +0 -3
- package/dist/icons/ONPE/IconDnie1/IconDnie1.d.ts +0 -4
- package/dist/icons/ONPE/IconDnie1/index.d.ts +0 -3
- package/dist/icons/ONPE/IconDnie2/IconDnie2.d.ts +0 -4
- package/dist/icons/ONPE/IconDnie2/index.d.ts +0 -3
- package/dist/icons/ONPE/IconDnie3/IconDnie3.backup.d.ts +0 -4
- package/dist/icons/ONPE/IconDnie3/IconDnie3.d.ts +0 -3
- package/dist/icons/ONPE/IconDnie3/index.d.ts +0 -3
- package/dist/icons/ONPE/IconNfc/IconNfc.backup.d.ts +0 -4
- package/dist/icons/ONPE/IconNfc/IconNfc.d.ts +0 -3
- package/dist/icons/ONPE/IconNfc/index.d.ts +0 -3
- package/dist/icons/ONPE/IconPersonOnpe/IconPersonOnpe.d.ts +0 -3
- package/dist/icons/ONPE/IconPersonOnpe/index.d.ts +0 -2
package/README.md
CHANGED
|
@@ -1,1966 +1,1966 @@
|
|
|
1
|
-
# 🗳️ ONPE UI
|
|
2
|
-
|
|
3
|
-
Librería completa de componentes de interfaz de usuario para aplicaciones de la Oficina Nacional de Procesos Electorales (ONPE) del Perú.
|
|
4
|
-
|
|
5
|
-
## ✨ Características
|
|
6
|
-
|
|
7
|
-
- 🎨 **Colores oficiales de ONPE** - Paleta de colores institucional
|
|
8
|
-
- ⚡ **Tailwind CSS v4** - Framework CSS moderno y eficiente
|
|
9
|
-
- 🔧 **TypeScript** - Tipado estático para mejor desarrollo
|
|
10
|
-
- 📱 **Responsive** - Diseño adaptable a todos los dispositivos
|
|
11
|
-
- 🎯 **Accesible** - Componentes que siguen estándares de accesibilidad
|
|
12
|
-
- 📦 **Tree-shakable** - Solo importa lo que necesitas
|
|
13
|
-
|
|
14
|
-
## 🚀 Instalación
|
|
15
|
-
|
|
16
|
-
### Instalación Completa de la Librería
|
|
17
|
-
```bash
|
|
18
|
-
npm install @onpe/ui
|
|
19
|
-
```
|
|
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
|
-
## 🛡️ Integración Sin Conflictos
|
|
31
|
-
|
|
32
|
-
**Esta librería está diseñada para funcionar perfectamente en proyectos que ya usan Tailwind CSS, Material UI, Shadcn, o cualquier otro framework CSS sin causar conflictos de estilos.**
|
|
33
|
-
|
|
34
|
-
### ✨ Características de Compatibilidad
|
|
35
|
-
|
|
36
|
-
- **Prefijos Únicos**: Todas las clases usan el prefijo `onpe-` para evitar conflictos
|
|
37
|
-
- **CSS Compilado**: Se genera un CSS optimizado y minificado sin `@import` de Tailwind
|
|
38
|
-
- **Variables CSS en `:root`**: Colores con prefijos únicos (`--onpe-ui-blue`, etc.) disponibles globalmente
|
|
39
|
-
- **Sin Reset de Tailwind**: No interfiere con tu configuración existente
|
|
40
|
-
- **Compatible con**: Material UI, Shadcn, Chakra UI, Ant Design, Bootstrap, etc.
|
|
41
|
-
- **CSP Compatible**: Genera archivos CSS externos para cumplir con Content Security Policy
|
|
42
|
-
|
|
43
|
-
### 🚀 Instalación Rápida
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npm install @onpe/ui
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
// Importar estilos compilados (solo una vez en tu app)
|
|
51
|
-
// ⚠️ IMPORTANTE: Esto define las variables CSS en :root
|
|
52
|
-
import '@onpe/ui/dist/index.css';
|
|
53
|
-
|
|
54
|
-
// O usando el export específico
|
|
55
|
-
import '@onpe/ui/css';
|
|
56
|
-
|
|
57
|
-
// Usar componentes
|
|
58
|
-
import { Button } from '@onpe/ui/components';
|
|
59
|
-
|
|
60
|
-
function App() {
|
|
61
|
-
return (
|
|
62
|
-
<div>
|
|
63
|
-
{/* Tu contenido existente con Material UI, Shadcn, etc. */}
|
|
64
|
-
<Button color="blue" title="Botón ONPE" />
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### 🎨 Variables CSS en `:root`
|
|
71
|
-
|
|
72
|
-
**Los colores ONPE se definen automáticamente en `:root` cuando importas el CSS:**
|
|
73
|
-
|
|
74
|
-
```css
|
|
75
|
-
:root {
|
|
76
|
-
--onpe-ui-blue: #003770;
|
|
77
|
-
--onpe-ui-skyblue: #0073cf;
|
|
78
|
-
--onpe-ui-skyblue-light: #69b2e8;
|
|
79
|
-
--onpe-ui-yellow: #ffb81c;
|
|
80
|
-
--onpe-ui-light-skyblue: #aaeff6;
|
|
81
|
-
--onpe-ui-gray: #bcbcbc;
|
|
82
|
-
--onpe-ui-gray-light: #bdbdbd;
|
|
83
|
-
--onpe-ui-gray-extra-light: #f2f2f2;
|
|
84
|
-
--onpe-ui-red: #e3002b;
|
|
85
|
-
--onpe-ui-dark-gray: #4f4f4f;
|
|
86
|
-
--onpe-ui-green: #76bd43;
|
|
87
|
-
--onpe-ui-yellow-light: #FFF1D2;
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Esto permite:**
|
|
92
|
-
- ✅ **Uso directo en CSS del proyecto host**
|
|
93
|
-
- ✅ **Sobrescribir colores si es necesario**
|
|
94
|
-
- ✅ **Compatibilidad con CSP (Content Security Policy)**
|
|
95
|
-
- ✅ **Acceso global a los colores ONPE**
|
|
96
|
-
|
|
97
|
-
### 💡 Uso de Variables CSS en tu Proyecto
|
|
98
|
-
|
|
99
|
-
**Puedes usar los colores ONPE directamente en tu CSS:**
|
|
100
|
-
|
|
101
|
-
```css
|
|
102
|
-
/* En tu archivo CSS del proyecto host */
|
|
103
|
-
.mi-componente {
|
|
104
|
-
color: var(--onpe-ui-blue);
|
|
105
|
-
background: var(--onpe-ui-skyblue-light);
|
|
106
|
-
border: 2px solid var(--onpe-ui-red);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.mi-boton-personalizado {
|
|
110
|
-
background: var(--onpe-ui-green);
|
|
111
|
-
color: white;
|
|
112
|
-
padding: 12px 24px;
|
|
113
|
-
border-radius: 8px;
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Sobrescribir colores si es necesario:**
|
|
118
|
-
|
|
119
|
-
```css
|
|
120
|
-
/* En tu archivo CSS del proyecto host */
|
|
121
|
-
:root {
|
|
122
|
-
/* Cambiar el azul principal */
|
|
123
|
-
--onpe-ui-blue: #1a365d;
|
|
124
|
-
|
|
125
|
-
/* Cambiar el rojo */
|
|
126
|
-
--onpe-ui-red: #c53030;
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
**Uso en componentes React con estilos inline:**
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
function MiComponente() {
|
|
134
|
-
return (
|
|
135
|
-
<div
|
|
136
|
-
style={{
|
|
137
|
-
color: 'var(--onpe-ui-blue)',
|
|
138
|
-
backgroundColor: 'var(--onpe-ui-skyblue-light)',
|
|
139
|
-
padding: '16px',
|
|
140
|
-
borderRadius: '8px'
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
Contenido con colores ONPE
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### 🎯 ¿Cómo Evitamos Conflictos?
|
|
150
|
-
|
|
151
|
-
1. **Prefijos Únicos**: `bg-blue-500` → `onpe-bg-onpe-ui-blue`
|
|
152
|
-
2. **CSS Scoped**: Todos los componentes están aislados
|
|
153
|
-
3. **Variables CSS Aisladas**: `--onpe-ui-blue` en lugar de `--blue`
|
|
154
|
-
4. **Sin Preflight**: No resetea estilos del proyecto host
|
|
155
|
-
|
|
156
|
-
## 📖 Uso Básico
|
|
157
|
-
|
|
158
|
-
### Instalar Componentes con CLI
|
|
159
|
-
|
|
160
|
-
#### Instalar componentes específicos
|
|
161
|
-
```bash
|
|
162
|
-
# Componentes
|
|
163
|
-
npx @onpe/ui add button
|
|
164
|
-
npx @onpe/ui add modal
|
|
165
|
-
npx @onpe/ui add portal
|
|
166
|
-
npx @onpe/ui add overlay
|
|
167
|
-
npx @onpe/ui add show
|
|
168
|
-
|
|
169
|
-
# Iconos
|
|
170
|
-
npx @onpe/ui add icon-close
|
|
171
|
-
npx @onpe/ui add icon-check
|
|
172
|
-
npx @onpe/ui add icon-warning
|
|
173
|
-
npx @onpe/ui add icon-chrome
|
|
174
|
-
npx @onpe/ui add icon-firefox
|
|
175
|
-
npx @onpe/ui add icon-safari
|
|
176
|
-
npx @onpe/ui add icon-edge
|
|
177
|
-
npx @onpe/ui add icon-windows
|
|
178
|
-
npx @onpe/ui add icon-apple
|
|
179
|
-
npx @onpe/ui add icon-android
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Usar componentes instalados individualmente
|
|
183
|
-
```tsx
|
|
184
|
-
// Después de instalar con CLI
|
|
185
|
-
import { Button } from './components/onpe-ui/Button';
|
|
186
|
-
import { Modal } from './components/onpe-ui/Modal';
|
|
187
|
-
import { IconClose } from './components/onpe-icons/IconClose';
|
|
188
|
-
import { useState } from 'react';
|
|
189
|
-
|
|
190
|
-
function App() {
|
|
191
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
192
|
-
|
|
193
|
-
return (
|
|
194
|
-
<div className="p-4">
|
|
195
|
-
<Button
|
|
196
|
-
color="primary"
|
|
197
|
-
title="Abrir Modal"
|
|
198
|
-
onClick={() => setIsOpen(true)}
|
|
199
|
-
/>
|
|
200
|
-
|
|
201
|
-
<Modal
|
|
202
|
-
isOpen={isOpen}
|
|
203
|
-
onClose={() => setIsOpen(false)}
|
|
204
|
-
closeButton={true}
|
|
205
|
-
overlayColor="blue"
|
|
206
|
-
>
|
|
207
|
-
<div className="p-6">
|
|
208
|
-
<h2 className="text-xl font-bold mb-4">Contenido del Modal</h2>
|
|
209
|
-
<p className="mb-4">Este es un ejemplo de modal con contenido.</p>
|
|
210
|
-
<div className="flex items-center gap-2">
|
|
211
|
-
<IconClose className="w-4 h-4" />
|
|
212
|
-
<Button
|
|
213
|
-
color="green"
|
|
214
|
-
title="Cerrar"
|
|
215
|
-
onClick={() => setIsOpen(false)}
|
|
216
|
-
/>
|
|
217
|
-
</div>
|
|
218
|
-
</div>
|
|
219
|
-
</Modal>
|
|
220
|
-
</div>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
#### Configuración requerida para componentes individuales
|
|
226
|
-
|
|
227
|
-
**1. Instalar Tailwind CSS:**
|
|
228
|
-
```bash
|
|
229
|
-
npm install -D tailwindcss postcss autoprefixer
|
|
230
|
-
npx tailwindcss init -p
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
**2. Configurar PostCSS para Tailwind v4:**
|
|
234
|
-
```javascript
|
|
235
|
-
// postcss.config.js
|
|
236
|
-
export default {
|
|
237
|
-
plugins: {
|
|
238
|
-
'@tailwindcss/postcss': {},
|
|
239
|
-
autoprefixer: {},
|
|
240
|
-
},
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**3. Crear archivo CSS con configuración Tailwind v4:**
|
|
245
|
-
```css
|
|
246
|
-
/* onpe-ui.css */
|
|
247
|
-
@import "tailwindcss";
|
|
248
|
-
|
|
249
|
-
@theme {
|
|
250
|
-
--color-onpe-ui-blue: #003770;
|
|
251
|
-
--color-onpe-ui-skyblue: #0073cf;
|
|
252
|
-
--color-onpe-ui-skyblue-light: #69b2e8;
|
|
253
|
-
--color-onpe-ui-yellow: #ffb81c;
|
|
254
|
-
--color-onpe-ui-light-skyblue: #aaeff6;
|
|
255
|
-
--color-onpe-ui-gray: #bcbcbc;
|
|
256
|
-
--color-onpe-ui-gray-light: #bdbdbd;
|
|
257
|
-
--color-onpe-ui-gray-extra-light: #f2f2f2;
|
|
258
|
-
--color-onpe-ui-red: #e3002b;
|
|
259
|
-
--color-onpe-ui-dark-gray: #4f4f4f;
|
|
260
|
-
--color-onpe-ui-green: #76bd43;
|
|
261
|
-
--color-onpe-ui-yellow-light: #FFF1D2;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/* Clases personalizadas ONPE */
|
|
265
|
-
@utility bg-onpe-ui-blue { background-color: var(--color-onpe-ui-blue); }
|
|
266
|
-
@utility text-onpe-ui-blue { color: var(--color-onpe-ui-blue); }
|
|
267
|
-
@utility border-onpe-ui-blue { border-color: var(--color-onpe-ui-blue); }
|
|
268
|
-
/* ... resto de clases personalizadas */
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
**4. Importar el archivo CSS en tu aplicación:**
|
|
272
|
-
```tsx
|
|
273
|
-
// En tu archivo principal (index.tsx o App.tsx)
|
|
274
|
-
import './onpe-ui.css'; // ← IMPORTANTE: Importar primero
|
|
275
|
-
import { Button } from './components/onpe-ui/Button';
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
**5. Para componentes que usan Portal, agregar en public/index.html:**
|
|
279
|
-
```html
|
|
280
|
-
<!DOCTYPE html>
|
|
281
|
-
<html lang="es">
|
|
282
|
-
<head>
|
|
283
|
-
<meta charset="utf-8" />
|
|
284
|
-
<title>Mi App</title>
|
|
285
|
-
</head>
|
|
286
|
-
<body>
|
|
287
|
-
<div id="root"></div>
|
|
288
|
-
<div id="portal"></div>
|
|
289
|
-
</body>
|
|
290
|
-
</html>
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
## 🎨 Paleta de Colores ONPE
|
|
294
|
-
|
|
295
|
-
### Colores Principales
|
|
296
|
-
- **Azul Principal**: `#003770` - Color institucional principal
|
|
297
|
-
- **Sky Blue**: `#0073cf` - Color secundario
|
|
298
|
-
- **Sky Blue Light**: `#69b2e8` - Color claro
|
|
299
|
-
- **Light Sky Blue**: `#aaeff6` - Color muy claro
|
|
300
|
-
|
|
301
|
-
### Colores de Acento
|
|
302
|
-
- **Amarillo**: `#ffb81c` - Para alertas y destacados
|
|
303
|
-
- **Verde**: `#76bd43` - Para éxito y confirmaciones
|
|
304
|
-
- **Rojo**: `#e3002b` - Para errores y advertencias
|
|
305
|
-
|
|
306
|
-
### Escala de Grises
|
|
307
|
-
- **Dark Gray**: `#4f4f4f` - Texto principal
|
|
308
|
-
- **Gray**: `#bcbcbc` - Texto secundario
|
|
309
|
-
- **Gray Light**: `#bdbdbd` - Texto terciario
|
|
310
|
-
- **Gray Extra Light**: `#f2f2f2` - Fondos suaves
|
|
311
|
-
|
|
312
|
-
## 🔗 Dependencias entre Componentes
|
|
313
|
-
|
|
314
|
-
### Mapa de Dependencias
|
|
315
|
-
```
|
|
316
|
-
Modal
|
|
317
|
-
├── Portal (requerido)
|
|
318
|
-
├── Overlay (requerido)
|
|
319
|
-
└── IconClose (requerido)
|
|
320
|
-
|
|
321
|
-
ModalBrowserIncompatible
|
|
322
|
-
├── Modal (requerido)
|
|
323
|
-
├── IconWarning (requerido)
|
|
324
|
-
├── IconChromeColor (requerido)
|
|
325
|
-
├── IconSafariColor (requerido)
|
|
326
|
-
├── IconMozillaColor (requerido)
|
|
327
|
-
└── IconEdgeColor (requerido)
|
|
328
|
-
|
|
329
|
-
ModalSystemIncompatible
|
|
330
|
-
├── Modal (requerido)
|
|
331
|
-
├── IconWarning (requerido)
|
|
332
|
-
├── IconWindow (requerido)
|
|
333
|
-
├── IconAndroid (requerido)
|
|
334
|
-
└── IconApple (requerido)
|
|
335
|
-
|
|
336
|
-
Portal
|
|
337
|
-
└── react-dom (createPortal)
|
|
338
|
-
|
|
339
|
-
Overlay
|
|
340
|
-
└── (sin dependencias externas)
|
|
341
|
-
|
|
342
|
-
Button
|
|
343
|
-
└── (sin dependencias externas)
|
|
344
|
-
|
|
345
|
-
Show
|
|
346
|
-
└── (sin dependencias externas)
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Instalación Automática de Dependencias
|
|
350
|
-
|
|
351
|
-
**Modal** - Instala automáticamente sus dependencias:
|
|
352
|
-
```bash
|
|
353
|
-
npx @onpe/ui add modal
|
|
354
|
-
# Esto instalará automáticamente:
|
|
355
|
-
# - Portal.tsx
|
|
356
|
-
# - Overlay.tsx
|
|
357
|
-
# - IconClose.tsx (si está disponible)
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
**ModalBrowserIncompatible** - Instala automáticamente sus dependencias:
|
|
361
|
-
```bash
|
|
362
|
-
npx @onpe/ui add modal-browser-incompatible
|
|
363
|
-
# Esto instalará automáticamente:
|
|
364
|
-
# - Modal.tsx
|
|
365
|
-
# - IconWarning.tsx
|
|
366
|
-
# - IconChromeColor.tsx
|
|
367
|
-
# - IconSafariColor.tsx
|
|
368
|
-
# - IconMozillaColor.tsx
|
|
369
|
-
# - IconEdgeColor.tsx
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
**ModalSystemIncompatible** - Instala automáticamente sus dependencias:
|
|
373
|
-
```bash
|
|
374
|
-
npx @onpe/ui add modal-system-incompatible
|
|
375
|
-
# Esto instalará automáticamente:
|
|
376
|
-
# - Modal.tsx
|
|
377
|
-
# - IconWarning.tsx
|
|
378
|
-
# - IconWindow.tsx
|
|
379
|
-
# - IconAndroid.tsx
|
|
380
|
-
# - IconApple.tsx
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Otros componentes** - Instalación independiente:
|
|
384
|
-
```bash
|
|
385
|
-
npx @onpe/ui add button # Sin dependencias
|
|
386
|
-
npx @onpe/ui add portal # Sin dependencias
|
|
387
|
-
npx @onpe/ui add overlay # Sin dependencias
|
|
388
|
-
npx @onpe/ui add show # Sin dependencias
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Estructura de Archivos Después de la Instalación
|
|
392
|
-
|
|
393
|
-
```
|
|
394
|
-
src/
|
|
395
|
-
└── components/
|
|
396
|
-
├── ui/ # shadcn/ui (si está instalado)
|
|
397
|
-
│ ├── button.tsx
|
|
398
|
-
│ └── input.tsx
|
|
399
|
-
├── onpe-ui/ # ONPE UI - Componentes
|
|
400
|
-
│ ├── Button.tsx
|
|
401
|
-
│ ├── Modal.tsx
|
|
402
|
-
│ ├── Overlay.tsx
|
|
403
|
-
│ ├── Portal.tsx
|
|
404
|
-
│ └── Show.tsx
|
|
405
|
-
└── onpe-icons/ # ONPE UI - Iconos
|
|
406
|
-
├── IconClose.tsx
|
|
407
|
-
├── IconCheck.tsx
|
|
408
|
-
├── IconChrome.tsx
|
|
409
|
-
├── IconFirefox.tsx
|
|
410
|
-
└── IconWindows.tsx
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
## 🧩 Componentes Disponibles
|
|
414
|
-
|
|
415
|
-
### Button
|
|
416
|
-
|
|
417
|
-
Botón versátil con múltiples colores y tamaños.
|
|
418
|
-
|
|
419
|
-
#### Ejemplo Básico
|
|
420
|
-
```tsx
|
|
421
|
-
import { Button } from '@onpe/ui/components';
|
|
422
|
-
|
|
423
|
-
function App() {
|
|
424
|
-
return (
|
|
425
|
-
<div className="space-y-4 p-4">
|
|
426
|
-
<h2 className="text-2xl font-bold">Botones ONPE</h2>
|
|
427
|
-
|
|
428
|
-
{/* Colores disponibles */}
|
|
429
|
-
<div className="space-y-2">
|
|
430
|
-
<h3 className="text-lg font-semibold">Colores:</h3>
|
|
431
|
-
<div className="flex flex-wrap gap-2">
|
|
432
|
-
<Button color="primary" title="Primario" />
|
|
433
|
-
<Button color="blue" title="Azul" />
|
|
434
|
-
<Button color="skyblue" title="Sky Blue" />
|
|
435
|
-
<Button color="green" title="Verde" />
|
|
436
|
-
<Button color="yellow" title="Amarillo" />
|
|
437
|
-
<Button color="red" title="Rojo" />
|
|
438
|
-
</div>
|
|
439
|
-
</div>
|
|
440
|
-
|
|
441
|
-
{/* Tamaños */}
|
|
442
|
-
<div className="space-y-2">
|
|
443
|
-
<h3 className="text-lg font-semibold">Tamaños:</h3>
|
|
444
|
-
<div className="flex items-center gap-2">
|
|
445
|
-
<Button color="primary" title="Pequeño" size="small" />
|
|
446
|
-
<Button color="primary" title="Mediano" size="normal" />
|
|
447
|
-
<Button color="primary" title="Grande" size="large" />
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
|
|
451
|
-
{/* Estados */}
|
|
452
|
-
<div className="space-y-2">
|
|
453
|
-
<h3 className="text-lg font-semibold">Estados:</h3>
|
|
454
|
-
<div className="flex gap-2">
|
|
455
|
-
<Button color="primary" title="Normal" />
|
|
456
|
-
<Button color="primary" title="Deshabilitado" disabled />
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### Ejemplo con Funcionalidad
|
|
465
|
-
```tsx
|
|
466
|
-
import { Button } from '@onpe/ui/components';
|
|
467
|
-
import { useState } from 'react';
|
|
468
|
-
|
|
469
|
-
function VotingApp() {
|
|
470
|
-
const [voted, setVoted] = useState(false);
|
|
471
|
-
const [loading, setLoading] = useState(false);
|
|
472
|
-
|
|
473
|
-
const handleVote = async () => {
|
|
474
|
-
setLoading(true);
|
|
475
|
-
// Simular llamada a API
|
|
476
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
477
|
-
setVoted(true);
|
|
478
|
-
setLoading(false);
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
return (
|
|
482
|
-
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
|
|
483
|
-
<h2 className="text-xl font-bold mb-4">Sistema de Votación</h2>
|
|
484
|
-
|
|
485
|
-
{!voted ? (
|
|
486
|
-
<div className="space-y-4">
|
|
487
|
-
<p className="text-gray-600">¿Desea votar por esta opción?</p>
|
|
488
|
-
<Button
|
|
489
|
-
color="primary"
|
|
490
|
-
title={loading ? "Procesando..." : "Votar Ahora"}
|
|
491
|
-
onClick={handleVote}
|
|
492
|
-
disabled={loading}
|
|
493
|
-
size="large"
|
|
494
|
-
/>
|
|
495
|
-
</div>
|
|
496
|
-
) : (
|
|
497
|
-
<div className="text-center">
|
|
498
|
-
<p className="text-green-600 font-semibold mb-4">¡Voto registrado exitosamente!</p>
|
|
499
|
-
<Button
|
|
500
|
-
color="green"
|
|
501
|
-
title="Ver Resultados"
|
|
502
|
-
onClick={() => setVoted(false)}
|
|
503
|
-
/>
|
|
504
|
-
</div>
|
|
505
|
-
)}
|
|
506
|
-
</div>
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Props del Button
|
|
512
|
-
|
|
513
|
-
| Prop | Tipo | Default | Descripción |
|
|
514
|
-
|------|------|---------|-------------|
|
|
515
|
-
| `color` | `'primary' \| 'blue' \| 'skyblue' \| 'skyblue-light' \| 'yellow' \| 'light-skyblue' \| 'gray' \| 'gray-light' \| 'gray-extra-light' \| 'red' \| 'dark-gray' \| 'green' \| 'yellow-light'` | `'primary'` | Color del botón |
|
|
516
|
-
| `title` | `string` | - | Texto del botón (requerido) |
|
|
517
|
-
| `size` | `'small' \| 'normal' \| 'large'` | `'normal'` | Tamaño del botón |
|
|
518
|
-
| `disabled` | `boolean` | `false` | Estado deshabilitado |
|
|
519
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
520
|
-
|
|
521
|
-
### Modal
|
|
522
|
-
|
|
523
|
-
Componente modal para mostrar contenido en overlay.
|
|
524
|
-
|
|
525
|
-
#### Ejemplo Básico
|
|
526
|
-
```tsx
|
|
527
|
-
import { Modal } from '@onpe/ui/components';
|
|
528
|
-
import { useState } from 'react';
|
|
529
|
-
|
|
530
|
-
function App() {
|
|
531
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
532
|
-
|
|
533
|
-
return (
|
|
534
|
-
<div className="p-4">
|
|
535
|
-
<button
|
|
536
|
-
onClick={() => setIsOpen(true)}
|
|
537
|
-
className="bg-blue-500 text-white px-4 py-2 rounded"
|
|
538
|
-
>
|
|
539
|
-
Abrir Modal
|
|
540
|
-
</button>
|
|
541
|
-
|
|
542
|
-
<Modal
|
|
543
|
-
isOpen={isOpen}
|
|
544
|
-
onClose={() => setIsOpen(false)}
|
|
545
|
-
closeButton={true}
|
|
546
|
-
overlayColor="blue"
|
|
547
|
-
>
|
|
548
|
-
<div className="p-6">
|
|
549
|
-
<h2 className="text-xl font-bold mb-4">Título del Modal</h2>
|
|
550
|
-
<p className="mb-4">Este es el contenido del modal.</p>
|
|
551
|
-
<button
|
|
552
|
-
onClick={() => setIsOpen(false)}
|
|
553
|
-
className="bg-gray-500 text-white px-4 py-2 rounded"
|
|
554
|
-
>
|
|
555
|
-
Cerrar
|
|
556
|
-
</button>
|
|
557
|
-
</div>
|
|
558
|
-
</Modal>
|
|
559
|
-
</div>
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
#### Ejemplo Avanzado - Modal de Confirmación
|
|
565
|
-
```tsx
|
|
566
|
-
import { Modal } from '@onpe/ui/components';
|
|
567
|
-
import { useState } from 'react';
|
|
568
|
-
|
|
569
|
-
function DeleteConfirmation() {
|
|
570
|
-
const [showModal, setShowModal] = useState(false);
|
|
571
|
-
const [itemToDelete, setItemToDelete] = useState('');
|
|
572
|
-
|
|
573
|
-
const handleDelete = (itemName) => {
|
|
574
|
-
setItemToDelete(itemName);
|
|
575
|
-
setShowModal(true);
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
const confirmDelete = () => {
|
|
579
|
-
// Lógica para eliminar el elemento
|
|
580
|
-
console.log(`Eliminando: ${itemToDelete}`);
|
|
581
|
-
setShowModal(false);
|
|
582
|
-
setItemToDelete('');
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
return (
|
|
586
|
-
<div className="p-4">
|
|
587
|
-
<div className="space-y-2">
|
|
588
|
-
<button
|
|
589
|
-
onClick={() => handleDelete('Usuario 1')}
|
|
590
|
-
className="bg-red-500 text-white px-4 py-2 rounded mr-2"
|
|
591
|
-
>
|
|
592
|
-
Eliminar Usuario 1
|
|
593
|
-
</button>
|
|
594
|
-
<button
|
|
595
|
-
onClick={() => handleDelete('Documento 2')}
|
|
596
|
-
className="bg-red-500 text-white px-4 py-2 rounded mr-2"
|
|
597
|
-
>
|
|
598
|
-
Eliminar Documento 2
|
|
599
|
-
</button>
|
|
600
|
-
</div>
|
|
601
|
-
|
|
602
|
-
<Modal
|
|
603
|
-
isOpen={showModal}
|
|
604
|
-
onClose={() => setShowModal(false)}
|
|
605
|
-
closeButton={true}
|
|
606
|
-
overlayColor="red"
|
|
607
|
-
closeDisabled={false}
|
|
608
|
-
>
|
|
609
|
-
<div className="p-6 text-center">
|
|
610
|
-
<div className="mb-4">
|
|
611
|
-
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
|
|
612
|
-
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
613
|
-
<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" />
|
|
614
|
-
</svg>
|
|
615
|
-
</div>
|
|
616
|
-
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
617
|
-
Confirmar Eliminación
|
|
618
|
-
</h3>
|
|
619
|
-
<p className="text-sm text-gray-500">
|
|
620
|
-
¿Está seguro de que desea eliminar <strong>{itemToDelete}</strong>?
|
|
621
|
-
Esta acción no se puede deshacer.
|
|
622
|
-
</p>
|
|
623
|
-
</div>
|
|
624
|
-
|
|
625
|
-
<div className="flex justify-center space-x-3">
|
|
626
|
-
<button
|
|
627
|
-
onClick={() => setShowModal(false)}
|
|
628
|
-
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium"
|
|
629
|
-
>
|
|
630
|
-
Cancelar
|
|
631
|
-
</button>
|
|
632
|
-
<button
|
|
633
|
-
onClick={confirmDelete}
|
|
634
|
-
className="bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium"
|
|
635
|
-
>
|
|
636
|
-
Eliminar
|
|
637
|
-
</button>
|
|
638
|
-
</div>
|
|
639
|
-
</div>
|
|
640
|
-
</Modal>
|
|
641
|
-
</div>
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
#### Ejemplo - Modal de Formulario
|
|
647
|
-
```tsx
|
|
648
|
-
import { Modal } from '@onpe/ui/components';
|
|
649
|
-
import { useState } from 'react';
|
|
650
|
-
|
|
651
|
-
function UserForm() {
|
|
652
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
653
|
-
const [formData, setFormData] = useState({
|
|
654
|
-
name: '',
|
|
655
|
-
email: '',
|
|
656
|
-
phone: ''
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
const handleSubmit = (e) => {
|
|
660
|
-
e.preventDefault();
|
|
661
|
-
console.log('Datos del formulario:', formData);
|
|
662
|
-
setIsOpen(false);
|
|
663
|
-
setFormData({ name: '', email: '', phone: '' });
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
return (
|
|
667
|
-
<div className="p-4">
|
|
668
|
-
<button
|
|
669
|
-
onClick={() => setIsOpen(true)}
|
|
670
|
-
className="bg-green-500 text-white px-4 py-2 rounded"
|
|
671
|
-
>
|
|
672
|
-
Agregar Usuario
|
|
673
|
-
</button>
|
|
674
|
-
|
|
675
|
-
<Modal
|
|
676
|
-
isOpen={isOpen}
|
|
677
|
-
onClose={() => setIsOpen(false)}
|
|
678
|
-
closeButton={true}
|
|
679
|
-
overlayColor="skyblue"
|
|
680
|
-
>
|
|
681
|
-
<div className="p-6">
|
|
682
|
-
<h2 className="text-xl font-bold mb-4">Nuevo Usuario</h2>
|
|
683
|
-
|
|
684
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
685
|
-
<div>
|
|
686
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
687
|
-
Nombre Completo
|
|
688
|
-
</label>
|
|
689
|
-
<input
|
|
690
|
-
type="text"
|
|
691
|
-
value={formData.name}
|
|
692
|
-
onChange={(e) => setFormData({...formData, name: e.target.value})}
|
|
693
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
694
|
-
required
|
|
695
|
-
/>
|
|
696
|
-
</div>
|
|
697
|
-
|
|
698
|
-
<div>
|
|
699
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
700
|
-
Correo Electrónico
|
|
701
|
-
</label>
|
|
702
|
-
<input
|
|
703
|
-
type="email"
|
|
704
|
-
value={formData.email}
|
|
705
|
-
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
|
706
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
707
|
-
required
|
|
708
|
-
/>
|
|
709
|
-
</div>
|
|
710
|
-
|
|
711
|
-
<div>
|
|
712
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
713
|
-
Teléfono
|
|
714
|
-
</label>
|
|
715
|
-
<input
|
|
716
|
-
type="tel"
|
|
717
|
-
value={formData.phone}
|
|
718
|
-
onChange={(e) => setFormData({...formData, phone: e.target.value})}
|
|
719
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
720
|
-
/>
|
|
721
|
-
</div>
|
|
722
|
-
|
|
723
|
-
<div className="flex justify-end space-x-3 pt-4">
|
|
724
|
-
<button
|
|
725
|
-
type="button"
|
|
726
|
-
onClick={() => setIsOpen(false)}
|
|
727
|
-
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md"
|
|
728
|
-
>
|
|
729
|
-
Cancelar
|
|
730
|
-
</button>
|
|
731
|
-
<button
|
|
732
|
-
type="submit"
|
|
733
|
-
className="bg-blue-600 text-white px-4 py-2 rounded-md"
|
|
734
|
-
>
|
|
735
|
-
Guardar Usuario
|
|
736
|
-
</button>
|
|
737
|
-
</div>
|
|
738
|
-
</form>
|
|
739
|
-
</div>
|
|
740
|
-
</Modal>
|
|
741
|
-
</div>
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
### Overlay
|
|
747
|
-
|
|
748
|
-
Componente overlay para superponer contenido.
|
|
749
|
-
|
|
750
|
-
```tsx
|
|
751
|
-
import { Overlay } from '@onpe/ui/components';
|
|
752
|
-
|
|
753
|
-
function App() {
|
|
754
|
-
return (
|
|
755
|
-
<Overlay>
|
|
756
|
-
<div>Contenido superpuesto</div>
|
|
757
|
-
</Overlay>
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
### Portal
|
|
763
|
-
|
|
764
|
-
Componente portal para renderizar fuera del DOM padre.
|
|
765
|
-
|
|
766
|
-
```tsx
|
|
767
|
-
import { Portal } from '@onpe/ui/components';
|
|
768
|
-
|
|
769
|
-
function App() {
|
|
770
|
-
return (
|
|
771
|
-
<Portal>
|
|
772
|
-
<div>Contenido renderizado en portal</div>
|
|
773
|
-
</Portal>
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
### Show
|
|
779
|
-
|
|
780
|
-
Componente condicional para mostrar/ocultar contenido.
|
|
781
|
-
|
|
782
|
-
```tsx
|
|
783
|
-
import { Show } from '@onpe/ui/components';
|
|
784
|
-
|
|
785
|
-
function App() {
|
|
786
|
-
const [visible, setVisible] = useState(true);
|
|
787
|
-
|
|
788
|
-
return (
|
|
789
|
-
<Show when={visible}>
|
|
790
|
-
<div>Contenido visible</div>
|
|
791
|
-
</Show>
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
### ModalConfirm
|
|
797
|
-
|
|
798
|
-
Modal de confirmación para acciones importantes con colores dinámicos.
|
|
799
|
-
|
|
800
|
-
```tsx
|
|
801
|
-
import { ModalConfirm } from '@onpe/ui/components';
|
|
802
|
-
|
|
803
|
-
function App() {
|
|
804
|
-
const [showConfirm, setShowConfirm] = useState(false);
|
|
805
|
-
|
|
806
|
-
return (
|
|
807
|
-
<div className="space-y-4">
|
|
808
|
-
{/* Modal Azul (Por Defecto) */}
|
|
809
|
-
<ModalConfirm
|
|
810
|
-
isOpen={showConfirm}
|
|
811
|
-
onClose={() => setShowConfirm(false)}
|
|
812
|
-
onConfirm={() => {
|
|
813
|
-
// Acción a confirmar
|
|
814
|
-
setShowConfirm(false);
|
|
815
|
-
}}
|
|
816
|
-
title="Confirmar acción"
|
|
817
|
-
message="¿Estás seguro de realizar esta acción?"
|
|
818
|
-
color="blue" // Por defecto
|
|
819
|
-
/>
|
|
820
|
-
|
|
821
|
-
{/* Modal Rojo para Advertencias */}
|
|
822
|
-
<ModalConfirm
|
|
823
|
-
isOpen={showConfirm}
|
|
824
|
-
onClose={() => setShowConfirm(false)}
|
|
825
|
-
onConfirm={() => {
|
|
826
|
-
// Acción peligrosa
|
|
827
|
-
setShowConfirm(false);
|
|
828
|
-
}}
|
|
829
|
-
title="Advertencia"
|
|
830
|
-
message="Esta acción es irreversible y no se puede deshacer."
|
|
831
|
-
color="red" // Color rojo para advertencias
|
|
832
|
-
icon="warning"
|
|
833
|
-
/>
|
|
834
|
-
</div>
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
#### Props del ModalConfirm
|
|
840
|
-
|
|
841
|
-
| Prop | Tipo | Default | Descripción |
|
|
842
|
-
|------|------|---------|-------------|
|
|
843
|
-
| `isOpen` | `boolean` | - | Estado de apertura del modal (requerido) |
|
|
844
|
-
| `onClose` | `function` | - | Función para cerrar el modal (requerido) |
|
|
845
|
-
| `onConfirm` | `function` | - | Función para confirmar la acción (requerido) |
|
|
846
|
-
| `title` | `string` | - | Título del modal (requerido) |
|
|
847
|
-
| `message` | `string` | - | Mensaje del modal (requerido) |
|
|
848
|
-
| `color` | `'blue' \| 'red'` | `'blue'` | Color del icono y título |
|
|
849
|
-
| `icon` | `'warning' \| 'success'` | `'warning'` | Tipo de icono a mostrar |
|
|
850
|
-
| `confirmText` | `string` | `'Confirmar'` | Texto del botón de confirmación |
|
|
851
|
-
| `cancelText` | `string` | `'Cancelar'` | Texto del botón de cancelación |
|
|
852
|
-
|
|
853
|
-
### ModalLoading
|
|
854
|
-
|
|
855
|
-
Modal de carga para mostrar estados de procesamiento.
|
|
856
|
-
|
|
857
|
-
```tsx
|
|
858
|
-
import { ModalLoading } from '@onpe/ui/components';
|
|
859
|
-
|
|
860
|
-
function App() {
|
|
861
|
-
const [loading, setLoading] = useState(false);
|
|
862
|
-
|
|
863
|
-
return (
|
|
864
|
-
<ModalLoading
|
|
865
|
-
isOpen={loading}
|
|
866
|
-
message="Procesando información..."
|
|
867
|
-
/>
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
### ModalBrowserIncompatible
|
|
873
|
-
|
|
874
|
-
Modal mejorado para mostrar cuando el navegador no es compatible con el sistema de votación.
|
|
875
|
-
|
|
876
|
-
```tsx
|
|
877
|
-
import { ModalBrowserIncompatible } from '@onpe/ui/components';
|
|
878
|
-
|
|
879
|
-
function App() {
|
|
880
|
-
const [showBrowserModal, setShowBrowserModal] = useState(false);
|
|
881
|
-
|
|
882
|
-
return (
|
|
883
|
-
<ModalBrowserIncompatible
|
|
884
|
-
isOpen={showBrowserModal}
|
|
885
|
-
onClose={() => setShowBrowserModal(false)}
|
|
886
|
-
/>
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
**Características del modal mejorado:**
|
|
892
|
-
- ✅ Mensaje claro sobre incompatibilidad
|
|
893
|
-
- ✅ Lista visual de navegadores compatibles con nombres
|
|
894
|
-
- ✅ Información sobre seguridad y versiones
|
|
895
|
-
- ✅ Diseño responsive y accesible
|
|
896
|
-
- ✅ Colores institucionales ONPE
|
|
897
|
-
|
|
898
|
-
### ModalSystemIncompatible
|
|
899
|
-
|
|
900
|
-
Modal mejorado para mostrar cuando el sistema operativo no es compatible con ONPEID.
|
|
901
|
-
|
|
902
|
-
```tsx
|
|
903
|
-
import { ModalSystemIncompatible } from '@onpe/ui/components';
|
|
904
|
-
|
|
905
|
-
function App() {
|
|
906
|
-
const [showSystemModal, setShowSystemModal] = useState(false);
|
|
907
|
-
|
|
908
|
-
return (
|
|
909
|
-
<ModalSystemIncompatible
|
|
910
|
-
isOpen={showSystemModal}
|
|
911
|
-
onClose={() => setShowSystemModal(false)}
|
|
912
|
-
/>
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
**Características del modal mejorado:**
|
|
918
|
-
- ✅ Información específica sobre ONPEID
|
|
919
|
-
- ✅ Lista de sistemas operativos compatibles con versiones mínimas
|
|
920
|
-
- ✅ Alternativa de acceso web
|
|
921
|
-
- ✅ Información de seguridad sobre fuentes oficiales
|
|
922
|
-
- ✅ Diseño intuitivo y profesional
|
|
923
|
-
|
|
924
|
-
## 🎯 Hooks Disponibles
|
|
925
|
-
|
|
926
|
-
### useDebounce
|
|
927
|
-
|
|
928
|
-
Hook para retrasar la ejecución de funciones.
|
|
929
|
-
|
|
930
|
-
```tsx
|
|
931
|
-
import { useDebounce } from '@onpe/ui/hooks';
|
|
932
|
-
|
|
933
|
-
function SearchComponent() {
|
|
934
|
-
const [query, setQuery] = useState('');
|
|
935
|
-
const debouncedQuery = useDebounce(query, 500);
|
|
936
|
-
|
|
937
|
-
useEffect(() => {
|
|
938
|
-
// Buscar solo después de 500ms sin cambios
|
|
939
|
-
searchAPI(debouncedQuery);
|
|
940
|
-
}, [debouncedQuery]);
|
|
941
|
-
|
|
942
|
-
return (
|
|
943
|
-
<input
|
|
944
|
-
value={query}
|
|
945
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
946
|
-
placeholder="Buscar..."
|
|
947
|
-
/>
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
### useLocalStorage
|
|
953
|
-
|
|
954
|
-
Hook para manejar localStorage de forma reactiva.
|
|
955
|
-
|
|
956
|
-
```tsx
|
|
957
|
-
import { useLocalStorage } from '@onpe/ui/hooks';
|
|
958
|
-
|
|
959
|
-
function SettingsComponent() {
|
|
960
|
-
const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
961
|
-
|
|
962
|
-
return (
|
|
963
|
-
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
|
|
964
|
-
<option value="light">Claro</option>
|
|
965
|
-
<option value="dark">Oscuro</option>
|
|
966
|
-
</select>
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
## 🎨 Iconos Disponibles
|
|
972
|
-
|
|
973
|
-
La librería incluye una colección completa de iconos organizados por categorías:
|
|
974
|
-
|
|
975
|
-
### Iconos de Acciones
|
|
976
|
-
- Iconos para acciones comunes (editar, eliminar, guardar, etc.)
|
|
977
|
-
|
|
978
|
-
### Iconos de Navegadores
|
|
979
|
-
- Iconos de navegadores web (Chrome, Firefox, Safari, Edge, etc.)
|
|
980
|
-
|
|
981
|
-
### Iconos de Sistemas Operativos
|
|
982
|
-
- Iconos de sistemas operativos (Windows, macOS, Linux, etc.)
|
|
983
|
-
|
|
984
|
-
### Iconos ONPE
|
|
985
|
-
- Iconos específicos de la institución ONPE
|
|
986
|
-
|
|
987
|
-
### Logos
|
|
988
|
-
- Logotipos oficiales y marcas
|
|
989
|
-
|
|
990
|
-
```tsx
|
|
991
|
-
import {
|
|
992
|
-
IconChrome,
|
|
993
|
-
IconFirefox,
|
|
994
|
-
IconSafari,
|
|
995
|
-
IconWindows,
|
|
996
|
-
IconMacOS
|
|
997
|
-
} from '@onpe/ui/icons';
|
|
998
|
-
|
|
999
|
-
function App() {
|
|
1000
|
-
return (
|
|
1001
|
-
<div>
|
|
1002
|
-
<IconChrome className="w-6 h-6" />
|
|
1003
|
-
<IconFirefox className="w-6 h-6" />
|
|
1004
|
-
<IconSafari className="w-6 h-6" />
|
|
1005
|
-
</div>
|
|
1006
|
-
);
|
|
1007
|
-
}
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
## 🛠️ Utilidades
|
|
1011
|
-
|
|
1012
|
-
### formatDate
|
|
1013
|
-
|
|
1014
|
-
Función para formatear fechas según estándares peruanos.
|
|
1015
|
-
|
|
1016
|
-
```tsx
|
|
1017
|
-
import { formatDate } from '@onpe/ui/utils';
|
|
1018
|
-
|
|
1019
|
-
const fecha = new Date('2024-04-14');
|
|
1020
|
-
const fechaFormateada = formatDate(fecha, 'dd/mm/yyyy');
|
|
1021
|
-
console.log(fechaFormateada); // "14/04/2024"
|
|
1022
|
-
```
|
|
1023
|
-
|
|
1024
|
-
### validateEmail
|
|
1025
|
-
|
|
1026
|
-
Función para validar direcciones de correo electrónico.
|
|
1027
|
-
|
|
1028
|
-
```tsx
|
|
1029
|
-
import { validateEmail } from '@onpe/ui/utils';
|
|
1030
|
-
|
|
1031
|
-
const email = 'usuario@onpe.gob.pe';
|
|
1032
|
-
const esValido = validateEmail(email);
|
|
1033
|
-
console.log(esValido); // true
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
## 📱 Breakpoints Responsive
|
|
1037
|
-
|
|
1038
|
-
La librería incluye breakpoints personalizados para ONPE:
|
|
1039
|
-
|
|
1040
|
-
- `sm`: 640px
|
|
1041
|
-
- `md`: 768px
|
|
1042
|
-
- `lg`: 1024px
|
|
1043
|
-
- `2lg`: 1200px
|
|
1044
|
-
- `xl`: 1280px
|
|
1045
|
-
- `2xl`: 1536px
|
|
1046
|
-
- `3xl`: 1650px
|
|
1047
|
-
|
|
1048
|
-
```css
|
|
1049
|
-
/* Ejemplo de uso */
|
|
1050
|
-
@media (min-width: 1200px) {
|
|
1051
|
-
.container {
|
|
1052
|
-
max-width: 1200px;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
## 🎨 Clases de Utilidad
|
|
1058
|
-
|
|
1059
|
-
### Colores de Texto
|
|
1060
|
-
```css
|
|
1061
|
-
.text-onpe-ui-blue /* Azul principal */
|
|
1062
|
-
.text-onpe-ui-skyblue /* Sky blue */
|
|
1063
|
-
.text-onpe-ui-yellow /* Amarillo */
|
|
1064
|
-
.text-onpe-ui-green /* Verde */
|
|
1065
|
-
.text-onpe-ui-red /* Rojo */
|
|
1066
|
-
.text-onpe-ui-gray /* Gris */
|
|
1067
|
-
.text-onpe-ui-dark-gray /* Gris oscuro */
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
### Colores de Fondo
|
|
1071
|
-
```css
|
|
1072
|
-
.bg-onpe-ui-blue /* Fondo azul */
|
|
1073
|
-
.bg-onpe-ui-skyblue /* Fondo sky blue */
|
|
1074
|
-
.bg-onpe-ui-yellow /* Fondo amarillo */
|
|
1075
|
-
.bg-onpe-ui-green /* Fondo verde */
|
|
1076
|
-
.bg-onpe-ui-red /* Fondo rojo */
|
|
1077
|
-
.bg-onpe-ui-gray /* Fondo gris */
|
|
1078
|
-
.bg-onpe-ui-gray-light /* Fondo gris claro */
|
|
1079
|
-
.bg-onpe-ui-gray-extra-light /* Fondo gris muy claro */
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
## 🛡️ Compatibilidad con CSP (Content Security Policy)
|
|
1083
|
-
|
|
1084
|
-
**La librería es completamente compatible con Content Security Policy estricto.**
|
|
1085
|
-
|
|
1086
|
-
### ✅ Características CSP
|
|
1087
|
-
|
|
1088
|
-
- **Archivos CSS externos**: No usa estilos inline que violen CSP
|
|
1089
|
-
- **Sin `'unsafe-inline'`**: Compatible con `style-src 'self'`
|
|
1090
|
-
- **Variables CSS en `:root`**: Disponibles globalmente sin violar políticas
|
|
1091
|
-
- **Archivos estáticos**: Todos los recursos se sirven como archivos externos
|
|
1092
|
-
|
|
1093
|
-
### 🔧 Configuración CSP Recomendada
|
|
1094
|
-
|
|
1095
|
-
```html
|
|
1096
|
-
<!-- En tu HTML head -->
|
|
1097
|
-
<meta http-equiv="Content-Security-Policy"
|
|
1098
|
-
content="style-src 'self' https://fonts.googleapis.com;
|
|
1099
|
-
script-src 'self';
|
|
1100
|
-
img-src 'self' data:;">
|
|
1101
|
-
```
|
|
1102
|
-
|
|
1103
|
-
### 📋 Instrucciones para Proyectos con CSP
|
|
1104
|
-
|
|
1105
|
-
**1. Instalar la librería:**
|
|
1106
|
-
```bash
|
|
1107
|
-
npm install @onpe/ui@1.2.40
|
|
1108
|
-
```
|
|
1109
|
-
|
|
1110
|
-
**2. Importar CSS (CRÍTICO):**
|
|
1111
|
-
```tsx
|
|
1112
|
-
// ✅ CORRECTO - Esto carga el CSS externo
|
|
1113
|
-
import '@onpe/ui/dist/index.css';
|
|
1114
|
-
import { ModalConfirm, Button } from '@onpe/ui/components';
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
**3. Verificar que las variables CSS estén disponibles:**
|
|
1118
|
-
```tsx
|
|
1119
|
-
// Las variables CSS se definen automáticamente en :root
|
|
1120
|
-
function MiComponente() {
|
|
1121
|
-
return (
|
|
1122
|
-
<div style={{ color: 'var(--onpe-ui-blue)' }}>
|
|
1123
|
-
Este texto usa el color azul ONPE
|
|
1124
|
-
</div>
|
|
1125
|
-
);
|
|
1126
|
-
}
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
### ⚠️ Problemas Comunes con CSP
|
|
1130
|
-
|
|
1131
|
-
**Error: "Refused to apply inline style"**
|
|
1132
|
-
```bash
|
|
1133
|
-
# ❌ PROBLEMA: Estilos inline bloqueados por CSP
|
|
1134
|
-
# ✅ SOLUCIÓN: Usar archivos CSS externos (ya implementado)
|
|
1135
|
-
```
|
|
1136
|
-
|
|
1137
|
-
**Error: "Variable CSS not defined"**
|
|
1138
|
-
```tsx
|
|
1139
|
-
// ❌ PROBLEMA: No importar el CSS
|
|
1140
|
-
import { Button } from '@onpe/ui/components';
|
|
1141
|
-
|
|
1142
|
-
// ✅ SOLUCIÓN: Importar CSS primero
|
|
1143
|
-
import '@onpe/ui/dist/index.css';
|
|
1144
|
-
import { Button } from '@onpe/ui/components';
|
|
1145
|
-
```
|
|
1146
|
-
|
|
1147
|
-
## 📋 Versiones
|
|
1148
|
-
|
|
1149
|
-
- **v1.2.40** - Versión actual con CSP compatible
|
|
1150
|
-
- Compatible con React 16.8+
|
|
1151
|
-
- TailwindCSS v4
|
|
1152
|
-
- TypeScript 5.3+
|
|
1153
|
-
- CSP (Content Security Policy) compatible
|
|
1154
|
-
|
|
1155
|
-
## 🔧 Desarrollo
|
|
1156
|
-
|
|
1157
|
-
### Requisitos
|
|
1158
|
-
- Node.js 18+
|
|
1159
|
-
- npm 9+
|
|
1160
|
-
|
|
1161
|
-
### Instalación para desarrollo
|
|
1162
|
-
```bash
|
|
1163
|
-
git clone https://github.com/ricardosv46/onpe-ui.git
|
|
1164
|
-
cd onpe-ui
|
|
1165
|
-
npm install
|
|
1166
|
-
```
|
|
1167
|
-
|
|
1168
|
-
### Scripts disponibles
|
|
1169
|
-
```bash
|
|
1170
|
-
npm run build # Construir para producción
|
|
1171
|
-
npm run dev # Desarrollo con watch mode
|
|
1172
|
-
npm run storybook # Ver componentes en Storybook
|
|
1173
|
-
npm run lint # Verificar código
|
|
1174
|
-
npm run lint:fix # Corregir problemas de linting
|
|
1175
|
-
npm run type-check # Verificar tipos TypeScript
|
|
1176
|
-
```
|
|
1177
|
-
|
|
1178
|
-
## 📄 Licencia
|
|
1179
|
-
|
|
1180
|
-
MIT © ONPE - Oficina Nacional de Procesos Electorales
|
|
1181
|
-
|
|
1182
|
-
## 🤝 Contribuir
|
|
1183
|
-
|
|
1184
|
-
1. Fork el proyecto
|
|
1185
|
-
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
|
|
1186
|
-
3. Commit tus cambios (`git commit -m 'Agregar nueva funcionalidad'`)
|
|
1187
|
-
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
|
|
1188
|
-
5. Abre un Pull Request
|
|
1189
|
-
|
|
1190
|
-
## 🚀 Ejemplos Completos de Aplicaciones
|
|
1191
|
-
|
|
1192
|
-
### Aplicación de Votación Electrónica
|
|
1193
|
-
```tsx
|
|
1194
|
-
import { Button, Modal, Show } from '@onpe/ui/components';
|
|
1195
|
-
import { useState } from 'react';
|
|
1196
|
-
|
|
1197
|
-
function VotingApp() {
|
|
1198
|
-
const [currentStep, setCurrentStep] = useState(1);
|
|
1199
|
-
const [selectedCandidate, setSelectedCandidate] = useState('');
|
|
1200
|
-
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
|
1201
|
-
const [voted, setVoted] = useState(false);
|
|
1202
|
-
|
|
1203
|
-
const candidates = [
|
|
1204
|
-
{ id: '1', name: 'Juan Pérez', party: 'Partido Democrático' },
|
|
1205
|
-
{ id: '2', name: 'María García', party: 'Partido Progresista' },
|
|
1206
|
-
{ id: '3', name: 'Carlos López', party: 'Partido Independiente' }
|
|
1207
|
-
];
|
|
1208
|
-
|
|
1209
|
-
const handleVote = () => {
|
|
1210
|
-
setVoted(true);
|
|
1211
|
-
setShowConfirmModal(false);
|
|
1212
|
-
setCurrentStep(4);
|
|
1213
|
-
};
|
|
1214
|
-
|
|
1215
|
-
return (
|
|
1216
|
-
<div className="min-h-screen bg-gray-50 py-8">
|
|
1217
|
-
<div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
|
|
1218
|
-
<div className="text-center mb-8">
|
|
1219
|
-
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
1220
|
-
Sistema de Votación ONPE
|
|
1221
|
-
</h1>
|
|
1222
|
-
<p className="text-gray-600">
|
|
1223
|
-
Seleccione su candidato preferido
|
|
1224
|
-
</p>
|
|
1225
|
-
</div>
|
|
1226
|
-
|
|
1227
|
-
<Show when={currentStep === 1}>
|
|
1228
|
-
<div className="space-y-4">
|
|
1229
|
-
<h2 className="text-xl font-semibold mb-4">Candidatos Disponibles:</h2>
|
|
1230
|
-
{candidates.map((candidate) => (
|
|
1231
|
-
<div
|
|
1232
|
-
key={candidate.id}
|
|
1233
|
-
className={`p-4 border-2 rounded-lg cursor-pointer transition-colors ${
|
|
1234
|
-
selectedCandidate === candidate.id
|
|
1235
|
-
? 'border-blue-500 bg-blue-50'
|
|
1236
|
-
: 'border-gray-200 hover:border-gray-300'
|
|
1237
|
-
}`}
|
|
1238
|
-
onClick={() => setSelectedCandidate(candidate.id)}
|
|
1239
|
-
>
|
|
1240
|
-
<div className="flex items-center justify-between">
|
|
1241
|
-
<div>
|
|
1242
|
-
<h3 className="font-semibold text-lg">{candidate.name}</h3>
|
|
1243
|
-
<p className="text-gray-600">{candidate.party}</p>
|
|
1244
|
-
</div>
|
|
1245
|
-
{selectedCandidate === candidate.id && (
|
|
1246
|
-
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
|
1247
|
-
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
1248
|
-
<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" />
|
|
1249
|
-
</svg>
|
|
1250
|
-
</div>
|
|
1251
|
-
)}
|
|
1252
|
-
</div>
|
|
1253
|
-
</div>
|
|
1254
|
-
))}
|
|
1255
|
-
|
|
1256
|
-
<div className="pt-4">
|
|
1257
|
-
<Button
|
|
1258
|
-
color="primary"
|
|
1259
|
-
title="Continuar"
|
|
1260
|
-
size="large"
|
|
1261
|
-
disabled={!selectedCandidate}
|
|
1262
|
-
onClick={() => setCurrentStep(2)}
|
|
1263
|
-
/>
|
|
1264
|
-
</div>
|
|
1265
|
-
</div>
|
|
1266
|
-
</Show>
|
|
1267
|
-
|
|
1268
|
-
<Show when={currentStep === 2}>
|
|
1269
|
-
<div className="text-center">
|
|
1270
|
-
<h2 className="text-xl font-semibold mb-4">Confirmar Voto</h2>
|
|
1271
|
-
<div className="bg-gray-50 p-4 rounded-lg mb-6">
|
|
1272
|
-
<p className="text-gray-600 mb-2">Ha seleccionado:</p>
|
|
1273
|
-
<p className="font-semibold text-lg">
|
|
1274
|
-
{candidates.find(c => c.id === selectedCandidate)?.name}
|
|
1275
|
-
</p>
|
|
1276
|
-
<p className="text-gray-600">
|
|
1277
|
-
{candidates.find(c => c.id === selectedCandidate)?.party}
|
|
1278
|
-
</p>
|
|
1279
|
-
</div>
|
|
1280
|
-
|
|
1281
|
-
<div className="flex justify-center space-x-4">
|
|
1282
|
-
<Button
|
|
1283
|
-
color="gray"
|
|
1284
|
-
title="Volver"
|
|
1285
|
-
onClick={() => setCurrentStep(1)}
|
|
1286
|
-
/>
|
|
1287
|
-
<Button
|
|
1288
|
-
color="primary"
|
|
1289
|
-
title="Confirmar Voto"
|
|
1290
|
-
onClick={() => setShowConfirmModal(true)}
|
|
1291
|
-
/>
|
|
1292
|
-
</div>
|
|
1293
|
-
</div>
|
|
1294
|
-
</Show>
|
|
1295
|
-
|
|
1296
|
-
<Show when={currentStep === 4}>
|
|
1297
|
-
<div className="text-center">
|
|
1298
|
-
<div className="mb-6">
|
|
1299
|
-
<div className="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4">
|
|
1300
|
-
<svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1301
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
1302
|
-
</svg>
|
|
1303
|
-
</div>
|
|
1304
|
-
<h2 className="text-2xl font-bold text-green-600 mb-2">
|
|
1305
|
-
¡Voto Registrado Exitosamente!
|
|
1306
|
-
</h2>
|
|
1307
|
-
<p className="text-gray-600">
|
|
1308
|
-
Su voto ha sido procesado correctamente. Gracias por participar en la democracia.
|
|
1309
|
-
</p>
|
|
1310
|
-
</div>
|
|
1311
|
-
|
|
1312
|
-
<Button
|
|
1313
|
-
color="green"
|
|
1314
|
-
title="Ver Resultados"
|
|
1315
|
-
size="large"
|
|
1316
|
-
onClick={() => window.location.reload()}
|
|
1317
|
-
/>
|
|
1318
|
-
</div>
|
|
1319
|
-
</Show>
|
|
1320
|
-
|
|
1321
|
-
{/* Modal de Confirmación */}
|
|
1322
|
-
<Modal
|
|
1323
|
-
isOpen={showConfirmModal}
|
|
1324
|
-
onClose={() => setShowConfirmModal(false)}
|
|
1325
|
-
closeButton={true}
|
|
1326
|
-
overlayColor="blue"
|
|
1327
|
-
>
|
|
1328
|
-
<div className="p-6 text-center">
|
|
1329
|
-
<div className="mb-4">
|
|
1330
|
-
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 mb-4">
|
|
1331
|
-
<svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1332
|
-
<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" />
|
|
1333
|
-
</svg>
|
|
1334
|
-
</div>
|
|
1335
|
-
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
1336
|
-
Confirmar Voto
|
|
1337
|
-
</h3>
|
|
1338
|
-
<p className="text-sm text-gray-500">
|
|
1339
|
-
¿Está seguro de que desea votar por{' '}
|
|
1340
|
-
<strong>{candidates.find(c => c.id === selectedCandidate)?.name}</strong>?
|
|
1341
|
-
Esta acción no se puede deshacer.
|
|
1342
|
-
</p>
|
|
1343
|
-
</div>
|
|
1344
|
-
|
|
1345
|
-
<div className="flex justify-center space-x-3">
|
|
1346
|
-
<Button
|
|
1347
|
-
color="gray"
|
|
1348
|
-
title="Cancelar"
|
|
1349
|
-
onClick={() => setShowConfirmModal(false)}
|
|
1350
|
-
/>
|
|
1351
|
-
<Button
|
|
1352
|
-
color="primary"
|
|
1353
|
-
title="Confirmar Voto"
|
|
1354
|
-
onClick={handleVote}
|
|
1355
|
-
/>
|
|
1356
|
-
</div>
|
|
1357
|
-
</div>
|
|
1358
|
-
</Modal>
|
|
1359
|
-
</div>
|
|
1360
|
-
</div>
|
|
1361
|
-
);
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
export default VotingApp;
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
### Dashboard de Administración
|
|
1368
|
-
```tsx
|
|
1369
|
-
import { Button, Modal, Show } from '@onpe/ui/components';
|
|
1370
|
-
import { useState } from 'react';
|
|
1371
|
-
|
|
1372
|
-
function AdminDashboard() {
|
|
1373
|
-
const [activeTab, setActiveTab] = useState('users');
|
|
1374
|
-
const [showUserModal, setShowUserModal] = useState(false);
|
|
1375
|
-
const [users, setUsers] = useState([
|
|
1376
|
-
{ id: 1, name: 'Juan Pérez', email: 'juan@onpe.gob.pe', role: 'Admin' },
|
|
1377
|
-
{ id: 2, name: 'María García', email: 'maria@onpe.gob.pe', role: 'Usuario' },
|
|
1378
|
-
{ id: 3, name: 'Carlos López', email: 'carlos@onpe.gob.pe', role: 'Usuario' }
|
|
1379
|
-
]);
|
|
1380
|
-
|
|
1381
|
-
const [newUser, setNewUser] = useState({
|
|
1382
|
-
name: '',
|
|
1383
|
-
email: '',
|
|
1384
|
-
role: 'Usuario'
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
const addUser = () => {
|
|
1388
|
-
const user = {
|
|
1389
|
-
id: users.length + 1,
|
|
1390
|
-
...newUser
|
|
1391
|
-
};
|
|
1392
|
-
setUsers([...users, user]);
|
|
1393
|
-
setNewUser({ name: '', email: '', role: 'Usuario' });
|
|
1394
|
-
setShowUserModal(false);
|
|
1395
|
-
};
|
|
1396
|
-
|
|
1397
|
-
return (
|
|
1398
|
-
<div className="min-h-screen bg-gray-100">
|
|
1399
|
-
{/* Header */}
|
|
1400
|
-
<header className="bg-white shadow-sm border-b">
|
|
1401
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
1402
|
-
<div className="flex justify-between items-center py-4">
|
|
1403
|
-
<div>
|
|
1404
|
-
<h1 className="text-2xl font-bold text-gray-900">Dashboard ONPE</h1>
|
|
1405
|
-
<p className="text-gray-600">Panel de administración del sistema electoral</p>
|
|
1406
|
-
</div>
|
|
1407
|
-
<div className="flex items-center space-x-4">
|
|
1408
|
-
<span className="text-sm text-gray-500">Administrador</span>
|
|
1409
|
-
<Button color="primary" title="Cerrar Sesión" size="small" />
|
|
1410
|
-
</div>
|
|
1411
|
-
</div>
|
|
1412
|
-
</div>
|
|
1413
|
-
</header>
|
|
1414
|
-
|
|
1415
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
1416
|
-
{/* Navigation Tabs */}
|
|
1417
|
-
<div className="mb-8">
|
|
1418
|
-
<nav className="flex space-x-8">
|
|
1419
|
-
<button
|
|
1420
|
-
onClick={() => setActiveTab('users')}
|
|
1421
|
-
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1422
|
-
activeTab === 'users'
|
|
1423
|
-
? 'border-blue-500 text-blue-600'
|
|
1424
|
-
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1425
|
-
}`}
|
|
1426
|
-
>
|
|
1427
|
-
Usuarios
|
|
1428
|
-
</button>
|
|
1429
|
-
<button
|
|
1430
|
-
onClick={() => setActiveTab('elections')}
|
|
1431
|
-
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1432
|
-
activeTab === 'elections'
|
|
1433
|
-
? 'border-blue-500 text-blue-600'
|
|
1434
|
-
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1435
|
-
}`}
|
|
1436
|
-
>
|
|
1437
|
-
Elecciones
|
|
1438
|
-
</button>
|
|
1439
|
-
<button
|
|
1440
|
-
onClick={() => setActiveTab('reports')}
|
|
1441
|
-
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1442
|
-
activeTab === 'reports'
|
|
1443
|
-
? 'border-blue-500 text-blue-600'
|
|
1444
|
-
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1445
|
-
}`}
|
|
1446
|
-
>
|
|
1447
|
-
Reportes
|
|
1448
|
-
</button>
|
|
1449
|
-
</nav>
|
|
1450
|
-
</div>
|
|
1451
|
-
|
|
1452
|
-
{/* Users Tab */}
|
|
1453
|
-
<Show when={activeTab === 'users'}>
|
|
1454
|
-
<div className="bg-white rounded-lg shadow">
|
|
1455
|
-
<div className="px-6 py-4 border-b border-gray-200">
|
|
1456
|
-
<div className="flex justify-between items-center">
|
|
1457
|
-
<h2 className="text-lg font-medium text-gray-900">Gestión de Usuarios</h2>
|
|
1458
|
-
<Button
|
|
1459
|
-
color="green"
|
|
1460
|
-
title="Agregar Usuario"
|
|
1461
|
-
onClick={() => setShowUserModal(true)}
|
|
1462
|
-
/>
|
|
1463
|
-
</div>
|
|
1464
|
-
</div>
|
|
1465
|
-
|
|
1466
|
-
<div className="overflow-x-auto">
|
|
1467
|
-
<table className="min-w-full divide-y divide-gray-200">
|
|
1468
|
-
<thead className="bg-gray-50">
|
|
1469
|
-
<tr>
|
|
1470
|
-
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1471
|
-
Nombre
|
|
1472
|
-
</th>
|
|
1473
|
-
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1474
|
-
Email
|
|
1475
|
-
</th>
|
|
1476
|
-
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1477
|
-
Rol
|
|
1478
|
-
</th>
|
|
1479
|
-
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1480
|
-
Acciones
|
|
1481
|
-
</th>
|
|
1482
|
-
</tr>
|
|
1483
|
-
</thead>
|
|
1484
|
-
<tbody className="bg-white divide-y divide-gray-200">
|
|
1485
|
-
{users.map((user) => (
|
|
1486
|
-
<tr key={user.id}>
|
|
1487
|
-
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
1488
|
-
{user.name}
|
|
1489
|
-
</td>
|
|
1490
|
-
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
1491
|
-
{user.email}
|
|
1492
|
-
</td>
|
|
1493
|
-
<td className="px-6 py-4 whitespace-nowrap">
|
|
1494
|
-
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
|
1495
|
-
user.role === 'Admin'
|
|
1496
|
-
? 'bg-red-100 text-red-800'
|
|
1497
|
-
: 'bg-green-100 text-green-800'
|
|
1498
|
-
}`}>
|
|
1499
|
-
{user.role}
|
|
1500
|
-
</span>
|
|
1501
|
-
</td>
|
|
1502
|
-
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
1503
|
-
<div className="flex space-x-2">
|
|
1504
|
-
<Button color="skyblue" title="Editar" size="small" />
|
|
1505
|
-
<Button color="red" title="Eliminar" size="small" />
|
|
1506
|
-
</div>
|
|
1507
|
-
</td>
|
|
1508
|
-
</tr>
|
|
1509
|
-
))}
|
|
1510
|
-
</tbody>
|
|
1511
|
-
</table>
|
|
1512
|
-
</div>
|
|
1513
|
-
</div>
|
|
1514
|
-
</Show>
|
|
1515
|
-
|
|
1516
|
-
{/* Elections Tab */}
|
|
1517
|
-
<Show when={activeTab === 'elections'}>
|
|
1518
|
-
<div className="bg-white rounded-lg shadow p-6">
|
|
1519
|
-
<h2 className="text-lg font-medium text-gray-900 mb-4">Gestión de Elecciones</h2>
|
|
1520
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
1521
|
-
<div className="bg-blue-50 p-4 rounded-lg">
|
|
1522
|
-
<h3 className="font-semibold text-blue-900">Elecciones Generales</h3>
|
|
1523
|
-
<p className="text-blue-700 text-sm mt-1">14 de abril, 2024</p>
|
|
1524
|
-
<Button color="blue" title="Ver Detalles" size="small" />
|
|
1525
|
-
</div>
|
|
1526
|
-
<div className="bg-green-50 p-4 rounded-lg">
|
|
1527
|
-
<h3 className="font-semibold text-green-900">Elecciones Regionales</h3>
|
|
1528
|
-
<p className="text-green-700 text-sm mt-1">28 de octubre, 2024</p>
|
|
1529
|
-
<Button color="green" title="Ver Detalles" size="small" />
|
|
1530
|
-
</div>
|
|
1531
|
-
<div className="bg-yellow-50 p-4 rounded-lg">
|
|
1532
|
-
<h3 className="font-semibold text-yellow-900">Elecciones Municipales</h3>
|
|
1533
|
-
<p className="text-yellow-700 text-sm mt-1">15 de diciembre, 2024</p>
|
|
1534
|
-
<Button color="yellow" title="Ver Detalles" size="small" />
|
|
1535
|
-
</div>
|
|
1536
|
-
</div>
|
|
1537
|
-
</div>
|
|
1538
|
-
</Show>
|
|
1539
|
-
|
|
1540
|
-
{/* Reports Tab */}
|
|
1541
|
-
<Show when={activeTab === 'reports'}>
|
|
1542
|
-
<div className="bg-white rounded-lg shadow p-6">
|
|
1543
|
-
<h2 className="text-lg font-medium text-gray-900 mb-4">Reportes del Sistema</h2>
|
|
1544
|
-
<div className="space-y-4">
|
|
1545
|
-
<div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
|
|
1546
|
-
<div>
|
|
1547
|
-
<h3 className="font-medium">Reporte de Participación Electoral</h3>
|
|
1548
|
-
<p className="text-sm text-gray-600">Generado el 15 de abril, 2024</p>
|
|
1549
|
-
</div>
|
|
1550
|
-
<Button color="primary" title="Descargar PDF" size="small" />
|
|
1551
|
-
</div>
|
|
1552
|
-
<div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
|
|
1553
|
-
<div>
|
|
1554
|
-
<h3 className="font-medium">Estadísticas de Usuarios</h3>
|
|
1555
|
-
<p className="text-sm text-gray-600">Generado el 14 de abril, 2024</p>
|
|
1556
|
-
</div>
|
|
1557
|
-
<Button color="primary" title="Descargar PDF" size="small" />
|
|
1558
|
-
</div>
|
|
1559
|
-
</div>
|
|
1560
|
-
</div>
|
|
1561
|
-
</Show>
|
|
1562
|
-
|
|
1563
|
-
{/* Add User Modal */}
|
|
1564
|
-
<Modal
|
|
1565
|
-
isOpen={showUserModal}
|
|
1566
|
-
onClose={() => setShowUserModal(false)}
|
|
1567
|
-
closeButton={true}
|
|
1568
|
-
overlayColor="skyblue"
|
|
1569
|
-
>
|
|
1570
|
-
<div className="p-6">
|
|
1571
|
-
<h2 className="text-xl font-bold mb-4">Agregar Nuevo Usuario</h2>
|
|
1572
|
-
|
|
1573
|
-
<div className="space-y-4">
|
|
1574
|
-
<div>
|
|
1575
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1576
|
-
Nombre Completo
|
|
1577
|
-
</label>
|
|
1578
|
-
<input
|
|
1579
|
-
type="text"
|
|
1580
|
-
value={newUser.name}
|
|
1581
|
-
onChange={(e) => setNewUser({...newUser, name: e.target.value})}
|
|
1582
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1583
|
-
placeholder="Ingrese el nombre completo"
|
|
1584
|
-
/>
|
|
1585
|
-
</div>
|
|
1586
|
-
|
|
1587
|
-
<div>
|
|
1588
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1589
|
-
Correo Electrónico
|
|
1590
|
-
</label>
|
|
1591
|
-
<input
|
|
1592
|
-
type="email"
|
|
1593
|
-
value={newUser.email}
|
|
1594
|
-
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
|
|
1595
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1596
|
-
placeholder="usuario@onpe.gob.pe"
|
|
1597
|
-
/>
|
|
1598
|
-
</div>
|
|
1599
|
-
|
|
1600
|
-
<div>
|
|
1601
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1602
|
-
Rol
|
|
1603
|
-
</label>
|
|
1604
|
-
<select
|
|
1605
|
-
value={newUser.role}
|
|
1606
|
-
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
|
|
1607
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1608
|
-
>
|
|
1609
|
-
<option value="Usuario">Usuario</option>
|
|
1610
|
-
<option value="Admin">Administrador</option>
|
|
1611
|
-
</select>
|
|
1612
|
-
</div>
|
|
1613
|
-
|
|
1614
|
-
<div className="flex justify-end space-x-3 pt-4">
|
|
1615
|
-
<Button
|
|
1616
|
-
color="gray"
|
|
1617
|
-
title="Cancelar"
|
|
1618
|
-
onClick={() => setShowUserModal(false)}
|
|
1619
|
-
/>
|
|
1620
|
-
<Button
|
|
1621
|
-
color="green"
|
|
1622
|
-
title="Agregar Usuario"
|
|
1623
|
-
onClick={addUser}
|
|
1624
|
-
/>
|
|
1625
|
-
</div>
|
|
1626
|
-
</div>
|
|
1627
|
-
</div>
|
|
1628
|
-
</Modal>
|
|
1629
|
-
</div>
|
|
1630
|
-
</div>
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
export default AdminDashboard;
|
|
1635
|
-
```
|
|
1636
|
-
|
|
1637
|
-
## 🐛 Solución de Problemas
|
|
1638
|
-
|
|
1639
|
-
### Problemas con la CLI
|
|
1640
|
-
|
|
1641
|
-
**Error: "Componente no encontrado"**
|
|
1642
|
-
```bash
|
|
1643
|
-
# Verificar componentes disponibles
|
|
1644
|
-
npx @onpe/ui add --help
|
|
1645
|
-
|
|
1646
|
-
# Componentes válidos:
|
|
1647
|
-
# button, modal, overlay, portal, show
|
|
1648
|
-
```
|
|
1649
|
-
|
|
1650
|
-
**Error: "No se pudo descargar el componente"**
|
|
1651
|
-
```bash
|
|
1652
|
-
# Verificar conexión a internet
|
|
1653
|
-
ping github.com
|
|
1654
|
-
|
|
1655
|
-
# Verificar que el repositorio esté disponible
|
|
1656
|
-
curl https://raw.githubusercontent.com/ricardosv46/onpe-ui/main/src/components/Button/Button.tsx
|
|
1657
|
-
```
|
|
1658
|
-
|
|
1659
|
-
**Los estilos no se aplican**
|
|
1660
|
-
```bash
|
|
1661
|
-
# Verificar que Tailwind esté instalado
|
|
1662
|
-
npm list tailwindcss
|
|
1663
|
-
|
|
1664
|
-
# Verificar configuración
|
|
1665
|
-
cat tailwind.config.js
|
|
1666
|
-
```
|
|
1667
|
-
|
|
1668
|
-
**Portal no funciona**
|
|
1669
|
-
```bash
|
|
1670
|
-
# Verificar que tengas el elemento portal en HTML
|
|
1671
|
-
grep -r "id=\"portal\"" public/
|
|
1672
|
-
```
|
|
1673
|
-
|
|
1674
|
-
### Problemas con la Librería Completa
|
|
1675
|
-
|
|
1676
|
-
**Error: "Module not found"**
|
|
1677
|
-
```bash
|
|
1678
|
-
# Verificar instalación
|
|
1679
|
-
npm list @onpe/ui
|
|
1680
|
-
|
|
1681
|
-
# Reinstalar si es necesario
|
|
1682
|
-
npm uninstall @onpe/ui
|
|
1683
|
-
npm install @onpe/ui
|
|
1684
|
-
```
|
|
1685
|
-
|
|
1686
|
-
**Estilos no se cargan**
|
|
1687
|
-
```tsx
|
|
1688
|
-
/* Verificar que tengas la importación correcta */
|
|
1689
|
-
import './onpe-ui.css';
|
|
1690
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1691
|
-
```
|
|
1692
|
-
|
|
1693
|
-
**Solución: Verificar rutas de importación**
|
|
1694
|
-
```tsx
|
|
1695
|
-
// ✅ CORRECTO: Importar archivo CSS personalizado
|
|
1696
|
-
import './onpe-ui.css';
|
|
1697
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1698
|
-
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1699
|
-
|
|
1700
|
-
// ❌ INCORRECTO: No importar el archivo CSS
|
|
1701
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1702
|
-
```
|
|
1703
|
-
|
|
1704
|
-
**Solución: Verificar configuración de bundler**
|
|
1705
|
-
```javascript
|
|
1706
|
-
// webpack.config.js
|
|
1707
|
-
module.exports = {
|
|
1708
|
-
resolve: {
|
|
1709
|
-
alias: {
|
|
1710
|
-
'@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
};
|
|
1714
|
-
|
|
1715
|
-
// vite.config.js
|
|
1716
|
-
export default {
|
|
1717
|
-
resolve: {
|
|
1718
|
-
alias: {
|
|
1719
|
-
'@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
};
|
|
1723
|
-
```
|
|
1724
|
-
|
|
1725
|
-
**Solución: Verificar orden de importación**
|
|
1726
|
-
```tsx
|
|
1727
|
-
// ✅ CORRECTO - Importar estilos ANTES de los componentes
|
|
1728
|
-
import '@onpe/ui/styles';
|
|
1729
|
-
import { Button } from '@onpe/ui/components';
|
|
1730
|
-
|
|
1731
|
-
// ❌ INCORRECTO - Importar estilos DESPUÉS de los componentes
|
|
1732
|
-
import { Button } from '@onpe/ui/components';
|
|
1733
|
-
import '@onpe/ui/styles';
|
|
1734
|
-
```
|
|
1735
|
-
|
|
1736
|
-
### Problemas con Estilos
|
|
1737
|
-
|
|
1738
|
-
**Los componentes se ven sin estilos**
|
|
1739
|
-
```bash
|
|
1740
|
-
# Verificar que la librería esté instalada
|
|
1741
|
-
npm list @onpe/ui
|
|
1742
|
-
|
|
1743
|
-
# Verificar que los estilos estén en node_modules
|
|
1744
|
-
ls node_modules/@onpe/ui/dist/
|
|
1745
|
-
```
|
|
1746
|
-
|
|
1747
|
-
**Solución: Verificar importación de estilos**
|
|
1748
|
-
```tsx
|
|
1749
|
-
// En tu archivo principal (index.tsx o App.tsx)
|
|
1750
|
-
import React from 'react';
|
|
1751
|
-
import ReactDOM from 'react-dom/client';
|
|
1752
|
-
import '@onpe/ui/styles'; // ← IMPORTANTE: Importar estilos primero
|
|
1753
|
-
import App from './App';
|
|
1754
|
-
|
|
1755
|
-
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
1756
|
-
root.render(<App />);
|
|
1757
|
-
```
|
|
1758
|
-
|
|
1759
|
-
**Solución: Verificar configuración de CSS**
|
|
1760
|
-
```css
|
|
1761
|
-
/* En tu archivo CSS principal */
|
|
1762
|
-
@import "@onpe/ui/styles";
|
|
1763
|
-
|
|
1764
|
-
/* O si usas CSS modules */
|
|
1765
|
-
@import "~@onpe/ui/styles";
|
|
1766
|
-
```
|
|
1767
|
-
|
|
1768
|
-
**Los colores personalizados no funcionan**
|
|
1769
|
-
```tsx
|
|
1770
|
-
// Verificar que estés usando las clases correctas
|
|
1771
|
-
<div className="bg-onpe-ui-blue text-white p-4">
|
|
1772
|
-
Contenido con colores ONPE
|
|
1773
|
-
</div>
|
|
1774
|
-
|
|
1775
|
-
// Verificar que los estilos estén importados
|
|
1776
|
-
import '@onpe/ui/styles';
|
|
1777
|
-
```
|
|
1778
|
-
|
|
1779
|
-
**Solución: Verificar configuración de Tailwind (para componentes individuales)**
|
|
1780
|
-
```javascript
|
|
1781
|
-
// tailwind.config.js
|
|
1782
|
-
module.exports = {
|
|
1783
|
-
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
1784
|
-
theme: {
|
|
1785
|
-
extend: {
|
|
1786
|
-
colors: {
|
|
1787
|
-
'onpe-ui-blue': '#003770',
|
|
1788
|
-
'onpe-ui-skyblue': '#0073cf',
|
|
1789
|
-
// ... resto de colores
|
|
1790
|
-
}
|
|
1791
|
-
},
|
|
1792
|
-
},
|
|
1793
|
-
}
|
|
1794
|
-
```
|
|
1795
|
-
|
|
1796
|
-
### Problemas con Storybook
|
|
1797
|
-
|
|
1798
|
-
**Error: "Failed to fetch dynamically imported module"**
|
|
1799
|
-
- Verificar que el archivo `preview.ts` importe correctamente `../src/styles.css`
|
|
1800
|
-
- Asegurar que Tailwind CSS esté configurado correctamente
|
|
1801
|
-
- Verificar que PostCSS esté configurado
|
|
1802
|
-
|
|
1803
|
-
**Solución: Configuración completa de Storybook**
|
|
1804
|
-
```typescript
|
|
1805
|
-
// .storybook/main.ts
|
|
1806
|
-
import type { StorybookConfig } from "@storybook/react-vite";
|
|
1807
|
-
|
|
1808
|
-
const config: StorybookConfig = {
|
|
1809
|
-
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
1810
|
-
addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
|
|
1811
|
-
framework: {
|
|
1812
|
-
name: "@storybook/react-vite",
|
|
1813
|
-
options: {},
|
|
1814
|
-
},
|
|
1815
|
-
viteFinal: async (config) => {
|
|
1816
|
-
config.css = {
|
|
1817
|
-
...config.css,
|
|
1818
|
-
postcss: {
|
|
1819
|
-
plugins: [require("tailwindcss"), require("autoprefixer")],
|
|
1820
|
-
},
|
|
1821
|
-
};
|
|
1822
|
-
return config;
|
|
1823
|
-
},
|
|
1824
|
-
};
|
|
1825
|
-
|
|
1826
|
-
export default config;
|
|
1827
|
-
```
|
|
1828
|
-
|
|
1829
|
-
```typescript
|
|
1830
|
-
// .storybook/preview.ts
|
|
1831
|
-
import type { Preview } from "@storybook/react";
|
|
1832
|
-
import "../src/styles.css"; // ← Importar estilos
|
|
1833
|
-
|
|
1834
|
-
const preview: Preview = {
|
|
1835
|
-
parameters: {
|
|
1836
|
-
actions: { argTypesRegex: "^on[A-Z].*" },
|
|
1837
|
-
controls: {
|
|
1838
|
-
matchers: {
|
|
1839
|
-
color: /(background|color)$/i,
|
|
1840
|
-
date: /Date$/,
|
|
1841
|
-
},
|
|
1842
|
-
},
|
|
1843
|
-
},
|
|
1844
|
-
};
|
|
1845
|
-
|
|
1846
|
-
export default preview;
|
|
1847
|
-
```
|
|
1848
|
-
|
|
1849
|
-
## 🚀 Guía Rápida de Solución
|
|
1850
|
-
|
|
1851
|
-
### ¿Los componentes no se ven con estilos?
|
|
1852
|
-
|
|
1853
|
-
**Paso 1: Verificar instalación**
|
|
1854
|
-
```bash
|
|
1855
|
-
npm list @onpe/ui
|
|
1856
|
-
```
|
|
1857
|
-
|
|
1858
|
-
**Paso 2: Configurar TailwindCSS**
|
|
1859
|
-
```bash
|
|
1860
|
-
# Instalar TailwindCSS
|
|
1861
|
-
npm install -D tailwindcss postcss autoprefixer
|
|
1862
|
-
npx tailwindcss init -p
|
|
1863
|
-
```
|
|
1864
|
-
|
|
1865
|
-
**Paso 3: Crear archivo CSS personalizado**
|
|
1866
|
-
```css
|
|
1867
|
-
/* Crear archivo onpe-ui.css */
|
|
1868
|
-
:root {
|
|
1869
|
-
--blue: #003770;
|
|
1870
|
-
--skyblue: #0073cf;
|
|
1871
|
-
/* ... resto de variables */
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
@theme {
|
|
1875
|
-
--color-onpe-ui-blue: var(--blue);
|
|
1876
|
-
/* ... resto de colores */
|
|
1877
|
-
}
|
|
1878
|
-
```
|
|
1879
|
-
|
|
1880
|
-
**Paso 4: Importar archivo CSS**
|
|
1881
|
-
```tsx
|
|
1882
|
-
// En tu archivo principal (index.tsx)
|
|
1883
|
-
import './onpe-ui.css';
|
|
1884
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1885
|
-
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1886
|
-
```
|
|
1887
|
-
|
|
1888
|
-
**Paso 5: Verificar orden de importación**
|
|
1889
|
-
```tsx
|
|
1890
|
-
// ✅ CORRECTO
|
|
1891
|
-
import './onpe-ui.css';
|
|
1892
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1893
|
-
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1894
|
-
|
|
1895
|
-
// ❌ INCORRECTO
|
|
1896
|
-
import { Button } from './components/onpe-ui/Button';
|
|
1897
|
-
// Falta importar el archivo CSS
|
|
1898
|
-
```
|
|
1899
|
-
|
|
1900
|
-
### ¿Los colores no funcionan?
|
|
1901
|
-
|
|
1902
|
-
**Solución: Usar clases correctas**
|
|
1903
|
-
```tsx
|
|
1904
|
-
// ✅ CORRECTO
|
|
1905
|
-
<div className="bg-onpe-ui-blue text-white p-4">
|
|
1906
|
-
Contenido
|
|
1907
|
-
</div>
|
|
1908
|
-
|
|
1909
|
-
// ❌ INCORRECTO
|
|
1910
|
-
<div className="bg-blue-500 text-white p-4">
|
|
1911
|
-
Contenido
|
|
1912
|
-
</div>
|
|
1913
|
-
```
|
|
1914
|
-
|
|
1915
|
-
### ¿Storybook no funciona?
|
|
1916
|
-
|
|
1917
|
-
**Solución: Configuración completa**
|
|
1918
|
-
```typescript
|
|
1919
|
-
// .storybook/preview.ts
|
|
1920
|
-
import "../src/styles.css";
|
|
1921
|
-
|
|
1922
|
-
// .storybook/main.ts
|
|
1923
|
-
viteFinal: async (config) => {
|
|
1924
|
-
config.css = {
|
|
1925
|
-
...config.css,
|
|
1926
|
-
postcss: {
|
|
1927
|
-
plugins: [require("tailwindcss"), require("autoprefixer")],
|
|
1928
|
-
},
|
|
1929
|
-
};
|
|
1930
|
-
return config;
|
|
1931
|
-
},
|
|
1932
|
-
```
|
|
1933
|
-
|
|
1934
|
-
### ¿CLI no instala componentes?
|
|
1935
|
-
|
|
1936
|
-
**Solución: Verificar comandos**
|
|
1937
|
-
```bash
|
|
1938
|
-
# ✅ CORRECTO
|
|
1939
|
-
npx @onpe/ui add button
|
|
1940
|
-
npx @onpe/ui add modal
|
|
1941
|
-
|
|
1942
|
-
# ❌ INCORRECTO
|
|
1943
|
-
npx @onpe/ui add Button
|
|
1944
|
-
npx @onpe/ui add Modal
|
|
1945
|
-
```
|
|
1946
|
-
|
|
1947
|
-
### ¿Portal no funciona?
|
|
1948
|
-
|
|
1949
|
-
**Solución: Agregar elemento HTML**
|
|
1950
|
-
```html
|
|
1951
|
-
<!-- En public/index.html -->
|
|
1952
|
-
<div id="root"></div>
|
|
1953
|
-
<div id="portal"></div>
|
|
1954
|
-
```
|
|
1955
|
-
|
|
1956
|
-
## 📞 Soporte
|
|
1957
|
-
|
|
1958
|
-
- 📧 Email: desarrollo@onpe.gob.pe
|
|
1959
|
-
- 🐛 Issues: [GitHub Issues](https://github.com/ricardosv46/onpe-ui/issues)
|
|
1960
|
-
- 📖 Documentación: [Storybook](https://onpe-ui-components.netlify.app)
|
|
1961
|
-
- 🔗 Repositorio: [GitHub](https://github.com/ricardosv46/onpe-ui)
|
|
1962
|
-
- 📦 NPM: [@onpe/ui](https://www.npmjs.com/package/@onpe/ui)
|
|
1963
|
-
|
|
1964
|
-
---
|
|
1965
|
-
|
|
1
|
+
# 🗳️ ONPE UI
|
|
2
|
+
|
|
3
|
+
Librería completa de componentes de interfaz de usuario para aplicaciones de la Oficina Nacional de Procesos Electorales (ONPE) del Perú.
|
|
4
|
+
|
|
5
|
+
## ✨ Características
|
|
6
|
+
|
|
7
|
+
- 🎨 **Colores oficiales de ONPE** - Paleta de colores institucional
|
|
8
|
+
- ⚡ **Tailwind CSS v4** - Framework CSS moderno y eficiente
|
|
9
|
+
- 🔧 **TypeScript** - Tipado estático para mejor desarrollo
|
|
10
|
+
- 📱 **Responsive** - Diseño adaptable a todos los dispositivos
|
|
11
|
+
- 🎯 **Accesible** - Componentes que siguen estándares de accesibilidad
|
|
12
|
+
- 📦 **Tree-shakable** - Solo importa lo que necesitas
|
|
13
|
+
|
|
14
|
+
## 🚀 Instalación
|
|
15
|
+
|
|
16
|
+
### Instalación Completa de la Librería
|
|
17
|
+
```bash
|
|
18
|
+
npm install @onpe/ui
|
|
19
|
+
```
|
|
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
|
+
## 🛡️ Integración Sin Conflictos
|
|
31
|
+
|
|
32
|
+
**Esta librería está diseñada para funcionar perfectamente en proyectos que ya usan Tailwind CSS, Material UI, Shadcn, o cualquier otro framework CSS sin causar conflictos de estilos.**
|
|
33
|
+
|
|
34
|
+
### ✨ Características de Compatibilidad
|
|
35
|
+
|
|
36
|
+
- **Prefijos Únicos**: Todas las clases usan el prefijo `onpe-` para evitar conflictos
|
|
37
|
+
- **CSS Compilado**: Se genera un CSS optimizado y minificado sin `@import` de Tailwind
|
|
38
|
+
- **Variables CSS en `:root`**: Colores con prefijos únicos (`--onpe-ui-blue`, etc.) disponibles globalmente
|
|
39
|
+
- **Sin Reset de Tailwind**: No interfiere con tu configuración existente
|
|
40
|
+
- **Compatible con**: Material UI, Shadcn, Chakra UI, Ant Design, Bootstrap, etc.
|
|
41
|
+
- **CSP Compatible**: Genera archivos CSS externos para cumplir con Content Security Policy
|
|
42
|
+
|
|
43
|
+
### 🚀 Instalación Rápida
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @onpe/ui
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// Importar estilos compilados (solo una vez en tu app)
|
|
51
|
+
// ⚠️ IMPORTANTE: Esto define las variables CSS en :root
|
|
52
|
+
import '@onpe/ui/dist/index.css';
|
|
53
|
+
|
|
54
|
+
// O usando el export específico
|
|
55
|
+
import '@onpe/ui/css';
|
|
56
|
+
|
|
57
|
+
// Usar componentes
|
|
58
|
+
import { Button } from '@onpe/ui/components';
|
|
59
|
+
|
|
60
|
+
function App() {
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
{/* Tu contenido existente con Material UI, Shadcn, etc. */}
|
|
64
|
+
<Button color="blue" title="Botón ONPE" />
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 🎨 Variables CSS en `:root`
|
|
71
|
+
|
|
72
|
+
**Los colores ONPE se definen automáticamente en `:root` cuando importas el CSS:**
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
:root {
|
|
76
|
+
--onpe-ui-blue: #003770;
|
|
77
|
+
--onpe-ui-skyblue: #0073cf;
|
|
78
|
+
--onpe-ui-skyblue-light: #69b2e8;
|
|
79
|
+
--onpe-ui-yellow: #ffb81c;
|
|
80
|
+
--onpe-ui-light-skyblue: #aaeff6;
|
|
81
|
+
--onpe-ui-gray: #bcbcbc;
|
|
82
|
+
--onpe-ui-gray-light: #bdbdbd;
|
|
83
|
+
--onpe-ui-gray-extra-light: #f2f2f2;
|
|
84
|
+
--onpe-ui-red: #e3002b;
|
|
85
|
+
--onpe-ui-dark-gray: #4f4f4f;
|
|
86
|
+
--onpe-ui-green: #76bd43;
|
|
87
|
+
--onpe-ui-yellow-light: #FFF1D2;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Esto permite:**
|
|
92
|
+
- ✅ **Uso directo en CSS del proyecto host**
|
|
93
|
+
- ✅ **Sobrescribir colores si es necesario**
|
|
94
|
+
- ✅ **Compatibilidad con CSP (Content Security Policy)**
|
|
95
|
+
- ✅ **Acceso global a los colores ONPE**
|
|
96
|
+
|
|
97
|
+
### 💡 Uso de Variables CSS en tu Proyecto
|
|
98
|
+
|
|
99
|
+
**Puedes usar los colores ONPE directamente en tu CSS:**
|
|
100
|
+
|
|
101
|
+
```css
|
|
102
|
+
/* En tu archivo CSS del proyecto host */
|
|
103
|
+
.mi-componente {
|
|
104
|
+
color: var(--onpe-ui-blue);
|
|
105
|
+
background: var(--onpe-ui-skyblue-light);
|
|
106
|
+
border: 2px solid var(--onpe-ui-red);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.mi-boton-personalizado {
|
|
110
|
+
background: var(--onpe-ui-green);
|
|
111
|
+
color: white;
|
|
112
|
+
padding: 12px 24px;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Sobrescribir colores si es necesario:**
|
|
118
|
+
|
|
119
|
+
```css
|
|
120
|
+
/* En tu archivo CSS del proyecto host */
|
|
121
|
+
:root {
|
|
122
|
+
/* Cambiar el azul principal */
|
|
123
|
+
--onpe-ui-blue: #1a365d;
|
|
124
|
+
|
|
125
|
+
/* Cambiar el rojo */
|
|
126
|
+
--onpe-ui-red: #c53030;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Uso en componentes React con estilos inline:**
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
function MiComponente() {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
style={{
|
|
137
|
+
color: 'var(--onpe-ui-blue)',
|
|
138
|
+
backgroundColor: 'var(--onpe-ui-skyblue-light)',
|
|
139
|
+
padding: '16px',
|
|
140
|
+
borderRadius: '8px'
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
Contenido con colores ONPE
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 🎯 ¿Cómo Evitamos Conflictos?
|
|
150
|
+
|
|
151
|
+
1. **Prefijos Únicos**: `bg-blue-500` → `onpe-bg-onpe-ui-blue`
|
|
152
|
+
2. **CSS Scoped**: Todos los componentes están aislados
|
|
153
|
+
3. **Variables CSS Aisladas**: `--onpe-ui-blue` en lugar de `--blue`
|
|
154
|
+
4. **Sin Preflight**: No resetea estilos del proyecto host
|
|
155
|
+
|
|
156
|
+
## 📖 Uso Básico
|
|
157
|
+
|
|
158
|
+
### Instalar Componentes con CLI
|
|
159
|
+
|
|
160
|
+
#### Instalar componentes específicos
|
|
161
|
+
```bash
|
|
162
|
+
# Componentes
|
|
163
|
+
npx @onpe/ui add button
|
|
164
|
+
npx @onpe/ui add modal
|
|
165
|
+
npx @onpe/ui add portal
|
|
166
|
+
npx @onpe/ui add overlay
|
|
167
|
+
npx @onpe/ui add show
|
|
168
|
+
|
|
169
|
+
# Iconos
|
|
170
|
+
npx @onpe/ui add icon-close
|
|
171
|
+
npx @onpe/ui add icon-check
|
|
172
|
+
npx @onpe/ui add icon-warning
|
|
173
|
+
npx @onpe/ui add icon-chrome
|
|
174
|
+
npx @onpe/ui add icon-firefox
|
|
175
|
+
npx @onpe/ui add icon-safari
|
|
176
|
+
npx @onpe/ui add icon-edge
|
|
177
|
+
npx @onpe/ui add icon-windows
|
|
178
|
+
npx @onpe/ui add icon-apple
|
|
179
|
+
npx @onpe/ui add icon-android
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Usar componentes instalados individualmente
|
|
183
|
+
```tsx
|
|
184
|
+
// Después de instalar con CLI
|
|
185
|
+
import { Button } from './components/onpe-ui/Button';
|
|
186
|
+
import { Modal } from './components/onpe-ui/Modal';
|
|
187
|
+
import { IconClose } from './components/onpe-icons/IconClose';
|
|
188
|
+
import { useState } from 'react';
|
|
189
|
+
|
|
190
|
+
function App() {
|
|
191
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div className="p-4">
|
|
195
|
+
<Button
|
|
196
|
+
color="primary"
|
|
197
|
+
title="Abrir Modal"
|
|
198
|
+
onClick={() => setIsOpen(true)}
|
|
199
|
+
/>
|
|
200
|
+
|
|
201
|
+
<Modal
|
|
202
|
+
isOpen={isOpen}
|
|
203
|
+
onClose={() => setIsOpen(false)}
|
|
204
|
+
closeButton={true}
|
|
205
|
+
overlayColor="blue"
|
|
206
|
+
>
|
|
207
|
+
<div className="p-6">
|
|
208
|
+
<h2 className="text-xl font-bold mb-4">Contenido del Modal</h2>
|
|
209
|
+
<p className="mb-4">Este es un ejemplo de modal con contenido.</p>
|
|
210
|
+
<div className="flex items-center gap-2">
|
|
211
|
+
<IconClose className="w-4 h-4" />
|
|
212
|
+
<Button
|
|
213
|
+
color="green"
|
|
214
|
+
title="Cerrar"
|
|
215
|
+
onClick={() => setIsOpen(false)}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</Modal>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Configuración requerida para componentes individuales
|
|
226
|
+
|
|
227
|
+
**1. Instalar Tailwind CSS:**
|
|
228
|
+
```bash
|
|
229
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
230
|
+
npx tailwindcss init -p
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**2. Configurar PostCSS para Tailwind v4:**
|
|
234
|
+
```javascript
|
|
235
|
+
// postcss.config.js
|
|
236
|
+
export default {
|
|
237
|
+
plugins: {
|
|
238
|
+
'@tailwindcss/postcss': {},
|
|
239
|
+
autoprefixer: {},
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**3. Crear archivo CSS con configuración Tailwind v4:**
|
|
245
|
+
```css
|
|
246
|
+
/* onpe-ui.css */
|
|
247
|
+
@import "tailwindcss";
|
|
248
|
+
|
|
249
|
+
@theme {
|
|
250
|
+
--color-onpe-ui-blue: #003770;
|
|
251
|
+
--color-onpe-ui-skyblue: #0073cf;
|
|
252
|
+
--color-onpe-ui-skyblue-light: #69b2e8;
|
|
253
|
+
--color-onpe-ui-yellow: #ffb81c;
|
|
254
|
+
--color-onpe-ui-light-skyblue: #aaeff6;
|
|
255
|
+
--color-onpe-ui-gray: #bcbcbc;
|
|
256
|
+
--color-onpe-ui-gray-light: #bdbdbd;
|
|
257
|
+
--color-onpe-ui-gray-extra-light: #f2f2f2;
|
|
258
|
+
--color-onpe-ui-red: #e3002b;
|
|
259
|
+
--color-onpe-ui-dark-gray: #4f4f4f;
|
|
260
|
+
--color-onpe-ui-green: #76bd43;
|
|
261
|
+
--color-onpe-ui-yellow-light: #FFF1D2;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Clases personalizadas ONPE */
|
|
265
|
+
@utility bg-onpe-ui-blue { background-color: var(--color-onpe-ui-blue); }
|
|
266
|
+
@utility text-onpe-ui-blue { color: var(--color-onpe-ui-blue); }
|
|
267
|
+
@utility border-onpe-ui-blue { border-color: var(--color-onpe-ui-blue); }
|
|
268
|
+
/* ... resto de clases personalizadas */
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**4. Importar el archivo CSS en tu aplicación:**
|
|
272
|
+
```tsx
|
|
273
|
+
// En tu archivo principal (index.tsx o App.tsx)
|
|
274
|
+
import './onpe-ui.css'; // ← IMPORTANTE: Importar primero
|
|
275
|
+
import { Button } from './components/onpe-ui/Button';
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**5. Para componentes que usan Portal, agregar en public/index.html:**
|
|
279
|
+
```html
|
|
280
|
+
<!DOCTYPE html>
|
|
281
|
+
<html lang="es">
|
|
282
|
+
<head>
|
|
283
|
+
<meta charset="utf-8" />
|
|
284
|
+
<title>Mi App</title>
|
|
285
|
+
</head>
|
|
286
|
+
<body>
|
|
287
|
+
<div id="root"></div>
|
|
288
|
+
<div id="portal"></div>
|
|
289
|
+
</body>
|
|
290
|
+
</html>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## 🎨 Paleta de Colores ONPE
|
|
294
|
+
|
|
295
|
+
### Colores Principales
|
|
296
|
+
- **Azul Principal**: `#003770` - Color institucional principal
|
|
297
|
+
- **Sky Blue**: `#0073cf` - Color secundario
|
|
298
|
+
- **Sky Blue Light**: `#69b2e8` - Color claro
|
|
299
|
+
- **Light Sky Blue**: `#aaeff6` - Color muy claro
|
|
300
|
+
|
|
301
|
+
### Colores de Acento
|
|
302
|
+
- **Amarillo**: `#ffb81c` - Para alertas y destacados
|
|
303
|
+
- **Verde**: `#76bd43` - Para éxito y confirmaciones
|
|
304
|
+
- **Rojo**: `#e3002b` - Para errores y advertencias
|
|
305
|
+
|
|
306
|
+
### Escala de Grises
|
|
307
|
+
- **Dark Gray**: `#4f4f4f` - Texto principal
|
|
308
|
+
- **Gray**: `#bcbcbc` - Texto secundario
|
|
309
|
+
- **Gray Light**: `#bdbdbd` - Texto terciario
|
|
310
|
+
- **Gray Extra Light**: `#f2f2f2` - Fondos suaves
|
|
311
|
+
|
|
312
|
+
## 🔗 Dependencias entre Componentes
|
|
313
|
+
|
|
314
|
+
### Mapa de Dependencias
|
|
315
|
+
```
|
|
316
|
+
Modal
|
|
317
|
+
├── Portal (requerido)
|
|
318
|
+
├── Overlay (requerido)
|
|
319
|
+
└── IconClose (requerido)
|
|
320
|
+
|
|
321
|
+
ModalBrowserIncompatible
|
|
322
|
+
├── Modal (requerido)
|
|
323
|
+
├── IconWarning (requerido)
|
|
324
|
+
├── IconChromeColor (requerido)
|
|
325
|
+
├── IconSafariColor (requerido)
|
|
326
|
+
├── IconMozillaColor (requerido)
|
|
327
|
+
└── IconEdgeColor (requerido)
|
|
328
|
+
|
|
329
|
+
ModalSystemIncompatible
|
|
330
|
+
├── Modal (requerido)
|
|
331
|
+
├── IconWarning (requerido)
|
|
332
|
+
├── IconWindow (requerido)
|
|
333
|
+
├── IconAndroid (requerido)
|
|
334
|
+
└── IconApple (requerido)
|
|
335
|
+
|
|
336
|
+
Portal
|
|
337
|
+
└── react-dom (createPortal)
|
|
338
|
+
|
|
339
|
+
Overlay
|
|
340
|
+
└── (sin dependencias externas)
|
|
341
|
+
|
|
342
|
+
Button
|
|
343
|
+
└── (sin dependencias externas)
|
|
344
|
+
|
|
345
|
+
Show
|
|
346
|
+
└── (sin dependencias externas)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Instalación Automática de Dependencias
|
|
350
|
+
|
|
351
|
+
**Modal** - Instala automáticamente sus dependencias:
|
|
352
|
+
```bash
|
|
353
|
+
npx @onpe/ui add modal
|
|
354
|
+
# Esto instalará automáticamente:
|
|
355
|
+
# - Portal.tsx
|
|
356
|
+
# - Overlay.tsx
|
|
357
|
+
# - IconClose.tsx (si está disponible)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**ModalBrowserIncompatible** - Instala automáticamente sus dependencias:
|
|
361
|
+
```bash
|
|
362
|
+
npx @onpe/ui add modal-browser-incompatible
|
|
363
|
+
# Esto instalará automáticamente:
|
|
364
|
+
# - Modal.tsx
|
|
365
|
+
# - IconWarning.tsx
|
|
366
|
+
# - IconChromeColor.tsx
|
|
367
|
+
# - IconSafariColor.tsx
|
|
368
|
+
# - IconMozillaColor.tsx
|
|
369
|
+
# - IconEdgeColor.tsx
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**ModalSystemIncompatible** - Instala automáticamente sus dependencias:
|
|
373
|
+
```bash
|
|
374
|
+
npx @onpe/ui add modal-system-incompatible
|
|
375
|
+
# Esto instalará automáticamente:
|
|
376
|
+
# - Modal.tsx
|
|
377
|
+
# - IconWarning.tsx
|
|
378
|
+
# - IconWindow.tsx
|
|
379
|
+
# - IconAndroid.tsx
|
|
380
|
+
# - IconApple.tsx
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Otros componentes** - Instalación independiente:
|
|
384
|
+
```bash
|
|
385
|
+
npx @onpe/ui add button # Sin dependencias
|
|
386
|
+
npx @onpe/ui add portal # Sin dependencias
|
|
387
|
+
npx @onpe/ui add overlay # Sin dependencias
|
|
388
|
+
npx @onpe/ui add show # Sin dependencias
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Estructura de Archivos Después de la Instalación
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
src/
|
|
395
|
+
└── components/
|
|
396
|
+
├── ui/ # shadcn/ui (si está instalado)
|
|
397
|
+
│ ├── button.tsx
|
|
398
|
+
│ └── input.tsx
|
|
399
|
+
├── onpe-ui/ # ONPE UI - Componentes
|
|
400
|
+
│ ├── Button.tsx
|
|
401
|
+
│ ├── Modal.tsx
|
|
402
|
+
│ ├── Overlay.tsx
|
|
403
|
+
│ ├── Portal.tsx
|
|
404
|
+
│ └── Show.tsx
|
|
405
|
+
└── onpe-icons/ # ONPE UI - Iconos
|
|
406
|
+
├── IconClose.tsx
|
|
407
|
+
├── IconCheck.tsx
|
|
408
|
+
├── IconChrome.tsx
|
|
409
|
+
├── IconFirefox.tsx
|
|
410
|
+
└── IconWindows.tsx
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## 🧩 Componentes Disponibles
|
|
414
|
+
|
|
415
|
+
### Button
|
|
416
|
+
|
|
417
|
+
Botón versátil con múltiples colores y tamaños.
|
|
418
|
+
|
|
419
|
+
#### Ejemplo Básico
|
|
420
|
+
```tsx
|
|
421
|
+
import { Button } from '@onpe/ui/components';
|
|
422
|
+
|
|
423
|
+
function App() {
|
|
424
|
+
return (
|
|
425
|
+
<div className="space-y-4 p-4">
|
|
426
|
+
<h2 className="text-2xl font-bold">Botones ONPE</h2>
|
|
427
|
+
|
|
428
|
+
{/* Colores disponibles */}
|
|
429
|
+
<div className="space-y-2">
|
|
430
|
+
<h3 className="text-lg font-semibold">Colores:</h3>
|
|
431
|
+
<div className="flex flex-wrap gap-2">
|
|
432
|
+
<Button color="primary" title="Primario" />
|
|
433
|
+
<Button color="blue" title="Azul" />
|
|
434
|
+
<Button color="skyblue" title="Sky Blue" />
|
|
435
|
+
<Button color="green" title="Verde" />
|
|
436
|
+
<Button color="yellow" title="Amarillo" />
|
|
437
|
+
<Button color="red" title="Rojo" />
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
{/* Tamaños */}
|
|
442
|
+
<div className="space-y-2">
|
|
443
|
+
<h3 className="text-lg font-semibold">Tamaños:</h3>
|
|
444
|
+
<div className="flex items-center gap-2">
|
|
445
|
+
<Button color="primary" title="Pequeño" size="small" />
|
|
446
|
+
<Button color="primary" title="Mediano" size="normal" />
|
|
447
|
+
<Button color="primary" title="Grande" size="large" />
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
{/* Estados */}
|
|
452
|
+
<div className="space-y-2">
|
|
453
|
+
<h3 className="text-lg font-semibold">Estados:</h3>
|
|
454
|
+
<div className="flex gap-2">
|
|
455
|
+
<Button color="primary" title="Normal" />
|
|
456
|
+
<Button color="primary" title="Deshabilitado" disabled />
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### Ejemplo con Funcionalidad
|
|
465
|
+
```tsx
|
|
466
|
+
import { Button } from '@onpe/ui/components';
|
|
467
|
+
import { useState } from 'react';
|
|
468
|
+
|
|
469
|
+
function VotingApp() {
|
|
470
|
+
const [voted, setVoted] = useState(false);
|
|
471
|
+
const [loading, setLoading] = useState(false);
|
|
472
|
+
|
|
473
|
+
const handleVote = async () => {
|
|
474
|
+
setLoading(true);
|
|
475
|
+
// Simular llamada a API
|
|
476
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
477
|
+
setVoted(true);
|
|
478
|
+
setLoading(false);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
return (
|
|
482
|
+
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
|
|
483
|
+
<h2 className="text-xl font-bold mb-4">Sistema de Votación</h2>
|
|
484
|
+
|
|
485
|
+
{!voted ? (
|
|
486
|
+
<div className="space-y-4">
|
|
487
|
+
<p className="text-gray-600">¿Desea votar por esta opción?</p>
|
|
488
|
+
<Button
|
|
489
|
+
color="primary"
|
|
490
|
+
title={loading ? "Procesando..." : "Votar Ahora"}
|
|
491
|
+
onClick={handleVote}
|
|
492
|
+
disabled={loading}
|
|
493
|
+
size="large"
|
|
494
|
+
/>
|
|
495
|
+
</div>
|
|
496
|
+
) : (
|
|
497
|
+
<div className="text-center">
|
|
498
|
+
<p className="text-green-600 font-semibold mb-4">¡Voto registrado exitosamente!</p>
|
|
499
|
+
<Button
|
|
500
|
+
color="green"
|
|
501
|
+
title="Ver Resultados"
|
|
502
|
+
onClick={() => setVoted(false)}
|
|
503
|
+
/>
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
506
|
+
</div>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Props del Button
|
|
512
|
+
|
|
513
|
+
| Prop | Tipo | Default | Descripción |
|
|
514
|
+
|------|------|---------|-------------|
|
|
515
|
+
| `color` | `'primary' \| 'blue' \| 'skyblue' \| 'skyblue-light' \| 'yellow' \| 'light-skyblue' \| 'gray' \| 'gray-light' \| 'gray-extra-light' \| 'red' \| 'dark-gray' \| 'green' \| 'yellow-light'` | `'primary'` | Color del botón |
|
|
516
|
+
| `title` | `string` | - | Texto del botón (requerido) |
|
|
517
|
+
| `size` | `'small' \| 'normal' \| 'large'` | `'normal'` | Tamaño del botón |
|
|
518
|
+
| `disabled` | `boolean` | `false` | Estado deshabilitado |
|
|
519
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
520
|
+
|
|
521
|
+
### Modal
|
|
522
|
+
|
|
523
|
+
Componente modal para mostrar contenido en overlay.
|
|
524
|
+
|
|
525
|
+
#### Ejemplo Básico
|
|
526
|
+
```tsx
|
|
527
|
+
import { Modal } from '@onpe/ui/components';
|
|
528
|
+
import { useState } from 'react';
|
|
529
|
+
|
|
530
|
+
function App() {
|
|
531
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
532
|
+
|
|
533
|
+
return (
|
|
534
|
+
<div className="p-4">
|
|
535
|
+
<button
|
|
536
|
+
onClick={() => setIsOpen(true)}
|
|
537
|
+
className="bg-blue-500 text-white px-4 py-2 rounded"
|
|
538
|
+
>
|
|
539
|
+
Abrir Modal
|
|
540
|
+
</button>
|
|
541
|
+
|
|
542
|
+
<Modal
|
|
543
|
+
isOpen={isOpen}
|
|
544
|
+
onClose={() => setIsOpen(false)}
|
|
545
|
+
closeButton={true}
|
|
546
|
+
overlayColor="blue"
|
|
547
|
+
>
|
|
548
|
+
<div className="p-6">
|
|
549
|
+
<h2 className="text-xl font-bold mb-4">Título del Modal</h2>
|
|
550
|
+
<p className="mb-4">Este es el contenido del modal.</p>
|
|
551
|
+
<button
|
|
552
|
+
onClick={() => setIsOpen(false)}
|
|
553
|
+
className="bg-gray-500 text-white px-4 py-2 rounded"
|
|
554
|
+
>
|
|
555
|
+
Cerrar
|
|
556
|
+
</button>
|
|
557
|
+
</div>
|
|
558
|
+
</Modal>
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### Ejemplo Avanzado - Modal de Confirmación
|
|
565
|
+
```tsx
|
|
566
|
+
import { Modal } from '@onpe/ui/components';
|
|
567
|
+
import { useState } from 'react';
|
|
568
|
+
|
|
569
|
+
function DeleteConfirmation() {
|
|
570
|
+
const [showModal, setShowModal] = useState(false);
|
|
571
|
+
const [itemToDelete, setItemToDelete] = useState('');
|
|
572
|
+
|
|
573
|
+
const handleDelete = (itemName) => {
|
|
574
|
+
setItemToDelete(itemName);
|
|
575
|
+
setShowModal(true);
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const confirmDelete = () => {
|
|
579
|
+
// Lógica para eliminar el elemento
|
|
580
|
+
console.log(`Eliminando: ${itemToDelete}`);
|
|
581
|
+
setShowModal(false);
|
|
582
|
+
setItemToDelete('');
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
return (
|
|
586
|
+
<div className="p-4">
|
|
587
|
+
<div className="space-y-2">
|
|
588
|
+
<button
|
|
589
|
+
onClick={() => handleDelete('Usuario 1')}
|
|
590
|
+
className="bg-red-500 text-white px-4 py-2 rounded mr-2"
|
|
591
|
+
>
|
|
592
|
+
Eliminar Usuario 1
|
|
593
|
+
</button>
|
|
594
|
+
<button
|
|
595
|
+
onClick={() => handleDelete('Documento 2')}
|
|
596
|
+
className="bg-red-500 text-white px-4 py-2 rounded mr-2"
|
|
597
|
+
>
|
|
598
|
+
Eliminar Documento 2
|
|
599
|
+
</button>
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
<Modal
|
|
603
|
+
isOpen={showModal}
|
|
604
|
+
onClose={() => setShowModal(false)}
|
|
605
|
+
closeButton={true}
|
|
606
|
+
overlayColor="red"
|
|
607
|
+
closeDisabled={false}
|
|
608
|
+
>
|
|
609
|
+
<div className="p-6 text-center">
|
|
610
|
+
<div className="mb-4">
|
|
611
|
+
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
|
|
612
|
+
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
613
|
+
<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" />
|
|
614
|
+
</svg>
|
|
615
|
+
</div>
|
|
616
|
+
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
617
|
+
Confirmar Eliminación
|
|
618
|
+
</h3>
|
|
619
|
+
<p className="text-sm text-gray-500">
|
|
620
|
+
¿Está seguro de que desea eliminar <strong>{itemToDelete}</strong>?
|
|
621
|
+
Esta acción no se puede deshacer.
|
|
622
|
+
</p>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<div className="flex justify-center space-x-3">
|
|
626
|
+
<button
|
|
627
|
+
onClick={() => setShowModal(false)}
|
|
628
|
+
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium"
|
|
629
|
+
>
|
|
630
|
+
Cancelar
|
|
631
|
+
</button>
|
|
632
|
+
<button
|
|
633
|
+
onClick={confirmDelete}
|
|
634
|
+
className="bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium"
|
|
635
|
+
>
|
|
636
|
+
Eliminar
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
</Modal>
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
#### Ejemplo - Modal de Formulario
|
|
647
|
+
```tsx
|
|
648
|
+
import { Modal } from '@onpe/ui/components';
|
|
649
|
+
import { useState } from 'react';
|
|
650
|
+
|
|
651
|
+
function UserForm() {
|
|
652
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
653
|
+
const [formData, setFormData] = useState({
|
|
654
|
+
name: '',
|
|
655
|
+
email: '',
|
|
656
|
+
phone: ''
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
const handleSubmit = (e) => {
|
|
660
|
+
e.preventDefault();
|
|
661
|
+
console.log('Datos del formulario:', formData);
|
|
662
|
+
setIsOpen(false);
|
|
663
|
+
setFormData({ name: '', email: '', phone: '' });
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
return (
|
|
667
|
+
<div className="p-4">
|
|
668
|
+
<button
|
|
669
|
+
onClick={() => setIsOpen(true)}
|
|
670
|
+
className="bg-green-500 text-white px-4 py-2 rounded"
|
|
671
|
+
>
|
|
672
|
+
Agregar Usuario
|
|
673
|
+
</button>
|
|
674
|
+
|
|
675
|
+
<Modal
|
|
676
|
+
isOpen={isOpen}
|
|
677
|
+
onClose={() => setIsOpen(false)}
|
|
678
|
+
closeButton={true}
|
|
679
|
+
overlayColor="skyblue"
|
|
680
|
+
>
|
|
681
|
+
<div className="p-6">
|
|
682
|
+
<h2 className="text-xl font-bold mb-4">Nuevo Usuario</h2>
|
|
683
|
+
|
|
684
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
685
|
+
<div>
|
|
686
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
687
|
+
Nombre Completo
|
|
688
|
+
</label>
|
|
689
|
+
<input
|
|
690
|
+
type="text"
|
|
691
|
+
value={formData.name}
|
|
692
|
+
onChange={(e) => setFormData({...formData, name: e.target.value})}
|
|
693
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
694
|
+
required
|
|
695
|
+
/>
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
<div>
|
|
699
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
700
|
+
Correo Electrónico
|
|
701
|
+
</label>
|
|
702
|
+
<input
|
|
703
|
+
type="email"
|
|
704
|
+
value={formData.email}
|
|
705
|
+
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
|
706
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
707
|
+
required
|
|
708
|
+
/>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<div>
|
|
712
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
713
|
+
Teléfono
|
|
714
|
+
</label>
|
|
715
|
+
<input
|
|
716
|
+
type="tel"
|
|
717
|
+
value={formData.phone}
|
|
718
|
+
onChange={(e) => setFormData({...formData, phone: e.target.value})}
|
|
719
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
720
|
+
/>
|
|
721
|
+
</div>
|
|
722
|
+
|
|
723
|
+
<div className="flex justify-end space-x-3 pt-4">
|
|
724
|
+
<button
|
|
725
|
+
type="button"
|
|
726
|
+
onClick={() => setIsOpen(false)}
|
|
727
|
+
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md"
|
|
728
|
+
>
|
|
729
|
+
Cancelar
|
|
730
|
+
</button>
|
|
731
|
+
<button
|
|
732
|
+
type="submit"
|
|
733
|
+
className="bg-blue-600 text-white px-4 py-2 rounded-md"
|
|
734
|
+
>
|
|
735
|
+
Guardar Usuario
|
|
736
|
+
</button>
|
|
737
|
+
</div>
|
|
738
|
+
</form>
|
|
739
|
+
</div>
|
|
740
|
+
</Modal>
|
|
741
|
+
</div>
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Overlay
|
|
747
|
+
|
|
748
|
+
Componente overlay para superponer contenido.
|
|
749
|
+
|
|
750
|
+
```tsx
|
|
751
|
+
import { Overlay } from '@onpe/ui/components';
|
|
752
|
+
|
|
753
|
+
function App() {
|
|
754
|
+
return (
|
|
755
|
+
<Overlay>
|
|
756
|
+
<div>Contenido superpuesto</div>
|
|
757
|
+
</Overlay>
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### Portal
|
|
763
|
+
|
|
764
|
+
Componente portal para renderizar fuera del DOM padre.
|
|
765
|
+
|
|
766
|
+
```tsx
|
|
767
|
+
import { Portal } from '@onpe/ui/components';
|
|
768
|
+
|
|
769
|
+
function App() {
|
|
770
|
+
return (
|
|
771
|
+
<Portal>
|
|
772
|
+
<div>Contenido renderizado en portal</div>
|
|
773
|
+
</Portal>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Show
|
|
779
|
+
|
|
780
|
+
Componente condicional para mostrar/ocultar contenido.
|
|
781
|
+
|
|
782
|
+
```tsx
|
|
783
|
+
import { Show } from '@onpe/ui/components';
|
|
784
|
+
|
|
785
|
+
function App() {
|
|
786
|
+
const [visible, setVisible] = useState(true);
|
|
787
|
+
|
|
788
|
+
return (
|
|
789
|
+
<Show when={visible}>
|
|
790
|
+
<div>Contenido visible</div>
|
|
791
|
+
</Show>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### ModalConfirm
|
|
797
|
+
|
|
798
|
+
Modal de confirmación para acciones importantes con colores dinámicos.
|
|
799
|
+
|
|
800
|
+
```tsx
|
|
801
|
+
import { ModalConfirm } from '@onpe/ui/components';
|
|
802
|
+
|
|
803
|
+
function App() {
|
|
804
|
+
const [showConfirm, setShowConfirm] = useState(false);
|
|
805
|
+
|
|
806
|
+
return (
|
|
807
|
+
<div className="space-y-4">
|
|
808
|
+
{/* Modal Azul (Por Defecto) */}
|
|
809
|
+
<ModalConfirm
|
|
810
|
+
isOpen={showConfirm}
|
|
811
|
+
onClose={() => setShowConfirm(false)}
|
|
812
|
+
onConfirm={() => {
|
|
813
|
+
// Acción a confirmar
|
|
814
|
+
setShowConfirm(false);
|
|
815
|
+
}}
|
|
816
|
+
title="Confirmar acción"
|
|
817
|
+
message="¿Estás seguro de realizar esta acción?"
|
|
818
|
+
color="blue" // Por defecto
|
|
819
|
+
/>
|
|
820
|
+
|
|
821
|
+
{/* Modal Rojo para Advertencias */}
|
|
822
|
+
<ModalConfirm
|
|
823
|
+
isOpen={showConfirm}
|
|
824
|
+
onClose={() => setShowConfirm(false)}
|
|
825
|
+
onConfirm={() => {
|
|
826
|
+
// Acción peligrosa
|
|
827
|
+
setShowConfirm(false);
|
|
828
|
+
}}
|
|
829
|
+
title="Advertencia"
|
|
830
|
+
message="Esta acción es irreversible y no se puede deshacer."
|
|
831
|
+
color="red" // Color rojo para advertencias
|
|
832
|
+
icon="warning"
|
|
833
|
+
/>
|
|
834
|
+
</div>
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### Props del ModalConfirm
|
|
840
|
+
|
|
841
|
+
| Prop | Tipo | Default | Descripción |
|
|
842
|
+
|------|------|---------|-------------|
|
|
843
|
+
| `isOpen` | `boolean` | - | Estado de apertura del modal (requerido) |
|
|
844
|
+
| `onClose` | `function` | - | Función para cerrar el modal (requerido) |
|
|
845
|
+
| `onConfirm` | `function` | - | Función para confirmar la acción (requerido) |
|
|
846
|
+
| `title` | `string` | - | Título del modal (requerido) |
|
|
847
|
+
| `message` | `string` | - | Mensaje del modal (requerido) |
|
|
848
|
+
| `color` | `'blue' \| 'red'` | `'blue'` | Color del icono y título |
|
|
849
|
+
| `icon` | `'warning' \| 'success'` | `'warning'` | Tipo de icono a mostrar |
|
|
850
|
+
| `confirmText` | `string` | `'Confirmar'` | Texto del botón de confirmación |
|
|
851
|
+
| `cancelText` | `string` | `'Cancelar'` | Texto del botón de cancelación |
|
|
852
|
+
|
|
853
|
+
### ModalLoading
|
|
854
|
+
|
|
855
|
+
Modal de carga para mostrar estados de procesamiento.
|
|
856
|
+
|
|
857
|
+
```tsx
|
|
858
|
+
import { ModalLoading } from '@onpe/ui/components';
|
|
859
|
+
|
|
860
|
+
function App() {
|
|
861
|
+
const [loading, setLoading] = useState(false);
|
|
862
|
+
|
|
863
|
+
return (
|
|
864
|
+
<ModalLoading
|
|
865
|
+
isOpen={loading}
|
|
866
|
+
message="Procesando información..."
|
|
867
|
+
/>
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### ModalBrowserIncompatible
|
|
873
|
+
|
|
874
|
+
Modal mejorado para mostrar cuando el navegador no es compatible con el sistema de votación.
|
|
875
|
+
|
|
876
|
+
```tsx
|
|
877
|
+
import { ModalBrowserIncompatible } from '@onpe/ui/components';
|
|
878
|
+
|
|
879
|
+
function App() {
|
|
880
|
+
const [showBrowserModal, setShowBrowserModal] = useState(false);
|
|
881
|
+
|
|
882
|
+
return (
|
|
883
|
+
<ModalBrowserIncompatible
|
|
884
|
+
isOpen={showBrowserModal}
|
|
885
|
+
onClose={() => setShowBrowserModal(false)}
|
|
886
|
+
/>
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
**Características del modal mejorado:**
|
|
892
|
+
- ✅ Mensaje claro sobre incompatibilidad
|
|
893
|
+
- ✅ Lista visual de navegadores compatibles con nombres
|
|
894
|
+
- ✅ Información sobre seguridad y versiones
|
|
895
|
+
- ✅ Diseño responsive y accesible
|
|
896
|
+
- ✅ Colores institucionales ONPE
|
|
897
|
+
|
|
898
|
+
### ModalSystemIncompatible
|
|
899
|
+
|
|
900
|
+
Modal mejorado para mostrar cuando el sistema operativo no es compatible con ONPEID.
|
|
901
|
+
|
|
902
|
+
```tsx
|
|
903
|
+
import { ModalSystemIncompatible } from '@onpe/ui/components';
|
|
904
|
+
|
|
905
|
+
function App() {
|
|
906
|
+
const [showSystemModal, setShowSystemModal] = useState(false);
|
|
907
|
+
|
|
908
|
+
return (
|
|
909
|
+
<ModalSystemIncompatible
|
|
910
|
+
isOpen={showSystemModal}
|
|
911
|
+
onClose={() => setShowSystemModal(false)}
|
|
912
|
+
/>
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
**Características del modal mejorado:**
|
|
918
|
+
- ✅ Información específica sobre ONPEID
|
|
919
|
+
- ✅ Lista de sistemas operativos compatibles con versiones mínimas
|
|
920
|
+
- ✅ Alternativa de acceso web
|
|
921
|
+
- ✅ Información de seguridad sobre fuentes oficiales
|
|
922
|
+
- ✅ Diseño intuitivo y profesional
|
|
923
|
+
|
|
924
|
+
## 🎯 Hooks Disponibles
|
|
925
|
+
|
|
926
|
+
### useDebounce
|
|
927
|
+
|
|
928
|
+
Hook para retrasar la ejecución de funciones.
|
|
929
|
+
|
|
930
|
+
```tsx
|
|
931
|
+
import { useDebounce } from '@onpe/ui/hooks';
|
|
932
|
+
|
|
933
|
+
function SearchComponent() {
|
|
934
|
+
const [query, setQuery] = useState('');
|
|
935
|
+
const debouncedQuery = useDebounce(query, 500);
|
|
936
|
+
|
|
937
|
+
useEffect(() => {
|
|
938
|
+
// Buscar solo después de 500ms sin cambios
|
|
939
|
+
searchAPI(debouncedQuery);
|
|
940
|
+
}, [debouncedQuery]);
|
|
941
|
+
|
|
942
|
+
return (
|
|
943
|
+
<input
|
|
944
|
+
value={query}
|
|
945
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
946
|
+
placeholder="Buscar..."
|
|
947
|
+
/>
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
### useLocalStorage
|
|
953
|
+
|
|
954
|
+
Hook para manejar localStorage de forma reactiva.
|
|
955
|
+
|
|
956
|
+
```tsx
|
|
957
|
+
import { useLocalStorage } from '@onpe/ui/hooks';
|
|
958
|
+
|
|
959
|
+
function SettingsComponent() {
|
|
960
|
+
const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
961
|
+
|
|
962
|
+
return (
|
|
963
|
+
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
|
|
964
|
+
<option value="light">Claro</option>
|
|
965
|
+
<option value="dark">Oscuro</option>
|
|
966
|
+
</select>
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
## 🎨 Iconos Disponibles
|
|
972
|
+
|
|
973
|
+
La librería incluye una colección completa de iconos organizados por categorías:
|
|
974
|
+
|
|
975
|
+
### Iconos de Acciones
|
|
976
|
+
- Iconos para acciones comunes (editar, eliminar, guardar, etc.)
|
|
977
|
+
|
|
978
|
+
### Iconos de Navegadores
|
|
979
|
+
- Iconos de navegadores web (Chrome, Firefox, Safari, Edge, etc.)
|
|
980
|
+
|
|
981
|
+
### Iconos de Sistemas Operativos
|
|
982
|
+
- Iconos de sistemas operativos (Windows, macOS, Linux, etc.)
|
|
983
|
+
|
|
984
|
+
### Iconos ONPE
|
|
985
|
+
- Iconos específicos de la institución ONPE
|
|
986
|
+
|
|
987
|
+
### Logos
|
|
988
|
+
- Logotipos oficiales y marcas
|
|
989
|
+
|
|
990
|
+
```tsx
|
|
991
|
+
import {
|
|
992
|
+
IconChrome,
|
|
993
|
+
IconFirefox,
|
|
994
|
+
IconSafari,
|
|
995
|
+
IconWindows,
|
|
996
|
+
IconMacOS
|
|
997
|
+
} from '@onpe/ui/icons';
|
|
998
|
+
|
|
999
|
+
function App() {
|
|
1000
|
+
return (
|
|
1001
|
+
<div>
|
|
1002
|
+
<IconChrome className="w-6 h-6" />
|
|
1003
|
+
<IconFirefox className="w-6 h-6" />
|
|
1004
|
+
<IconSafari className="w-6 h-6" />
|
|
1005
|
+
</div>
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
## 🛠️ Utilidades
|
|
1011
|
+
|
|
1012
|
+
### formatDate
|
|
1013
|
+
|
|
1014
|
+
Función para formatear fechas según estándares peruanos.
|
|
1015
|
+
|
|
1016
|
+
```tsx
|
|
1017
|
+
import { formatDate } from '@onpe/ui/utils';
|
|
1018
|
+
|
|
1019
|
+
const fecha = new Date('2024-04-14');
|
|
1020
|
+
const fechaFormateada = formatDate(fecha, 'dd/mm/yyyy');
|
|
1021
|
+
console.log(fechaFormateada); // "14/04/2024"
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### validateEmail
|
|
1025
|
+
|
|
1026
|
+
Función para validar direcciones de correo electrónico.
|
|
1027
|
+
|
|
1028
|
+
```tsx
|
|
1029
|
+
import { validateEmail } from '@onpe/ui/utils';
|
|
1030
|
+
|
|
1031
|
+
const email = 'usuario@onpe.gob.pe';
|
|
1032
|
+
const esValido = validateEmail(email);
|
|
1033
|
+
console.log(esValido); // true
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
## 📱 Breakpoints Responsive
|
|
1037
|
+
|
|
1038
|
+
La librería incluye breakpoints personalizados para ONPE:
|
|
1039
|
+
|
|
1040
|
+
- `sm`: 640px
|
|
1041
|
+
- `md`: 768px
|
|
1042
|
+
- `lg`: 1024px
|
|
1043
|
+
- `2lg`: 1200px
|
|
1044
|
+
- `xl`: 1280px
|
|
1045
|
+
- `2xl`: 1536px
|
|
1046
|
+
- `3xl`: 1650px
|
|
1047
|
+
|
|
1048
|
+
```css
|
|
1049
|
+
/* Ejemplo de uso */
|
|
1050
|
+
@media (min-width: 1200px) {
|
|
1051
|
+
.container {
|
|
1052
|
+
max-width: 1200px;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
## 🎨 Clases de Utilidad
|
|
1058
|
+
|
|
1059
|
+
### Colores de Texto
|
|
1060
|
+
```css
|
|
1061
|
+
.text-onpe-ui-blue /* Azul principal */
|
|
1062
|
+
.text-onpe-ui-skyblue /* Sky blue */
|
|
1063
|
+
.text-onpe-ui-yellow /* Amarillo */
|
|
1064
|
+
.text-onpe-ui-green /* Verde */
|
|
1065
|
+
.text-onpe-ui-red /* Rojo */
|
|
1066
|
+
.text-onpe-ui-gray /* Gris */
|
|
1067
|
+
.text-onpe-ui-dark-gray /* Gris oscuro */
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
### Colores de Fondo
|
|
1071
|
+
```css
|
|
1072
|
+
.bg-onpe-ui-blue /* Fondo azul */
|
|
1073
|
+
.bg-onpe-ui-skyblue /* Fondo sky blue */
|
|
1074
|
+
.bg-onpe-ui-yellow /* Fondo amarillo */
|
|
1075
|
+
.bg-onpe-ui-green /* Fondo verde */
|
|
1076
|
+
.bg-onpe-ui-red /* Fondo rojo */
|
|
1077
|
+
.bg-onpe-ui-gray /* Fondo gris */
|
|
1078
|
+
.bg-onpe-ui-gray-light /* Fondo gris claro */
|
|
1079
|
+
.bg-onpe-ui-gray-extra-light /* Fondo gris muy claro */
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
## 🛡️ Compatibilidad con CSP (Content Security Policy)
|
|
1083
|
+
|
|
1084
|
+
**La librería es completamente compatible con Content Security Policy estricto.**
|
|
1085
|
+
|
|
1086
|
+
### ✅ Características CSP
|
|
1087
|
+
|
|
1088
|
+
- **Archivos CSS externos**: No usa estilos inline que violen CSP
|
|
1089
|
+
- **Sin `'unsafe-inline'`**: Compatible con `style-src 'self'`
|
|
1090
|
+
- **Variables CSS en `:root`**: Disponibles globalmente sin violar políticas
|
|
1091
|
+
- **Archivos estáticos**: Todos los recursos se sirven como archivos externos
|
|
1092
|
+
|
|
1093
|
+
### 🔧 Configuración CSP Recomendada
|
|
1094
|
+
|
|
1095
|
+
```html
|
|
1096
|
+
<!-- En tu HTML head -->
|
|
1097
|
+
<meta http-equiv="Content-Security-Policy"
|
|
1098
|
+
content="style-src 'self' https://fonts.googleapis.com;
|
|
1099
|
+
script-src 'self';
|
|
1100
|
+
img-src 'self' data:;">
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
### 📋 Instrucciones para Proyectos con CSP
|
|
1104
|
+
|
|
1105
|
+
**1. Instalar la librería:**
|
|
1106
|
+
```bash
|
|
1107
|
+
npm install @onpe/ui@1.2.40
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
**2. Importar CSS (CRÍTICO):**
|
|
1111
|
+
```tsx
|
|
1112
|
+
// ✅ CORRECTO - Esto carga el CSS externo
|
|
1113
|
+
import '@onpe/ui/dist/index.css';
|
|
1114
|
+
import { ModalConfirm, Button } from '@onpe/ui/components';
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
**3. Verificar que las variables CSS estén disponibles:**
|
|
1118
|
+
```tsx
|
|
1119
|
+
// Las variables CSS se definen automáticamente en :root
|
|
1120
|
+
function MiComponente() {
|
|
1121
|
+
return (
|
|
1122
|
+
<div style={{ color: 'var(--onpe-ui-blue)' }}>
|
|
1123
|
+
Este texto usa el color azul ONPE
|
|
1124
|
+
</div>
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### ⚠️ Problemas Comunes con CSP
|
|
1130
|
+
|
|
1131
|
+
**Error: "Refused to apply inline style"**
|
|
1132
|
+
```bash
|
|
1133
|
+
# ❌ PROBLEMA: Estilos inline bloqueados por CSP
|
|
1134
|
+
# ✅ SOLUCIÓN: Usar archivos CSS externos (ya implementado)
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
**Error: "Variable CSS not defined"**
|
|
1138
|
+
```tsx
|
|
1139
|
+
// ❌ PROBLEMA: No importar el CSS
|
|
1140
|
+
import { Button } from '@onpe/ui/components';
|
|
1141
|
+
|
|
1142
|
+
// ✅ SOLUCIÓN: Importar CSS primero
|
|
1143
|
+
import '@onpe/ui/dist/index.css';
|
|
1144
|
+
import { Button } from '@onpe/ui/components';
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
## 📋 Versiones
|
|
1148
|
+
|
|
1149
|
+
- **v1.2.40** - Versión actual con CSP compatible
|
|
1150
|
+
- Compatible con React 16.8+
|
|
1151
|
+
- TailwindCSS v4
|
|
1152
|
+
- TypeScript 5.3+
|
|
1153
|
+
- CSP (Content Security Policy) compatible
|
|
1154
|
+
|
|
1155
|
+
## 🔧 Desarrollo
|
|
1156
|
+
|
|
1157
|
+
### Requisitos
|
|
1158
|
+
- Node.js 18+
|
|
1159
|
+
- npm 9+
|
|
1160
|
+
|
|
1161
|
+
### Instalación para desarrollo
|
|
1162
|
+
```bash
|
|
1163
|
+
git clone https://github.com/ricardosv46/onpe-ui.git
|
|
1164
|
+
cd onpe-ui
|
|
1165
|
+
npm install
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
### Scripts disponibles
|
|
1169
|
+
```bash
|
|
1170
|
+
npm run build # Construir para producción
|
|
1171
|
+
npm run dev # Desarrollo con watch mode
|
|
1172
|
+
npm run storybook # Ver componentes en Storybook
|
|
1173
|
+
npm run lint # Verificar código
|
|
1174
|
+
npm run lint:fix # Corregir problemas de linting
|
|
1175
|
+
npm run type-check # Verificar tipos TypeScript
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
## 📄 Licencia
|
|
1179
|
+
|
|
1180
|
+
MIT © ONPE - Oficina Nacional de Procesos Electorales
|
|
1181
|
+
|
|
1182
|
+
## 🤝 Contribuir
|
|
1183
|
+
|
|
1184
|
+
1. Fork el proyecto
|
|
1185
|
+
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
|
|
1186
|
+
3. Commit tus cambios (`git commit -m 'Agregar nueva funcionalidad'`)
|
|
1187
|
+
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
|
|
1188
|
+
5. Abre un Pull Request
|
|
1189
|
+
|
|
1190
|
+
## 🚀 Ejemplos Completos de Aplicaciones
|
|
1191
|
+
|
|
1192
|
+
### Aplicación de Votación Electrónica
|
|
1193
|
+
```tsx
|
|
1194
|
+
import { Button, Modal, Show } from '@onpe/ui/components';
|
|
1195
|
+
import { useState } from 'react';
|
|
1196
|
+
|
|
1197
|
+
function VotingApp() {
|
|
1198
|
+
const [currentStep, setCurrentStep] = useState(1);
|
|
1199
|
+
const [selectedCandidate, setSelectedCandidate] = useState('');
|
|
1200
|
+
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
|
1201
|
+
const [voted, setVoted] = useState(false);
|
|
1202
|
+
|
|
1203
|
+
const candidates = [
|
|
1204
|
+
{ id: '1', name: 'Juan Pérez', party: 'Partido Democrático' },
|
|
1205
|
+
{ id: '2', name: 'María García', party: 'Partido Progresista' },
|
|
1206
|
+
{ id: '3', name: 'Carlos López', party: 'Partido Independiente' }
|
|
1207
|
+
];
|
|
1208
|
+
|
|
1209
|
+
const handleVote = () => {
|
|
1210
|
+
setVoted(true);
|
|
1211
|
+
setShowConfirmModal(false);
|
|
1212
|
+
setCurrentStep(4);
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
return (
|
|
1216
|
+
<div className="min-h-screen bg-gray-50 py-8">
|
|
1217
|
+
<div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
|
|
1218
|
+
<div className="text-center mb-8">
|
|
1219
|
+
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
1220
|
+
Sistema de Votación ONPE
|
|
1221
|
+
</h1>
|
|
1222
|
+
<p className="text-gray-600">
|
|
1223
|
+
Seleccione su candidato preferido
|
|
1224
|
+
</p>
|
|
1225
|
+
</div>
|
|
1226
|
+
|
|
1227
|
+
<Show when={currentStep === 1}>
|
|
1228
|
+
<div className="space-y-4">
|
|
1229
|
+
<h2 className="text-xl font-semibold mb-4">Candidatos Disponibles:</h2>
|
|
1230
|
+
{candidates.map((candidate) => (
|
|
1231
|
+
<div
|
|
1232
|
+
key={candidate.id}
|
|
1233
|
+
className={`p-4 border-2 rounded-lg cursor-pointer transition-colors ${
|
|
1234
|
+
selectedCandidate === candidate.id
|
|
1235
|
+
? 'border-blue-500 bg-blue-50'
|
|
1236
|
+
: 'border-gray-200 hover:border-gray-300'
|
|
1237
|
+
}`}
|
|
1238
|
+
onClick={() => setSelectedCandidate(candidate.id)}
|
|
1239
|
+
>
|
|
1240
|
+
<div className="flex items-center justify-between">
|
|
1241
|
+
<div>
|
|
1242
|
+
<h3 className="font-semibold text-lg">{candidate.name}</h3>
|
|
1243
|
+
<p className="text-gray-600">{candidate.party}</p>
|
|
1244
|
+
</div>
|
|
1245
|
+
{selectedCandidate === candidate.id && (
|
|
1246
|
+
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
|
1247
|
+
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
1248
|
+
<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" />
|
|
1249
|
+
</svg>
|
|
1250
|
+
</div>
|
|
1251
|
+
)}
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
))}
|
|
1255
|
+
|
|
1256
|
+
<div className="pt-4">
|
|
1257
|
+
<Button
|
|
1258
|
+
color="primary"
|
|
1259
|
+
title="Continuar"
|
|
1260
|
+
size="large"
|
|
1261
|
+
disabled={!selectedCandidate}
|
|
1262
|
+
onClick={() => setCurrentStep(2)}
|
|
1263
|
+
/>
|
|
1264
|
+
</div>
|
|
1265
|
+
</div>
|
|
1266
|
+
</Show>
|
|
1267
|
+
|
|
1268
|
+
<Show when={currentStep === 2}>
|
|
1269
|
+
<div className="text-center">
|
|
1270
|
+
<h2 className="text-xl font-semibold mb-4">Confirmar Voto</h2>
|
|
1271
|
+
<div className="bg-gray-50 p-4 rounded-lg mb-6">
|
|
1272
|
+
<p className="text-gray-600 mb-2">Ha seleccionado:</p>
|
|
1273
|
+
<p className="font-semibold text-lg">
|
|
1274
|
+
{candidates.find(c => c.id === selectedCandidate)?.name}
|
|
1275
|
+
</p>
|
|
1276
|
+
<p className="text-gray-600">
|
|
1277
|
+
{candidates.find(c => c.id === selectedCandidate)?.party}
|
|
1278
|
+
</p>
|
|
1279
|
+
</div>
|
|
1280
|
+
|
|
1281
|
+
<div className="flex justify-center space-x-4">
|
|
1282
|
+
<Button
|
|
1283
|
+
color="gray"
|
|
1284
|
+
title="Volver"
|
|
1285
|
+
onClick={() => setCurrentStep(1)}
|
|
1286
|
+
/>
|
|
1287
|
+
<Button
|
|
1288
|
+
color="primary"
|
|
1289
|
+
title="Confirmar Voto"
|
|
1290
|
+
onClick={() => setShowConfirmModal(true)}
|
|
1291
|
+
/>
|
|
1292
|
+
</div>
|
|
1293
|
+
</div>
|
|
1294
|
+
</Show>
|
|
1295
|
+
|
|
1296
|
+
<Show when={currentStep === 4}>
|
|
1297
|
+
<div className="text-center">
|
|
1298
|
+
<div className="mb-6">
|
|
1299
|
+
<div className="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4">
|
|
1300
|
+
<svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1301
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
1302
|
+
</svg>
|
|
1303
|
+
</div>
|
|
1304
|
+
<h2 className="text-2xl font-bold text-green-600 mb-2">
|
|
1305
|
+
¡Voto Registrado Exitosamente!
|
|
1306
|
+
</h2>
|
|
1307
|
+
<p className="text-gray-600">
|
|
1308
|
+
Su voto ha sido procesado correctamente. Gracias por participar en la democracia.
|
|
1309
|
+
</p>
|
|
1310
|
+
</div>
|
|
1311
|
+
|
|
1312
|
+
<Button
|
|
1313
|
+
color="green"
|
|
1314
|
+
title="Ver Resultados"
|
|
1315
|
+
size="large"
|
|
1316
|
+
onClick={() => window.location.reload()}
|
|
1317
|
+
/>
|
|
1318
|
+
</div>
|
|
1319
|
+
</Show>
|
|
1320
|
+
|
|
1321
|
+
{/* Modal de Confirmación */}
|
|
1322
|
+
<Modal
|
|
1323
|
+
isOpen={showConfirmModal}
|
|
1324
|
+
onClose={() => setShowConfirmModal(false)}
|
|
1325
|
+
closeButton={true}
|
|
1326
|
+
overlayColor="blue"
|
|
1327
|
+
>
|
|
1328
|
+
<div className="p-6 text-center">
|
|
1329
|
+
<div className="mb-4">
|
|
1330
|
+
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 mb-4">
|
|
1331
|
+
<svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1332
|
+
<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" />
|
|
1333
|
+
</svg>
|
|
1334
|
+
</div>
|
|
1335
|
+
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
1336
|
+
Confirmar Voto
|
|
1337
|
+
</h3>
|
|
1338
|
+
<p className="text-sm text-gray-500">
|
|
1339
|
+
¿Está seguro de que desea votar por{' '}
|
|
1340
|
+
<strong>{candidates.find(c => c.id === selectedCandidate)?.name}</strong>?
|
|
1341
|
+
Esta acción no se puede deshacer.
|
|
1342
|
+
</p>
|
|
1343
|
+
</div>
|
|
1344
|
+
|
|
1345
|
+
<div className="flex justify-center space-x-3">
|
|
1346
|
+
<Button
|
|
1347
|
+
color="gray"
|
|
1348
|
+
title="Cancelar"
|
|
1349
|
+
onClick={() => setShowConfirmModal(false)}
|
|
1350
|
+
/>
|
|
1351
|
+
<Button
|
|
1352
|
+
color="primary"
|
|
1353
|
+
title="Confirmar Voto"
|
|
1354
|
+
onClick={handleVote}
|
|
1355
|
+
/>
|
|
1356
|
+
</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</Modal>
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
export default VotingApp;
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
### Dashboard de Administración
|
|
1368
|
+
```tsx
|
|
1369
|
+
import { Button, Modal, Show } from '@onpe/ui/components';
|
|
1370
|
+
import { useState } from 'react';
|
|
1371
|
+
|
|
1372
|
+
function AdminDashboard() {
|
|
1373
|
+
const [activeTab, setActiveTab] = useState('users');
|
|
1374
|
+
const [showUserModal, setShowUserModal] = useState(false);
|
|
1375
|
+
const [users, setUsers] = useState([
|
|
1376
|
+
{ id: 1, name: 'Juan Pérez', email: 'juan@onpe.gob.pe', role: 'Admin' },
|
|
1377
|
+
{ id: 2, name: 'María García', email: 'maria@onpe.gob.pe', role: 'Usuario' },
|
|
1378
|
+
{ id: 3, name: 'Carlos López', email: 'carlos@onpe.gob.pe', role: 'Usuario' }
|
|
1379
|
+
]);
|
|
1380
|
+
|
|
1381
|
+
const [newUser, setNewUser] = useState({
|
|
1382
|
+
name: '',
|
|
1383
|
+
email: '',
|
|
1384
|
+
role: 'Usuario'
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
const addUser = () => {
|
|
1388
|
+
const user = {
|
|
1389
|
+
id: users.length + 1,
|
|
1390
|
+
...newUser
|
|
1391
|
+
};
|
|
1392
|
+
setUsers([...users, user]);
|
|
1393
|
+
setNewUser({ name: '', email: '', role: 'Usuario' });
|
|
1394
|
+
setShowUserModal(false);
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
return (
|
|
1398
|
+
<div className="min-h-screen bg-gray-100">
|
|
1399
|
+
{/* Header */}
|
|
1400
|
+
<header className="bg-white shadow-sm border-b">
|
|
1401
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
1402
|
+
<div className="flex justify-between items-center py-4">
|
|
1403
|
+
<div>
|
|
1404
|
+
<h1 className="text-2xl font-bold text-gray-900">Dashboard ONPE</h1>
|
|
1405
|
+
<p className="text-gray-600">Panel de administración del sistema electoral</p>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div className="flex items-center space-x-4">
|
|
1408
|
+
<span className="text-sm text-gray-500">Administrador</span>
|
|
1409
|
+
<Button color="primary" title="Cerrar Sesión" size="small" />
|
|
1410
|
+
</div>
|
|
1411
|
+
</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
</header>
|
|
1414
|
+
|
|
1415
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
1416
|
+
{/* Navigation Tabs */}
|
|
1417
|
+
<div className="mb-8">
|
|
1418
|
+
<nav className="flex space-x-8">
|
|
1419
|
+
<button
|
|
1420
|
+
onClick={() => setActiveTab('users')}
|
|
1421
|
+
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1422
|
+
activeTab === 'users'
|
|
1423
|
+
? 'border-blue-500 text-blue-600'
|
|
1424
|
+
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1425
|
+
}`}
|
|
1426
|
+
>
|
|
1427
|
+
Usuarios
|
|
1428
|
+
</button>
|
|
1429
|
+
<button
|
|
1430
|
+
onClick={() => setActiveTab('elections')}
|
|
1431
|
+
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1432
|
+
activeTab === 'elections'
|
|
1433
|
+
? 'border-blue-500 text-blue-600'
|
|
1434
|
+
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1435
|
+
}`}
|
|
1436
|
+
>
|
|
1437
|
+
Elecciones
|
|
1438
|
+
</button>
|
|
1439
|
+
<button
|
|
1440
|
+
onClick={() => setActiveTab('reports')}
|
|
1441
|
+
className={`py-2 px-1 border-b-2 font-medium text-sm ${
|
|
1442
|
+
activeTab === 'reports'
|
|
1443
|
+
? 'border-blue-500 text-blue-600'
|
|
1444
|
+
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
1445
|
+
}`}
|
|
1446
|
+
>
|
|
1447
|
+
Reportes
|
|
1448
|
+
</button>
|
|
1449
|
+
</nav>
|
|
1450
|
+
</div>
|
|
1451
|
+
|
|
1452
|
+
{/* Users Tab */}
|
|
1453
|
+
<Show when={activeTab === 'users'}>
|
|
1454
|
+
<div className="bg-white rounded-lg shadow">
|
|
1455
|
+
<div className="px-6 py-4 border-b border-gray-200">
|
|
1456
|
+
<div className="flex justify-between items-center">
|
|
1457
|
+
<h2 className="text-lg font-medium text-gray-900">Gestión de Usuarios</h2>
|
|
1458
|
+
<Button
|
|
1459
|
+
color="green"
|
|
1460
|
+
title="Agregar Usuario"
|
|
1461
|
+
onClick={() => setShowUserModal(true)}
|
|
1462
|
+
/>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
|
|
1466
|
+
<div className="overflow-x-auto">
|
|
1467
|
+
<table className="min-w-full divide-y divide-gray-200">
|
|
1468
|
+
<thead className="bg-gray-50">
|
|
1469
|
+
<tr>
|
|
1470
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1471
|
+
Nombre
|
|
1472
|
+
</th>
|
|
1473
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1474
|
+
Email
|
|
1475
|
+
</th>
|
|
1476
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1477
|
+
Rol
|
|
1478
|
+
</th>
|
|
1479
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
1480
|
+
Acciones
|
|
1481
|
+
</th>
|
|
1482
|
+
</tr>
|
|
1483
|
+
</thead>
|
|
1484
|
+
<tbody className="bg-white divide-y divide-gray-200">
|
|
1485
|
+
{users.map((user) => (
|
|
1486
|
+
<tr key={user.id}>
|
|
1487
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
1488
|
+
{user.name}
|
|
1489
|
+
</td>
|
|
1490
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
1491
|
+
{user.email}
|
|
1492
|
+
</td>
|
|
1493
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
1494
|
+
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
|
1495
|
+
user.role === 'Admin'
|
|
1496
|
+
? 'bg-red-100 text-red-800'
|
|
1497
|
+
: 'bg-green-100 text-green-800'
|
|
1498
|
+
}`}>
|
|
1499
|
+
{user.role}
|
|
1500
|
+
</span>
|
|
1501
|
+
</td>
|
|
1502
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
1503
|
+
<div className="flex space-x-2">
|
|
1504
|
+
<Button color="skyblue" title="Editar" size="small" />
|
|
1505
|
+
<Button color="red" title="Eliminar" size="small" />
|
|
1506
|
+
</div>
|
|
1507
|
+
</td>
|
|
1508
|
+
</tr>
|
|
1509
|
+
))}
|
|
1510
|
+
</tbody>
|
|
1511
|
+
</table>
|
|
1512
|
+
</div>
|
|
1513
|
+
</div>
|
|
1514
|
+
</Show>
|
|
1515
|
+
|
|
1516
|
+
{/* Elections Tab */}
|
|
1517
|
+
<Show when={activeTab === 'elections'}>
|
|
1518
|
+
<div className="bg-white rounded-lg shadow p-6">
|
|
1519
|
+
<h2 className="text-lg font-medium text-gray-900 mb-4">Gestión de Elecciones</h2>
|
|
1520
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
1521
|
+
<div className="bg-blue-50 p-4 rounded-lg">
|
|
1522
|
+
<h3 className="font-semibold text-blue-900">Elecciones Generales</h3>
|
|
1523
|
+
<p className="text-blue-700 text-sm mt-1">14 de abril, 2024</p>
|
|
1524
|
+
<Button color="blue" title="Ver Detalles" size="small" />
|
|
1525
|
+
</div>
|
|
1526
|
+
<div className="bg-green-50 p-4 rounded-lg">
|
|
1527
|
+
<h3 className="font-semibold text-green-900">Elecciones Regionales</h3>
|
|
1528
|
+
<p className="text-green-700 text-sm mt-1">28 de octubre, 2024</p>
|
|
1529
|
+
<Button color="green" title="Ver Detalles" size="small" />
|
|
1530
|
+
</div>
|
|
1531
|
+
<div className="bg-yellow-50 p-4 rounded-lg">
|
|
1532
|
+
<h3 className="font-semibold text-yellow-900">Elecciones Municipales</h3>
|
|
1533
|
+
<p className="text-yellow-700 text-sm mt-1">15 de diciembre, 2024</p>
|
|
1534
|
+
<Button color="yellow" title="Ver Detalles" size="small" />
|
|
1535
|
+
</div>
|
|
1536
|
+
</div>
|
|
1537
|
+
</div>
|
|
1538
|
+
</Show>
|
|
1539
|
+
|
|
1540
|
+
{/* Reports Tab */}
|
|
1541
|
+
<Show when={activeTab === 'reports'}>
|
|
1542
|
+
<div className="bg-white rounded-lg shadow p-6">
|
|
1543
|
+
<h2 className="text-lg font-medium text-gray-900 mb-4">Reportes del Sistema</h2>
|
|
1544
|
+
<div className="space-y-4">
|
|
1545
|
+
<div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
|
|
1546
|
+
<div>
|
|
1547
|
+
<h3 className="font-medium">Reporte de Participación Electoral</h3>
|
|
1548
|
+
<p className="text-sm text-gray-600">Generado el 15 de abril, 2024</p>
|
|
1549
|
+
</div>
|
|
1550
|
+
<Button color="primary" title="Descargar PDF" size="small" />
|
|
1551
|
+
</div>
|
|
1552
|
+
<div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
|
|
1553
|
+
<div>
|
|
1554
|
+
<h3 className="font-medium">Estadísticas de Usuarios</h3>
|
|
1555
|
+
<p className="text-sm text-gray-600">Generado el 14 de abril, 2024</p>
|
|
1556
|
+
</div>
|
|
1557
|
+
<Button color="primary" title="Descargar PDF" size="small" />
|
|
1558
|
+
</div>
|
|
1559
|
+
</div>
|
|
1560
|
+
</div>
|
|
1561
|
+
</Show>
|
|
1562
|
+
|
|
1563
|
+
{/* Add User Modal */}
|
|
1564
|
+
<Modal
|
|
1565
|
+
isOpen={showUserModal}
|
|
1566
|
+
onClose={() => setShowUserModal(false)}
|
|
1567
|
+
closeButton={true}
|
|
1568
|
+
overlayColor="skyblue"
|
|
1569
|
+
>
|
|
1570
|
+
<div className="p-6">
|
|
1571
|
+
<h2 className="text-xl font-bold mb-4">Agregar Nuevo Usuario</h2>
|
|
1572
|
+
|
|
1573
|
+
<div className="space-y-4">
|
|
1574
|
+
<div>
|
|
1575
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1576
|
+
Nombre Completo
|
|
1577
|
+
</label>
|
|
1578
|
+
<input
|
|
1579
|
+
type="text"
|
|
1580
|
+
value={newUser.name}
|
|
1581
|
+
onChange={(e) => setNewUser({...newUser, name: e.target.value})}
|
|
1582
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1583
|
+
placeholder="Ingrese el nombre completo"
|
|
1584
|
+
/>
|
|
1585
|
+
</div>
|
|
1586
|
+
|
|
1587
|
+
<div>
|
|
1588
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1589
|
+
Correo Electrónico
|
|
1590
|
+
</label>
|
|
1591
|
+
<input
|
|
1592
|
+
type="email"
|
|
1593
|
+
value={newUser.email}
|
|
1594
|
+
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
|
|
1595
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1596
|
+
placeholder="usuario@onpe.gob.pe"
|
|
1597
|
+
/>
|
|
1598
|
+
</div>
|
|
1599
|
+
|
|
1600
|
+
<div>
|
|
1601
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1602
|
+
Rol
|
|
1603
|
+
</label>
|
|
1604
|
+
<select
|
|
1605
|
+
value={newUser.role}
|
|
1606
|
+
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
|
|
1607
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1608
|
+
>
|
|
1609
|
+
<option value="Usuario">Usuario</option>
|
|
1610
|
+
<option value="Admin">Administrador</option>
|
|
1611
|
+
</select>
|
|
1612
|
+
</div>
|
|
1613
|
+
|
|
1614
|
+
<div className="flex justify-end space-x-3 pt-4">
|
|
1615
|
+
<Button
|
|
1616
|
+
color="gray"
|
|
1617
|
+
title="Cancelar"
|
|
1618
|
+
onClick={() => setShowUserModal(false)}
|
|
1619
|
+
/>
|
|
1620
|
+
<Button
|
|
1621
|
+
color="green"
|
|
1622
|
+
title="Agregar Usuario"
|
|
1623
|
+
onClick={addUser}
|
|
1624
|
+
/>
|
|
1625
|
+
</div>
|
|
1626
|
+
</div>
|
|
1627
|
+
</div>
|
|
1628
|
+
</Modal>
|
|
1629
|
+
</div>
|
|
1630
|
+
</div>
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
export default AdminDashboard;
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
## 🐛 Solución de Problemas
|
|
1638
|
+
|
|
1639
|
+
### Problemas con la CLI
|
|
1640
|
+
|
|
1641
|
+
**Error: "Componente no encontrado"**
|
|
1642
|
+
```bash
|
|
1643
|
+
# Verificar componentes disponibles
|
|
1644
|
+
npx @onpe/ui add --help
|
|
1645
|
+
|
|
1646
|
+
# Componentes válidos:
|
|
1647
|
+
# button, modal, overlay, portal, show
|
|
1648
|
+
```
|
|
1649
|
+
|
|
1650
|
+
**Error: "No se pudo descargar el componente"**
|
|
1651
|
+
```bash
|
|
1652
|
+
# Verificar conexión a internet
|
|
1653
|
+
ping github.com
|
|
1654
|
+
|
|
1655
|
+
# Verificar que el repositorio esté disponible
|
|
1656
|
+
curl https://raw.githubusercontent.com/ricardosv46/onpe-ui/main/src/components/Button/Button.tsx
|
|
1657
|
+
```
|
|
1658
|
+
|
|
1659
|
+
**Los estilos no se aplican**
|
|
1660
|
+
```bash
|
|
1661
|
+
# Verificar que Tailwind esté instalado
|
|
1662
|
+
npm list tailwindcss
|
|
1663
|
+
|
|
1664
|
+
# Verificar configuración
|
|
1665
|
+
cat tailwind.config.js
|
|
1666
|
+
```
|
|
1667
|
+
|
|
1668
|
+
**Portal no funciona**
|
|
1669
|
+
```bash
|
|
1670
|
+
# Verificar que tengas el elemento portal en HTML
|
|
1671
|
+
grep -r "id=\"portal\"" public/
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
### Problemas con la Librería Completa
|
|
1675
|
+
|
|
1676
|
+
**Error: "Module not found"**
|
|
1677
|
+
```bash
|
|
1678
|
+
# Verificar instalación
|
|
1679
|
+
npm list @onpe/ui
|
|
1680
|
+
|
|
1681
|
+
# Reinstalar si es necesario
|
|
1682
|
+
npm uninstall @onpe/ui
|
|
1683
|
+
npm install @onpe/ui
|
|
1684
|
+
```
|
|
1685
|
+
|
|
1686
|
+
**Estilos no se cargan**
|
|
1687
|
+
```tsx
|
|
1688
|
+
/* Verificar que tengas la importación correcta */
|
|
1689
|
+
import './onpe-ui.css';
|
|
1690
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
**Solución: Verificar rutas de importación**
|
|
1694
|
+
```tsx
|
|
1695
|
+
// ✅ CORRECTO: Importar archivo CSS personalizado
|
|
1696
|
+
import './onpe-ui.css';
|
|
1697
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1698
|
+
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1699
|
+
|
|
1700
|
+
// ❌ INCORRECTO: No importar el archivo CSS
|
|
1701
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
**Solución: Verificar configuración de bundler**
|
|
1705
|
+
```javascript
|
|
1706
|
+
// webpack.config.js
|
|
1707
|
+
module.exports = {
|
|
1708
|
+
resolve: {
|
|
1709
|
+
alias: {
|
|
1710
|
+
'@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
// vite.config.js
|
|
1716
|
+
export default {
|
|
1717
|
+
resolve: {
|
|
1718
|
+
alias: {
|
|
1719
|
+
'@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
```
|
|
1724
|
+
|
|
1725
|
+
**Solución: Verificar orden de importación**
|
|
1726
|
+
```tsx
|
|
1727
|
+
// ✅ CORRECTO - Importar estilos ANTES de los componentes
|
|
1728
|
+
import '@onpe/ui/styles';
|
|
1729
|
+
import { Button } from '@onpe/ui/components';
|
|
1730
|
+
|
|
1731
|
+
// ❌ INCORRECTO - Importar estilos DESPUÉS de los componentes
|
|
1732
|
+
import { Button } from '@onpe/ui/components';
|
|
1733
|
+
import '@onpe/ui/styles';
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
### Problemas con Estilos
|
|
1737
|
+
|
|
1738
|
+
**Los componentes se ven sin estilos**
|
|
1739
|
+
```bash
|
|
1740
|
+
# Verificar que la librería esté instalada
|
|
1741
|
+
npm list @onpe/ui
|
|
1742
|
+
|
|
1743
|
+
# Verificar que los estilos estén en node_modules
|
|
1744
|
+
ls node_modules/@onpe/ui/dist/
|
|
1745
|
+
```
|
|
1746
|
+
|
|
1747
|
+
**Solución: Verificar importación de estilos**
|
|
1748
|
+
```tsx
|
|
1749
|
+
// En tu archivo principal (index.tsx o App.tsx)
|
|
1750
|
+
import React from 'react';
|
|
1751
|
+
import ReactDOM from 'react-dom/client';
|
|
1752
|
+
import '@onpe/ui/styles'; // ← IMPORTANTE: Importar estilos primero
|
|
1753
|
+
import App from './App';
|
|
1754
|
+
|
|
1755
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
1756
|
+
root.render(<App />);
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
**Solución: Verificar configuración de CSS**
|
|
1760
|
+
```css
|
|
1761
|
+
/* En tu archivo CSS principal */
|
|
1762
|
+
@import "@onpe/ui/styles";
|
|
1763
|
+
|
|
1764
|
+
/* O si usas CSS modules */
|
|
1765
|
+
@import "~@onpe/ui/styles";
|
|
1766
|
+
```
|
|
1767
|
+
|
|
1768
|
+
**Los colores personalizados no funcionan**
|
|
1769
|
+
```tsx
|
|
1770
|
+
// Verificar que estés usando las clases correctas
|
|
1771
|
+
<div className="bg-onpe-ui-blue text-white p-4">
|
|
1772
|
+
Contenido con colores ONPE
|
|
1773
|
+
</div>
|
|
1774
|
+
|
|
1775
|
+
// Verificar que los estilos estén importados
|
|
1776
|
+
import '@onpe/ui/styles';
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
**Solución: Verificar configuración de Tailwind (para componentes individuales)**
|
|
1780
|
+
```javascript
|
|
1781
|
+
// tailwind.config.js
|
|
1782
|
+
module.exports = {
|
|
1783
|
+
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
1784
|
+
theme: {
|
|
1785
|
+
extend: {
|
|
1786
|
+
colors: {
|
|
1787
|
+
'onpe-ui-blue': '#003770',
|
|
1788
|
+
'onpe-ui-skyblue': '#0073cf',
|
|
1789
|
+
// ... resto de colores
|
|
1790
|
+
}
|
|
1791
|
+
},
|
|
1792
|
+
},
|
|
1793
|
+
}
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
### Problemas con Storybook
|
|
1797
|
+
|
|
1798
|
+
**Error: "Failed to fetch dynamically imported module"**
|
|
1799
|
+
- Verificar que el archivo `preview.ts` importe correctamente `../src/styles.css`
|
|
1800
|
+
- Asegurar que Tailwind CSS esté configurado correctamente
|
|
1801
|
+
- Verificar que PostCSS esté configurado
|
|
1802
|
+
|
|
1803
|
+
**Solución: Configuración completa de Storybook**
|
|
1804
|
+
```typescript
|
|
1805
|
+
// .storybook/main.ts
|
|
1806
|
+
import type { StorybookConfig } from "@storybook/react-vite";
|
|
1807
|
+
|
|
1808
|
+
const config: StorybookConfig = {
|
|
1809
|
+
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
1810
|
+
addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
|
|
1811
|
+
framework: {
|
|
1812
|
+
name: "@storybook/react-vite",
|
|
1813
|
+
options: {},
|
|
1814
|
+
},
|
|
1815
|
+
viteFinal: async (config) => {
|
|
1816
|
+
config.css = {
|
|
1817
|
+
...config.css,
|
|
1818
|
+
postcss: {
|
|
1819
|
+
plugins: [require("tailwindcss"), require("autoprefixer")],
|
|
1820
|
+
},
|
|
1821
|
+
};
|
|
1822
|
+
return config;
|
|
1823
|
+
},
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
export default config;
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
```typescript
|
|
1830
|
+
// .storybook/preview.ts
|
|
1831
|
+
import type { Preview } from "@storybook/react";
|
|
1832
|
+
import "../src/styles.css"; // ← Importar estilos
|
|
1833
|
+
|
|
1834
|
+
const preview: Preview = {
|
|
1835
|
+
parameters: {
|
|
1836
|
+
actions: { argTypesRegex: "^on[A-Z].*" },
|
|
1837
|
+
controls: {
|
|
1838
|
+
matchers: {
|
|
1839
|
+
color: /(background|color)$/i,
|
|
1840
|
+
date: /Date$/,
|
|
1841
|
+
},
|
|
1842
|
+
},
|
|
1843
|
+
},
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
export default preview;
|
|
1847
|
+
```
|
|
1848
|
+
|
|
1849
|
+
## 🚀 Guía Rápida de Solución
|
|
1850
|
+
|
|
1851
|
+
### ¿Los componentes no se ven con estilos?
|
|
1852
|
+
|
|
1853
|
+
**Paso 1: Verificar instalación**
|
|
1854
|
+
```bash
|
|
1855
|
+
npm list @onpe/ui
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
**Paso 2: Configurar TailwindCSS**
|
|
1859
|
+
```bash
|
|
1860
|
+
# Instalar TailwindCSS
|
|
1861
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
1862
|
+
npx tailwindcss init -p
|
|
1863
|
+
```
|
|
1864
|
+
|
|
1865
|
+
**Paso 3: Crear archivo CSS personalizado**
|
|
1866
|
+
```css
|
|
1867
|
+
/* Crear archivo onpe-ui.css */
|
|
1868
|
+
:root {
|
|
1869
|
+
--blue: #003770;
|
|
1870
|
+
--skyblue: #0073cf;
|
|
1871
|
+
/* ... resto de variables */
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
@theme {
|
|
1875
|
+
--color-onpe-ui-blue: var(--blue);
|
|
1876
|
+
/* ... resto de colores */
|
|
1877
|
+
}
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1880
|
+
**Paso 4: Importar archivo CSS**
|
|
1881
|
+
```tsx
|
|
1882
|
+
// En tu archivo principal (index.tsx)
|
|
1883
|
+
import './onpe-ui.css';
|
|
1884
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1885
|
+
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1886
|
+
```
|
|
1887
|
+
|
|
1888
|
+
**Paso 5: Verificar orden de importación**
|
|
1889
|
+
```tsx
|
|
1890
|
+
// ✅ CORRECTO
|
|
1891
|
+
import './onpe-ui.css';
|
|
1892
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1893
|
+
import { IconClose } from './components/onpe-icons/IconClose';
|
|
1894
|
+
|
|
1895
|
+
// ❌ INCORRECTO
|
|
1896
|
+
import { Button } from './components/onpe-ui/Button';
|
|
1897
|
+
// Falta importar el archivo CSS
|
|
1898
|
+
```
|
|
1899
|
+
|
|
1900
|
+
### ¿Los colores no funcionan?
|
|
1901
|
+
|
|
1902
|
+
**Solución: Usar clases correctas**
|
|
1903
|
+
```tsx
|
|
1904
|
+
// ✅ CORRECTO
|
|
1905
|
+
<div className="bg-onpe-ui-blue text-white p-4">
|
|
1906
|
+
Contenido
|
|
1907
|
+
</div>
|
|
1908
|
+
|
|
1909
|
+
// ❌ INCORRECTO
|
|
1910
|
+
<div className="bg-blue-500 text-white p-4">
|
|
1911
|
+
Contenido
|
|
1912
|
+
</div>
|
|
1913
|
+
```
|
|
1914
|
+
|
|
1915
|
+
### ¿Storybook no funciona?
|
|
1916
|
+
|
|
1917
|
+
**Solución: Configuración completa**
|
|
1918
|
+
```typescript
|
|
1919
|
+
// .storybook/preview.ts
|
|
1920
|
+
import "../src/styles.css";
|
|
1921
|
+
|
|
1922
|
+
// .storybook/main.ts
|
|
1923
|
+
viteFinal: async (config) => {
|
|
1924
|
+
config.css = {
|
|
1925
|
+
...config.css,
|
|
1926
|
+
postcss: {
|
|
1927
|
+
plugins: [require("tailwindcss"), require("autoprefixer")],
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1930
|
+
return config;
|
|
1931
|
+
},
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
### ¿CLI no instala componentes?
|
|
1935
|
+
|
|
1936
|
+
**Solución: Verificar comandos**
|
|
1937
|
+
```bash
|
|
1938
|
+
# ✅ CORRECTO
|
|
1939
|
+
npx @onpe/ui add button
|
|
1940
|
+
npx @onpe/ui add modal
|
|
1941
|
+
|
|
1942
|
+
# ❌ INCORRECTO
|
|
1943
|
+
npx @onpe/ui add Button
|
|
1944
|
+
npx @onpe/ui add Modal
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
### ¿Portal no funciona?
|
|
1948
|
+
|
|
1949
|
+
**Solución: Agregar elemento HTML**
|
|
1950
|
+
```html
|
|
1951
|
+
<!-- En public/index.html -->
|
|
1952
|
+
<div id="root"></div>
|
|
1953
|
+
<div id="portal"></div>
|
|
1954
|
+
```
|
|
1955
|
+
|
|
1956
|
+
## 📞 Soporte
|
|
1957
|
+
|
|
1958
|
+
- 📧 Email: desarrollo@onpe.gob.pe
|
|
1959
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/ricardosv46/onpe-ui/issues)
|
|
1960
|
+
- 📖 Documentación: [Storybook](https://onpe-ui-components.netlify.app)
|
|
1961
|
+
- 🔗 Repositorio: [GitHub](https://github.com/ricardosv46/onpe-ui)
|
|
1962
|
+
- 📦 NPM: [@onpe/ui](https://www.npmjs.com/package/@onpe/ui)
|
|
1963
|
+
|
|
1964
|
+
---
|
|
1965
|
+
|
|
1966
1966
|
**Desarrollado con ❤️ para la democracia peruana** 🇵🇪
|