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.
- package/README.md +542 -21
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/table/index.js +650 -0
- package/lib/commonjs/table/index.js.map +1 -0
- package/lib/commonjs/table/usepaginatedquery.js +181 -0
- package/lib/commonjs/table/usepaginatedquery.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/table/index.js +644 -0
- package/lib/module/table/index.js.map +1 -0
- package/lib/module/table/usepaginatedquery.js +178 -0
- package/lib/module/table/usepaginatedquery.js.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/table/index.d.ts +107 -0
- package/lib/typescript/table/index.d.ts.map +1 -0
- package/lib/typescript/table/usepaginatedquery.d.ts +114 -0
- package/lib/typescript/table/usepaginatedquery.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/table/index.tsx +843 -0
- package/src/table/usepaginatedquery.tsx +275 -0
|
@@ -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
|
+
}
|