This commit is contained in:
2026-01-23 20:35:40 +03:00
parent cf3b0e48ec
commit 66c151653e
137 changed files with 41495 additions and 0 deletions

233
app/components/ui/Form.tsx Normal file
View 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>
);
}