- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design. - Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri. - Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling. - Updated `ScribeShell` with migration popup logic to prompt users for data migration. - Integrated `AlertStack` for better alert handling and notification management. - Enhanced Rust/Tauri services with migration command implementations. - Added translations and styles for new components.
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
'use client';
|
|
import React, {ReactNode, useEffect, useState} from 'react';
|
|
import {createPortal} from 'react-dom';
|
|
import {LucideIcon, X} from "lucide-react";
|
|
import Button from "@/components/ui/Button";
|
|
import IconButton from "@/components/ui/IconButton";
|
|
|
|
type ModalSize = 'sm' | 'md' | 'lg';
|
|
|
|
interface ModalProps {
|
|
title: string;
|
|
icon?: LucideIcon;
|
|
children: ReactNode;
|
|
size?: ModalSize;
|
|
onClose: () => void;
|
|
onConfirm?: () => void;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
footer?: ReactNode;
|
|
actions?: ReactNode;
|
|
enableOverflow?: boolean;
|
|
}
|
|
|
|
const sizeClasses: Record<ModalSize, string> = {
|
|
sm: 'md:w-3/4 xl:w-1/4 lg:w-2/4 sm:w-11/12',
|
|
md: 'md:w-3/4 xl:w-2/5 lg:w-2/4 sm:w-11/12',
|
|
lg: 'md:w-3/4 xl:w-3/5 lg:w-3/4 sm:w-11/12',
|
|
};
|
|
|
|
export default function Modal(
|
|
{
|
|
title,
|
|
icon: Icon,
|
|
children,
|
|
size = 'md',
|
|
onClose,
|
|
onConfirm,
|
|
confirmText = 'Confirmer',
|
|
cancelText = 'Annuler',
|
|
footer,
|
|
actions,
|
|
enableOverflow = true,
|
|
}: ModalProps) {
|
|
const [mounted, setMounted] = useState<boolean>(false);
|
|
|
|
useEffect((): (() => void) => {
|
|
setMounted(true);
|
|
document.body.style.overflow = 'hidden';
|
|
return (): void => {
|
|
setMounted(false);
|
|
document.body.style.overflow = 'auto';
|
|
};
|
|
}, []);
|
|
|
|
const hasFooter: boolean = !!footer || !!onConfirm;
|
|
|
|
const modalContent: React.JSX.Element = (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-darkest-background/60 backdrop-blur-md animate-fadeIn">
|
|
<div
|
|
className={`relative bg-tertiary text-text-primary rounded-xl max-h-[90vh] overflow-hidden flex flex-col ${sizeClasses[size]}`}>
|
|
<div className="flex justify-between items-center px-6 py-4">
|
|
<h2 className="flex items-center gap-3 font-['ADLaM_Display'] text-xl tracking-wide">
|
|
{Icon && <Icon className="w-6 h-6" strokeWidth={1.75}/>}
|
|
{title}
|
|
</h2>
|
|
<div className="flex items-center gap-2">
|
|
{actions}
|
|
<IconButton icon={X} variant="light" onClick={onClose}/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`flex-1 min-h-0 bg-darkest-background rounded-xl mx-2 flex flex-col overflow-hidden ${!hasFooter ? 'mb-2' : ''}`}>
|
|
<div
|
|
className={`flex-1 min-h-0 ${enableOverflow ? 'overflow-auto custom-scrollbar' : 'overflow-hidden'}`}>
|
|
<div className="p-5 space-y-6">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{hasFooter && (
|
|
<div className="flex justify-end gap-3 px-6 py-4">
|
|
{footer ? footer : (
|
|
<>
|
|
<Button variant="secondary" onClick={onClose}>
|
|
{cancelText}
|
|
</Button>
|
|
<Button variant="primary" onClick={onConfirm}>
|
|
{confirmText}
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (!mounted) return null;
|
|
|
|
return createPortal(modalContent, document.body);
|
|
}
|