diff --git a/package-lock.json b/package-lock.json index bc04a7602..f3faea4e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,12 @@ "classnames": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.18.0", "react-transition-group": "^4.4.5" }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.9.12", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1183,10 +1184,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.9.12.tgz", - "integrity": "sha512-/OcmxMa34lYLFlGx7Ig926W1U1qjrnXbjFJ2TzUcDaLmED+A5se652NcWwGOidXRuMAOYLPU2jNYBEkKyXrFJA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", @@ -3256,7 +3258,8 @@ "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -3403,6 +3406,19 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -8734,6 +8750,28 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", + "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -9232,6 +9270,12 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", diff --git a/package.json b/package.json index f412fefe6..f67a81e92 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ "classnames": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.18.0", "react-transition-group": "^4.4.5" }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.9.12", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/src/App.tsx b/src/App.tsx index 189a990c8..e8bfabb83 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,45 @@ import React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { useSearchParams } from 'react-router'; import './App.css'; +import { Pagination } from './components/Pagination'; import { getNumbers } from './utils'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const items = getNumbers(1, 42).map(n => `Item ${n}`); +enum PerPage { + THREE = 3, + FIVE = 5, + TEN = 10, + TWENTY = 20, +} + +const items: string[] = getNumbers(1, 42).map(n => `Item ${n}`); export const App: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const page = Number(searchParams.get('page')) || 1; + const perPage = Number(searchParams.get('perPage')) || PerPage.FIVE; + + const startIndex = (page - 1) * perPage; + const endIndex = Math.min(startIndex + perPage, items.length); + const visibleItems = items.slice(startIndex, endIndex); + + const handlePageChange = (newPage: number) => { + searchParams.set('page', newPage.toString()); + setSearchParams(searchParams); + }; + + const handlePerPageChange = (e: React.ChangeEvent) => { + searchParams.set('perPage', e.target.value); + searchParams.set('page', '1'); + setSearchParams(searchParams); + }; + return (

Items with Pagination

- Page 1 (items 1 - 5 of 42) + Page {page} (items {startIndex + 1} - {endIndex} of {items.length})

@@ -19,11 +47,14 @@ export const App: React.FC = () => {
@@ -32,78 +63,20 @@ export const App: React.FC = () => {
- {/* Move this markup to Pagination */} - + ); diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index e417a09fc..6d19e543c 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -1 +1,89 @@ -export const Pagination = () => {}; +import cn from 'classnames'; +import React from 'react'; +interface Props { + total: number; + perPage: number; + currentPage?: number; + onPageChange: (selectedPage: number) => void; +} + +export const Pagination: React.FC = ({ + total, + perPage = 5, + currentPage = 1, + onPageChange, +}) => { + const totalPages: number = Math.ceil(total / perPage); + const pages: number[] = Array.from({ length: totalPages }, (_, i) => i + 1); + + return ( + + ); +}; diff --git a/src/index.tsx b/src/index.tsx index 226f3f4bd..d960d5966 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,11 @@ import { createRoot } from 'react-dom/client'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { BrowserRouter } from 'react-router'; import { App } from './App'; -createRoot(document.getElementById('root') as HTMLElement).render(); +createRoot(document.getElementById('root') as HTMLElement).render( + + + , +);