diff --git a/frontend/src/components/ConfirmDialog.jsx b/frontend/src/components/ConfirmDialog.jsx index f46cf10..1dff978 100644 --- a/frontend/src/components/ConfirmDialog.jsx +++ b/frontend/src/components/ConfirmDialog.jsx @@ -18,76 +18,88 @@ export default function ConfirmDialog({ }) { if (!open) return null; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onCancel?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onCancel?.(); }} - > -
-
{ - if (e.key === 'Tab') { - const c = e.currentTarget; - const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onCancel?.(); }} + > +
+
{ + if (e.key === 'Tab') { + const c = e.currentTarget; + const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } } - } - }} - > -
-
{title}
-
- -
-
- {destructive && ( -
- + }} + > +
+
{title}
+
+ +
+
+ {destructive && ( +
+ +
+ )} +
+

{message}

- )} -
-

{message}

-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/ConfirmDiffModal.jsx b/frontend/src/components/ConfirmDiffModal.jsx index 9bee46d..72823c6 100644 --- a/frontend/src/components/ConfirmDiffModal.jsx +++ b/frontend/src/components/ConfirmDiffModal.jsx @@ -11,93 +11,105 @@ function ConfirmDiffModal({ show, diff, onConfirm, onClose, loading = false }) { if (!show) return null; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
{ - if (e.key === 'Tab') { - const c = e.currentTarget; - const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
{ + if (e.key === 'Tab') { + const c = e.currentTarget; + const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } } - } - }} - > -
-
Подтвердить сохранение
-
- -
-
-
-
-
- Добавлено -
{added}
+ }} + > +
+
Подтвердить сохранение
+
+ +
+
+
+
+
+ Добавлено +
{added}
+
-
-
-
-
- Удалено -
{removed}
+
+
+
+ Удалено +
{removed}
+
-
-
-
-
- Изменено -
{changed}
+
+
+
+ Изменено +
{changed}
+
-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/ConfirmModal.jsx b/frontend/src/components/ConfirmModal.jsx index 71f450c..c763ef4 100644 --- a/frontend/src/components/ConfirmModal.jsx +++ b/frontend/src/components/ConfirmModal.jsx @@ -23,75 +23,87 @@ function ConfirmModal({ onConfirm?.(); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
{ - if (e.key === 'Tab') { - const c = e.currentTarget; - const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
{ + if (e.key === 'Tab') { + const c = e.currentTarget; + const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } } - } - }} - > -
-
{title}
-
- -
-
- {showIcon && Icon && ( -
- + }} + > +
+
{title}
+
+ +
+
+ {showIcon && Icon && ( +
+ +
+ )} +
+ {typeof message === 'string' ?

{message}

: message}
- )} -
- {typeof message === 'string' ?

{message}

: message}
-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/FormModal.jsx b/frontend/src/components/FormModal.jsx index c4c5dc4..2f02420 100644 --- a/frontend/src/components/FormModal.jsx +++ b/frontend/src/components/FormModal.jsx @@ -36,55 +36,67 @@ function FormModal({ } }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
-
-
-
-
{title}
-
- -
- {children} -
- -
- - -
-
+
+
+
+
+
{title}
+
+ +
+ {children} +
+ +
+ + +
+
+
-
+ ); } diff --git a/frontend/src/components/HistoryModal.jsx b/frontend/src/components/HistoryModal.jsx index 56957bc..3bb20e5 100644 --- a/frontend/src/components/HistoryModal.jsx +++ b/frontend/src/components/HistoryModal.jsx @@ -55,13 +55,23 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack }) refetch().catch(() => notify.error('Не удалось загрузить историю версий')); } + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
{ if (e.key === 'Escape') onClose?.(); }} >
@@ -90,7 +100,7 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack }) История версий -
diff --git a/frontend/src/components/ImportModal.jsx b/frontend/src/components/ImportModal.jsx index d8eec5f..ea70faa 100644 --- a/frontend/src/components/ImportModal.jsx +++ b/frontend/src/components/ImportModal.jsx @@ -86,23 +86,34 @@ function ImportModal({ URL.revokeObjectURL(url); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - {title} -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + {title} +
+
{/* Description */} diff --git a/frontend/src/components/Modal.jsx b/frontend/src/components/Modal.jsx index 2698b50..489e9de 100644 --- a/frontend/src/components/Modal.jsx +++ b/frontend/src/components/Modal.jsx @@ -50,63 +50,75 @@ function Modal({ if (!show) return null; + const handleBackdropClick = (e) => { + if (backdrop && e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > + <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
{ if (e.key === 'Escape') onClose?.(); }} >
{ - if (e.key === 'Tab') { - const c = e.currentTarget; - const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); - } - } - }} + className={`modal-dialog${size !== 'md' ? ` modal-${size}` : ''}${scrollable ? ' modal-dialog-scrollable' : ''}${centered ? ' modal-dialog-centered' : ''}`} + role="document" > - {title && ( -
-
{title}
-
+ )} + +
+ {children}
- )} - -
- {children} + + {footer && ( +
+ {footer} +
+ )}
- - {footer && ( -
- {footer} -
- )}
-
+ ); } diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx index cf9ffe8..1cddd3f 100644 --- a/frontend/src/components/SettingsModal.jsx +++ b/frontend/src/components/SettingsModal.jsx @@ -103,23 +103,34 @@ export default function SettingsModal({ open, onClose }) { } }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - Настройки интерфейса -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + Настройки интерфейса +
+
{error && setError('')} />} diff --git a/frontend/src/components/WsUpdateModal.jsx b/frontend/src/components/WsUpdateModal.jsx index 8e48ed9..f44bfdf 100644 --- a/frontend/src/components/WsUpdateModal.jsx +++ b/frontend/src/components/WsUpdateModal.jsx @@ -117,24 +117,35 @@ function WsUpdateModal({ show, url, onClose }) { ); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - Логи запуска - {getStatusBadge()} -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + Логи запуска + {getStatusBadge()} +
+
{/* Controls */}