first commit

This commit is contained in:
Lars Behrends
2026-01-21 21:40:09 +01:00
commit 4853b860fc
45 changed files with 16072 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
import { motion } from 'framer-motion'
interface PaginationProps {
currentPage: number
lastPage: number
total: number
onPageChange: (page: number) => void
itemsPerPage: number
onItemsPerPageChange: (items: number) => void
}
export default function Pagination({
currentPage,
lastPage,
total,
onPageChange,
itemsPerPage,
onItemsPerPageChange
}: PaginationProps) {
const handlePrevious = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1)
}
}
const handleNext = () => {
if (currentPage < lastPage) {
onPageChange(currentPage + 1)
}
}
const handlePageSelect = (page: number) => {
onPageChange(page)
}
// Generate page numbers to show
const getVisiblePages = () => {
const pages: number[] = []
const maxVisible = 5
if (lastPage <= maxVisible) {
for (let i = 1; i <= lastPage; i++) {
pages.push(i)
}
} else {
const start = Math.max(1, currentPage - 2)
const end = Math.min(lastPage, start + maxVisible - 1)
for (let i = start; i <= end; i++) {
pages.push(i)
}
}
return pages
}
if (lastPage <= 1) {
return null
}
return (
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 mt-8 p-4 bg-white/5 dark:bg-gray-800/50 rounded-xl border border-white/10">
{/* Items per page selector */}
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400">Show:</span>
<select
value={itemsPerPage}
onChange={(e) => onItemsPerPageChange(parseInt(e.target.value))}
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
<span className="text-sm text-gray-600 dark:text-gray-400">
of {total} items
</span>
</div>
{/* Pagination controls */}
<div className="flex items-center gap-2">
<motion.button
onClick={handlePrevious}
disabled={currentPage === 1}
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
whileHover={{ scale: currentPage !== 1 ? 1.05 : 1 }}
whileTap={{ scale: currentPage !== 1 ? 0.95 : 1 }}
>
Previous
</motion.button>
<div className="flex items-center gap-1">
{getVisiblePages().map((page) => (
<motion.button
key={page}
onClick={() => handlePageSelect(page)}
className={`w-8 h-8 text-sm rounded-lg transition-colors ${
currentPage === page
? 'bg-purple-600 text-white'
: 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-600'
}`}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
{page}
</motion.button>
))}
</div>
<motion.button
onClick={handleNext}
disabled={currentPage === lastPage}
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
whileHover={{ scale: currentPage !== lastPage ? 1.05 : 1 }}
whileTap={{ scale: currentPage !== lastPage ? 0.95 : 1 }}
>
Next
</motion.button>
</div>
{/* Page info */}
<div className="text-sm text-gray-600 dark:text-gray-400">
Page {currentPage} of {lastPage}
</div>
</div>
)
}