Flatted and issues fixed with Claude Desktop.
This commit is contained in:
36
src/components/panels/LayersPanel.jsx
Normal file
36
src/components/panels/LayersPanel.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
export const LayersPanel = memo(function LayersPanel({ elements, selectedId, onSelect, onDelete }) {
|
||||
const getIcon = (el) => el.type === 'image' ? (el.bgRemoved ? '🖼️' : '📷') : el.type === 'text' ? '📝' : '🎨';
|
||||
const getName = (el) => el.type === 'image' ? (el.bgRemoved ? 'Image (BG ✓)' : 'Image') : el.type === 'text' ? (el.text?.substring(0, 20) || 'Text') : 'Sticker';
|
||||
|
||||
if (elements.length === 0) {
|
||||
return <div style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)', fontSize: '12px' }}>No elements yet. Add images, text, or stickers to your design.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 style={{ margin: '0 0 0.75rem 0', fontSize: '12px', fontWeight: '600', color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Layers ({elements.length})</h3>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||
{elements.map((element) => (
|
||||
<div key={element.id} onClick={() => onSelect(element.id)}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.5rem 0.75rem',
|
||||
background: selectedId === element.id ? 'var(--accent-bg)' : 'transparent',
|
||||
border: `1px solid ${selectedId === element.id ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 'var(--radius-sm)', cursor: 'pointer',
|
||||
}}>
|
||||
<span style={{ fontSize: '14px' }}>{getIcon(element)}</span>
|
||||
<span style={{ flex: 1, fontSize: '12px', color: selectedId === element.id ? 'var(--accent)' : 'var(--text-primary)', fontWeight: selectedId === element.id ? '600' : '400', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{getName(element)}
|
||||
</span>
|
||||
<button onClick={(e) => { e.stopPropagation(); onDelete(element.id); }}
|
||||
style={{ width: '24px', height: '24px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px', color: 'var(--text-muted)' }}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
113
src/components/panels/PropertiesPanel.jsx
Normal file
113
src/components/panels/PropertiesPanel.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { memo } from 'react';
|
||||
import { BackgroundRemovalButton } from '../sidebar/BackgroundRemovalButton';
|
||||
|
||||
export const PropertiesPanel = memo(function PropertiesPanel({ element, onUpdate, onDelete, onEditPhoto }) {
|
||||
if (!element) {
|
||||
return (
|
||||
<div className="properties-panel">
|
||||
<div style={{ padding: '1rem', borderBottom: '1px solid var(--border)' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '14px', fontWeight: '600', color: 'var(--text-primary)' }}>Properties</h3>
|
||||
</div>
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', color: 'var(--text-muted)', fontSize: '12px', textAlign: 'center' }}>
|
||||
Select an element to edit its properties
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handlePositionChange = (axis, value) => onUpdate({ [axis]: parseFloat(value) || 0 });
|
||||
const handleSizeChange = (axis, value) => onUpdate({ [axis]: Math.max(20, parseFloat(value) || 20) });
|
||||
const handleRotationChange = (value) => onUpdate({ rotation: Math.max(-180, Math.min(180, parseFloat(value) || 0)) });
|
||||
|
||||
const inputStyle = { width: '100%', padding: '0.5rem', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', fontSize: '13px' };
|
||||
const labelStyle = { display: 'block', fontSize: '11px', fontWeight: '600', color: 'var(--text-secondary)', marginBottom: '0.5rem', textTransform: 'uppercase' };
|
||||
|
||||
return (
|
||||
<div className="properties-panel">
|
||||
<div style={{ padding: '1rem', borderBottom: '1px solid var(--border)' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '14px', fontWeight: '600', color: 'var(--text-primary)' }}>Properties</h3>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '1rem' }}>
|
||||
{/* Element type badge */}
|
||||
<div style={{ display: 'inline-block', padding: '4px 8px', background: 'var(--accent-bg)', borderRadius: 'var(--radius-sm)', fontSize: '11px', fontWeight: '600', color: 'var(--accent)', textTransform: 'uppercase', marginBottom: '1rem' }}>
|
||||
{element.type}
|
||||
</div>
|
||||
|
||||
{/* Position */}
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<label style={labelStyle}>Position</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ fontSize: '10px', color: 'var(--text-muted)' }}>X</label>
|
||||
<input type="number" value={Math.round(element.x)} onChange={(e) => handlePositionChange('x', e.target.value)} style={inputStyle} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ fontSize: '10px', color: 'var(--text-muted)' }}>Y</label>
|
||||
<input type="number" value={Math.round(element.y)} onChange={(e) => handlePositionChange('y', e.target.value)} style={inputStyle} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Size (for images and stickers) */}
|
||||
{(element.type === 'image' || element.type === 'sticker') && (
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<label style={labelStyle}>Size</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ fontSize: '10px', color: 'var(--text-muted)' }}>W</label>
|
||||
<input type="number" value={Math.round(element.width)} onChange={(e) => handleSizeChange('width', e.target.value)} style={inputStyle} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ fontSize: '10px', color: 'var(--text-muted)' }}>H</label>
|
||||
<input type="number" value={Math.round(element.height)} onChange={(e) => handleSizeChange('height', e.target.value)} style={inputStyle} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit Photo button */}
|
||||
{element.type === 'image' && onEditPhoto && (
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<button onClick={() => onEditPhoto(element)} style={{ width: '100%', padding: '0.75rem', border: '1px solid var(--accent)', borderRadius: 'var(--radius-md)', background: 'var(--accent-bg)', color: 'var(--accent)', fontSize: '13px', fontWeight: '600', cursor: 'pointer' }}>
|
||||
✏️ Edit Photo
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Text-specific controls */}
|
||||
{element.type === 'text' && (
|
||||
<>
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<label style={labelStyle}>Font Size: {Math.round(element.fontSize)}px</label>
|
||||
<input type="range" min="12" max="120" value={element.fontSize} onChange={(e) => onUpdate({ fontSize: parseInt(e.target.value, 10) })} style={{ width: '100%' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<label style={labelStyle}>Color</label>
|
||||
<input type="color" value={element.fill} onChange={(e) => onUpdate({ fill: e.target.value })} style={{ width: '100%', height: '36px', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', cursor: 'pointer', padding: '2px' }} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Rotation */}
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<label style={labelStyle}>Rotation: {Math.round(element.rotation)}°</label>
|
||||
<input type="range" min="-180" max="180" value={element.rotation} onChange={(e) => handleRotationChange(e.target.value)} style={{ width: '100%' }} />
|
||||
</div>
|
||||
|
||||
{/* Background Removal (for images) */}
|
||||
{element.type === 'image' && (
|
||||
<BackgroundRemovalButton
|
||||
selectedElement={element}
|
||||
onUpdate={(_id, attrs) => onUpdate(attrs)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete */}
|
||||
<button onClick={() => onDelete(element.id)} style={{ width: '100%', padding: '0.75rem', border: 'none', borderRadius: 'var(--radius-md)', background: 'var(--error)', color: '#fff', fontSize: '13px', fontWeight: '600', cursor: 'pointer', marginTop: '1rem' }}>
|
||||
Delete Element
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
2
src/components/panels/index.js
Normal file
2
src/components/panels/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { LayersPanel } from './LayersPanel';
|
||||
export { PropertiesPanel } from './PropertiesPanel';
|
||||
Reference in New Issue
Block a user