@umituz/react-native-design-system 2.8.42 → 2.8.44
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/package.json +15 -14
- package/src/device/detection/deviceDetection.ts +10 -10
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +56 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +29 -140
- package/src/responsive/compute/computeDeviceInfo.ts +7 -5
- package/src/responsive/compute/computeResponsiveSizes.ts +9 -7
- package/src/responsive/gridUtils.ts +6 -2
- package/src/responsive/responsiveModal.ts +4 -4
- package/src/responsive/responsiveSizing.ts +37 -13
- package/src/responsive/useResponsive.ts +14 -13
- package/src/tanstack/domain/repositories/BaseRepository.ts +16 -55
- package/src/tanstack/domain/repositories/RepositoryTypes.ts +39 -0
- package/src/tanstack/index.ts +13 -13
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +8 -90
- package/src/tanstack/infrastructure/monitoring/DevMonitor.types.ts +40 -0
- package/src/tanstack/infrastructure/monitoring/DevMonitorLogger.ts +58 -0
- package/src/timezone/domain/entities/Timezone.ts +16 -221
- package/src/timezone/domain/entities/TimezoneCapabilities.ts +189 -0
- package/src/timezone/domain/entities/TimezoneTypes.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.44",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and onboarding utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"url": "https://github.com/umituz/react-native-design-system"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
|
-
"@tanstack/query-async-storage-persister": ">=5.0.0",
|
|
64
|
-
"@tanstack/react-query": ">=5.0.0",
|
|
65
|
-
"@tanstack/react-query-persist-client": ">=5.0.0",
|
|
66
63
|
"@expo/vector-icons": ">=15.0.0",
|
|
64
|
+
"@react-native-async-storage/async-storage": ">=1.18.0",
|
|
67
65
|
"@react-native-community/datetimepicker": ">=8.0.0",
|
|
68
66
|
"@react-native-community/slider": ">=4.0.0",
|
|
69
|
-
"@react-native-async-storage/async-storage": ">=1.18.0",
|
|
70
67
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
71
68
|
"@react-navigation/native": ">=7.0.0",
|
|
72
69
|
"@react-navigation/stack": ">=7.0.0",
|
|
70
|
+
"@tanstack/query-async-storage-persister": ">=5.0.0",
|
|
71
|
+
"@tanstack/react-query": ">=5.0.0",
|
|
72
|
+
"@tanstack/react-query-persist-client": ">=5.0.0",
|
|
73
73
|
"@umituz/react-native-haptics": "*",
|
|
74
74
|
"@umituz/react-native-localization": "*",
|
|
75
75
|
"@umituz/react-native-uuid": "*",
|
|
@@ -104,9 +104,6 @@
|
|
|
104
104
|
}
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@tanstack/query-async-storage-persister": "^5.66.7",
|
|
108
|
-
"@tanstack/react-query": "^5.66.7",
|
|
109
|
-
"@tanstack/react-query-persist-client": "^5.66.7",
|
|
110
107
|
"@eslint/js": "^9.39.2",
|
|
111
108
|
"@expo/vector-icons": "^15.0.0",
|
|
112
109
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
@@ -115,6 +112,9 @@
|
|
|
115
112
|
"@react-navigation/bottom-tabs": "^7.9.0",
|
|
116
113
|
"@react-navigation/native": "^7.1.26",
|
|
117
114
|
"@react-navigation/stack": "^7.6.13",
|
|
115
|
+
"@tanstack/query-async-storage-persister": "^5.66.7",
|
|
116
|
+
"@tanstack/react-query": "^5.66.7",
|
|
117
|
+
"@tanstack/react-query-persist-client": "^5.66.7",
|
|
118
118
|
"@testing-library/react": "^16.3.1",
|
|
119
119
|
"@testing-library/react-native": "^13.3.3",
|
|
120
120
|
"@types/jest": "^30.0.0",
|
|
@@ -135,19 +135,20 @@
|
|
|
135
135
|
"expo-device": "~7.0.2",
|
|
136
136
|
"expo-file-system": "^19.0.21",
|
|
137
137
|
"expo-font": "~14.0.0",
|
|
138
|
-
"expo-network": "~8.0.0",
|
|
139
|
-
"expo-video": "~3.0.0",
|
|
140
138
|
"expo-haptics": "~14.0.0",
|
|
139
|
+
"expo-image": "~3.0.11",
|
|
141
140
|
"expo-image-manipulator": "~13.0.0",
|
|
142
141
|
"expo-image-picker": "~16.0.0",
|
|
143
|
-
"expo-media-library": "~17.0.0",
|
|
144
|
-
"expo-image": "~3.0.11",
|
|
145
142
|
"expo-localization": "~16.0.1",
|
|
143
|
+
"expo-media-library": "~17.0.0",
|
|
144
|
+
"expo-modules-core": "^3.0.29",
|
|
145
|
+
"expo-network": "~8.0.0",
|
|
146
146
|
"expo-secure-store": "~14.0.0",
|
|
147
|
-
"i18next": "^25.0.0",
|
|
148
|
-
"react-i18next": "^16.0.0",
|
|
149
147
|
"expo-sharing": "~14.0.8",
|
|
148
|
+
"expo-video": "~3.0.0",
|
|
149
|
+
"i18next": "^25.0.0",
|
|
150
150
|
"react": "19.1.0",
|
|
151
|
+
"react-i18next": "^16.0.0",
|
|
151
152
|
"react-native": "0.81.5",
|
|
152
153
|
"react-native-gesture-handler": "^2.20.0",
|
|
153
154
|
"react-native-haptic-feedback": "^2.3.3",
|
|
@@ -60,9 +60,9 @@ export const isPhone = (): boolean => {
|
|
|
60
60
|
* Check if current device is a small phone (iPhone SE, 13 mini)
|
|
61
61
|
* Uses width breakpoint within phone category
|
|
62
62
|
*/
|
|
63
|
-
export const isSmallPhone = (): boolean => {
|
|
63
|
+
export const isSmallPhone = (offset?: { width: number }): boolean => {
|
|
64
64
|
if (!isPhone()) return false;
|
|
65
|
-
const { width } = getScreenDimensions();
|
|
65
|
+
const { width } = offset || getScreenDimensions();
|
|
66
66
|
return width <= DEVICE_BREAKPOINTS.SMALL_PHONE;
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -70,17 +70,17 @@ export const isSmallPhone = (): boolean => {
|
|
|
70
70
|
* Check if current device is a large phone (Pro Max, Plus models)
|
|
71
71
|
* Uses width breakpoint within phone category
|
|
72
72
|
*/
|
|
73
|
-
export const isLargePhone = (): boolean => {
|
|
73
|
+
export const isLargePhone = (offset?: { width: number }): boolean => {
|
|
74
74
|
if (!isPhone()) return false;
|
|
75
|
-
const { width } = getScreenDimensions();
|
|
75
|
+
const { width } = offset || getScreenDimensions();
|
|
76
76
|
return width >= DEVICE_BREAKPOINTS.MEDIUM_PHONE;
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Check if device is in landscape mode
|
|
81
81
|
*/
|
|
82
|
-
export const isLandscape = (): boolean => {
|
|
83
|
-
const { width, height } = getScreenDimensions();
|
|
82
|
+
export const isLandscape = (offset?: { width: number; height: number }): boolean => {
|
|
83
|
+
const { width, height } = offset || getScreenDimensions();
|
|
84
84
|
return width > height;
|
|
85
85
|
};
|
|
86
86
|
|
|
@@ -88,14 +88,14 @@ export const isLandscape = (): boolean => {
|
|
|
88
88
|
* Get current device type with fine-grained phone distinctions
|
|
89
89
|
* Uses expo-device for PHONE vs TABLET, width for phone size variants
|
|
90
90
|
*/
|
|
91
|
-
export const getDeviceType = (): DeviceType => {
|
|
91
|
+
export const getDeviceType = (offset?: { width: number }): DeviceType => {
|
|
92
92
|
// Use expo-device for primary detection
|
|
93
93
|
if (isTablet()) {
|
|
94
94
|
return DeviceType.TABLET;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// For phones, use width for size variants
|
|
98
|
-
const { width } = getScreenDimensions();
|
|
98
|
+
const { width } = offset || getScreenDimensions();
|
|
99
99
|
|
|
100
100
|
if (width <= DEVICE_BREAKPOINTS.SMALL_PHONE) {
|
|
101
101
|
return DeviceType.SMALL_PHONE;
|
|
@@ -109,12 +109,12 @@ export const getDeviceType = (): DeviceType => {
|
|
|
109
109
|
/**
|
|
110
110
|
* Responsive spacing multiplier based on device type
|
|
111
111
|
*/
|
|
112
|
-
export const getSpacingMultiplier = (): number => {
|
|
112
|
+
export const getSpacingMultiplier = (offset?: { width: number }): number => {
|
|
113
113
|
if (isTablet()) {
|
|
114
114
|
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_TABLET;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
if (isSmallPhone()) {
|
|
117
|
+
if (isSmallPhone(offset)) {
|
|
118
118
|
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_SMALL;
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -6,6 +6,62 @@
|
|
|
6
6
|
import type { InfiniteScrollConfig } from "../../domain/types/infinite-scroll-config";
|
|
7
7
|
import type { InfiniteScrollState } from "../../domain/types/infinite-scroll-state";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Sleep utility for retry delay
|
|
11
|
+
*/
|
|
12
|
+
export function sleep(ms: number): Promise<void> {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retry logic with exponential backoff
|
|
18
|
+
*/
|
|
19
|
+
export async function retryWithBackoff<T>(
|
|
20
|
+
fn: () => Promise<T>,
|
|
21
|
+
maxRetries: number,
|
|
22
|
+
baseDelay: number,
|
|
23
|
+
): Promise<T> {
|
|
24
|
+
let lastError: Error | undefined;
|
|
25
|
+
|
|
26
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
27
|
+
try {
|
|
28
|
+
return await fn();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
31
|
+
|
|
32
|
+
if (attempt < maxRetries) {
|
|
33
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
34
|
+
if (__DEV__) {
|
|
35
|
+
console.log(
|
|
36
|
+
`[useInfiniteScroll] Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
await sleep(delay);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw lastError;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createInitialState<T>(
|
|
48
|
+
initialPage: number,
|
|
49
|
+
totalItems?: number,
|
|
50
|
+
): InfiniteScrollState<T> {
|
|
51
|
+
return {
|
|
52
|
+
items: [],
|
|
53
|
+
pages: [],
|
|
54
|
+
currentPage: initialPage,
|
|
55
|
+
cursor: null,
|
|
56
|
+
hasMore: true,
|
|
57
|
+
isLoading: true,
|
|
58
|
+
isLoadingMore: false,
|
|
59
|
+
isRefreshing: false,
|
|
60
|
+
error: null,
|
|
61
|
+
totalItems,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
9
65
|
export function isCursorMode<T>(
|
|
10
66
|
config: InfiniteScrollConfig<T>,
|
|
11
67
|
): config is Extract<InfiniteScrollConfig<T>, { paginationMode: "cursor" }> {
|
|
@@ -13,7 +13,13 @@ import { useState, useCallback, useEffect, useRef } from "react";
|
|
|
13
13
|
import type { InfiniteScrollConfig } from "../../domain/types/infinite-scroll-config";
|
|
14
14
|
import type { InfiniteScrollState } from "../../domain/types/infinite-scroll-state";
|
|
15
15
|
import type { UseInfiniteScrollReturn } from "../../domain/types/infinite-scroll-return";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
loadData,
|
|
18
|
+
loadMoreData,
|
|
19
|
+
isCursorMode,
|
|
20
|
+
createInitialState,
|
|
21
|
+
retryWithBackoff,
|
|
22
|
+
} from "./pagination.helper";
|
|
17
23
|
|
|
18
24
|
const DEFAULT_CONFIG = {
|
|
19
25
|
pageSize: 20,
|
|
@@ -24,62 +30,6 @@ const DEFAULT_CONFIG = {
|
|
|
24
30
|
retryDelay: 1000,
|
|
25
31
|
};
|
|
26
32
|
|
|
27
|
-
function createInitialState<T>(
|
|
28
|
-
initialPage: number,
|
|
29
|
-
totalItems?: number,
|
|
30
|
-
): InfiniteScrollState<T> {
|
|
31
|
-
return {
|
|
32
|
-
items: [],
|
|
33
|
-
pages: [],
|
|
34
|
-
currentPage: initialPage,
|
|
35
|
-
cursor: null,
|
|
36
|
-
hasMore: true,
|
|
37
|
-
isLoading: true,
|
|
38
|
-
isLoadingMore: false,
|
|
39
|
-
isRefreshing: false,
|
|
40
|
-
error: null,
|
|
41
|
-
totalItems,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Sleep utility for retry delay
|
|
47
|
-
*/
|
|
48
|
-
function sleep(ms: number): Promise<void> {
|
|
49
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Retry logic with exponential backoff
|
|
54
|
-
*/
|
|
55
|
-
async function retryWithBackoff<T>(
|
|
56
|
-
fn: () => Promise<T>,
|
|
57
|
-
maxRetries: number,
|
|
58
|
-
baseDelay: number,
|
|
59
|
-
): Promise<T> {
|
|
60
|
-
let lastError: Error | undefined;
|
|
61
|
-
|
|
62
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
63
|
-
try {
|
|
64
|
-
return await fn();
|
|
65
|
-
} catch (error) {
|
|
66
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
67
|
-
|
|
68
|
-
if (attempt < maxRetries) {
|
|
69
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
70
|
-
if (__DEV__) {
|
|
71
|
-
console.log(
|
|
72
|
-
`[useInfiniteScroll] Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
await sleep(delay);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
throw lastError;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
33
|
export function useInfiniteScroll<T>(
|
|
84
34
|
config: InfiniteScrollConfig<T>,
|
|
85
35
|
): UseInfiniteScrollReturn<T> {
|
|
@@ -125,20 +75,16 @@ export function useInfiniteScroll<T>(
|
|
|
125
75
|
const loadInitial = useCallback(async () => {
|
|
126
76
|
if (isLoadingRef.current) return;
|
|
127
77
|
isLoadingRef.current = true;
|
|
128
|
-
|
|
129
78
|
cancelPendingRequests();
|
|
130
79
|
|
|
131
|
-
if (isMountedRef.current) {
|
|
132
|
-
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
133
|
-
}
|
|
80
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
134
81
|
|
|
135
82
|
const startTime = __DEV__ ? performance.now() : 0;
|
|
136
83
|
|
|
137
84
|
try {
|
|
138
85
|
const newState = await retryWithBackoff(
|
|
139
86
|
async () => {
|
|
140
|
-
|
|
141
|
-
return result;
|
|
87
|
+
return await loadData(config, initialPage, pageSize, totalItems);
|
|
142
88
|
},
|
|
143
89
|
maxRetries,
|
|
144
90
|
retryDelay,
|
|
@@ -146,7 +92,6 @@ export function useInfiniteScroll<T>(
|
|
|
146
92
|
|
|
147
93
|
if (isMountedRef.current) {
|
|
148
94
|
setState(newState);
|
|
149
|
-
|
|
150
95
|
if (__DEV__) {
|
|
151
96
|
const duration = performance.now() - startTime;
|
|
152
97
|
console.log(
|
|
@@ -157,18 +102,9 @@ export function useInfiniteScroll<T>(
|
|
|
157
102
|
}
|
|
158
103
|
} catch (error) {
|
|
159
104
|
if (isMountedRef.current) {
|
|
160
|
-
const errorMessage =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setState((prev) => ({
|
|
164
|
-
...prev,
|
|
165
|
-
isLoading: false,
|
|
166
|
-
error: errorMessage,
|
|
167
|
-
}));
|
|
168
|
-
|
|
169
|
-
if (__DEV__) {
|
|
170
|
-
console.error("[useInfiniteScroll] Load initial failed:", errorMessage);
|
|
171
|
-
}
|
|
105
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to load data";
|
|
106
|
+
setState((prev) => ({ ...prev, isLoading: false, error: errorMessage }));
|
|
107
|
+
if (__DEV__) console.error("[useInfiniteScroll] Load initial failed:", errorMessage);
|
|
172
108
|
}
|
|
173
109
|
} finally {
|
|
174
110
|
isLoadingRef.current = false;
|
|
@@ -181,25 +117,20 @@ export function useInfiniteScroll<T>(
|
|
|
181
117
|
!state.hasMore ||
|
|
182
118
|
state.isLoadingMore ||
|
|
183
119
|
state.isLoading
|
|
184
|
-
)
|
|
120
|
+
)
|
|
185
121
|
return;
|
|
186
|
-
}
|
|
187
122
|
|
|
188
123
|
if (isCursorMode(config) && !state.cursor) return;
|
|
189
124
|
|
|
190
125
|
isLoadingRef.current = true;
|
|
191
|
-
|
|
192
|
-
if (isMountedRef.current) {
|
|
193
|
-
setState((prev) => ({ ...prev, isLoadingMore: true, error: null }));
|
|
194
|
-
}
|
|
126
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, isLoadingMore: true, error: null }));
|
|
195
127
|
|
|
196
128
|
const startTime = __DEV__ ? performance.now() : 0;
|
|
197
129
|
|
|
198
130
|
try {
|
|
199
131
|
const updates = await retryWithBackoff(
|
|
200
132
|
async () => {
|
|
201
|
-
|
|
202
|
-
return result;
|
|
133
|
+
return await loadMoreData(config, state, pageSize);
|
|
203
134
|
},
|
|
204
135
|
maxRetries,
|
|
205
136
|
retryDelay,
|
|
@@ -207,30 +138,19 @@ export function useInfiniteScroll<T>(
|
|
|
207
138
|
|
|
208
139
|
if (isMountedRef.current) {
|
|
209
140
|
setState((prev) => ({ ...prev, ...updates }));
|
|
210
|
-
|
|
211
141
|
if (__DEV__) {
|
|
212
142
|
const duration = performance.now() - startTime;
|
|
213
|
-
const newItemsCount = updates.items?.length || 0;
|
|
214
143
|
console.log(
|
|
215
144
|
`[useInfiniteScroll] Load more completed in ${duration.toFixed(2)}ms`,
|
|
216
|
-
`Loaded ${
|
|
145
|
+
`Loaded ${updates.items?.length || 0} items`,
|
|
217
146
|
);
|
|
218
147
|
}
|
|
219
148
|
}
|
|
220
149
|
} catch (error) {
|
|
221
150
|
if (isMountedRef.current) {
|
|
222
|
-
const errorMessage =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
setState((prev) => ({
|
|
226
|
-
...prev,
|
|
227
|
-
isLoadingMore: false,
|
|
228
|
-
error: errorMessage,
|
|
229
|
-
}));
|
|
230
|
-
|
|
231
|
-
if (__DEV__) {
|
|
232
|
-
console.error("[useInfiniteScroll] Load more failed:", errorMessage);
|
|
233
|
-
}
|
|
151
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to load more items";
|
|
152
|
+
setState((prev) => ({ ...prev, isLoadingMore: false, error: errorMessage }));
|
|
153
|
+
if (__DEV__) console.error("[useInfiniteScroll] Load more failed:", errorMessage);
|
|
234
154
|
}
|
|
235
155
|
} finally {
|
|
236
156
|
isLoadingRef.current = false;
|
|
@@ -240,20 +160,16 @@ export function useInfiniteScroll<T>(
|
|
|
240
160
|
const refresh = useCallback(async () => {
|
|
241
161
|
if (isLoadingRef.current) return;
|
|
242
162
|
isLoadingRef.current = true;
|
|
243
|
-
|
|
244
163
|
cancelPendingRequests();
|
|
245
164
|
|
|
246
|
-
if (isMountedRef.current) {
|
|
247
|
-
setState((prev) => ({ ...prev, isRefreshing: true, error: null }));
|
|
248
|
-
}
|
|
165
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, isRefreshing: true, error: null }));
|
|
249
166
|
|
|
250
167
|
const startTime = __DEV__ ? performance.now() : 0;
|
|
251
168
|
|
|
252
169
|
try {
|
|
253
170
|
const newState = await retryWithBackoff(
|
|
254
171
|
async () => {
|
|
255
|
-
|
|
256
|
-
return result;
|
|
172
|
+
return await loadData(config, initialPage, pageSize, totalItems);
|
|
257
173
|
},
|
|
258
174
|
maxRetries,
|
|
259
175
|
retryDelay,
|
|
@@ -261,7 +177,6 @@ export function useInfiniteScroll<T>(
|
|
|
261
177
|
|
|
262
178
|
if (isMountedRef.current) {
|
|
263
179
|
setState(newState);
|
|
264
|
-
|
|
265
180
|
if (__DEV__) {
|
|
266
181
|
const duration = performance.now() - startTime;
|
|
267
182
|
console.log(
|
|
@@ -272,18 +187,9 @@ export function useInfiniteScroll<T>(
|
|
|
272
187
|
}
|
|
273
188
|
} catch (error) {
|
|
274
189
|
if (isMountedRef.current) {
|
|
275
|
-
const errorMessage =
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
setState((prev) => ({
|
|
279
|
-
...prev,
|
|
280
|
-
isRefreshing: false,
|
|
281
|
-
error: errorMessage,
|
|
282
|
-
}));
|
|
283
|
-
|
|
284
|
-
if (__DEV__) {
|
|
285
|
-
console.error("[useInfiniteScroll] Refresh failed:", errorMessage);
|
|
286
|
-
}
|
|
190
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to refresh data";
|
|
191
|
+
setState((prev) => ({ ...prev, isRefreshing: false, error: errorMessage }));
|
|
192
|
+
if (__DEV__) console.error("[useInfiniteScroll] Refresh failed:", errorMessage);
|
|
287
193
|
}
|
|
288
194
|
} finally {
|
|
289
195
|
isLoadingRef.current = false;
|
|
@@ -294,34 +200,17 @@ export function useInfiniteScroll<T>(
|
|
|
294
200
|
isLoadingRef.current = false;
|
|
295
201
|
cancelPendingRequests();
|
|
296
202
|
setState(createInitialState<T>(initialPage, totalItems));
|
|
297
|
-
|
|
298
|
-
if (__DEV__) {
|
|
299
|
-
console.log("[useInfiniteScroll] State reset");
|
|
300
|
-
}
|
|
203
|
+
if (__DEV__) console.log("[useInfiniteScroll] State reset");
|
|
301
204
|
}, [initialPage, totalItems, cancelPendingRequests]);
|
|
302
205
|
|
|
303
206
|
useEffect(() => {
|
|
304
|
-
if (autoLoad)
|
|
305
|
-
loadInitial();
|
|
306
|
-
}
|
|
307
|
-
|
|
207
|
+
if (autoLoad) loadInitial();
|
|
308
208
|
return () => {
|
|
309
|
-
|
|
310
|
-
if (__DEV__) {
|
|
311
|
-
console.log("[useInfiniteScroll] Config changed, cleaning up");
|
|
312
|
-
}
|
|
209
|
+
if (__DEV__) console.log("[useInfiniteScroll] Config changed, cleaning up");
|
|
313
210
|
};
|
|
314
211
|
}, [autoLoad, loadInitial]);
|
|
315
212
|
|
|
316
|
-
const canLoadMore =
|
|
317
|
-
state.hasMore && !state.isLoadingMore && !state.isLoading;
|
|
213
|
+
const canLoadMore = state.hasMore && !state.isLoadingMore && !state.isLoading;
|
|
318
214
|
|
|
319
|
-
return {
|
|
320
|
-
items: state.items,
|
|
321
|
-
state,
|
|
322
|
-
loadMore,
|
|
323
|
-
refresh,
|
|
324
|
-
reset,
|
|
325
|
-
canLoadMore,
|
|
326
|
-
};
|
|
215
|
+
return { items: state.items, state, loadMore, refresh, reset, canLoadMore };
|
|
327
216
|
}
|
|
@@ -13,10 +13,12 @@ export interface ComputedDeviceInfo {
|
|
|
13
13
|
readonly spacingMultiplier: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const computeDeviceInfo = (
|
|
17
|
-
|
|
16
|
+
export const computeDeviceInfo = (
|
|
17
|
+
dimensions?: { width: number; height: number }
|
|
18
|
+
): ComputedDeviceInfo => ({
|
|
19
|
+
isSmallDevice: isSmallPhone(dimensions),
|
|
18
20
|
isTabletDevice: isTablet(),
|
|
19
|
-
isLandscapeDevice: isLandscape(),
|
|
20
|
-
deviceType: getDeviceType(),
|
|
21
|
-
spacingMultiplier: getSpacingMultiplier(),
|
|
21
|
+
isLandscapeDevice: isLandscape(dimensions),
|
|
22
|
+
deviceType: getDeviceType(dimensions),
|
|
23
|
+
spacingMultiplier: getSpacingMultiplier(dimensions),
|
|
22
24
|
});
|
|
@@ -25,14 +25,16 @@ export interface ComputedResponsiveSizes {
|
|
|
25
25
|
readonly gridColumns: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export const computeResponsiveSizes = (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
export const computeResponsiveSizes = (
|
|
29
|
+
dimensions?: { width: number; height: number }
|
|
30
|
+
): ComputedResponsiveSizes => ({
|
|
31
|
+
logoSize: getResponsiveLogoSize(undefined, dimensions),
|
|
32
|
+
inputHeight: getResponsiveInputHeight(undefined, dimensions),
|
|
33
|
+
iconContainerSize: getResponsiveIconContainerSize(undefined, dimensions),
|
|
34
|
+
maxContentWidth: getResponsiveMaxWidth(undefined, dimensions),
|
|
33
35
|
minTouchTarget: getMinTouchTarget(),
|
|
34
|
-
modalMaxHeight: getResponsiveModalMaxHeight(),
|
|
35
|
-
modalMinHeight: getResponsiveMinModalHeight(),
|
|
36
|
+
modalMaxHeight: getResponsiveModalMaxHeight(dimensions),
|
|
37
|
+
modalMinHeight: getResponsiveMinModalHeight(dimensions),
|
|
36
38
|
gridColumns: getResponsiveGridColumns(),
|
|
37
39
|
});
|
|
38
40
|
|
|
@@ -46,9 +46,13 @@ export interface GridCellSizeConfig {
|
|
|
46
46
|
* Calculates optimal square cell size for a grid that fills available space
|
|
47
47
|
*
|
|
48
48
|
* @param config - Grid configuration
|
|
49
|
+
* @param dimensions - Optional dimensions override
|
|
49
50
|
* @returns Responsive cell size (width = height for square cells)
|
|
50
51
|
*/
|
|
51
|
-
export const getResponsiveGridCellSize = (
|
|
52
|
+
export const getResponsiveGridCellSize = (
|
|
53
|
+
config: GridCellSizeConfig,
|
|
54
|
+
dimensions?: { width: number; height: number }
|
|
55
|
+
): number => {
|
|
52
56
|
try {
|
|
53
57
|
const {
|
|
54
58
|
columns,
|
|
@@ -61,7 +65,7 @@ export const getResponsiveGridCellSize = (config: GridCellSizeConfig): number =>
|
|
|
61
65
|
statusBarHeight = 50,
|
|
62
66
|
} = config;
|
|
63
67
|
|
|
64
|
-
const { width, height } = getScreenDimensions();
|
|
68
|
+
const { width, height } = dimensions || getScreenDimensions();
|
|
65
69
|
|
|
66
70
|
const totalHorizontalGap = gap * (columns - 1);
|
|
67
71
|
const availableWidth = width - horizontalPadding - totalHorizontalGap;
|
|
@@ -32,9 +32,9 @@ export interface ResponsiveDialogLayout {
|
|
|
32
32
|
borderRadius: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export const getResponsiveModalMaxHeight = (): string => {
|
|
35
|
+
export const getResponsiveModalMaxHeight = (dimensions?: { height: number }): string => {
|
|
36
36
|
try {
|
|
37
|
-
const { height } = getScreenDimensions();
|
|
37
|
+
const { height } = dimensions || getScreenDimensions();
|
|
38
38
|
|
|
39
39
|
if (height <= HEIGHT_THRESHOLDS.SMALL_DEVICE) {
|
|
40
40
|
return LAYOUT_CONSTANTS.MODAL_HEIGHT_SMALL;
|
|
@@ -48,9 +48,9 @@ export const getResponsiveModalMaxHeight = (): string => {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export const getResponsiveMinModalHeight = (): number => {
|
|
51
|
+
export const getResponsiveMinModalHeight = (dimensions?: { height: number }): number => {
|
|
52
52
|
try {
|
|
53
|
-
const { height } = getScreenDimensions();
|
|
53
|
+
const { height } = dimensions || getScreenDimensions();
|
|
54
54
|
|
|
55
55
|
if (height <= HEIGHT_THRESHOLDS.SMALL_DEVICE) {
|
|
56
56
|
const calculatedHeight = height * MODAL_CONFIG.MIN_HEIGHT_PERCENT_SMALL;
|
|
@@ -22,11 +22,19 @@ export {
|
|
|
22
22
|
* Responsive logo/icon size
|
|
23
23
|
* @param baseSize - Base logo size (default: 140)
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Responsive logo/icon size
|
|
27
|
+
* @param baseSize - Base logo size (default: 140)
|
|
28
|
+
* @param dimensions - Optional dimensions override
|
|
29
|
+
*/
|
|
30
|
+
export const getResponsiveLogoSize = (
|
|
31
|
+
baseSize: number = 140,
|
|
32
|
+
dimensions?: { width: number }
|
|
33
|
+
): number => {
|
|
26
34
|
try {
|
|
27
35
|
const validatedBaseSize = validateNumber(baseSize, 'baseSize', 50, 500);
|
|
28
|
-
const { width } = getScreenDimensions();
|
|
29
|
-
const isSmallPhoneDevice = isSmallPhone();
|
|
36
|
+
const { width } = dimensions || getScreenDimensions();
|
|
37
|
+
const isSmallPhoneDevice = isSmallPhone(dimensions);
|
|
30
38
|
const isTabletDevice = isTablet();
|
|
31
39
|
|
|
32
40
|
if (isSmallPhoneDevice) {
|
|
@@ -46,11 +54,15 @@ export const getResponsiveLogoSize = (baseSize: number = 140): number => {
|
|
|
46
54
|
/**
|
|
47
55
|
* Responsive multiline input height
|
|
48
56
|
* @param baseHeight - Base input height (default: 200)
|
|
57
|
+
* @param dimensions - Optional dimensions override
|
|
49
58
|
*/
|
|
50
|
-
export const getResponsiveInputHeight = (
|
|
59
|
+
export const getResponsiveInputHeight = (
|
|
60
|
+
baseHeight: number = 200,
|
|
61
|
+
dimensions?: { height: number }
|
|
62
|
+
): number => {
|
|
51
63
|
try {
|
|
52
64
|
const validatedBaseHeight = validateNumber(baseHeight, 'baseHeight', 50, 500);
|
|
53
|
-
const { height } = getScreenDimensions();
|
|
65
|
+
const { height } = dimensions || getScreenDimensions();
|
|
54
66
|
|
|
55
67
|
if (height <= HEIGHT_THRESHOLDS.SMALL_DEVICE) {
|
|
56
68
|
const calculatedHeight = safePercentage(height, RESPONSIVE_PERCENTAGES.INPUT_SMALL_DEVICE);
|
|
@@ -69,12 +81,16 @@ export const getResponsiveInputHeight = (baseHeight: number = 200): number => {
|
|
|
69
81
|
/**
|
|
70
82
|
* Responsive icon container size
|
|
71
83
|
* @param baseSize - Base container size (default: 140)
|
|
84
|
+
* @param dimensions - Optional dimensions override
|
|
72
85
|
*/
|
|
73
|
-
export const getResponsiveIconContainerSize = (
|
|
86
|
+
export const getResponsiveIconContainerSize = (
|
|
87
|
+
baseSize: number = 140,
|
|
88
|
+
dimensions?: { width: number }
|
|
89
|
+
): number => {
|
|
74
90
|
try {
|
|
75
91
|
const validatedBaseSize = validateNumber(baseSize, 'baseSize', 50, 300);
|
|
76
|
-
const { width } = getScreenDimensions();
|
|
77
|
-
const isSmallPhoneDevice = isSmallPhone();
|
|
92
|
+
const { width } = dimensions || getScreenDimensions();
|
|
93
|
+
const isSmallPhoneDevice = isSmallPhone(dimensions);
|
|
78
94
|
const isTabletDevice = isTablet();
|
|
79
95
|
|
|
80
96
|
if (isSmallPhoneDevice) {
|
|
@@ -94,12 +110,16 @@ export const getResponsiveIconContainerSize = (baseSize: number = 140): number =
|
|
|
94
110
|
/**
|
|
95
111
|
* Responsive max width for content
|
|
96
112
|
* @param baseWidth - Base content width (default: 400)
|
|
113
|
+
* @param dimensions - Optional dimensions override
|
|
97
114
|
*/
|
|
98
|
-
export const getResponsiveMaxWidth = (
|
|
115
|
+
export const getResponsiveMaxWidth = (
|
|
116
|
+
baseWidth: number = 400,
|
|
117
|
+
dimensions?: { width: number }
|
|
118
|
+
): number => {
|
|
99
119
|
try {
|
|
100
120
|
const validatedBaseWidth = validateNumber(baseWidth, 'baseWidth', 100, 1000);
|
|
101
|
-
const { width } = getScreenDimensions();
|
|
102
|
-
const isSmallPhoneDevice = isSmallPhone();
|
|
121
|
+
const { width } = dimensions || getScreenDimensions();
|
|
122
|
+
const isSmallPhoneDevice = isSmallPhone(dimensions);
|
|
103
123
|
const isTabletDevice = isTablet();
|
|
104
124
|
|
|
105
125
|
if (isSmallPhoneDevice) {
|
|
@@ -119,11 +139,15 @@ export const getResponsiveMaxWidth = (baseWidth: number = 400): number => {
|
|
|
119
139
|
/**
|
|
120
140
|
* Responsive font size
|
|
121
141
|
* @param baseFontSize - Base font size
|
|
142
|
+
* @param dimensions - Optional dimensions override
|
|
122
143
|
*/
|
|
123
|
-
export const getResponsiveFontSize = (
|
|
144
|
+
export const getResponsiveFontSize = (
|
|
145
|
+
baseFontSize: number,
|
|
146
|
+
dimensions?: { width: number }
|
|
147
|
+
): number => {
|
|
124
148
|
try {
|
|
125
149
|
const validatedBaseSize = validateFontSize(baseFontSize);
|
|
126
|
-
const isSmallPhoneDevice = isSmallPhone();
|
|
150
|
+
const isSmallPhoneDevice = isSmallPhone(dimensions);
|
|
127
151
|
const isTabletDevice = isTablet();
|
|
128
152
|
|
|
129
153
|
if (isSmallPhoneDevice) {
|