Phase 3: Sidebar & Properties Panel
- Three-column layout (sidebar/canvas/properties) - Sidebar with tabs: Upload, Stickers, Text - Upload tab with drag-and-drop and click-to-upload - Stickers tab with 6 categories (40+ emojis) - Text tab with font selector, size slider, color picker - Properties panel with position, size, rotation controls - Delete button for selected element - Responsive layout for mobile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
109
client/src/components/properties/PropertiesPanel.jsx
Normal file
109
client/src/components/properties/PropertiesPanel.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
export function PropertiesPanel({ selectedElement, onUpdate, onDelete }) {
|
||||
if (!selectedElement) {
|
||||
return (
|
||||
<div className="properties-panel">
|
||||
<h3>Properties</h3>
|
||||
<div className="no-selection">
|
||||
<p>Select an element to edit its properties</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handlePositionChange = (axis, value) => {
|
||||
onUpdate(selectedElement.id, { [axis]: Number(value) });
|
||||
};
|
||||
|
||||
const handleSizeChange = (dimension, value) => {
|
||||
onUpdate(selectedElement.id, { [dimension]: Number(value) });
|
||||
};
|
||||
|
||||
const handleRotationChange = (value) => {
|
||||
onUpdate(selectedElement.id, { rotation: Number(value) });
|
||||
};
|
||||
|
||||
const getIcon = () => {
|
||||
if (selectedElement.type === 'image') return '🖼️';
|
||||
if (selectedElement.type === 'text') return 'T';
|
||||
if (selectedElement.type === 'sticker') return '😊';
|
||||
return '📦';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="properties-panel">
|
||||
<h3>Properties</h3>
|
||||
<div className="element-header">
|
||||
<span className="element-icon">{getIcon()}</span>
|
||||
<span className="element-name">
|
||||
{selectedElement.type === 'text'
|
||||
? selectedElement.text?.substring(0, 20) || 'Text'
|
||||
: `${selectedElement.type}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="property-group">
|
||||
<label>Position</label>
|
||||
<div className="property-row">
|
||||
<div className="property-input">
|
||||
<span className="property-label">X</span>
|
||||
<input
|
||||
type="number"
|
||||
value={Math.round(selectedElement.x)}
|
||||
onChange={(e) => handlePositionChange('x', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="property-input">
|
||||
<span className="property-label">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
value={Math.round(selectedElement.y)}
|
||||
onChange={(e) => handlePositionChange('y', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="property-group">
|
||||
<label>Size</label>
|
||||
<div className="property-row">
|
||||
<div className="property-input">
|
||||
<span className="property-label">W</span>
|
||||
<input
|
||||
type="number"
|
||||
value={Math.round(selectedElement.width || selectedElement.fontSize || 0)}
|
||||
onChange={(e) =>
|
||||
handleSizeChange(selectedElement.text ? 'fontSize' : 'width', e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{selectedElement.type !== 'text' && (
|
||||
<div className="property-input">
|
||||
<span className="property-label">H</span>
|
||||
<input
|
||||
type="number"
|
||||
value={Math.round(selectedElement.height || 0)}
|
||||
onChange={(e) => handleSizeChange('height', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="property-group">
|
||||
<label>Rotation: {Math.round(selectedElement.rotation || 0)}°</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="360"
|
||||
value={selectedElement.rotation || 0}
|
||||
onChange={(e) => handleRotationChange(e.target.value)}
|
||||
className="rotation-slider"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="delete-btn" onClick={() => onDelete(selectedElement.id)}>
|
||||
Delete Element
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user