| | 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; |