mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-05 03:02:56 +00:00
dev day #63
This commit is contained in:
@@ -482,6 +482,10 @@ const MatchesAdminPage = () => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [startX, setStartX] = useState(0);
|
||||
const [scrollLeft, setScrollLeft] = useState(0);
|
||||
const [lastX, setLastX] = useState(0);
|
||||
const [lastTime, setLastTime] = useState(0);
|
||||
const velocityRef = useRef(0);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
// Color modes for past/future matches
|
||||
const pastMatchBg = useColorModeValue('gray.100', 'gray.700');
|
||||
@@ -499,11 +503,20 @@ const MatchesAdminPage = () => {
|
||||
// Drag-to-scroll handlers
|
||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!scrollRef.current) return;
|
||||
// Cancel any ongoing momentum animation
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
animationRef.current = null;
|
||||
}
|
||||
setIsDragging(true);
|
||||
setStartX(e.pageX - scrollRef.current.offsetLeft);
|
||||
setScrollLeft(scrollRef.current.scrollLeft);
|
||||
setLastX(e.pageX);
|
||||
setLastTime(Date.now());
|
||||
velocityRef.current = 0;
|
||||
scrollRef.current.style.cursor = 'grabbing';
|
||||
scrollRef.current.style.userSelect = 'none';
|
||||
scrollRef.current.style.scrollBehavior = 'auto'; // Disable smooth scroll during drag
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
@@ -519,6 +532,24 @@ const MatchesAdminPage = () => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.style.cursor = 'grab';
|
||||
scrollRef.current.style.userSelect = 'auto';
|
||||
scrollRef.current.style.scrollBehavior = 'smooth';
|
||||
|
||||
// Apply momentum scrolling
|
||||
const velocity = velocityRef.current;
|
||||
if (Math.abs(velocity) > 0.5) {
|
||||
const applyMomentum = () => {
|
||||
if (!scrollRef.current) return;
|
||||
velocityRef.current *= 0.95; // Deceleration factor
|
||||
scrollRef.current.scrollLeft -= velocityRef.current;
|
||||
|
||||
if (Math.abs(velocityRef.current) > 0.5) {
|
||||
animationRef.current = requestAnimationFrame(applyMomentum);
|
||||
} else {
|
||||
animationRef.current = null;
|
||||
}
|
||||
};
|
||||
animationRef.current = requestAnimationFrame(applyMomentum);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -526,8 +557,77 @@ const MatchesAdminPage = () => {
|
||||
if (!isDragging || !scrollRef.current) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - scrollRef.current.offsetLeft;
|
||||
const walk = (x - startX) * 2; // Scroll speed multiplier
|
||||
const walk = (x - startX) * 1.5; // Scroll speed multiplier (reduced for smoother feel)
|
||||
scrollRef.current.scrollLeft = scrollLeft - walk;
|
||||
|
||||
// Calculate velocity for momentum
|
||||
const now = Date.now();
|
||||
const timeDelta = now - lastTime;
|
||||
if (timeDelta > 0) {
|
||||
const currentX = e.pageX;
|
||||
const distance = currentX - lastX;
|
||||
velocityRef.current = distance / timeDelta * 16; // Normalize to ~60fps
|
||||
setLastX(currentX);
|
||||
setLastTime(now);
|
||||
}
|
||||
};
|
||||
|
||||
// Touch handlers for mobile
|
||||
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
if (!scrollRef.current) return;
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
animationRef.current = null;
|
||||
}
|
||||
const touch = e.touches[0];
|
||||
setIsDragging(true);
|
||||
setStartX(touch.pageX - scrollRef.current.offsetLeft);
|
||||
setScrollLeft(scrollRef.current.scrollLeft);
|
||||
setLastX(touch.pageX);
|
||||
setLastTime(Date.now());
|
||||
velocityRef.current = 0;
|
||||
if (scrollRef.current) scrollRef.current.style.scrollBehavior = 'auto';
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
if (!isDragging || !scrollRef.current) return;
|
||||
const touch = e.touches[0];
|
||||
const x = touch.pageX - scrollRef.current.offsetLeft;
|
||||
const walk = (x - startX) * 1.5;
|
||||
scrollRef.current.scrollLeft = scrollLeft - walk;
|
||||
|
||||
const now = Date.now();
|
||||
const timeDelta = now - lastTime;
|
||||
if (timeDelta > 0) {
|
||||
const currentX = touch.pageX;
|
||||
const distance = currentX - lastX;
|
||||
velocityRef.current = distance / timeDelta * 16;
|
||||
setLastX(currentX);
|
||||
setLastTime(now);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
setIsDragging(false);
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.style.scrollBehavior = 'smooth';
|
||||
|
||||
const velocity = velocityRef.current;
|
||||
if (Math.abs(velocity) > 0.5) {
|
||||
const applyMomentum = () => {
|
||||
if (!scrollRef.current) return;
|
||||
velocityRef.current *= 0.95;
|
||||
scrollRef.current.scrollLeft -= velocityRef.current;
|
||||
|
||||
if (Math.abs(velocityRef.current) > 0.5) {
|
||||
animationRef.current = requestAnimationFrame(applyMomentum);
|
||||
} else {
|
||||
animationRef.current = null;
|
||||
}
|
||||
};
|
||||
animationRef.current = requestAnimationFrame(applyMomentum);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Utility to check if match is in the past
|
||||
@@ -551,7 +651,13 @@ const MatchesAdminPage = () => {
|
||||
updateScrollShadow();
|
||||
const onResize = () => updateScrollShadow();
|
||||
window.addEventListener('resize', onResize);
|
||||
return () => window.removeEventListener('resize', onResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
// Cleanup momentum animation on unmount
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const headerBg = useColorModeValue('brand.primary', 'gray.700');
|
||||
@@ -656,8 +762,8 @@ const MatchesAdminPage = () => {
|
||||
</WrapItem>
|
||||
</Wrap>
|
||||
{showScrollHint && (
|
||||
<Text fontSize="xs" color="blue.600" fontWeight="600" mb={2}>
|
||||
💡 Tip: Tabulku můžete posouvat tažením myší nebo touchem →
|
||||
<Text fontSize="xs" color="blue.600" fontWeight="600" mb={2} display="flex" alignItems="center" gap={1}>
|
||||
💡 Tip: Tabulku můžete plynule posouvat tažením myší nebo prstem →
|
||||
</Text>
|
||||
)}
|
||||
<Box
|
||||
@@ -676,23 +782,33 @@ const MatchesAdminPage = () => {
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseMove={handleMouseMove}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onScroll={(e) => {
|
||||
updateScrollShadow();
|
||||
if ((e.currentTarget as HTMLDivElement).scrollLeft > 0 && showScrollHint) setShowScrollHint(false);
|
||||
}}
|
||||
sx={{
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollBehavior: 'smooth',
|
||||
'th, td': { whiteSpace: 'nowrap' },
|
||||
'::-webkit-scrollbar': { height: '12px' },
|
||||
'::-webkit-scrollbar': { height: '14px' },
|
||||
'::-webkit-scrollbar-thumb': {
|
||||
background: '#3182ce',
|
||||
borderRadius: '8px',
|
||||
'&:hover': { background: '#2c5aa0' }
|
||||
borderRadius: '10px',
|
||||
border: '3px solid transparent',
|
||||
backgroundClip: 'content-box',
|
||||
transition: 'background 0.2s ease',
|
||||
'&:hover': { background: '#2c5aa0', backgroundClip: 'content-box' },
|
||||
'&:active': { background: '#2a4e8a', backgroundClip: 'content-box' }
|
||||
},
|
||||
'::-webkit-scrollbar-track': {
|
||||
background: '#e2e8f0',
|
||||
borderRadius: '8px',
|
||||
margin: '0 4px'
|
||||
background: useColorModeValue('#f7fafc', '#2d3748'),
|
||||
borderRadius: '10px',
|
||||
margin: '0 8px',
|
||||
border: '1px solid',
|
||||
borderColor: useColorModeValue('#e2e8f0', '#4a5568')
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user