add paginations
This commit is contained in:
parent
f3c1990af4
commit
3c1574a393
411
dist/index.js
vendored
411
dist/index.js
vendored
@ -1,6 +1,407 @@
|
||||
const e = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), i = (r) => String(r).replace(/\d/g, (t) => "۰۱۲۳۴۵۶۷۸۹"[Number(t)]), o = (r) => String(r).replace(/[۰-۹]/g, (n) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(n)));
|
||||
export {
|
||||
o as toEnDigits,
|
||||
i as toFaDigits,
|
||||
e as toFaNumber
|
||||
import { jsxs as I, jsx as o } from "react/jsx-runtime";
|
||||
import { useState as y, useRef as M, useEffect as A, useCallback as _ } from "react";
|
||||
import { HStack as B, IconButton as N, Button as S, Input as E, Text as v } from "@chakra-ui/react";
|
||||
import { IoChevronForwardOutline as z, IoChevronBackOutline as V } from "react-icons/io5";
|
||||
const K = (i) => new Intl.NumberFormat("fa-IR").format(Number(i)), t = (i) => String(i).replace(/\d/g, (n) => "۰۱۲۳۴۵۶۷۸۹"[Number(n)]), D = (i) => String(i).replace(/[۰-۹]/g, (C) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(C))), O = ({
|
||||
currentPage: i,
|
||||
totalPages: n,
|
||||
totalCount: C,
|
||||
onPageChange: d
|
||||
}) => {
|
||||
const [c, l] = y(t(i)), [a, f] = y(!1), [b, s] = y(null), x = M(null), w = [.../* @__PURE__ */ new Set([1, i, n])].filter(
|
||||
(e) => e >= 1 && e <= n
|
||||
);
|
||||
A(() => {
|
||||
if (b !== null) {
|
||||
i === b && (s(null), f(!1), l(t(i)));
|
||||
return;
|
||||
}
|
||||
a || l(t(i));
|
||||
}, [i, a, b]), A(() => {
|
||||
if (!a || !c || b !== null) return;
|
||||
const e = parseInt(D(c), 10);
|
||||
if (isNaN(e)) return;
|
||||
const u = setTimeout(() => {
|
||||
var m;
|
||||
const r = Math.min(Math.max(e, 1), n);
|
||||
r !== i ? (s(r), l(t(r)), d(r)) : (f(!1), l(t(i))), (m = x.current) == null || m.blur();
|
||||
}, 1e3);
|
||||
return () => clearTimeout(u);
|
||||
}, [
|
||||
c,
|
||||
a,
|
||||
b,
|
||||
n,
|
||||
i,
|
||||
d
|
||||
]);
|
||||
const k = () => {
|
||||
var r, m;
|
||||
const e = parseInt(D(c), 10);
|
||||
if (isNaN(e)) {
|
||||
l(t(i)), f(!1), s(null), (r = x.current) == null || r.blur();
|
||||
return;
|
||||
}
|
||||
const u = Math.min(Math.max(e, 1), n);
|
||||
u !== i ? (s(u), l(t(u)), d(u)) : (l(t(i)), f(!1), s(null)), (m = x.current) == null || m.blur();
|
||||
};
|
||||
return n === 0 || !C ? null : /* @__PURE__ */ I(
|
||||
B,
|
||||
{
|
||||
justifyContent: "space-around",
|
||||
backgroundColor: "white",
|
||||
py: "10px",
|
||||
px: "16px",
|
||||
rounded: "lg",
|
||||
border: "1px",
|
||||
borderColor: "blue.200",
|
||||
w: "100%",
|
||||
children: [
|
||||
/* @__PURE__ */ I(B, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => d(i - 1),
|
||||
isDisabled: i === 1,
|
||||
icon: /* @__PURE__ */ o(z, {}),
|
||||
"aria-label": "صفحه قبلی"
|
||||
}
|
||||
),
|
||||
w.map((e) => /* @__PURE__ */ o(
|
||||
S,
|
||||
{
|
||||
size: "md",
|
||||
px: 2,
|
||||
bg: i === e ? "#4B49AC" : "#e6e6f1",
|
||||
color: i === e ? "#e6e6f1" : "#4B49AC",
|
||||
_hover: { bg: i === e ? "#4B49AC" : "#cbcbe8" },
|
||||
onClick: () => d(e),
|
||||
variant: "soft",
|
||||
children: t(e)
|
||||
},
|
||||
e
|
||||
)),
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => d(i + 1),
|
||||
isDisabled: i === n,
|
||||
icon: /* @__PURE__ */ o(V, {}),
|
||||
"aria-label": "صفحه بعدی"
|
||||
}
|
||||
),
|
||||
/* @__PURE__ */ o(
|
||||
E,
|
||||
{
|
||||
ref: x,
|
||||
type: "text",
|
||||
inputMode: "numeric",
|
||||
placeholder: "صفحه",
|
||||
w: "56px",
|
||||
value: c,
|
||||
onFocus: () => {
|
||||
f(!0), s(null), l("");
|
||||
},
|
||||
onChange: (e) => {
|
||||
const u = e.target.value, r = D(u).replace(/\D/g, "");
|
||||
l(t(r));
|
||||
},
|
||||
onBlur: () => {
|
||||
c || (l(t(i)), f(!1), s(null));
|
||||
},
|
||||
onKeyDown: (e) => {
|
||||
e.key === "Enter" && k();
|
||||
},
|
||||
textAlign: "center",
|
||||
color: "#1D2939",
|
||||
px: 1,
|
||||
minW: 0,
|
||||
dir: "ltr"
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__PURE__ */ I(B, { spacing: 2, children: [
|
||||
/* @__PURE__ */ o(v, { w: "auto", fontWeight: "bold", whiteSpace: "nowrap", children: "تعداد کل:" }),
|
||||
/* @__PURE__ */ o(v, { children: t(C) })
|
||||
] })
|
||||
]
|
||||
}
|
||||
);
|
||||
}, L = ({
|
||||
currentPage: i,
|
||||
totalPages: n,
|
||||
totalCount: C,
|
||||
onPageChange: d
|
||||
}) => {
|
||||
const [c, l] = y(t(i)), [a, f] = y(!1), [b, s] = y(null), x = M(null), w = Array.from(
|
||||
/* @__PURE__ */ new Set([1, i, i + 1, n])
|
||||
).filter((e) => e >= 1 && e <= n).sort((e, u) => e - u);
|
||||
A(() => {
|
||||
if (b !== null) {
|
||||
i === b && (s(null), f(!1), l(t(i)));
|
||||
return;
|
||||
}
|
||||
a || l(t(i));
|
||||
}, [i, a, b]), A(() => {
|
||||
if (!a || !c || b !== null) return;
|
||||
const e = parseInt(D(c), 10);
|
||||
if (isNaN(e)) return;
|
||||
const u = setTimeout(() => {
|
||||
var m;
|
||||
const r = Math.min(Math.max(e, 1), n);
|
||||
r !== i ? (s(r), l(t(r)), d(r)) : (f(!1), l(t(i))), (m = x.current) == null || m.blur();
|
||||
}, 1e3);
|
||||
return () => clearTimeout(u);
|
||||
}, [
|
||||
c,
|
||||
a,
|
||||
b,
|
||||
n,
|
||||
i,
|
||||
d
|
||||
]);
|
||||
const k = () => {
|
||||
var r, m;
|
||||
const e = parseInt(D(c), 10);
|
||||
if (isNaN(e)) {
|
||||
l(t(i)), f(!1), s(null), (r = x.current) == null || r.blur();
|
||||
return;
|
||||
}
|
||||
const u = Math.min(Math.max(e, 1), n);
|
||||
u !== i ? (s(u), l(t(u)), d(u)) : (l(t(i)), f(!1), s(null)), (m = x.current) == null || m.blur();
|
||||
};
|
||||
return n === 0 || !C ? null : /* @__PURE__ */ I(
|
||||
B,
|
||||
{
|
||||
justifyContent: "space-around",
|
||||
backgroundColor: "white",
|
||||
py: "10px",
|
||||
px: "16px",
|
||||
rounded: "lg",
|
||||
border: "1px",
|
||||
borderColor: "blue.200",
|
||||
w: "100%",
|
||||
children: [
|
||||
/* @__PURE__ */ I(B, { spacing: 2, whiteSpace: "nowrap", children: [
|
||||
/* @__PURE__ */ o(v, { fontWeight: "bold", children: "صفحه" }),
|
||||
/* @__PURE__ */ o(v, { children: t(i) }),
|
||||
/* @__PURE__ */ o(v, { fontWeight: "bold", children: "از" }),
|
||||
/* @__PURE__ */ o(v, { children: t(n) })
|
||||
] }),
|
||||
/* @__PURE__ */ I(B, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => d(i - 1),
|
||||
isDisabled: i === 1,
|
||||
icon: /* @__PURE__ */ o(z, {}),
|
||||
"aria-label": "صفحه قبلی"
|
||||
}
|
||||
),
|
||||
w.map((e) => /* @__PURE__ */ o(
|
||||
S,
|
||||
{
|
||||
size: "md",
|
||||
bg: i === e ? "#4B49AC" : "#e6e6f1",
|
||||
color: i === e ? "#e6e6f1" : "#4B49AC",
|
||||
_hover: { bg: i === e ? "#4B49AC" : "#cbcbe8" },
|
||||
onClick: () => d(e),
|
||||
variant: "soft",
|
||||
children: t(e)
|
||||
},
|
||||
e
|
||||
)),
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => d(i + 1),
|
||||
isDisabled: i === n,
|
||||
icon: /* @__PURE__ */ o(V, {}),
|
||||
"aria-label": "صفحه بعدی"
|
||||
}
|
||||
),
|
||||
/* @__PURE__ */ o(
|
||||
E,
|
||||
{
|
||||
ref: x,
|
||||
type: "text",
|
||||
inputMode: "numeric",
|
||||
placeholder: "صفحه",
|
||||
w: "100px",
|
||||
value: c,
|
||||
onFocus: () => {
|
||||
f(!0), s(null), l("");
|
||||
},
|
||||
onChange: (e) => {
|
||||
const u = e.target.value, r = D(u).replace(/\D/g, "");
|
||||
l(t(r));
|
||||
},
|
||||
onBlur: () => {
|
||||
c || (l(t(i)), f(!1), s(null));
|
||||
},
|
||||
onKeyDown: (e) => {
|
||||
e.key === "Enter" && k();
|
||||
},
|
||||
textAlign: "center",
|
||||
color: "#1D2939",
|
||||
px: 1,
|
||||
minW: 0,
|
||||
dir: "ltr"
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__PURE__ */ I(B, { spacing: 2, whiteSpace: "nowrap", children: [
|
||||
/* @__PURE__ */ o(v, { fontWeight: "bold", children: "تعداد کل:" }),
|
||||
/* @__PURE__ */ o(v, { children: t(C) })
|
||||
] })
|
||||
]
|
||||
}
|
||||
);
|
||||
}, H = ({
|
||||
currentPage: i,
|
||||
totalPages: n,
|
||||
totalCount: C,
|
||||
onPageChange: d
|
||||
}) => {
|
||||
const [c, l] = y(""), [a, f] = y(!1), [b, s] = y(null), x = M(null), w = b ?? i, k = _(
|
||||
(p) => {
|
||||
const h = Math.min(Math.max(p, 1), n);
|
||||
f(!1), l(""), s(null), h !== i && d(h);
|
||||
},
|
||||
[i, n, d]
|
||||
), e = _(() => {
|
||||
if (!c) {
|
||||
f(!1), l(""), s(null);
|
||||
return;
|
||||
}
|
||||
const p = parseInt(D(c), 10);
|
||||
if (isNaN(p)) {
|
||||
f(!1), l(""), s(null);
|
||||
return;
|
||||
}
|
||||
const h = Math.min(Math.max(p, 1), n);
|
||||
f(!1), l(""), h !== i ? (s(h), d(h)) : s(null);
|
||||
}, [c, i, n, d]);
|
||||
A(() => {
|
||||
var p;
|
||||
a && ((p = x.current) == null || p.focus());
|
||||
}, [a]), A(() => {
|
||||
b !== null && i === b && s(null);
|
||||
}, [i, b]), A(() => {
|
||||
if (!a || !c) return;
|
||||
const p = setTimeout(() => {
|
||||
e();
|
||||
}, 1e3);
|
||||
return () => clearTimeout(p);
|
||||
}, [c, a, e]);
|
||||
const u = () => {
|
||||
s(null), l(""), f(!0);
|
||||
}, r = (p, h = !1) => /* @__PURE__ */ o(
|
||||
S,
|
||||
{
|
||||
size: "md",
|
||||
bg: h ? "#4B49AC" : "#e6e6f1",
|
||||
color: h ? "#e6e6f1" : "#4B49AC",
|
||||
_hover: { bg: h ? "#4B49AC" : "#cbcbe8" },
|
||||
onClick: h ? u : () => k(p),
|
||||
variant: "soft",
|
||||
children: t(p)
|
||||
},
|
||||
p
|
||||
);
|
||||
if (n === 0 || !C) return null;
|
||||
const m = n > 1 && w !== 1, j = n > 1 && w !== n;
|
||||
return /* @__PURE__ */ o(
|
||||
B,
|
||||
{
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
py: "10px",
|
||||
px: "16px",
|
||||
rounded: "lg",
|
||||
border: "1px",
|
||||
borderColor: "blue.200",
|
||||
w: "100%",
|
||||
children: /* @__PURE__ */ I(B, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => k(w - 1),
|
||||
isDisabled: w === 1,
|
||||
icon: /* @__PURE__ */ o(z, {}),
|
||||
"aria-label": "صفحه قبلی"
|
||||
}
|
||||
),
|
||||
m && r(1),
|
||||
a ? /* @__PURE__ */ o(
|
||||
E,
|
||||
{
|
||||
ref: x,
|
||||
type: "text",
|
||||
inputMode: "numeric",
|
||||
placeholder: "صفحه",
|
||||
w: "48px",
|
||||
value: c,
|
||||
onChange: (p) => {
|
||||
const h = p.target.value, T = D(h).replace(/\D/g, "");
|
||||
l(t(T));
|
||||
},
|
||||
onBlur: () => {
|
||||
c ? e() : (l(""), f(!1), s(null));
|
||||
},
|
||||
onKeyDown: (p) => {
|
||||
p.key === "Enter" && e(), p.key === "Escape" && (l(""), f(!1), s(null));
|
||||
},
|
||||
textAlign: "center",
|
||||
color: "#1D2939",
|
||||
px: 1,
|
||||
minW: 0,
|
||||
dir: "ltr"
|
||||
}
|
||||
) : r(w, !0),
|
||||
j && r(n),
|
||||
/* @__PURE__ */ o(
|
||||
N,
|
||||
{
|
||||
size: "md",
|
||||
bg: "#e6e6f1",
|
||||
color: "#4B49AC",
|
||||
_hover: { bg: "#cbcbe8" },
|
||||
onClick: () => k(w + 1),
|
||||
isDisabled: w === n,
|
||||
icon: /* @__PURE__ */ o(V, {}),
|
||||
"aria-label": "صفحه بعدی"
|
||||
}
|
||||
)
|
||||
] })
|
||||
}
|
||||
);
|
||||
};
|
||||
export {
|
||||
O as LightPagination,
|
||||
L as Pagination,
|
||||
H as SimplePagination,
|
||||
D as toEnDigits,
|
||||
t as toFaDigits,
|
||||
K as toFaNumber
|
||||
};
|
||||
|
||||
@ -7,9 +7,6 @@
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
},
|
||||
|
||||
191
src/components/pagination/LightPagination.jsx
Normal file
191
src/components/pagination/LightPagination.jsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Button, HStack, IconButton, Input, Text } from "@chakra-ui/react";
|
||||
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
|
||||
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
|
||||
|
||||
export const LightPagination = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalCount,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState(toFaDigits(currentPage));
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [pendingPage, setPendingPage] = useState(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const pageNumbers = [...new Set([1, currentPage, totalPages])].filter(
|
||||
(page) => page >= 1 && page <= totalPages,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingPage !== null) {
|
||||
if (currentPage === pendingPage) {
|
||||
setPendingPage(null);
|
||||
setIsEditing(false);
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEditing) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
}, [currentPage, isEditing, pendingPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) return;
|
||||
if (!inputValue) return;
|
||||
if (pendingPage !== null) return;
|
||||
|
||||
const page = parseInt(toEnDigits(inputValue), 10);
|
||||
if (isNaN(page)) return;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
setPendingPage(safePage);
|
||||
setInputValue(toFaDigits(safePage));
|
||||
onPageChange(safePage);
|
||||
} else {
|
||||
setIsEditing(false);
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
|
||||
inputRef.current?.blur();
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [
|
||||
inputValue,
|
||||
isEditing,
|
||||
pendingPage,
|
||||
totalPages,
|
||||
currentPage,
|
||||
onPageChange,
|
||||
]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const page = parseInt(toEnDigits(inputValue), 10);
|
||||
|
||||
if (isNaN(page)) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
inputRef.current?.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
setPendingPage(safePage);
|
||||
setInputValue(toFaDigits(safePage));
|
||||
onPageChange(safePage);
|
||||
} else {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
|
||||
inputRef.current?.blur();
|
||||
};
|
||||
|
||||
if (totalPages === 0 || !totalCount) return null;
|
||||
|
||||
return (
|
||||
<HStack
|
||||
justifyContent={"space-around"}
|
||||
backgroundColor={"white"}
|
||||
py={"10px"}
|
||||
px={"16px"}
|
||||
rounded={"lg"}
|
||||
border={"1px"}
|
||||
borderColor={"blue.200"}
|
||||
w={"100%"}
|
||||
>
|
||||
<HStack spacing={2} alignItems={"center"} justifyContent={"center"}>
|
||||
<IconButton
|
||||
size={"md"}
|
||||
bg={"#e6e6f1"}
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
isDisabled={currentPage === 1}
|
||||
icon={<IoChevronForwardOutline />}
|
||||
aria-label="صفحه قبلی"
|
||||
/>
|
||||
|
||||
{pageNumbers.map((number) => (
|
||||
<Button
|
||||
size={"md"}
|
||||
px={2}
|
||||
bg={currentPage === number ? "#4B49AC" : "#e6e6f1"}
|
||||
color={currentPage === number ? "#e6e6f1" : "#4B49AC"}
|
||||
_hover={{ bg: currentPage === number ? "#4B49AC" : "#cbcbe8" }}
|
||||
key={number}
|
||||
onClick={() => onPageChange(number)}
|
||||
variant={"soft"}
|
||||
>
|
||||
{toFaDigits(number)}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
<IconButton
|
||||
size={"md"}
|
||||
bg={"#e6e6f1"}
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
isDisabled={currentPage === totalPages}
|
||||
icon={<IoChevronBackOutline />}
|
||||
aria-label="صفحه بعدی"
|
||||
/>
|
||||
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
placeholder="صفحه"
|
||||
w="56px"
|
||||
value={inputValue}
|
||||
onFocus={() => {
|
||||
setIsEditing(true);
|
||||
setPendingPage(null);
|
||||
setInputValue("");
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const rawValue = e.target.value;
|
||||
const onlyDigits = toEnDigits(rawValue).replace(/\D/g, "");
|
||||
setInputValue(toFaDigits(onlyDigits));
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!inputValue) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit();
|
||||
}
|
||||
}}
|
||||
textAlign="center"
|
||||
color="#1D2939"
|
||||
px={1}
|
||||
minW={0}
|
||||
dir="ltr"
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={2}>
|
||||
<Text w={"auto"} fontWeight={"bold"} whiteSpace={"nowrap"}>
|
||||
تعداد کل:
|
||||
</Text>
|
||||
<Text>{toFaDigits(totalCount)}</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
197
src/components/pagination/Pagination.jsx
Normal file
197
src/components/pagination/Pagination.jsx
Normal file
@ -0,0 +1,197 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Button, HStack, IconButton, Input, Text } from "@chakra-ui/react";
|
||||
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
|
||||
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
|
||||
|
||||
export const Pagination = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalCount,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState(toFaDigits(currentPage));
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [pendingPage, setPendingPage] = useState(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const pageNumbers = Array.from(
|
||||
new Set([1, currentPage, currentPage + 1, totalPages]),
|
||||
)
|
||||
.filter((page) => page >= 1 && page <= totalPages)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingPage !== null) {
|
||||
if (currentPage === pendingPage) {
|
||||
setPendingPage(null);
|
||||
setIsEditing(false);
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEditing) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
}, [currentPage, isEditing, pendingPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) return;
|
||||
if (!inputValue) return;
|
||||
if (pendingPage !== null) return;
|
||||
|
||||
const page = parseInt(toEnDigits(inputValue), 10);
|
||||
if (isNaN(page)) return;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
setPendingPage(safePage);
|
||||
setInputValue(toFaDigits(safePage));
|
||||
onPageChange(safePage);
|
||||
} else {
|
||||
setIsEditing(false);
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
}
|
||||
|
||||
inputRef.current?.blur();
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [
|
||||
inputValue,
|
||||
isEditing,
|
||||
pendingPage,
|
||||
totalPages,
|
||||
currentPage,
|
||||
onPageChange,
|
||||
]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const page = parseInt(toEnDigits(inputValue), 10);
|
||||
|
||||
if (isNaN(page)) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
inputRef.current?.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
setPendingPage(safePage);
|
||||
setInputValue(toFaDigits(safePage));
|
||||
onPageChange(safePage);
|
||||
} else {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
|
||||
inputRef.current?.blur();
|
||||
};
|
||||
|
||||
if (totalPages === 0 || !totalCount) return null;
|
||||
|
||||
return (
|
||||
<HStack
|
||||
justifyContent={"space-around"}
|
||||
backgroundColor={"white"}
|
||||
py={"10px"}
|
||||
px={"16px"}
|
||||
rounded={"lg"}
|
||||
border={"1px"}
|
||||
borderColor={"blue.200"}
|
||||
w={"100%"}
|
||||
>
|
||||
<HStack spacing={2} whiteSpace={"nowrap"}>
|
||||
<Text fontWeight={"bold"}>صفحه</Text>
|
||||
<Text>{toFaDigits(currentPage)}</Text>
|
||||
<Text fontWeight={"bold"}>از</Text>
|
||||
<Text>{toFaDigits(totalPages)}</Text>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={2} alignItems={"center"} justifyContent={"center"}>
|
||||
<IconButton
|
||||
size={"md"}
|
||||
bg={"#e6e6f1"}
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
isDisabled={currentPage === 1}
|
||||
icon={<IoChevronForwardOutline />}
|
||||
aria-label="صفحه قبلی"
|
||||
/>
|
||||
|
||||
{pageNumbers.map((number) => (
|
||||
<Button
|
||||
size={"md"}
|
||||
bg={currentPage === number ? "#4B49AC" : "#e6e6f1"}
|
||||
color={currentPage === number ? "#e6e6f1" : "#4B49AC"}
|
||||
_hover={{ bg: currentPage === number ? "#4B49AC" : "#cbcbe8" }}
|
||||
key={number}
|
||||
onClick={() => onPageChange(number)}
|
||||
variant={"soft"}
|
||||
>
|
||||
{toFaDigits(number)}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
<IconButton
|
||||
size={"md"}
|
||||
bg={"#e6e6f1"}
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
isDisabled={currentPage === totalPages}
|
||||
icon={<IoChevronBackOutline />}
|
||||
aria-label="صفحه بعدی"
|
||||
/>
|
||||
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
placeholder="صفحه"
|
||||
w="100px"
|
||||
value={inputValue}
|
||||
onFocus={() => {
|
||||
setIsEditing(true);
|
||||
setPendingPage(null);
|
||||
setInputValue("");
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const rawValue = e.target.value;
|
||||
const onlyDigits = toEnDigits(rawValue).replace(/\D/g, "");
|
||||
setInputValue(toFaDigits(onlyDigits));
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!inputValue) {
|
||||
setInputValue(toFaDigits(currentPage));
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit();
|
||||
}
|
||||
}}
|
||||
textAlign="center"
|
||||
color="#1D2939"
|
||||
px={1}
|
||||
minW={0}
|
||||
dir="ltr"
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={2} whiteSpace={"nowrap"}>
|
||||
<Text fontWeight={"bold"}>تعداد کل:</Text>
|
||||
<Text>{toFaDigits(totalCount)}</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
195
src/components/pagination/SimplePagination.jsx
Normal file
195
src/components/pagination/SimplePagination.jsx
Normal file
@ -0,0 +1,195 @@
|
||||
import { Button, HStack, IconButton, Input } from "@chakra-ui/react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
|
||||
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
|
||||
|
||||
export const SimplePagination = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalCount,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [pendingPage, setPendingPage] = useState(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const visibleCurrentPage = pendingPage ?? currentPage;
|
||||
|
||||
const goToPage = useCallback(
|
||||
(page) => {
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
setIsEditing(false);
|
||||
setInputValue("");
|
||||
setPendingPage(null);
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
onPageChange(safePage);
|
||||
}
|
||||
},
|
||||
[currentPage, totalPages, onPageChange],
|
||||
);
|
||||
|
||||
const commitInput = useCallback(() => {
|
||||
if (!inputValue) {
|
||||
setIsEditing(false);
|
||||
setInputValue("");
|
||||
setPendingPage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const page = parseInt(toEnDigits(inputValue), 10);
|
||||
|
||||
if (isNaN(page)) {
|
||||
setIsEditing(false);
|
||||
setInputValue("");
|
||||
setPendingPage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const safePage = Math.min(Math.max(page, 1), totalPages);
|
||||
|
||||
setIsEditing(false);
|
||||
setInputValue("");
|
||||
|
||||
if (safePage !== currentPage) {
|
||||
setPendingPage(safePage);
|
||||
onPageChange(safePage);
|
||||
} else {
|
||||
setPendingPage(null);
|
||||
}
|
||||
}, [inputValue, currentPage, totalPages, onPageChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingPage !== null && currentPage === pendingPage) {
|
||||
setPendingPage(null);
|
||||
}
|
||||
}, [currentPage, pendingPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) return;
|
||||
if (!inputValue) return;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
commitInput();
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [inputValue, isEditing, commitInput]);
|
||||
|
||||
const startEditing = () => {
|
||||
setPendingPage(null);
|
||||
setInputValue("");
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const renderPageButton = (number, isCurrent = false) => (
|
||||
<Button
|
||||
size="md"
|
||||
bg={isCurrent ? "#4B49AC" : "#e6e6f1"}
|
||||
color={isCurrent ? "#e6e6f1" : "#4B49AC"}
|
||||
_hover={{ bg: isCurrent ? "#4B49AC" : "#cbcbe8" }}
|
||||
key={number}
|
||||
onClick={isCurrent ? startEditing : () => goToPage(number)}
|
||||
variant="soft"
|
||||
>
|
||||
{toFaDigits(number)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (totalPages === 0 || !totalCount) return null;
|
||||
|
||||
const showFirstButton = totalPages > 1 && visibleCurrentPage !== 1;
|
||||
const showLastButton = totalPages > 1 && visibleCurrentPage !== totalPages;
|
||||
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="center"
|
||||
backgroundColor="white"
|
||||
py="10px"
|
||||
px="16px"
|
||||
rounded="lg"
|
||||
border="1px"
|
||||
borderColor="blue.200"
|
||||
w="100%"
|
||||
>
|
||||
<HStack spacing={2} alignItems="center" justifyContent="center">
|
||||
<IconButton
|
||||
size="md"
|
||||
bg="#e6e6f1"
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => goToPage(visibleCurrentPage - 1)}
|
||||
isDisabled={visibleCurrentPage === 1}
|
||||
icon={<IoChevronForwardOutline />}
|
||||
aria-label="صفحه قبلی"
|
||||
/>
|
||||
|
||||
{showFirstButton && renderPageButton(1)}
|
||||
|
||||
{isEditing ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
placeholder="صفحه"
|
||||
w="48px"
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
const rawValue = e.target.value;
|
||||
const onlyDigits = toEnDigits(rawValue).replace(/\D/g, "");
|
||||
setInputValue(toFaDigits(onlyDigits));
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (inputValue) {
|
||||
commitInput();
|
||||
} else {
|
||||
setInputValue("");
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
commitInput();
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
setInputValue("");
|
||||
setIsEditing(false);
|
||||
setPendingPage(null);
|
||||
}
|
||||
}}
|
||||
textAlign="center"
|
||||
color="#1D2939"
|
||||
px={1}
|
||||
minW={0}
|
||||
dir="ltr"
|
||||
/>
|
||||
) : (
|
||||
renderPageButton(visibleCurrentPage, true)
|
||||
)}
|
||||
|
||||
{showLastButton && renderPageButton(totalPages)}
|
||||
|
||||
<IconButton
|
||||
size="md"
|
||||
bg="#e6e6f1"
|
||||
color="#4B49AC"
|
||||
_hover={{ bg: "#cbcbe8" }}
|
||||
onClick={() => goToPage(visibleCurrentPage + 1)}
|
||||
isDisabled={visibleCurrentPage === totalPages}
|
||||
icon={<IoChevronBackOutline />}
|
||||
aria-label="صفحه بعدی"
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
@ -1 +1,5 @@
|
||||
export { toFaNumber, toFaDigits, toEnDigits } from "./utils/numbers.js";
|
||||
|
||||
export { LightPagination } from "./components/pagination/LightPagination.jsx";
|
||||
export { Pagination } from "./components/pagination/Pagination.jsx";
|
||||
export { SimplePagination } from "./components/pagination/SimplePagination.jsx";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user