Files
paint-ui/components/editor/tool-options.tsx
Sebastian Krüger f948d544a6 feat(text-tool): add comprehensive text formatting controls
Enhanced the text tool toolbar with professional formatting options:
- Font style controls: Bold and Italic toggle buttons
- Text alignment: Left, Center, Right buttons with visual feedback
- Line height slider: Range 0.5-3.0 with 0.1 step precision
- Letter spacing slider: Range -10px to 50px
- Expanded font family: Added 10 more fonts (16 total)
  - Added: Trebuchet MS, Impact, Comic Sans MS, Palatino, Garamond,
    Bookman, Tahoma, Lucida Console, Monaco, Consolas

All controls have active state highlighting and smooth transitions
for better user experience.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 15:16:52 +01:00

394 lines
14 KiB
TypeScript

'use client';
import { useToolStore } from '@/store';
import { useShapeStore } from '@/store/shape-store';
import { useSelectionStore } from '@/store/selection-store';
import { useTextStore } from '@/store/text-store';
export function ToolOptions() {
const { activeTool, settings, setSize, setOpacity, setHardness, setColor, setFlow } = useToolStore();
const { settings: shapeSettings, setShapeType } = useShapeStore();
const { selectionType, setSelectionType } = useSelectionStore();
const {
settings: textSettings,
setFontFamily,
setFontSize,
setFontStyle,
setFontWeight,
setAlign,
setLineHeight,
setLetterSpacing,
setColor: setTextColor,
} = useTextStore();
// Drawing tools: brush, pencil, eraser
const isDrawingTool = ['brush', 'eraser', 'pencil'].includes(activeTool);
const showHardness = ['brush'].includes(activeTool);
const showColor = ['brush', 'pencil'].includes(activeTool);
const showFlow = ['brush'].includes(activeTool);
// Fill tool
const isFillTool = activeTool === 'fill';
// Shape tool
const isShapeTool = activeTool === 'shape';
// Selection tool
const isSelectionTool = activeTool === 'select';
// Text tool
const isTextTool = activeTool === 'text';
// Don't show options bar if no options available
if (!isDrawingTool && !isFillTool && !isShapeTool && !isSelectionTool && !isTextTool) {
return null;
}
return (
<div className="flex items-center gap-6 px-4 flex-1">
{/* Drawing Tools Options */}
{isDrawingTool && (
<>
{showColor && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Color:
</label>
<input
type="color"
value={settings.color}
onChange={(e) => setColor(e.target.value)}
className="h-8 w-16 rounded border border-border cursor-pointer"
/>
<input
type="text"
value={settings.color}
onChange={(e) => setColor(e.target.value)}
className="w-24 px-2 py-1 text-xs rounded border border-border bg-background text-foreground"
/>
</div>
)}
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Size:
</label>
<input
type="range"
min="1"
max="200"
value={settings.size}
onChange={(e) => setSize(Number(e.target.value))}
className="w-32"
/>
<span className="text-sm text-muted-foreground w-12">
{settings.size}px
</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Opacity:
</label>
<input
type="range"
min="0"
max="100"
value={settings.opacity * 100}
onChange={(e) => setOpacity(Number(e.target.value) / 100)}
className="w-32"
/>
<span className="text-sm text-muted-foreground w-10">
{Math.round(settings.opacity * 100)}%
</span>
</div>
{showHardness && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Hardness:
</label>
<input
type="range"
min="0"
max="100"
value={settings.hardness * 100}
onChange={(e) => setHardness(Number(e.target.value) / 100)}
className="w-32"
/>
<span className="text-sm text-muted-foreground w-10">
{Math.round(settings.hardness * 100)}%
</span>
</div>
)}
{showFlow && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Flow:
</label>
<input
type="range"
min="0"
max="100"
value={settings.flow * 100}
onChange={(e) => setFlow(Number(e.target.value) / 100)}
className="w-32"
/>
<span className="text-sm text-muted-foreground w-10">
{Math.round(settings.flow * 100)}%
</span>
</div>
)}
</>
)}
{/* Fill Tool Options */}
{isFillTool && (
<>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Color:
</label>
<input
type="color"
value={settings.color}
onChange={(e) => setColor(e.target.value)}
className="h-8 w-16 rounded border border-border cursor-pointer"
/>
<input
type="text"
value={settings.color}
onChange={(e) => setColor(e.target.value)}
className="w-24 px-2 py-1 text-xs rounded border border-border bg-background text-foreground"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Opacity:
</label>
<input
type="range"
min="0"
max="100"
value={settings.opacity * 100}
onChange={(e) => setOpacity(Number(e.target.value) / 100)}
className="w-32"
/>
<span className="text-sm text-muted-foreground w-10">
{Math.round(settings.opacity * 100)}%
</span>
</div>
</>
)}
{/* Shape Tool Options */}
{isShapeTool && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Shape:
</label>
<select
value={shapeSettings.type}
onChange={(e) => setShapeType(e.target.value as any)}
className="px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground"
>
<option value="rectangle">Rectangle</option>
<option value="ellipse">Ellipse</option>
<option value="line">Line</option>
<option value="arrow">Arrow</option>
<option value="polygon">Polygon</option>
<option value="star">Star</option>
<option value="triangle">Triangle</option>
</select>
</div>
)}
{/* Selection Tool Options */}
{isSelectionTool && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Mode:
</label>
<select
value={selectionType}
onChange={(e) => setSelectionType(e.target.value as any)}
className="px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground"
>
<option value="rectangular">Rectangular</option>
<option value="elliptical">Elliptical</option>
<option value="lasso">Lasso</option>
<option value="magic-wand">Magic Wand</option>
</select>
</div>
)}
{/* Text Tool Options */}
{isTextTool && (
<>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Font:
</label>
<select
value={textSettings.fontFamily}
onChange={(e) => setFontFamily(e.target.value)}
className="px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground"
>
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Georgia">Georgia</option>
<option value="Courier New">Courier New</option>
<option value="Verdana">Verdana</option>
<option value="Trebuchet MS">Trebuchet MS</option>
<option value="Impact">Impact</option>
<option value="Comic Sans MS">Comic Sans MS</option>
<option value="Palatino">Palatino</option>
<option value="Garamond">Garamond</option>
<option value="Bookman">Bookman</option>
<option value="Tahoma">Tahoma</option>
<option value="Lucida Console">Lucida Console</option>
<option value="Monaco">Monaco</option>
<option value="Consolas">Consolas</option>
</select>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Size:
</label>
<input
type="number"
min="8"
max="500"
value={textSettings.fontSize}
onChange={(e) => setFontSize(Number(e.target.value))}
className="w-20 px-2 py-1 text-sm rounded border border-border bg-background text-foreground"
/>
<span className="text-sm text-muted-foreground">px</span>
</div>
{/* Font Style: Bold/Italic */}
<div className="flex items-center gap-1">
<button
onClick={() => setFontWeight(textSettings.fontWeight === 'bold' ? 'normal' : 'bold')}
className={`px-3 py-1.5 text-sm font-bold rounded-md border transition-colors ${
textSettings.fontWeight === 'bold'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background text-foreground border-border hover:bg-accent'
}`}
title="Bold"
>
B
</button>
<button
onClick={() => setFontStyle(textSettings.fontStyle === 'italic' ? 'normal' : 'italic')}
className={`px-3 py-1.5 text-sm italic rounded-md border transition-colors ${
textSettings.fontStyle === 'italic'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background text-foreground border-border hover:bg-accent'
}`}
title="Italic"
>
I
</button>
</div>
{/* Text Alignment */}
<div className="flex items-center gap-1">
<button
onClick={() => setAlign('left')}
className={`px-3 py-1.5 text-sm rounded-md border transition-colors ${
textSettings.align === 'left'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background text-foreground border-border hover:bg-accent'
}`}
title="Align Left"
>
</button>
<button
onClick={() => setAlign('center')}
className={`px-3 py-1.5 text-sm rounded-md border transition-colors ${
textSettings.align === 'center'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background text-foreground border-border hover:bg-accent'
}`}
title="Align Center"
>
</button>
<button
onClick={() => setAlign('right')}
className={`px-3 py-1.5 text-sm rounded-md border transition-colors ${
textSettings.align === 'right'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background text-foreground border-border hover:bg-accent'
}`}
title="Align Right"
>
</button>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Color:
</label>
<input
type="color"
value={textSettings.color}
onChange={(e) => setTextColor(e.target.value)}
className="h-8 w-16 rounded border border-border cursor-pointer"
/>
<input
type="text"
value={textSettings.color}
onChange={(e) => setTextColor(e.target.value)}
className="w-24 px-2 py-1 text-xs rounded border border-border bg-background text-foreground"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Line Height:
</label>
<input
type="range"
min="0.5"
max="3"
step="0.1"
value={textSettings.lineHeight}
onChange={(e) => setLineHeight(Number(e.target.value))}
className="w-24"
/>
<span className="text-sm text-muted-foreground w-8">
{textSettings.lineHeight.toFixed(1)}
</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
Letter Spacing:
</label>
<input
type="range"
min="-10"
max="50"
step="1"
value={textSettings.letterSpacing}
onChange={(e) => setLetterSpacing(Number(e.target.value))}
className="w-24"
/>
<span className="text-sm text-muted-foreground w-10">
{textSettings.letterSpacing}px
</span>
</div>
</>
)}
</div>
);
}