Phase 6: Template System

- Added 8 pre-designed templates across 8 categories
- Templates: Team Sport, Band Merch, Minimal Quote, Funny Cat,
  Gradient Vibes, Vintage Badge, Nature Lover, Tech Geek
- Templates tab with category filter pills
- Template preview cards with 2-column grid
- One-click template application to canvas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Khalid A
2026-04-21 01:24:03 -05:00
parent 7bf9ce3a9c
commit 72495fec3e
6 changed files with 445 additions and 2 deletions

View File

@@ -66,11 +66,18 @@ function App() {
}
};
const handleApplyTemplate = (template) => {
// Clear existing elements and apply template
template.elements.forEach((el, index) => {
setTimeout(() => addElement({ ...el }), index * 50);
});
};
return (
<div className="app-layout">
{/* Left Sidebar */}
<aside className="sidebar-container">
<Sidebar onElementAdd={handleAddElement} onUpload={handleUpload} />
<Sidebar onElementAdd={handleAddElement} onUpload={handleUpload} onApplyTemplate={handleApplyTemplate} />
</aside>
{/* Center Canvas */}

View File

@@ -2,14 +2,16 @@ import { useState } from 'react';
import { UploadTab } from './UploadTab';
import { StickersTab } from './StickersTab';
import { TextTab } from './TextTab';
import { TemplatesTab } from './TemplatesTab';
const TABS = [
{ id: 'upload', label: 'Upload', icon: '📁' },
{ id: 'stickers', label: 'Stickers', icon: '😊' },
{ id: 'text', label: 'Text', icon: 'T' },
{ id: 'templates', label: 'Templates', icon: '📋' },
];
export function Sidebar({ onElementAdd, onUpload }) {
export function Sidebar({ onElementAdd, onUpload, onApplyTemplate }) {
const [activeTab, setActiveTab] = useState('upload');
const renderTabContent = () => {
@@ -20,6 +22,8 @@ export function Sidebar({ onElementAdd, onUpload }) {
return <StickersTab onAddSticker={onElementAdd} />;
case 'text':
return <TextTab onAddText={onElementAdd} />;
case 'templates':
return <TemplatesTab onApplyTemplate={onApplyTemplate} />;
default:
return null;
}

View File

@@ -0,0 +1,56 @@
import { useState } from 'react';
import { TEMPLATES, TEMPLATE_CATEGORIES } from '../../constants/templates';
export function TemplatesTab({ onApplyTemplate }) {
const [selectedCategory, setSelectedCategory] = useState('All');
const filteredTemplates =
selectedCategory === 'All'
? TEMPLATES
: TEMPLATES.filter((t) => t.category === selectedCategory);
return (
<div className="templates-tab">
<h3>Templates</h3>
<div className="category-pills">
{TEMPLATE_CATEGORIES.map((cat) => (
<button
key={cat}
className={`category-pill ${selectedCategory === cat ? 'active' : ''}`}
onClick={() => setSelectedCategory(cat)}
>
{cat}
</button>
))}
</div>
<div className="templates-grid">
{filteredTemplates.map((template) => (
<button
key={template.id}
className="template-card"
onClick={() => onApplyTemplate(template)}
>
<div className="template-preview">
{template.elements.slice(0, 3).map((el, i) => (
<span
key={i}
className="template-preview-element"
style={{
fontSize: el.type === 'text' && el.text.length === 1 ? '16px' : '8px',
color: el.fill,
}}
>
{el.text}
</span>
))}
</div>
<div className="template-info">
<span className="template-name">{template.name}</span>
<span className="template-category">{template.category}</span>
</div>
</button>
))}
</div>
</div>
);
}

View File

@@ -2,3 +2,4 @@ export { Sidebar } from './Sidebar';
export { UploadTab } from './UploadTab';
export { StickersTab } from './StickersTab';
export { TextTab } from './TextTab';
export { TemplatesTab } from './TemplatesTab';

View File

@@ -0,0 +1,309 @@
// Pre-designed templates for t-shirt customization
export const TEMPLATES = [
{
id: 'team-sport',
name: 'Team Sport',
category: 'Sports',
description: 'Classic team jersey with number and text',
elements: [
{
type: 'text',
text: 'TEAM NAME',
x: 75,
y: 80,
fontSize: 28,
fontFamily: 'Impact',
fill: '#ffffff',
rotation: 0,
},
{
type: 'text',
text: '23',
x: 150,
y: 150,
fontSize: 72,
fontFamily: 'Impact',
fill: '#ffffff',
rotation: 0,
},
],
},
{
id: 'band-merch',
name: 'Band Merch',
category: 'Music',
description: 'Classic band t-shirt design',
elements: [
{
type: 'text',
text: 'BAND NAME',
x: 150,
y: 70,
fontSize: 32,
fontFamily: 'Georgia',
fill: '#fbbf24',
rotation: 0,
},
{
type: 'text',
text: 'WORLD TOUR 2026',
x: 150,
y: 110,
fontSize: 16,
fontFamily: 'Arial',
fill: '#ffffff',
rotation: 0,
},
{
type: 'text',
text: '🎸',
x: 150,
y: 180,
fontSize: 64,
fontFamily: 'Arial',
fill: '#ffffff',
rotation: 0,
},
],
},
{
id: 'minimal-quote',
name: 'Minimal Quote',
category: 'Quotes',
description: 'Simple centered quote design',
elements: [
{
type: 'text',
text: '"Be the change"',
x: 150,
y: 130,
fontSize: 24,
fontFamily: 'Georgia',
fill: '#1e293b',
rotation: 0,
},
{
type: 'text',
text: 'you wish to see',
x: 150,
y: 160,
fontSize: 18,
fontFamily: 'Arial',
fill: '#64748b',
rotation: 0,
},
],
},
{
id: 'funny-cat',
name: 'Funny Cat',
category: 'Animals',
description: 'Cute cat with funny text',
elements: [
{
type: 'text',
text: '😼',
x: 150,
y: 100,
fontSize: 80,
fontFamily: 'Arial',
fill: '#000000',
rotation: 0,
},
{
type: 'text',
text: 'I do what I want',
x: 150,
y: 200,
fontSize: 20,
fontFamily: 'Comic Sans MS',
fill: '#475569',
rotation: 0,
},
],
},
{
id: 'gradient-vibes',
name: 'Gradient Vibes',
category: 'Abstract',
description: 'Modern gradient text design',
elements: [
{
type: 'text',
text: 'GOOD',
x: 150,
y: 110,
fontSize: 48,
fontFamily: 'Impact',
fill: '#ec4899',
rotation: -5,
},
{
type: 'text',
text: 'VIBES',
x: 150,
y: 160,
fontSize: 48,
fontFamily: 'Impact',
fill: '#8b5cf6',
rotation: 5,
},
{
type: 'text',
text: '✨',
x: 80,
y: 90,
fontSize: 32,
fontFamily: 'Arial',
fill: '#fbbf24',
rotation: 0,
},
{
type: 'text',
text: '🌙',
x: 220,
y: 190,
fontSize: 32,
fontFamily: 'Arial',
fill: '#38bdf8',
rotation: 0,
},
],
},
{
id: 'vintage-badge',
name: 'Vintage Badge',
category: 'Vintage',
description: 'Retro badge style design',
elements: [
{
type: 'text',
text: 'EST.',
x: 150,
y: 80,
fontSize: 18,
fontFamily: 'Times New Roman',
fill: '#78716c',
rotation: 0,
},
{
type: 'text',
text: '2026',
x: 150,
y: 105,
fontSize: 36,
fontFamily: 'Times New Roman',
fill: '#78716c',
rotation: 0,
},
{
type: 'text',
text: 'AUTHENTIC',
x: 150,
y: 150,
fontSize: 24,
fontFamily: 'Times New Roman',
fill: '#78716c',
rotation: 0,
},
{
type: 'text',
text: 'QUALITY',
x: 150,
y: 180,
fontSize: 24,
fontFamily: 'Times New Roman',
fill: '#78716c',
rotation: 0,
},
],
},
{
id: 'nature-lover',
name: 'Nature Lover',
category: 'Nature',
description: 'Mountain and nature themed',
elements: [
{
type: 'text',
text: '🏔️',
x: 150,
y: 90,
fontSize: 56,
fontFamily: 'Arial',
fill: '#000000',
rotation: 0,
},
{
type: 'text',
text: 'ADVENTURE',
x: 150,
y: 160,
fontSize: 28,
fontFamily: 'Impact',
fill: '#059669',
rotation: 0,
},
{
type: 'text',
text: 'AWAITS',
x: 150,
y: 190,
fontSize: 20,
fontFamily: 'Arial',
fill: '#6b7280',
rotation: 0,
},
],
},
{
id: 'tech-geek',
name: 'Tech Geek',
category: 'Tech',
description: 'Programming themed design',
elements: [
{
type: 'text',
text: '</>',
x: 150,
y: 100,
fontSize: 64,
fontFamily: 'Courier New',
fill: '#3b82f6',
rotation: 0,
},
{
type: 'text',
text: 'Hello, World!',
x: 150,
y: 170,
fontSize: 20,
fontFamily: 'Courier New',
fill: '#1e293b',
rotation: 0,
},
{
type: 'text',
text: '// Code is life',
x: 150,
y: 195,
fontSize: 14,
fontFamily: 'Courier New',
fill: '#94a3b8',
rotation: 0,
},
],
},
];
export const TEMPLATE_CATEGORIES = [
'All',
'Sports',
'Music',
'Quotes',
'Animals',
'Abstract',
'Vintage',
'Nature',
'Tech',
];

View File

@@ -389,6 +389,72 @@ input, textarea, select {
text-align: center;
}
/* Templates Tab */
.templates-tab h3 {
font-size: 0.875rem;
margin: 0 0 1rem 0;
}
.templates-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
max-height: 400px;
overflow-y: auto;
}
.template-card {
display: flex;
flex-direction: column;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg-primary);
cursor: pointer;
transition: all 0.2s;
text-align: left;
}
.template-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.template-preview {
height: 60px;
background: var(--bg-tertiary);
border-radius: var(--radius-sm);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
flex-wrap: wrap;
padding: 0.25rem;
}
.template-preview-element {
font-weight: 500;
}
.template-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.template-name {
font-size: 0.75rem;
font-weight: 500;
color: var(--text-primary);
}
.template-category {
font-size: 0.625rem;
color: var(--text-muted);
}
/* Properties Panel */
.properties-panel {
padding: 1rem;