feat(phase-13): implement gradient tool with linear, radial, and angular modes
Add comprehensive gradient tool with three gradient types and full UI integration. Features: - Gradient tool with drag-to-create interaction - Three gradient types: Linear, Radial, and Angular (conic) - Live preview during drag with 70% opacity overlay - Primary and secondary color selection - Gradient type selector in tool options - Undo/redo support through command system - Fallback to radial gradient for browsers without conic gradient support Changes: - Created tools/gradient-tool.ts with GradientTool class - Added 'gradient' to ToolType in types/tool.ts - Extended ToolSettings with secondaryColor and gradientType - Updated store/tool-store.ts with setSecondaryColor and setGradientType methods - Added gradient tool loading in lib/tool-loader.ts - Added gradient button to tool palette with 'G' shortcut - Added gradient tool options UI in components/editor/tool-options.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import { googleFontsLoader } from '@/lib/google-fonts-loader';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function ToolOptions() {
|
||||
const { activeTool, settings, setSize, setOpacity, setHardness, setColor, setFlow } = useToolStore();
|
||||
const { activeTool, settings, setSize, setOpacity, setHardness, setColor, setFlow, setSecondaryColor, setGradientType } = useToolStore();
|
||||
const { settings: shapeSettings, setShapeType } = useShapeStore();
|
||||
const { selectionType, setSelectionType } = useSelectionStore();
|
||||
const {
|
||||
@@ -49,6 +49,9 @@ export function ToolOptions() {
|
||||
// Fill tool
|
||||
const isFillTool = activeTool === 'fill';
|
||||
|
||||
// Gradient tool
|
||||
const isGradientTool = activeTool === 'gradient';
|
||||
|
||||
// Shape tool
|
||||
const isShapeTool = activeTool === 'shape';
|
||||
|
||||
@@ -59,7 +62,7 @@ export function ToolOptions() {
|
||||
const isTextTool = activeTool === 'text';
|
||||
|
||||
// Don't show options bar if no options available
|
||||
if (!isDrawingTool && !isFillTool && !isShapeTool && !isSelectionTool && !isTextTool) {
|
||||
if (!isDrawingTool && !isFillTool && !isGradientTool && !isShapeTool && !isSelectionTool && !isTextTool) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -202,6 +205,79 @@ export function ToolOptions() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Gradient Tool Options */}
|
||||
{isGradientTool && (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
|
||||
Type:
|
||||
</label>
|
||||
<select
|
||||
value={settings.gradientType || 'linear'}
|
||||
onChange={(e) => setGradientType(e.target.value as 'linear' | 'radial' | 'angular')}
|
||||
className="px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground"
|
||||
>
|
||||
<option value="linear">Linear</option>
|
||||
<option value="radial">Radial</option>
|
||||
<option value="angular">Angular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium text-card-foreground whitespace-nowrap">
|
||||
Start 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">
|
||||
End Color:
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={settings.secondaryColor || '#ffffff'}
|
||||
onChange={(e) => setSecondaryColor(e.target.value)}
|
||||
className="h-8 w-16 rounded border border-border cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.secondaryColor || '#ffffff'}
|
||||
onChange={(e) => setSecondaryColor(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">
|
||||
|
||||
Reference in New Issue
Block a user