Powerpoint_AI / components /slides /ThreeColumnLayout.tsx
Reubencf's picture
Readme updated
45b3fab
/*
* ThreeColumnLayout.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 ThreeColumnLayoutProps {
title: string;
columns: Array<{ heading: string; text: string }>;
styles: TemplateStyles;
slideId?: string;
isEditable?: boolean;
onFieldUpdate?: (slideId: string, field: string, value: string, index?: number) => void;
}
export default function ThreeColumnLayout({
title,
columns,
styles,
slideId,
isEditable = false,
onFieldUpdate,
}: ThreeColumnLayoutProps) {
const [editingField, setEditingField] = useState<{ field: string; index?: number; sub?: string } | null>(null);
const [tempTitle, setTempTitle] = useState(title);
const [tempColumns, setTempColumns] = useState(columns);
const handleBlur = (field: string, index?: number) => {
if (!slideId || !onFieldUpdate) return;
if (field === 'title' && tempTitle !== title) onFieldUpdate(slideId, 'title', tempTitle);
if (field === 'columns' && index !== undefined) {
onFieldUpdate(slideId, 'columns', JSON.stringify(tempColumns[index]), index);
}
setEditingField(null);
};
const handleKeyDown = (e: React.KeyboardEvent, field: string, index?: number) => {
if (e.key === 'Enter') { e.preventDefault(); handleBlur(field, index); }
if (e.key === 'Escape') {
setEditingField(null);
setTempTitle(title);
setTempColumns(columns);
}
};
return (
<div
className="w-full h-full flex flex-col relative overflow-hidden p-10"
style={{ backgroundColor: styles.colors.background, color: styles.colors.text }}
>
{styles.dotPattern && (
<div aria-hidden className="absolute inset-0 opacity-30" style={{ background: styles.dotPattern }} />
)}
<div className="relative z-10 flex flex-col h-full">
{/* Title */}
{editingField?.field === '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-8"
style={{
fontFamily: styles.fonts.heading,
fontSize: '42px',
fontWeight: 'bold',
textTransform: 'uppercase',
backgroundColor: styles.colors.accent,
border: `${styles.border.width} solid ${styles.colors.border}`,
boxShadow: styles.border.shadow,
padding: '8px 24px',
color: styles.colors.text,
}}
autoFocus
/>
) : (
<h2
className={`mb-8 ${isEditable ? 'cursor-pointer' : ''}`}
style={{
fontFamily: styles.fonts.heading,
fontSize: '42px',
fontWeight: 'bold',
textTransform: 'uppercase',
display: 'inline-block',
alignSelf: 'flex-start',
backgroundColor: styles.colors.accent,
border: `${styles.border.width} solid ${styles.colors.border}`,
boxShadow: styles.border.shadow,
padding: '8px 24px',
color: styles.colors.text,
}}
onClick={() => { if (isEditable) { setEditingField({ field: 'title' }); setTempTitle(title); } }}
>
{title}
</h2>
)}
{/* Three Columns */}
<div className="flex gap-6 flex-1">
{columns.map((col, i) => (
<div
key={i}
className="flex-1 flex flex-col p-6"
style={{
backgroundColor: styles.colors.cardBg,
border: `${styles.border.width} solid ${styles.colors.border}`,
boxShadow: styles.border.shadow,
borderRadius: styles.border.radius,
}}
>
{/* Column number */}
<span
className="mb-4"
style={{
fontFamily: styles.fonts.heading,
fontSize: '14px',
fontWeight: 'bold',
color: styles.colors.accent,
backgroundColor: styles.colors.text,
display: 'inline-block',
padding: '4px 12px',
alignSelf: 'flex-start',
borderRadius: styles.border.radius,
}}
>
{String(i + 1).padStart(2, '0')}
</span>
{/* Heading */}
{editingField?.field === 'columns' && editingField?.index === i && editingField?.sub === 'heading' ? (
<input
type="text"
value={tempColumns[i]?.heading || ''}
onChange={(e) => {
const next = [...tempColumns];
next[i] = { ...next[i], heading: e.target.value };
setTempColumns(next);
}}
onBlur={() => handleBlur('columns', i)}
onKeyDown={(e) => handleKeyDown(e, 'columns', i)}
className="bg-transparent outline-none mb-3"
style={{ fontFamily: styles.fonts.heading, fontSize: '22px', fontWeight: 'bold', color: styles.colors.text }}
autoFocus
/>
) : (
<h3
className={`mb-3 ${isEditable ? 'cursor-pointer hover:opacity-70' : ''}`}
style={{
fontFamily: styles.fonts.heading,
fontSize: '22px',
fontWeight: 'bold',
color: styles.colors.text,
textTransform: 'uppercase',
}}
onClick={() => { if (isEditable) { setEditingField({ field: 'columns', index: i, sub: 'heading' }); setTempColumns(columns); } }}
>
{col.heading}
</h3>
)}
{/* Text */}
{editingField?.field === 'columns' && editingField?.index === i && editingField?.sub === 'text' ? (
<textarea
value={tempColumns[i]?.text || ''}
onChange={(e) => {
const next = [...tempColumns];
next[i] = { ...next[i], text: e.target.value };
setTempColumns(next);
}}
onBlur={() => handleBlur('columns', i)}
onKeyDown={(e) => handleKeyDown(e, 'columns', i)}
className="bg-transparent outline-none flex-1 resize-none"
style={{ fontFamily: styles.fonts.body, fontSize: '15px', color: styles.colors.text, opacity: 0.8 }}
autoFocus
/>
) : (
<p
className={`flex-1 ${isEditable ? 'cursor-pointer hover:opacity-70' : ''}`}
style={{ fontFamily: styles.fonts.body, fontSize: '15px', color: styles.colors.text, opacity: 0.8, lineHeight: 1.6 }}
onClick={() => { if (isEditable) { setEditingField({ field: 'columns', index: i, sub: 'text' }); setTempColumns(columns); } }}
>
{col.text}
</p>
)}
</div>
))}
</div>
</div>
</div>
);
}