xpaint_zh / components /Toolbar.tsx
suisuyy
Rename project to xpaintai_zh in package.json and metadata.json; integrate translation support in various components and hooks
69ae4e4
import React from 'react';
import { UndoIcon, RedoIcon, UploadIcon, DownloadIcon, ClearIcon, BrushIcon, EraserIcon, MagicSparkleIcon, SettingsIcon } from './icons';
import { TFunction } from '../types';
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;
t: TFunction;
}
const Toolbar: React.FC<ToolbarProps> = ({
penColor,
onColorChange,
penSize,
onSizeChange,
onUndo,
canUndo,
onRedo,
canRedo,
onLoadImage,
onExportImage,
onClearCanvas,
isEraserMode,
onToggleEraser,
onMagicUpload,
isMagicUploading,
canShare,
onToggleSettings,
t,
}) => {
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={t('undo')}
className="p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors border-gray-300"
aria-label={t('undoAria')}
>
<UndoIcon className="w-5 h-5 text-gray-700" />
</button>
<button
onClick={onRedo}
disabled={!canRedo}
title={t('redo')}
className="p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors border-gray-300"
aria-label={t('redoAria')}
>
<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={t('loadImageAria')}
/>
<button
onClick={handleUploadClick}
title={t('loadImage')}
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300"
aria-label={t('loadImageAria')}
>
<UploadIcon className="w-5 h-5 text-gray-700" />
</button>
<button
onClick={onExportImage}
title={t('exportImage')}
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300"
aria-label={t('exportImageAria')}
>
<DownloadIcon className="w-5 h-5 text-gray-700" />
</button>
<button
onClick={onMagicUpload}
title={t('shareAndEditWithAI')}
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={t('shareAndEditWithAIAria')}
>
<MagicSparkleIcon className="w-5 h-5 text-purple-600" />
</button>
<button
onClick={onClearCanvas}
title={t('clearCanvas')}
className="p-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors tool-button"
aria-label={t('clearCanvasAria')}
>
<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 ? t('switchToBrush') : t('switchToEraser')}
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 ? t('switchToBrushAria') : t('switchToEraserAria')}
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">{t('penColor')}:</label>
<input
type="color"
id="penColor"
title={t('penColor')}
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={t('selectPenColorAria')}
/>
</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">{t('penSize', { size: penSize })}:</label>
<input
type="range"
id="penSize"
title={t('penSize', { 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={t('penSizeAria', { size: penSize })}
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={t('openSettings')}
className="p-2 rounded-md hover:bg-gray-100 transition-colors border-gray-300"
aria-label={t('openSettings')}
>
<SettingsIcon className="w-5 h-5 text-gray-700" />
</button>
</div>
</div>
);
};
export default Toolbar;