Powerpoint_AI / components /slides /ReferenceLayout.tsx
Reubencf's picture
Readme updated
45b3fab
/*
* ReferenceLayout.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 ReferenceLayoutProps {
title: string;
items: Array<{ text: string }>;
styles: TemplateStyles;
slideId?: string;
isEditable?: boolean;
onFieldUpdate?: (slideId: string, field: string, value: string, index?: number) => void;
}
export default function ReferenceLayout({
title,
items,
styles,
slideId,
isEditable = false,
onFieldUpdate,
}: ReferenceLayoutProps) {
const [editingField, setEditingField] = useState<{ field: string; index?: number } | null>(null);
const [tempTitle, setTempTitle] = useState(title);
const [tempItems, setTempItems] = useState(items.map((i) => i.text));
const handleBlur = (field: string, index?: number) => {
if (!slideId || !onFieldUpdate) return;
if (field === 'title' && tempTitle !== title) onFieldUpdate(slideId, 'title', tempTitle);
if (field === 'items' && index !== undefined && tempItems[index] !== items[index]?.text)
onFieldUpdate(slideId, 'items', tempItems[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);
setTempItems(items.map((i) => i.text));
}
};
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: '40px',
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: '40px',
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>
)}
{/* Reference Items */}
<div className="flex flex-col gap-3 flex-1">
{items.map((item, i) => (
<div
key={i}
className="flex items-start gap-4 py-3"
style={{ borderBottom: `1px solid ${styles.colors.border}20` }}
>
<span
className="shrink-0 mt-1"
style={{
fontFamily: styles.fonts.heading,
fontSize: '13px',
fontWeight: 'bold',
color: styles.colors.accent,
backgroundColor: styles.colors.text,
padding: '2px 8px',
borderRadius: styles.border.radius,
}}
>
[{i + 1}]
</span>
{editingField?.field === 'items' && editingField?.index === i ? (
<input
type="text"
value={tempItems[i] || ''}
onChange={(e) => {
const next = [...tempItems];
next[i] = e.target.value;
setTempItems(next);
}}
onBlur={() => handleBlur('items', i)}
onKeyDown={(e) => handleKeyDown(e, 'items', i)}
className="flex-1 bg-transparent outline-none"
style={{ fontFamily: styles.fonts.body, fontSize: '16px', color: styles.colors.text }}
autoFocus
/>
) : (
<span
className={`flex-1 ${isEditable ? 'cursor-pointer hover:opacity-70' : ''}`}
style={{ fontFamily: styles.fonts.body, fontSize: '16px', color: styles.colors.text, opacity: 0.85, lineHeight: 1.6 }}
onClick={() => { if (isEditable) { setEditingField({ field: 'items', index: i }); setTempItems(items.map((x) => x.text)); } }}
>
{item.text}
</span>
)}
</div>
))}
</div>
</div>
</div>
);
}