sn: 침수흔적심선/위선 화살표 연속 scroll 추가

This commit is contained in:
2026-09-08 18:15:40 +09:00
parent b99f4904b0
commit 23341b93c7
2 changed files with 116 additions and 44 deletions
@@ -5,6 +5,7 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { RefObject } from "react";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import type Feature from "ol/Feature.js";
import VectorSource from "ol/source/Vector.js";
@@ -29,6 +30,96 @@ type FldngHstryGisLayer = {
) => void | Promise<void>;
};
type FldngHstryScrollButtonProps = {
targetRef: RefObject<HTMLDivElement | null>;
direction: -1 | 1;
label: string;
children: string;
};
const FLDNG_HSTRY_SCROLL_CLICK_DISTANCE = 120;
const FLDNG_HSTRY_SCROLL_HOLD_DELAY = 300;
const FLDNG_HSTRY_SCROLL_HOLD_SPEED = 4;
/** 짧게 누르면 한 칸, 길게 누르면 목록을 해당 방향으로 계속 이동한다. */
const FldngHstryScrollButton = ({
targetRef,
direction,
label,
children,
}: FldngHstryScrollButtonProps) => {
const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const animationFrameRef = useRef<number | null>(null);
const pointerClickHandledRef = useRef(false);
const stopContinuousScroll = useCallback(() => {
if (holdTimerRef.current !== null) {
clearTimeout(holdTimerRef.current);
holdTimerRef.current = null;
}
if (animationFrameRef.current !== null) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
}, []);
const scrollOnce = useCallback(() => {
targetRef.current?.scrollBy({
left: direction * FLDNG_HSTRY_SCROLL_CLICK_DISTANCE,
behavior: "smooth",
});
}, [direction, targetRef]);
const startContinuousScroll = useCallback(() => {
const scroll = () => {
const target = targetRef.current;
if (!target) {
stopContinuousScroll();
return;
}
target.scrollLeft += direction * FLDNG_HSTRY_SCROLL_HOLD_SPEED;
animationFrameRef.current = requestAnimationFrame(scroll);
};
animationFrameRef.current = requestAnimationFrame(scroll);
}, [direction, stopContinuousScroll, targetRef]);
useEffect(() => stopContinuousScroll, [stopContinuousScroll]);
return (
<button
type="button"
aria-label={label}
className="shrink-0 select-none px-1 text-lg leading-none text-foreground"
onPointerDown={(event) => {
if (event.button !== 0) return;
stopContinuousScroll();
pointerClickHandledRef.current = true;
event.currentTarget.setPointerCapture(event.pointerId);
scrollOnce();
holdTimerRef.current = setTimeout(
startContinuousScroll,
FLDNG_HSTRY_SCROLL_HOLD_DELAY,
);
}}
onPointerUp={stopContinuousScroll}
onPointerCancel={() => {
stopContinuousScroll();
pointerClickHandledRef.current = false;
}}
onLostPointerCapture={stopContinuousScroll}
onClick={() => {
if (pointerClickHandledRef.current) {
pointerClickHandledRef.current = false;
return;
}
scrollOnce();
}}
>
{children}
</button>
);
};
const cloneFldngHstryFeature = (feature: Feature) => {
const clonedFeature = feature.clone();
const featureId = feature.getId();
@@ -171,19 +262,13 @@ export const useFldngHstryFeatureClick = () => {
<span className="w-9 shrink-0 text-[1.1rem] font-semibold">
심선
</span>
<button
type="button"
aria-label="이전 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
fldngHstryDepthYearListRef.current?.scrollBy({
left: -120,
behavior: "smooth",
})
}
<FldngHstryScrollButton
targetRef={fldngHstryDepthYearListRef}
direction={-1}
label="이전 연도"
>
‹
</button>
</FldngHstryScrollButton>
<div
ref={fldngHstryDepthYearListRef}
className="flex min-w-0 flex-1 gap-2 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
@@ -231,19 +316,13 @@ export const useFldngHstryFeatureClick = () => {
);
})}
</div>
<button
type="button"
aria-label="다음 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
fldngHstryDepthYearListRef.current?.scrollBy({
left: 120,
behavior: "smooth",
})
}
<FldngHstryScrollButton
targetRef={fldngHstryDepthYearListRef}
direction={1}
label="다음 연도"
>
›
</button>
</FldngHstryScrollButton>
</div>
)}
{fldngHstryWtlvRows.length > 0 && (
@@ -251,19 +330,13 @@ export const useFldngHstryFeatureClick = () => {
<span className="w-9 shrink-0 text-[1.1rem] font-semibold">
위선
</span>
<button
type="button"
aria-label="이전 위선"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
fldngHstryWtlvListRef.current?.scrollBy({
left: -120,
behavior: "smooth",
})
}
<FldngHstryScrollButton
targetRef={fldngHstryWtlvListRef}
direction={-1}
label="이전 위선"
>
‹
</button>
</FldngHstryScrollButton>
<div
ref={fldngHstryWtlvListRef}
className="flex min-w-0 flex-1 gap-2 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
@@ -295,19 +368,13 @@ export const useFldngHstryFeatureClick = () => {
);
})}
</div>
<button
type="button"
aria-label="다음 위선"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
fldngHstryWtlvListRef.current?.scrollBy({
left: 120,
behavior: "smooth",
})
}
<FldngHstryScrollButton
targetRef={fldngHstryWtlvListRef}
direction={1}
label="다음 위선"
>
›
</button>
</FldngHstryScrollButton>
</div>
)}
</div>
@@ -292,9 +292,14 @@
cursor: pointer;
}
.zoom-level-panel .zoom-level-btn:first-child {
border-radius: 3px 3px 0 0;
}
.zoom-level-panel .zoom-level-btn:last-child {
border-top: 1px solid #eeeeee;
border-bottom: 0 none;
border-radius: 0 0 3px 3px;
}
.zoom-level-btn:hover {