fluent-styles 1.60.0 → 1.62.0

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,275 @@
1
+ /**
2
+ * usePaginatedQuery
3
+ *
4
+ * A generic hook that manages page state, loading, error, and data fetching
5
+ * for use with StyledTable's externalPagination mode.
6
+ *
7
+ * Works with:
8
+ * - REST / GraphQL APIs (pass a `fetcher` function)
9
+ * - Realm (pass a `realmQuery` function)
10
+ * - SQLite / WatermelonDB (pass a `fetcher` function)
11
+ * - Any async datasource
12
+ *
13
+ * Usage with REST API:
14
+ * const table = usePaginatedQuery({
15
+ * pageSize: 10,
16
+ * fetcher: async ({ page, pageSize, sortKey, sortDir }) => {
17
+ * const res = await api.get('/orders', { params: { page, pageSize, sortKey, sortDir } })
18
+ * return { data: res.data.items, totalCount: res.data.total }
19
+ * },
20
+ * })
21
+ *
22
+ * Usage with Realm:
23
+ * const table = usePaginatedQuery({
24
+ * pageSize: 10,
25
+ * realmQuery: ({ page, pageSize, sortKey, sortDir }) => {
26
+ * const results = realm.objects('Order')
27
+ * .sorted(sortKey ?? 'createdAt', sortDir === 'desc')
28
+ * const total = results.length
29
+ * const data = results.slice(page * pageSize, (page + 1) * pageSize)
30
+ * return { data: Array.from(data), totalCount: total }
31
+ * },
32
+ * })
33
+ *
34
+ * Then wire into StyledTable:
35
+ * <StyledTable
36
+ * {...table.tableProps}
37
+ * columns={COLUMNS}
38
+ * />
39
+ */
40
+
41
+ import { useState, useEffect, useCallback, useRef } from "react";
42
+ import type { SortDirection } from "./";
43
+
44
+ // ─── Types ────────────────────────────────────────────────────────────────────
45
+
46
+ export interface PaginatedQueryParams {
47
+ page: number;
48
+ pageSize: number;
49
+ sortKey: string | null;
50
+ sortDir: SortDirection;
51
+ search?: string;
52
+ filters?: Record<string, any>;
53
+ }
54
+
55
+ export interface PaginatedQueryResult<T> {
56
+ data: T[];
57
+ totalCount: number;
58
+ }
59
+
60
+ export interface UsePaginatedQueryOptions<T> {
61
+ pageSize?: number;
62
+
63
+ /**
64
+ * Async fetcher for REST / GraphQL / SQLite.
65
+ * Return `{ data, totalCount }`.
66
+ */
67
+ fetcher?: (params: PaginatedQueryParams) => Promise<PaginatedQueryResult<T>>;
68
+
69
+ /**
70
+ * Synchronous Realm query function.
71
+ * Called whenever page / sort changes.
72
+ * Return `{ data, totalCount }`.
73
+ */
74
+ realmQuery?: (params: PaginatedQueryParams) => PaginatedQueryResult<T>;
75
+
76
+ /** Initial sort column key */
77
+ initialSortKey?: string;
78
+ /** Initial sort direction */
79
+ initialSortDir?: SortDirection;
80
+ /** Initial search string */
81
+ initialSearch?: string;
82
+ /** Initial filter values */
83
+ initialFilters?: Record<string, any>;
84
+ /** Debounce ms for search input changes — default 300 */
85
+ searchDebounce?: number;
86
+ }
87
+
88
+ export interface UsePaginatedQueryReturn<T> {
89
+ // Data
90
+ data: T[];
91
+ loading: boolean;
92
+ error: Error | null;
93
+ totalCount: number;
94
+ totalPages: number;
95
+
96
+ // Pagination state
97
+ page: number;
98
+ pageSize: number;
99
+ setPage: (page: number) => void;
100
+
101
+ // Sort state
102
+ sortKey: string | null;
103
+ sortDir: SortDirection;
104
+ setSort: (key: string, dir: SortDirection) => void;
105
+
106
+ // Search / filter
107
+ search: string;
108
+ setSearch: (s: string) => void;
109
+ filters: Record<string, any>;
110
+ setFilters: (f: Record<string, any>) => void;
111
+
112
+ /** Manually re-fetch the current page (e.g. after a mutation) */
113
+ refresh: () => void;
114
+
115
+ /**
116
+ * Spread these directly onto StyledTable:
117
+ * <StyledTable {...table.tableProps} columns={COLUMNS} />
118
+ */
119
+ tableProps: {
120
+ data: T[];
121
+ loading: boolean;
122
+ externalPagination: true;
123
+ currentPage: number;
124
+ totalPages: number;
125
+ totalCount: number;
126
+ pageSize: number;
127
+ onPageChange: (page: number) => void;
128
+ sortKey?: string | null;
129
+ sortDirection: SortDirection;
130
+ onSort: (key: string, dir: SortDirection) => void;
131
+ };
132
+ }
133
+
134
+ // ─── Hook ─────────────────────────────────────────────────────────────────────
135
+
136
+ export function usePaginatedQuery<T>(
137
+ options: UsePaginatedQueryOptions<T>
138
+ ): UsePaginatedQueryReturn<T> {
139
+ const {
140
+ pageSize = 10,
141
+ fetcher,
142
+ realmQuery,
143
+ initialSortKey = null,
144
+ initialSortDir = null,
145
+ initialSearch = "",
146
+ initialFilters = {},
147
+ searchDebounce = 300,
148
+ } = options;
149
+
150
+ const [page, setPageState] = useState(0);
151
+ const [sortKey, setSortKey] = useState<string | null>(initialSortKey);
152
+ const [sortDir, setSortDir] = useState<SortDirection>(initialSortDir);
153
+ const [search, setSearchState] = useState(initialSearch);
154
+ const [filters, setFiltersState]= useState<Record<string, any>>(initialFilters);
155
+ const [data, setData] = useState<T[]>([]);
156
+ const [totalCount, setTotalCount] = useState(0);
157
+ const [loading, setLoading] = useState(false);
158
+ const [error, setError] = useState<Error | null>(null);
159
+ const [refreshKey, setRefreshKey] = useState(0);
160
+
161
+ // Debounced search
162
+ const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
163
+ const [debouncedSearch, setDebouncedSearch] = useState(initialSearch);
164
+
165
+ const setSearch = useCallback((s: string) => {
166
+ setSearchState(s);
167
+ if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
168
+ searchTimerRef.current = setTimeout(() => {
169
+ setDebouncedSearch(s);
170
+ setPageState(0); // reset to first page on new search
171
+ }, searchDebounce);
172
+ }, [searchDebounce]);
173
+
174
+ const setPage = useCallback((p: number) => {
175
+ setPageState(p);
176
+ }, []);
177
+
178
+ const setSort = useCallback((key: string, dir: SortDirection) => {
179
+ setSortKey(key);
180
+ setSortDir(dir);
181
+ setPageState(0); // reset to first page on sort change
182
+ }, []);
183
+
184
+ const setFilters = useCallback((f: Record<string, any>) => {
185
+ setFiltersState(f);
186
+ setPageState(0);
187
+ }, []);
188
+
189
+ const refresh = useCallback(() => {
190
+ setRefreshKey((k) => k + 1);
191
+ }, []);
192
+
193
+ const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
194
+
195
+ const params: PaginatedQueryParams = {
196
+ page,
197
+ pageSize,
198
+ sortKey,
199
+ sortDir,
200
+ search: debouncedSearch,
201
+ filters,
202
+ };
203
+
204
+ // ── Realm (synchronous) ────────────────────────────────────────────────
205
+ useEffect(() => {
206
+ if (!realmQuery) return;
207
+ try {
208
+ const result = realmQuery(params);
209
+ setData(result.data);
210
+ setTotalCount(result.totalCount);
211
+ setError(null);
212
+ } catch (e) {
213
+ setError(e instanceof Error ? e : new Error(String(e)));
214
+ }
215
+ }, [page, sortKey, sortDir, debouncedSearch, filters, refreshKey]);
216
+
217
+ // ── Async fetcher (REST / GraphQL / SQLite) ────────────────────────────
218
+ useEffect(() => {
219
+ if (!fetcher) return;
220
+ let cancelled = false;
221
+
222
+ const run = async () => {
223
+ setLoading(true);
224
+ setError(null);
225
+ try {
226
+ const result = await fetcher(params);
227
+ if (!cancelled) {
228
+ setData(result.data);
229
+ setTotalCount(result.totalCount);
230
+ }
231
+ } catch (e) {
232
+ if (!cancelled) {
233
+ setError(e instanceof Error ? e : new Error(String(e)));
234
+ }
235
+ } finally {
236
+ if (!cancelled) setLoading(false);
237
+ }
238
+ };
239
+
240
+ run();
241
+ return () => { cancelled = true; };
242
+ }, [page, sortKey, sortDir, debouncedSearch, filters, refreshKey]);
243
+
244
+ return {
245
+ data,
246
+ loading,
247
+ error,
248
+ totalCount,
249
+ totalPages,
250
+ page,
251
+ pageSize,
252
+ setPage,
253
+ sortKey,
254
+ sortDir,
255
+ setSort,
256
+ search,
257
+ setSearch,
258
+ filters,
259
+ setFilters,
260
+ refresh,
261
+ tableProps: {
262
+ data,
263
+ loading,
264
+ externalPagination: true as const,
265
+ currentPage: page,
266
+ totalPages,
267
+ totalCount,
268
+ pageSize,
269
+ onPageChange: setPage,
270
+ sortKey,
271
+ sortDirection: sortDir,
272
+ onSort: setSort,
273
+ },
274
+ };
275
+ }