@umituz/react-native-design-system 4.26.13 → 4.27.1

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.
Files changed (32) hide show
  1. package/dist/molecules/ListItem.d.ts +1 -1
  2. package/dist/molecules/action-footer/ActionFooter.d.ts +1 -1
  3. package/dist/molecules/icon-grid/IconGrid.d.ts +1 -1
  4. package/dist/molecules/navigation/index.d.ts +78 -0
  5. package/dist/onboarding/presentation/components/OnboardingFooter.d.ts +1 -1
  6. package/dist/onboarding/presentation/components/OnboardingHeader.d.ts +1 -1
  7. package/dist/utils/math/CalculationUtils.d.ts +69 -0
  8. package/dist/utils/math/OpacityUtils.d.ts +31 -0
  9. package/dist/utils/math/ProgressUtils.d.ts +37 -0
  10. package/dist/utils/math/index.d.ts +6 -0
  11. package/package.json +1 -1
  12. package/src/device/detection/deviceDetection.ts +3 -1
  13. package/src/device/presentation/hooks/useAnonymousUser.ts +8 -13
  14. package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +14 -1
  15. package/src/molecules/navigation/hooks/useAppNavigation.ts +8 -3
  16. package/src/molecules/navigation/index.ts +85 -1
  17. package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +3 -3
  18. package/src/onboarding/presentation/components/OnboardingFooter.tsx +1 -1
  19. package/src/storage/cache/domain/CacheManager.ts +5 -3
  20. package/src/storage/cache/presentation/useCachedValue.ts +6 -1
  21. package/src/storage/presentation/hooks/usePersistentCache.ts +17 -2
  22. package/src/storage/presentation/hooks/useStorageState.ts +13 -2
  23. package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +0 -2
  24. package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +2 -1
  25. package/src/theme/infrastructure/stores/themeStore.ts +22 -2
  26. package/src/timezone/infrastructure/utils/SimpleCache.ts +10 -4
  27. package/src/typography/presentation/utils/textColorUtils.ts +4 -1
  28. package/src/typography/presentation/utils/textStyleUtils.ts +2 -1
  29. package/src/utils/async/retryWithBackoff.ts +7 -5
  30. package/src/utils/constants/TimeConstants.ts +34 -0
  31. package/src/utils/errors/DesignSystemError.ts +3 -2
  32. package/src/utils/logger.ts +10 -4
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { ListItemProps } from './listitem/types';
3
3
  export type { ListItemProps };
4
- export declare const ListItem: React.FC<ListItemProps>;
4
+ export declare const ListItem: React.NamedExoticComponent<ListItemProps>;
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
2
  import type { ActionFooterProps } from './types';
