fix: make bar length actually update during drag with visual feedback

Fixed the draggable bars to show immediate visual feedback!

Problem: Bars didn't resize during drag because:
- Switching source unit kept relative proportions the same (log scale)
- Bar width was using item.percentage which didn't update visually
- No direct visual feedback for the dragged bar

Solution: Use draggedPercentage state for immediate visual updates
- Added draggedPercentage state to track visual position during drag
- Save baseConversionsRef when drag starts (preserves original scale)
- Calculate new value from percentage using BASE scale (not updated scale)
- Use displayPercentage = isDragging ? draggedPercentage : item.percentage
- Bar width and percentage label both use displayPercentage

How it works now:
1. Mouse/touch down: save base conversions and current percentage
2. Mouse/touch move: calculate new percentage from drag delta
3. Set draggedPercentage state immediately (visual update!)
4. Calculate value from percentage using BASE scale
5. Call onValueChange to update conversions
6. Dragged bar shows draggedPercentage, others show calculated percentage
7. On release: clear draggedPercentage, bars settle to calculated positions

Result: The dragged bar now visually follows your cursor in real-time!

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-08 10:56:46 +01:00
parent f214ddf0ba
commit a6a6f84618

View File

@@ -16,10 +16,12 @@ export default function VisualComparison({
onValueChange,
}: VisualComparisonProps) {
const [draggingUnit, setDraggingUnit] = useState<string | null>(null);
const [draggedPercentage, setDraggedPercentage] = useState<number | null>(null);
const dragStartX = useRef<number>(0);
const dragStartWidth = useRef<number>(0);
const activeBarRef = useRef<HTMLDivElement | null>(null);
const lastUpdateTime = useRef<number>(0);
const baseConversionsRef = useRef<ConversionResult[]>([]);
// Calculate percentages for visual bars using logarithmic scale
const withPercentages = useMemo(() => {
if (conversions.length === 0) return [];
@@ -86,10 +88,13 @@ export default function VisualComparison({
e.preventDefault();
setDraggingUnit(unit);
setDraggedPercentage(currentPercentage);
dragStartX.current = e.clientX;
dragStartWidth.current = currentPercentage;
activeBarRef.current = barElement;
}, [onValueChange]);
// Save the current conversions as reference
baseConversionsRef.current = [...conversions];
}, [onValueChange, conversions]);
const handleMouseMove = useCallback((e: MouseEvent) => {
if (!draggingUnit || !activeBarRef.current || !onValueChange) return;
@@ -106,12 +111,14 @@ export default function VisualComparison({
let newPercentage = dragStartWidth.current + deltaPercentage;
newPercentage = Math.max(3, Math.min(100, newPercentage));
// Find the conversion result for this unit
const conversion = conversions.find(c => c.unit === draggingUnit);
if (!conversion) return;
// Update visual percentage immediately
setDraggedPercentage(newPercentage);
// Calculate min/max values for the scale
const values = conversions.map(c => Math.abs(c.value));
// Use the base conversions (from when drag started) for scale calculation
const baseConversions = baseConversionsRef.current.length > 0 ? baseConversionsRef.current : conversions;
// Calculate min/max values for the scale from BASE conversions
const values = baseConversions.map(c => Math.abs(c.value));
const maxValue = Math.max(...values);
const minValue = Math.min(...values.filter(v => v > 0));
@@ -130,7 +137,9 @@ export default function VisualComparison({
}
}
setDraggingUnit(null);
setDraggedPercentage(null);
activeBarRef.current = null;
baseConversionsRef.current = [];
}, [draggingUnit, conversions, onValueChange]);
// Touch drag handlers
@@ -139,10 +148,13 @@ export default function VisualComparison({
const touch = e.touches[0];
setDraggingUnit(unit);
setDraggedPercentage(currentPercentage);
dragStartX.current = touch.clientX;
dragStartWidth.current = currentPercentage;
activeBarRef.current = barElement;
}, [onValueChange]);
// Save the current conversions as reference
baseConversionsRef.current = [...conversions];
}, [onValueChange, conversions]);
const handleTouchMove = useCallback((e: TouchEvent) => {
if (!draggingUnit || !activeBarRef.current || !onValueChange) return;
@@ -161,10 +173,13 @@ export default function VisualComparison({
let newPercentage = dragStartWidth.current + deltaPercentage;
newPercentage = Math.max(3, Math.min(100, newPercentage));
const conversion = conversions.find(c => c.unit === draggingUnit);
if (!conversion) return;
// Update visual percentage immediately
setDraggedPercentage(newPercentage);
const values = conversions.map(c => Math.abs(c.value));
// Use the base conversions (from when drag started) for scale calculation
const baseConversions = baseConversionsRef.current.length > 0 ? baseConversionsRef.current : conversions;
const values = baseConversions.map(c => Math.abs(c.value));
const maxValue = Math.max(...values);
const minValue = Math.min(...values.filter(v => v > 0));
@@ -182,7 +197,9 @@ export default function VisualComparison({
}
}
setDraggingUnit(null);
setDraggedPercentage(null);
activeBarRef.current = null;
baseConversionsRef.current = [];
}, [draggingUnit, conversions, onValueChange]);
// Add/remove global event listeners for drag
@@ -215,6 +232,8 @@ export default function VisualComparison({
{withPercentages.map(item => {
const isDragging = draggingUnit === item.unit;
const isDraggable = !!onValueChange;
// Use draggedPercentage if this bar is being dragged
const displayPercentage = isDragging && draggedPercentage !== null ? draggedPercentage : item.percentage;
return (
<div key={item.unit} className="space-y-1.5">
@@ -255,14 +274,14 @@ export default function VisualComparison({
draggingUnit ? "transition-none" : "transition-all duration-500 ease-out"
)}
style={{
width: `${item.percentage}%`,
width: `${displayPercentage}%`,
backgroundColor: color,
}}
/>
{/* Percentage label overlay */}
<div className="absolute inset-0 flex items-center px-3 text-xs font-bold pointer-events-none">
<span className="text-foreground drop-shadow-sm">
{Math.round(item.percentage)}%
{Math.round(displayPercentage)}%
</span>
</div>