Skip to content

feat: (JDS) Pagination 컴포넌트 구현 - #700

Open
areumH wants to merge 28 commits into
devfrom
feat/693-jds-pagination
Open

areumH wants to merge 28 commits into
devfrom
feat/693-jds-pagination

Conversation

@areumH

@areumH areumH commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

💡 작업 내용

  • Pagination 컴포넌트와 공개 타입 추가
  • 제어, 비제어 버튼 방식과 href 기반 링크 방식 지원
  • visiblePageCount에 따른 페이지 표시 범위와 말줄임 계산 구현
  • Storybook, Changeset 문서 작성

💡 자세한 설명

페이지 표시 및 구조

nav > ul > li 구조로 페이지 번호와 말줄임, 이전/다음 이동 요소를 렌더링합니다. visiblePageCount는 이전/다음 이동 요소를 제외하고 말줄임을 포함해 표시할 최대 항목 수이며, 7부터 11까지 지정할 수 있습니다.

현재 선택된 페이지에는 aria-current="page"를 적용합니다. 이전과 다음 이동 요소에는 각각 접근 가능한 이름을 제공하며, 말줄임은 스크린 리더에서 생략됨으로 안내합니다. totalPages가 0 이하이면 컴포넌트를 렌더링하지 않습니다.

내부 렌더링에만 사용하는 PaginationItem, PaginationArrow의 props 타입은 타입 선언 규약에 따라 Pagination.tsx 파일 내부에 배치했습니다.


페이지 표시 범위 계산

페이지 값 보정과 표시 범위 계산은 pagination.utils.ts의 순수 함수로 분리했습니다.

  • normalizePaginationValues : totalPages를 0 이상의 정수로 보정하고 현재 페이지를 1부터 전체 페이지 수 사이로 제한
  • getPaginationWindow : 현재 페이지와 이전 숫자 구간을 기준으로 이번에 표시할 연속된 페이지 번호의 시작과 끝을 계산
  • getPaginationRange : 계산된 숫자 구간에 첫 페이지, 마지막 페이지와 시작 또는 끝 말줄임을 조합
  • createPageRange : 연속된 페이지 번호 배열을 생성하는 내부 보조 함수

usePaginationRange.ts는 이전에 표시한 숫자 구간을 기억하고 유틸 함수의 계산 결과를 컴포넌트에 제공합니다. 표시된 구간의 안쪽 페이지를 선택하면 기존 구간을 유지하고, 양끝 페이지를 선택하면 해당 방향으로 구간을 이동합니다. 양쪽 말줄임이 필요한 가운데 구간을 처음 계산할 때 홀수 표시 개수는 현재 페이지를 중앙에 배치하고, 짝수 표시 개수는 현재 페이지 오른쪽에 번호를 하나 더 배치합니다. 말줄임은 두 페이지 이상이 생략될 때만 표시합니다.


버튼 방식의 상태 관리

버튼 방식은 page를 전달하는 제어 방식과 defaultPage를 사용하는 비제어 방식을 지원합니다. 비제어 방식은 defaultPage를 초기값으로 사용하고, 제어 방식은 소비처가 page를 관리합니다. onPageChange는 페이지 이동 요소를 클릭했을 때 호출됩니다. page나 totalPages의 유효 범위 보정만으로는 호출하지 않습니다.

import { useState } from "react";
import { Pagination } from "@jects/jds";

// 비제어 방식
function UncontrolledPagination() {
  return <Pagination aria-label="페이지 이동" defaultPage={1} totalPages={10} />;
}

// 제어 방식
function ControlledPagination() {
  const [page, setPage] = useState(1);

  return (
    <Pagination
      aria-label="페이지 이동"
      page={page}
      totalPages={10}
      onPageChange={setPage}
    />
  );
}

링크 방식의 상태 관리

링크 방식은 getPageHref로 목적지 URL을 만들고, linkAs로 href를 받는 라우팅 컴포넌트를 지정합니다. 현재 페이지를 URL과 동기화하는 작업은 소비처가 담당합니다.

Next.js App Router에서는 JDS가 Next.js에 직접 의존하지 않도록 소비처의 Client Component에서 NextLink를 linkAs로 전달할 수 있습니다.

"use client";

import NextLink from "next/link";
import { Pagination } from "@jects/jds";

interface NextPaginationProps {
  page: number;
  totalPages: number;
}

export function NextPagination({ page, totalPages }: NextPaginationProps) {
  return (
    <Pagination
      aria-label="페이지 이동"
      page={page}
      totalPages={totalPages}
      getPageHref={targetPage => `?page=${targetPage}`}
      linkAs={NextLink}
    />
  );
}

첫 페이지에서는 이전 페이지 이동 요소를, 마지막 페이지에서는 다음 페이지 이동 요소를 비활성화합니다. 버튼 방식은 네이티브 disabled를 사용하고, 링크 방식은 href 제거와 aria-disabled, tabIndex={-1} 적용으로 이동과 키보드 포커스를 막습니다. disabled prop을 전달하면 모든 페이지 이동 요소에 같은 비활성 상태를 적용합니다.

📗 참고 자료 (선택)

📢 리뷰 요구 사항 (선택)

