add remote Pagination
All checks were successful
Build and Deploy Next.js + Nginx Docker Image / build-and-deploy (push) Successful in 1m52s
Build and Deploy Next.js + Nginx Docker Image / deploy (push) Successful in 7s

This commit is contained in:
Mohamadzadeh 2026-07-14 10:39:25 +03:30
parent 0a14270aaa
commit c8ec2e3759
8 changed files with 1714 additions and 188 deletions

63
dist/remote-ui.d.ts vendored
View File

@ -1,23 +1,54 @@
import type { ComponentType, ReactNode } from "react";
import type { BadgeProps, StackProps } from "@chakra-ui/react";
import type { ReactNode } from "react";
export type UikitRemoteDepsOptions = {
React?: unknown;
createRoot?: unknown;
ChakraProvider?: unknown;
Badge?: unknown;
theme?: unknown;
chakraProviderProps?: Record<string, unknown>;
export type UikitRemoteLevel = "full" | "compact" | "simple";
export interface UikitRemoteSetupOptions {
React?: any;
createRoot?: any;
ChakraProvider?: any;
Badge?: any;
Button?: any;
HStack?: any;
IconButton?: any;
Input?: any;
Text?: any;
theme?: any;
chakraProviderProps?: Record<string, any>;
/** Backward compatible alias for GBadgeUrl. */
remoteUrl?: string;
};
GBadgeUrl?: string;
PaginationUrl?: string;
remoteUrls?: {
GBadge?: string;
Pagination?: string;
};
}
export declare function setupUikitRemoteDeps(options?: UikitRemoteDepsOptions): void;
export declare function setupUikitRemoteDeps(
options?: UikitRemoteSetupOptions,
): void;
export type GBadgeProps = {
export interface GBadgeProps extends BadgeProps {
children?: ReactNode;
remoteUrl?: string;
theme?: unknown;
chakraProviderProps?: Record<string, unknown>;
[key: string]: unknown;
};
theme?: any;
chakraProviderProps?: Record<string, any>;
}
export declare const GBadge: ComponentType<GBadgeProps>;
export declare const GBadge: (props: GBadgeProps) => JSX.Element;
export interface PaginationProps extends Omit<StackProps, "onChange"> {
currentPage?: number;
totalCount?: number;
totalPages?: number;
pageSize?: number;
onPageChange?: (page: number) => void;
level?: UikitRemoteLevel;
remoteUrl?: string;
theme?: any;
chakraProviderProps?: Record<string, any>;
}
export declare const Pagination: (props: PaginationProps) => JSX.Element;

185
dist/remote-ui.js vendored
View File

