Fix module issues, fix styling, add conditions to when the background removal and edit controls are shown

This commit is contained in:
khalid@traclabs.com
2026-04-23 08:48:11 -05:00
parent 4d19363d58
commit 628a6765f4
32 changed files with 11663 additions and 287 deletions

View File

@@ -1,31 +1,27 @@
import { memo } from 'react';
import '../../styles/LayersPanel.css';
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 className="layers-empty">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' }}>
<h3 className="layers-title">Layers ({elements.length})</h3>
<div className="layers-list">
{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' }}>
className={`layers-item${selectedId === element.id ? ' selected' : ''}`}>
<span className="layers-item-icon">{getIcon(element)}</span>
<span className={`layers-item-name${selectedId === element.id ? ' selected' : ''}`}>
{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)' }}>
className="layers-item-delete">
×
</button>
</div>

View File

@@ -1,14 +1,15 @@
import { memo } from 'react';
import { BackgroundRemovalButton } from '../sidebar/BackgroundRemovalButton';
import '../../styles/PropertiesPanel.css';
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 className="properties-panel__header">
<h3 className="properties-panel__title">Properties</h3>
</div>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', color: 'var(--text-muted)', fontSize: '12px', textAlign: 'center' }}>
<div className="properties-panel__empty">
Select an element to edit its properties
</div>
</div>
@@ -19,57 +20,53 @@ export const PropertiesPanel = memo(function PropertiesPanel({ element, onUpdate
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 className="properties-panel__header">
<h3 className="properties-panel__title">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' }}>
<div className="properties-panel__body">
<div className="properties-panel__type-badge">
{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 className="properties-panel__section">
<label className="properties-panel__label">Position</label>
<div className="properties-panel__row">
<div className="properties-panel__field">
<label className="properties-panel__axis-label">X</label>
<input type="number" value={Math.round(element.x)} onChange={(e) => handlePositionChange('x', e.target.value)} className="properties-panel__input" />
</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 className="properties-panel__field">
<label className="properties-panel__axis-label">Y</label>
<input type="number" value={Math.round(element.y)} onChange={(e) => handlePositionChange('y', e.target.value)} className="properties-panel__input" />
</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 className="properties-panel__section">
<label className="properties-panel__label">Size</label>
<div className="properties-panel__row">
<div className="properties-panel__field">
<label className="properties-panel__axis-label">W</label>
<input type="number" value={Math.round(element.width)} onChange={(e) => handleSizeChange('width', e.target.value)} className="properties-panel__input" />
</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 className="properties-panel__field">
<label className="properties-panel__axis-label">H</label>
<input type="number" value={Math.round(element.height)} onChange={(e) => handleSizeChange('height', e.target.value)} className="properties-panel__input" />
</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 (user uploads only, not stickers) */}
{element.type === 'image' && !element.emoji && onEditPhoto && (
<div className="properties-panel__section">
<button onClick={() => onEditPhoto(element)} className="properties-panel__edit-btn">
Edit Photo
</button>
</div>
@@ -78,25 +75,25 @@ export const PropertiesPanel = memo(function PropertiesPanel({ element, onUpdate
{/* 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 className="properties-panel__section">
<label className="properties-panel__label">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) })} className="properties-panel__range" />
</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 className="properties-panel__section">
<label className="properties-panel__label">Color</label>
<input type="color" value={element.fill} onChange={(e) => onUpdate({ fill: e.target.value })} className="properties-panel__color-input" />
</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 className="properties-panel__section">
<label className="properties-panel__label">Rotation: {Math.round(element.rotation)}°</label>
<input type="range" min="-180" max="180" value={element.rotation} onChange={(e) => handleRotationChange(e.target.value)} className="properties-panel__range" />
</div>
{/* Background Removal (for images) */}
{element.type === 'image' && (
{/* Background Removal (user uploads only, not stickers) */}
{element.type === 'image' && !element.emoji && (
<BackgroundRemovalButton
selectedElement={element}
onUpdate={(_id, attrs) => onUpdate(attrs)}
@@ -104,7 +101,7 @@ export const PropertiesPanel = memo(function PropertiesPanel({ element, onUpdate
)}
{/* 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' }}>
<button onClick={() => onDelete(element.id)} className="properties-panel__delete-btn">
Delete Element
</button>
</div>