Fortura/packages/ui/src/components/layout/layout-utils.tsx
2025-08-20 12:59:31 -06:00

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>
);
}