Spaces:
Running
Running
| /* | |
| * 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> | |
| ); | |
| } | |