Pagination | CoUI

Pagination

대용량 콘텐츠를 여러 페이지로 나누어 탐색하는 페이지네이션 컴포넌트

Pagination#

대용량 콘텐츠를 페이지 단위로 나누어 탐색할 수 있는 컴포넌트입니다. 현재 페이지 표시, 이전/다음 이동, 첫/마지막 페이지 이동을 지원합니다.

Live Preview#

Web
Flutter
Loading Flutter...
Pagination(
  page: 1,
  totalPages: 10,
  onPageChanged: handlePageChange,
)
Pagination(
  page: 1,
  totalPages: 10,
  onPageChanged: handlePageChange,
)

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 테이블, 검색 결과, 상품 목록처럼 많은 데이터를 페이지 단위로 나눠 표시할 때
  • 사용자가 특정 페이지로 직접 이동할 수 있어야 할 때
  • 전체 데이터 양이 많지 않아 페이지 번호로 탐색이 유용한 경우

대신 다른 컴포넌트를 사용하세요:

  • RefreshTrigger: 무한 스크롤 방식으로 데이터를 로드할 때
  • Select: 페이지 크기 선택 등 드롭다운 옵션이 필요할 때

기본 사용법 (Basic Usage)#

// 기본 페이지네이션
Pagination(
  totalPages: 10,
  page: _currentPage,
  onPageChanged: handlePageChanged,
)

// 첫/마지막 페이지 버튼 포함
Pagination(
  totalPages: 20,
  page: _currentPage,
  onPageChanged: handlePageChanged,
  maxPages: 7,
  showSkipToFirstPage: true,
  showSkipToLastPage: true,
)

// 첫 페이지에서 이전 버튼 숨김
Pagination(
  totalPages: 5,
  page: _currentPage,
  onPageChanged: handlePageChanged,
  hidePreviousOnFirstPage: true,
)
// 기본 페이지네이션 (이전/다음 버튼 포함)
Pagination(
  page: currentPage,
  totalPages: 10,
  onPageChanged: handlePageChanged,
)

// 형제 페이지 — 7페이지 이하는 전체 표시, 초과 시 생략 처리
Pagination(
  page: currentPage,
  totalPages: 20,
  onPageChanged: handlePageChanged,
)

// 이전/다음 버튼과 함께 현재 페이지 강조
Pagination(
  page: 5,
  totalPages: 15,
  onPageChanged: handlePageChanged,
)

Props / Parameters#

속성타입기본값설명
totalPagesint필수전체 페이지 수
pageint필수현재 페이지 (1-based)
onPageChanged void Function(int) 필수 페이지 변경 핸들러
maxPages int 7 표시할 최대 페이지 버튼 수
showSkipToFirstPage bool true 첫 페이지 이동 버튼 표시 여부
showSkipToLastPage bool true 마지막 페이지 이동 버튼 표시 여부
hidePreviousOnFirstPage bool false 첫 페이지에서 이전 버튼 숨김
hideNextOnLastPage bool false 마지막 페이지에서 다음 버튼 숨김
paginationStyle CorePaginationStyle<Color>? / CorePaginationStyle<CoreColor>? null 인스턴스 스타일 (Style 시스템 참조)

스타일 시스템 (Style System)#

Pagination 의 모든 chrome / dimensional / nested-slot 오버라이드는 CorePaginationStyle<Clr> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 시맨틱 / behaviour 필드 (page / totalPages / onPageChanged / maxPages / showSkipToFirstPage / showSkipToLastPage / hidePreviousOnFirstPage / hideNextOnLastPage / showLabel) 는 위젯/컴포넌트 파라미터로 직접 전달합니다.

시맨틱 vs 스타일#

  • 시맨틱 / behaviour: 위젯/컴포넌트 파라미터로 직접 (page, totalPages, onPageChanged, maxPages, showSkipToFirstPage, showSkipToLastPage, hidePreviousOnFirstPage, hideNextOnLastPage, showLabel)
  • chrome / dimensional / 슬롯 스타일: CorePaginationStyle 한 곳으로 (gap / pageButtonStyle / prevNextButtonStyle / ellipsisStyle)

Resolve chain#

design system default for pagination
  → CorePaginationTheme.style                  // 프로젝트 공통
  → parent component slot override
  → widget.paginationStyle                     // 인스턴스별

각 nested 슬롯 스타일 (pageButtonStyle / prevNextButtonStyle / ellipsisStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.

슬롯 매핑 (CorePaginationStyle 4 필드)#

필드타입 (Flutter)적용 영역
gapdouble?페이지네이션 버튼 간격 (px)
pageButtonStyle CoreButtonStyle<Color>? 페이지 번호 버튼 chrome (현재 구현은 GhostButton / PrimaryButton ; CoButton(variant: ghost) asChild 통합은 #1322 이월)
prevNextButtonStyle CoreButtonStyle<Color>? 이전/다음 버튼 chrome (위와 동일)
ellipsisStyle CoreTextStyle<Color>? 생략 표시 () 텍스트 스타일

Migration#

기존 PaginationTheme 의 평면 chrome (gap / showLabel) 은 호환을 위해 유지되지만, 새 코드는 paginationStyle 슬롯을 사용하세요:

기존 (legacy theme 필드)새 위치
PaginationTheme.gap paginationStyle.gap 또는 CorePaginationTheme.style.gap
인스턴스 페이지 버튼 색/패딩 (불가능)paginationStyle.pageButtonStyle
인스턴스 prev/next 버튼 색/패딩 (불가능)paginationStyle.prevNextButtonStyle
인스턴스 생략 표시 텍스트 스타일 (불가능)paginationStyle.ellipsisStyle

사용 예 (Flutter)#

Pagination(
  page: 1,
  totalPages: 10,
  onPageChanged: handlePageChanged,
  paginationStyle: CorePaginationStyle(
    gap: 4,
    ellipsisStyle: CoreTextStyle(fontSize: 14, color: Colors.grey),
  ),
)

사용 예 (Web)#

Pagination(
  page: 1,
  totalPages: 10,
  onPageChanged: (p) => setState(() => _page = p),
  paginationStyle: CorePaginationStyle<CoreColor>(
    gap: 8,
  ),
)

변형 (Variants)#

기본 (Default)#

숫자 버튼과 이전/다음 버튼을 표시합니다.

Pagination(
  totalPages: 10,
  page: 3,
  onPageChanged: handlePageChanged,
)

첫/마지막 포함#

첫 페이지와 마지막 페이지로 바로 이동하는 버튼을 추가합니다.

Pagination(
  totalPages: 10,
  page: 3,
  onPageChanged: handlePageChanged,
  showSkipToFirstPage: true,
  showSkipToLastPage: true,
)

넓은 범위 (Wide Pages)#

현재 페이지 주변에 더 많은 페이지 번호를 표시합니다.

Pagination(
  totalPages: 10,
  page: 5,
  onPageChanged: handlePageChanged,
  maxPages: 9,
)

동작 스펙 (Behavior)#

인터랙션#

  • 페이지 버튼 클릭: 해당 페이지로 이동 (onPageChanged(page) 호출)
  • 이전/다음 버튼: 현재 페이지에서 1씩 감소/증가
  • 첫/마지막 버튼: 1페이지 또는 totalPages로 이동
  • 현재 페이지 버튼: 클릭해도 이벤트 없음 (비활성 강조)
  • 경계 버튼: 첫 페이지에서 이전 버튼, 마지막 페이지에서 다음 버튼은 비활성

상태 전환#

  • 현재 페이지 버튼: 강조 색상 + 비활성 클릭
  • 경계 도달 시 이전/다음 버튼 disabled 처리
  • 페이지 수가 많으면 현재 페이지 기준으로 중간 항목 ...으로 생략

애니메이션#

  • 페이지 전환 시 별도 애니메이션 없음 (콘텐츠 영역 전환은 부모가 처리)
  • 버튼 호버 시 배경색 200ms 전환

사용 가이드라인 (Usage Guidelines)#

✅ Do#

많은 데이터에는 첫/마지막 버튼도 함께 표시

CouiPagination(
  totalPages: 50,
  currentPage: _currentPage,
  onPageChanged: handlePageChanged,
  showFirstLast: true,
)

50페이지 이상의 데이터에서 첫/마지막 버튼은 멀리 있는 페이지로의 빠른 이동을 지원한다.


❌ Don't#

페이지가 2개 이하일 때 Pagination 표시

// ❌ 페이지가 1~2개뿐인데 Pagination 표시
CouiPagination(
  totalPages: 1,
  currentPage: 1,
  onPageChanged: handlePageChanged,
)

페이지가 1개뿐이면 Pagination이 의미 없고 오히려 혼란을 준다. totalPages > 1인 경우에만 표시한다.

✅ Do#

페이지 크기 선택과 함께 사용해 사용자 편의 제공

Row(
  children: [
    CouiSelect(
      label: '페이지당 항목',
      value: pageSize.toString(),
      onChanged: handlePageSizeChanged,
      items: ['10', '25', '50', '100'],
    ),
    const Spacer(),
    CouiPagination(
      totalPages: (totalItems / pageSize).ceil(),
      currentPage: _currentPage,
      onPageChanged: handlePageChanged,
    ),
  ],
)

페이지 크기 옵션을 함께 제공하면 사용자가 데이터 양을 스스로 제어할 수 있다.


❌ Don't#

siblingCount를 너무 크게 설정하지 않기

// ❌ 버튼이 너무 많아 화면을 넘칠 수 있음
CouiPagination(
  totalPages: 20,
  currentPage: 10,
  onPageChanged: handlePageChanged,
  siblingCount: 5, // 너무 많은 버튼
)

siblingCount가 크면 버튼이 너무 많아 모바일 화면에서 줄바꿈이 발생한다. 1~2가 적당하다.

✅ Do#

현재 페이지와 전체 페이지 수를 함께 표시하세요.

CouiPagination(
  currentPage: currentPage,
  totalPages: totalPages,
  onPageChanged: handlePageChanged,
  showFirstLast: true,
  showPageInfo: true,  // '3 / 20' 형태로 표시
)

현재 위치와 전체 범위를 알면 사용자가 탐색 진행 상황을 파악하고 원하는 페이지로 빠르게 이동할 수 있습니다.


❌ Don't#

데이터가 적을 때 불필요하게 페이지네이션을 표시하지 마세요.

// ❌ 항목이 5개뿐인데 페이지네이션 표시
CouiPagination(
  currentPage: 1,
  totalPages: 1,  // 한 페이지에 다 들어오는데도 표시
  onPageChanged: handlePageChanged,
)

단일 페이지라면 페이지네이션은 불필요한 UI 요소입니다. 총 항목 수가 페이지당 개수보다 적으면 숨기세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Tab다음 페이지 버튼으로 포커스
Enter / Space포커스된 페이지 버튼 활성화
Arrow Left/Right이전/다음 페이지 버튼으로 포커스 이동

스크린 리더#

  • Flutter: Semantics(label: '페이지 3, 전체 10페이지') 형태로 현재/전체 페이지 전달
  • Web: role="navigation" + aria-label="페이지 탐색" + 현재 페이지에 aria-current="page" 적용

터치 타겟#

  • 각 페이지 버튼 최소 크기: 44x44dp

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CouiPaginationPagination
페이지 기준1-based1-based
버튼 스타일CouiButton 기반<button> 기반
생략 표시 ... 텍스트 <span aria-hidden>...</span>
  • Table: 페이지네이션이 필요한 대용량 테이블 데이터에 사용
  • Select: 페이지 크기 선택 드롭다운과 조합

조합 예제#

// 데이터 테이블 + 페이지네이션
Column(
  children: [
    CouiTable(
      columns: tableColumns,
      rows: pagedData,
    ),
    const Gap.md(),
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text('총 ${totalItems}개 항목'),
        CouiPagination(
          totalPages: totalPages,
          currentPage: _currentPage,
          onPageChanged: handlePageChanged,
          showFirstLast: true,
        ),
      ],
    ),
  ],
)