Powerpoint_AI / components /slides /ImageAndTextLayout.tsx
Reubencf's picture
Readme updated
45b3fab
/*
* ImageAndTextLayout.tsx
* Purpose: Implements one of the generic fallback slide layouts used when a template-specific renderer is unavailable.
* Used by: components/slides/SlideFactory.
* Depends on: Template styling props and inline field-edit callbacks.
*/
'use client';
import React, { useState } from 'react';
import { TemplateStyles } from '@/data/templates';
export interface ImageAndTextLayoutProps {
title: string;
body?: string;
imageUrl?: string;
styles: TemplateStyles;
slideId?: string;
isEditable?: boolean;
onFieldUpdate?: (slideId: string, field: string, value: string) => void;
}
export default function ImageAndTextLayout({
title,
body,
imageUrl,
styles,
slideId,
isEditable = false,
onFieldUpdate,
}: ImageAndTextLayoutProps) {
const [editingField, setEditingField] = useState<string | null>(null);
const [tempTitle, setTempTitle] = useState(title);
const [tempBody, setTempBody] = useState(body || '');
const handleBlur = (field: string) => {
if (!slideId || !onFieldUpdate) return;
if (field === 'title' && tempTitle !== title) onFieldUpdate(slideId, 'title', tempTitle);
if (field === 'body' && tempBody !== body) onFieldUpdate(slideId, 'body', tempBody);
setEditingField(null);
};
const handleKeyDown = (e: React.KeyboardEvent, field: string) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleBlur(field); }
if (e.key === 'Escape') {
setEditingField(null);
setTempTitle(title);
setTempBody(body || '');
}
};
return (
<div
className="w-full h-full flex relative overflow-hidden"
style={{ backgroundColor: styles.colors.background, color: styles.colors.text }}
>
{styles.dotPattern && (
<div aria-hidden className="absolute inset-0 opacity-30" style={{ background: styles.dotPattern }} />
)}
{/* Image Side */}
<div
className="w-[45%] h-full relative shrink-0"
style={{
backgroundColor: styles.colors.cardBg,
borderRight: `${styles.border.width} solid ${styles.colors.border}`,
}}
>
{imageUrl ? (
<img
src={imageUrl}
alt={title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center" style={{ backgroundColor: styles.colors.cardBg }}>
<span style={{ fontFamily: styles.fonts.body, fontSize: '14px', opacity: 0.5 }}>
Image placeholder
</span>
</div>
)}
</div>
{/* Text Side */}
<div className="flex-1 p-10 flex flex-col justify-center relative z-10">
{/* Title */}
{editingField === 'title' ? (
<input
type="text"
value={tempTitle}
onChange={(e) => setTempTitle(e.target.value)}
onBlur={() => handleBlur('title')}
onKeyDown={(e) => handleKeyDown(e, 'title')}
className="bg-transparent outline-none mb-6"
style={{
fontFamily: styles.fonts.heading,
fontSize: '36px',
fontWeight: 'bold',
textTransform: 'uppercase',
color: styles.colors.text,
backgroundColor: styles.colors.accent,
border: `${styles.border.width} solid ${styles.colors.border}`,
boxShadow: styles.border.shadow,
padding: '8px 20px',
}}
autoFocus
/>
) : (
<h2
className={`mb-6 ${isEditable ? 'cursor-pointer' : ''}`}
style={{
fontFamily: styles.fonts.heading,
fontSize: '36px',
fontWeight: 'bold',
textTransform: 'uppercase',
color: styles.colors.text,
display: 'inline-block',
alignSelf: 'flex-start',
backgroundColor: styles.colors.accent,
border: `${styles.border.width} solid ${styles.colors.border}`,
boxShadow: styles.border.shadow,
padding: '8px 20px',
}}
onClick={() => { if (isEditable) { setEditingField('title'); setTempTitle(title); } }}
>
{title}
</h2>
)}
{/* Body text */}
{editingField === 'body' ? (
<textarea
value={tempBody}
onChange={(e) => setTempBody(e.target.value)}
onBlur={() => handleBlur('body')}
onKeyDown={(e) => handleKeyDown(e, 'body')}
className="bg-transparent outline-none resize-none flex-1"
style={{
fontFamily: styles.fonts.body,
fontSize: '17px',
color: styles.colors.text,
lineHeight: 1.7,
opacity: 0.85,
}}
autoFocus
/>
) : (
<p
className={`${isEditable ? 'cursor-pointer hover:opacity-70' : ''}`}
style={{
fontFamily: styles.fonts.body,
fontSize: '17px',
color: styles.colors.text,
lineHeight: 1.7,
opacity: 0.85,
}}
onClick={() => { if (isEditable) { setEditingField('body'); setTempBody(body || ''); } }}
>
{body || (isEditable ? 'Click to add description' : '')}
</p>
)}
</div>
</div>
);
}