271 lines
6.2 KiB
TypeScript
271 lines
6.2 KiB
TypeScript
|
|
/**
|
||
|
|
* FormGrid Component
|
||
|
|
*
|
||
|
|
* A reusable grid layout component that:
|
||
|
|
* - Provides responsive grid layouts for form fields
|
||
|
|
* - Handles different column configurations
|
||
|
|
* - Supports gap spacing
|
||
|
|
* - Maintains consistent alignment
|
||
|
|
*
|
||
|
|
* This component improves form layout and responsiveness.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import React from 'react';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// TYPES
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
export interface FormGridProps {
|
||
|
|
// Grid configuration
|
||
|
|
cols?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
sm?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
md?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
lg?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
xl?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
|
||
|
|
// Spacing
|
||
|
|
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||
|
|
gapX?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||
|
|
gapY?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||
|
|
|
||
|
|
// Alignment
|
||
|
|
align?: 'start' | 'center' | 'end' | 'stretch';
|
||
|
|
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
||
|
|
|
||
|
|
// Styling
|
||
|
|
className?: string;
|
||
|
|
|
||
|
|
// Children
|
||
|
|
children: React.ReactNode;
|
||
|
|
}
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// COMPONENT
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
export function FormGrid({
|
||
|
|
cols = 1,
|
||
|
|
sm,
|
||
|
|
md,
|
||
|
|
lg,
|
||
|
|
xl,
|
||
|
|
gap = 'md',
|
||
|
|
gapX,
|
||
|
|
gapY,
|
||
|
|
align = 'start',
|
||
|
|
justify = 'start',
|
||
|
|
className = '',
|
||
|
|
children,
|
||
|
|
}: FormGridProps) {
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// HELPER FUNCTIONS
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
const getGridCols = (cols: number) => {
|
||
|
|
return `grid-cols-${cols}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
const getResponsiveCols = () => {
|
||
|
|
const classes = [getGridCols(cols)];
|
||
|
|
|
||
|
|
if (sm) classes.push(`sm:grid-cols-${sm}`);
|
||
|
|
if (md) classes.push(`md:grid-cols-${md}`);
|
||
|
|
if (lg) classes.push(`lg:grid-cols-${lg}`);
|
||
|
|
if (xl) classes.push(`xl:grid-cols-${xl}`);
|
||
|
|
|
||
|
|
return classes.join(' ');
|
||
|
|
};
|
||
|
|
|
||
|
|
const getGap = (gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
|
||
|
|
switch (gap) {
|
||
|
|
case 'xs':
|
||
|
|
return 'gap-1';
|
||
|
|
case 'sm':
|
||
|
|
return 'gap-2';
|
||
|
|
case 'lg':
|
||
|
|
return 'gap-6';
|
||
|
|
case 'xl':
|
||
|
|
return 'gap-8';
|
||
|
|
default:
|
||
|
|
return 'gap-4';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getGapX = (gapX: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
|
||
|
|
switch (gapX) {
|
||
|
|
case 'xs':
|
||
|
|
return 'gap-x-1';
|
||
|
|
case 'sm':
|
||
|
|
return 'gap-x-2';
|
||
|
|
case 'lg':
|
||
|
|
return 'gap-x-6';
|
||
|
|
case 'xl':
|
||
|
|
return 'gap-x-8';
|
||
|
|
default:
|
||
|
|
return 'gap-x-4';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getGapY = (gapY: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => {
|
||
|
|
switch (gapY) {
|
||
|
|
case 'xs':
|
||
|
|
return 'gap-y-1';
|
||
|
|
case 'sm':
|
||
|
|
return 'gap-y-2';
|
||
|
|
case 'lg':
|
||
|
|
return 'gap-y-6';
|
||
|
|
case 'xl':
|
||
|
|
return 'gap-y-8';
|
||
|
|
default:
|
||
|
|
return 'gap-y-4';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getAlign = (align: 'start' | 'center' | 'end' | 'stretch') => {
|
||
|
|
switch (align) {
|
||
|
|
case 'center':
|
||
|
|
return 'items-center';
|
||
|
|
case 'end':
|
||
|
|
return 'items-end';
|
||
|
|
case 'stretch':
|
||
|
|
return 'items-stretch';
|
||
|
|
default:
|
||
|
|
return 'items-start';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getJustify = (justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly') => {
|
||
|
|
switch (justify) {
|
||
|
|
case 'center':
|
||
|
|
return 'justify-center';
|
||
|
|
case 'end':
|
||
|
|
return 'justify-end';
|
||
|
|
case 'between':
|
||
|
|
return 'justify-between';
|
||
|
|
case 'around':
|
||
|
|
return 'justify-around';
|
||
|
|
case 'evenly':
|
||
|
|
return 'justify-evenly';
|
||
|
|
default:
|
||
|
|
return 'justify-start';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// MAIN RENDER
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
const gridClasses = cn(
|
||
|
|
'grid',
|
||
|
|
getResponsiveCols(),
|
||
|
|
gapX ? getGapX(gapX) : gapY ? getGapY(gapY) : getGap(gap),
|
||
|
|
getAlign(align),
|
||
|
|
getJustify(justify),
|
||
|
|
className
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={gridClasses}>
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// GRID ITEM COMPONENT
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
export interface FormGridItemProps {
|
||
|
|
// Span configuration
|
||
|
|
span?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
sm?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
md?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
lg?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
xl?: 1 | 2 | 3 | 4 | 6 | 12;
|
||
|
|
|
||
|
|
// Alignment
|
||
|
|
align?: 'start' | 'center' | 'end' | 'stretch';
|
||
|
|
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
||
|
|
|
||
|
|
// Styling
|
||
|
|
className?: string;
|
||
|
|
|
||
|
|
// Children
|
||
|
|
children: React.ReactNode;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function FormGridItem({
|
||
|
|
span = 1,
|
||
|
|
sm,
|
||
|
|
md,
|
||
|
|
lg,
|
||
|
|
xl,
|
||
|
|
align,
|
||
|
|
justify,
|
||
|
|
className = '',
|
||
|
|
children,
|
||
|
|
}: FormGridItemProps) {
|
||
|
|
|
||
|
|
const getSpan = (span: number) => {
|
||
|
|
return `col-span-${span}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
const getResponsiveSpan = () => {
|
||
|
|
const classes = [getSpan(span)];
|
||
|
|
|
||
|
|
if (sm) classes.push(`sm:col-span-${sm}`);
|
||
|
|
if (md) classes.push(`md:col-span-${md}`);
|
||
|
|
if (lg) classes.push(`lg:col-span-${lg}`);
|
||
|
|
if (xl) classes.push(`xl:col-span-${xl}`);
|
||
|
|
|
||
|
|
return classes.join(' ');
|
||
|
|
};
|
||
|
|
|
||
|
|
const getAlign = (align: 'start' | 'center' | 'end' | 'stretch') => {
|
||
|
|
switch (align) {
|
||
|
|
case 'center':
|
||
|
|
return 'self-center';
|
||
|
|
case 'end':
|
||
|
|
return 'self-end';
|
||
|
|
case 'stretch':
|
||
|
|
return 'self-stretch';
|
||
|
|
default:
|
||
|
|
return 'self-start';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getJustify = (justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly') => {
|
||
|
|
switch (justify) {
|
||
|
|
case 'center':
|
||
|
|
return 'justify-self-center';
|
||
|
|
case 'end':
|
||
|
|
return 'justify-self-end';
|
||
|
|
case 'between':
|
||
|
|
return 'justify-self-stretch';
|
||
|
|
case 'around':
|
||
|
|
return 'justify-self-stretch';
|
||
|
|
case 'evenly':
|
||
|
|
return 'justify-self-stretch';
|
||
|
|
default:
|
||
|
|
return 'justify-self-start';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const itemClasses = cn(
|
||
|
|
getResponsiveSpan(),
|
||
|
|
align && getAlign(align),
|
||
|
|
justify && getJustify(justify),
|
||
|
|
className
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={itemClasses}>
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default FormGrid;
|