DaisyUIで美しい静的ページネーションを実装する方法

公開日:
目次

ブログやニュースサイトなど、大量のコンテンツを扱うWebアプリケーションでは、ページネーションは必須の機能です。この記事では、DaisyUIのjoinコンポーネントを活用して、美しく機能的な静的ページネーションを実装する方法を詳しく解説します。

DaisyUIのjoinコンポーネントとは

DaisyUIのjoinコンポーネントは、複数の要素を視覚的に結合するためのコンポーネントです。ページネーションのようなナビゲーション要素を作成するのに最適です。

主な特徴:

  • 統一感のあるデザイン: 隣接する要素が自然に結合される
  • レスポンシブ対応: モバイルでも適切に表示される
  • アクセシビリティ: スクリーンリーダーに優しい構造
  • カスタマイズ可能: Tailwind CSSクラスで柔軟にスタイル調整

基本的なページネーション実装

1ページ目用のコンポーネント

// pages/recent.tsx
import Link from 'next/link'

type Props = {
  posts: Post[]
  totalPages: number
}

export default function RecentPage({ posts, totalPages }: Props) {
  return (
    <Layout>
      <Container>
        <div className="flex flex-col max-w-6xl mx-auto px-10 md:px-0">
          <main>
            <RecentPost posts={posts} />
          </main>
          
          {/* DaisyUIページネーション */}
          {totalPages > 1 && (
            <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
              <div className="join">
                <span className="join-item btn btn-active">1</span>
                
                {totalPages > 1 && (
                  <Link href="/recent/2" className="join-item btn">
                    »
                  </Link>
                )}
              </div>
            </nav>
          )}
        </div>
      </Container>
    </Layout>
  )
}

2ページ目以降用のコンポーネント

// pages/recent/[page].tsx
import { GetStaticProps, GetStaticPaths } from 'next'
import Link from 'next/link'

type Props = {
  posts: Post[]
  currentPage: number
  totalPages: number
}

export default function RecentPageWithPagination({ posts, currentPage, totalPages }: Props) {
  return (
    <Layout>
      <Container>
        <div className="flex flex-col max-w-6xl mx-auto px-10 md:px-0">
          <main>
            <RecentPost posts={posts} />
          </main>
          
          {/* 静的ページネーション */}
          <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
            <div className="join">
              {currentPage > 1 && (
                <Link 
                  href={currentPage === 2 ? '/recent' : `/recent/${currentPage - 1}`} 
                  className="join-item btn"
                >
                  «
                </Link>
              )}
              
              <span className="join-item btn btn-active">
                {currentPage}
              </span>
              
              {currentPage < totalPages && (
                <Link href={`/recent/${currentPage + 1}`} className="join-item btn">
                  »
                </Link>
              )}
            </div>
          </nav>
        </div>
      </Container>
    </Layout>
  )
}

高度なページネーション実装

数字ページング

複数のページ番号を表示する場合:

const PaginationWithNumbers = ({ currentPage, totalPages, basePath }: Props) => {
  const generatePageNumbers = () => {
    const pages = []
    const showPages = 5 // 表示するページ数
    
    let startPage = Math.max(1, currentPage - Math.floor(showPages / 2))
    let endPage = Math.min(totalPages, startPage + showPages - 1)
    
    // 最初に調整
    if (endPage - startPage + 1 < showPages) {
      startPage = Math.max(1, endPage - showPages + 1)
    }
    
    for (let i = startPage; i <= endPage; i++) {
      pages.push(i)
    }
    
    return pages
  }

  return (
    <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
      <div className="join">
        {/* 前へボタン */}
        {currentPage > 1 && (
          <Link 
            href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
            className="join-item btn"
          >
            «
          </Link>
        )}
        
        {/* 最初のページ */}
        {generatePageNumbers()[0] > 1 && (
          <>
            <Link href={basePath} className="join-item btn">
              1
            </Link>
            {generatePageNumbers()[0] > 2 && (
              <span className="join-item btn btn-disabled">...</span>
            )}
          </>
        )}
        
        {/* ページ番号 */}
        {generatePageNumbers().map(page => (
          <Link
            key={page}
            href={page === 1 ? basePath : `${basePath}/${page}`}
            className={`join-item btn ${page === currentPage ? 'btn-active' : ''}`}
          >
            {page}
          </Link>
        ))}
        
        {/* 最後のページ */}
        {generatePageNumbers()[generatePageNumbers().length - 1] < totalPages && (
          <>
            {generatePageNumbers()[generatePageNumbers().length - 1] < totalPages - 1 && (
              <span className="join-item btn btn-disabled">...</span>
            )}
            <Link href={`${basePath}/${totalPages}`} className="join-item btn">
              {totalPages}
            </Link>
          </>
        )}
        
        {/* 次へボタン */}
        {currentPage < totalPages && (
          <Link href={`${basePath}/${currentPage + 1}`} className="join-item btn">
            »
          </Link>
        )}
      </div>
    </nav>
  )
}

レスポンシブデザイン対応

モバイルファーストなページネーション

