migration wip
This commit is contained in:
192
components/cards/CardGrid.tsx
Normal file
192
components/cards/CardGrid.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { LoadingSkeleton } from '@/components/ui';
|
||||
|
||||
// CardGrid column options
|
||||
export type GridColumns = 1 | 2 | 3 | 4;
|
||||
|
||||
// CardGrid gap options
|
||||
export type GridGap = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
// CardGrid props interface
|
||||
export interface CardGridProps {
|
||||
/** Card items to render */
|
||||
items?: ReactNode[];
|
||||
/** Number of columns */
|
||||
columns?: GridColumns;
|
||||
/** Gap spacing */
|
||||
gap?: GridGap;
|
||||
/** Loading state */
|
||||
loading?: boolean;
|
||||
/** Empty state message */
|
||||
emptyMessage?: string;
|
||||
/** Empty state component */
|
||||
emptyComponent?: ReactNode;
|
||||
/** Loading skeleton count */
|
||||
skeletonCount?: number;
|
||||
/** Additional classes */
|
||||
className?: string;
|
||||
/** Children (alternative to items) */
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// Helper function to get gap classes
|
||||
const getGapClasses = (gap: GridGap): string => {
|
||||
const gapMap = {
|
||||
xs: 'gap-2',
|
||||
sm: 'gap-4',
|
||||
md: 'gap-6',
|
||||
lg: 'gap-8',
|
||||
xl: 'gap-12',
|
||||
};
|
||||
return gapMap[gap];
|
||||
};
|
||||
|
||||
// Helper function to get column classes
|
||||
const getColumnClasses = (columns: GridColumns): string => {
|
||||
const columnMap = {
|
||||
1: 'grid-cols-1',
|
||||
2: 'grid-cols-1 md:grid-cols-2',
|
||||
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
return columnMap[columns];
|
||||
};
|
||||
|
||||
// Skeleton loader component
|
||||
const GridSkeleton = ({
|
||||
count,
|
||||
columns,
|
||||
gap
|
||||
}: {
|
||||
count: number;
|
||||
columns: GridColumns;
|
||||
gap: GridGap;
|
||||
}) => {
|
||||
const gapClasses = getGapClasses(gap);
|
||||
const columnClasses = getColumnClasses(columns);
|
||||
|
||||
return (
|
||||
<div className={cn('grid', columnClasses, gapClasses)}>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<div key={index} className="animate-pulse">
|
||||
<div className="bg-gray-200 rounded-lg h-64" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Empty state component
|
||||
const EmptyState = ({
|
||||
message,
|
||||
customComponent
|
||||
}: {
|
||||
message?: string;
|
||||
customComponent?: ReactNode;
|
||||
}) => {
|
||||
if (customComponent) {
|
||||
return <div className="text-center py-12">{customComponent}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-gray-400 text-lg mb-2">
|
||||
{message || 'No items to display'}
|
||||
</div>
|
||||
<div className="text-gray-300 text-sm">
|
||||
{message ? '' : 'Try adjusting your filters or check back later'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CardGrid: React.FC<CardGridProps> = ({
|
||||
items,
|
||||
columns = 3,
|
||||
gap = 'md',
|
||||
loading = false,
|
||||
emptyMessage,
|
||||
emptyComponent,
|
||||
skeletonCount = 6,
|
||||
className = '',
|
||||
children,
|
||||
}) => {
|
||||
// Use children if provided, otherwise use items
|
||||
const content = children || items;
|
||||
|
||||
// Loading state
|
||||
if (loading) {
|
||||
return (
|
||||
<GridSkeleton
|
||||
count={skeletonCount}
|
||||
columns={columns}
|
||||
gap={gap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state
|
||||
if (!content || (Array.isArray(content) && content.length === 0)) {
|
||||
return (
|
||||
<EmptyState
|
||||
message={emptyMessage}
|
||||
customComponent={emptyComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Render grid
|
||||
const gapClasses = getGapClasses(gap);
|
||||
const columnClasses = getColumnClasses(columns);
|
||||
|
||||
return (
|
||||
<div className={cn('grid', columnClasses, gapClasses, className)}>
|
||||
{Array.isArray(content)
|
||||
? content.map((item, index) => (
|
||||
<div key={index} className="contents">
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
: content
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Grid variations
|
||||
export const CardGrid2: React.FC<CardGridProps> = (props) => (
|
||||
<CardGrid {...props} columns={2} />
|
||||
);
|
||||
|
||||
export const CardGrid3: React.FC<CardGridProps> = (props) => (
|
||||
<CardGrid {...props} columns={3} />
|
||||
);
|
||||
|
||||
export const CardGrid4: React.FC<CardGridProps> = (props) => (
|
||||
<CardGrid {...props} columns={4} />
|
||||
);
|
||||
|
||||
// Responsive grid with auto columns
|
||||
export const CardGridAuto: React.FC<CardGridProps> = ({
|
||||
gap = 'md',
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
const gapClasses = getGapClasses(gap);
|
||||
|
||||
return (
|
||||
<div className={cn('grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4', gapClasses, className)}>
|
||||
{props.items && Array.isArray(props.items)
|
||||
? props.items.map((item, index) => (
|
||||
<div key={index} className="contents">
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
: props.children
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user