Fix double swipe/validation in Focus Mode & Improve Button Layout

This commit is contained in:
Michael committed 2026-02-12 13:18:58 +01:00
1 parent c350ca4893
commit dfa56173b9
1 file changed
+37 -33
+37 -33
View File
@@ -27,6 +27,8 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
const [exitX, setExitX] = useState(0); const [exitX, setExitX] = useState(0);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [isProcessing, setIsProcessing] = useState(false);
// Visible stack size // Visible stack size
const visibleArticles = articles.slice(currentIndex, currentIndex + 3); const visibleArticles = articles.slice(currentIndex, currentIndex + 3);
const topArticle = visibleArticles[0]; const topArticle = visibleArticles[0];
@@ -44,8 +46,31 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
const xSpring = useSpring(x, { stiffness: 1000, damping: 50 }); const xSpring = useSpring(x, { stiffness: 1000, damping: 50 });
const rotateSpring = useSpring(rotate, { stiffness: 1000, damping: 50 }); const rotateSpring = useSpring(rotate, { stiffness: 1000, damping: 50 });
const handleAction = (action: 'read' | 'keep' | 'undo') => {
if (isProcessing) return;
setIsProcessing(true);
if (action === 'undo') {
if (currentIndex > 0) {
setCurrentIndex(prev => Math.max(0, prev - 1));
x.set(0);
}
} else if (action === 'read') {
onMarkRead(topArticle.id);
setCurrentIndex(prev => prev + 1);
x.set(0);
} else {
onKeep(topArticle.id);
setCurrentIndex(prev => prev + 1);
x.set(0);
}
// Lock for animation duration
setTimeout(() => setIsProcessing(false), 500);
};
const bind = useDrag(({ active, movement: [mx], velocity: [vx] }) => { const bind = useDrag(({ active, movement: [mx], velocity: [vx] }) => {
if (readingArticle) return; // Disable drag when reading if (readingArticle || isProcessing) return; // Disable drag when reading or processing
const trigger = Math.abs(mx) > 200 || (Math.abs(vx) > 0.5 && Math.abs(mx) > 50); const trigger = Math.abs(mx) > 200 || (Math.abs(vx) > 0.5 && Math.abs(mx) > 50);
@@ -54,20 +79,8 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
const isRight = mx > 0; const isRight = mx > 0;
setExitX(isRight ? 500 : -500); // Trigger exit animation setExitX(isRight ? 500 : -500); // Trigger exit animation
// Logic handled after animation completes via AnimatePresence onExitComplete // Trigger action via handler
// But here we need to trigger state change. handleAction(isRight ? 'read' : 'keep');
// Better approach: State change triggers exit animation naturally?
// No, libraries like Framer Motion handle exit animations when components are removed from DOM.
// So we just need to advance index.
if (isRight) {
onMarkRead(topArticle.id);
} else {
onKeep(topArticle.id);
}
setCurrentIndex(prev => prev + 1);
x.set(0); // Reset for next card
} else { } else {
// Dragging or Snap Back // Dragging or Snap Back
x.set(active ? mx : 0); x.set(active ? mx : 0);
@@ -188,13 +201,11 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
</AnimatePresence> </AnimatePresence>
</div> </div>
<div className="absolute bottom-10 left-0 right-0 flex justify-center gap-8 z-20 items-center"> <div className="absolute bottom-10 left-0 right-0 flex justify-center items-center gap-4 px-4 sm:gap-8 z-20">
<button <button
onClick={() => { onClick={() => handleAction('keep')}
onKeep(topArticle.id); disabled={isProcessing}
setCurrentIndex(prev => prev + 1); className="p-4 rounded-full bg-white shadow-lg text-blue-500 hover:bg-blue-50 hover:scale-110 transition-all disabled:opacity-50 disabled:scale-100"
}}
className="p-4 rounded-full bg-white shadow-lg text-blue-500 hover:bg-blue-50 hover:scale-110 transition-all"
title="Garder (Gauche)" title="Garder (Gauche)"
> >
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -204,13 +215,8 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
{/* Undo Button - Always rendered for layout, hidden if inactive */} {/* Undo Button - Always rendered for layout, hidden if inactive */}
<button <button
onClick={() => { onClick={() => handleAction('undo')}
if (currentIndex > 0) { disabled={currentIndex === 0 || isProcessing}
setCurrentIndex(prev => Math.max(0, prev - 1));
x.set(0); // Reset position
}
}}
disabled={currentIndex === 0}
className={`p-3 rounded-full backdrop-blur-md text-white shadow-lg border border-white/20 transition-all duration-300 ${currentIndex > 0 className={`p-3 rounded-full backdrop-blur-md text-white shadow-lg border border-white/20 transition-all duration-300 ${currentIndex > 0
? 'bg-nature/80 hover:bg-nature hover:scale-105 opacity-100 cursor-pointer' ? 'bg-nature/80 hover:bg-nature hover:scale-105 opacity-100 cursor-pointer'
: 'bg-white/5 opacity-0 pointer-events-none scale-90' : 'bg-white/5 opacity-0 pointer-events-none scale-90'
@@ -223,11 +229,9 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
</button> </button>
<button <button
onClick={() => { onClick={() => handleAction('read')}
onMarkRead(topArticle.id); disabled={isProcessing}
setCurrentIndex(prev => prev + 1); className="p-4 rounded-full bg-nature shadow-lg text-white hover:bg-nature-light hover:scale-110 transition-all disabled:opacity-50 disabled:scale-100"
}}
className="p-4 rounded-full bg-nature shadow-lg text-white hover:bg-nature-light hover:scale-110 transition-all"
title="J'ai lu (Droite)" title="J'ai lu (Droite)"
> >
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">