const ResponsivePagination = ({ currentPage, totalPages, basePath }: Props) => {
  return (
    <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
      <div className="join">
        {/* モバイル: シンプルな前後ボタンのみ */}
        <div className="flex md:hidden">
          {currentPage > 1 && (
            <Link 
              href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
              className="join-item btn btn-sm"
            >
              ← 前
            </Link>
          )}
          
          <span className="join-item btn btn-sm btn-active">
            {currentPage} / {totalPages}
          </span>
          
          {currentPage < totalPages && (
            <Link href={`${basePath}/${currentPage + 1}`} className="join-item btn btn-sm">
              次 →
            </Link>
          )}
        </div>
        
        {/* デスクトップ: 完全なページネーション */}
        <div className="hidden md:flex">
          {currentPage > 1 && (
            <Link 
              href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
              className="join-item btn"
            >
              «
            </Link>
          )}
          
          <span className="join-item btn btn-active">
            {currentPage}
          </span>
          
          {currentPage < totalPages && (
            <Link href={`${basePath}/${currentPage + 1}`} className="join-item btn">
              »
            </Link>
          )}
        </div>
      </div>
    </nav>
  )
}

カスタムスタイリング

テーマに応じたスタイル調整

const ThemedPagination = ({ currentPage, totalPages, basePath }: Props) => {
  return (
    <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
      <div className="join shadow-lg">
        {currentPage > 1 && (
          <Link 
            href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
            className="join-item btn btn-outline hover:btn-primary transition-all duration-200"
          >
            «
          </Link>
        )}
        
        <span className="join-item btn btn-primary">
          {currentPage}
        </span>
        
        {currentPage < totalPages && (
          <Link 
            href={`${basePath}/${currentPage + 1}`} 
            className="join-item btn btn-outline hover:btn-primary transition-all duration-200"
          >
            »
          </Link>
        )}
      </div>
    </nav>
  )
}

ダークモード対応

/* styles/globals.css */
[data-theme="dark"] .pagination {
  --btn-text-case: none;
  --rounded-btn: 0.5rem;
}

[data-theme="dark"] .join-item.btn {
  background-color: hsl(var(--b2));
  border-color: hsl(var(--b3));
  color: hsl(var(--bc));
}

[data-theme="dark"] .join-item.btn:hover {
  background-color: hsl(var(--b3));
  border-color: hsl(var(--b3));
}

[data-theme="dark"] .join-item.btn.btn-active {
  background-color: hsl(var(--p));
  color: hsl(var(--pc));
  border-color: hsl(var(--p));
}

アクセシビリティの考慮

WAI-ARIA属性の追加

const AccessiblePagination = ({ currentPage, totalPages, basePath }: Props) => {
  return (
    <nav 
      aria-label="ページネーション" 
      className="flex justify-center pt-5 mx-auto max-w-4xl"
    >
      <div className="join" role="group" aria-label="ページナビゲーション">
        {currentPage > 1 && (
          <Link 
            href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
            className="join-item btn"
            aria-label={`${currentPage - 1}ページに移動`}
          >
            «
          </Link>
        )}
        
        <span 
          className="join-item btn btn-active"
          aria-current="page"
          aria-label={`現在のページ: ${currentPage}`}
        >
          {currentPage}
        </span>
        
        {currentPage < totalPages && (
          <Link 
            href={`${basePath}/${currentPage + 1}`} 
            className="join-item btn"
            aria-label={`${currentPage + 1}ページに移動`}
          >
            »
          </Link>
        )}
      </div>
    </nav>
  )
}

パフォーマンス最適化

プリフェッチの活用

import Link from 'next/link'

const OptimizedPagination = ({ currentPage, totalPages, basePath }: Props) => {
  return (
    <nav className="flex justify-center pt-5 mx-auto max-w-4xl">
      <div className="join">
        {currentPage > 1 && (
          <Link 
            href={currentPage === 2 ? basePath : `${basePath}/${currentPage - 1}`}
            className="join-item btn"
            prefetch={true} // 重要: プリフェッチを有効化
          >
            «
          </Link>
        )}
        
        <span className="join-item btn btn-active">
          {currentPage}
        </span>
        
        {currentPage < totalPages && (
          <Link 
            href={`${basePath}/${currentPage + 1}`} 
            className="join-item btn"
            prefetch={true} // 重要: プリフェッチを有効化
          >
            »
          </Link>
        )}
      </div>
    </nav>
  )
}

まとめ

DaisyUIのjoinコンポーネントを使用することで、以下のメリットを持つページネーションを効率的に実装できます:

主な利点

  • 一貫性のあるデザイン: DaisyUIのデザインシステムに準拠
  • レスポンシブ対応: モバイルとデスクトップで最適化された表示
  • アクセシビリティ: スクリーンリーダーに対応した構造
  • カスタマイズ性: Tailwind CSSクラスで柔軟にスタイル調整

実装のポイント

  1. 適切なURL設計: 1ページ目と2ページ目以降の適切な分離
  2. レスポンシブ考慮: モバイルとデスクトップでの最適化
  3. アクセシビリティ: ARIA属性とセマンティックHTML
  4. パフォーマンス: Next.jsのプリフェッチ機能活用

DaisyUIの力を借りることで、美しく機能的なページネーションを短時間で実装できるでしょう。