refactor(builder): extract shared Modal component for the three editor modals
New src/ui/Modal.tsx ({ open, onClose, title?, children, width?,
closeOnEscape?, closeOnBackdropClick?, backdropStyle?, backdropProps? })
owns the backdrop, Escape-to-close, backdrop-click-to-close, and a new
body-scroll-lock while open. Adopted by TemplateModal, HeadCodeModal, and
SitesmithModal; each keeps its own panel styling/header/footer as children
since those differ per modal.
- TemplateModal: Escape/backdrop-click still close the confirm-template
sub-dialog first via a wrapped onClose passed to Modal; the header's X
button keeps using the raw onClose prop (always fully closes), matching
prior asymmetric behavior.
- HeadCodeModal: straightforward adoption, no prior custom close logic.
- SitesmithModal: previously had no `open` prop, no Escape-to-close, and no
backdrop-click-to-close. Preserved via open (always mounted-open by its
parent already), closeOnEscape={false}, closeOnBackdropClick={false}; role
and aria-modal are passed through via backdropProps to keep them on the
same backdrop element as before.
Body scroll-lock while a modal is open is a small new addition (requested by
the task) applied uniformly; it has no visible effect since each modal's
opaque fixed-position backdrop already fully covers the viewport.
Added a light Modal.test.tsx (renders children, Escape/backdrop-click close
behavior, scroll-lock) using the existing react-dom/client + act harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import { MessageList } from './MessageList';
|
||||
import { ChatInput } from './ChatInput';
|
||||
import { WorkingIndicator } from './WorkingIndicator';
|
||||
import { SitesmithResponse } from '../../types/sitesmith';
|
||||
import { Modal } from '../../ui/Modal';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
@@ -39,7 +40,6 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
|
||||
const canChat = summary && (summary.status === 'OK_BONUS' || summary.status === 'OK_MONTHLY');
|
||||
|
||||
const overlay: React.CSSProperties = { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.8)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center' };
|
||||
const panel: React.CSSProperties = { background: '#0f0f17', border: '1px solid #27272a', borderRadius: 12, width: 'min(720px, 90vw)', maxHeight: '90vh', display: 'flex', flexDirection: 'column' };
|
||||
const header: React.CSSProperties = { display: 'flex', alignItems: 'center', padding: '14px 18px', borderBottom: '1px solid #27272a', gap: 8 };
|
||||
const body: React.CSSProperties = { flex: 1, padding: '14px 18px', overflowY: 'auto', display: 'flex', flexDirection: 'column' };
|
||||
@@ -93,7 +93,14 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div role="dialog" aria-modal="true" style={overlay}>
|
||||
<Modal
|
||||
open
|
||||
onClose={onClose}
|
||||
closeOnEscape={false}
|
||||
closeOnBackdropClick={false}
|
||||
backdropStyle={{ backgroundColor: 'rgba(0,0,0,0.8)', zIndex: 9000 }}
|
||||
backdropProps={{ role: 'dialog', 'aria-modal': true }}
|
||||
>
|
||||
<div style={panel}>
|
||||
<div style={header}>
|
||||
<div style={{ fontWeight: 600, color: '#fff' }}>✨ Sitesmith</div>
|
||||
@@ -166,6 +173,6 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
onCancel={() => setPendingReplace(null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user