Fix QC Kak Inno 8 Des
Fix QC Kak Ayu 8 Des Fix QC Pak Jun 8 Des
This commit is contained in:
@@ -25,25 +25,27 @@ const textHeading = {
|
||||
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
|
||||
};
|
||||
|
||||
const HEIGHT = 720;
|
||||
|
||||
function Layanan() {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"xl"} py={"md"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"md"}>
|
||||
<Stack align="center" gap={"0"}>
|
||||
<Stack pos="relative" bg={colors.grey[1]} gap="xl" py="md">
|
||||
<Container w={{ base: "100%", md: "80%" }} p="md">
|
||||
<Stack align="center" gap="0">
|
||||
<Text
|
||||
fw={"bold"}
|
||||
fw="bold"
|
||||
c={colors["blue-button"]}
|
||||
fz={{ base: "1.8rem", md: "3.4rem" }}
|
||||
>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
<Text ta="center" fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
{textHeading.des}
|
||||
</Text>
|
||||
<Box p={"md"}>
|
||||
<Box p="md">
|
||||
<Button
|
||||
component={Link}
|
||||
href={"/darmasaba/desa/layanan"}
|
||||
href="/darmasaba/desa/layanan"
|
||||
variant="filled"
|
||||
bg={colors["blue-button"]}
|
||||
radius={100}
|
||||
@@ -59,30 +61,26 @@ function Layanan() {
|
||||
);
|
||||
}
|
||||
|
||||
const height = 720;
|
||||
|
||||
function Slider() {
|
||||
const state = useProxy(stateLayananDesa);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const mobile = useMediaQuery("(max-width: 768px)", false);
|
||||
const router = useTransitionRouter();
|
||||
|
||||
// Refs for smooth animation
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollPositionRef = useRef(0);
|
||||
const animationFrameRef = useRef<number>(0);
|
||||
const scrollPosRef = useRef(0);
|
||||
const animFrameRef = useRef<number>(0);
|
||||
const isHoveredRef = useRef(false);
|
||||
|
||||
// Refs for drag functionality
|
||||
const isDraggingRef = useRef(false);
|
||||
const startXRef = useRef(0);
|
||||
const scrollLeftRef = useRef(0);
|
||||
const velocityRef = useRef(0);
|
||||
const lastScrollTimeRef = useRef(0);
|
||||
const lastScrollRef = useRef(0);
|
||||
|
||||
// Speed configuration
|
||||
const normalSpeed = 1.0; // pixels per frame
|
||||
const hoverSpeed = 0.3; // slower speed on hover
|
||||
const SPEED_NORMAL = 1.0;
|
||||
const SPEED_HOVER = 0.3;
|
||||
const VELOCITY_DECAY = 0.95;
|
||||
const SCROLL_THRESHOLD = 100;
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
@@ -100,126 +98,118 @@ function Slider() {
|
||||
|
||||
const data = state.suratKeterangan.findMany.data || [];
|
||||
|
||||
// Duplicate slides for seamless infinite loop
|
||||
// We need at least 3x the data for smooth infinite scrolling
|
||||
const slidesData = [...data, ...data, ...data];
|
||||
// Triple data untuk infinite loop (desktop only)
|
||||
const slidesData = mobile ? data : [...data, ...data, ...data];
|
||||
|
||||
// Auto-scroll animation untuk desktop
|
||||
useEffect(() => {
|
||||
if (loading || !containerRef.current || slidesData.length === 0) return;
|
||||
if (loading || !containerRef.current || data.length === 0 || mobile) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
const slideWidth = container.scrollWidth / slidesData.length;
|
||||
const originalDataLength = data.length;
|
||||
const originalLength = data.length;
|
||||
|
||||
// Start from the middle set of slides
|
||||
scrollPositionRef.current = slideWidth * originalDataLength;
|
||||
container.scrollLeft = scrollPositionRef.current;
|
||||
// Start dari middle set
|
||||
scrollPosRef.current = slideWidth * originalLength;
|
||||
container.scrollLeft = scrollPosRef.current;
|
||||
|
||||
const animate = () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
const slideWidth = container.scrollWidth / slidesData.length;
|
||||
const timeSinceScroll = Date.now() - lastScrollRef.current;
|
||||
const isUserScrolling = timeSinceScroll < SCROLL_THRESHOLD;
|
||||
|
||||
// Check if user recently scrolled manually
|
||||
const timeSinceLastScroll = Date.now() - lastScrollTimeRef.current;
|
||||
const isUserScrolling = timeSinceLastScroll < 100;
|
||||
|
||||
// Only auto-scroll if user is not actively scrolling or dragging
|
||||
if (!isDraggingRef.current && !isUserScrolling) {
|
||||
const currentSpeed = isHoveredRef.current ? hoverSpeed : normalSpeed;
|
||||
scrollPositionRef.current += currentSpeed;
|
||||
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
|
||||
scrollPosRef.current += speed;
|
||||
|
||||
// Reset position for infinite loop
|
||||
if (scrollPositionRef.current >= slideWidth * (originalDataLength * 2)) {
|
||||
scrollPositionRef.current -= slideWidth * originalDataLength;
|
||||
// Reset untuk infinite loop
|
||||
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
|
||||
scrollPosRef.current -= slideWidth * originalLength;
|
||||
}
|
||||
if (scrollPosRef.current <= 0) {
|
||||
scrollPosRef.current += slideWidth * originalLength;
|
||||
}
|
||||
|
||||
if (scrollPositionRef.current <= 0) {
|
||||
scrollPositionRef.current += slideWidth * originalDataLength;
|
||||
}
|
||||
|
||||
container.scrollLeft = scrollPositionRef.current;
|
||||
container.scrollLeft = scrollPosRef.current;
|
||||
} else {
|
||||
// Sync scroll position when user is scrolling
|
||||
scrollPositionRef.current = container.scrollLeft;
|
||||
scrollPosRef.current = container.scrollLeft;
|
||||
|
||||
// Apply momentum/velocity for smooth drag release
|
||||
// Momentum untuk drag release
|
||||
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
||||
scrollPositionRef.current += velocityRef.current;
|
||||
velocityRef.current *= 0.95; // Decay velocity
|
||||
container.scrollLeft = scrollPositionRef.current;
|
||||
scrollPosRef.current += velocityRef.current;
|
||||
velocityRef.current *= VELOCITY_DECAY;
|
||||
container.scrollLeft = scrollPosRef.current;
|
||||
}
|
||||
}
|
||||
|
||||
animationFrameRef.current = requestAnimationFrame(animate);
|
||||
animFrameRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animationFrameRef.current = requestAnimationFrame(animate);
|
||||
animFrameRef.current = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
if (animFrameRef.current) {
|
||||
cancelAnimationFrame(animFrameRef.current);
|
||||
}
|
||||
};
|
||||
}, [loading, slidesData.length, data.length, mobile]);
|
||||
}, [loading, data.length, mobile]);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
isHoveredRef.current = true;
|
||||
if (!mobile) isHoveredRef.current = true;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isHoveredRef.current = false;
|
||||
isDraggingRef.current = false;
|
||||
if (!mobile) {
|
||||
isHoveredRef.current = false;
|
||||
isDraggingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Mouse drag handlers
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current || mobile) return;
|
||||
|
||||
isDraggingRef.current = true;
|
||||
startXRef.current = e.pageX - containerRef.current.offsetLeft;
|
||||
scrollLeftRef.current = containerRef.current.scrollLeft;
|
||||
velocityRef.current = 0;
|
||||
|
||||
containerRef.current.style.cursor = 'grabbing';
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent) => {
|
||||
if (!isDraggingRef.current || !containerRef.current) return;
|
||||
if (!isDraggingRef.current || !containerRef.current || mobile) return;
|
||||
|
||||
e.preventDefault();
|
||||
const x = e.pageX - containerRef.current.offsetLeft;
|
||||
const walk = (x - startXRef.current) * 2; // Multiply for faster scroll
|
||||
const walk = (x - startXRef.current) * 2;
|
||||
const newScrollLeft = scrollLeftRef.current - walk;
|
||||
|
||||
// Calculate velocity for momentum
|
||||
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
|
||||
|
||||
containerRef.current.scrollLeft = newScrollLeft;
|
||||
scrollPositionRef.current = newScrollLeft;
|
||||
lastScrollTimeRef.current = Date.now();
|
||||
scrollPosRef.current = newScrollLeft;
|
||||
lastScrollRef.current = Date.now();
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current || mobile) return;
|
||||
|
||||
isDraggingRef.current = false;
|
||||
containerRef.current.style.cursor = 'grab';
|
||||
};
|
||||
|
||||
// Wheel scroll handler
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current || mobile) return;
|
||||
|
||||
e.preventDefault();
|
||||
containerRef.current.scrollLeft += e.deltaY;
|
||||
scrollPositionRef.current = containerRef.current.scrollLeft;
|
||||
lastScrollTimeRef.current = Date.now();
|
||||
scrollPosRef.current = containerRef.current.scrollLeft;
|
||||
lastScrollRef.current = Date.now();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Skeleton height={height} />;
|
||||
return <Skeleton height={HEIGHT} />;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
@@ -242,9 +232,13 @@ function Slider() {
|
||||
onMouseUp={handleMouseUp}
|
||||
onWheel={handleWheel}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
cursor: "grab",
|
||||
overflowX: mobile ? "auto" : "hidden",
|
||||
overflowY: "hidden",
|
||||
cursor: mobile ? "default" : "grab",
|
||||
userSelect: "none",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
scrollbarWidth: "none",
|
||||
msOverflowStyle: "none",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -259,13 +253,13 @@ function Slider() {
|
||||
<Box
|
||||
key={`${item.id}-${index}`}
|
||||
style={{
|
||||
flex: `0 0 ${mobile ? "100%" : "calc(33.333% - 1rem)"}`,
|
||||
minWidth: mobile ? "100%" : "calc(33.333% - 1rem)",
|
||||
flex: `0 0 ${mobile ? "90%" : "calc(33.333% - 1rem)"}`,
|
||||
minWidth: mobile ? "90%" : "calc(33.333% - 1rem)",
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
h={height}
|
||||
pos={"relative"}
|
||||
h={HEIGHT}
|
||||
pos="relative"
|
||||
style={{
|
||||
backgroundImage: `url(${item.image?.link})`,
|
||||
backgroundSize: "cover",
|
||||
@@ -280,23 +274,23 @@ function Slider() {
|
||||
borderRadius: 8,
|
||||
zIndex: 0,
|
||||
}}
|
||||
pos={"absolute"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
pos="absolute"
|
||||
w="100%"
|
||||
h="100%"
|
||||
bg={colors.trans.dark[2]}
|
||||
/>
|
||||
<Stack
|
||||
justify="space-between"
|
||||
h={"100%"}
|
||||
h="100%"
|
||||
gap={0}
|
||||
p={"lg"}
|
||||
pos={"relative"}
|
||||
p="lg"
|
||||
pos="relative"
|
||||
>
|
||||
<Box p={"lg"}>
|
||||
<Box p="lg">
|
||||
<Text
|
||||
fw={"bold"}
|
||||
c={"white"}
|
||||
size={"3.5rem"}
|
||||
fw="bold"
|
||||
c="white"
|
||||
fz={{base: "xl", md: "3.5rem"}}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
@@ -310,7 +304,7 @@ function Slider() {
|
||||
router.push(`/darmasaba/desa/layanan/${item.id}`)
|
||||
}
|
||||
px={46}
|
||||
radius={"100"}
|
||||
radius="100"
|
||||
size="md"
|
||||
bg={colors["blue-button"]}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user