mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Remove nested git repository from bugulma/frontend/.git - Add all frontend files to main repository tracking - Convert from separate frontend/backend repos to unified monorepo - Preserve all frontend code and development history as tracked files - Eliminate nested repository complexity for simpler development workflow This creates a proper monorepo structure with frontend and backend coexisting in the same repository for easier development and deployment.
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import Button from '@/components/ui/Button.tsx';
|
|
import { X } from 'lucide-react';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import { useFocusTrap } from '@/hooks/ui/useFocusTrap.ts';
|
|
import { useEscapeKey } from '@/hooks/useKeyboard.ts';
|
|
|
|
interface WizardProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
const Wizard = ({ isOpen, onClose, title, children }: WizardProps): React.ReactPortal | null => {
|
|
const { t } = useTranslation();
|
|
const trapRef = useFocusTrap<HTMLDivElement>(isOpen);
|
|
|
|
useEscapeKey(onClose, { enabled: isOpen });
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = 'hidden';
|
|
} else {
|
|
document.body.style.overflow = '';
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = '';
|
|
};
|
|
}, [isOpen]);
|
|
|
|
// Use portal for modal to render outside main DOM tree for better performance
|
|
const modalContent = (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
className="fixed inset-0 z-50 bg-overlay/50 backdrop-blur-sm flex items-center justify-center p-4"
|
|
onClick={onClose}
|
|
>
|
|
<motion.div
|
|
ref={trapRef}
|
|
initial={{ y: 50, scale: 0.95 }}
|
|
animate={{ y: 0, scale: 1 }}
|
|
exit={{ y: 50, scale: 0.95 }}
|
|
transition={{ duration: 0.3, ease: 'easeOut' }}
|
|
className="bg-background rounded-2xl shadow-2xl w-full max-w-2xl max-h-[calc(100vh-2rem)] flex flex-col"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="wizard-title"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<header className="p-4 border-b flex items-center justify-between shrink-0">
|
|
<h2 id="wizard-title" className="text-xl font-semibold">
|
|
{title}
|
|
</h2>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="p-2 h-8 w-8"
|
|
onClick={onClose}
|
|
aria-label={t('wizard.close')}
|
|
>
|
|
<X className="h-4 text-current w-4" />
|
|
</Button>
|
|
</header>
|
|
<main className="p-6 overflow-y-auto flex-1">{children}</main>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
|
|
// Render modal in portal for better performance and z-index management
|
|
return isOpen && typeof document !== 'undefined'
|
|
? createPortal(modalContent, document.body)
|
|
: null;
|
|
};
|
|
|
|
export default Wizard;
|