@opexa/portal-components 0.0.544 → 0.0.545

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ export interface GamesListProps {
20
20
  heading?: string | ReactNode;
21
21
  viewGameProvidersUrl?: string;
22
22
  className?: string | ClassNameEntries;
23
+ pagination?: 'lazy-load' | 'paginated';
23
24
  }
24
25
  export declare function GamesList__client(props: GamesListProps): import("react/jsx-runtime").JSX.Element;
25
26
  export {};
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { isString, noop } from 'lodash-es';
4
4
  import Link from 'next/link';
5
+ import { useEffect, useState } from 'react';
5
6
  import { twMerge } from 'tailwind-merge';
6
7
  import { useGamesQuery } from '../../client/hooks/useGamesQuery.js';
7
8
  import { useInfiniteQueryHelper } from '../../client/hooks/useInfiniteQueryHelper.js';
@@ -13,20 +14,42 @@ import { Progress } from '../../ui/Progress/index.js';
13
14
  import { Empty } from '../shared/Empty.js';
14
15
  import { Game } from './Game.js';
15
16
  import { GameContext } from './GamesContext.js';
17
+ import { GamesPagination } from './GamesPagination.js';
16
18
  export function GamesList__client(props) {
19
+ const { pagination = 'lazy-load' } = props;
20
+ const perPage = props.first ?? 24;
17
21
  const { loading, availableRows, totalRows, totalAvailableRows, hasNextPage, next, } = useInfiniteQueryHelper(useGamesQuery, {
18
- first: props.first ?? 24,
22
+ first: perPage,
19
23
  filter: props.filter,
20
24
  search: props.search,
21
25
  sort: props.sort,
22
26
  });
27
+ const [currentPage, setCurrentPage] = useState(1);
28
+ const totalPages = Math.ceil(totalRows / perPage);
29
+ useEffect(() => {
30
+ if (pagination === 'paginated') {
31
+ const requiredRows = currentPage * perPage;
32
+ if (requiredRows > availableRows.length && hasNextPage && !loading) {
33
+ next();
34
+ }
35
+ }
36
+ }, [
37
+ pagination,
38
+ currentPage,
39
+ perPage,
40
+ availableRows.length,
41
+ hasNextPage,
42
+ loading,
43
+ next,
44
+ ]);
45
+ const paginatedRows = availableRows.slice((currentPage - 1) * perPage, currentPage * perPage);
23
46
  const classNames = isString(props.className)
24
47
  ? { root: props.className }
25
48
  : (props.className ?? {});
26
49
  return (_jsxs("div", { className: classNames.root, children: [_jsxs("div", { className: "flex flex-col lg:flex-row lg:items-center lg:justify-between", children: [_jsx("h2", { className: "order-2 mt-4xl font-semibold text-lg lg:order-none lg:mt-0", children: props.heading ?? 'Games' }), props.viewGameProvidersUrl && (_jsxs(Link, { href: props.viewGameProvidersUrl, className: "order-1 flex items-center gap-sm font-semibold text-button-tertiary-fg text-lg lg:order-none lg:text-sm", children: [_jsx(ChevronLeftIcon, { className: "size-5" }), "Back to all Providers"] }))] }), totalRows <= 0 && (_jsx(Empty, { icon: loading ? SpinnerIcon : GamingPad01Icon, title: loading ? 'Just a moment' : 'No games', message: loading
27
50
  ? 'Fetching latest games...'
28
- : 'No game is currently available. Please check back later', className: "mt-lg" })), totalRows >= 1 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "mt-lg grid grid-cols-3 gap-1.5 lg:grid-cols-6 lg:gap-2.5", children: availableRows.map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, className: {
51
+ : 'No game is currently available. Please check back later', className: "mt-lg" })), totalRows >= 1 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "mt-lg grid grid-cols-3 gap-1.5 lg:grid-cols-6 lg:gap-2.5", children: (pagination === 'paginated' ? paginatedRows : availableRows).map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, className: {
29
52
  root: classNames.thumbnailRoot,
30
53
  title: classNames.thumbnailTitle,
31
- } }) }, game.id))) }), _jsxs("div", { className: "mt-2xl flex flex-col items-center lg:mt-3xl", children: [_jsx(Progress.Root, { min: 0, max: totalRows, value: totalAvailableRows, onValueChange: noop, className: twMerge('w-[12.5rem]', classNames.progressRoot), children: _jsx(Progress.Track, { className: twMerge('bg-bg-tertiary', classNames.progressTrack), children: _jsx(Progress.Range, {}) }) }), _jsx("p", { className: "mt-md text-button-tertiary-fg text-sm", children: `Displaying ${totalAvailableRows} of ${totalRows}` }), hasNextPage && (_jsx(Button, { size: "sm", variant: "outline", fullWidth: false, onClick: next, className: twMerge('mt-lg', classNames.loadMoreButton), children: "Load More" }))] })] }))] }));
54
+ } }) }, game.id))) }), _jsx("div", { className: 'mt-2xl flex flex-col items-center lg:mt-3xl', children: pagination === 'paginated' && totalRows > 0 ? (_jsx(GamesPagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: setCurrentPage })) : (_jsxs(_Fragment, { children: [_jsx(Progress.Root, { min: 0, max: totalRows, value: totalAvailableRows, onValueChange: noop, className: twMerge('w-[12.5rem]', classNames.progressRoot), children: _jsx(Progress.Track, { className: twMerge('bg-bg-tertiary', classNames.progressTrack), children: _jsx(Progress.Range, {}) }) }), _jsx("p", { className: "mt-md text-button-tertiary-fg text-sm", children: `Displaying ${totalAvailableRows} of ${totalRows}` }), hasNextPage && (_jsx(Button, { size: "sm", variant: "outline", fullWidth: false, onClick: next, className: twMerge('mt-lg', classNames.loadMoreButton), children: "Load More" }))] })) })] }))] }));
32
55
  }
