Files
klz-cables.com/components/layout/ResponsiveWrapper.tsx
2025-12-29 18:18:48 +01:00

337 lines
8.1 KiB
TypeScript

import React, { ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { getViewport } from '@/lib/responsive';
interface ResponsiveWrapperProps {
children: ReactNode;
className?: string;
// Visibility control
showOn?: ('mobile' | 'tablet' | 'desktop' | 'largeDesktop')[];
hideOn?: ('mobile' | 'tablet' | 'desktop' | 'largeDesktop')[];
// Mobile-specific behavior
stackOnMobile?: boolean;
centerOnMobile?: boolean;
// Padding control
padding?: 'none' | 'sm' | 'md' | 'lg' | 'responsive';
// Container control
container?: boolean;
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
}
/**
* ResponsiveWrapper Component
* Provides comprehensive responsive behavior for any content
*/
export function ResponsiveWrapper({
children,
className = '',
showOn,
hideOn,
stackOnMobile = false,
centerOnMobile = false,
padding = 'md',
container = false,
maxWidth = 'xl',
}: ResponsiveWrapperProps) {
// Get visibility classes
const getVisibilityClasses = () => {
let classes = '';
if (showOn) {
// Hide by default, show only on specified breakpoints
classes += 'hidden ';
if (showOn.includes('mobile')) classes += 'xs:block ';
if (showOn.includes('tablet')) classes += 'md:block ';
if (showOn.includes('desktop')) classes += 'lg:block ';
if (showOn.includes('largeDesktop')) classes += 'xl:block ';
}
if (hideOn) {
// Show by default, hide on specified breakpoints
if (hideOn.includes('mobile')) classes += 'xs:hidden ';
if (hideOn.includes('tablet')) classes += 'md:hidden ';
if (hideOn.includes('desktop')) classes += 'lg:hidden ';
if (hideOn.includes('largeDesktop')) classes += 'xl:hidden ';
}
return classes;
};
// Get mobile-specific classes
const getMobileClasses = () => {
let classes = '';
if (stackOnMobile) {
classes += 'flex-col xs:flex-row ';
}
if (centerOnMobile) {
classes += 'items-center xs:items-start text-center xs:text-left ';
}
return classes;
};
// Get padding classes
const getPaddingClasses = () => {
switch (padding) {
case 'none':
return '';
case 'sm':
return 'px-3 py-2 xs:px-4 xs:py-3';
case 'md':
return 'px-4 py-3 xs:px-6 xs:py-4';
case 'lg':
return 'px-5 py-4 xs:px-8 xs:py-6';
case 'responsive':
return 'px-4 py-3 xs:px-6 xs:py-4 md:px-8 md:py-6 lg:px-10 lg:py-8';
default:
return 'px-4 py-3';
}
};
// Get container classes if needed
const getContainerClasses = () => {
if (!container) return '';
const maxWidthClasses = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
'2xl': 'max-w-2xl',
'3xl': 'max-w-3xl',
full: 'max-w-full',
};
return `mx-auto ${maxWidthClasses[maxWidth]} w-full`;
};
const wrapperClasses = cn(
// Base classes
'responsive-wrapper',
// Visibility
getVisibilityClasses(),
// Mobile behavior
getMobileClasses(),
// Padding
getPaddingClasses(),
// Container
getContainerClasses(),
// Custom classes
className
);
return <div className={wrapperClasses}>{children}</div>;
}
/**
* ResponsiveGrid Wrapper
* Creates responsive grid layouts with mobile-first approach
*/
interface ResponsiveGridProps {
children: ReactNode;
className?: string;
// Column configuration
columns?: {
mobile?: number;
tablet?: number;
desktop?: number;
largeDesktop?: number;
};
gap?: 'none' | 'sm' | 'md' | 'lg' | 'responsive';
// Mobile stacking
stackMobile?: boolean;
// Alignment
alignItems?: 'start' | 'center' | 'end' | 'stretch';
justifyItems?: 'start' | 'center' | 'end' | 'stretch';
}
export function ResponsiveGrid({
children,
className = '',
columns = {},
gap = 'md',
stackMobile = false,
alignItems = 'start',
justifyItems = 'start',
}: ResponsiveGridProps) {
const getGridColumns = () => {
if (stackMobile) {
return `grid-cols-1 sm:grid-cols-2 md:grid-cols-${columns.tablet || 3} lg:grid-cols-${columns.desktop || 4}`;
}
const mobile = columns.mobile || 1;
const tablet = columns.tablet || 2;
const desktop = columns.desktop || 3;
const largeDesktop = columns.largeDesktop || 4;
return `grid-cols-${mobile} sm:grid-cols-${tablet} md:grid-cols-${desktop} lg:grid-cols-${largeDesktop}`;
};
const getGapClasses = () => {
switch (gap) {
case 'none':
return 'gap-0';
case 'sm':
return 'gap-2 sm:gap-3 md:gap-4';
case 'md':
return 'gap-3 sm:gap-4 md:gap-6 lg:gap-8';
case 'lg':
return 'gap-4 sm:gap-6 md:gap-8 lg:gap-12';
case 'responsive':
return 'gap-2 xs:gap-3 sm:gap-4 md:gap-6 lg:gap-8 xl:gap-12';
default:
return 'gap-4';
}
};
return (
<div
className={cn(
'grid',
'w-full',
getGridColumns(),
getGapClasses(),
alignItems && `items-${alignItems}`,
justifyItems && `justify-items-${justifyItems}`,
className
)}
>
{children}
</div>
);
}
/**
* ResponsiveStack Wrapper
* Creates vertical stack that becomes horizontal on larger screens
*/
interface ResponsiveStackProps {
children: ReactNode;
className?: string;
gap?: 'none' | 'sm' | 'md' | 'lg' | 'responsive';
reverseOnMobile?: boolean;
wrap?: boolean;
}
export function ResponsiveStack({
children,
className = '',
gap = 'md',
reverseOnMobile = false,
wrap = false,
}: ResponsiveStackProps) {
const getGapClasses = () => {
switch (gap) {
case 'none':
return 'gap-0';
case 'sm':
return 'gap-2 sm:gap-3 md:gap-4';
case 'md':
return 'gap-3 sm:gap-4 md:gap-6 lg:gap-8';
case 'lg':
return 'gap-4 sm:gap-6 md:gap-8 lg:gap-12';
case 'responsive':
return 'gap-2 xs:gap-3 sm:gap-4 md:gap-6 lg:gap-8';
default:
return 'gap-4';
}
};
return (
<div
className={cn(
'flex',
// Mobile-first: column, then row
'flex-col',
'xs:flex-row',
// Gap
getGapClasses(),
// Wrap
wrap ? 'flex-wrap xs:flex-nowrap' : 'flex-nowrap',
// Reverse on mobile
reverseOnMobile && 'flex-col-reverse xs:flex-row',
// Ensure proper spacing
'w-full',
className
)}
>
{children}
</div>
);
}
/**
* ResponsiveSection Wrapper
* Optimized section with responsive padding and max-width
*/
interface ResponsiveSectionProps {
children: ReactNode;
className?: string;
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'responsive';
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | 'full';
centered?: boolean;
safeArea?: boolean;
}
export function ResponsiveSection({
children,
className = '',
padding = 'responsive',
maxWidth = '6xl',
centered = true,
safeArea = false,
}: ResponsiveSectionProps) {
const getPaddingClasses = () => {
switch (padding) {
case 'none':
return '';
case 'sm':
return 'px-3 py-4 xs:px-4 xs:py-6 md:px-6 md:py-8';
case 'md':
return 'px-4 py-6 xs:px-6 xs:py-8 md:px-8 md:py-12';
case 'lg':
return 'px-5 py-8 xs:px-8 xs:py-12 md:px-12 md:py-16';
case 'xl':
return 'px-6 py-10 xs:px-10 xs:py-14 md:px-16 md:py-20';
case 'responsive':
return 'px-4 py-6 xs:px-6 xs:py-8 md:px-8 md:py-12 lg:px-12 lg:py-16 xl:px-16 xl:py-20';
default:
return 'px-4 py-6';
}
};
const getMaxWidthClasses = () => {
const maxWidthMap = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
'2xl': 'max-w-2xl',
'3xl': 'max-w-3xl',
'4xl': 'max-w-4xl',
'5xl': 'max-w-5xl',
'6xl': 'max-w-6xl',
full: 'max-w-full',
};
return maxWidthMap[maxWidth];
};
return (
<section
className={cn(
'w-full',
centered && 'mx-auto',
getMaxWidthClasses(),
getPaddingClasses(),
safeArea && 'safe-area-p',
className
)}
>
{children}
</section>
);
}
export default ResponsiveWrapper;