110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
import * as React from 'react';
|
|
|
|
import { cn } from '@workspace/ui/lib/utils';
|
|
|
|
// Container component for consistent horizontal padding and optional max-width centering
|
|
type MaxWidth = '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | '10xl' | 'full';
|
|
|
|
const maxWidthClassMap: Record<MaxWidth, string> = {
|
|
'3xl': 'max-w-3xl',
|
|
'4xl': 'max-w-4xl',
|
|
'5xl': 'max-w-5xl',
|
|
'6xl': 'max-w-6xl',
|
|
'7xl': 'max-w-7xl',
|
|
'10xl': 'max-w-8xl',
|
|
'full': 'max-w-none',
|
|
};
|
|
|
|
export function Container({
|
|
children,
|
|
className,
|
|
maxWidth = 'full',
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement> & { maxWidth?: MaxWidth }) {
|
|
const maxW = maxWidthClassMap[maxWidth] ?? maxWidthClassMap['7xl'];
|
|
return (
|
|
<div className={cn('w-full mx-auto px-6 md:px-8', maxW, className)} {...props}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Section component for vertical spacing and optional background/border
|
|
export function Section({
|
|
children,
|
|
className,
|
|
id,
|
|
background = 'default', // 'default', 'muted', 'card' - maps to bg-* classes
|
|
padding = 'default', // 'default', 'sm', 'lg', 'xl', 'none'
|
|
border = false, // Adds a top border
|
|
...props
|
|
}: React.HTMLAttributes<HTMLElement> & {
|
|
background?: 'default' | 'muted' | 'card';
|
|
padding?: 'default' | 'sm' | 'lg' | 'xl' | 'none';
|
|
border?: boolean;
|
|
}) {
|
|
const backgroundClass = background === 'muted' ? 'bg-muted' : background === 'card' ? 'bg-card' : '';
|
|
const borderClass = border ? 'border-t' : '';
|
|
|
|
// More generous spacing for a cleaner aesthetic
|
|
const paddingClasses = {
|
|
'default': 'py-16 md:py-20',
|
|
'sm': 'py-8 md:py-12',
|
|
'lg': 'py-20 md:py-28',
|
|
'xl': 'py-28 md:py-40',
|
|
'none': '',
|
|
};
|
|
const paddingClass = paddingClasses[padding] || paddingClasses['default'];
|
|
|
|
return (
|
|
<section
|
|
id={id}
|
|
className={cn(paddingClass, backgroundClass, borderClass, className)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// Grid component for consistent grid layouts
|
|
// Uses CSS Grid with responsive columns
|
|
export function Grid({
|
|
children,
|
|
className,
|
|
columns = {
|
|
initial: 1, // Number of columns on smallest screens
|
|
sm: undefined, // Optional breakpoint
|
|
md: undefined, // Optional breakpoint
|
|
lg: undefined, // Optional breakpoint
|
|
xl: undefined, // Optional breakpoint
|
|
},
|
|
gap = '6', // Tailwind gap size (e.g., '4', '6', '8')
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement> & {
|
|
columns?: {
|
|
initial?: number;
|
|
sm?: number;
|
|
md?: number;
|
|
lg?: number;
|
|
xl?: number;
|
|
};
|
|
gap?: string; // Tailwind gap class suffix (e.g., '4' for gap-4)
|
|
}) {
|
|
const columnClasses = [];
|
|
if (columns.initial) columnClasses.push(`grid-cols-${columns.initial}`);
|
|
if (columns.sm) columnClasses.push(`sm:grid-cols-${columns.sm}`);
|
|
if (columns.md) columnClasses.push(`md:grid-cols-${columns.md}`);
|
|
if (columns.lg) columnClasses.push(`lg:grid-cols-${columns.lg}`);
|
|
if (columns.xl) columnClasses.push(`xl:grid-cols-${columns.xl}`);
|
|
|
|
return (
|
|
<div
|
|
className={cn('grid', columnClasses.join(' '), gap && `gap-${gap}`, className)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|