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
hbyang 2026-06-12 17:00:24 +09:00
parent 955ffae4cf
commit 3bcc47c0e8
1 changed files with 84 additions and 4 deletions

View File

@ -1,4 +1,4 @@
import { useMemo, useState } from "react"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { timeOnly, dateKey } from "@/lib/format"; import { timeOnly, dateKey } from "@/lib/format";
import { type Lang, dict, teamShort, roundLabel as tRound, dayName, monthEn } from "@/lib/i18n"; import { type Lang, dict, teamShort, roundLabel as tRound, dayName, monthEn } from "@/lib/i18n";
@ -90,8 +90,8 @@ export default function ScheduleBoard({
))} ))}
</div> </div>
{/* 칩: 날짜 또는 조 */} {/* 칩: 날짜 또는 조 — 단일 줄 가로 스크롤 + 양 끝 화살표(PC에서 마우스로 넘김, 끝 도달 시 숨김) */}
<div className="mb-4 flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> <ChipScroller resetKey={tab}>
{tab === "date" {tab === "date"
? dates.map((d) => ( ? dates.map((d) => (
<Chip key={d} active={d === selDate} onClick={() => setSelDate(d)}> <Chip key={d} active={d === selDate} onClick={() => setSelDate(d)}>
@ -103,7 +103,7 @@ export default function ScheduleBoard({
{lang === "en" ? `Group ${g}` : `${g}`} {lang === "en" ? `Group ${g}` : `${g}`}
</Chip> </Chip>
))} ))}
</div> </ChipScroller>
{/* 경기 카드 */} {/* 경기 카드 */}
<div className="flex flex-col gap-2.5"> <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({ function Chip({
active, active,
onClick, onClick,