| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- 'use client';
- import { useCallback, useEffect, useRef } from 'react';
- import Link from 'next/link';
- import { Trophy, Users, Heart, TrendingUp, Crown, ChevronLeft, ChevronRight } from 'lucide-react';
- import useDragScroll from '@/hooks/useDragScroll';
- type RankingTabKey = 'comprehensive'|'creator'|'donor'|'rising'|'hall-of-fame';
- interface Props {
- active: RankingTabKey;
- }
- const SCROLL_AMOUNT = 120;
- const TABS: { key: RankingTabKey; href: string; label: string; icon: React.ElementType }[] = [
- { key: 'hall-of-fame', href: '/ranking/hall-of-fame', label: '명예의 전당', icon: Crown },
- { key: 'comprehensive', href: '/ranking', label: '종합', icon: Trophy },
- { key: 'creator', href: '/ranking/creator', label: '크리에이터', icon: Users },
- { key: 'donor', href: '/ranking/donor', label: '후원자', icon: Heart },
- { key: 'rising', href: '/ranking/rising', label: '급상승', icon: TrendingUp }
- ];
- export default function RankingTabs({ active }: Props) {
- const dragScroll = useDragScroll<HTMLElement>();
- const activeItemRef = useRef<HTMLAnchorElement>(null);
- // 활성 탭(active) 변경 시 뷰포트 중앙으로 스크롤해 모바일에서 현재 위치 가시화
- useEffect(() => {
- const target = activeItemRef.current;
- if (!target) {
- return;
- }
- const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
- target.scrollIntoView({
- behavior: prefersReduced ? 'auto' : 'smooth',
- inline: 'center',
- block: 'nearest'
- });
- }, [active]);
- const scroll = useCallback((dir: 'left'|'right') => {
- const el = dragScroll.ref.current;
- if (!el) {
- return;
- }
- const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
- el.scrollBy({
- left: dir === 'left' ? -SCROLL_AMOUNT : SCROLL_AMOUNT,
- behavior: prefersReduced ? 'auto' : 'smooth'
- });
- }, [dragScroll.ref]);
- return (
- <div className="ranking__tabs-wrap">
- <button
- type="button"
- className="ranking__tabs-arrow ranking__tabs-arrow--left"
- onClick={() => scroll('left')}
- aria-label="이전 탭으로 스크롤"
- >
- <ChevronLeft size={18} />
- </button>
- <nav
- className="ranking__tabs"
- aria-label="순위 탭"
- ref={dragScroll.ref}
- onMouseDown={dragScroll.onMouseDown}
- onMouseMove={dragScroll.onMouseMove}
- onMouseUp={dragScroll.onMouseUp}
- onMouseLeave={dragScroll.onMouseLeave}
- >
- {TABS.map((tab) => {
- const Icon = tab.icon;
- const isActive = tab.key === active;
- return (
- <Link
- key={tab.key}
- href={tab.href}
- className={`ranking__tabs-item ${isActive ? 'ranking__tabs-item--active' : ''}`}
- aria-current={isActive ? 'page' : undefined}
- ref={isActive ? activeItemRef : undefined}
- draggable={false}
- >
- <Icon size={18} />
- <span>{tab.label}</span>
- </Link>
- );
- })}
- </nav>
- <button
- type="button"
- className="ranking__tabs-arrow ranking__tabs-arrow--right"
- onClick={() => scroll('right')}
- aria-label="다음 탭으로 스크롤"
- >
- <ChevronRight size={18} />
- </button>
- </div>
- );
- }
|