uup
This commit is contained in:
233
app/components/ui/Form.tsx
Normal file
233
app/components/ui/Form.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
import { ReactNode, FormHTMLAttributes } from 'react';
|
||||
import { Form as RemixForm } from '@remix-run/react';
|
||||
import { defaultLayoutConfig, type LayoutConfig } from '~/lib/layout-utils';
|
||||
import { Button } from './Button';
|
||||
import { Text } from './Text';
|
||||
|
||||
interface FormProps extends Omit<FormHTMLAttributes<HTMLFormElement>, 'className'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
config?: Partial<LayoutConfig>;
|
||||
title?: string;
|
||||
description?: string;
|
||||
loading?: boolean;
|
||||
error?: string;
|
||||
success?: string;
|
||||
actions?: ReactNode;
|
||||
spacing?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export function Form({
|
||||
children,
|
||||
className = '',
|
||||
config = {},
|
||||
title,
|
||||
description,
|
||||
loading = false,
|
||||
error,
|
||||
success,
|
||||
actions,
|
||||
spacing = 'md',
|
||||
...props
|
||||
}: FormProps) {
|
||||
const layoutConfig = { ...defaultLayoutConfig, ...config };
|
||||
|
||||
const spacingClasses = {
|
||||
sm: 'space-y-4',
|
||||
md: 'space-y-6',
|
||||
lg: 'space-y-8',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${className}`} dir={layoutConfig.direction}>
|
||||
{/* Form Header */}
|
||||
{(title || description) && (
|
||||
<div className="mb-6">
|
||||
{title && (
|
||||
<Text as="h2" size="xl" weight="semibold" className="mb-2">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success Message */}
|
||||
{success && (
|
||||
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<svg className="h-5 w-5 text-green-400 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<Text color="success" size="sm">
|
||||
{success}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<svg className="h-5 w-5 text-red-400 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<Text color="error" size="sm">
|
||||
{error}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form Content */}
|
||||
<RemixForm className={spacingClasses[spacing]} {...props}>
|
||||
{children}
|
||||
|
||||
{/* Form Actions */}
|
||||
{actions && (
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
</RemixForm>
|
||||
|
||||
{/* Loading Overlay */}
|
||||
{loading && (
|
||||
<div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center rounded-lg">
|
||||
<div className="flex items-center space-x-2 space-x-reverse">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
<Text color="secondary">جاري المعالجة...</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Form Actions Component
|
||||
interface FormActionsProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
config?: Partial<LayoutConfig>;
|
||||
justify?: 'start' | 'center' | 'end' | 'between';
|
||||
spacing?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export function FormActions({
|
||||
children,
|
||||
className = '',
|
||||
config = {},
|
||||
justify = 'end',
|
||||
spacing = 'md',
|
||||
}: FormActionsProps) {
|
||||
const layoutConfig = { ...defaultLayoutConfig, ...config };
|
||||
|
||||
const justifyClasses = {
|
||||
start: 'justify-start',
|
||||
center: 'justify-center',
|
||||
end: 'justify-end',
|
||||
between: 'justify-between',
|
||||
};
|
||||
|
||||
const spacingClasses = {
|
||||
sm: 'space-x-2 space-x-reverse',
|
||||
md: 'space-x-4 space-x-reverse',
|
||||
lg: 'space-x-6 space-x-reverse',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex ${justifyClasses[justify]} ${spacingClasses[spacing]} ${className}`}
|
||||
dir={layoutConfig.direction}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Form Section Component
|
||||
interface FormSectionProps {
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
description?: string;
|
||||
className?: string;
|
||||
config?: Partial<LayoutConfig>;
|
||||
}
|
||||
|
||||
export function FormSection({
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
className = '',
|
||||
config = {},
|
||||
}: FormSectionProps) {
|
||||
const layoutConfig = { ...defaultLayoutConfig, ...config };
|
||||
|
||||
return (
|
||||
<div className={`${className}`} dir={layoutConfig.direction}>
|
||||
{(title || description) && (
|
||||
<div className="mb-4">
|
||||
{title && (
|
||||
<Text as="h3" size="lg" weight="medium" className="mb-1">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text color="secondary" size="sm">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Form Grid Component
|
||||
interface FormGridProps {
|
||||
children: ReactNode;
|
||||
columns?: 1 | 2 | 3 | 4;
|
||||
className?: string;
|
||||
config?: Partial<LayoutConfig>;
|
||||
gap?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export function FormGrid({
|
||||
children,
|
||||
columns = 2,
|
||||
className = '',
|
||||
config = {},
|
||||
gap = 'md',
|
||||
}: FormGridProps) {
|
||||
const layoutConfig = { ...defaultLayoutConfig, ...config };
|
||||
|
||||
const columnClasses = {
|
||||
1: 'grid-cols-1',
|
||||
2: 'grid-cols-1 md:grid-cols-2',
|
||||
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
|
||||
};
|
||||
|
||||
const gapClasses = {
|
||||
sm: 'gap-4',
|
||||
md: 'gap-6',
|
||||
lg: 'gap-8',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`grid ${columnClasses[columns]} ${gapClasses[gap]} ${className}`}
|
||||
dir={layoutConfig.direction}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user