diff options
Diffstat (limited to 'src/components/Pagination')
| -rw-r--r-- | src/components/Pagination/Pagination.module.scss | 92 | ||||
| -rw-r--r-- | src/components/Pagination/Pagination.tsx | 131 |
2 files changed, 223 insertions, 0 deletions
diff --git a/src/components/Pagination/Pagination.module.scss b/src/components/Pagination/Pagination.module.scss new file mode 100644 index 0000000..4d74d1b --- /dev/null +++ b/src/components/Pagination/Pagination.module.scss @@ -0,0 +1,92 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; +@use "@styles/abstracts/placeholders"; + +.list { + @extend %flex-list; + justify-content: center; + + row-gap: var(--spacing-sm); +} + +.link { + display: block; + padding: var(--spacing-xs) var(--spacing-sm); + background: var(--color-bg); + border: fun.convert-px(2) solid var(--color-primary); + box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 + var(--color-primary-darker); + font-weight: 600; + text-decoration: none; + + @include mix.pointer("fine") { + padding: var(--spacing-2xs) var(--spacing-xs); + } + + &--current { + padding: calc(var(--spacing-xs) / 1.5) var(--spacing-sm); + border-color: var(--color-primary-darker); + box-shadow: none; + color: var(--color-primary-darker); + transform: translateY(#{fun.convert-px(10)}); + + @include mix.pointer("fine") { + padding: calc(var(--spacing-2xs) / 1.5) var(--spacing-xs); + transform: translateY(#{fun.convert-px(7)}); + } + } + + &:not(.link--current) { + &:hover, + &:focus { + border-color: var(--color-primary-light); + box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 + var(--color-primary-darker), + 0 fun.convert-px(2) fun.convert-px(2) fun.convert-px(1) + var(--color-shadow-dark), + 0 fun.convert-px(7) fun.convert-px(7) fun.convert-px(2) + var(--color-shadow-light); + color: var(--color-primary-light); + transform: translateY(#{fun.convert-px(-5)}); + } + + &:active { + padding: calc(var(--spacing-xs) / 1.5) var(--spacing-sm); + border-color: var(--color-primary-dark); + box-shadow: none; + color: var(--color-primary-dark); + transform: translateY(#{fun.convert-px(10)}); + + @include mix.pointer("fine") { + padding: calc(var(--spacing-2xs) / 1.5) var(--spacing-xs); + transform: translateY(#{fun.convert-px(7)}); + } + } + } +} + +.item { + position: relative; + + &:first-child { + .link { + border-top-left-radius: fun.convert-px(4); + border-bottom-left-radius: fun.convert-px(4); + } + } + + &:last-child { + .link { + border-top-right-radius: fun.convert-px(4); + border-bottom-right-radius: fun.convert-px(4); + } + } + + &:not(:first-child) { + margin-left: fun.convert-px(-1); + } + + &:not(:last-child) { + margin-right: fun.convert-px(-1); + } +} diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx new file mode 100644 index 0000000..2c24a8c --- /dev/null +++ b/src/components/Pagination/Pagination.tsx @@ -0,0 +1,131 @@ +import { settings } from '@utils/config'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useIntl } from 'react-intl'; +import styles from './Pagination.module.scss'; + +const Pagination = ({ baseUrl, total }: { baseUrl: string; total: number }) => { + const intl = useIntl(); + const { asPath } = useRouter(); + const totalPages = Math.floor(total / settings.postsPerPage); + const currentPage = asPath.includes('/page/') + ? Number(asPath.split(`${baseUrl}/page/`)[1]) + : 1; + const hasPreviousPage = currentPage !== 1; + const hasNextPage = currentPage !== totalPages; + + const getPreviousPageItem = () => { + return ( + <li className={styles.item}> + <Link href={`${baseUrl}/page/${currentPage - 1}`}> + <a className={styles.link}> + {intl.formatMessage( + { + defaultMessage: '{icon} Previous page', + description: 'Pagination: previous page link', + }, + { icon: '←' } + )} + </a> + </Link> + </li> + ); + }; + + const getNextPageItem = () => { + return ( + <li className={styles.item}> + <Link href={`${baseUrl}/page/${currentPage + 1}`}> + <a className={styles.link}> + {intl.formatMessage( + { + defaultMessage: 'Next page {icon}', + description: 'Pagination: Next page link', + }, + { icon: '→' } + )} + </a> + </Link> + </li> + ); + }; + + const getPages = () => { + const pages = []; + for (let i = 1; i <= totalPages; i++) { + if (i === currentPage) { + pages.push({ + id: `page-${i}`, + link: ( + <span className={`${styles.link} ${styles['link--current']}`}> + {intl.formatMessage( + { + defaultMessage: '<a11y>Page </a11y>{number}', + description: 'Pagination: page number', + }, + { + number: i, + a11y: (chunks: string) => ( + <span className="screen-reader-text">{chunks}</span> + ), + } + )} + </span> + ), + }); + } else { + pages.push({ + id: `page-${i}`, + link: ( + <Link href={`${baseUrl}/page/${i}`}> + <a className={styles.link}> + {intl.formatMessage( + { + defaultMessage: '<a11y>Page </a11y>{number}', + description: 'Pagination: page number', + }, + { + number: i, + a11y: (chunks: string) => ( + <span className="screen-reader-text">{chunks}</span> + ), + } + )} + </a> + </Link> + ), + }); + } + } + + return pages; + }; + + const getItems = () => { + const pages = getPages(); + + return pages.map((page) => ( + <li key={page.id} className={styles.item}> + {page.link} + </li> + )); + }; + + return ( + <nav className={styles.wrapper} aria-labelledby="pagination-title"> + <h2 id="pagination-title" className="screen-reader-text"> + {intl.formatMessage({ + defaultMessage: 'Pagination', + description: 'Pagination: pagination title', + })} + </h2> + <ul className={styles.list}> + {hasPreviousPage && getPreviousPageItem()} + {getItems()} + {hasNextPage && getNextPageItem()} + </ul> + </nav> + ); +}; + +export default Pagination; |
