frontend_nyla/src/components/ui/Popup/Popup.tsx
2025-10-12 14:36:39 +02:00

132 lines
No EOL
3.4 KiB
TypeScript
Raw 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 React from 'react';
import styles from './Popup.module.css';
// Action button interface
export interface PopupAction {
label: string;
icon?: string | React.ReactElement;
onClick: () => void;
disabled?: boolean;
loading?: boolean;
variant?: 'primary' | 'secondary' | 'success' | 'danger';
}
// Generic popup props
export interface PopupProps {
isOpen: boolean;
title: string;
onClose: () => void;
children: React.ReactNode;
footerContent?: React.ReactNode;
className?: string;
size?: 'small' | 'medium' | 'large' | 'fullscreen';
closable?: boolean;
actions?: PopupAction[];
}
// Generic Popup component - can be used for any content
export function Popup({
isOpen,
title,
onClose,
children,
footerContent,
className = '',
size = 'medium',
closable = true,
actions = []
}: PopupProps) {
// Handle escape key
React.useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && closable) {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
// Prevent body scroll when popup is open
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = 'unset';
};
}, [isOpen, closable, onClose]);
if (!isOpen) return null;
// Handle backdrop click
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget && closable) {
onClose();
}
};
return (
<div className={styles.overlay} onClick={handleBackdropClick}>
<div className={`${styles.popup} ${styles[size]} ${className}`}>
{/* Header */}
<div className={styles.header}>
<h2 className={styles.title}>{title}</h2>
<div className={styles.headerActions}>
{/* Action buttons */}
{actions.map((action, index) => (
<button
key={index}
className={`${styles.actionButton} ${styles[action.variant || 'primary']}`}
onClick={action.onClick}
disabled={action.disabled || action.loading}
title={action.label}
>
{action.loading ? (
<>
<span className={styles.spinner}></span>
{action.label}
</>
) : (
<>
{action.icon && (
<span style={{ fontSize: '18px' }}>
{typeof action.icon === 'string' ? action.icon : action.icon}
</span>
)}
{action.label}
</>
)}
</button>
))}
{/* Close button */}
{closable && (
<button
className={styles.closeButton}
onClick={onClose}
aria-label="Close"
>
×
</button>
)}
</div>
</div>
{/* Content */}
<div className={styles.content}>
{children}
</div>
{/* Footer (optional) */}
{footerContent && (
<div className={styles.footer}>
{footerContent}
</div>
)}
</div>
</div>
);
}
export default Popup;