Phase 3: Sidebar & Properties Panel

Implemented full editor UI with three-column layout:
- Sidebar with 4 tabs (Upload, Stickers, Text, Templates)
- UploadTab with drag-and-drop file upload, wires to POST /api/upload
- StickersTab with 96 emoji stickers across 6 categories
- TextTab with font picker (20 Google Fonts), size slider, color picker
- TemplatesTab placeholder for future template system
- LayersPanel showing all elements with select/delete
- PropertiesPanel with position, size, rotation controls

Also added:
- Constants for fonts and stickers
- Enhanced CSS with editor-layout, sidebar, properties-panel classes
- Updated App.jsx to integrate all components
This commit is contained in:
Khalid A
2026-04-21 01:27:59 -05:00
parent e67017b259
commit fd11a36d93
13 changed files with 1375 additions and 36 deletions

View File

@@ -0,0 +1,199 @@
import { useState } from 'react';
import { FONTS } from '../../constants/fonts';
export function TextTab({ onAddText }) {
const [text, setText] = useState('Your text here');
const [fontFamily, setFontFamily] = useState('Roboto');
const [fontSize, setFontSize] = useState(48);
const [fill, setFill] = useState('#0f172a');
const handleAddText = () => {
onAddText({
type: 'text',
x: 150,
y: 150,
text,
fontFamily,
fontSize,
fill,
rotation: 0,
});
};
return (
<div>
<h3 style={{ margin: '0 0 1rem 0', fontSize: '14px', color: 'var(--text-primary)' }}>
Add Text
</h3>
{/* Text input */}
<div style={{ marginBottom: '1rem' }}>
<label style={{
display: 'block',
fontSize: '11px',
fontWeight: '600',
color: 'var(--text-secondary)',
marginBottom: '0.5rem',
textTransform: 'uppercase',
}}>
Text Content
</label>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={3}
style={{
width: '100%',
padding: '0.75rem',
border: `1px solid var(--border)`,
borderRadius: 'var(--radius-md)',
fontSize: '14px',
fontFamily: 'var(--font-body)',
resize: 'vertical',
}}
/>
</div>
{/* Font selector */}
<div style={{ marginBottom: '1rem' }}>
<label style={{
display: 'block',
fontSize: '11px',
fontWeight: '600',
color: 'var(--text-secondary)',
marginBottom: '0.5rem',
textTransform: 'uppercase',
}}>
Font
</label>
<select
value={fontFamily}
onChange={(e) => setFontFamily(e.target.value)}
style={{
width: '100%',
padding: '0.75rem',
border: `1px solid var(--border)`,
borderRadius: 'var(--radius-md)',
fontSize: '13px',
fontFamily,
cursor: 'pointer',
background: 'var(--bg-primary)',
}}
>
{FONTS.map((font) => (
<option key={font.family} value={font.family}>
{font.name}
</option>
))}
</select>
</div>
{/* Font size */}
<div style={{ marginBottom: '1rem' }}>
<label style={{
display: 'block',
fontSize: '11px',
fontWeight: '600',
color: 'var(--text-secondary)',
marginBottom: '0.5rem',
textTransform: 'uppercase',
}}>
Font Size: {fontSize}px
</label>
<input
type="range"
min="12"
max="120"
value={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value, 10))}
style={{ width: '100%' }}
/>
</div>
{/* Color picker */}
<div style={{ marginBottom: '1rem' }}>
<label style={{
display: 'block',
fontSize: '11px',
fontWeight: '600',
color: 'var(--text-secondary)',
marginBottom: '0.5rem',
textTransform: 'uppercase',
}}>
Color
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<input
type="color"
value={fill}
onChange={(e) => setFill(e.target.value)}
style={{
width: '40px',
height: '40px',
border: `1px solid var(--border)`,
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
padding: '2px',
}}
/>
<input
type="text"
value={fill}
onChange={(e) => setFill(e.target.value)}
style={{
flex: 1,
padding: '0.75rem',
border: `1px solid var(--border)`,
borderRadius: 'var(--radius-md)',
fontSize: '13px',
fontFamily: 'var(--font-mono)',
}}
/>
</div>
</div>
{/* Preview */}
<div style={{
padding: '1rem',
background: 'var(--bg-primary)',
borderRadius: 'var(--radius-md)',
marginBottom: '1rem',
textAlign: 'center',
}}>
<div style={{
fontFamily,
fontSize: `${fontSize * 0.5}px`,
color: fill,
wordBreak: 'break-word',
}}>
{text}
</div>
</div>
{/* Add Text button */}
<button
onClick={handleAddText}
style={{
width: '100%',
padding: '0.875rem',
border: 'none',
borderRadius: 'var(--radius-md)',
background: 'var(--accent)',
color: '#fff',
fontSize: '14px',
fontWeight: '600',
cursor: 'pointer',
transition: 'all 0.15s ease',
}}
onMouseEnter={(e) => {
e.target.style.background = 'var(--accent-hover)';
}}
onMouseLeave={(e) => {
e.target.style.background = 'var(--accent)';
}}
>
Add Text to Canvas
</button>
</div>
);
}