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;
+ }
+ }
+ }
+}