Skip to content

Commit a34b6aa

Browse files
committed
Merge branch 'vk/0c11-paginationselect' of github.com:zenkigen/zenkigen-component into vk/0c11-paginationselect
2 parents ec153eb + 1730457 commit a34b6aa

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

docs/component/pagination-select-specification.md

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,237 @@ Tailwind CSSのユーティリティクラスと`@zenkigen-inc/component-config`
231231
| 日付 | 内容 | 担当者 |
232232
| -------------------- | -------- | ------ |
233233
| 2025-12-03 09:14 JST | 新規作成 | - |
234+
235+
# PaginationSelect コンポーネント仕様書
236+
237+
## 目次
238+
239+
1. [概要](#概要)
240+
2. [インポート](#インポート)
241+
3. [基本的な使用方法](#基本的な使用方法)
242+
4. [Props](#props)
243+
- [必須プロパティ](#必須プロパティ)
244+
- [オプションプロパティ](#オプションプロパティ)
245+
- [排他的プロパティグループ](#排他的プロパティグループ)
246+
- [特殊機能の詳細](#特殊機能の詳細)
247+
- [継承プロパティ](#継承プロパティ)
248+
5. [状態とスタイル](#状態とスタイル)
249+
- [サイズバリエーション](#サイズバリエーション)
250+
- [状態に応じたスタイル](#状態に応じたスタイル)
251+
- [その他のスタイル仕様](#その他のスタイル仕様)
252+
6. [使用例](#使用例)
253+
- [基本的な使用例](#基本的な使用例)
254+
- [バリエーション例1](#バリエーション例1)
255+
- [バリエーション例2](#バリエーション例2)
256+
7. [アクセシビリティ](#アクセシビリティ)
257+
8. [技術的な詳細](#技術的な詳細)
258+
- [実装について](#実装について)
259+
9. [注意事項](#注意事項)
260+
10. [スタイルのカスタマイズ](#スタイルのカスタマイズ)
261+
11. [更新履歴](#更新履歴)
262+
263+
---
264+
265+
## 概要
266+
267+
PaginationSelectコンポーネントは、検索結果などの大量データをページング表示している場面で、現在表示しているアイテム件数の範囲と総ページ数を示しつつ、Selectによるページ直接指定と前後ボタンによるページ遷移を提供するUIである。表示領域が限られているケースでも省スペースにページ選択を集約できる。
268+
269+
## インポート
270+
271+
```typescript
272+
import { PaginationSelect } from '@zenkigen-inc/component-ui';
273+
```
274+
275+
## 基本的な使用方法
276+
277+
```typescript
278+
import { useState } from 'react';
279+
import { PaginationSelect } from '@zenkigen-inc/component-ui';
280+
281+
const totalSize = 320;
282+
283+
export const Example = () => {
284+
const [page, setPage] = useState(1);
285+
286+
return (
287+
<PaginationSelect
288+
totalSize={totalSize}
289+
sizePerPage={20}
290+
currentPage={page}
291+
onChange={(value) => setPage(value)}
292+
onClickPrevButton={() => setPage((prev) => Math.max(1, prev - 1))}
293+
onClickNextButton={() => setPage((prev) => Math.min(Math.ceil(totalSize / 20), prev + 1))}
294+
/>
295+
);
296+
};
297+
```
298+
299+
## Props
300+
301+
### 必須プロパティ
302+
303+
| プロパティ || 説明 |
304+
| ------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
305+
| `totalSize` | `number` | レコードの総件数。`sizePerPage`と組み合わせて全ページ数(`pageMax`)と表示件数の上下限を算出する。0を渡すと件数表示は0件となり、Selectは非活性化される。 |
306+
| `sizePerPage` | `number` | 1ページあたりに表示する件数。`Math.ceil(totalSize / sizePerPage)`でページ数を求めるため、1以上の値を渡す。 |
307+
| `currentPage` | `number` | 現在表示中のページ番号(1起算)。Selectの選択状態と、前後ボタンの活性状態を決める。 |
308+
| `onClickPrevButton` | `() => void` | 前ページボタンがクリックされた際に呼ばれるハンドラ。呼び出し側で`currentPage`の状態遷移を制御する。 |
309+
| `onClickNextButton` | `() => void` | 次ページボタンがクリックされた際に呼ばれるハンドラ。 |
310+
| `onChange` | `(value: number) => void` | Selectで任意のページ番号が選択された際に受け取るハンドラ。Selectの選択値(文字列)を数値に変換した結果が渡される。 |
311+
312+
### オプションプロパティ
313+
314+
| プロパティ || デフォルト値 | 説明 |
315+
| --------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------ |
316+
| `countLabel` | `string` | `'件'` | 表示件数テキストの末尾に付与する単位ラベル。例: `"〜件"``"results"`|
317+
| `pageLabel` | `string` | `'ページ'` | `/ {pageMax}`表示に付与するページ単位のラベル。 |
318+
| `optionListMaxHeight` | `number` | `190` | Selectのドロップダウンリストに適用する最大高さ(px)。件数が増えた場合のスクロール量を制限できる。 |
319+
320+
### 排他的プロパティグループ
321+
322+
なし。
323+
324+
### 特殊機能の詳細
325+
326+
#### ページ計算と表示レンジ
327+
328+
- 総ページ数は`pageMax = Math.ceil(totalSize / sizePerPage)`で算出する。端数は切り上げられ、最終ページは不足件数のみを表示する。
329+
- `totalSize`が0の場合は`pageMax`も0となり、カウンターは`0件 / 0ページ`を表示する。同時にSelectが`disabled`となり、当該状態でのみ次ページボタンも非活性となる。
330+
- 表示中件数は`minCount`(1ページ目であれば1)と`maxCount``currentPage * sizePerPage`を上限`totalSize`でクランプ)で求められ、`"min - max"`形式で描画する。1ページ目で空の場合は`0`のみが描画される。
331+
332+
#### Selectオプション生成
333+
334+
- `totalSize`に基づいて`1``pageMax`の連番を`SelectOption`配列として自動生成し、`Select.Option`で描画する。
335+
- 選択肢は常にページ番号の文字列と一致しており、`currentPage`と同じ値のオプションが選択状態になる。
336+
337+
#### ナビゲーションボタン
338+
339+
- 前後ボタンは`IconButton``variant="text"``size="small"`で利用し、`currentPage === 1`で戻るボタンが、`currentPage === pageMax`または`pageMax === 0`で進むボタンが`disabled`になる。
340+
- アプリケーション側で`currentPage`の境界チェックを行い、ボタンハンドラ内で無効なページ番号に更新しないようにする。
341+
342+
### 継承プロパティ
343+
344+
追加のHTML属性は公開していない。`nav`要素や各子要素に任意の属性を付加することはできない。
345+
346+
## 状態とスタイル
347+
348+
### サイズバリエーション
349+
350+
単一サイズで提供する。
351+
352+
- 全体: `nav.flex.items-center.gap-x-1`でSelectブロックとナビゲーションを横並びにする。
353+
- 件数テキスト: `typography-label14regular text-text01`
354+
- Select: `size="medium"`, `variant="outline"`(Selectコンポーネントの既定スタイルを継承)。
355+
- `/ ページ`表記: `typography-label14regular text-text02`
356+
357+
### 状態に応じたスタイル
358+
359+
- **通常状態**: Selectは`variant="outline"`、IconButtonは`variant="text"`で、コンポーネントテーマに定義されたテキスト色(`text-text01`/`text-text02`)を用いる。
360+
- **ホバー状態**: Select・IconButtonともに各コンポーネント固有のhoverスタイルが適用される。
361+
- **選択状態**: Selectは選択中のオプションが強調表示される(Selectコンポーネントに準ずる)。
362+
- **無効状態**: `pageMax === 0`時のSelect、および境界に到達したIconButtonに`isDisabled`が渡り、`disabled`属性と`pointer-events-none`が付与される。
363+
364+
### その他のスタイル仕様
365+
366+
- カウンターテキストとSelectの間隔は`gap-x-2`で、IconButtonとの間は`gap-x-1`で揃えている。
367+
- Selectのドロップダウンは`optionListMaxHeight`でスクロール量を制御できる。未指定時は190pxで縦スクロールバーが現れる。
368+
- ナビゲーション領域は`div.flex.items-center`で水平方向に密に配置され、左右の余白はIconButton内部の`size="small"`設定に依存する。
369+
370+
## 使用例
371+
372+
### 基本的な使用例
373+
374+
```typescript
375+
import { useState } from 'react';
376+
import { PaginationSelect } from '@zenkigen-inc/component-ui';
377+
378+
const PaginatedTable = ({ totalSize }: { totalSize: number }) => {
379+
const [page, setPage] = useState(1);
380+
const sizePerPage = 25;
381+
const pageMax = Math.ceil(totalSize / sizePerPage);
382+
383+
return (
384+
<PaginationSelect
385+
totalSize={totalSize}
386+
sizePerPage={sizePerPage}
387+
currentPage={page}
388+
onChange={(value) => setPage(value)}
389+
onClickPrevButton={() => setPage((prev) => Math.max(1, prev - 1))}
390+
onClickNextButton={() => setPage((prev) => Math.min(pageMax, prev + 1))}
391+
/>
392+
);
393+
};
394+
```
395+
396+
### バリエーション例1
397+
398+
```typescript
399+
import { useState } from 'react';
400+
401+
const CustomLabels = () => {
402+
const totalSize = 980;
403+
const sizePerPage = 50;
404+
const [page, setPage] = useState(5);
405+
const pageMax = Math.ceil(totalSize / sizePerPage);
406+
407+
return (
408+
<PaginationSelect
409+
totalSize={totalSize}
410+
sizePerPage={sizePerPage}
411+
currentPage={page}
412+
countLabel="件中"
413+
pageLabel="ページ目"
414+
optionListMaxHeight={240}
415+
onChange={(value) => setPage(value)}
416+
onClickPrevButton={() => setPage((prev) => Math.max(1, prev - 1))}
417+
onClickNextButton={() => setPage((prev) => Math.min(pageMax, prev + 1))}
418+
/>
419+
);
420+
};
421+
```
422+
423+
### バリエーション例2
424+
425+
```typescript
426+
// 件数がまだ確定していない検索画面の初期状態
427+
<PaginationSelect
428+
totalSize={0}
429+
sizePerPage={25}
430+
currentPage={1}
431+
onChange={(value) => console.log('ページ選択: ', value)}
432+
onClickPrevButton={() => {}}
433+
onClickNextButton={() => {}}
434+
/>
435+
```
436+
437+
## アクセシビリティ
438+
439+
- ルート要素は`<nav aria-label="pagination">`として描画され、スクリーンリーダーにナビゲーション領域であることを明示する。
440+
- Selectは既存のSelectコンポーネントのアクセシビリティ仕様(キーボード操作、ロール付与、`aria-expanded`管理など)を継承する。
441+
- IconButtonは`button`要素として描画され、`disabled`属性が適切に付与されることでフォーカス不可となる。
442+
- ページ範囲のテキストはライブリージョンではなく静的表示のため、ページ切り替え時にユーザーへ更新を伝える場合はアプリケーション側で補助を検討する。
443+
444+
## 技術的な詳細
445+
446+
### 実装について
447+
448+
- `pageMax``Math.ceil`で算出し、Selectのオプション数と次ボタンの活性条件に再利用している。
449+
- Selectの`onChange`では取得した`SelectOption``value`(文字列)を`Number`で変換してから`onChange`コールバックへ渡す。
450+
- 件数テキストの下限は`totalSize`が0のとき`0`とし、それ以外は`(currentPage - 1) * sizePerPage + 1`で算出している。
451+
- `IconButton``Select`は既存コンポーネントへの依存であり、個別のフォーカス・キーボード処理を本コンポーネント内では保持しない。
452+
453+
## 注意事項
454+
455+
1. `sizePerPage`に0または負の値を渡すとページ数計算が破綻するため、1以上の整数を指定すること。
456+
2. `currentPage`はコンポーネント内部でクランプされない。状態管理側で`1``Math.max(pageMax, 1)`の範囲に制御する。
457+
3. `totalSize`の更新に伴い`pageMax`が減少した場合、`currentPage`が範囲外になる可能性があるためハンドラ内で再計算する。
458+
459+
## スタイルのカスタマイズ
460+
461+
Tailwind CSSのユーティリティクラスと`@zenkigen-inc/component-config`で定義されたデザイントークンに依存する。色(`text-text01/02`)、タイポグラフィ(`typography-label14regular`)、IconButton/Selectのvariant・sizeはテーマで管理されているため、変更が必要な場合は`component-config``component-theme`を更新して一貫性を保つ。
462+
463+
## 更新履歴
464+
465+
| 日付 | 内容 | 担当者 |
466+
| -------------------- | -------- | ------ |
467+
| 2025-12-03 09:14 JST | 新規作成 | - |

0 commit comments

Comments
 (0)