@djangocfg/ui-core 2.1.319 → 2.1.321

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.
@@ -0,0 +1,176 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { cn } from '../../../lib';
6
+ import { useIsMobile } from '../../../hooks';
7
+ import { useLocation, useQueryParams } from '../../../hooks/router';
8
+
9
+ import {
10
+ Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink,
11
+ PaginationNext, PaginationPrevious
12
+ } from './pagination';
13
+
14
+ interface SSRPaginationProps {
15
+ currentPage: number;
16
+ totalPages: number;
17
+ totalItems: number;
18
+ itemsPerPage: number;
19
+ hasNextPage: boolean;
20
+ hasPreviousPage: boolean;
21
+ className?: string;
22
+ showInfo?: boolean;
23
+ maxVisiblePages?: number;
24
+ baseUrl?: string;
25
+ pathname?: string;
26
+ preserveQuery?: boolean;
27
+ }
28
+
29
+ function buildSearch(params: Record<string, string | string[]>, page: number, preserveQuery: boolean): string {
30
+ const next = new URLSearchParams();
31
+ if (preserveQuery) {
32
+ for (const [key, value] of Object.entries(params)) {
33
+ if (key === 'page') continue;
34
+ if (Array.isArray(value)) {
35
+ for (const v of value) next.append(key, v);
36
+ } else {
37
+ next.set(key, value);
38
+ }
39
+ }
40
+ }
41
+ if (page !== 1) next.set('page', page.toString());
42
+ return next.toString();
43
+ }
44
+
45
+ export const SSRPagination: React.FC<SSRPaginationProps> = ({
46
+ currentPage,
47
+ totalPages,
48
+ totalItems,
49
+ itemsPerPage,
50
+ hasNextPage,
51
+ hasPreviousPage: _hasPreviousPage,
52
+ className,
53
+ showInfo = true,
54
+ maxVisiblePages = 7,
55
+ baseUrl,
56
+ pathname: propPathname,
57
+ preserveQuery = true,
58
+ }) => {
59
+ const queryParams = useQueryParams();
60
+ const { pathname } = useLocation();
61
+ const isMobile = useIsMobile();
62
+
63
+ const getCurrentPageFromUrl = (): number => {
64
+ const pageParam = queryParams.get('page');
65
+ if (pageParam) {
66
+ const pageNum = parseInt(pageParam, 10);
67
+ return isNaN(pageNum) ? 1 : pageNum;
68
+ }
69
+ return 1;
70
+ };
71
+
72
+ const actualCurrentPage = getCurrentPageFromUrl() || currentPage;
73
+ const actualHasPreviousPage = actualCurrentPage > 1;
74
+
75
+ const getPageUrl = (page: number): string => {
76
+ if (baseUrl) {
77
+ return `${baseUrl}?page=${page}`;
78
+ }
79
+ const queryString = buildSearch(queryParams.params, page, preserveQuery);
80
+ const basePath = propPathname || pathname || '';
81
+ return queryString ? `${basePath}?${queryString}` : basePath;
82
+ };
83
+
84
+ const getVisiblePages = (): (number | 'ellipsis')[] => {
85
+ const mobileMaxVisible = 3;
86
+ const effectiveMaxVisible = isMobile ? mobileMaxVisible : maxVisiblePages;
87
+
88
+ if (totalPages <= effectiveMaxVisible) {
89
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
90
+ }
91
+
92
+ const pages: (number | 'ellipsis')[] = [];
93
+ const halfVisible = Math.floor(effectiveMaxVisible / 2);
94
+
95
+ if (isMobile) {
96
+ if (actualCurrentPage > 1) pages.push(actualCurrentPage - 1);
97
+ pages.push(actualCurrentPage);
98
+ if (actualCurrentPage < totalPages) pages.push(actualCurrentPage + 1);
99
+ } else {
100
+ pages.push(1);
101
+
102
+ let start = Math.max(2, actualCurrentPage - halfVisible);
103
+ let end = Math.min(totalPages - 1, actualCurrentPage + halfVisible);
104
+
105
+ if (actualCurrentPage <= halfVisible + 1) {
106
+ end = Math.min(totalPages - 1, effectiveMaxVisible - 1);
107
+ } else if (actualCurrentPage >= totalPages - halfVisible) {
108
+ start = Math.max(2, totalPages - effectiveMaxVisible + 2);
109
+ }
110
+
111
+ if (start > 2) pages.push('ellipsis');
112
+ for (let i = start; i <= end; i++) pages.push(i);
113
+ if (end < totalPages - 1) pages.push('ellipsis');
114
+ if (totalPages > 1) pages.push(totalPages);
115
+ }
116
+
117
+ return pages;
118
+ };
119
+
120
+ const visiblePages = getVisiblePages();
121
+ const startItem = (actualCurrentPage - 1) * itemsPerPage + 1;
122
+ const endItem = Math.min(actualCurrentPage * itemsPerPage, totalItems);
123
+
124
+ if (totalItems === 0) {
125
+ return null;
126
+ }
127
+
128
+ return (
129
+ <div className={cn("space-y-4", className)}>
130
+ {showInfo && (
131
+ <div className="text-sm text-muted-foreground text-center">
132
+ {isMobile ? (
133
+ `Page ${actualCurrentPage} of ${totalPages}`
134
+ ) : (
135
+ `Showing ${startItem.toLocaleString()} to ${endItem.toLocaleString()} of ${totalItems.toLocaleString()} results`
136
+ )}
137
+ </div>
138
+ )}
139
+
140
+ <Pagination>
141
+ <PaginationContent>
142
+ <PaginationItem>
143
+ <PaginationPrevious
144
+ href={actualHasPreviousPage ? getPageUrl(actualCurrentPage - 1) : undefined}
145
+ className={!actualHasPreviousPage ? "pointer-events-none opacity-50" : undefined}
146
+ />
147
+ </PaginationItem>
148
+
149
+ {visiblePages.map((page, index) => (
150
+ <PaginationItem key={index}>
151
+ {page === 'ellipsis' ? (
152
+ <PaginationEllipsis />
153
+ ) : (
154
+ <PaginationLink
155
+ href={getPageUrl(page)}
156
+ isActive={page === actualCurrentPage}
157
+ >
158
+ {page}
159
+ </PaginationLink>
160
+ )}
161
+ </PaginationItem>
162
+ ))}
163
+
164
+ <PaginationItem>
165
+ <PaginationNext
166
+ href={hasNextPage ? getPageUrl(actualCurrentPage + 1) : undefined}
167
+ className={!hasNextPage ? "pointer-events-none opacity-50" : undefined}
168
+ />
169
+ </PaginationItem>
170
+ </PaginationContent>
171
+ </Pagination>
172
+ </div>
173
+ );
174
+ };
175
+
176
+ SSRPagination.displayName = 'SSRPagination';
@@ -0,0 +1,26 @@
1
+ export {
2
+ Sidebar,
3
+ SidebarContent,
4
+ SidebarFooter,
5
+ SidebarGroup,
6
+ SidebarGroupAction,
7
+ SidebarGroupContent,
8
+ SidebarGroupLabel,
9
+ SidebarHeader,
10
+ SidebarInput,
11
+ SidebarInset,
12
+ SidebarMenu,
13
+ SidebarMenuAction,
14
+ SidebarMenuBadge,
15
+ SidebarMenuButton,
16
+ SidebarMenuItem,
17
+ SidebarMenuSkeleton,
18
+ SidebarMenuSub,
19
+ SidebarMenuSubButton,
20
+ SidebarMenuSubItem,
21
+ SidebarProvider,
22
+ SidebarRail,
23
+ SidebarSeparator,
24
+ SidebarTrigger,
25
+ useSidebar,
26
+ } from './sidebar';