migration wip
This commit is contained in:
251
components/ui/Grid.tsx
Normal file
251
components/ui/Grid.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, { forwardRef, ReactNode, HTMLAttributes } from 'react';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { getViewport } from '../../lib/responsive';
|
||||
|
||||
// Grid column types
|
||||
type GridCols = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
// Grid gap types
|
||||
type GridGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'responsive';
|
||||
|
||||
// Grid props interface
|
||||
interface GridProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: ReactNode;
|
||||
cols?: GridCols;
|
||||
gap?: GridGap;
|
||||
colsSm?: GridCols;
|
||||
colsMd?: GridCols;
|
||||
colsLg?: GridCols;
|
||||
colsXl?: GridCols;
|
||||
alignItems?: 'start' | 'center' | 'end' | 'stretch';
|
||||
justifyItems?: 'start' | 'center' | 'end' | 'stretch';
|
||||
// Mobile-first stacking
|
||||
stackMobile?: boolean;
|
||||
// Responsive columns
|
||||
responsiveCols?: {
|
||||
mobile?: GridCols;
|
||||
tablet?: GridCols;
|
||||
desktop?: GridCols;
|
||||
};
|
||||
}
|
||||
|
||||
// Grid item props interface
|
||||
interface GridItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: ReactNode;
|
||||
colSpan?: GridCols;
|
||||
colSpanSm?: GridCols;
|
||||
colSpanMd?: GridCols;
|
||||
colSpanLg?: GridCols;
|
||||
colSpanXl?: GridCols;
|
||||
rowSpan?: GridCols;
|
||||
rowSpanSm?: GridCols;
|
||||
rowSpanMd?: GridCols;
|
||||
rowSpanLg?: GridCols;
|
||||
rowSpanXl?: GridCols;
|
||||
}
|
||||
|
||||
// Helper function to get gap styles
|
||||
const getGapStyles = (gap: GridGap, responsiveGap?: boolean) => {
|
||||
if (gap === 'responsive' || responsiveGap) {
|
||||
return 'gap-2 xs:gap-3 sm:gap-4 md:gap-6 lg:gap-8';
|
||||
}
|
||||
|
||||
switch (gap) {
|
||||
case 'none':
|
||||
return 'gap-0';
|
||||
case 'xs':
|
||||
return 'gap-1';
|
||||
case 'sm':
|
||||
return 'gap-2';
|
||||
case 'md':
|
||||
return 'gap-4';
|
||||
case 'lg':
|
||||
return 'gap-6';
|
||||
case 'xl':
|
||||
return 'gap-8';
|
||||
case '2xl':
|
||||
return 'gap-12';
|
||||
default:
|
||||
return 'gap-4';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get column classes
|
||||
const getColClasses = (cols: GridCols | undefined, breakpoint: string = '') => {
|
||||
if (!cols) return '';
|
||||
const prefix = breakpoint ? `${breakpoint}:` : '';
|
||||
return `${prefix}grid-cols-${cols}`;
|
||||
};
|
||||
|
||||
// Helper function to get span classes
|
||||
const getSpanClasses = (span: GridCols | undefined, type: 'col' | 'row', breakpoint: string = '') => {
|
||||
if (!span) return '';
|
||||
const prefix = breakpoint ? `${breakpoint}:` : '';
|
||||
const typePrefix = type === 'col' ? 'col' : 'row';
|
||||
return `${prefix}${typePrefix}-span-${span}`;
|
||||
};
|
||||
|
||||
// Helper function to get responsive column classes
|
||||
const getResponsiveColClasses = (responsiveCols: GridProps['responsiveCols']) => {
|
||||
if (!responsiveCols) return '';
|
||||
|
||||
let classes = '';
|
||||
|
||||
// Mobile (default)
|
||||
if (responsiveCols.mobile) {
|
||||
classes += `grid-cols-${responsiveCols.mobile} `;
|
||||
}
|
||||
|
||||
// Tablet
|
||||
if (responsiveCols.tablet) {
|
||||
classes += `md:grid-cols-${responsiveCols.tablet} `;
|
||||
}
|
||||
|
||||
// Desktop
|
||||
if (responsiveCols.desktop) {
|
||||
classes += `lg:grid-cols-${responsiveCols.desktop} `;
|
||||
}
|
||||
|
||||
return classes;
|
||||
};
|
||||
|
||||
// Main Grid Component
|
||||
export const Grid = forwardRef<HTMLDivElement, GridProps>(
|
||||
(
|
||||
{
|
||||
cols = 1,
|
||||
gap = 'md',
|
||||
colsSm,
|
||||
colsMd,
|
||||
colsLg,
|
||||
colsXl,
|
||||
alignItems,
|
||||
justifyItems,
|
||||
className = '',
|
||||
children,
|
||||
stackMobile = false,
|
||||
responsiveCols,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
// Get responsive column configuration
|
||||
const getResponsiveColumns = () => {
|
||||
if (responsiveCols) {
|
||||
return getResponsiveColClasses(responsiveCols);
|
||||
}
|
||||
|
||||
if (stackMobile) {
|
||||
// Mobile-first: 1 column, then scale up
|
||||
return `grid-cols-1 sm:grid-cols-2 ${colsMd ? `md:grid-cols-${colsMd}` : 'md:grid-cols-3'} ${colsLg ? `lg:grid-cols-${colsLg}` : ''}`;
|
||||
}
|
||||
|
||||
// Default responsive behavior
|
||||
let colClasses = `grid-cols-${cols}`;
|
||||
if (colsSm) colClasses += ` sm:grid-cols-${colsSm}`;
|
||||
if (colsMd) colClasses += ` md:grid-cols-${colsMd}`;
|
||||
if (colsLg) colClasses += ` lg:grid-cols-${colsLg}`;
|
||||
if (colsXl) colClasses += ` xl:grid-cols-${colsXl}`;
|
||||
|
||||
return colClasses;
|
||||
};
|
||||
|
||||
// Get responsive gap
|
||||
const getResponsiveGap = () => {
|
||||
if (gap === 'responsive') {
|
||||
return 'gap-2 xs:gap-3 sm:gap-4 md:gap-6 lg:gap-8';
|
||||
}
|
||||
|
||||
// Mobile-first gap scaling
|
||||
if (stackMobile) {
|
||||
return 'gap-3 sm:gap-4 md:gap-6 lg:gap-8';
|
||||
}
|
||||
|
||||
return getGapStyles(gap);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
// Base grid
|
||||
'grid',
|
||||
// Responsive columns
|
||||
getResponsiveColumns(),
|
||||
// Gap (responsive)
|
||||
getResponsiveGap(),
|
||||
// Alignment
|
||||
alignItems && `items-${alignItems}`,
|
||||
justifyItems && `justify-items-${justifyItems}`,
|
||||
// Mobile-specific: ensure full width
|
||||
'w-full',
|
||||
// Custom classes
|
||||
className
|
||||
)}
|
||||
// Add role for accessibility
|
||||
role="grid"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Grid.displayName = 'Grid';
|
||||
|
||||
// Grid Item Component
|
||||
export const GridItem = forwardRef<HTMLDivElement, GridItemProps>(
|
||||
(
|
||||
{
|
||||
colSpan,
|
||||
colSpanSm,
|
||||
colSpanMd,
|
||||
colSpanLg,
|
||||
colSpanXl,
|
||||
rowSpan,
|
||||
rowSpanSm,
|
||||
rowSpanMd,
|
||||
rowSpanLg,
|
||||
rowSpanXl,
|
||||
className = '',
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
// Column spans
|
||||
getSpanClasses(colSpan, 'col'),
|
||||
getSpanClasses(colSpanSm, 'col', 'sm'),
|
||||
getSpanClasses(colSpanMd, 'col', 'md'),
|
||||
getSpanClasses(colSpanLg, 'col', 'lg'),
|
||||
getSpanClasses(colSpanXl, 'col', 'xl'),
|
||||
// Row spans
|
||||
getSpanClasses(rowSpan, 'row'),
|
||||
getSpanClasses(rowSpanSm, 'row', 'sm'),
|
||||
getSpanClasses(rowSpanMd, 'row', 'md'),
|
||||
getSpanClasses(rowSpanLg, 'row', 'lg'),
|
||||
getSpanClasses(rowSpanXl, 'row', 'xl'),
|
||||
// Ensure item doesn't overflow
|
||||
'min-w-0',
|
||||
// Custom classes
|
||||
className
|
||||
)}
|
||||
// Add role for accessibility
|
||||
role="gridcell"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
GridItem.displayName = 'GridItem';
|
||||
|
||||
// Export types for external use
|
||||
export type { GridProps, GridItemProps, GridCols, GridGap };
|
||||
Reference in New Issue
Block a user