migration wip
This commit is contained in:
337
components/layout/ResponsiveWrapper.tsx
Normal file
337
components/layout/ResponsiveWrapper.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user