137 lines
5.9 KiB
TypeScript
137 lines
5.9 KiB
TypeScript
import React from 'react';
|
|
import { Shape, COLORS, STROKE_WIDTHS, FONT_SIZES } from '../types';
|
|
|
|
interface PropertiesPanelProps {
|
|
selectedShape: Shape | null;
|
|
updateShape: (id: string, attrs: Partial<Shape>) => void;
|
|
}
|
|
|
|
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
selectedShape,
|
|
updateShape,
|
|
}) => {
|
|
if (!selectedShape) {
|
|
return (
|
|
<div className="w-72 bg-amber-100 border-l-4 border-amber-900 p-6 flex flex-col items-center justify-center text-center text-amber-800/60 font-comic shrink-0">
|
|
<span className="text-4xl mb-4 opacity-50">👆</span>
|
|
<p className="font-bold text-lg">Select something!</p>
|
|
<p className="text-sm">Click an item to tweak it.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="w-72 bg-amber-100 border-l-4 border-amber-900 p-6 flex flex-col gap-8 overflow-y-auto shrink-0 font-comic">
|
|
<div className="border-b-2 border-amber-900/20 pb-4">
|
|
<h2 className="font-impact text-2xl text-amber-900 uppercase tracking-wide">
|
|
Edit {selectedShape.type}
|
|
</h2>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-sm font-bold text-amber-900 uppercase tracking-wider mb-3 flex items-center gap-2">
|
|
Color <span className="text-xs bg-amber-200 px-1 border border-amber-900 rounded-sm text-amber-900 font-normal normal-case">Pick one</span>
|
|
</h3>
|
|
<div className="grid grid-cols-6 gap-3">
|
|
{COLORS.map((color) => (
|
|
<button
|
|
key={color}
|
|
onClick={() => updateShape(selectedShape.id, { stroke: color, fill: selectedShape.type === 'text' ? color : undefined })}
|
|
className={`w-8 h-8 rounded-full border-2 focus:outline-none transition-transform hover:scale-110 ${
|
|
selectedShape.stroke === color
|
|
? 'ring-2 ring-offset-2 ring-amber-900 border-black scale-110'
|
|
: 'border-amber-900/30 hover:border-amber-900'
|
|
}`}
|
|
style={{ backgroundColor: color }}
|
|
title={color}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{(selectedShape.type === 'rect' || selectedShape.type === 'arrow') && (
|
|
<div>
|
|
<h3 className="text-sm font-bold text-amber-900 uppercase tracking-wider mb-3">
|
|
Thickness
|
|
</h3>
|
|
<div className="flex gap-2 bg-amber-50 p-2 border-2 border-amber-900 shadow-[2px_2px_0px_0px_rgba(69,26,3,0.1)]">
|
|
{STROKE_WIDTHS.map((width) => (
|
|
<button
|
|
key={width}
|
|
onClick={() => updateShape(selectedShape.id, { strokeWidth: width })}
|
|
className={`flex-1 py-1 text-sm font-bold border-2 transition-all ${
|
|
selectedShape.strokeWidth === width
|
|
? 'bg-amber-900 text-white border-black shadow-sm'
|
|
: 'bg-white text-amber-900 border-transparent hover:border-amber-200'
|
|
}`}
|
|
>
|
|
{width}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{selectedShape.type === 'text' && (
|
|
<>
|
|
<div>
|
|
<h3 className="text-sm font-bold text-amber-900 uppercase tracking-wider mb-3">
|
|
Size
|
|
</h3>
|
|
<select
|
|
value={(selectedShape as any).fontSize}
|
|
onChange={(e) =>
|
|
updateShape(selectedShape.id, { fontSize: Number(e.target.value) })
|
|
}
|
|
className="w-full bg-white border-2 border-amber-900 text-amber-900 font-bold p-2 shadow-[4px_4px_0px_0px_rgba(69,26,3,0.1)] focus:outline-none focus:shadow-[2px_2px_0px_0px_rgba(69,26,3,0.1)] focus:translate-x-[2px] focus:translate-y-[2px]"
|
|
>
|
|
{FONT_SIZES.map((size) => (
|
|
<option key={size} value={size}>
|
|
{size}px
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-bold text-amber-900 uppercase tracking-wider mb-3">
|
|
Text
|
|
</h3>
|
|
<textarea
|
|
rows={4}
|
|
value={(selectedShape as any).text}
|
|
onChange={(e) => updateShape(selectedShape.id, { text: e.target.value })}
|
|
className="w-full bg-white border-2 border-amber-900 text-amber-900 p-2 font-comic shadow-[4px_4px_0px_0px_rgba(69,26,3,0.1)] focus:outline-none focus:shadow-[2px_2px_0px_0px_rgba(69,26,3,0.1)] focus:translate-x-[2px] focus:translate-y-[2px]"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{selectedShape.type === 'rect' && (
|
|
<div>
|
|
<h3 className="text-sm font-bold text-amber-900 uppercase tracking-wider mb-3">
|
|
Fill
|
|
</h3>
|
|
<div className="flex flex-col gap-2">
|
|
<button
|
|
onClick={() => updateShape(selectedShape.id, { fill: 'transparent' })}
|
|
className={`px-3 py-2 text-sm font-bold border-2 text-left flex items-center gap-2 ${selectedShape.fill === 'transparent' ? 'bg-white border-amber-900 text-amber-900 shadow-[2px_2px_0px_0px_rgba(69,26,3,0.2)]' : 'border-transparent text-amber-800/70 hover:bg-amber-200/50'}`}
|
|
>
|
|
<div className="w-4 h-4 border border-amber-900 bg-white"></div>
|
|
Empty (Transparent)
|
|
</button>
|
|
<button
|
|
onClick={() => updateShape(selectedShape.id, { fill: selectedShape.stroke + '40' })}
|
|
className={`px-3 py-2 text-sm font-bold border-2 text-left flex items-center gap-2 ${selectedShape.fill !== 'transparent' ? 'bg-white border-amber-900 text-amber-900 shadow-[2px_2px_0px_0px_rgba(69,26,3,0.2)]' : 'border-transparent text-amber-800/70 hover:bg-amber-200/50'}`}
|
|
>
|
|
<div className="w-4 h-4 border border-amber-900 bg-current opacity-40"></div>
|
|
Filled (Transparent)
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PropertiesPanel;
|