3
- export declare const ActionFooter: React.FC<ActionFooterProps>;
3
+ export declare const ActionFooter: React.NamedExoticComponent<ActionFooterProps>;
@@ -44,4 +44,4 @@ export interface IconGridProps {
44
44
  * />
45
45
  * ```
46
46
  */
47
- export declare const IconGrid: React.FC<IconGridProps>;
47
+ export declare const IconGrid: React.NamedExoticComponent<IconGridProps>;
@@ -8,14 +8,92 @@ export type { BottomTabScreenProps, BottomTabNavigationOptions, } from "@react-n
8
8
  export { DEFAULT_FAB_CONFIG } from "./types";
9
9
  export { NavigationCleanupManager } from "./utils/NavigationCleanup";
10
10
  export type { NavigationCleanup } from "./utils/NavigationCleanup";
11
+ /**
12
+ * AppNavigation - Global navigation utility for programmatic navigation outside React components.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { AppNavigation } from '@umituz/react-native-design-system/molecules';
17
+ *
18
+ * // Navigate from a non-React context
19
+ * AppNavigation.navigate('ScreenName', { param: 'value' });
20
+ * ```
21
+ */
11
22
  export { AppNavigation } from "./utils/AppNavigation";
12
23
  export { TabLabel, type TabLabelProps } from "./components/TabLabel";
13
24
  export * from "./components/NavigationHeader";
14
25
  export { useTabBarStyles, type TabBarConfig } from "./hooks/useTabBarStyles";
15
26
  export { useTabConfig, type UseTabConfigProps } from "./hooks/useTabConfig";
27
+ /**
28
+ * useAppNavigation - Standard navigation hook for all React Native packages.
29
+ *
30
+ * Provides a clean, type-safe navigation API that wraps React Navigation.
31
+ * Use this hook instead of @react-navigation/native's useNavigation for consistency.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
36
+ *
37
+ * function MyScreen() {
38
+ * const navigation = useAppNavigation();
39
+ *
40
+ * return (
41
+ * <Button onPress={() => navigation.navigate('Details', { id: 123 })}>
42
+ * Go to Details
43
+ * </Button>
44
+ * );
45
+ * }
46
+ * ```
47
+ */
16
48
  export { useAppNavigation } from "./hooks/useAppNavigation";
49
+ export type { AppNavigationResult } from "./hooks/useAppNavigation";
50
+ /**
51
+ * useAppRoute - Hook to access current route parameters.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { useAppRoute } from '@umituz/react-native-design-system/molecules';
56
+ *
57
+ * function DetailsScreen() {
58
+ * const route = useAppRoute<{ id: number }>();
59
+ * const id = route.params?.id;
60
+ * }
61
+ * ```
62
+ */
17
63
  export { useAppRoute, type RouteProp } from "./hooks/useAppRoute";
64
+ /**
65
+ * useAppFocusEffect - Run effects when screen comes into focus.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * import { useAppFocusEffect } from '@umituz/react-native-design-system/molecules';
70
+ * import { useCallback } from 'react';
71
+ *
72
+ * function ProfileScreen() {
73
+ * useAppFocusEffect(
74
+ * useCallback(() => {
75
+ * console.log('Screen focused');
76
+ * return () => console.log('Screen unfocused');
77
+ * }, [])
78
+ * );
79
+ * }
80
+ * ```
81
+ */
18
82
  export { useAppFocusEffect } from "./hooks/useAppFocusEffect";
83
+ /**
84
+ * useAppIsFocused - Check if current screen is focused.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { useAppIsFocused } from '@umituz/react-native-design-system/molecules';
89
+ *
90
+ * function VideoPlayer() {
91
+ * const isFocused = useAppIsFocused();
92
+ *
93
+ * return <Video playing={isFocused} />;
94
+ * }
95
+ * ```
96
+ */
19
97
  export { useAppIsFocused } from "./hooks/useAppIsFocused";
20
98
  export { createScreenOptions } from "./utils/createScreenOptions";
21
99
  export type { ScreenOptionsParams } from "./utils/createScreenOptions";
@@ -9,4 +9,4 @@ export interface OnboardingFooterProps {
9
9
  showProgressText?: boolean;
10
10
  disabled?: boolean;
11
11
  }
12
- export declare const OnboardingFooter: ({ currentIndex, totalSlides, isLastSlide, onNext, showProgressBar, showDots, showProgressText, disabled, }: OnboardingFooterProps) => React.JSX.Element;
12
+ export declare const OnboardingFooter: React.NamedExoticComponent<OnboardingFooterProps>;
@@ -7,4 +7,4 @@ export interface OnboardingHeaderProps {
7
7
  showSkipButton?: boolean;
8
8
  skipButtonText?: string;
9
9
  }
10
- export declare const OnboardingHeader: ({ isFirstSlide, onBack, onSkip, showBackButton, showSkipButton, skipButtonText, }: OnboardingHeaderProps) => React.JSX.Element;
10
+ export declare const OnboardingHeader: React.NamedExoticComponent<OnboardingHeaderProps>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Calculation Utilities
3
+ *
4
+ * Common mathematical calculations used throughout the app
5
+ */
6
+ /**
7
+ * Clamps a number between a minimum and maximum value
8
+ * @param value - The value to clamp
9
+ * @param min - Minimum allowed value (default: 0)
10
+ * @param max - Maximum allowed value (default: 100)
11
+ * @returns Clamped value
12
+ */
13
+ export declare function clamp(value: number, min?: number, max?: number): number;
14
+ /**
15
+ * Calculates progress percentage
16
+ * @param current - Current value
17
+ * @param total - Total value
18
+ * @returns Percentage (0-100)
19
+ */
20
+ export declare function calculatePercentage(current: number, total: number): number;
21
+ /**
22
+ * Rounds a number to specified decimal places
23
+ * @param value - The value to round
24
+ * @param decimals - Number of decimal places (default: 0)
25
+ * @returns Rounded value
26
+ */
27
+ export declare function roundTo(value: number, decimals?: number): number;
28
+ /**
29
+ * Converts intensity (0-100) to opacity (0-1)
30
+ * @param intensity - Intensity value (0-100)
31
+ * @param minOpacity - Minimum opacity (default: 0.05)
32
+ * @param maxOpacity - Maximum opacity (default: 0.95)
33
+ * @returns Opacity value (0-1)
34
+ */
35
+ export declare function intensityToOpacity(intensity: number, minOpacity?: number, maxOpacity?: number): number;
36
+ /**
37
+ * Calculates grid item width based on container width and columns
38
+ * @param containerWidth - Total container width
39
+ * @param columns - Number of columns
40
+ * @param gap - Gap between items in pixels
41
+ * @returns Item width in pixels
42
+ */
43
+ export declare function calculateGridItemWidth(containerWidth: number, columns: number, gap: number): number;
44
+ /**
45
+ * Checks if a value is within a range (inclusive)
46
+ * @param value - Value to check
47
+ * @param min - Range minimum
48
+ * @param max - Range maximum
49
+ * @returns True if value is in range
50
+ */
51
+ export declare function isInRange(value: number, min: number, max: number): boolean;
52
+ /**
53
+ * Linear interpolation between two values
54
+ * @param start - Start value
55
+ * @param end - End value
56
+ * @param progress - Progress (0-1)
57
+ * @returns Interpolated value
58
+ */
59
+ export declare function lerp(start: number, end: number, progress: number): number;
60
+ /**
61
+ * Maps a value from one range to another
62
+ * @param value - Value to map
63
+ * @param inMin - Input range minimum
64
+ * @param inMax - Input range maximum
65
+ * @param outMin - Output range minimum
66
+ * @param outMax - Output range maximum
67
+ * @returns Mapped value
68
+ */
69
+ export declare function mapRange(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Opacity Utilities
3
+ *
4
+ * Helper functions for opacity-related calculations
5
+ */
6
+ /**
7
+ * Converts intensity value to opacity for glassmorphism effects
8
+ * @param intensity - Intensity value (0-100)
9
+ * @param options - Configuration options
10
+ * @returns Opacity value (0-1)
11
+ */
12
+ export declare function intensityToOpacity(intensity: number, options?: {
13
+ minOpacity?: number;
14
+ maxOpacity?: number;
15
+ invert?: boolean;
16
+ }): number;
17
+ /**
18
+ * Creates an RGBA color string with specified opacity
19
+ * @param rgb - RGB color as array [r, g, b]
20
+ * @param opacity - Opacity value (0-1)
21
+ * @returns RGBA color string
22
+ */
23
+ export declare function createRgbaColor(rgb: [number, number, number], opacity: number): string;
24
+ /**
25
+ * Calculates opacity based on a ratio (0-1)
26
+ * @param ratio - Ratio value (0-1)
27
+ * @param minOpacity - Minimum opacity (default: 0.1)
28
+ * @param maxOpacity - Maximum opacity (default: 1)
29
+ * @returns Opacity value (0-1)
30
+ */
31
+ export declare function ratioToOpacity(ratio: number, minOpacity?: number, maxOpacity?: number): number;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Progress Utilities
3
+ *
4
+ * Helper functions for progress-related calculations
5
+ */
6
+ /**
7
+ * Validates and clamps a progress value to ensure it's within valid range
8
+ * @param value - Raw progress value
9
+ * @returns Clamped progress value (0-100)
10
+ */
11
+ export declare function normalizeProgress(value: number): number;
12
+ /**
13
+ * Formats a progress value as a percentage string
14
+ * @param value - Progress value (0-100)
15
+ * @param decimals - Number of decimal places (default: 0)
16
+ * @returns Formatted percentage string
17
+ */
18
+ export declare function formatPercentage(value: number, decimals?: number): string;
19
+ /**
20
+ * Calculates the percentage completed of a multi-step process
21
+ * @param currentStep - Current step (1-indexed)
22
+ * @param totalSteps - Total number of steps
23
+ * @returns Percentage (0-100)
24
+ */
25
+ export declare function calculateStepProgress(currentStep: number, totalSteps: number): number;
26
+ /**
27
+ * Checks if progress is complete
28
+ * @param value - Progress value (0-100)
29
+ * @returns True if progress is 100%
30
+ */
31
+ export declare function isComplete(value: number): boolean;
32
+ /**
33
+ * Checks if progress has started
34
+ * @param value - Progress value (0-100)
35
+ * @returns True if progress > 0%
36
+ */
37
+ export declare function hasStarted(value: number): boolean;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Math Utilities Index
3
+ */
4
+ export { clamp, calculatePercentage, roundTo, calculateGridItemWidth, isInRange, lerp, mapRange, } from './CalculationUtils';
5
+ export { normalizeProgress, formatPercentage, calculateStepProgress, isComplete, hasStarted, } from './ProgressUtils';
6
+ export { intensityToOpacity, createRgbaColor, ratioToOpacity, } from './OpacityUtils';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "4.26.13",
3
+ "version": "4.27.1",
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, onboarding, and loading utilities - TanStack persistence and expo-image-manipulator now lazy loaded",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -46,7 +46,9 @@ export const isTablet = (): boolean => {
46
46
  return Device.deviceType === Device.DeviceType.TABLET;
47
47
  }
48
48
  // Fallback: Platform.isPad (iOS) or screen width >= 600dp (Android)
49
- if (Platform.OS === 'ios' && (Platform as any).isPad) return true;
49
+ // Platform.isPad is not in React Native types but exists on iOS
50
+ const platformWithIsPad = Platform as { isPad?: boolean };
51
+ if (Platform.OS === 'ios' && platformWithIsPad.isPad) return true;
50
52
  const { width, height } = getScreenDimensions();
51
53
  return Math.min(width, height) >= 600;
52
54
  };
