carconnect-gatherleads-ui-lib 2.4.0 → 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.