337 lines
8.1 KiB
TypeScript
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; |