@@ -46,7 +46,6 @@ export const useAnonymousUser = (
46
46
  ): UseAnonymousUserResult => {
47
47
  const {
48
48
  anonymousDisplayName = 'Anonymous',
49
- fallbackUserId = 'anonymous_fallback',
50
49
  } = options || {};
51
50
 
52
51
  const { data: anonymousUser, isLoading, error, execute } = useAsyncOperation<AnonymousUser, string>(
@@ -56,9 +55,14 @@ export const useAnonymousUser = (
56
55
  DeviceService.getUserFriendlyId(),
57
56
  ]);
58
57
 
58
+ // No fallback - if we can't get ID, let it error
59
+ if (!userId) {
60
+ throw new Error('Failed to generate device ID');
61
+ }
62
+
59
63
  return {
60
- userId: userId || fallbackUserId,
61
- deviceName: deviceName || 'Unknown Device',
64
+ userId,
65
+ deviceName: deviceName ?? 'Device',
62
66
  displayName: anonymousDisplayName,
63
67
  isAnonymous: true,
64
68
  };
@@ -66,16 +70,7 @@ export const useAnonymousUser = (
66
70
  {
67
71
  immediate: true,
68
72
  initialData: null,
69
- errorHandler: () => 'Failed to generate device ID',
70
- onError: () => {
71
- // Fallback on error - set default anonymous user
72
- return {
73
- userId: fallbackUserId,
74
- deviceName: 'Unknown Device',
75
- displayName: anonymousDisplayName,
76
- isAnonymous: true,
77
- };
78
- },
73
+ errorHandler: (err) => `Failed to generate device ID: ${err instanceof Error ? err.message : String(err)}`,
79
74
  }
80
75
  );
81
76
 
@@ -27,7 +27,13 @@ export const useCalendar = () => {
27
27
  const view = useCalendarView();
28
28
 
29
29
  // Utility functions - memoized to prevent recreating on every render
30
- const getEventsForDate = useCallback((date: Date) => {
30
+ const getEventsForDate = useCallback((date: Date | null | undefined) => {
31
+ if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
32
+ if (__DEV__) {
33
+ console.warn('[CalendarStore] getEventsForDate called with invalid date:', date);
34
+ }
35
+ return [];
36
+ }
31
37
  return events.events.filter(event => {
32
38
  const eventDate = new Date(event.date);
33
39
  return eventDate.toDateString() === date.toDateString();
@@ -35,6 +41,13 @@ export const useCalendar = () => {
35
41
  }, [events.events]);
36
42
 
37
43
  const getEventsForMonth = useCallback((year: number, month: number) => {
44
+ if (typeof year !== 'number' || typeof month !== 'number' ||
45
+ isNaN(year) || isNaN(month) || month < 0 || month > 11) {
46
+ if (__DEV__) {
47
+ console.warn('[CalendarStore] getEventsForMonth called with invalid year/month:', { year, month });
48
+ }
49
+ return [];
50
+ }
38
51
  return events.events.filter(event => {
39
52
  const eventDate = new Date(event.date);
40
53
  return eventDate.getFullYear() === year && eventDate.getMonth() === month;
@@ -1,4 +1,4 @@
1
- import { useNavigation, StackActions } from "@react-navigation/native";
1
+ import { useNavigation, StackActions, CommonActions } from "@react-navigation/native";
2
2
  import type { NavigationProp, ParamListBase } from "@react-navigation/native";
3
3
  import { useCallback, useMemo } from "react";
4
4
 
@@ -31,8 +31,13 @@ export function useAppNavigation(): AppNavigationResult {
31
31
 
32
32
  const navigate = useCallback(
33
33
  (screen: string, params?: Record<string, unknown>) => {
34
- // Dynamic navigation: bypass ParamListBase constraint to allow arbitrary screen names
35
- (navigation as any).navigate(screen, params);
34
+ // Dynamic navigation: use CommonActions for type-safe arbitrary screen navigation
35
+ navigation.dispatch(
36
+ CommonActions.navigate({
37
+ name: screen,
38
+ params,
39
+ })
40
+ );
36
41
  },
37
42
  [navigation]
38
43
  );
@@ -31,14 +31,98 @@ export { NavigationCleanupManager } from "./utils/NavigationCleanup";
31
31
  export type { NavigationCleanup } from "./utils/NavigationCleanup";
32
32
 
33
33
  // Navigation Utilities
34
+
35
+ /**
36
+ * AppNavigation - Global navigation utility for programmatic navigation outside React components.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { AppNavigation } from '@umituz/react-native-design-system/molecules';
41
+ *
42
+ * // Navigate from a non-React context
43
+ * AppNavigation.navigate('ScreenName', { param: 'value' });
44
+ * ```
45
+ */
34
46
  export { AppNavigation } from "./utils/AppNavigation";
47
+
35
48
  export { TabLabel, type TabLabelProps } from "./components/TabLabel";
36
- export * from "./components/NavigationHeader";
49
+ export { NavigationHeader, type NavigationHeaderProps } from "./components/NavigationHeader";
37
50
  export { useTabBarStyles, type TabBarConfig } from "./hooks/useTabBarStyles";
38
51
  export { useTabConfig, type UseTabConfigProps } from "./hooks/useTabConfig";
52
+
53
+ /**
54
+ * useAppNavigation - Standard navigation hook for all React Native packages.
55
+ *
56
+ * Provides a clean, type-safe navigation API that wraps React Navigation.
57
+ * Use this hook instead of @react-navigation/native's useNavigation for consistency.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
62
+ *
63
+ * function MyScreen() {
64
+ * const navigation = useAppNavigation();
65
+ *
66
+ * return (
67
+ * <Button onPress={() => navigation.navigate('Details', { id: 123 })}>
68
+ * Go to Details
69
+ * </Button>
70
+ * );
71
+ * }
72
+ * ```
73
+ */
39
74
  export { useAppNavigation } from "./hooks/useAppNavigation";
75
+ export type { AppNavigationResult } from "./hooks/useAppNavigation";
76
+
77
+ /**
78
+ * useAppRoute - Hook to access current route parameters.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * import { useAppRoute } from '@umituz/react-native-design-system/molecules';
83
+ *
84
+ * function DetailsScreen() {
85
+ * const route = useAppRoute<{ id: number }>();
86
+ * const id = route.params?.id;
87
+ * }
88
+ * ```
89
+ */
40
90
  export { useAppRoute, type RouteProp } from "./hooks/useAppRoute";
91
+
92
+ /**
93
+ * useAppFocusEffect - Run effects when screen comes into focus.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * import { useAppFocusEffect } from '@umituz/react-native-design-system/molecules';
98
+ * import { useCallback } from 'react';
99
+ *
100
+ * function ProfileScreen() {
101
+ * useAppFocusEffect(
102
+ * useCallback(() => {
103
+ * console.log('Screen focused');
104
+ * return () => console.log('Screen unfocused');
105
+ * }, [])
106
+ * );
107
+ * }
108
+ * ```
109
+ */
41
110
  export { useAppFocusEffect } from "./hooks/useAppFocusEffect";
111
+
112
+ /**
113
+ * useAppIsFocused - Check if current screen is focused.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * import { useAppIsFocused } from '@umituz/react-native-design-system/molecules';
118
+ *
119
+ * function VideoPlayer() {
120
+ * const isFocused = useAppIsFocused();
121
+ *
122
+ * return <Video playing={isFocused} />;
123
+ * }
124
+ * ```
125
+ */
42
126
  export { useAppIsFocused } from "./hooks/useAppIsFocused";
43
127
 
44
128
  // Screen Options
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import React, { useMemo } from "react";
8
- import { View, Image as RNImage, StyleSheet } from "react-native";
8
+ import { View, Image as RNImage, StyleSheet, type ImageURISource, type ImageStyle } from "react-native";
9
9
  import { useSafeAreaInsets } from "../../../safe-area/hooks/useSafeAreaInsets";
10
10
  import {
11
11
  generateGridLayout,
@@ -93,8 +93,8 @@ export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
93
93
  return (
94
94
  <RNImage
95
95
  key={String(item.source)}
96
- source={item.source as any}
97
- style={item.style as any}
96
+ source={item.source as ImageURISource | number}
97
+ style={item.style as ImageStyle}
98
98
  resizeMode="cover"
99
99
  />
100
100
  );
@@ -52,7 +52,7 @@ export const OnboardingFooter = React.memo<OnboardingFooterProps>(({
52
52
  const progressFillStyle = useMemo(
53
53
  () => ({
54
54
  ...styles.progressFill,
55
- width: `${progressPercent}%` as any,
55
+ width: `${progressPercent}%` as `${number}%`,
56
56
  backgroundColor: colors.progressFillColor,
57
57
  }),
58
58
  [progressPercent, colors.progressFillColor]
@@ -20,10 +20,12 @@ export class CacheManager {
20
20
  }
21
21
 
22
22
  getCache<T>(name: string, config?: CacheConfig): Cache<T> {
23
- if (!this.caches.has(name)) {
24
- this.caches.set(name, new Cache<T>(config));
23
+ let cache = this.caches.get(name);
24
+ if (!cache) {
25
+ cache = new Cache<T>(config);
26
+ this.caches.set(name, cache);
25
27
  }
26
- return this.caches.get(name)!;
28
+ return cache;
27
29
  }
28
30
 
29
31
  deleteCache(name: string): boolean {
@@ -25,7 +25,12 @@ export function useCachedValue<T>(
25
25
  return cached;
26
26
  }
27
27
 
28
- const data = await fetcherRef.current!();
28
+ const fetcherFn = fetcherRef.current;
29
+ if (!fetcherFn) {
30
+ throw new Error('Fetcher function is not defined');
31
+ }
32
+
33
+ const data = await fetcherFn();
29
34
  cache.set(key, data, configRef.current?.ttl);
30
35
  return data;
31
36
  },
@@ -142,10 +142,25 @@ export function usePersistentCache<T>(
142
142
 
143
143
  const setData = useCallback(
144
144
  async (value: T) => {
145
- await cacheOps.saveToStorage(key, value, { ttl, version, enabled });
145
+ // Optimistic update
146
+ const previousData = state.data;
146
147
  stableActionsRef.current.setData(value);
148
+
149
+ try {
150
+ await cacheOps.saveToStorage(key, value, { ttl, version, enabled });
151
+ } catch (error) {
152
+ // Rollback on error
153
+ if (previousData !== null) {
154
+ stableActionsRef.current.setData(previousData);
155
+ } else {
156
+ stableActionsRef.current.clearData();
157
+ }
158
+ if (__DEV__) {
159
+ console.warn('[usePersistentCache] Failed to save to storage, rolling back:', error);
160
+ }
161
+ }
147
162
  },
148
- [key, ttl, version, enabled, cacheOps],
163
+ [key, ttl, version, enabled, cacheOps, state.data],
149
164
  );
150
165
 
151
166
  const clearData = useCallback(async () => {
@@ -57,10 +57,21 @@ export const useStorageState = <T>(
57
57
  // Update state and persist to storage
58
58
  const updateState = useCallback(
59
59
  async (value: T) => {
60
+ // Optimistic update
61
+ const previousValue = state;
60
62
  setState(value);
61
- await storageRepository.setItem(keyString, value);
63
+
64
+ try {
65
+ await storageRepository.setItem(keyString, value);
66
+ } catch (error) {
67
+ // Rollback on error
68
+ setState(previousValue);
69
+ if (__DEV__) {
70
+ console.warn('[useStorageState] Failed to persist state, rolling back:', error);
71
+ }
72
+ }
62
73
  },
63
- [keyString]
74
+ [keyString, state]
64
75
  );
65
76
 
66
77
  return [state, updateState, isLoading];
@@ -188,8 +188,6 @@ class DevMonitorClass {
188
188
  reset(): void {
189
189
  if (!this.isEnabled) return;
190
190
  this.detach();
191
- this.stopStatsLogging();
192
- this.clear();
193
191
  if (this.options.enableLogging) {
194
192
  DevMonitorLogger.logReset();
195
193
  }
@@ -9,6 +9,7 @@ import type { CustomThemeColors } from '../../core/CustomColors';
9
9
  import type { SplashScreenProps } from '../../../molecules/splash/types';
10
10
  import { useIconStore } from '../../../atoms/icon/iconStore';
11
11
  import type { IconRenderer, IconNames } from '../../../atoms/icon/iconStore';
12
+ import { FIVE_SECONDS_MS } from '../../../utils/constants/TimeConstants';
12
13
 
13
14
  // Lazy load SplashScreen to avoid circular dependency
14
15
  const SplashScreen = lazy(() => import('../../../molecules/splash').then(m => ({ default: m.SplashScreen })));
@@ -82,7 +83,7 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
82
83
  if (!prev) onError?.(new Error('DesignSystemProvider initialization timed out'));
83
84
  return true;
84
85
  });
85
- }, 5000);
86
+ }, FIVE_SECONDS_MS);
86
87
 
87
88
  initialize()
88
89
  .then(() => {
@@ -12,6 +12,26 @@ import { useDesignSystemTheme } from '../globalThemeStore';
12
12
  import type { ThemeMode } from '../../core/ColorPalette';
13
13
  import type { CustomThemeColors } from '../../core/CustomColors';
14
14
 
15
+ /**
16
+ * Shallow equality check for CustomThemeColors
17
+ * Compares all defined properties without deep object traversal
18
+ */
19
+ function areCustomColorsEqual(a?: CustomThemeColors, b?: CustomThemeColors): boolean {
20
+ if (a === b) return true;
21
+ if (!a || !b) return false;
22
+
23
+ const keysA = Object.keys(a) as (keyof CustomThemeColors)[];
24
+ const keysB = Object.keys(b) as (keyof CustomThemeColors)[];
25
+
26
+ if (keysA.length !== keysB.length) return false;
27
+
28
+ for (const key of keysA) {
29
+ if (a[key] !== b[key]) return false;
30
+ }
31
+
32
+ return true;
33
+ }
34
+
15
35
  interface ThemeState {
16
36
  theme: Theme;
17
37
  themeMode: ThemeMode;
@@ -118,8 +138,8 @@ export const useTheme = createStore<ThemeState, ThemeActions>({
118
138
  const { _updateInProgress, customColors: currentColors } = get();
119
139
  if (_updateInProgress) return;
120
140
 
121
- // Deep comparison to avoid redundant updates from new object references
122
- if (JSON.stringify(colors) === JSON.stringify(currentColors)) return;
141
+ // Shallow comparison to avoid redundant updates from new object references
142
+ if (areCustomColorsEqual(colors, currentColors)) return;
123
143
 
124
144
  const updateId = Date.now();
125
145
  set({ _updateInProgress: true, _lastUpdateId: updateId, customColors: colors });
@@ -5,6 +5,8 @@
5
5
  * No external dependencies - pure TypeScript implementation
6
6
  */
7
7
 
8
+ import { ONE_MINUTE_MS } from '../../../utils/constants/TimeConstants';
9
+
8
10
  interface CacheEntry<T> {
9
11
  value: T;
10
12
  expires: number;
@@ -17,7 +19,7 @@ export class SimpleCache<T> {
17
19
  private destroyed = false;
18
20
  private cleanupScheduleLock = false;
19
21
 
20
- constructor(defaultTTL: number = 60000) {
22
+ constructor(defaultTTL: number = ONE_MINUTE_MS) {
21
23
  this.defaultTTL = defaultTTL;
22
24
  this.scheduleCleanup();
23
25
  }
@@ -91,9 +93,13 @@ export class SimpleCache<T> {
91
93
 
92
94
  if (!this.destroyed) {
93
95
  this.cleanupTimeout = setTimeout(() => {
94
- this.cleanupScheduleLock = false;
95
- this.scheduleCleanup();
96
- }, 60000);
96
+ if (!this.destroyed) {
97
+ this.cleanupScheduleLock = false;
98
+ this.scheduleCleanup();
99
+ }
100
+ }, ONE_MINUTE_MS);
101
+ } else {
102
+ this.cleanupScheduleLock = false;
97
103
  }
98
104
  } catch (error) {
99
105
  this.cleanupScheduleLock = false;
@@ -53,7 +53,10 @@ export function getTextColor(
53
53
 
54
54
  const cacheKey = `${color}_${Object.keys(tokens.colors).length}_${tokens.colors.textPrimary}`;
55
55
 
56
- if (colorCache.has(cacheKey)) return colorCache.get(cacheKey)!;
56
+ if (colorCache.has(cacheKey)) {
57
+ const cached = colorCache.get(cacheKey);
58
+ if (cached) return cached;
59
+ }
57
60
 
58
61
  const colorKey = COLOR_MAP[color as ColorVariant] ?? 'textPrimary';
59
62
  const resolvedColor = tokens.colors[colorKey];
@@ -112,7 +112,8 @@ export function getTextStyle(
112
112
 
113
113
  // Check cache first
114
114
  if (typographyCache.has(cacheKey)) {
115
- return typographyCache.get(cacheKey)!;
115
+ const cached = typographyCache.get(cacheKey);
116
+ if (cached) return cached;
116
117
  }
117
118
 
118
119
  // Resolve style and cache it
@@ -5,6 +5,8 @@
5
5
  * Useful for network requests, file operations, etc.
6
6
  */
7
7
 
8
+ import { DEFAULT_LONG_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ONE_MINUTE_MS, ONE_SECOND_MS, TEN_SECONDS_MS } from '../constants/TimeConstants';
9
+
8
10
  export interface RetryOptions {
9
11
  /**
10
12
  * Maximum number of retry attempts
@@ -59,14 +61,14 @@ export async function retryWithBackoff<T>(
59
61
  ): Promise<T> {
60
62
  const {
61
63
  maxRetries = 3,
62
- baseDelay = 1000,
63
- maxDelay = 10000,
64
+ baseDelay = ONE_SECOND_MS,
65
+ maxDelay = TEN_SECONDS_MS,
64
66
  backoffMultiplier = 2,
65
67
  shouldRetry = () => true,
66
68
  onRetry,
67
69
  } = options;
68
70
 
69
- let lastError: Error;
71
+ let lastError: Error | undefined;
70
72
 
71
73
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
72
74
  try {
@@ -106,7 +108,7 @@ export async function retryWithBackoff<T>(
106
108
  }
107
109
 
108
110
  // This should never be reached, but TypeScript needs it
109
- throw lastError!;
111
+ throw lastError ?? new Error('Retry operation failed with unknown error');
110
112
  }
111
113
 
112
114
  /**
@@ -125,7 +127,7 @@ export async function retryWithTimeout<T>(
125
127
  fn: () => Promise<T>,
126
128
  options: RetryOptions & { timeout?: number } = {}
127
129
  ): Promise<T> {
128
- const { timeout = 30000, ...retryOptions } = options;
130
+ const { timeout = DEFAULT_LONG_TIMEOUT_MS, ...retryOptions } = options;
129
131
 
130
132
  return retryWithBackoff(
131
133
  () => withTimeout(fn(), timeout),
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Time Constants
3
+ *
4
+ * Centralized time-related constants to replace magic numbers
5
+ */
6
+
7
+ export const MILLISECONDS_PER_SECOND = 1000;
8
+ export const MILLISECONDS_PER_MINUTE = 60 * 1000; // 60000
9
+ export const MILLISECONDS_PER_HOUR = 60 * 60 * 1000; // 3600000
10
+ export const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; // 86400000
11
+
12
+ export const SECONDS_PER_MINUTE = 60;
13
+ export const SECONDS_PER_HOUR = 60 * 60;
14
+ export const SECONDS_PER_DAY = 24 * 60 * 60;
15
+
16
+ export const MINUTES_PER_HOUR = 60;
17
+ export const MINUTES_PER_DAY = 24 * 60;
18
+
19
+ // Common time intervals
20
+ export const ONE_SECOND_MS = MILLISECONDS_PER_SECOND;
21
+ export const FIVE_SECONDS_MS = 5 * MILLISECONDS_PER_SECOND;
22
+ export const TEN_SECONDS_MS = 10 * MILLISECONDS_PER_SECOND;
23
+ export const THIRTY_SECONDS_MS = 30 * MILLISECONDS_PER_SECOND;
24
+ export const ONE_MINUTE_MS = MILLISECONDS_PER_MINUTE;
25
+ export const FIVE_MINUTES_MS = 5 * MILLISECONDS_PER_MINUTE;
26
+ export const TEN_MINUTES_MS = 10 * MILLISECONDS_PER_MINUTE;
27
+ export const THIRTY_MINUTES_MS = 30 * MILLISECONDS_PER_MINUTE;
28
+ export const ONE_HOUR_MS = MILLISECONDS_PER_HOUR;
29
+ export const ONE_DAY_MS = MILLISECONDS_PER_DAY;
30
+
31
+ // Default timeouts
32
+ export const DEFAULT_TIMEOUT_MS = FIVE_SECONDS_MS;
33
+ export const DEFAULT_LONG_TIMEOUT_MS = THIRTY_SECONDS_MS;
34
+ export const DEFAULT_CACHE_TTL_MS = ONE_MINUTE_MS;
@@ -60,8 +60,9 @@ export class DesignSystemError extends Error {
60
60
  this.retryable = metadata?.retryable ?? false;
61
61
 
62
62
  // Maintains proper stack trace for where our error was thrown (only available on V8)
63
- if (typeof (Error as any).captureStackTrace === 'function') {
64
- (Error as any).captureStackTrace(this, DesignSystemError);
63
+ const ErrorConstructor = Error as { captureStackTrace?: (error: Error, constructor: typeof DesignSystemError) => void };
64
+ if (typeof ErrorConstructor.captureStackTrace === 'function') {
65
+ ErrorConstructor.captureStackTrace(this, DesignSystemError);
65
66
  }
66
67
  }
67
68
 
@@ -100,8 +100,11 @@ class Logger {
100
100
  * Use for performance measurements
101
101
  */
102
102
  time(label: string): void {
103
- if (this.isDev && (console as any).time) {
104
- (console as any).time(label);
103
+ if (this.isDev) {
104
+ const consoleWithTime = console as { time?: (label: string) => void };
105
+ if (typeof consoleWithTime.time === 'function') {
106
+ consoleWithTime.time(label);
107
+ }
105
108
  }
106
109
  }
107
110
 
@@ -109,8 +112,11 @@ class Logger {
109
112
  * End time measurement - only in development
110
113
  */
111
114
  timeEnd(label: string): void {
112
- if (this.isDev && (console as any).timeEnd) {
113
- (console as any).timeEnd(label);
115
+ if (this.isDev) {
116
+ const consoleWithTimeEnd = console as { timeEnd?: (label: string) => void };
117
+ if (typeof consoleWithTimeEnd.timeEnd === 'function') {
118
+ consoleWithTimeEnd.timeEnd(label);
119
+ }
114
120
  }
115
121
  }
116
122