carconnect-gatherleads-ui-lib 2.4.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -2614,3 +2614,399 @@ const apiData = await fetch('/api/users').then((r) => r.json());
|
|
|
2614
2614
|
```
|
|
2615
2615
|
|
|
2616
2616
|
Esta guía completa cubre todos los aspectos de GatherTable, desde uso básico hasta implementaciones avanzadas con ordenamiento, filtrado y renderizado personalizado.
|
|
2617
|
+
|
|
2618
|
+
# 🪟 Guía Completa de GatherModal
|
|
2619
|
+
|
|
2620
|
+
## 🎯 Introducción
|
|
2621
|
+
|
|
2622
|
+
`GatherModal` es un componente modal genérico y altamente personalizable que proporciona diálogos flexibles con soporte completo para diferentes posiciones, tamaños, animaciones y accesibilidad avanzada.
|
|
2623
|
+
|
|
2624
|
+
## 🏗️ Características Principales
|
|
2625
|
+
|
|
2626
|
+
- **Posiciones**: center, left, right (ideal para paneles laterales)
|
|
2627
|
+
- **Tamaños**: sm, md, lg, xl, full
|
|
2628
|
+
- **Header personalizable**: Título simple o contenido completamente custom
|
|
2629
|
+
- **Footer flexible**: Botones por defecto o contenido personalizado
|
|
2630
|
+
- **Accesibilidad avanzada**: Focus management, keyboard navigation
|
|
2631
|
+
- **Animaciones**: Transiciones suaves configurables
|
|
2632
|
+
- **Portal rendering**: Se renderiza fuera del árbol DOM
|
|
2633
|
+
|
|
2634
|
+
## 📋 Uso Básico
|
|
2635
|
+
|
|
2636
|
+
### Modal Simple de Confirmación
|
|
2637
|
+
|
|
2638
|
+
```tsx
|
|
2639
|
+
import { GatherModal } from '@/base/modal';
|
|
2640
|
+
import { useState } from 'react';
|
|
2641
|
+
|
|
2642
|
+
const BasicModalExample = () => {
|
|
2643
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2644
|
+
|
|
2645
|
+
return (
|
|
2646
|
+
<>
|
|
2647
|
+
<button onClick={() => setIsOpen(true)}>Abrir Modal</button>
|
|
2648
|
+
|
|
2649
|
+
<GatherModal
|
|
2650
|
+
open={isOpen}
|
|
2651
|
+
onClose={() => setIsOpen(false)}
|
|
2652
|
+
header={{
|
|
2653
|
+
title: 'Confirmar Acción',
|
|
2654
|
+
subtitle: '¿Estás seguro de continuar?',
|
|
2655
|
+
}}
|
|
2656
|
+
footer={{
|
|
2657
|
+
acceptText: 'Confirmar',
|
|
2658
|
+
cancelText: 'Cancelar',
|
|
2659
|
+
acceptVariant: 'gather-primary',
|
|
2660
|
+
cancelVariant: 'gather-outline',
|
|
2661
|
+
onAccept: () => {
|
|
2662
|
+
console.log('Confirmado');
|
|
2663
|
+
setIsOpen(false);
|
|
2664
|
+
},
|
|
2665
|
+
onCancel: () => setIsOpen(false),
|
|
2666
|
+
}}
|
|
2667
|
+
>
|
|
2668
|
+
<p>Esta acción no se puede deshacer.</p>
|
|
2669
|
+
</GatherModal>
|
|
2670
|
+
</>
|
|
2671
|
+
);
|
|
2672
|
+
};
|
|
2673
|
+
```
|
|
2674
|
+
|
|
2675
|
+
### Modal de Formulario
|
|
2676
|
+
|
|
2677
|
+
```tsx
|
|
2678
|
+
const FormModalExample = () => {
|
|
2679
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2680
|
+
const [loading, setLoading] = useState(false);
|
|
2681
|
+
|
|
2682
|
+
const handleSubmit = async () => {
|
|
2683
|
+
setLoading(true);
|
|
2684
|
+
try {
|
|
2685
|
+
// Simular API call
|
|
2686
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2687
|
+
setIsOpen(false);
|
|
2688
|
+
} finally {
|
|
2689
|
+
setLoading(false);
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
|
|
2693
|
+
return (
|
|
2694
|
+
<GatherModal
|
|
2695
|
+
open={isOpen}
|
|
2696
|
+
onClose={() => setIsOpen(false)}
|
|
2697
|
+
size='lg'
|
|
2698
|
+
header={{
|
|
2699
|
+
title: 'Nuevo Usuario',
|
|
2700
|
+
subtitle: 'Complete los datos del usuario',
|
|
2701
|
+
}}
|
|
2702
|
+
footer={{
|
|
2703
|
+
acceptText: 'Guardar',
|
|
2704
|
+
cancelText: 'Cancelar',
|
|
2705
|
+
acceptLoading: loading,
|
|
2706
|
+
acceptDisabled: loading,
|
|
2707
|
+
onAccept: handleSubmit,
|
|
2708
|
+
onCancel: () => setIsOpen(false),
|
|
2709
|
+
}}
|
|
2710
|
+
>
|
|
2711
|
+
<div className='space-y-4'>
|
|
2712
|
+
<div>
|
|
2713
|
+
<label className='mb-1 block text-sm font-medium'>
|
|
2714
|
+
Nombre completo
|
|
2715
|
+
</label>
|
|
2716
|
+
<input
|
|
2717
|
+
type='text'
|
|
2718
|
+
className='w-full rounded border p-2'
|
|
2719
|
+
placeholder='Juan Pérez'
|
|
2720
|
+
/>
|
|
2721
|
+
</div>
|
|
2722
|
+
<div>
|
|
2723
|
+
<label className='mb-1 block text-sm font-medium'>Email</label>
|
|
2724
|
+
<input
|
|
2725
|
+
type='email'
|
|
2726
|
+
className='w-full rounded border p-2'
|
|
2727
|
+
placeholder='juan@ejemplo.com'
|
|
2728
|
+
/>
|
|
2729
|
+
</div>
|
|
2730
|
+
</div>
|
|
2731
|
+
</GatherModal>
|
|
2732
|
+
);
|
|
2733
|
+
};
|
|
2734
|
+
```
|
|
2735
|
+
|
|
2736
|
+
## 🎨 Posiciones y Tamaños
|
|
2737
|
+
|
|
2738
|
+
### Modal Centrado (Por Defecto)
|
|
2739
|
+
|
|
2740
|
+
```tsx
|
|
2741
|
+
<GatherModal
|
|
2742
|
+
open={isOpen}
|
|
2743
|
+
position='center' // Por defecto
|
|
2744
|
+
size='md' // Tamaño mediano
|
|
2745
|
+
onClose={() => setIsOpen(false)}
|
|
2746
|
+
>
|
|
2747
|
+
<p>Modal centrado en la pantalla</p>
|
|
2748
|
+
</GatherModal>
|
|
2749
|
+
```
|
|
2750
|
+
|
|
2751
|
+
### Panel Lateral Derecho
|
|
2752
|
+
|
|
2753
|
+
```tsx
|
|
2754
|
+
<GatherModal
|
|
2755
|
+
open={isOpen}
|
|
2756
|
+
position='right'
|
|
2757
|
+
size='lg'
|
|
2758
|
+
onClose={() => setIsOpen(false)}
|
|
2759
|
+
header={{
|
|
2760
|
+
title: 'Panel de Configuración',
|
|
2761
|
+
}}
|
|
2762
|
+
>
|
|
2763
|
+
<div className='space-y-4'>
|
|
2764
|
+
<h3>Configuraciones</h3>
|
|
2765
|
+
<div>Contenido del panel lateral...</div>
|
|
2766
|
+
</div>
|
|
2767
|
+
</GatherModal>
|
|
2768
|
+
```
|
|
2769
|
+
|
|
2770
|
+
### Modal de Pantalla Completa
|
|
2771
|
+
|
|
2772
|
+
```tsx
|
|
2773
|
+
<GatherModal
|
|
2774
|
+
open={isOpen}
|
|
2775
|
+
size='full'
|
|
2776
|
+
onClose={() => setIsOpen(false)}
|
|
2777
|
+
header={{
|
|
2778
|
+
title: 'Vista Completa',
|
|
2779
|
+
}}
|
|
2780
|
+
scrollBehavior={{
|
|
2781
|
+
bodyScrollable: true,
|
|
2782
|
+
bodyMaxHeight: '80vh',
|
|
2783
|
+
}}
|
|
2784
|
+
>
|
|
2785
|
+
<div>Contenido que ocupa toda la pantalla...</div>
|
|
2786
|
+
</GatherModal>
|
|
2787
|
+
```
|
|
2788
|
+
|
|
2789
|
+
## 🔧 Configuración Avanzada
|
|
2790
|
+
|
|
2791
|
+
### Modal con Contenido Personalizado
|
|
2792
|
+
|
|
2793
|
+
```tsx
|
|
2794
|
+
<GatherModal
|
|
2795
|
+
open={isOpen}
|
|
2796
|
+
onClose={() => setIsOpen(false)}
|
|
2797
|
+
header={{
|
|
2798
|
+
children: (
|
|
2799
|
+
<div className='flex items-center gap-3'>
|
|
2800
|
+
<div className='flex h-8 w-8 items-center justify-center rounded-full bg-blue-500'>
|
|
2801
|
+
<span className='text-sm text-white'>!</span>
|
|
2802
|
+
</div>
|
|
2803
|
+
<div>
|
|
2804
|
+
<h2 className='text-lg font-semibold'>Advertencia</h2>
|
|
2805
|
+
<p className='text-sm text-gray-600'>Acción requerida</p>
|
|
2806
|
+
</div>
|
|
2807
|
+
</div>
|
|
2808
|
+
),
|
|
2809
|
+
showCloseButton: true,
|
|
2810
|
+
}}
|
|
2811
|
+
footer={{
|
|
2812
|
+
children: (
|
|
2813
|
+
<div className='flex w-full justify-between'>
|
|
2814
|
+
<span className='text-sm text-gray-500'>
|
|
2815
|
+
Última actualización: hace 5 min
|
|
2816
|
+
</span>
|
|
2817
|
+
<div className='flex gap-2'>
|
|
2818
|
+
<button className='rounded px-4 py-2 text-gray-600 hover:bg-gray-100'>
|
|
2819
|
+
Ignorar
|
|
2820
|
+
</button>
|
|
2821
|
+
<button className='rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600'>
|
|
2822
|
+
Resolver
|
|
2823
|
+
</button>
|
|
2824
|
+
</div>
|
|
2825
|
+
</div>
|
|
2826
|
+
),
|
|
2827
|
+
}}
|
|
2828
|
+
>
|
|
2829
|
+
<p>Contenido del modal con header y footer personalizados.</p>
|
|
2830
|
+
</GatherModal>
|
|
2831
|
+
```
|
|
2832
|
+
|
|
2833
|
+
### Modal con Scroll Personalizado
|
|
2834
|
+
|
|
2835
|
+
```tsx
|
|
2836
|
+
<GatherModal
|
|
2837
|
+
open={isOpen}
|
|
2838
|
+
onClose={() => setIsOpen(false)}
|
|
2839
|
+
header={{
|
|
2840
|
+
title: 'Contenido Extenso',
|
|
2841
|
+
scrollable: true,
|
|
2842
|
+
maxHeight: '150px',
|
|
2843
|
+
}}
|
|
2844
|
+
scrollBehavior={{
|
|
2845
|
+
bodyScrollable: true,
|
|
2846
|
+
bodyMaxHeight: '400px',
|
|
2847
|
+
modalScrollable: false,
|
|
2848
|
+
}}
|
|
2849
|
+
footer={{
|
|
2850
|
+
scrollable: true,
|
|
2851
|
+
maxHeight: '100px',
|
|
2852
|
+
}}
|
|
2853
|
+
>
|
|
2854
|
+
<div className='space-y-4'>
|
|
2855
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
2856
|
+
<p key={i}>
|
|
2857
|
+
Párrafo {i + 1}: Lorem ipsum dolor sit amet consectetur adipisicing
|
|
2858
|
+
elit...
|
|
2859
|
+
</p>
|
|
2860
|
+
))}
|
|
2861
|
+
</div>
|
|
2862
|
+
</GatherModal>
|
|
2863
|
+
```
|
|
2864
|
+
|
|
2865
|
+
## ⚙️ Props Principales
|
|
2866
|
+
|
|
2867
|
+
### Props del Modal
|
|
2868
|
+
|
|
2869
|
+
```tsx
|
|
2870
|
+
interface GatherModalProps {
|
|
2871
|
+
// ESTADO
|
|
2872
|
+
open: boolean; // Si el modal está abierto
|
|
2873
|
+
onClose?: () => void; // Función de cierre
|
|
2874
|
+
|
|
2875
|
+
// DISEÑO
|
|
2876
|
+
position?: 'center' | 'left' | 'right'; // Posición
|
|
2877
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; // Tamaño
|
|
2878
|
+
|
|
2879
|
+
// COMPORTAMIENTO
|
|
2880
|
+
closeOnOverlayClick?: boolean; // Cerrar al hacer clic fuera
|
|
2881
|
+
closeOnEscape?: boolean; // Cerrar con tecla Escape
|
|
2882
|
+
preventBodyScroll?: boolean; // Prevenir scroll del body
|
|
2883
|
+
autoFocus?: boolean; // Enfocar automáticamente
|
|
2884
|
+
|
|
2885
|
+
// CONTENIDO
|
|
2886
|
+
header?: GatherModalHeaderProps; // Configuración del header
|
|
2887
|
+
footer?: GatherModalFooterProps; // Configuración del footer
|
|
2888
|
+
children: React.ReactNode; // Contenido principal
|
|
2889
|
+
|
|
2890
|
+
// ACCESIBILIDAD Y ANIMACIÓN
|
|
2891
|
+
animationDuration?: number; // Duración de animaciones (ms)
|
|
2892
|
+
zIndex?: number; // Z-index base
|
|
2893
|
+
scrollBehavior?: ScrollConfig; // Configuración de scroll
|
|
2894
|
+
}
|
|
2895
|
+
```
|
|
2896
|
+
|
|
2897
|
+
### Props del Header
|
|
2898
|
+
|
|
2899
|
+
```tsx
|
|
2900
|
+
interface GatherModalHeaderProps {
|
|
2901
|
+
title?: string; // Título del modal
|
|
2902
|
+
subtitle?: string; // Subtítulo
|
|
2903
|
+
showCloseButton?: boolean; // Mostrar botón X
|
|
2904
|
+
onClose?: () => void; // Función del botón cerrar
|
|
2905
|
+
closeIcon?: React.ReactNode; // Ícono personalizado
|
|
2906
|
+
scrollable?: boolean; // Si el header hace scroll
|
|
2907
|
+
children?: React.ReactNode; // Contenido personalizado
|
|
2908
|
+
}
|
|
2909
|
+
```
|
|
2910
|
+
|
|
2911
|
+
### Props del Footer
|
|
2912
|
+
|
|
2913
|
+
```tsx
|
|
2914
|
+
interface GatherModalFooterProps {
|
|
2915
|
+
// BOTONES POR DEFECTO
|
|
2916
|
+
acceptText?: string; // Texto botón aceptar
|
|
2917
|
+
cancelText?: string; // Texto botón cancelar
|
|
2918
|
+
onAccept?: () => void; // Acción de aceptar
|
|
2919
|
+
onCancel?: () => void; // Acción de cancelar
|
|
2920
|
+
|
|
2921
|
+
// ESTADOS DE LOS BOTONES
|
|
2922
|
+
acceptLoading?: boolean; // Loading en botón aceptar
|
|
2923
|
+
acceptDisabled?: boolean; // Deshabilitar aceptar
|
|
2924
|
+
|
|
2925
|
+
// VARIANTES Y DISEÑO
|
|
2926
|
+
acceptVariant?: ButtonVariant; // Estilo del botón aceptar
|
|
2927
|
+
cancelVariant?: ButtonVariant; // Estilo del botón cancelar
|
|
2928
|
+
buttonsOrder?: 'normal' | 'reverse'; // Orden de botones
|
|
2929
|
+
buttonsAlignment?: 'start' | 'center' | 'end' | 'between';
|
|
2930
|
+
|
|
2931
|
+
// PERSONALIZACIÓN
|
|
2932
|
+
children?: React.ReactNode; // Footer completamente personalizado
|
|
2933
|
+
scrollable?: boolean; // Si el footer hace scroll
|
|
2934
|
+
}
|
|
2935
|
+
```
|
|
2936
|
+
|
|
2937
|
+
## 🎯 Casos de Uso Comunes
|
|
2938
|
+
|
|
2939
|
+
### Modal de Confirmación de Eliminación
|
|
2940
|
+
|
|
2941
|
+
```tsx
|
|
2942
|
+
const DeleteConfirmModal = ({ item, onConfirm, onCancel }) => (
|
|
2943
|
+
<GatherModal
|
|
2944
|
+
open={!!item}
|
|
2945
|
+
onClose={onCancel}
|
|
2946
|
+
size='sm'
|
|
2947
|
+
header={{
|
|
2948
|
+
title: 'Confirmar Eliminación',
|
|
2949
|
+
subtitle: '¿Estás seguro de eliminar este elemento?',
|
|
2950
|
+
}}
|
|
2951
|
+
footer={{
|
|
2952
|
+
acceptText: 'Eliminar',
|
|
2953
|
+
cancelText: 'Cancelar',
|
|
2954
|
+
acceptVariant: 'gather-error',
|
|
2955
|
+
onAccept: () => onConfirm(item),
|
|
2956
|
+
onCancel: onCancel,
|
|
2957
|
+
}}
|
|
2958
|
+
>
|
|
2959
|
+
<p>Esta acción no se puede deshacer.</p>
|
|
2960
|
+
{item && (
|
|
2961
|
+
<div className='mt-3 rounded bg-gray-50 p-3'>
|
|
2962
|
+
<strong>{item.name}</strong>
|
|
2963
|
+
</div>
|
|
2964
|
+
)}
|
|
2965
|
+
</GatherModal>
|
|
2966
|
+
);
|
|
2967
|
+
```
|
|
2968
|
+
|
|
2969
|
+
### Panel de Detalles Lateral
|
|
2970
|
+
|
|
2971
|
+
```tsx
|
|
2972
|
+
const DetailsSidebar = ({ user, isOpen, onClose }) => (
|
|
2973
|
+
<GatherModal
|
|
2974
|
+
open={isOpen}
|
|
2975
|
+
onClose={onClose}
|
|
2976
|
+
position='right'
|
|
2977
|
+
size='lg'
|
|
2978
|
+
header={{
|
|
2979
|
+
title: 'Detalles del Usuario',
|
|
2980
|
+
subtitle: user?.name,
|
|
2981
|
+
}}
|
|
2982
|
+
preventBodyScroll={true}
|
|
2983
|
+
>
|
|
2984
|
+
{user && (
|
|
2985
|
+
<div className='space-y-6'>
|
|
2986
|
+
<div className='text-center'>
|
|
2987
|
+
<img
|
|
2988
|
+
src={user.avatar}
|
|
2989
|
+
alt={user.name}
|
|
2990
|
+
className='mx-auto mb-4 h-20 w-20 rounded-full'
|
|
2991
|
+
/>
|
|
2992
|
+
<h3 className='text-xl font-semibold'>{user.name}</h3>
|
|
2993
|
+
<p className='text-gray-600'>{user.email}</p>
|
|
2994
|
+
</div>
|
|
2995
|
+
|
|
2996
|
+
<div className='space-y-4'>
|
|
2997
|
+
<div>
|
|
2998
|
+
<label className='font-medium'>Departamento</label>
|
|
2999
|
+
<p>{user.department}</p>
|
|
3000
|
+
</div>
|
|
3001
|
+
<div>
|
|
3002
|
+
<label className='font-medium'>Rol</label>
|
|
3003
|
+
<p>{user.role}</p>
|
|
3004
|
+
</div>
|
|
3005
|
+
</div>
|
|
3006
|
+
</div>
|
|
3007
|
+
)}
|
|
3008
|
+
</GatherModal>
|
|
3009
|
+
);
|
|
3010
|
+
```
|
|
3011
|
+
|
|
3012
|
+
Esta guía cubre los aspectos principales de GatherModal, proporcionando ejemplos prácticos para implementaciones comunes desde modales simples hasta paneles laterales complejos.
|