slideshare/src/components/themes/LayoutDetailPage.tsx
Michael Mainguy 1008bd4bca Implement slide preview and fix import standards project-wide
## New Feature: Full Screen Slide Preview
- Add SlidePreviewModal component for full screen slide preview in SlideEditor
- ESC key support and temporary hint for user guidance
- Proper aspect ratio handling with theme CSS inheritance
- Modal follows existing UI patterns for consistency

## Import Standards Compliance (31 files updated)
- Fix all imports to use explicit .tsx/.ts extensions per IMPORT_STANDARDS.md
- Eliminate barrel imports in App.tsx for better Vite tree shaking
- Add direct imports with explicit paths across entire codebase
- Preserve CSS imports and external library imports unchanged

## Code Architecture Improvements
- Add comprehensive CSS & Component Architecture Guidelines to CLAUDE.md
- Document modal patterns, aspect ratio handling, and CSS reuse principles
- Reference all coding standards files for consistent development workflow
- Prevent future CSS overcomplication and component duplication

## Performance Optimizations
- Enable Vite tree shaking with proper import structure
- Improve module resolution speed with explicit extensions
- Optimize build performance through direct imports

## Files Changed
- 31 TypeScript/React files with import fixes
- 2 new SlidePreviewModal files (component + CSS)
- Updated project documentation and coding guidelines
- Fixed aspect ratio CSS patterns across components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 06:52:56 -05:00

193 lines
6.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, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import type { Theme, SlideLayout } from '../../types/theme.ts';
import { getTheme } from '../../themes/index.ts';
import './LayoutDetailPage.css';
export const LayoutDetailPage: React.FC = () => {
const { themeId, layoutId } = useParams<{ themeId: string; layoutId: string }>();
const [theme, setTheme] = useState<Theme | null>(null);
const [layout, setLayout] = useState<SlideLayout | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'template' | 'slots'>('slots');
useEffect(() => {
const loadThemeAndLayout = async () => {
if (!themeId || !layoutId) {
setError('Missing theme ID or layout ID');
setLoading(false);
return;
}
try {
setLoading(true);
const themeData = await getTheme(themeId);
if (!themeData) {
setError(`Theme "${themeId}" not found`);
return;
}
const layoutData = themeData.layouts.find((l: SlideLayout) => l.id === layoutId);
if (!layoutData) {
setError(`Layout "${layoutId}" not found in theme "${themeId}"`);
return;
}
setTheme(themeData);
setLayout(layoutData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load theme and layout');
} finally {
setLoading(false);
}
};
loadThemeAndLayout();
}, [themeId, layoutId]);
if (loading) {
return (
<div className="layout-detail-page">
<div className="loading-spinner">Loading layout...</div>
</div>
);
}
if (error) {
return (
<div className="layout-detail-page">
<div className="error-message">
<h2>Error</h2>
<p>{error}</p>
<Link to="/themes" className="back-link"> Back to Themes</Link>
</div>
</div>
);
}
if (!theme || !layout) {
return (
<div className="layout-detail-page">
<div className="not-found">
<h2>Layout Not Found</h2>
<p>The requested layout could not be found.</p>
<Link to="/themes" className="back-link"> Back to Themes</Link>
</div>
</div>
);
}
return (
<div className="layout-detail-page">
<header className="layout-detail-header">
<nav className="breadcrumb">
<Link to="/themes">Themes</Link>
<span className="separator"></span>
<Link to={`/themes/${theme.id}`}>{theme.name}</Link>
<span className="separator"></span>
<span className="current">{layout.name}</span>
</nav>
<div className="layout-info">
<h1 className="layout-name">{layout.name}</h1>
<p className="layout-description">{layout.description}</p>
<div className="layout-stats">
<span className="slot-count">{layout.slots.length} slots</span>
<span className="theme-name">from {theme.name}</span>
<Link
to={`/themes/${theme.id}/layouts/${layout.id}/preview`}
className="preview-link"
>
View Full Preview
</Link>
</div>
</div>
</header>
<div className="view-mode-selector">
<button
type="button"
className={`mode-button ${viewMode === 'slots' ? 'active' : ''}`}
onClick={() => setViewMode('slots')}
>
Slots
</button>
<button
type="button"
className={`mode-button ${viewMode === 'template' ? 'active' : ''}`}
onClick={() => setViewMode('template')}
>
Template
</button>
</div>
<main className="layout-content">
{viewMode === 'template' && (
<section className="template-section">
<h2>HTML Template</h2>
<div className="template-code">
<pre><code>{layout.htmlTemplate}</code></pre>
</div>
</section>
)}
{viewMode === 'slots' && (
<section className="slots-section">
<h2>Slot Configuration</h2>
<div className="slots-grid">
{layout.slots.map((slot) => (
<div key={slot.id} className="slot-card">
<div className="slot-header">
<h3 className="slot-id">{slot.id}</h3>
<span className={`slot-type ${slot.type}`}>{slot.type}</span>
{slot.required && <span className="required-badge">Required</span>}
</div>
<div className="slot-details">
{slot.placeholder && (
<div className="slot-field">
<strong>Placeholder:</strong> {slot.placeholder}
</div>
)}
{slot.defaultContent && (
<div className="slot-field">
<strong>Default Content:</strong> {slot.defaultContent}
</div>
)}
{slot.className && (
<div className="slot-field">
<strong>CSS Class:</strong> <code>{slot.className}</code>
</div>
)}
{slot.style && (
<div className="slot-field">
<strong>Inline Style:</strong> <code>{slot.style}</code>
</div>
)}
{slot.attributes && Object.keys(slot.attributes).length > 0 && (
<div className="slot-field">
<strong>Attributes:</strong>
<div className="attributes-list">
{Object.entries(slot.attributes).map(([key, value]) => (
<div key={key} className="attribute-item">
<code>{key}="{value}"</code>
</div>
))}
</div>
</div>
)}
</div>
</div>
))}
</div>
</section>
)}
</main>
</div>
);
};