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
200 lines
5.2 KiB
JavaScript
200 lines
5.2 KiB
JavaScript
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>
|
|
);
|
|
}
|