- Remove padding and shadows for true full-screen experience - Use theme background color to fill empty space around slides - Optimize aspect ratio calculations to maximize screen usage - Maintain proper aspect ratios without distortion - Add theme-background class for proper CSS variable inheritance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
118 lines
3.1 KiB
TypeScript
118 lines
3.1 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import type { SlideLayout } from '../../types/theme.ts';
|
|
import { sanitizeSlideTemplate } from '../../utils/htmlSanitizer.ts';
|
|
import './SlidePreviewModal.css';
|
|
|
|
interface SlidePreviewModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
layout: SlideLayout;
|
|
content: Record<string, string>;
|
|
aspectRatio: string;
|
|
themeName: string;
|
|
}
|
|
|
|
export const SlidePreviewModal: React.FC<SlidePreviewModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
layout,
|
|
content,
|
|
aspectRatio,
|
|
themeName
|
|
}) => {
|
|
const [showHint, setShowHint] = useState(true);
|
|
|
|
// Handle ESC key press
|
|
useEffect(() => {
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape' && isOpen) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
if (isOpen) {
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
document.body.style.overflow = 'hidden'; // Prevent scrolling
|
|
|
|
// Hide hint after 3 seconds
|
|
const hintTimer = setTimeout(() => {
|
|
setShowHint(false);
|
|
}, 3000);
|
|
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
document.body.style.overflow = 'unset';
|
|
clearTimeout(hintTimer);
|
|
};
|
|
}
|
|
}, [isOpen, onClose]);
|
|
|
|
// Reset hint when modal opens
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setShowHint(true);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
// Render template with actual content
|
|
const renderTemplateWithContent = (layout: SlideLayout, content: Record<string, string>): string => {
|
|
let rendered = layout.htmlTemplate;
|
|
|
|
// Replace content placeholders
|
|
Object.entries(content).forEach(([slotId, value]) => {
|
|
const placeholder = new RegExp(`\\{\\{${slotId}\\}\\}`, 'g');
|
|
rendered = rendered.replace(placeholder, value || '');
|
|
});
|
|
|
|
// Clean up any remaining placeholders
|
|
rendered = rendered.replace(/\{\{[^}]+\}\}/g, '');
|
|
|
|
return rendered;
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const handleOverlayClick = (event: React.MouseEvent) => {
|
|
if (event.target === event.currentTarget) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="slide-preview-modal theme-background" onClick={handleOverlayClick}>
|
|
{/* ESC hint */}
|
|
{showHint && (
|
|
<div className="preview-hint">
|
|
<span>Press ESC to exit preview</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Close button */}
|
|
<button
|
|
className="preview-close-button"
|
|
onClick={onClose}
|
|
type="button"
|
|
title="Close preview (ESC)"
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
{/* Theme info */}
|
|
<div className="preview-info">
|
|
<span className="theme-name">{themeName}</span>
|
|
<span className="layout-name">{layout.name}</span>
|
|
<span className="aspect-ratio">{aspectRatio}</span>
|
|
</div>
|
|
|
|
{/* Slide content */}
|
|
<div className="slide-preview-wrapper">
|
|
<div
|
|
className={`slide-container aspect-${aspectRatio.replace(':', '-')}`}
|
|
dangerouslySetInnerHTML={{
|
|
__html: sanitizeSlideTemplate(renderTemplateWithContent(layout, content))
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |