Pagination
페이지 네비게이션을 표시하는 컴포넌트입니다.
개요
Pagination은 여러 페이지로 나뉜 콘텐츠를 탐색할 수 있도록 하는 네비게이션 컴포넌트입니다. Cals Pagination은 Cals 브랜드 스타일이 적용되어 있으며, 예약 목록 페이징과 고객 목록 페이징에 최적화되어 있습니다. Foundation Pagination의 모든 기능을 상속합니다.
주요 특징:
- 숫자 페이지 버튼
- 이전/다음 네비게이션
- Cals 브랜드 컬러 적용 (Pink, Blue, Purple)
- 현재 페이지 강조
- 페이지 점프 기능
- 반응형 디자인
사용 사례:
- Cals 예약 목록 페이징
- 고객 목록 페이징
- 서비스 목록 페이징
- 검색 결과 페이징
설치
npx @vortex/cli add pagination --package cals기본 사용법
import "@vortex/ui-cals/theme"; // Cals 테마 적용
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@vortex/ui-cals";
export default function Example() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Variants (변형)
Default
기본 페이지네이션 스타일입니다.
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>With Ellipsis (생략 표시)
페이지가 많을 때 생략 표시를 사용합니다.
import { PaginationEllipsis } from "@vortex/ui-cals";
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
5
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">10</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>;With Page Info
총 페이지 수와 현재 페이지 정보를 표시합니다.
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
총 <strong>120</strong>개 중 <strong>1-10</strong>개 표시
</p>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</div>With Page Size Selector
페이지당 항목 수를 선택할 수 있습니다.
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@vortex/ui-cals";
<div className="flex items-center justify-between">
<div className="flex items-center gap-sm">
<span className="text-sm text-muted-foreground">페이지당</span>
<Select defaultValue="10">
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
<span className="text-sm text-muted-foreground">개씩 보기</span>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</div>;Cals 브랜딩
브랜드 컬러
Cals Pagination은 다음 브랜드 컬러를 사용합니다:
- Primary (Pink):
#e91e63- 활성 페이지 강조 - Secondary (Blue):
#03a9f4- 호버 상태 - Accent (Purple):
#9c27b0- 특별 페이지 강조
Cals 특화 사용 가이드
예약 목록 페이징:
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis } from '@vortex/ui-cals'
import { Badge } from '@vortex/ui-cals'
const currentPage = 5
const totalPages = 10
const totalReservations = 120
<div className="space-y-md">
<div className="flex items-center justify-between">
<div className="flex items-center gap-sm">
<span className="text-sm text-muted-foreground">
총 <strong className="text-foreground">{totalReservations}</strong>개 예약
</span>
<Badge variant="confirmed" size="sm">확정 45개</Badge>
<Badge variant="pending" size="sm">대기 12개</Badge>
</div>
<span className="text-sm text-muted-foreground">
{(currentPage - 1) * 10 + 1}-{Math.min(currentPage * 10, totalReservations)}개 표시
</span>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
disabled={currentPage === 1}
/>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">{currentPage - 1}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>{currentPage}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">{currentPage + 1}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">{totalPages}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext
href="#"
disabled={currentPage === totalPages}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>고객 목록 페이징 (페이지 사이즈 선택 포함):
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@vortex/ui-cals";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@vortex/ui-cals";
<div className="space-y-md">
<div className="flex items-center justify-between">
<div className="flex items-center gap-sm">
<span className="text-sm text-muted-foreground">페이지당</span>
<Select defaultValue="10">
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
<span className="text-sm text-muted-foreground">명씩 보기</span>
</div>
<p className="text-sm text-muted-foreground">
총 <strong>350</strong>명 고객
</p>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</div>;Foundation/iCignal/Cals 비교
| 속성 | Foundation | iCignal | Cals |
|---|---|---|---|
| 활성 색상 | 중립 블루 | iCignal Blue (#2196f3) | Cals Pink (#e91e63) |
| 호버 색상 | 중립 회색 | iCignal Blue | Cals Blue (#03a9f4) |
| 브랜드 적용 | 없음 | iCignal Analytics | Cals 예약 시스템 |
| 사용 맥락 | 범용 | 데이터 분석 | 예약 관리 |
| Badge 통합 | ❌ | ❌ | ✅ 상태별 개수 표시 |
| 테마 | 중립적 테마 | iCignal 테마 | Cals 테마 (@vortex/ui-cals/theme) |
import 경로 차이:
// Foundation
import { Pagination } from "@vortex/ui-foundation";
// iCignal
import "@vortex/ui-icignal/theme";
import { Pagination } from "@vortex/ui-icignal";
// Cals
import "@vortex/ui-cals/theme"; // 테마 import 필수
import { Pagination } from "@vortex/ui-cals";Props API
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | 추가 CSS 클래스 |
| children | ReactNode | - | 페이지네이션 내용 |
PaginationLink Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| isActive | boolean | false | 활성 페이지 여부 |
| href | string | - | 링크 URL |
| onClick | () => void | - | 클릭 이벤트 핸들러 |
PaginationPrevious/Next Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| disabled | boolean | false | 비활성화 상태 |
| href | string | - | 링크 URL |
| onClick | () => void | - | 클릭 이벤트 핸들러 |
접근성
Pagination 컴포넌트는 WCAG 2.1 AA 기준을 준수합니다.
ARIA 속성:
aria-label="pagination"(네비게이션 역할 명시)aria-current="page"(현재 페이지 표시)aria-disabled(비활성 버튼)
키보드 네비게이션:
Tab: 페이지 버튼 간 이동Enter: 페이지 선택
스크린 리더 지원:
- 현재 페이지 명확하게 안내
- 총 페이지 수 안내
- 이전/다음 버튼 상태 안내
예제
예제 1: 예약 목록 페이징 ⭐
import "@vortex/ui-cals/theme";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from "@vortex/ui-cals";
import { Badge, Button } from "@vortex/ui-cals";
import { Card, CardHeader, CardContent, CardFooter } from "@vortex/ui-cals";
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@vortex/ui-cals";
import { Calendar, Download } from "lucide-react";
import { useState } from "react";
export default function ReservationListPagination() {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 12;
const totalReservations = 120;
const pageSize = 10;
const statusCounts = {
confirmed: 45,
pending: 12,
available: 30,
cancelled: 8,
completed: 25,
};
const reservations = [
{ id: "#001", customer: "홍길동", date: "2024-01-20", status: "confirmed" },
{ id: "#002", customer: "김영희", date: "2024-01-21", status: "pending" },
{ id: "#003", customer: "이철수", date: "2024-01-22", status: "available" },
// ... more reservations
];
const getPageNumbers = () => {
const pages = [];
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
if (currentPage <= 4) {
for (let i = 1; i <= 5; i++) pages.push(i);
pages.push("ellipsis");
pages.push(totalPages);
} else if (currentPage >= totalPages - 3) {
pages.push(1);
pages.push("ellipsis");
for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
} else {
pages.push(1);
pages.push("ellipsis");
for (let i = currentPage - 1; i <= currentPage + 1; i++) pages.push(i);
pages.push("ellipsis");
pages.push(totalPages);
}
}
return pages;
};
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-md">
<h2 className="text-xl font-bold">예약 목록</h2>
<div className="flex items-center gap-xs">
<Badge variant="outline">전체 {totalReservations}</Badge>
<Badge variant="confirmed" size="sm">
{statusCounts.confirmed}
</Badge>
<Badge variant="pending" size="sm">
{statusCounts.pending}
</Badge>
<Badge variant="available" size="sm">
{statusCounts.available}
</Badge>
</div>
</div>
<Button variant="outline">
<Download size={16} className="mr-xs" />
엑셀 다운로드
</Button>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>예약번호</TableHead>
<TableHead>고객명</TableHead>
<TableHead>예약일</TableHead>
<TableHead>상태</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reservations.map((reservation) => (
<TableRow
key={reservation.id}
className={`bg-${reservation.status}/10`}
>
<TableCell>{reservation.id}</TableCell>
<TableCell>{reservation.customer}</TableCell>
<TableCell>{reservation.date}</TableCell>
<TableCell>
<Badge variant={reservation.status}>
{reservation.status === "confirmed" && "예약 확정"}
{reservation.status === "pending" && "승인 대기"}
{reservation.status === "available" && "예약 가능"}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter>
<div className="w-full space-y-md">
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>
{(currentPage - 1) * pageSize + 1}-
{Math.min(currentPage * pageSize, totalReservations)}개 표시
</span>
<span>
총{" "}
<strong className="text-foreground">{totalReservations}</strong>개
예약
</span>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
/>
</PaginationItem>
{getPageNumbers().map((page, index) =>
page === "ellipsis" ? (
<PaginationItem key={`ellipsis-${index}`}>
<PaginationEllipsis />
</PaginationItem>
) : (
<PaginationItem key={page}>
<PaginationLink
onClick={() => setCurrentPage(page)}
isActive={currentPage === page}
>
{page}
</PaginationLink>
</PaginationItem>
)
)}
<PaginationItem>
<PaginationNext
onClick={() =>
setCurrentPage((p) => Math.min(totalPages, p + 1))
}
disabled={currentPage === totalPages}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
</CardFooter>
</Card>
);
}예제 2: 고객 목록 페이징 (페이지 사이즈 선택) ⭐
import "@vortex/ui-cals/theme";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@vortex/ui-cals";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@vortex/ui-cals";
import { useState } from "react";
export default function CustomerListPagination() {
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const totalCustomers = 350;
const totalPages = Math.ceil(totalCustomers / pageSize);
return (
<div className="space-y-md">
<div className="flex items-center justify-between">
<div className="flex items-center gap-sm">
<span className="text-sm text-muted-foreground">페이지당</span>
<Select
value={pageSize.toString()}
onValueChange={(v) => setPageSize(Number(v))}
>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
<span className="text-sm text-muted-foreground">명씩 보기</span>
</div>
<div className="text-sm text-muted-foreground">
총 <strong className="text-foreground">{totalCustomers}</strong>명
고객 |{" "}
<strong className="text-foreground">
{(currentPage - 1) * pageSize + 1}
</strong>
-
<strong className="text-foreground">
{Math.min(currentPage * pageSize, totalCustomers)}
</strong>{" "}
표시
</div>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
/>
</PaginationItem>
{[...Array(Math.min(5, totalPages))].map((_, i) => {
const pageNumber = i + 1;
return (
<PaginationItem key={pageNumber}>
<PaginationLink
onClick={() => setCurrentPage(pageNumber)}
isActive={currentPage === pageNumber}
>
{pageNumber}
</PaginationLink>
</PaginationItem>
);
})}
<PaginationItem>
<PaginationNext
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}관련 컴포넌트
- Foundation Pagination - 기본 버전 참조
- iCignal Pagination - iCignal 버전 참조
- Table - 페이징 대상 테이블
- Button - 페이지 버튼
- Select - 페이지 사이즈 선택
- Badge - 상태별 개수 표시
Last updated on