103 lines
6.9 KiB
Markdown
103 lines
6.9 KiB
Markdown
# Pagination
|
||
|
||
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Pagination 고유의 API·Token·상태·결정만 기록한다.
|
||
|
||
## 1. 상태
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 구현 상태 | 구현 완료 (현재 HM 규격) |
|
||
| 기준일 | 2026-10-02 |
|
||
| 기반 | shadcn/ui Pagination (`nav > ul > li > a`, Radix 없음) |
|
||
| 대표 Story | `Components/Navigation/Pagination` → `All` (이름 "Pagination") |
|
||
| 보류 | 아래 §11 |
|
||
|
||
## 2. 적용 근거
|
||
|
||
- **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Pagination이 컴포넌트 이름으로만 있고 규격은 없다. 참고할 수치가 없었다.
|
||
- **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic 체계(fg-primary, action-primary, accent)로 새로 정했다.
|
||
- **구현 방식**: shadcn Pagination 구조 유지. Presentational / navigation primitive로, page state와 href는 소비자가 관리한다. 링크는 `a`로 유지하고 Button으로 바꾸지 않는다.
|
||
- **Button과의 관계**: shadcn은 `buttonVariants`를 입혀 Button처럼 그리지만, HM Button은 `data-slot="button"` 기반이라 이를 그대로 쓰면 HM 스타일이 적용되지 않고 Button token도 재사용하면 안 된다. 그래서 `PaginationLink`는 독립 anchor 스타일(`--hm-pagination-*`)로 구현했다.
|
||
- **보류**: §11.
|
||
|
||
## 3. 기본 규격 (현재 HM 규격)
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| Page link | 34 × 34px(min-width 34), radius 4px(`--hm-radius-control-md`), 14 / 20 / 500, `--hm-sem-fg-primary` |
|
||
| Previous / Next | 높이 34px, padding `0 12px`, 아이콘 16px, 라벨 `hidden sm:block`("Previous"/"Next", 모바일은 아이콘만) |
|
||
| 간격 | 목록 gap 4px |
|
||
| Current Page | 배경 `--hm-sem-action-primary`(Primary Green `#123328`), 글자 `--hm-sem-on-action-primary`(흰색), weight 700 |
|
||
| Ellipsis | 34 × 34 영역, 아이콘 16px, `--hm-sem-fg-secondary` |
|
||
|
||
## 4. 상태 규격
|
||
|
||
| 상태 | 값 |
|
||
|---|---|
|
||
| Hover | 배경 `--accent` 45%, 글자 `--accent-foreground` |
|
||
| Focus (`:focus-visible`) | ring 모양 `0 0 0 3px ring 50%` |
|
||
| Current (`isActive`) | `aria-current="page"`, 위 Current 스타일(hover에도 `Current`로 보이도록 hover 배경 대신 유지) |
|
||
| Disabled (`aria-disabled="true"`) | `opacity: 0.6`(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), `cursor: not-allowed`, hover 면 없음, `tabIndex -1`, 클릭 취소(`preventDefault`) |
|
||
|
||
## 5. API
|
||
|
||
export: `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink`, `PaginationPrevious`, `PaginationNext`, `PaginationEllipsis`.
|
||
|
||
- `PaginationLink`: `a` props + `isActive`(현재 페이지), `size` `"default" | "icon"`(기본 `icon`; shadcn은 Button size, HM은 `default`/`icon` 둘만). 출력 `data-slot="pagination-link"`, `data-active`, `data-size`.
|
||
- disabled: 별도 prop 없이 `aria-disabled`를 쓴다. `aria-disabled="true"`이면 컴포넌트가 `tabIndex=-1`로 두고 클릭(onClick 호출 포함)을 취소한다. anchor에는 native disabled가 없어 class만으로는 접근성이 보장되지 않기 때문에 보완했다.
|
||
- `PaginationPrevious`/`PaginationNext`: 기본 `aria-label`("Go to previous/next page")과 라벨, `children`으로 교체 가능.
|
||
- 새 상태 관리, `page`/`total` 같은 prop은 만들지 않았다.
|
||
|
||
## 6. Token
|
||
|
||
Component Mapping 위치: `packages/ui/src/theme/pagination.css` (`--hm-pagination-*`).
|
||
|
||
`gap`, `height`, `min-width`, `padding-x`, `radius`, `font-size|line-height|font-weight`, `icon-size`, `text`, `hover-background`, `hover-text`, `active-background`, `active-text`, `active-font-weight`, `ellipsis-color`, `disabled-opacity`. Semantic만 참조하고 `--hm-button-*` 등 다른 컴포넌트 Token을 쓰지 않는다. 신규 Primitive 없음.
|
||
|
||
## 7. shadcn에서 유지한 항목
|
||
|
||
`nav role="navigation" aria-label="pagination"`, `ul`/`li` 구조, `a` 링크, `isActive`→`aria-current="page"`, Previous/Next 아이콘과 `aria-label`, Ellipsis의 `aria-hidden` + sr-only, `data-slot`, `cn(..., className)`.
|
||
|
||
## 8. HM에서 변경한 항목
|
||
|
||
- `buttonVariants` 합성을 제거하고 독립 anchor 스타일로 대체(위 §2).
|
||
- `size`를 shadcn의 Button size 전체에서 `default`/`icon`으로 좁혔다.
|
||
- `aria-disabled` 처리(tabIndex, 클릭 취소)를 추가했다.
|
||
- 현재 페이지를 outline 대신 채움(primary)으로 표시.
|
||
|
||
## 9. Storybook
|
||
|
||
- 대표 Story `All`("Pagination"): First page, Middle page, Last page, Long range with ellipsis(1 … 9 10 11 … 20)를 한 페이지에 둔다. 각 Specimen이 local state를 가지고 클릭 시 현재 페이지가 실제로 바뀐다(`#page-N` href + `preventDefault`).
|
||
- verification: `First Page`, `Last Page`, `Long Range`.
|
||
|
||
## 10. 사용자 확정 결정
|
||
|
||
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
|
||
- 2026-10-06 (Pagination Current 원복): 직전 Active Semantic 작업에서 Current를 `--hm-sem-state-active`(Light 2)로 바꾼 변경을 취소하고 이전 값 `--hm-sem-action-primary` / `--hm-sem-on-action-primary`(Primary Green `#123328`, 흰색)로 복원했다. Current + Hover도 Current 색을 유지한다. Hover의 `--accent` 45%는 유지한다. Focus·Disabled·API·구조 변경 없음.
|
||
|
||
## 11. 미확정 / 보류
|
||
|
||
- 수치(34px, 4px radius, 12px padding, 채움 current, Disabled opacity 0.6)는 현재 HM 규격이며 디자인 확정 시 재검토.
|
||
- page range(ellipsis 계산)는 소비자 책임이다(Story의 `pageRange`는 예시일 뿐 컴포넌트가 아님).
|
||
- 언어: Previous/Next 기본 라벨은 shadcn 영어 그대로(`children`으로 교체).
|
||
- 좁은 화면 compact 표현은 shadcn의 라벨 숨김(`sm` 미만)만 유지. 별도 compact variant 없음.
|
||
- 페이지 크기 선택, 결과 수 표시는 범위 밖.
|
||
|
||
## 12. 관련 파일
|
||
|
||
```
|
||
packages/ui/src/components/ui/pagination.tsx
|
||
packages/ui/src/theme/pagination.css
|
||
packages/ui/src/theme.css (import)
|
||
packages/ui/src/index.ts (export)
|
||
src/components/pagination.stories.tsx
|
||
```
|
||
|
||
## 13. 검증 상태
|
||
|
||
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
|
||
- 실제 interaction(Browser pane, JS click): page 4 클릭으로 current가 4로 변경, Next로 5, 첫 페이지의 Previous(`aria-disabled`)와 마지막 페이지의 Next 클릭은 상태를 바꾸지 않음. `aria-disabled` 링크는 `tabindex=-1`, opacity 0.6, cursor not-allowed. Current는 `aria-current="page"`, 34×34, 배경 `rgb(18,51,40)`, 글자 white, weight 700.
|
||
- 구조: `a` 요소, ellipsis 위치(1 … 9 10 11 … 20)와 First/Last 상태 확인.
|
||
- Responsive: 390×844에서 Previous/Next 라벨이 숨겨져 아이콘만 남고, 20페이지 범위(`1 … 9 10 11 … 20`)가 350px 한 줄에 들어가며 가로 overflow 없음.
|
||
- 미확인: 실제 키보드 Tab/Enter 순서, pointer hover·focus ring 시각.
|