@ -1,93 +1,138 @@
import { jsx as g } from "react/jsx-runtime";
import h, { useRef as u, useEffect as m } from "react";
import { createRoot as _ } from "react-dom/client";
import { Badge as E, ChakraProvider as P } from "@chakra-ui/react";
const R = "https://uikit.d.aiengines.ir/GBadge.js", d = "__UIKIT_REMOTE_DEPS__", f = "__UIKIT_REMOTE_CONFIG__", i = /* @__PURE__ */ new Map();
function w(e = {}) {
import { jsx as E } from "react/jsx-runtime";
import p, { useRef as s, useEffect as R } from "react";
import { createRoot as k } from "react-dom/client";
import { Text as U, Input as _, IconButton as I, HStack as v, Button as G, Badge as C, ChakraProvider as T } from "@chakra-ui/react";
const h = {
GBadge: "https://uikit.d.aiengines.ir/GBadge.js",
Pagination: "https://uikit.d.aiengines.ir/Pagination.js"
}, l = "__UIKIT_REMOTE_DEPS__", g = "__UIKIT_REMOTE_CONFIG__", u = /* @__PURE__ */ new Map(), y = {
GBadge: ["React", "createRoot", "ChakraProvider", "Badge"],
Pagination: [
"React",
"createRoot",
"ChakraProvider",
"Button",
"HStack",
"IconButton",
"Input",
"Text"
]
};
function D(e = {}) {
if (typeof window > "u") return;
const r = window[d] || {}, n = window[f] || {};
window[d] = {
...r,
React: e.React || r.React || h,
createRoot: e.createRoot || r.createRoot || _,
ChakraProvider: e.ChakraProvider || r.ChakraProvider || P,
Badge: e.Badge || r.Badge || E,
theme: Object.prototype.hasOwnProperty.call(e, "theme") ? e.theme : r.theme,
const t = window[l] || {}, r = window[g] || {};
window[l] = {
...t,
React: e.React || t.React || p,
createRoot: e.createRoot || t.createRoot || k,
ChakraProvider: e.ChakraProvider || t.ChakraProvider || T,
Badge: e.Badge || t.Badge || C,
Button: e.Button || t.Button || G,
HStack: e.HStack || t.HStack || v,
IconButton: e.IconButton || t.IconButton || I,
Input: e.Input || t.Input || _,
Text: e.Text || t.Text || U,
theme: Object.prototype.hasOwnProperty.call(e, "theme") ? e.theme : t.theme,
chakraProviderProps: {
...r.chakraProviderProps || {},
...t.chakraProviderProps || {},
...e.chakraProviderProps || {}
}
}, window[f] = {
...n,
...e.remoteUrl ? { GBadgeUrl: e.remoteUrl } : {}
};
const n = e.remoteUrls || {};
window[g] = {
...r,
...e.remoteUrl ? { GBadgeUrl: e.remoteUrl } : {},
...e.GBadgeUrl ? { GBadgeUrl: e.GBadgeUrl } : {},
...e.PaginationUrl ? { PaginationUrl: e.PaginationUrl } : {},
...n.GBadge ? { GBadgeUrl: n.GBadge } : {},
...n.Pagination ? { PaginationUrl: n.Pagination } : {}
};
}
function v() {
function O(e) {
if (typeof window > "u") return;
const e = window[d];
(!(e != null && e.React) || !(e != null && e.createRoot) || !(e != null && e.ChakraProvider) || !(e != null && e.Badge)) && w();
const t = window[l];
(y[e] || []).some((o) => !(t != null && t[o])) && D();
}
function k() {
var e;
return typeof window > "u" ? R : ((e = window[f]) == null ? void 0 : e.GBadgeUrl) || R;
function S(e, t) {
var r;
return t || (typeof window > "u" ? h[e] : ((r = window[g]) == null ? void 0 : r[`${e}Url`]) || h[e]);
}
function y() {
const e = k();
return i.has(e) || i.set(
e,
function M(e, t) {
const r = S(e, t), n = `${e}:${r}`;
return u.has(n) || u.set(
n,
import(
/* webpackIgnore: true */
/* @vite-ignore */
e
).catch((r) => {
throw i.delete(e), r;
r
).catch((o) => {
throw u.delete(n), o;
})
), i.get(e);
), u.get(n);
}
function B(e) {
function x(e) {
if (typeof e == "string" || typeof e == "number")
return e;
e != null && console.warn(
"[uikit/remote/ui] GBadge only accepts text/number children across the remote boundary."
);
}
const T = ({
children: e,
remoteUrl: r,
theme: n,
chakraProviderProps: s,
...p
}) => {
const a = u(null), t = u(null), c = u(null);
return c.current = {
...p,
children: B(e),
...n !== void 0 ? { theme: n } : {},
...s ? { chakraProviderProps: s } : {}
}, m(() => {
let l = !0;
return v(), r && w({ remoteUrl: r }), y().then(() => {
if (!l || !a.current) return;
const o = document.createElement("uikit-g-badge");
o.componentProps = c.current, a.current.appendChild(o), t.current = o;
}).catch((o) => {
console.error("[uikit/remote/ui] Failed to load GBadge:", o);
}), () => {
l = !1, t.current && (t.current.remove(), t.current = null);
};
}, [r]), m(() => {
t.current && (t.current.componentProps = c.current);
}), /* @__PURE__ */ g(
"span",
{
ref: a,
style: {
display: "contents"
function w({
componentName: e,
tagName: t,
normalizeProps: r
}) {
return function({
remoteUrl: o,
theme: f,
chakraProviderProps: m,
...B
}) {
const c = s(null), a = s(null), d = s(null);
return d.current = {
...r ? r(B) : B,
...f !== void 0 ? { theme: f } : {},
...m ? { chakraProviderProps: m } : {}
}, R(() => {
let P = !0;
return O(e), M(e, o).then(() => {
if (!P || !c.current) return;
const i = document.createElement(t);
i.componentProps = d.current, c.current.appendChild(i), a.current = i;
}).catch((i) => {
console.error(
`[uikit/remote/ui] Failed to load ${e}:`,
i
);
}), () => {
P = !1, a.current && (a.current.remove(), a.current = null);
};
}, [e, o, t]), R(() => {
a.current && (a.current.componentProps = d.current);
}), /* @__PURE__ */ E(
"span",
{
ref: c,
style: {
display: "contents"
}
}
}
);
};
);
};
}
const F = w({
componentName: "GBadge",
tagName: "uikit-g-badge",
normalizeProps: ({ children: e, ...t }) => ({
...t,
children: x(e)
})
}), $ = w({
componentName: "Pagination",
tagName: "uikit-pagination"
});
export {
T as GBadge,
w as setupUikitRemoteDeps
F as GBadge,
$ as Pagination,
D as setupUikitRemoteDeps
};

335
dist/remote/Pagination.js vendored Normal file
View File

@ -0,0 +1,335 @@
const H = {
full: {
showPageSummary: !0,
showTotalCount: !0,
showInput: !0,
inputWidth: "100px",
justifyContent: "space-around",
pageButtonPx: void 0,
getPageNumbers: ({ currentPage: t, totalPages: e }) => [
1,
t,
t + 1,
e
]
},
compact: {
showPageSummary: !1,
showTotalCount: !0,
showInput: !0,
inputWidth: "56px",
justifyContent: "space-around",
pageButtonPx: 2,
getPageNumbers: ({ currentPage: t, totalPages: e }) => [
1,
t,
e
]
},
simple: {
showPageSummary: !1,
showTotalCount: !1,
showInput: !1,
inputWidth: "48px",
justifyContent: "center",
pageButtonPx: void 0,
getPageNumbers: () => []
}
}, V = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"], ne = /[۰-۹]/g, oe = /[٠-٩]/g;
function k() {
const t = window.__UIKIT_REMOTE_DEPS__, e = [];
if (t?.React || e.push("React"), t?.createRoot || e.push("createRoot"), t?.ChakraProvider || e.push("ChakraProvider"), t?.Button || e.push("Button"), t?.HStack || e.push("HStack"), t?.IconButton || e.push("IconButton"), t?.Input || e.push("Input"), t?.Text || e.push("Text"), e.length)
throw new Error(
`[uikit-pagination] Missing remote dependencies: ${e.join(", ")}. Set window.__UIKIT_REMOTE_DEPS__ before loading the remote component.`
);
return t;
}
function a(t) {
return t == null ? "" : String(t).replace(/\d/g, (e) => V[Number(e)]);
}
function G(t) {
return t == null ? "" : String(t).replace(
ne,
(e) => String(V.indexOf(e))
).replace(oe, (e) => String(e.charCodeAt(0) - 1632));
}
function re(t) {
return H[t] ? t : "full";
}
function W(t, e) {
const m = Number(t), g = Number(e);
return !g || g <= 0 || !m || Number.isNaN(m) ? 1 : Math.min(Math.max(m, 1), g);
}
function ie(t) {
const { React: e } = k();
return e.createElement(
"svg",
{
width: "20",
height: "20",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
...t
},
e.createElement("path", {
d: "M15 18L9 12L15 6",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round"
})
);
}
function se(t) {
const { React: e } = k();
return e.createElement(
"svg",
{
width: "20",
height: "20",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
...t
},
e.createElement("path", {
d: "M9 18L15 12L9 6",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round"
})
);
}
function ue({
currentPage: t = 1,
totalCount: e = 0,
totalPages: m,
pageSize: g = 10,
onPageChange: h,
level: v = "full",
...B
}) {
const { React: n, Button: K, HStack: S, IconButton: j, Input: O, Text: P } = k(), { useCallback: I, useEffect: N, useMemo: U, useRef: X, useState: R } = n, F = re(v), f = H[F], r = F === "simple", y = Number(g), $ = Number.isFinite(y) && y > 0 ? y : 10, L = Number(e), D = Number.isFinite(L) && L > 0 ? L : 0, A = Number(m), u = Number.isFinite(A) && A > 0 ? Math.ceil(A) : Math.ceil(D / $), i = W(t, u), [d, c] = R(
r ? "" : a(i)
), [E, b] = R(!1), [p, l] = R(null), C = X(null), w = p ?? i, q = D > 0, J = U(() => r || u <= 0 ? [] : Array.from(
new Set(
f.getPageNumbers({
currentPage: i,
totalPages: u
})
)
).filter((o) => o >= 1 && o <= u).sort((o, s) => o - s), [f, r, i, u]), _ = I(() => {
c(r ? "" : a(i)), b(!1), l(null);
}, [r, i]), M = I(
(o) => {
const s = W(o, u);
b(!1), c(r ? "" : a(i)), l(null), s !== i && h?.(s);
},
[r, h, i, u]
), x = I(() => {
if (!d) {
_(), C.current?.blur();
return;
}
const o = parseInt(G(d), 10);
if (Number.isNaN(o)) {
_(), C.current?.blur();
return;
}
const s = W(o, u);
if (r) {
b(!1), c(""), s !== i ? (l(s), h?.(s)) : l(null), C.current?.blur();
return;
}
s !== i ? (l(s), c(a(s)), h?.(s)) : (c(a(i)), b(!1), l(null)), C.current?.blur();
}, [
d,
r,
h,
_,
i,
u
]), Q = I(() => {
l(null), c(""), b(!0);
}, []), Y = I((o) => {
const s = o.target.value, te = G(s).replace(/\D/g, "");
c(a(te));
}, []);
if (N(() => {
if (r) {
p !== null && i === p && l(null);
return;
}
if (p !== null) {
i === p && (l(null), b(!1), c(a(i)));
return;
}
E || c(a(i));
}, [E, r, p, i]), N(() => {
r && E && C.current?.focus();
}, [E, r]), N(() => {
if (!E || !d || !r && p !== null) return;
const o = setTimeout(() => {
x();
}, 1e3);
return () => clearTimeout(o);
}, [x, d, E, r, p]), u <= 0) return null;
const z = () => n.createElement(O, {
ref: C,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: f.inputWidth,
value: d,
onFocus: () => {
r || (b(!0), l(null), c(""));
},
onChange: Y,
onBlur: () => {
if (d) {
r && x();
return;
}
_();
},
onKeyDown: (o) => {
o.key === "Enter" && x(), o.key === "Escape" && _();
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr"
}), T = (o, s = !1) => n.createElement(
K,
{
key: o,
size: "md",
px: f.pageButtonPx,
bg: s ? "#4B49AC" : "#e6e6f1",
color: s ? "#e6e6f1" : "#4B49AC",
_hover: { bg: s ? "#4B49AC" : "#cbcbe8" },
onClick: r && s ? Q : () => M(o),
variant: "soft"
},
a(o)
), Z = r && u > 1 && w !== 1, ee = r && u > 1 && w !== u;
return n.createElement(
S,
{
justifyContent: f.justifyContent,
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
...B
},
f.showPageSummary && n.createElement(
S,
{ spacing: 2, whiteSpace: "nowrap" },
n.createElement(P, { fontWeight: "bold" }, "صفحه"),
n.createElement(P, null, a(i)),
n.createElement(P, { fontWeight: "bold" }, "از"),
n.createElement(P, null, a(u))
),
n.createElement(
S,
{ spacing: 2, alignItems: "center", justifyContent: "center" },
n.createElement(j, {
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => M(w - 1),
isDisabled: w === 1,
icon: n.createElement(ie),
"aria-label": "صفحه قبلی"
}),
r ? [
Z ? T(1) : null,
E ? n.createElement(
n.Fragment,
{ key: "input" },
z()
) : T(w, !0),
ee ? T(u) : null
] : [
...J.map(
(o) => T(o, i === o)
),
f.showInput ? n.createElement(
n.Fragment,
{ key: "input" },
z()
) : null
],
n.createElement(j, {
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => M(w + 1),
isDisabled: w === u,
icon: n.createElement(se),
"aria-label": "صفحه بعدی"
})
),
f.showTotalCount && q && n.createElement(
S,
{ spacing: 2, whiteSpace: "nowrap" },
n.createElement(
P,
{ w: "auto", fontWeight: "bold" },
"تعداد کل:"
),
n.createElement(P, null, a(D))
)
);
}
class ae extends HTMLElement {
constructor() {
super(), this._props = {}, this._root = null;
}
connectedCallback() {
const { createRoot: e } = k();
this._root || (this._root = e(this)), this.renderComponent();
}
disconnectedCallback() {
this._root && (this._root.unmount(), this._root = null);
}
set componentProps(e) {
this._props = e ?? {}, this.isConnected && this.renderComponent();
}
get componentProps() {
return this._props;
}
renderComponent() {
if (!this._root) return;
const {
React: e,
ChakraProvider: m,
theme: g,
chakraProviderProps: h
} = k(), { theme: v, chakraProviderProps: B, ...n } = this._props || {};
this._root.render(
e.createElement(
m,
{
theme: v || g,
resetCSS: !1,
...h || {},
...B || {}
},
e.createElement(ue, n)
)
);
}
}
customElements.get("uikit-pagination") || customElements.define("uikit-pagination", ae);

View File

@ -0,0 +1,560 @@
// Remote Web Component for Pagination.
// No imports. React, createRoot and Chakra components are provided by the host via window.__UIKIT_REMOTE_DEPS__.
const LEVELS = {
full: {
showPageSummary: true,
showTotalCount: true,
showInput: true,
inputWidth: "100px",
justifyContent: "space-around",
pageButtonPx: undefined,
getPageNumbers: ({ currentPage, totalPages }) => [
1,
currentPage,
currentPage + 1,
totalPages,
],
},
compact: {
showPageSummary: false,
showTotalCount: true,
showInput: true,
inputWidth: "56px",
justifyContent: "space-around",
pageButtonPx: 2,
getPageNumbers: ({ currentPage, totalPages }) => [
1,
currentPage,
totalPages,
],
},
simple: {
showPageSummary: false,
showTotalCount: false,
showInput: false,
inputWidth: "48px",
justifyContent: "center",
pageButtonPx: undefined,
getPageNumbers: () => [],
},
};
const PERSIAN_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"];
const PERSIAN_DIGIT_REGEX = /[۰-۹]/g;
const ARABIC_DIGIT_REGEX = /[٠-٩]/g;
function getRemoteDeps() {
const deps = window.__UIKIT_REMOTE_DEPS__;
const missing = [];
if (!deps?.React) missing.push("React");
if (!deps?.createRoot) missing.push("createRoot");
if (!deps?.ChakraProvider) missing.push("ChakraProvider");
if (!deps?.Button) missing.push("Button");
if (!deps?.HStack) missing.push("HStack");
if (!deps?.IconButton) missing.push("IconButton");
if (!deps?.Input) missing.push("Input");
if (!deps?.Text) missing.push("Text");
if (missing.length) {
throw new Error(
`[uikit-pagination] Missing remote dependencies: ${missing.join(", ")}. ` +
"Set window.__UIKIT_REMOTE_DEPS__ before loading the remote component.",
);
}
return deps;
}
function toFaDigits(value) {
if (value === undefined || value === null) return "";
return String(value).replace(/\d/g, (digit) => PERSIAN_DIGITS[Number(digit)]);
}
function toEnDigits(value) {
if (value === undefined || value === null) return "";
return String(value)
.replace(PERSIAN_DIGIT_REGEX, (digit) =>
String(PERSIAN_DIGITS.indexOf(digit)),
)
.replace(ARABIC_DIGIT_REGEX, (digit) => String(digit.charCodeAt(0) - 1632));
}
function normalizeLevel(level) {
if (LEVELS[level]) return level;
return "full";
}
function getSafePage(page, totalPages) {
const numericPage = Number(page);
const numericTotalPages = Number(totalPages);
if (!numericTotalPages || numericTotalPages <= 0) return 1;
if (!numericPage || Number.isNaN(numericPage)) return 1;
return Math.min(Math.max(numericPage, 1), numericTotalPages);
}
function ChevronRightIcon(props) {
const { React } = getRemoteDeps();
return React.createElement(
"svg",
{
width: "20",
height: "20",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
...props,
},
React.createElement("path", {
d: "M15 18L9 12L15 6",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
}),
);
}
function ChevronLeftIcon(props) {
const { React } = getRemoteDeps();
return React.createElement(
"svg",
{
width: "20",
height: "20",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
...props,
},
React.createElement("path", {
d: "M9 18L15 12L9 6",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
}),
);
}
function RemotePagination({
currentPage = 1,
totalCount = 0,
totalPages,
pageSize = 10,
onPageChange,
level = "full",
...containerProps
}) {
const { React, Button, HStack, IconButton, Input, Text } = getRemoteDeps();
const { useCallback, useEffect, useMemo, useRef, useState } = React;
const resolvedLevel = normalizeLevel(level);
const config = LEVELS[resolvedLevel];
const isSimple = resolvedLevel === "simple";
const numericPageSize = Number(pageSize);
const safePageSize =
Number.isFinite(numericPageSize) && numericPageSize > 0
? numericPageSize
: 10;
const numericTotalCount = Number(totalCount);
const safeTotalCount =
Number.isFinite(numericTotalCount) && numericTotalCount > 0
? numericTotalCount
: 0;
const numericTotalPages = Number(totalPages);
const safeTotalPages =
Number.isFinite(numericTotalPages) && numericTotalPages > 0
? Math.ceil(numericTotalPages)
: Math.ceil(safeTotalCount / safePageSize);
const safeCurrentPage = getSafePage(currentPage, safeTotalPages);
const [inputValue, setInputValue] = useState(
isSimple ? "" : toFaDigits(safeCurrentPage),
);
const [isEditing, setIsEditing] = useState(false);
const [pendingPage, setPendingPage] = useState(null);
const inputRef = useRef(null);
const visibleCurrentPage = pendingPage ?? safeCurrentPage;
const hasTotalCount = safeTotalCount > 0;
const pageNumbers = useMemo(() => {
if (isSimple || safeTotalPages <= 0) return [];
return Array.from(
new Set(
config.getPageNumbers({
currentPage: safeCurrentPage,
totalPages: safeTotalPages,
}),
),
)
.filter((page) => page >= 1 && page <= safeTotalPages)
.sort((a, b) => a - b);
}, [config, isSimple, safeCurrentPage, safeTotalPages]);
const resetInput = useCallback(() => {
setInputValue(isSimple ? "" : toFaDigits(safeCurrentPage));
setIsEditing(false);
setPendingPage(null);
}, [isSimple, safeCurrentPage]);
const goToPage = useCallback(
(page) => {
const safePage = getSafePage(page, safeTotalPages);
setIsEditing(false);
setInputValue(isSimple ? "" : toFaDigits(safeCurrentPage));
setPendingPage(null);
if (safePage !== safeCurrentPage) {
onPageChange?.(safePage);
}
},
[isSimple, onPageChange, safeCurrentPage, safeTotalPages],
);
const commitInput = useCallback(() => {
if (!inputValue) {
resetInput();
inputRef.current?.blur();
return;
}
const page = parseInt(toEnDigits(inputValue), 10);
if (Number.isNaN(page)) {
resetInput();
inputRef.current?.blur();
return;
}
const safePage = getSafePage(page, safeTotalPages);
if (isSimple) {
setIsEditing(false);
setInputValue("");
if (safePage !== safeCurrentPage) {
setPendingPage(safePage);
onPageChange?.(safePage);
} else {
setPendingPage(null);
}
inputRef.current?.blur();
return;
}
if (safePage !== safeCurrentPage) {
setPendingPage(safePage);
setInputValue(toFaDigits(safePage));
onPageChange?.(safePage);
} else {
setInputValue(toFaDigits(safeCurrentPage));
setIsEditing(false);
setPendingPage(null);
}
inputRef.current?.blur();
}, [
inputValue,
isSimple,
onPageChange,
resetInput,
safeCurrentPage,
safeTotalPages,
]);
const startEditing = useCallback(() => {
setPendingPage(null);
setInputValue("");
setIsEditing(true);
}, []);
const handleInputChange = useCallback((event) => {
const rawValue = event.target.value;
const onlyDigits = toEnDigits(rawValue).replace(/\D/g, "");
setInputValue(toFaDigits(onlyDigits));
}, []);
useEffect(() => {
if (isSimple) {
if (pendingPage !== null && safeCurrentPage === pendingPage) {
setPendingPage(null);
}
return;
}
if (pendingPage !== null) {
if (safeCurrentPage === pendingPage) {
setPendingPage(null);
setIsEditing(false);
setInputValue(toFaDigits(safeCurrentPage));
}
return;
}
if (!isEditing) {
setInputValue(toFaDigits(safeCurrentPage));
}
}, [isEditing, isSimple, pendingPage, safeCurrentPage]);
useEffect(() => {
if (isSimple && isEditing) {
inputRef.current?.focus();
}
}, [isEditing, isSimple]);
useEffect(() => {
if (!isEditing) return;
if (!inputValue) return;
if (!isSimple && pendingPage !== null) return;
const timeout = setTimeout(() => {
commitInput();
}, 1000);
return () => clearTimeout(timeout);
}, [commitInput, inputValue, isEditing, isSimple, pendingPage]);
if (safeTotalPages <= 0) return null;
const renderInput = () =>
React.createElement(Input, {
ref: inputRef,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: config.inputWidth,
value: inputValue,
onFocus: () => {
if (!isSimple) {
setIsEditing(true);
setPendingPage(null);
setInputValue("");
}
},
onChange: handleInputChange,
onBlur: () => {
if (inputValue) {
if (isSimple) {
commitInput();
}
return;
}
resetInput();
},
onKeyDown: (event) => {
if (event.key === "Enter") {
commitInput();
}
if (event.key === "Escape") {
resetInput();
}
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr",
});
const renderPageButton = (number, isCurrent = false) =>
React.createElement(
Button,
{
key: number,
size: "md",
px: config.pageButtonPx,
bg: isCurrent ? "#4B49AC" : "#e6e6f1",
color: isCurrent ? "#e6e6f1" : "#4B49AC",
_hover: { bg: isCurrent ? "#4B49AC" : "#cbcbe8" },
onClick: isSimple && isCurrent ? startEditing : () => goToPage(number),
variant: "soft",
},
toFaDigits(number),
);
const showFirstButton =
isSimple && safeTotalPages > 1 && visibleCurrentPage !== 1;
const showLastButton =
isSimple && safeTotalPages > 1 && visibleCurrentPage !== safeTotalPages;
return React.createElement(
HStack,
{
justifyContent: config.justifyContent,
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
...containerProps,
},
config.showPageSummary &&
React.createElement(
HStack,
{ spacing: 2, whiteSpace: "nowrap" },
React.createElement(Text, { fontWeight: "bold" }, "صفحه"),
React.createElement(Text, null, toFaDigits(safeCurrentPage)),
React.createElement(Text, { fontWeight: "bold" }, "از"),
React.createElement(Text, null, toFaDigits(safeTotalPages)),
),
React.createElement(
HStack,
{ spacing: 2, alignItems: "center", justifyContent: "center" },
React.createElement(IconButton, {
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => goToPage(visibleCurrentPage - 1),
isDisabled: visibleCurrentPage === 1,
icon: React.createElement(ChevronRightIcon),
"aria-label": "صفحه قبلی",
}),
isSimple
? [
showFirstButton ? renderPageButton(1) : null,
isEditing
? React.createElement(
React.Fragment,
{ key: "input" },
renderInput(),
)
: renderPageButton(visibleCurrentPage, true),
showLastButton ? renderPageButton(safeTotalPages) : null,
]
: [
...pageNumbers.map((number) =>
renderPageButton(number, safeCurrentPage === number),
),
config.showInput
? React.createElement(
React.Fragment,
{ key: "input" },
renderInput(),
)
: null,
],
React.createElement(IconButton, {
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => goToPage(visibleCurrentPage + 1),
isDisabled: visibleCurrentPage === safeTotalPages,
icon: React.createElement(ChevronLeftIcon),
"aria-label": "صفحه بعدی",
}),
),
config.showTotalCount &&
hasTotalCount &&
React.createElement(
HStack,
{ spacing: 2, whiteSpace: "nowrap" },
React.createElement(
Text,
{ w: "auto", fontWeight: "bold" },
"تعداد کل:",
),
React.createElement(Text, null, toFaDigits(safeTotalCount)),
),
);
}
class PaginationElement extends HTMLElement {
constructor() {
super();
this._props = {};
this._root = null;
}
connectedCallback() {
const { createRoot } = getRemoteDeps();
if (!this._root) {
this._root = createRoot(this);
}
this.renderComponent();
}
disconnectedCallback() {
if (this._root) {
this._root.unmount();
this._root = null;
}
}
set componentProps(value) {
this._props = value ?? {};
if (this.isConnected) {
this.renderComponent();
}
}
get componentProps() {
return this._props;
}
renderComponent() {
if (!this._root) return;
const {
React,
ChakraProvider,
theme: defaultTheme,
chakraProviderProps: defaultProviderProps,
} = getRemoteDeps();
const { theme, chakraProviderProps, ...paginationProps } =
this._props || {};
this._root.render(
React.createElement(
ChakraProvider,
{
theme: theme || defaultTheme,
resetCSS: false,
...(defaultProviderProps || {}),
...(chakraProviderProps || {}),
},
React.createElement(RemotePagination, paginationProps),
),
);
}
}
if (!customElements.get("uikit-pagination")) {
customElements.define("uikit-pagination", PaginationElement);
}

View File

@ -1,23 +1,54 @@
import type { ComponentType, ReactNode } from "react";
import type { BadgeProps, StackProps } from "@chakra-ui/react";
import type { ReactNode } from "react";
export type UikitRemoteDepsOptions = {
React?: unknown;
createRoot?: unknown;
ChakraProvider?: unknown;
Badge?: unknown;
theme?: unknown;
chakraProviderProps?: Record<string, unknown>;
export type UikitRemoteLevel = "full" | "compact" | "simple";
export interface UikitRemoteSetupOptions {
React?: any;
createRoot?: any;
ChakraProvider?: any;
Badge?: any;
Button?: any;
HStack?: any;
IconButton?: any;
Input?: any;
Text?: any;
theme?: any;
chakraProviderProps?: Record<string, any>;
/** Backward compatible alias for GBadgeUrl. */
remoteUrl?: string;
};
GBadgeUrl?: string;
PaginationUrl?: string;
remoteUrls?: {
GBadge?: string;
Pagination?: string;
};
}
export declare function setupUikitRemoteDeps(options?: UikitRemoteDepsOptions): void;
export declare function setupUikitRemoteDeps(
options?: UikitRemoteSetupOptions,
): void;
export type GBadgeProps = {
export interface GBadgeProps extends BadgeProps {
children?: ReactNode;
remoteUrl?: string;
theme?: unknown;
chakraProviderProps?: Record<string, unknown>;
[key: string]: unknown;
};
theme?: any;
chakraProviderProps?: Record<string, any>;
}
export declare const GBadge: ComponentType<GBadgeProps>;
export declare const GBadge: (props: GBadgeProps) => JSX.Element;
export interface PaginationProps extends Omit<StackProps, "onChange"> {
currentPage?: number;
totalCount?: number;
totalPages?: number;
pageSize?: number;
onPageChange?: (page: number) => void;
level?: UikitRemoteLevel;
remoteUrl?: string;
theme?: any;
chakraProviderProps?: Record<string, any>;
}
export declare const Pagination: (props: PaginationProps) => JSX.Element;

View File

@ -2,14 +2,40 @@
import React, { useEffect, useRef } from "react";
import { createRoot } from "react-dom/client";
import { Badge, ChakraProvider } from "@chakra-ui/react";
import {
Badge,
Button,
ChakraProvider,
HStack,
IconButton,
Input,
Text,
} from "@chakra-ui/react";
const DEFAULT_REMOTE_URLS = {
GBadge: "https://uikit.d.aiengines.ir/GBadge.js",
Pagination: "https://uikit.d.aiengines.ir/Pagination.js",
};
const DEFAULT_REMOTE_URL = "https://uikit.d.aiengines.ir/GBadge.js";
const REMOTE_DEPS_KEY = "__UIKIT_REMOTE_DEPS__";
const REMOTE_CONFIG_KEY = "__UIKIT_REMOTE_CONFIG__";
const remotePromises = new Map();
const REQUIRED_DEPS = {
GBadge: ["React", "createRoot", "ChakraProvider", "Badge"],
Pagination: [
"React",
"createRoot",
"ChakraProvider",
"Button",
"HStack",
"IconButton",
"Input",
"Text",
],
};
export function setupUikitRemoteDeps(options = {}) {
if (typeof window === "undefined") return;
@ -23,6 +49,11 @@ export function setupUikitRemoteDeps(options = {}) {
ChakraProvider:
options.ChakraProvider || previousDeps.ChakraProvider || ChakraProvider,
Badge: options.Badge || previousDeps.Badge || Badge,
Button: options.Button || previousDeps.Button || Button,
HStack: options.HStack || previousDeps.HStack || HStack,
IconButton: options.IconButton || previousDeps.IconButton || IconButton,
Input: options.Input || previousDeps.Input || Input,
Text: options.Text || previousDeps.Text || Text,
theme: Object.prototype.hasOwnProperty.call(options, "theme")
? options.theme
: previousDeps.theme,
@ -32,54 +63,65 @@ export function setupUikitRemoteDeps(options = {}) {
},
};
const remoteUrls = options.remoteUrls || {};
window[REMOTE_CONFIG_KEY] = {
...previousConfig,
...(options.remoteUrl ? { GBadgeUrl: options.remoteUrl } : {}),
...(options.GBadgeUrl ? { GBadgeUrl: options.GBadgeUrl } : {}),
...(options.PaginationUrl ? { PaginationUrl: options.PaginationUrl } : {}),
...(remoteUrls.GBadge ? { GBadgeUrl: remoteUrls.GBadge } : {}),
...(remoteUrls.Pagination ? { PaginationUrl: remoteUrls.Pagination } : {}),
};
}
function ensureRemoteDeps() {
function ensureRemoteDeps(componentName) {
if (typeof window === "undefined") return;
const deps = window[REMOTE_DEPS_KEY];
const requiredDeps = REQUIRED_DEPS[componentName] || [];
const hasMissingDeps = requiredDeps.some((key) => !deps?.[key]);
if (
!deps?.React ||
!deps?.createRoot ||
!deps?.ChakraProvider ||
!deps?.Badge
) {
if (hasMissingDeps) {
setupUikitRemoteDeps();
}
}
function getRemoteUrl() {
if (typeof window === "undefined") return DEFAULT_REMOTE_URL;
function getRemoteUrl(componentName, explicitRemoteUrl) {
if (explicitRemoteUrl) return explicitRemoteUrl;
return window[REMOTE_CONFIG_KEY]?.GBadgeUrl || DEFAULT_REMOTE_URL;
if (typeof window === "undefined") {
return DEFAULT_REMOTE_URLS[componentName];
}
return (
window[REMOTE_CONFIG_KEY]?.[`${componentName}Url`] ||
DEFAULT_REMOTE_URLS[componentName]
);
}
function loadRemote() {
const remoteUrl = getRemoteUrl();
function loadRemote(componentName, explicitRemoteUrl) {
const remoteUrl = getRemoteUrl(componentName, explicitRemoteUrl);
const cacheKey = `${componentName}:${remoteUrl}`;
if (!remotePromises.has(remoteUrl)) {
if (!remotePromises.has(cacheKey)) {
remotePromises.set(
remoteUrl,
cacheKey,
import(
/* webpackIgnore: true */
/* @vite-ignore */
remoteUrl
).catch((error) => {
remotePromises.delete(remoteUrl);
remotePromises.delete(cacheKey);
throw error;
}),
);
}
return remotePromises.get(remoteUrl);
return remotePromises.get(cacheKey);
}
function normalizeChildren(children) {
function normalizeBadgeChildren(children) {
if (typeof children === "string" || typeof children === "number") {
return children;
}
@ -95,72 +137,89 @@ function normalizeChildren(children) {
return undefined;
}
export const GBadge = ({
children,
remoteUrl,
theme,
chakraProviderProps,
...props
}) => {
const containerRef = useRef(null);
const elementRef = useRef(null);
const latestPropsRef = useRef(null);
function createRemoteElementComponent({
componentName,
tagName,
normalizeProps,
}) {
return function RemoteElementComponent({
remoteUrl,
theme,
chakraProviderProps,
...props
}) {
const containerRef = useRef(null);
const elementRef = useRef(null);
const latestPropsRef = useRef(null);
latestPropsRef.current = {
...props,
children: normalizeChildren(children),
...(theme !== undefined ? { theme } : {}),
...(chakraProviderProps ? { chakraProviderProps } : {}),
};
useEffect(() => {
let active = true;
ensureRemoteDeps();
if (remoteUrl) {
setupUikitRemoteDeps({ remoteUrl });
}
loadRemote()
.then(() => {
if (!active) return;
if (!containerRef.current) return;
const element = document.createElement("uikit-g-badge");
element.componentProps = latestPropsRef.current;
containerRef.current.appendChild(element);
elementRef.current = element;
})
.catch((error) => {
console.error("[uikit/remote/ui] Failed to load GBadge:", error);
});
return () => {
active = false;
if (elementRef.current) {
elementRef.current.remove();
elementRef.current = null;
}
latestPropsRef.current = {
...(normalizeProps ? normalizeProps(props) : props),
...(theme !== undefined ? { theme } : {}),
...(chakraProviderProps ? { chakraProviderProps } : {}),
};
}, [remoteUrl]);
useEffect(() => {
if (!elementRef.current) return;
useEffect(() => {
let active = true;
elementRef.current.componentProps = latestPropsRef.current;
});
ensureRemoteDeps(componentName);
return (
<span
ref={containerRef}
style={{
display: "contents",
}}
/>
);
};
loadRemote(componentName, remoteUrl)
.then(() => {
if (!active) return;
if (!containerRef.current) return;
const element = document.createElement(tagName);
element.componentProps = latestPropsRef.current;
containerRef.current.appendChild(element);
elementRef.current = element;
})
.catch((error) => {
console.error(
`[uikit/remote/ui] Failed to load ${componentName}:`,
error,
);
});
return () => {
active = false;
if (elementRef.current) {
elementRef.current.remove();
elementRef.current = null;
}
};
}, [componentName, remoteUrl, tagName]);
useEffect(() => {
if (!elementRef.current) return;
elementRef.current.componentProps = latestPropsRef.current;
});
return (
<span
ref={containerRef}
style={{
display: "contents",
}}
/>
);
};
}
export const GBadge = createRemoteElementComponent({
componentName: "GBadge",
tagName: "uikit-g-badge",
normalizeProps: ({ children, ...props }) => ({
...props,
children: normalizeBadgeChildren(children),
}),
});
export const Pagination = createRemoteElementComponent({
componentName: "Pagination",
tagName: "uikit-pagination",
});

449
src/ui/Pagination.jsx Normal file
View File

@ -0,0 +1,449 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button, HStack, IconButton, Input, Text } from "@chakra-ui/react";
const LEVELS = {
full: {
showPageSummary: true,
showTotalCount: true,
showInput: true,
inputWidth: "100px",
justifyContent: "space-around",
pageButtonPx: undefined,
getPageNumbers: ({ currentPage, totalPages }) => [
1,
currentPage,
currentPage + 1,
totalPages,
],
},
compact: {
showPageSummary: false,
showTotalCount: true,
showInput: true,
inputWidth: "56px",
justifyContent: "space-around",
pageButtonPx: 2,
getPageNumbers: ({ currentPage, totalPages }) => [
1,
currentPage,
totalPages,
],
},
simple: {
showPageSummary: false,
showTotalCount: false,
showInput: false,
inputWidth: "48px",
justifyContent: "center",
pageButtonPx: undefined,
getPageNumbers: () => [],
},
};
const PERSIAN_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"];
const PERSIAN_DIGIT_REGEX = /[۰-۹]/g;
const ARABIC_DIGIT_REGEX = /[٠-٩]/g;
const toFaDigits = (value) => {
if (value === undefined || value === null) return "";
return String(value).replace(/\d/g, (digit) => PERSIAN_DIGITS[Number(digit)]);
};
const toEnDigits = (value) => {
if (value === undefined || value === null) return "";
return String(value)
.replace(PERSIAN_DIGIT_REGEX, (digit) =>
String(PERSIAN_DIGITS.indexOf(digit)),
)
.replace(ARABIC_DIGIT_REGEX, (digit) => String(digit.charCodeAt(0) - 1632));
};
const normalizeLevel = (level) => {
if (LEVELS[level]) return level;
return "full";
};
const getSafePage = (page, totalPages) => {
const numericPage = Number(page);
const numericTotalPages = Number(totalPages);
if (!numericTotalPages || numericTotalPages <= 0) return 1;
if (!numericPage || Number.isNaN(numericPage)) return 1;
return Math.min(Math.max(numericPage, 1), numericTotalPages);
};
const ChevronRightIcon = (props) => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...props}
>
<path
d="M15 18L9 12L15 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
const ChevronLeftIcon = (props) => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...props}
>
<path
d="M9 18L15 12L9 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function Pagination({
currentPage = 1,
totalCount = 0,
totalPages,
pageSize = 10,
onPageChange,
level = "full",
...containerProps
}) {
const resolvedLevel = normalizeLevel(level);
const config = LEVELS[resolvedLevel];
const isSimple = resolvedLevel === "simple";
const numericPageSize = Number(pageSize);
const safePageSize =
Number.isFinite(numericPageSize) && numericPageSize > 0
? numericPageSize
: 10;
const numericTotalCount = Number(totalCount);
const safeTotalCount =
Number.isFinite(numericTotalCount) && numericTotalCount > 0
? numericTotalCount
: 0;
const numericTotalPages = Number(totalPages);
const safeTotalPages =
Number.isFinite(numericTotalPages) && numericTotalPages > 0
? Math.ceil(numericTotalPages)
: Math.ceil(safeTotalCount / safePageSize);
const safeCurrentPage = getSafePage(currentPage, safeTotalPages);
const [inputValue, setInputValue] = useState(
isSimple ? "" : toFaDigits(safeCurrentPage),
);
const [isEditing, setIsEditing] = useState(false);
const [pendingPage, setPendingPage] = useState(null);
const inputRef = useRef(null);
const visibleCurrentPage = pendingPage ?? safeCurrentPage;
const hasTotalCount = safeTotalCount > 0;
const pageNumbers = useMemo(() => {
if (isSimple || safeTotalPages <= 0) return [];
return Array.from(
new Set(
config.getPageNumbers({
currentPage: safeCurrentPage,
totalPages: safeTotalPages,
}),
),
)
.filter((page) => page >= 1 && page <= safeTotalPages)
.sort((a, b) => a - b);
}, [config, isSimple, safeCurrentPage, safeTotalPages]);
const resetInput = useCallback(() => {
setInputValue(isSimple ? "" : toFaDigits(safeCurrentPage));
setIsEditing(false);
setPendingPage(null);
}, [isSimple, safeCurrentPage]);
const goToPage = useCallback(
(page) => {
const safePage = getSafePage(page, safeTotalPages);
setIsEditing(false);
setInputValue(isSimple ? "" : toFaDigits(safeCurrentPage));
setPendingPage(null);
if (safePage !== safeCurrentPage) {
onPageChange?.(safePage);
}
},
[isSimple, onPageChange, safeCurrentPage, safeTotalPages],
);
const commitInput = useCallback(() => {
if (!inputValue) {
resetInput();
inputRef.current?.blur();
return;
}
const page = parseInt(toEnDigits(inputValue), 10);
if (Number.isNaN(page)) {
resetInput();
inputRef.current?.blur();
return;
}
const safePage = getSafePage(page, safeTotalPages);
if (isSimple) {
setIsEditing(false);
setInputValue("");
if (safePage !== safeCurrentPage) {
setPendingPage(safePage);
onPageChange?.(safePage);
} else {
setPendingPage(null);
}
inputRef.current?.blur();
return;
}
if (safePage !== safeCurrentPage) {
setPendingPage(safePage);
setInputValue(toFaDigits(safePage));
onPageChange?.(safePage);
} else {
setInputValue(toFaDigits(safeCurrentPage));
setIsEditing(false);
setPendingPage(null);
}
inputRef.current?.blur();
}, [
inputValue,
isSimple,
onPageChange,
resetInput,
safeCurrentPage,
safeTotalPages,
]);
const startEditing = useCallback(() => {
setPendingPage(null);
setInputValue("");
setIsEditing(true);
}, []);
const handleInputChange = useCallback((event) => {
const rawValue = event.target.value;
const onlyDigits = toEnDigits(rawValue).replace(/\D/g, "");
setInputValue(toFaDigits(onlyDigits));
}, []);
useEffect(() => {
if (isSimple) {
if (pendingPage !== null && safeCurrentPage === pendingPage) {
setPendingPage(null);
}
return;
}
if (pendingPage !== null) {
if (safeCurrentPage === pendingPage) {
setPendingPage(null);
setIsEditing(false);
setInputValue(toFaDigits(safeCurrentPage));
}
return;
}
if (!isEditing) {
setInputValue(toFaDigits(safeCurrentPage));
}
}, [isEditing, isSimple, pendingPage, safeCurrentPage]);
useEffect(() => {
if (isSimple && isEditing) {
inputRef.current?.focus();
}
}, [isEditing, isSimple]);
useEffect(() => {
if (!isEditing) return;
if (!inputValue) return;
if (!isSimple && pendingPage !== null) return;
const timeout = setTimeout(() => {
commitInput();
}, 1000);
return () => clearTimeout(timeout);
}, [commitInput, inputValue, isEditing, isSimple, pendingPage]);
if (safeTotalPages <= 0) return null;
const renderInput = () => (
<Input
ref={inputRef}
type="text"
inputMode="numeric"
placeholder="صفحه"
w={config.inputWidth}
value={inputValue}
onFocus={() => {
if (!isSimple) {
setIsEditing(true);
setPendingPage(null);
setInputValue("");
}
}}
onChange={handleInputChange}
onBlur={() => {
if (inputValue) {
if (isSimple) {
commitInput();
}
return;
}
resetInput();
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
commitInput();
}
if (event.key === "Escape") {
resetInput();
}
}}
textAlign="center"
color="#1D2939"
px={1}
minW={0}
dir="ltr"
/>
);
const renderPageButton = (number, isCurrent = false) => (
<Button
key={number}
size="md"
px={config.pageButtonPx}
bg={isCurrent ? "#4B49AC" : "#e6e6f1"}
color={isCurrent ? "#e6e6f1" : "#4B49AC"}
_hover={{ bg: isCurrent ? "#4B49AC" : "#cbcbe8" }}
onClick={isSimple && isCurrent ? startEditing : () => goToPage(number)}
variant="soft"
>
{toFaDigits(number)}
</Button>
);
const showFirstButton =
isSimple && safeTotalPages > 1 && visibleCurrentPage !== 1;
const showLastButton =
isSimple && safeTotalPages > 1 && visibleCurrentPage !== safeTotalPages;
return (
<HStack
justifyContent={config.justifyContent}
backgroundColor="white"
py="10px"
px="16px"
rounded="lg"
border="1px"
borderColor="blue.200"
w="100%"
{...containerProps}
>
{config.showPageSummary && (
<HStack spacing={2} whiteSpace="nowrap">
<Text fontWeight="bold">صفحه</Text>
<Text>{toFaDigits(safeCurrentPage)}</Text>
<Text fontWeight="bold">از</Text>
<Text>{toFaDigits(safeTotalPages)}</Text>
</HStack>
)}
<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={<ChevronRightIcon />}
aria-label="صفحه قبلی"
/>
{isSimple ? (
<>
{showFirstButton && renderPageButton(1)}
{isEditing
? renderInput()
: renderPageButton(visibleCurrentPage, true)}
{showLastButton && renderPageButton(safeTotalPages)}
</>
) : (
<>
{pageNumbers.map((number) =>
renderPageButton(number, safeCurrentPage === number),
)}
{config.showInput && renderInput()}
</>
)}
<IconButton
size="md"
bg="#e6e6f1"
color="#4B49AC"
_hover={{ bg: "#cbcbe8" }}
onClick={() => goToPage(visibleCurrentPage + 1)}
isDisabled={visibleCurrentPage === safeTotalPages}
icon={<ChevronLeftIcon />}
aria-label="صفحه بعدی"
/>
</HStack>
{config.showTotalCount && hasTotalCount && (
<HStack spacing={2} whiteSpace="nowrap">
<Text w="auto" fontWeight="bold">
تعداد کل:
</Text>
<Text>{toFaDigits(safeTotalCount)}</Text>
</HStack>
)}
</HStack>
);
}
export default Pagination;

View File

@ -43,8 +43,21 @@ export default defineConfig(({ mode }) => {
target: "es2020",
minify: "esbuild",
sourcemap: false,
lib: {
entry: path.resolve(process.cwd(), "src/remote/ui/GBadge.remote.jsx"),
entry: {
GBadge: path.resolve(
process.cwd(),
"src/remote/ui/GBadge.remote.jsx",
),
Pagination: path.resolve(
process.cwd(),
"src/remote/ui/Pagination.remote.jsx",
),
},
formats: ["es"],
},
@ -53,9 +66,11 @@ export default defineConfig(({ mode }) => {
treeshake: true,
output: {
entryFileNames: "remote/GBadge.js",
entryFileNames: "remote/[name].js",
inlineDynamicImports: true,
chunkFileNames: "remote/chunks/[name]-[hash].js",
assetFileNames: "remote/assets/[name]-[hash][extname]",
},
},
},
@ -94,6 +109,7 @@ export default defineConfig(({ mode }) => {
external: (id) => {
return (
externalPackages.includes(id) ||
id.startsWith("react-dom/") ||
id.startsWith("react-icons/") ||
id.startsWith("@emotion/") ||
id.startsWith("react-select/")