import { useMemo } from 'react'; import { cn } from '@/lib/utils'; export interface GridColumns { sm?: number; md?: number; lg?: number; xl?: number; '2xl'?: number; } export interface GridProps { children: React.ReactNode; columns?: number | GridColumns; gap?: number; rowGap?: number; columnGap?: number; className?: string; } /** * Mapping des classes Tailwind pour les colonnes de grille */ const GRID_COLS_CLASSES: Record = { 1: 'grid-cols-1', 2: 'grid-cols-2', 3: 'grid-cols-3', 4: 'grid-cols-4', 5: 'grid-cols-5', 6: 'grid-cols-6', 7: 'grid-cols-7', 8: 'grid-cols-8', 9: 'grid-cols-9', 10: 'grid-cols-10', 11: 'grid-cols-11', 12: 'grid-cols-12', }; const GRID_COLS_SM_CLASSES: Record = { 1: 'sm:grid-cols-1', 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3', 4: 'sm:grid-cols-4', 5: 'sm:grid-cols-5', 6: 'sm:grid-cols-6', }; const GRID_COLS_MD_CLASSES: Record = { 1: 'md:grid-cols-1', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3', 4: 'md:grid-cols-4', 5: 'md:grid-cols-5', 6: 'md:grid-cols-6', 7: 'md:grid-cols-7', 8: 'md:grid-cols-8', }; const GRID_COLS_LG_CLASSES: Record = { 1: 'lg:grid-cols-1', 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4', 5: 'lg:grid-cols-5', 6: 'lg:grid-cols-6', 7: 'lg:grid-cols-7', 8: 'lg:grid-cols-8', 9: 'lg:grid-cols-9', 10: 'lg:grid-cols-10', 11: 'lg:grid-cols-11', 12: 'lg:grid-cols-12', }; const GRID_COLS_XL_CLASSES: Record = { 1: 'xl:grid-cols-1', 2: 'xl:grid-cols-2', 3: 'xl:grid-cols-3', 4: 'xl:grid-cols-4', 5: 'xl:grid-cols-5', 6: 'xl:grid-cols-6', 7: 'xl:grid-cols-7', 8: 'xl:grid-cols-8', 9: 'xl:grid-cols-9', 10: 'xl:grid-cols-10', 11: 'xl:grid-cols-11', 12: 'xl:grid-cols-12', }; const GRID_COLS_2XL_CLASSES: Record = { 1: '2xl:grid-cols-1', 2: '2xl:grid-cols-2', 3: '2xl:grid-cols-3', 4: '2xl:grid-cols-4', 5: '2xl:grid-cols-5', 6: '2xl:grid-cols-6', 7: '2xl:grid-cols-7', 8: '2xl:grid-cols-8', 9: '2xl:grid-cols-9', 10: '2xl:grid-cols-10', 11: '2xl:grid-cols-11', 12: '2xl:grid-cols-12', }; const GAP_CLASSES: Record = { 0: 'gap-0', 1: 'gap-1', 2: 'gap-2', 3: 'gap-3', 4: 'gap-4', 5: 'gap-5', 6: 'gap-6', 7: 'gap-7', 8: 'gap-8', 9: 'gap-9', 10: 'gap-10', 11: 'gap-11', 12: 'gap-12', }; /** * Composant Grid responsive pour afficher des items en grille. */ export function Grid({ children, columns = 3, gap, rowGap, columnGap, className, }: GridProps) { const gridClasses = useMemo(() => { const classes: string[] = ['grid']; // Gestion des colonnes if (typeof columns === 'number') { // Colonnes fixes const colsClass = GRID_COLS_CLASSES[columns] || `grid-cols-${columns}`; classes.push(colsClass); } else { // Colonnes responsives const { sm, md, lg, xl, '2xl': xl2 } = columns; // Colonne par défaut (mobile first) if (sm) { classes.push(GRID_COLS_CLASSES[sm] || `grid-cols-${sm}`); } else { classes.push('grid-cols-1'); // Par défaut 1 colonne sur mobile } if (sm) { classes.push(GRID_COLS_SM_CLASSES[sm] || `sm:grid-cols-${sm}`); } if (md) { classes.push(GRID_COLS_MD_CLASSES[md] || `md:grid-cols-${md}`); } if (lg) { classes.push(GRID_COLS_LG_CLASSES[lg] || `lg:grid-cols-${lg}`); } if (xl) { classes.push(GRID_COLS_XL_CLASSES[xl] || `xl:grid-cols-${xl}`); } if (xl2) { classes.push(GRID_COLS_2XL_CLASSES[xl2] || `2xl:grid-cols-${xl2}`); } } // Gestion du gap if (gap !== undefined) { classes.push(GAP_CLASSES[gap] || `gap-${gap}`); } else { // Si rowGap et columnGap sont spécifiés, on les utilise if (rowGap !== undefined) { classes.push(`gap-y-${rowGap}`); } if (columnGap !== undefined) { classes.push(`gap-x-${columnGap}`); } // Si aucun gap n'est spécifié, on utilise gap-4 par défaut if ( gap === undefined && rowGap === undefined && columnGap === undefined ) { classes.push('gap-4'); } } return classes; }, [columns, gap, rowGap, columnGap]); // Calcul du style inline pour les colonnes personnalisées const inlineStyle = useMemo(() => { const style: React.CSSProperties = {}; if (typeof columns === 'number') { // Si c'est un nombre et qu'il n'est pas dans les classes prédéfinies if (columns > 12 || columns < 1) { style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; } } else { // Pour les breakpoints responsives, on utilise les classes Tailwind // mais on peut ajouter un style par défaut pour mobile const { sm } = columns; if (!sm) { style.gridTemplateColumns = 'repeat(1, minmax(0, 1fr))'; } } return Object.keys(style).length > 0 ? style : undefined; }, [columns]); return (
{children}
); }