From 7bde352ea74ea08715b4b96492e88ddab9928a03 Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Sun, 14 Jul 2024 10:56:04 +0200 Subject: [PATCH] Add pagination components. --- demo/DemoApp.tsx | 42 +++++- src/Components/Pagination/Paginate.tsx | 137 ++++++++++++++++++++ src/Components/Pagination/Pagination.tsx | 141 +++++++++++++++++++++ src/styles/_components.less | 1 + src/styles/components/_pagination.less | 1 + src/styles/components/pagination/_nav.less | 67 ++++++++++ 6 files changed, 384 insertions(+), 5 deletions(-) create mode 100644 src/Components/Pagination/Paginate.tsx create mode 100644 src/Components/Pagination/Pagination.tsx create mode 100644 src/styles/components/_pagination.less create mode 100644 src/styles/components/pagination/_nav.less diff --git a/demo/DemoApp.tsx b/demo/DemoApp.tsx index fac4bab..1f4b34e 100644 --- a/demo/DemoApp.tsx +++ b/demo/DemoApp.tsx @@ -24,6 +24,7 @@ import {Application} from "../src/Application/Application"; import {Outlet} from "react-router-dom"; import {ToggleSwitch} from "../src/Components/Forms/ToggleSwitch"; import {Step, Steps} from "../src/Components/Steps/Steps"; +import {AsyncPaginate, AutoPaginate, Paginate} from "../src/Components/Pagination/Paginate"; export function DemoApp() { @@ -31,6 +32,8 @@ export function DemoApp() const [selected, setSelected] = useState(null); + const [page, setPage] = useState(11); + return ( @@ -63,9 +66,7 @@ export function DemoApp()

TODO

    -
  • Pagination
  • -
  • Global states
  • -
  • Async
  • +
  • Errors
  • Subapps
  • Modals
@@ -283,7 +284,7 @@ export function DemoApp()

Simple loaders

- + @@ -310,7 +311,7 @@ export function DemoApp() Submenu - } floatingOptions={{ placement: "right-start" }}> + } floatingOptions={{placement: "right-start"}}> @@ -410,6 +411,37 @@ export function DemoApp() + +

Pagination

+ +

Normal pagination

+ + + Page {page} + + +

Auto pagination

+ + + {(page) => ( + Page {page} + )} + + +

Async pagination

