@classytic/fluid 0.2.4 → 0.3.3
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/LICENSE +21 -0
- package/README.md +149 -62
- package/dist/api-pagination-CJ0vR_w6.d.mts +34 -0
- package/dist/api-pagination-DBTE0yk4.mjs +190 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/calendar.d.mts +105 -0
- package/dist/client/calendar.mjs +202 -0
- package/dist/client/core.d.mts +1614 -0
- package/dist/client/core.mjs +2779 -0
- package/dist/client/error.d.mts +125 -0
- package/dist/client/error.mjs +166 -0
- package/dist/client/hooks.d.mts +162 -0
- package/dist/client/hooks.mjs +447 -0
- package/dist/client/table.d.mts +84 -0
- package/dist/client/table.mjs +373 -0
- package/dist/client/theme.d.mts +6 -0
- package/dist/client/theme.mjs +65 -0
- package/dist/command.d.mts +134 -0
- package/dist/command.mjs +132 -0
- package/dist/compact.d.mts +359 -0
- package/dist/compact.mjs +892 -0
- package/dist/dashboard.d.mts +778 -0
- package/dist/dashboard.mjs +1617 -0
- package/dist/filter-utils-DqMmy_v-.mjs +72 -0
- package/dist/filter-utils-IZ0GtuPo.d.mts +40 -0
- package/dist/forms.d.mts +1549 -0
- package/dist/forms.mjs +3740 -0
- package/dist/index.d.mts +296 -0
- package/dist/index.mjs +432 -0
- package/dist/layouts.d.mts +215 -0
- package/dist/layouts.mjs +460 -0
- package/dist/search-context-DR7DBs7S.mjs +19 -0
- package/dist/search.d.mts +254 -0
- package/dist/search.mjs +523 -0
- package/dist/sheet-wrapper-C13Y-Q6w.mjs +211 -0
- package/dist/use-base-search-BGgWnWaF.d.mts +35 -0
- package/dist/use-debounce-xmZucz5e.mjs +53 -0
- package/dist/use-keyboard-shortcut-Bl6YM5Q7.mjs +82 -0
- package/dist/use-keyboard-shortcut-_mRCh3QO.d.mts +24 -0
- package/dist/use-media-query-BnVNIKT4.mjs +17 -0
- package/dist/use-mobile-BX3SQVo2.mjs +20 -0
- package/dist/use-scroll-detection-CsgsQYvy.mjs +43 -0
- package/dist/utils-CDue7cEt.d.mts +6 -0
- package/dist/utils-DQ5SCVoW.mjs +10 -0
- package/package.json +85 -45
- package/styles.css +2 -2
- package/dist/chunk-GUHK2DTW.js +0 -15
- package/dist/chunk-GUHK2DTW.js.map +0 -1
- package/dist/chunk-H3NFL3GJ.js +0 -57
- package/dist/chunk-H3NFL3GJ.js.map +0 -1
- package/dist/chunk-J2YRTQE4.js +0 -293
- package/dist/chunk-J2YRTQE4.js.map +0 -1
- package/dist/compact.d.ts +0 -217
- package/dist/compact.js +0 -986
- package/dist/compact.js.map +0 -1
- package/dist/dashboard.d.ts +0 -387
- package/dist/dashboard.js +0 -1032
- package/dist/dashboard.js.map +0 -1
- package/dist/index.d.ts +0 -2140
- package/dist/index.js +0 -6422
- package/dist/index.js.map +0 -1
- package/dist/layout.d.ts +0 -25
- package/dist/layout.js +0 -4
- package/dist/layout.js.map +0 -1
- package/dist/search.d.ts +0 -172
- package/dist/search.js +0 -341
- package/dist/search.js.map +0 -1
- package/dist/use-base-search-AS5Z3SAy.d.ts +0 -64
- package/dist/utils-Cbsgs0XP.d.ts +0 -5
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { a as getApiParams, i as clearSearchAndFilterParams, r as buildSearchParams, t as buildFilterParams } from "../filter-utils-DqMmy_v-.mjs";
|
|
4
|
+
import { t as useIsMobile } from "../use-mobile-BX3SQVo2.mjs";
|
|
5
|
+
import { t as useMediaQuery } from "../use-media-query-BnVNIKT4.mjs";
|
|
6
|
+
import { t as useScrollDetection } from "../use-scroll-detection-CsgsQYvy.mjs";
|
|
7
|
+
import { n as useDebouncedCallback, t as useDebounce } from "../use-debounce-xmZucz5e.mjs";
|
|
8
|
+
import { t as useKeyboardShortcut } from "../use-keyboard-shortcut-Bl6YM5Q7.mjs";
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
11
|
+
|
|
12
|
+
//#region src/hooks/use-base-search.ts
|
|
13
|
+
/**
|
|
14
|
+
* Base search hook that provides common search functionality
|
|
15
|
+
* Can be extended by specific search hooks for different entities
|
|
16
|
+
* Supports bracket syntax: field[operator]=value
|
|
17
|
+
*/
|
|
18
|
+
function useBaseSearch(config) {
|
|
19
|
+
const { basePath, searchFields = {}, filterFields = {}, defaultSearchType = Object.keys(searchFields)[0] || "" } = config;
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const searchParams = useSearchParams();
|
|
22
|
+
const [searchType, setSearchType] = useState(() => {
|
|
23
|
+
for (const [type, paramName] of Object.entries(searchFields)) if (searchParams.has(paramName)) return type;
|
|
24
|
+
return defaultSearchType;
|
|
25
|
+
});
|
|
26
|
+
const [searchValue, setSearchValue] = useState(() => {
|
|
27
|
+
for (const paramName of Object.values(searchFields)) {
|
|
28
|
+
const value = searchParams.get(paramName);
|
|
29
|
+
if (value) return value;
|
|
30
|
+
}
|
|
31
|
+
return "";
|
|
32
|
+
});
|
|
33
|
+
const [filters, setFilters] = useState(() => {
|
|
34
|
+
const initialFilters = {};
|
|
35
|
+
Object.entries(filterFields).forEach(([key, fieldConfig]) => {
|
|
36
|
+
const paramValue = searchParams.get(fieldConfig.paramName);
|
|
37
|
+
if (paramValue) if (fieldConfig.type === "array") initialFilters[key] = paramValue.split(",");
|
|
38
|
+
else initialFilters[key] = paramValue;
|
|
39
|
+
else initialFilters[key] = fieldConfig.defaultValue ?? (fieldConfig.type === "array" ? [] : "");
|
|
40
|
+
});
|
|
41
|
+
return initialFilters;
|
|
42
|
+
});
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let urlType = defaultSearchType;
|
|
45
|
+
for (const [type, paramName] of Object.entries(searchFields)) if (searchParams.has(paramName)) {
|
|
46
|
+
urlType = type;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
setSearchType((prev) => prev === urlType ? prev : urlType);
|
|
50
|
+
let urlValue = "";
|
|
51
|
+
for (const paramName of Object.values(searchFields)) {
|
|
52
|
+
const v = searchParams.get(paramName);
|
|
53
|
+
if (v) {
|
|
54
|
+
urlValue = v;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
setSearchValue((prev) => prev === urlValue ? prev : urlValue);
|
|
59
|
+
const urlFilters = {};
|
|
60
|
+
Object.entries(filterFields).forEach(([key, fieldConfig]) => {
|
|
61
|
+
const paramValue = searchParams.get(fieldConfig.paramName);
|
|
62
|
+
if (paramValue) urlFilters[key] = fieldConfig.type === "array" ? paramValue.split(",") : paramValue;
|
|
63
|
+
else urlFilters[key] = fieldConfig.defaultValue ?? (fieldConfig.type === "array" ? [] : "");
|
|
64
|
+
});
|
|
65
|
+
setFilters((prev) => {
|
|
66
|
+
return Object.keys(urlFilters).some((k) => {
|
|
67
|
+
const a = prev[k];
|
|
68
|
+
const b = urlFilters[k];
|
|
69
|
+
if (a === b) return false;
|
|
70
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i])) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}) ? urlFilters : prev;
|
|
73
|
+
});
|
|
74
|
+
}, [searchParams]);
|
|
75
|
+
const stableConfig = {
|
|
76
|
+
basePath,
|
|
77
|
+
searchFields,
|
|
78
|
+
filterFields,
|
|
79
|
+
defaultSearchType
|
|
80
|
+
};
|
|
81
|
+
const handleSearch = useCallback(() => {
|
|
82
|
+
const params = new URLSearchParams(searchParams);
|
|
83
|
+
clearSearchAndFilterParams(params, stableConfig);
|
|
84
|
+
const searchParamsNew = buildSearchParams(searchType, searchValue, searchFields);
|
|
85
|
+
for (const [key, value] of searchParamsNew) params.set(key, value);
|
|
86
|
+
const filterParams = buildFilterParams(filters, filterFields);
|
|
87
|
+
for (const [key, value] of filterParams) params.set(key, value);
|
|
88
|
+
const nextQs = params.toString();
|
|
89
|
+
if (nextQs !== searchParams.toString()) router.push(nextQs ? `${basePath}?${nextQs}` : basePath);
|
|
90
|
+
}, [
|
|
91
|
+
searchType,
|
|
92
|
+
searchValue,
|
|
93
|
+
filters,
|
|
94
|
+
searchFields,
|
|
95
|
+
filterFields,
|
|
96
|
+
searchParams,
|
|
97
|
+
router,
|
|
98
|
+
basePath,
|
|
99
|
+
defaultSearchType
|
|
100
|
+
]);
|
|
101
|
+
const clearSearch = useCallback(() => {
|
|
102
|
+
const params = new URLSearchParams(searchParams);
|
|
103
|
+
clearSearchAndFilterParams(params, stableConfig);
|
|
104
|
+
setSearchValue("");
|
|
105
|
+
setSearchType(defaultSearchType);
|
|
106
|
+
const resetFilters = {};
|
|
107
|
+
Object.entries(filterFields).forEach(([key, fieldConfig]) => {
|
|
108
|
+
resetFilters[key] = fieldConfig.defaultValue ?? (fieldConfig.type === "array" ? [] : "");
|
|
109
|
+
});
|
|
110
|
+
setFilters(resetFilters);
|
|
111
|
+
const next = params.toString();
|
|
112
|
+
router.push(next ? `${basePath}?${next}` : `${basePath}`);
|
|
113
|
+
}, [
|
|
114
|
+
searchParams,
|
|
115
|
+
router,
|
|
116
|
+
basePath,
|
|
117
|
+
defaultSearchType,
|
|
118
|
+
searchFields,
|
|
119
|
+
filterFields
|
|
120
|
+
]);
|
|
121
|
+
const clearSearchValue = useCallback(() => {
|
|
122
|
+
const params = new URLSearchParams(searchParams);
|
|
123
|
+
Object.values(searchFields).forEach((paramName) => {
|
|
124
|
+
params.delete(paramName);
|
|
125
|
+
});
|
|
126
|
+
setSearchValue("");
|
|
127
|
+
setSearchType(defaultSearchType);
|
|
128
|
+
params.delete("page");
|
|
129
|
+
const next = params.toString();
|
|
130
|
+
router.push(next ? `${basePath}?${next}` : basePath);
|
|
131
|
+
}, [
|
|
132
|
+
searchParams,
|
|
133
|
+
router,
|
|
134
|
+
basePath,
|
|
135
|
+
defaultSearchType,
|
|
136
|
+
searchFields
|
|
137
|
+
]);
|
|
138
|
+
const clearFilters = useCallback(() => {
|
|
139
|
+
const params = new URLSearchParams(searchParams);
|
|
140
|
+
Object.values(filterFields).forEach((fc) => {
|
|
141
|
+
params.delete(fc.paramName);
|
|
142
|
+
const base = fc.paramName.replace(/\[.*\]$/, "");
|
|
143
|
+
params.delete(`${base}[in]`);
|
|
144
|
+
});
|
|
145
|
+
const resetFilters = {};
|
|
146
|
+
Object.entries(filterFields).forEach(([key, fieldConfig]) => {
|
|
147
|
+
resetFilters[key] = fieldConfig.defaultValue ?? (fieldConfig.type === "array" ? [] : "");
|
|
148
|
+
});
|
|
149
|
+
setFilters(resetFilters);
|
|
150
|
+
params.delete("page");
|
|
151
|
+
const next = params.toString();
|
|
152
|
+
router.push(next ? `${basePath}?${next}` : basePath);
|
|
153
|
+
}, [
|
|
154
|
+
searchParams,
|
|
155
|
+
router,
|
|
156
|
+
basePath,
|
|
157
|
+
filterFields
|
|
158
|
+
]);
|
|
159
|
+
const removeFilter = useCallback((key) => {
|
|
160
|
+
const fieldConfig = filterFields[key];
|
|
161
|
+
if (!fieldConfig) return;
|
|
162
|
+
const defaultValue = fieldConfig.defaultValue ?? (fieldConfig.type === "array" ? [] : "");
|
|
163
|
+
setFilters((prev) => ({
|
|
164
|
+
...prev,
|
|
165
|
+
[key]: defaultValue
|
|
166
|
+
}));
|
|
167
|
+
const params = new URLSearchParams(searchParams);
|
|
168
|
+
params.delete(fieldConfig.paramName);
|
|
169
|
+
const base = fieldConfig.paramName.replace(/\[.*\]$/, "");
|
|
170
|
+
params.delete(`${base}[in]`);
|
|
171
|
+
params.delete("page");
|
|
172
|
+
const next = params.toString();
|
|
173
|
+
router.push(next ? `${basePath}?${next}` : basePath);
|
|
174
|
+
}, [
|
|
175
|
+
filterFields,
|
|
176
|
+
searchParams,
|
|
177
|
+
router,
|
|
178
|
+
basePath
|
|
179
|
+
]);
|
|
180
|
+
const getSearchParamsForApi = useCallback(() => {
|
|
181
|
+
return getApiParams(searchParams);
|
|
182
|
+
}, [searchParams]);
|
|
183
|
+
return {
|
|
184
|
+
searchType,
|
|
185
|
+
setSearchType,
|
|
186
|
+
searchValue,
|
|
187
|
+
setSearchValue,
|
|
188
|
+
filters,
|
|
189
|
+
setFilters,
|
|
190
|
+
updateFilter: useCallback((key, value) => {
|
|
191
|
+
setFilters((prev) => ({
|
|
192
|
+
...prev,
|
|
193
|
+
[key]: value
|
|
194
|
+
}));
|
|
195
|
+
}, []),
|
|
196
|
+
handleSearch,
|
|
197
|
+
clearSearch,
|
|
198
|
+
clearSearchValue,
|
|
199
|
+
clearFilters,
|
|
200
|
+
removeFilter,
|
|
201
|
+
getSearchParams: getSearchParamsForApi,
|
|
202
|
+
filterFields,
|
|
203
|
+
hasActiveSearch: Object.values(searchFields).some((paramName) => {
|
|
204
|
+
return searchParams.has(paramName);
|
|
205
|
+
}),
|
|
206
|
+
hasActiveFilters: Object.values(filterFields).some((fieldConfig) => {
|
|
207
|
+
const baseField = fieldConfig.paramName.replace(/\[.*\]$/, "");
|
|
208
|
+
return searchParams.has(fieldConfig.paramName) || searchParams.has(`${baseField}[in]`) || searchParams.has(baseField);
|
|
209
|
+
})
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/hooks/use-copy-to-clipboard.ts
|
|
215
|
+
/**
|
|
216
|
+
* useCopyToClipboard — Copy text to clipboard with status tracking.
|
|
217
|
+
*
|
|
218
|
+
* @param resetDelay - How long (ms) the `copied` state stays true. Default: 2000
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```tsx
|
|
222
|
+
* const { copy, copied } = useCopyToClipboard();
|
|
223
|
+
*
|
|
224
|
+
* <Button onClick={() => copy(apiKey)}>
|
|
225
|
+
* {copied ? "Copied!" : "Copy API Key"}
|
|
226
|
+
* </Button>
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
function useCopyToClipboard(resetDelay = 2e3) {
|
|
230
|
+
const [copied, setCopied] = useState(false);
|
|
231
|
+
const [error, setError] = useState(null);
|
|
232
|
+
const timerRef = useRef(void 0);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
return () => {
|
|
235
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
236
|
+
};
|
|
237
|
+
}, []);
|
|
238
|
+
const reset = useCallback(() => {
|
|
239
|
+
setCopied(false);
|
|
240
|
+
setError(null);
|
|
241
|
+
}, []);
|
|
242
|
+
return {
|
|
243
|
+
copy: useCallback(async (text) => {
|
|
244
|
+
if (!navigator?.clipboard) {
|
|
245
|
+
setError(/* @__PURE__ */ new Error("Clipboard API not available"));
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
await navigator.clipboard.writeText(text);
|
|
250
|
+
setCopied(true);
|
|
251
|
+
setError(null);
|
|
252
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
253
|
+
timerRef.current = setTimeout(() => setCopied(false), resetDelay);
|
|
254
|
+
return true;
|
|
255
|
+
} catch (e) {
|
|
256
|
+
setError(e instanceof Error ? e : /* @__PURE__ */ new Error("Copy failed"));
|
|
257
|
+
setCopied(false);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}, [resetDelay]),
|
|
261
|
+
copied,
|
|
262
|
+
error,
|
|
263
|
+
reset
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/lib/storage.ts
|
|
269
|
+
/**
|
|
270
|
+
* A utility module for handling localStorage operations with error handling and SSR safety
|
|
271
|
+
*/
|
|
272
|
+
/**
|
|
273
|
+
* Gets an item from localStorage with parsing and expiry check
|
|
274
|
+
* @param {string} key - The key to retrieve from localStorage
|
|
275
|
+
* @param {any} defaultValue - Default value to return if key doesn't exist or on error
|
|
276
|
+
* @returns {any} The parsed value or defaultValue
|
|
277
|
+
*/
|
|
278
|
+
const getStorageItem = (key, defaultValue = null) => {
|
|
279
|
+
if (typeof window === "undefined") return defaultValue;
|
|
280
|
+
try {
|
|
281
|
+
const item = window.localStorage.getItem(key);
|
|
282
|
+
if (!item) return defaultValue;
|
|
283
|
+
const parsed = JSON.parse(item);
|
|
284
|
+
if (parsed && typeof parsed === "object" && parsed.__expiresAt) {
|
|
285
|
+
if (Date.now() > parsed.__expiresAt) {
|
|
286
|
+
window.localStorage.removeItem(key);
|
|
287
|
+
return defaultValue;
|
|
288
|
+
}
|
|
289
|
+
return parsed.value;
|
|
290
|
+
}
|
|
291
|
+
return parsed;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
294
|
+
return defaultValue;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Sets an item in localStorage with serialization and optional expiry
|
|
299
|
+
* @param {string} key - The key to set in localStorage
|
|
300
|
+
* @param {any} value - The value to serialize and store
|
|
301
|
+
* @param {number} [ttl] - Time to live in milliseconds (optional)
|
|
302
|
+
* @returns {boolean} Success status
|
|
303
|
+
*/
|
|
304
|
+
const setStorageItem = (key, value, ttl = null) => {
|
|
305
|
+
if (typeof window === "undefined") return false;
|
|
306
|
+
try {
|
|
307
|
+
let itemToStore = value;
|
|
308
|
+
if (ttl && typeof ttl === "number" && ttl > 0) itemToStore = {
|
|
309
|
+
value,
|
|
310
|
+
__expiresAt: Date.now() + ttl
|
|
311
|
+
};
|
|
312
|
+
window.localStorage.setItem(key, JSON.stringify(itemToStore));
|
|
313
|
+
return true;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Removes an item from localStorage
|
|
321
|
+
* @param {string} key - The key to remove from localStorage
|
|
322
|
+
* @returns {boolean} Success status
|
|
323
|
+
*/
|
|
324
|
+
const removeStorageItem = (key) => {
|
|
325
|
+
if (typeof window === "undefined") return false;
|
|
326
|
+
try {
|
|
327
|
+
window.localStorage.removeItem(key);
|
|
328
|
+
return true;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error(`Error removing localStorage key "${key}":`, error);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Clears all items from localStorage
|
|
336
|
+
* @returns {boolean} Success status
|
|
337
|
+
*/
|
|
338
|
+
const clearStorage = () => {
|
|
339
|
+
if (typeof window === "undefined") return false;
|
|
340
|
+
try {
|
|
341
|
+
window.localStorage.clear();
|
|
342
|
+
return true;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error("Error clearing localStorage:", error);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Checks if localStorage is empty for a specific key
|
|
350
|
+
* @param {string} key - The key to check in localStorage
|
|
351
|
+
* @returns {boolean} True if empty or doesn't exist
|
|
352
|
+
*/
|
|
353
|
+
const isStorageEmpty = (key) => {
|
|
354
|
+
const data = getStorageItem(key);
|
|
355
|
+
return !data || Array.isArray(data) && data.length === 0;
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* Generates a UUID (v4)
|
|
359
|
+
* Uses crypto.randomUUID() when available, with a fallback for older environments.
|
|
360
|
+
* @returns {string} A random UUID
|
|
361
|
+
*/
|
|
362
|
+
const generateUUID = () => {
|
|
363
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
364
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
365
|
+
const r = Math.random() * 16 | 0;
|
|
366
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Common expiry durations in milliseconds
|
|
371
|
+
*/
|
|
372
|
+
const TTL = {
|
|
373
|
+
MINUTE: 60 * 1e3,
|
|
374
|
+
HOUR: 3600 * 1e3,
|
|
375
|
+
DAY: 1440 * 60 * 1e3,
|
|
376
|
+
WEEK: 10080 * 60 * 1e3,
|
|
377
|
+
MONTH: 720 * 60 * 60 * 1e3
|
|
378
|
+
};
|
|
379
|
+
/**
|
|
380
|
+
* Shorthand object for all localStorage operations
|
|
381
|
+
*/
|
|
382
|
+
const storage = {
|
|
383
|
+
get: getStorageItem,
|
|
384
|
+
set: setStorageItem,
|
|
385
|
+
remove: removeStorageItem,
|
|
386
|
+
clear: clearStorage,
|
|
387
|
+
isEmpty: isStorageEmpty,
|
|
388
|
+
generateUUID,
|
|
389
|
+
TTL
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/hooks/use-local-storage.ts
|
|
394
|
+
/**
|
|
395
|
+
* useLocalStorage — Persist state in localStorage with type safety and optional expiry.
|
|
396
|
+
*
|
|
397
|
+
* @param key - The localStorage key
|
|
398
|
+
* @param initialValue - Default value if no stored value exists
|
|
399
|
+
* @param ttl - Time to live in milliseconds (optional)
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```tsx
|
|
403
|
+
* const [theme, setTheme] = useLocalStorage("theme", "light");
|
|
404
|
+
* const [cache, setCache] = useLocalStorage("api-cache", {}, 60000); // 1 min TTL
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
function useLocalStorage(key, initialValue, ttl) {
|
|
408
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
409
|
+
const item = storage.get(key, initialValue);
|
|
410
|
+
return item !== null ? item : initialValue;
|
|
411
|
+
});
|
|
412
|
+
const keyRef = useRef(key);
|
|
413
|
+
const ttlRef = useRef(ttl);
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
keyRef.current = key;
|
|
416
|
+
ttlRef.current = ttl;
|
|
417
|
+
}, [key, ttl]);
|
|
418
|
+
const setValue = useCallback((value) => {
|
|
419
|
+
setStoredValue((prev) => {
|
|
420
|
+
const nextValue = value instanceof Function ? value(prev) : value;
|
|
421
|
+
storage.set(keyRef.current, nextValue, ttlRef.current);
|
|
422
|
+
return nextValue;
|
|
423
|
+
});
|
|
424
|
+
}, []);
|
|
425
|
+
const removeValue = useCallback(() => {
|
|
426
|
+
storage.remove(keyRef.current);
|
|
427
|
+
setStoredValue(initialValue);
|
|
428
|
+
}, [initialValue]);
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
const handleStorage = (e) => {
|
|
431
|
+
if (e.key === keyRef.current) {
|
|
432
|
+
const item = storage.get(keyRef.current, initialValue);
|
|
433
|
+
setStoredValue(item !== null ? item : initialValue);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
window.addEventListener("storage", handleStorage);
|
|
437
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
438
|
+
}, [initialValue]);
|
|
439
|
+
return [
|
|
440
|
+
storedValue,
|
|
441
|
+
setValue,
|
|
442
|
+
removeValue
|
|
443
|
+
];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
//#endregion
|
|
447
|
+
export { TTL, clearStorage, generateUUID, getStorageItem, isStorageEmpty, removeStorageItem, setStorageItem, storage, useBaseSearch, useCopyToClipboard, useDebounce, useDebouncedCallback, useIsMobile, useKeyboardShortcut, useLocalStorage, useMediaQuery, useScrollDetection };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { n as ApiPaginationData } from "../api-pagination-CJ0vR_w6.mjs";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
import React, { ReactNode } from "react";
|
|
4
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
5
|
+
|
|
6
|
+
//#region src/components/data-table.d.ts
|
|
7
|
+
interface DataTablePaginationProps extends Partial<ApiPaginationData> {
|
|
8
|
+
onPageChange?: (page: number) => void;
|
|
9
|
+
}
|
|
10
|
+
interface DataTableProps<TData, TValue> {
|
|
11
|
+
columns: ColumnDef<TData, TValue>[];
|
|
12
|
+
data: TData[];
|
|
13
|
+
isLoading?: boolean;
|
|
14
|
+
pagination?: DataTablePaginationProps;
|
|
15
|
+
enableSorting?: boolean;
|
|
16
|
+
enableRowSelection?: boolean;
|
|
17
|
+
onRowSelectionChange?: (selectedRows: TData[]) => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Custom loading state renderer */
|
|
20
|
+
loadingState?: React.ReactNode;
|
|
21
|
+
/** Custom empty state renderer */
|
|
22
|
+
emptyState?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
declare function DataTable<TData, TValue>({
|
|
25
|
+
columns,
|
|
26
|
+
data,
|
|
27
|
+
isLoading,
|
|
28
|
+
pagination,
|
|
29
|
+
enableSorting,
|
|
30
|
+
enableRowSelection,
|
|
31
|
+
onRowSelectionChange,
|
|
32
|
+
className,
|
|
33
|
+
loadingState: customLoadingState,
|
|
34
|
+
emptyState: customEmptyState
|
|
35
|
+
}: DataTableProps<TData, TValue>): react_jsx_runtime0.JSX.Element;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/components/data-table-toolbar.d.ts
|
|
38
|
+
interface DataTableToolbarProps {
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
className?: string;
|
|
41
|
+
showSearch?: boolean;
|
|
42
|
+
searchPlaceholder?: string;
|
|
43
|
+
showFilters?: boolean;
|
|
44
|
+
filterContent?: ReactNode;
|
|
45
|
+
filterTitle?: string;
|
|
46
|
+
selectedCount?: number;
|
|
47
|
+
bulkActions?: ReactNode;
|
|
48
|
+
totalResults?: number;
|
|
49
|
+
showResultCount?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* DataTableToolbar - Composable toolbar with search, filters, and bulk actions
|
|
53
|
+
*
|
|
54
|
+
* Must be used inside a `Search.Root` context.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <Search.Root hook={searchHook}>
|
|
59
|
+
* <DataTableToolbar
|
|
60
|
+
* searchPlaceholder="Search users..."
|
|
61
|
+
* filterContent={<SelectInput name="role" items={roles} />}
|
|
62
|
+
* totalResults={data?.total}
|
|
63
|
+
* selectedCount={selected.length}
|
|
64
|
+
* bulkActions={<Button variant="destructive">Delete</Button>}
|
|
65
|
+
* />
|
|
66
|
+
* <DataTable columns={columns} data={data?.items ?? []} />
|
|
67
|
+
* </Search.Root>
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function DataTableToolbar({
|
|
71
|
+
children,
|
|
72
|
+
className,
|
|
73
|
+
showSearch,
|
|
74
|
+
searchPlaceholder,
|
|
75
|
+
showFilters,
|
|
76
|
+
filterContent,
|
|
77
|
+
filterTitle,
|
|
78
|
+
selectedCount,
|
|
79
|
+
bulkActions,
|
|
80
|
+
totalResults,
|
|
81
|
+
showResultCount
|
|
82
|
+
}: DataTableToolbarProps): react_jsx_runtime0.JSX.Element;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { DataTable, type DataTablePaginationProps, type DataTableProps, DataTableToolbar, type DataTableToolbarProps };
|