✅ 셀프 체크리스트

  • 머지할 브랜치 확인했나요?
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 기능이 잘 동작하나요?
  • 불필요한 코드는 제거했나요?

closes #693

- defaultPage를 초기값으로 사용하는 내부 페이지 상태 제공
- page와 onPageChange를 사용하는 기존 제어 방식 유지
- aria-label과 aria-labelledby를 기본값 없이 소비처 값 그대로 전달
- linkAs로 페이지와 화살표 링크를 교체하고 비활성 링크는 네이티브 앵커로 안전하게 렌더링
- 이전 방향 여부를 파생 변수로 분리해 반복 조건을 단순화
- 페이지 범위 생성 함수와 경계 범수 이름을 역할에 맞게 명확화
- 페이지 값 타입을 공통화하고 범위 계산 옵션 타입으로 확장
- 문제: 전체 페이지 수가 줄어들면 비제어 페이지 보정 과정에서 onPageChange가 자동 호출됨
- 수정: 렌더링 시 유효 페이지를 표시하되 내부 상태 동기화 effect는 제거
- JDS 신규 Pagination과 공개 타입을 minor 릴리스로 기록
- 버튼과 링크 방식의 주요 props 및 사용 예시 정리
- visiblePageCount에 8과 10을 추가하고 Storybook 표시 개수 예시에 반영
- 이전 숫자 구간을 전용 훅에서 관리해 안쪽 선택은 유지하고 양끝 선택은 이동
- 홀수의 초기 양끝 배치를 유지하면서 페이지 수가 0일 때 구간을 초기화
- 표시 개수를 7, 8, 9, 10, 11로 맞추고 구간 유지와 초기 배치 동작 설명
- 제어, 비제어, 링크 사용 예시와 URL 기반 페이지 전달 방식 정리
- usePaginationRange가 숫자 구간 상태와 최종 표시 목록 생성을 함께 담당
- Pagination 컴포넌트에서 내부 window 의존성을 제거하고 렌더링 책임만 유지
- getPaginationRange를 결정된 숫자 구간의 순수 변환 함수로 정리
- 홀수와 짝수 모두 최소 두 페이지를 생략할 때만 말줄임 표시
- 한 페이지만 생략되는 경계에서는 숫자 구간을 가장자리까지 연속 표시
- 홀수 표시 개수의 양끝 전용 초기 배치 예외 제거
- 말줄임이 두 페이지 이상 숨기는 공통 기준으로 표시 범위 결정
- 홀짝 초기 배치와 말줄임 표시 기준 명시
- 내부 상태 설명을 제거하고 소비자 관점의 구간 이동 동작으로 정리
- 공개 props 구성 타입만 pagination.types.ts에 유지
- 하위 렌더링 전용 타입을 Pagination.tsx에 배치
@areumH
areumH requested review from D5ng and ccconac September 16, 2026 16:01
@areumH areumH self-assigned this Sep 16, 2026
@areumH areumH added the ✨feature 구현, 개선 사항 관련 부분 label Sep 16, 2026
@vercel

vercel Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ject-official-web-site-client-web Ready Ready Preview Sep 23, 2026 6:35am UTC

@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 4b687d67-57d6-4a4b-ae34-1ccc3f0be96f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@areumH areumH added the vrt-baseline (⚠️ PR 생성 이후 사용, PR 생성 시 워크플로와 충돌 가능) BackstopJS baseline 재생성 트리거 label Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🖼️ VRT baseline 재생성 완료

  • 삭제된 스토리의 스냅샷을 정리하고 현재 스토리 기준으로 baseline을 새로 커밋했습니다.
  • 워크플로: 실행 내역 보기

@github-actions github-actions Bot removed the vrt-baseline (⚠️ PR 생성 이후 사용, PR 생성 시 워크플로와 충돌 가능) BackstopJS baseline 재생성 트리거 label Sep 16, 2026
@areumH areumH added the alpha-storybook 스토리북 알파 배포 트리거 label Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🎨 스토리북 알파 배포 완료

📦 배포 주소

ℹ️ 정보

@github-actions github-actions Bot removed the alpha-storybook 스토리북 알파 배포 트리거 label Sep 17, 2026

@D5ng D5ng left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

미리 승인 해두겠습니다~! 고생하셨어요, 검토해보고 생각나는대로 리뷰는 추가해둘게요

Comment on lines +53 to +54
const edgeRangePageCount = visiblePageCount - 2;
const middlePageCount = visiblePageCount - 4;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visiblePageCount - 2, visiblePageCount - 4에서 2와 4가 어떤 렌더링 항목을 의미하는지 코드를 읽는 시점에서 바로 떠오르지 않는것 같아요..!

  • 가장자리 구간은 연속 페이지 외에 말줄임표 1개 + 첫 페이지 또는 마지막 페이지 1개를 표시하므로 2개
  • 중앙 구간은 연속 페이지 외에 첫 페이지 + 양쪽 말줄임표 2개 + 마지막 페이지를 표시하므로 4개

이 부분을 주석으로 표현하거나, 상수로 사용하면 어떨까요?

@areumH areumH Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15614c7 개선했습니다!

This branch was successfully deployed

1 active deployment
Preview — 81f09a65 Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature 구현, 개선 사항 관련 부분

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: (JDS) Pagination 컴포넌트 구현

2 participants