forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusePagination.ts
More file actions
171 lines (150 loc) · 4.87 KB
/
Copy pathusePagination.ts
File metadata and controls
171 lines (150 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { useCallback, useMemo, useRef } from 'react'
const DEFAULT_MAX_VISIBLE = 5
type UsePaginationOptions = {
totalItems: number
maxVisible?: number
selectedIndex?: number
}
type UsePaginationResult<T> = {
// For backwards compatibility with page-based terminology
currentPage: number
totalPages: number
startIndex: number
endIndex: number
needsPagination: boolean
pageSize: number
// Get visible slice of items
getVisibleItems: (items: T[]) => T[]
// Convert visible index to actual index
toActualIndex: (visibleIndex: number) => number
// Check if actual index is visible
isOnCurrentPage: (actualIndex: number) => boolean
// Navigation (kept for API compatibility)
goToPage: (page: number) => void
nextPage: () => void
prevPage: () => void
// Handle selection - just updates the index, scrolling is automatic
handleSelectionChange: (
newIndex: number,
setSelectedIndex: (index: number) => void,
) => void
// Page navigation - returns false for continuous scrolling (not needed)
handlePageNavigation: (
direction: 'left' | 'right',
setSelectedIndex: (index: number) => void,
) => boolean
// Scroll position info for UI display
scrollPosition: {
current: number
total: number
canScrollUp: boolean
canScrollDown: boolean
}
}
export function usePagination<T>({
totalItems,
maxVisible = DEFAULT_MAX_VISIBLE,
selectedIndex = 0,
}: UsePaginationOptions): UsePaginationResult<T> {
const needsPagination = totalItems > maxVisible
// Use a ref to track the previous scroll offset for smooth scrolling
const scrollOffsetRef = useRef(0)
// Compute the scroll offset based on selectedIndex
// This ensures the selected item is always visible
const scrollOffset = useMemo(() => {
if (!needsPagination) return 0
const prevOffset = scrollOffsetRef.current
// If selected item is above the visible window, scroll up
if (selectedIndex < prevOffset) {
scrollOffsetRef.current = selectedIndex
return selectedIndex
}
// If selected item is below the visible window, scroll down
if (selectedIndex >= prevOffset + maxVisible) {
const newOffset = selectedIndex - maxVisible + 1
scrollOffsetRef.current = newOffset
return newOffset
}
// Selected item is within visible window, keep current offset
// But ensure offset is still valid
const maxOffset = Math.max(0, totalItems - maxVisible)
const clampedOffset = Math.min(prevOffset, maxOffset)
scrollOffsetRef.current = clampedOffset
return clampedOffset
}, [selectedIndex, maxVisible, needsPagination, totalItems])
const startIndex = scrollOffset
const endIndex = Math.min(scrollOffset + maxVisible, totalItems)
const getVisibleItems = useCallback(
(items: T[]): T[] => {
if (!needsPagination) return items
return items.slice(startIndex, endIndex)
},
[needsPagination, startIndex, endIndex],
)
const toActualIndex = useCallback(
(visibleIndex: number): number => {
return startIndex + visibleIndex
},
[startIndex],
)
const isOnCurrentPage = useCallback(
(actualIndex: number): boolean => {
return actualIndex >= startIndex && actualIndex < endIndex
},
[startIndex, endIndex],
)
// These are mostly no-ops for continuous scrolling but kept for API compatibility
const goToPage = useCallback((_page: number) => {
// No-op - scrolling is controlled by selectedIndex
}, [])
const nextPage = useCallback(() => {
// No-op - scrolling is controlled by selectedIndex
}, [])
const prevPage = useCallback(() => {
// No-op - scrolling is controlled by selectedIndex
}, [])
// Simple selection handler - just updates the index
// Scrolling happens automatically via the useMemo above
const handleSelectionChange = useCallback(
(newIndex: number, setSelectedIndex: (index: number) => void) => {
const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1))
setSelectedIndex(clampedIndex)
},
[totalItems],
)
// Page navigation - disabled for continuous scrolling
const handlePageNavigation = useCallback(
(
_direction: 'left' | 'right',
_setSelectedIndex: (index: number) => void,
): boolean => {
return false
},
[],
)
// Calculate page-like values for backwards compatibility
const totalPages = Math.max(1, Math.ceil(totalItems / maxVisible))
const currentPage = Math.floor(scrollOffset / maxVisible)
return {
currentPage,
totalPages,
startIndex,
endIndex,
needsPagination,
pageSize: maxVisible,
getVisibleItems,
toActualIndex,
isOnCurrentPage,
goToPage,
nextPage,
prevPage,
handleSelectionChange,
handlePageNavigation,
scrollPosition: {
current: selectedIndex + 1,
total: totalItems,
canScrollUp: scrollOffset > 0,
canScrollDown: scrollOffset + maxVisible < totalItems,
},
}
}