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:
111
client/src/components/sidebar/StickersTab.jsx
Normal file
111
client/src/components/sidebar/StickersTab.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { useState } from 'react';
|
||||
import { STICKERS, STICKER_CATEGORIES } from '../../constants/stickers';
|
||||
|
||||
export function StickersTab({ onAddSticker }) {
|
||||
const [activeCategory, setActiveCategory] = useState('all');
|
||||
|
||||
const categories = ['all', ...STICKER_CATEGORIES];
|
||||
|
||||
const filteredStickers = activeCategory === 'all'
|
||||
? STICKERS
|
||||
: STICKERS.filter(s => s.category === activeCategory);
|
||||
|
||||
const handleAddSticker = (emoji) => {
|
||||
// Create a canvas element with the emoji
|
||||
const canvas = document.createElement('canvas');
|
||||
const size = 100;
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.font = `${size * 0.8}px Arial`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(emoji, size / 2, size / 2);
|
||||
|
||||
const dataUrl = canvas.toDataURL('image/png');
|
||||
|
||||
onAddSticker({
|
||||
type: 'sticker',
|
||||
x: 125,
|
||||
y: 125,
|
||||
width: 80,
|
||||
height: 80,
|
||||
rotation: 0,
|
||||
src: dataUrl,
|
||||
emoji,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 style={{ margin: '0 0 1rem 0', fontSize: '14px', color: 'var(--text-primary)' }}>
|
||||
Stickers
|
||||
</h3>
|
||||
|
||||
{/* Category pills */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '6px',
|
||||
marginBottom: '1rem',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActiveCategory(cat)}
|
||||
style={{
|
||||
padding: '6px 12px',
|
||||
border: `1px solid ${activeCategory === cat ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
background: activeCategory === cat ? 'var(--accent)' : 'var(--bg-primary)',
|
||||
color: activeCategory === cat ? '#fff' : 'var(--text-secondary)',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
textTransform: 'capitalize',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Sticker grid */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(5, 1fr)',
|
||||
gap: '8px',
|
||||
}}>
|
||||
{filteredStickers.map((sticker, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleAddSticker(sticker.emoji)}
|
||||
style={{
|
||||
aspectRatio: '1',
|
||||
border: 'none',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
background: 'var(--bg-primary)',
|
||||
fontSize: '28px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.background = 'var(--accent-bg)';
|
||||
e.target.style.transform = 'scale(1.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.target.style.background = 'var(--bg-primary)';
|
||||
e.target.style.transform = 'scale(1)';
|
||||
}}
|
||||
>
|
||||
{sticker.emoji}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user