diff --git a/README.md b/README.md index be8a8c6b6..b28be7c41 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Here is the [working version](https://mate-academy.github.io/react_pagination/) -You a given a list of items and markup for the `Pagination`. Implement the +You a given a list of items and markup for the `Pagination`. Implement the `Pagination` as a stateless component to show only the items for a current page. 1. The `Pagination` should be used with the next props: @@ -32,4 +32,4 @@ You a given a list of items and markup for the `Pagination`. Implement the - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). - Open one more terminal and run tests with `npm test` to ensure your solution is correct. -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_pagination/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://maximtsyrulnyk.github.io/react_pagination/) and add it to the PR description. diff --git a/package-lock.json b/package-lock.json index bc04a7602..d7c3eac74 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-dom": "^7.13.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", @@ -3403,6 +3405,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 +8749,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "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-router-dom": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "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 +9285,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..f1029b11b 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-dom": "^7.13.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..cfa6bef89 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,55 @@ import React from 'react'; +import { useSearchParams } from 'react-router-dom'; import './App.css'; import { getNumbers } from './utils'; +import { Pagination } from './components/Pagination'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars const items = getNumbers(1, 42).map(n => `Item ${n}`); export const App: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const currentPage = Number(searchParams.get('page')) || 1; + const perPage = Number(searchParams.get('perPage')) || 5; + + const startIndex = (currentPage - 1) * perPage; + const endIndex = startIndex + perPage; + const visibleItems = items.slice(startIndex, endIndex); + + const itemFrom = startIndex + 1; + const itemTo = Math.min(items.length, endIndex); + + const updateParams = (params: { page?: string; perPage?: string }) => { + const newParams = new URLSearchParams(searchParams); + + if (params.page) { + newParams.set('page', params.page); + } + + if (params.perPage) { + newParams.set('perPage', params.perPage); + } + + setSearchParams(newParams); + }; + + const handlePerPageChange = (event: React.ChangeEvent) => { + updateParams({ + perPage: event.target.value, + page: '1', + }); + }; + + const handlePageChange = (page: number) => { + updateParams({ page: page.toString() }); + }; + return (

Items with Pagination

- Page 1 (items 1 - 5 of 42) + {`Page ${currentPage} (items ${itemFrom} - ${itemTo} of ${items.length})`}

@@ -19,94 +57,35 @@ export const App: React.FC = () => {
-
- {/* Move this markup to Pagination */} - -
    -
  • Item 1
  • -
  • Item 2
  • -
  • Item 3
  • -
  • Item 4
  • -
  • Item 5
  • + + +
      + {visibleItems.map(item => ( +
    • + {item} +
    • + ))}
    ); }; - -export default App; diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index e417a09fc..60c02c60f 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -1 +1,78 @@ -export const Pagination = () => {}; +import React from 'react'; +import { getNumbers } from '../../utils'; + +interface Props { + total: number; + perPage: number; + currentPage?: number; + onPageChange: (page: number) => void; +} + +export const Pagination: React.FC = ({ + total, + perPage, + currentPage = 1, + onPageChange, +}) => { + const totalPages = Math.ceil(total / perPage); + const pages = getNumbers(1, totalPages); + + return ( + + ); +}; diff --git a/src/components/Pagination/index.ts b/src/components/Pagination/index.tsx similarity index 100% rename from src/components/Pagination/index.ts rename to src/components/Pagination/index.tsx diff --git a/src/index.tsx b/src/index.tsx index 226f3f4bd..7187ac57e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,10 @@ import { createRoot } from 'react-dom/client'; +import { HashRouter as Router } from 'react-router-dom'; import { App } from './App'; -createRoot(document.getElementById('root') as HTMLElement).render(); +createRoot(document.getElementById('root') as HTMLElement).render( + + + , +);