+ + { return 72; }} getData={async () => (["a", Math.random(), "c"])}> + {(data) => ( + <> + { + data.map((value, index) => ( +
{value}
+ )) + } + + )} +
+
); } diff --git a/src/Components/Pagination/Paginate.tsx b/src/Components/Pagination/Paginate.tsx new file mode 100644 index 0000000..6686cb1 --- /dev/null +++ b/src/Components/Pagination/Paginate.tsx @@ -0,0 +1,137 @@ +import React, {useCallback, useState} from "react"; +import {Pagination} from "./Pagination"; +import {SpinningLoader} from "../Loaders/SpinningLoader"; +import {Await, useAsync} from "../../Async"; + +/** + * Paginated content component with custom page handling. + */ +export function Paginate({ page, onChange, count, children }: React.PropsWithChildren<{ + /** + * The current page. + */ + page: number; + + /** + * Called when a new page is selected. + * @param newPage The newly selected page. + */ + onChange: (newPage: number) => void; + + /** + * Pages count. + */ + count: number; +}>) +{ + return ( + <> + {children} + + + + ); +} + +/** + * Paginated content component. + */ +export function AutoPaginate({ count, children }: { + /** + * Pages count. + */ + count: number; + + /** + * Show the given page. + * @param page The page to show. + */ + children: (page: number) => React.ReactElement; +}) +{ + // The current page. + const [page, setPage] = useState(1); + + return ( + + {children(page)} + + ); +} + +/** + * Asynchronous paginated content component. + */ +export function AsyncPaginate({ count, getData, children }: { + /** + * Get pages count. + */ + count: () => Promise; + + /** + * Get data for the given page. + * @param page The page for which to get data. + */ + getData: (page: number) => Promise; + + /** + * Show the current page with its retrieved data. + * @param data Data of the page to show. + * @param page The page to show. + */ + children: (data: T, page: number) => React.ReactElement; +}) +{ + // Getting pages count. + const asyncCount = useAsync(count, []); + + return ( + }> + { + (count) => ( + + {(page) => } + + ) + } + + ); +} + +/** + * An async page to render. + */ +export function AsyncPage({page, getData, render}: { + /** + * The page number to show. + */ + page: number; + + /** + * Get data for the given page. + * @param page The page for which to get data. + */ + getData: (page: number) => Promise; + + /** + * Render the page with its retrieved data. + * @param data Data of the page to show. + * @param page The page to show. + */ + render: (data: T, page: number) => React.ReactElement; +}) +{ + // Store function to get page data. + const getPageData = useCallback(() => { + return getData(page); + }, [page]); + + // Getting page data. + const asyncPageData = useAsync(getPageData, [getPageData]); + + return ( + }> + {(pageData) => render(pageData, page)} + + ); +} diff --git a/src/Components/Pagination/Pagination.tsx b/src/Components/Pagination/Pagination.tsx new file mode 100644 index 0000000..271a08d --- /dev/null +++ b/src/Components/Pagination/Pagination.tsx @@ -0,0 +1,141 @@ +import React, {useEffect, useState} from "react"; +import {CaretLeft, CaretRight} from "@phosphor-icons/react"; +import {Tooltip} from "../Floating/Tooltip"; +import {usePreviousValue} from "../../Utils"; + +/** + * Pagination component. + */ +export function Pagination({ page, onChange, count }: { + /** + * The current page. + */ + page: number; + + /** + * Called when a new page is selected. + * @param newPage The newly selected page. + */ + onChange: (newPage: number) => void; + + /** + * Pages count. + */ + count: number; +}) +{ + // Memorize previous page. + const previousPage = usePreviousValue(page); + + // The input text to use, when the currently entered value is not a number. + const [inputText, setInputText] = useState(undefined); + + useEffect(() => { + if (page != previousPage) + // If the page has changed, resetting the input text. + setInputText(undefined); + }, [previousPage, page]); + + return ( + + ); +} diff --git a/src/styles/_components.less b/src/styles/_components.less index 2713841..645159c 100644 --- a/src/styles/_components.less +++ b/src/styles/_components.less @@ -9,6 +9,7 @@ @import "components/_list"; @import "components/_loaders"; @import "components/_menus"; +@import "components/_pagination"; @import "components/_select"; @import "components/_steps"; @import "components/_steps-counter"; diff --git a/src/styles/components/_pagination.less b/src/styles/components/_pagination.less new file mode 100644 index 0000000..bb700da --- /dev/null +++ b/src/styles/components/_pagination.less @@ -0,0 +1 @@ +@import "pagination/_nav"; diff --git a/src/styles/components/pagination/_nav.less b/src/styles/components/pagination/_nav.less new file mode 100644 index 0000000..c90a2be --- /dev/null +++ b/src/styles/components/pagination/_nav.less @@ -0,0 +1,67 @@ +nav.pages +{ + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + margin: auto; + padding: 0.25em; + width: 50em; + max-width: 95%; + box-sizing: border-box; + border-radius: 0.25em; + + border: solid var(--background-darkest) thin; + background: var(--background-lighter); + + button, .button + { + margin: auto; + border: none; + + &:hover + { + background: var(--background-darker); + } + } + + input + { + transition: background 0.2s ease; + width: 5em; + border-color: var(--background-darker); + outline: none; + + text-align: center; + + &:focus + { + background: var(--background-darker); + } + } + + > ul + { + flex: 1; + + margin: 0; + padding: 0; + list-style: none; + + text-align: center; + vertical-align: middle; + + > li + { + display: inline-block; + padding: 0; + vertical-align: middle; + + button, .button + { + margin: auto 0.25em; + } + } + } +}