Refactor components and modals

This commit is contained in:
Urtzi Alfaro
2025-09-26 07:46:25 +02:00
parent cf4405b771
commit d573c38621
80 changed files with 3421 additions and 4617 deletions

View File

@@ -0,0 +1,321 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { LucideIcon, AlertTriangle, CheckCircle, XCircle, Info, X } from 'lucide-react';
import Modal, { ModalHeader, ModalBody, ModalFooter } from '../Modal/Modal';
import { Button } from '../Button';
export interface DialogModalAction {
label: string;
variant?: 'primary' | 'secondary' | 'outline' | 'danger';
onClick: () => void | Promise<void>;
disabled?: boolean;
loading?: boolean;
}
export interface DialogModalProps {
isOpen: boolean;
onClose: () => void;
// Content
title: string;
message: string | React.ReactNode;
type?: 'info' | 'warning' | 'error' | 'success' | 'confirm' | 'custom';
icon?: LucideIcon;
// Actions
actions?: DialogModalAction[];
showCloseButton?: boolean;
// Convenience props for common dialogs
onConfirm?: () => void | Promise<void>;
onCancel?: () => void;
confirmLabel?: string;
cancelLabel?: string;
// Layout
size?: 'xs' | 'sm' | 'md';
loading?: boolean;
}
/**
* DialogModal - Standardized component for simple confirmation dialogs and alerts
*
* Features:
* - Predefined dialog types with appropriate icons and styling
* - Support for custom actions or convenient confirm/cancel pattern
* - Responsive design optimized for mobile
* - Built-in loading states
* - Accessibility features
*/
export const DialogModal: React.FC<DialogModalProps> = ({
isOpen,
onClose,
title,
message,
type = 'info',
icon,
actions,
showCloseButton = true,
onConfirm,
onCancel,
confirmLabel,
cancelLabel,
size = 'sm',
loading = false,
}) => {
const { t } = useTranslation(['common']);
// Default labels with translation fallbacks
const defaultConfirmLabel = confirmLabel || t('common:modals.actions.confirm', 'Confirmar');
const defaultCancelLabel = cancelLabel || t('common:modals.actions.cancel', 'Cancelar');
// Get icon and colors based on dialog type
const getDialogConfig = () => {
if (icon) {
return { icon, color: 'var(--text-primary)' };
}
switch (type) {
case 'warning':
return { icon: AlertTriangle, color: '#f59e0b' }; // yellow-500
case 'error':
return { icon: XCircle, color: '#ef4444' }; // red-500
case 'success':
return { icon: CheckCircle, color: '#10b981' }; // emerald-500
case 'confirm':
return { icon: AlertTriangle, color: '#f59e0b' }; // yellow-500
case 'info':
default:
return { icon: Info, color: '#3b82f6' }; // blue-500
}
};
const { icon: DialogIcon, color } = getDialogConfig();
const handleConfirm = async () => {
if (onConfirm) {
await onConfirm();
}
onClose();
};
const handleCancel = () => {
if (onCancel) {
onCancel();
}
onClose();
};
// Generate actions based on type and props
const getActions = (): DialogModalAction[] => {
if (actions) {
return actions;
}
// For simple info/success/error dialogs, just show OK button
if (type === 'info' || type === 'success' || type === 'error') {
return [
{
label: t('common:modals.actions.ok', 'OK'),
variant: 'primary',
onClick: onClose,
disabled: loading,
}
];
}
// For confirm/warning dialogs, show cancel and confirm buttons
if (type === 'confirm' || type === 'warning') {
return [
{
label: defaultCancelLabel,
variant: 'outline',
onClick: handleCancel,
disabled: loading,
},
{
label: defaultConfirmLabel,
variant: type === 'warning' ? 'danger' : 'primary',
onClick: handleConfirm,
disabled: loading,
loading: loading,
}
];
}
// Default: just OK button
return [
{
label: 'OK',
variant: 'primary',
onClick: onClose,
disabled: loading,
}
];
};
const dialogActions = getActions();
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={size}
closeOnOverlayClick={!loading}
closeOnEscape={!loading}
showCloseButton={false}
>
<ModalHeader
title={
<div className="flex items-center gap-3">
{/* Dialog icon */}
<div
className="flex-shrink-0 p-2 rounded-lg"
style={{ backgroundColor: `${color}15` }}
>
<DialogIcon
className="w-6 h-6"
style={{ color }}
/>
</div>
{/* Title */}
<div>
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
{title}
</h2>
</div>
</div>
}
showCloseButton={showCloseButton && !loading}
onClose={onClose}
/>
<ModalBody padding="lg">
{loading && (
<div className="absolute inset-0 bg-[var(--bg-primary)]/80 backdrop-blur-sm flex items-center justify-center z-10">
<div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[var(--color-primary)]"></div>
<span className="text-[var(--text-secondary)]">{t('common:modals.processing', 'Procesando...')}</span>
</div>
</div>
)}
<div className="text-[var(--text-primary)]">
{typeof message === 'string' ? (
<p className="leading-relaxed">{message}</p>
) : (
message
)}
</div>
</ModalBody>
{dialogActions.length > 0 && (
<ModalFooter justify="end">
<div className="flex gap-3 w-full sm:w-auto">
{dialogActions.map((action, index) => (
<Button
key={index}
variant={action.variant || 'outline'}
onClick={action.onClick}
disabled={action.disabled || loading}
className={`${
dialogActions.length > 1 && size === 'xs' ? 'flex-1 sm:flex-none' : ''
} min-w-[80px]`}
>
{action.loading ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current"></div>
) : (
action.label
)}
</Button>
))}
</div>
</ModalFooter>
)}
</Modal>
);
};
// Convenience functions for common dialog types
export const showInfoDialog = (
title: string,
message: string,
onClose: () => void
): React.ReactElement => (
<DialogModal
isOpen={true}
onClose={onClose}
title={title}
message={message}
type="info"
/>
);
export const showWarningDialog = (
title: string,
message: string,
onConfirm: () => void,
onCancel?: () => void
): React.ReactElement => (
<DialogModal
isOpen={true}
onClose={onCancel || (() => {})}
title={title}
message={message}
type="warning"
onConfirm={onConfirm}
onCancel={onCancel}
/>
);
export const showErrorDialog = (
title: string,
message: string,
onClose: () => void
): React.ReactElement => (
<DialogModal
isOpen={true}
onClose={onClose}
title={title}
message={message}
type="error"
/>
);
export const showSuccessDialog = (
title: string,
message: string,
onClose: () => void
): React.ReactElement => (
<DialogModal
isOpen={true}
onClose={onClose}
title={title}
message={message}
type="success"
/>
);
export const showConfirmDialog = (
title: string,
message: string,
onConfirm: () => void,
onCancel?: () => void,
confirmLabel?: string,
cancelLabel?: string
): React.ReactElement => (
<DialogModal
isOpen={true}
onClose={onCancel || (() => {})}
title={title}
message={message}
type="confirm"
onConfirm={onConfirm}
onCancel={onCancel}
confirmLabel={confirmLabel}
cancelLabel={cancelLabel}
/>
);
export default DialogModal;

View File

@@ -0,0 +1,14 @@
export {
DialogModal,
default,
showInfoDialog,
showWarningDialog,
showErrorDialog,
showSuccessDialog,
showConfirmDialog
} from './DialogModal';
export type {
DialogModalProps,
DialogModalAction
} from './DialogModal';