@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.8.42",
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 { loadData, loadMoreData, isCursorMode } from "./pagination.helper";
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
- const result = await loadData(config, initialPage, pageSize, totalItems);
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
- error instanceof Error ? error.message : "Failed to load data";
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
- const result = await loadMoreData(config, state, pageSize);
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 ${newItemsCount} items, total: ${updates.items?.length || 0}`,
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
- error instanceof Error ? error.message : "Failed to load more items";
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
- const result = await loadData(config, initialPage, pageSize, totalItems);
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
- error instanceof Error ? error.message : "Failed to refresh data";
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
- // Cleanup on config change
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 = (): ComputedDeviceInfo => ({
17
- isSmallDevice: isSmallPhone(),
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 = (): ComputedResponsiveSizes => ({
29
- logoSize: getResponsiveLogoSize(),
30
- inputHeight: getResponsiveInputHeight(),
31
- iconContainerSize: getResponsiveIconContainerSize(),
32
- maxContentWidth: getResponsiveMaxWidth(),
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 = (config: GridCellSizeConfig): number => {
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
- export const getResponsiveLogoSize = (baseSize: number = 140): number => {
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 = (baseHeight: number = 200): number => {
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 = (baseSize: number = 140): number => {
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 = (baseWidth: number = 400): number => {
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 = (baseFontSize: number): number => {
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) {