|
|
import React from 'react'; |
|
|
import { UndoIcon, RedoIcon, UploadIcon, DownloadIcon, ClearIcon, BrushIcon, EraserIcon, MagicSparkleIcon, SettingsIcon } from './icons'; |
|
|
|
|
|
interface ToolbarProps { |
|
|
penColor: string; |
|
|
onColorChange: (color: string) => void; |
|
|
penSize: number; |
|
|
onSizeChange: (size: number) => void; |
|
|
onUndo: () => void; |
|
|
canUndo: boolean; |
|
|
onRedo: () => void; |
|
|
canRedo: boolean; |
|
|
onLoadImage: (event: React.ChangeEvent<HTMLInputElement>) => void; |
|
|
onExportImage: () => void; |
|
|
onClearCanvas: () => void; |
|
|
isEraserMode: boolean; |
|
|
onToggleEraser: () => void; |
|
|
onMagicUpload: () => void; |
|
|
isMagicUploading: boolean; |
|
|
canShare: boolean; |
|
|
onToggleSettings: () => void; |
|
|
} |
|
|
|
|
|
const Toolbar: React.FC<ToolbarProps> = ({ |
|
|
penColor, |
|
|
onColorChange, |
|
|
penSize, |
|
|
onSizeChange, |
|
|
onUndo, |
|
|
canUndo, |
|
|
onRedo, |
|
|
canRedo, |
|
|
onLoadImage, |
|
|
onExportImage, |
|
|
onClearCanvas, |
|
|
isEraserMode, |
|
|
onToggleEraser, |
|
|
onMagicUpload, |
|
|
isMagicUploading, |
|
|
canShare, |
|
|
onToggleSettings, // New prop |
|
|
}) => { |
|
|
const fileInputRef = React.useRef<HTMLInputElement>(null); |
|
|
|
|
|
const handleUploadClick = () => { |
|
|
fileInputRef.current?.click(); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="bg-slate-200 p-3 shadow-lg flex flex-col items-start justify-center gap-y-2 sticky top-0 z-50 border-b border-slate-300 w-full"> |
|
|
{/* First Row: Action Buttons */} |
|
|
<div className="flex flex-wrap items-center justify-start gap-x-3 gap-y-2"> |
|
|
<button |
|
|
onClick={onUndo} |
|
|
disabled={!canUndo} |
|
|
title="Undo" |
|
|
className="p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors border-gray-300" |
|
|
aria-label="Undo last action" |
|
|
> |
|
|
<UndoIcon className="w-5 h-5 text-gray-700" /> |
|
|
</button> |
|
|
|
|
|
<button |
|
|
onClick={onRedo} |
|
|
disabled={!canRedo} |
|
|
title="Redo" |
|
|
className="p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors border-gray-300" |
|
|
aria-label="Redo last undone action" |
|
|
> |
|
|
<RedoIcon className="w-5 h-5 text-gray-700" /> |
|
|
</button> |
|
|
|
|
|
<input |
|
|
type="file" |
|
|
ref={fileInputRef} |
|
|
onChange={onLoadImage} |
|
|
accept="image/png, image/jpeg" |
|
|
className="hidden" |
|
|
aria-label="Load image from file" |
|
|
/> |
|
|
<button |
|
|
onClick={handleUploadClick} |
|
|
title="Load Image" |
|
|
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300" |
|
|
aria-label="Load image" |
|
|
> |
|
|
<UploadIcon className="w-5 h-5 text-gray-700" /> |
|
|
</button> |
|
|
|
|
|
<button |
|
|
onClick={onExportImage} |
|
|
title="Export Image" |
|
|
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300" |
|
|
aria-label="Export canvas as image" |
|
|
> |
|
|
<DownloadIcon className="w-5 h-5 text-gray-700" /> |
|
|
</button> |
|
|
|
|
|
<button |
|
|
onClick={onMagicUpload} |
|
|
title="Share Canvas & Edit with AI" |
|
|
disabled={isMagicUploading || !canShare} |
|
|
className="p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors border-gray-300" |
|
|
aria-label="Share canvas by uploading and open AI edit options" |
|
|
> |
|
|
<MagicSparkleIcon className="w-5 h-5 text-purple-600" /> |
|
|
</button> |
|
|
|
|
|
<button |
|
|
onClick={onClearCanvas} |
|
|
title="Clear Canvas" |
|
|
className="p-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors tool-button" |
|
|
aria-label="Clear canvas" |
|
|
> |
|
|
<ClearIcon className="w-5 h-5" /> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
{/* Second Row: Drawing Tools & Settings */} |
|
|
<div className="flex flex-wrap items-center justify-start gap-x-3 gap-y-2 mt-2"> |
|
|
<button |
|
|
onClick={onToggleEraser} |
|
|
title={isEraserMode ? "Switch to Brush" : "Switch to Eraser"} |
|
|
className={`p-2 rounded-md transition-colors ${isEraserMode ? 'bg-blue-500 text-white hover:bg-blue-600' : ' hover:bg-gray-100 text-gray-700 border-gray-300'}`} |
|
|
aria-label={isEraserMode ? "Switch to Brush mode" : "Switch to Eraser mode"} |
|
|
aria-pressed={isEraserMode} |
|
|
> |
|
|
{isEraserMode ? <BrushIcon className="w-5 h-5" /> : <EraserIcon className="w-5 h-5" />} |
|
|
</button> |
|
|
|
|
|
<div className="flex items-center gap-2 p-1 rounded-md hover:bg-slate-300 transition-colors"> |
|
|
<label htmlFor="penColor" className="text-sm font-medium text-gray-700 sr-only">Color:</label> |
|
|
<input |
|
|
type="color" |
|
|
id="penColor" |
|
|
title="Pen Color" |
|
|
value={penColor} |
|
|
onChange={(e) => onColorChange(e.target.value)} |
|
|
className={`w-8 h-8 rounded-full cursor-pointer border-2 ${isEraserMode ? 'border-gray-400 opacity-50' : 'border-white shadow-sm'}`} |
|
|
disabled={isEraserMode} |
|
|
aria-label="Select pen color" |
|
|
/> |
|
|
</div> |
|
|
|
|
|
<div className="flex items-center gap-2 p-1 rounded-md hover:bg-slate-300 transition-colors"> |
|
|
<label htmlFor="penSize" className="text-sm font-medium text-gray-700 sr-only">Size:</label> |
|
|
<input |
|
|
type="range" |
|
|
id="penSize" |
|
|
title={`Pen Size: ${penSize}`} |
|
|
min="1" |
|
|
max="50" |
|
|
value={penSize} |
|
|
onChange={(e) => onSizeChange(parseInt(e.target.value, 10))} |
|
|
className="w-24 md:w-32 cursor-pointer accent-blue-600" |
|
|
aria-label={`Pen size ${penSize} pixels`} |
|
|
aria-valuemin={1} |
|
|
aria-valuemax={50} |
|
|
aria-valuenow={penSize} |
|
|
/> |
|
|
<span className="text-xs text-gray-600 w-6 text-right" aria-hidden="true">{penSize}</span> |
|
|
</div> |
|
|
|
|
|
<button |
|
|
onClick={onToggleSettings} |
|
|
title="Open Settings" |
|
|
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300" |
|
|
aria-label="Open application settings" |
|
|
> |
|
|
<SettingsIcon className="w-5 h-5 text-gray-700" /> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default Toolbar; |