@@ -0,0 +1,8 @@
1
+ interface GamesPaginationProps {
2
+ currentPage: number;
3
+ totalPages: number;
4
+ onPageChange: (page: number) => void;
5
+ siblingCount?: number;
6
+ }
7
+ export declare function GamesPagination({ currentPage, totalPages, onPageChange, siblingCount, }: GamesPaginationProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from 'react';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
6
+ import { ArrowRightIcon } from '../../icons/ArrowRightIcon.js';
7
+ import { Button } from '../../ui/Button/index.js';
8
+ const DOTS = '...';
9
+ const range = (start, end) => {
10
+ const length = end - start + 1;
11
+ return Array.from({ length }, (_, idx) => idx + start);
12
+ };
13
+ export function GamesPagination({ currentPage, totalPages, onPageChange, siblingCount = 1, }) {
14
+ const paginationRange = useMemo(() => {
15
+ const totalPageNumbers = siblingCount + 5;
16
+ if (totalPageNumbers >= totalPages) {
17
+ return range(1, totalPages);
18
+ }
19
+ const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
20
+ const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
21
+ const shouldShowLeftDots = leftSiblingIndex > 2;
22
+ const shouldShowRightDots = rightSiblingIndex < totalPages - 1;
23
+ const firstPageIndex = 1;
24
+ const lastPageIndex = totalPages;
25
+ if (!shouldShowLeftDots && shouldShowRightDots) {
26
+ const leftItemCount = 3 + 2 * siblingCount;
27
+ const leftRange = range(1, leftItemCount);
28
+ return [...leftRange, DOTS, totalPages];
29
+ }
30
+ if (shouldShowLeftDots && !shouldShowRightDots) {
31
+ const rightItemCount = 3 + 2 * siblingCount;
32
+ const rightRange = range(totalPages - rightItemCount + 1, totalPages);
33
+ return [firstPageIndex, DOTS, ...rightRange];
34
+ }
35
+ if (shouldShowLeftDots && shouldShowRightDots) {
36
+ const middleRange = range(leftSiblingIndex, rightSiblingIndex);
37
+ return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
38
+ }
39
+ return range(1, totalPages);
40
+ }, [totalPages, siblingCount, currentPage]);
41
+ const handlePrevious = () => {
42
+ if (currentPage > 1) {
43
+ onPageChange(currentPage - 1);
44
+ }
45
+ };
46
+ const handleNext = () => {
47
+ if (currentPage < totalPages) {
48
+ onPageChange(currentPage + 1);
49
+ }
50
+ };
51
+ if (currentPage === 0 || (paginationRange && paginationRange.length < 2)) {
52
+ return null;
53
+ }
54
+ return (_jsx("div", { className: 'mx-10 flex items-center justify-center', children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "w-fit rounded-md border-transparent", onClick: handlePrevious, disabled: currentPage === 1, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-6" }) }), paginationRange?.map((pageNumber, index) => {
55
+ if (pageNumber === DOTS) {
56
+ return (_jsx("span", { className: "flex h-10 w-10 items-center justify-center text-sm text-text-secondary", children: "..." }, `dots-${index}`));
57
+ }
58
+ return (_jsx(Button, { variant: pageNumber === currentPage ? 'solid' : 'outline', colorScheme: "gray", className: twMerge('h-10 w-10 rounded-md', pageNumber !== currentPage && 'border-transparent', pageNumber === currentPage &&
59
+ 'border-border-brand-primary bg-bg-brand-primary text-text-primary-inverted'), onClick: () => onPageChange(pageNumber), children: pageNumber }, pageNumber));
60
+ }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "w-fit rounded-md border-transparent", onClick: handleNext, disabled: currentPage === totalPages, "aria-label": "Next", children: _jsx(ArrowRightIcon, { className: "size-6" }) })] }) }));
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.544",
3
+ "version": "0.0.545",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",