car_mms/app/components/ui/Form.tsx
2025-09-11 14:22:27 +03:00

233 lines
6.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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