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
112 lines
3.1 KiB
JavaScript
112 lines
3.1 KiB
JavaScript
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>
|
|
);
|
|
}
|