Files
paint-ui/components/editor/status-bar.tsx
Sebastian Krüger 5c4763cb62 feat(phase-12): add professional UI polish with status bar, navigator, and shortcuts help
Implements comprehensive quality-of-life improvements for professional editing experience:

**1. Status Bar Component** (`components/editor/status-bar.tsx`):
- Real-time canvas dimensions display (width × height)
- Live zoom percentage indicator
- Dynamic cursor position tracking in canvas coordinates
- FPS counter for performance monitoring
- Memory usage display (when browser supports performance.memory)
- Icons for each metric (Maximize2, ZoomIn, MousePointer, Activity, HardDrive)
- Fixed bottom position with clean UI
- Updates at 60 FPS for smooth cursor tracking
- Memory updates every 2 seconds to reduce overhead

**2. Mini-Map / Navigator** (`components/canvas/mini-map.tsx`):
- Live thumbnail preview of entire canvas
- Renders all visible layers with proper stacking order
- Checkerboard background for transparency visualization
- Interactive viewport indicator (blue rectangle with semi-transparent fill)
- Click or drag to pan viewport to different canvas areas
- Collapsible with expand/minimize toggle button
- Maintains canvas aspect ratio (max 200px)
- Positioned in bottom-right corner as floating overlay
- Zoom percentage display at bottom
- Smart scaling for optimal thumbnail size
- Cursor changes to pointer/grabbing during interaction

**3. Keyboard Shortcuts Help Panel** (`components/editor/shortcuts-help-panel.tsx`):
- Comprehensive list of 40+ keyboard shortcuts
- 7 categories: File, Edit, View, Tools, Layers, Transform, Adjustments, Help
- Real-time search filtering (searches action, category, keys, description)
- Beautiful kbd element styling for shortcut keys
- Modal overlay with backdrop blur
- Opens with `?` or `F1` keys
- Closes with `Esc` key or backdrop click
- Fully responsive with scrollable content
- Organized sections with category headers
- Shows key combinations with proper separators (+)
- Optional descriptions for special shortcuts (e.g., "Hold to pan")
- Footer with helpful hints

**Integration Changes:**

**Canvas Component** (`canvas-with-tools.tsx`):
- Added `onCursorMove` prop callback for cursor position reporting
- Modified `handlePointerMove` to report canvas coordinates
- Created `handlePointerLeave` to clear cursor when leaving canvas
- Integrated MiniMap component as overlay

**Editor Layout** (`editor-layout.tsx`):
- Added cursor position state management
- Integrated StatusBar at bottom of layout
- Added ShortcutsHelpPanel with state management
- Keyboard event handlers for `?` and `F1` to open shortcuts
- Cursor position passed down to CanvasWithTools and up to StatusBar

**Features:**
- Non-intrusive overlays that don't block canvas interaction
- All components optimized for performance
- Responsive design adapts to different screen sizes
- Professional appearance matching app theme
- Smooth animations and transitions
- Real-time updates without lag

**User Experience Improvements:**
- Quick access to all shortcuts via `?` or `F1`
- Always-visible status information in bottom bar
- Easy canvas navigation with mini-map
- Performance monitoring at a glance
- Professional editor feel with polished UI

All features tested and working smoothly with no performance impact.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:51:32 +01:00

109 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useCanvasStore } from '@/store';
import { useState, useEffect } from 'react';
import { Maximize2, ZoomIn, MousePointer, Activity, HardDrive } from 'lucide-react';
interface StatusBarProps {
cursorX?: number;
cursorY?: number;
}
export function StatusBar({ cursorX, cursorY }: StatusBarProps) {
const { width, height, zoom } = useCanvasStore();
const [fps, setFps] = useState(0);
const [memory, setMemory] = useState<number | null>(null);
// FPS counter
useEffect(() => {
let frameCount = 0;
let lastTime = performance.now();
let animationFrameId: number;
const calculateFPS = () => {
frameCount++;
const currentTime = performance.now();
const elapsed = currentTime - lastTime;
if (elapsed >= 1000) {
setFps(Math.round((frameCount * 1000) / elapsed));
frameCount = 0;
lastTime = currentTime;
}
animationFrameId = requestAnimationFrame(calculateFPS);
};
animationFrameId = requestAnimationFrame(calculateFPS);
return () => {
cancelAnimationFrame(animationFrameId);
};
}, []);
// Memory usage (if available in browser)
useEffect(() => {
const updateMemory = () => {
// @ts-ignore - performance.memory is not in all browsers
if (performance.memory) {
// @ts-ignore
const usedMB = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024);
setMemory(usedMB);
}
};
updateMemory();
const interval = setInterval(updateMemory, 2000); // Update every 2 seconds
return () => clearInterval(interval);
}, []);
return (
<div className="flex items-center justify-between h-8 px-4 bg-card border-t border-border text-xs text-muted-foreground">
{/* Left side - Canvas info */}
<div className="flex items-center gap-4">
{/* Canvas dimensions */}
<div className="flex items-center gap-1.5" title="Canvas dimensions">
<Maximize2 className="h-3 w-3" />
<span className="font-mono">
{width} × {height}
</span>
</div>
{/* Zoom level */}
<div className="flex items-center gap-1.5" title="Zoom level">
<ZoomIn className="h-3 w-3" />
<span className="font-mono">{Math.round(zoom * 100)}%</span>
</div>
{/* Cursor position */}
{cursorX !== undefined && cursorY !== undefined && (
<div className="flex items-center gap-1.5" title="Cursor position">
<MousePointer className="h-3 w-3" />
<span className="font-mono">
X: {Math.round(cursorX)}, Y: {Math.round(cursorY)}
</span>
</div>
)}
</div>
{/* Right side - Performance info */}
<div className="flex items-center gap-4">
{/* FPS */}
<div className="flex items-center gap-1.5" title="Frames per second">
<Activity className="h-3 w-3" />
<span className="font-mono">{fps} FPS</span>
</div>
{/* Memory usage */}
{memory !== null && (
<div className="flex items-center gap-1.5" title="Memory usage">
<HardDrive className="h-3 w-3" />
<span className="font-mono">{memory} MB</span>
</div>
)}
</div>
</div>
);
}