@onpe/ui 1.2.5 → 1.2.7

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