Schedule chips: horizontal scroll with edge arrows (PC navigable)
Replace hidden-scrollbar row (inaccessible on desktop without touch) with a ChipScroller: single-row scroll + left/right arrow buttons that auto-hide at each end and a bg fade. Resets to start on tab switch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>develop
parent
955ffae4cf
commit
3bcc47c0e8
|
|
@ -1,4 +1,4 @@
|
|||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { timeOnly, dateKey } from "@/lib/format";
|
||||
import { type Lang, dict, teamShort, roundLabel as tRound, dayName, monthEn } from "@/lib/i18n";
|
||||
|
|
@ -90,8 +90,8 @@ export default function ScheduleBoard({
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* 칩: 날짜 또는 조 */}
|
||||
<div className="mb-4 flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
{/* 칩: 날짜 또는 조 — 단일 줄 가로 스크롤 + 양 끝 화살표(PC에서 마우스로 넘김, 끝 도달 시 숨김) */}
|
||||
<ChipScroller resetKey={tab}>
|
||||
{tab === "date"
|
||||
? dates.map((d) => (
|
||||
<Chip key={d} active={d === selDate} onClick={() => setSelDate(d)}>
|
||||
|
|
@ -103,7 +103,7 @@ export default function ScheduleBoard({
|
|||
{lang === "en" ? `Group ${g}` : `${g}조`}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</ChipScroller>
|
||||
|
||||
{/* 경기 카드 */}
|
||||
<div className="flex flex-col gap-2.5">
|
||||
|
|
@ -120,6 +120,86 @@ export default function ScheduleBoard({
|
|||
);
|
||||
}
|
||||
|
||||
// 단일 줄 가로 스크롤 + 양 끝 화살표. 끝에 닿으면 해당 방향 화살표를 숨긴다.
|
||||
// resetKey 가 바뀌면(탭 전환 등) 맨 앞으로 되감고 재측정한다.
|
||||
function ChipScroller({
|
||||
children,
|
||||
resetKey,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
resetKey?: string | number;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [atStart, setAtStart] = useState(true);
|
||||
const [atEnd, setAtEnd] = useState(true);
|
||||
|
||||
const measure = () => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
const max = el.scrollWidth - el.clientWidth;
|
||||
setAtStart(el.scrollLeft <= 1);
|
||||
setAtEnd(el.scrollLeft >= max - 1);
|
||||
};
|
||||
|
||||
// 탭/목록 변경 시 맨 앞으로 + 재측정 (레이아웃 확정 후)
|
||||
useLayoutEffect(() => {
|
||||
const el = ref.current;
|
||||
if (el) el.scrollLeft = 0;
|
||||
measure();
|
||||
}, [resetKey]);
|
||||
|
||||
useEffect(() => {
|
||||
measure();
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
el.addEventListener("scroll", measure, { passive: true });
|
||||
window.addEventListener("resize", measure);
|
||||
return () => {
|
||||
el.removeEventListener("scroll", measure);
|
||||
window.removeEventListener("resize", measure);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const nudge = (dir: 1 | -1) =>
|
||||
ref.current?.scrollBy({ left: dir * 220, behavior: "smooth" });
|
||||
|
||||
return (
|
||||
<div className="relative mb-4">
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{!atStart && (
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pr-8 bg-gradient-to-r from-[var(--bg)] via-[var(--bg)] to-transparent">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="이전"
|
||||
onClick={() => nudge(-1)}
|
||||
className="pointer-events-auto grid h-7 w-7 place-items-center rounded-full border border-[var(--line-d)] bg-[var(--bg2)] text-[15px] leading-none text-white/80 shadow transition hover:text-white active:scale-95"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!atEnd && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center justify-end pl-8 bg-gradient-to-l from-[var(--bg)] via-[var(--bg)] to-transparent">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="다음"
|
||||
onClick={() => nudge(1)}
|
||||
className="pointer-events-auto grid h-7 w-7 place-items-center rounded-full border border-[var(--line-d)] bg-[var(--bg2)] text-[15px] leading-none text-white/80 shadow transition hover:text-white active:scale-95"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Chip({
|
||||
active,
|
||||
onClick,
|
||||
|
|
|
|||
Loading…
Reference in New Issue