@umituz/react-native-design-system 4.23.82 → 4.23.84

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/atoms/input/hooks/useInputState.ts +2 -4
  3. package/src/image/presentation/components/editor/text-editor/TextTransformTab.tsx +40 -152
  4. package/src/image/presentation/components/editor/text-editor/components/TransformButtonRow.tsx +124 -0
  5. package/src/layouts/Grid/Grid.tsx +16 -11
  6. package/src/media/domain/utils/FileValidator.ts +156 -0
  7. package/src/media/infrastructure/services/MediaPickerService.ts +18 -57
  8. package/src/media/infrastructure/utils/PermissionManager.ts +92 -0
  9. package/src/media/infrastructure/utils/file-media-utils.ts +25 -8
  10. package/src/media/presentation/hooks/useMedia.ts +5 -4
  11. package/src/molecules/alerts/AlertBanner.tsx +9 -25
  12. package/src/molecules/alerts/AlertInline.tsx +4 -23
  13. package/src/molecules/alerts/AlertModal.tsx +4 -11
  14. package/src/molecules/alerts/AlertToast.tsx +14 -13
  15. package/src/molecules/alerts/utils/alertUtils.ts +133 -0
  16. package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +65 -25
  17. package/src/molecules/countdown/hooks/useCountdown.ts +13 -5
  18. package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +15 -123
  19. package/src/molecules/swipe-actions/domain/utils/swipeActionHelpers.ts +109 -0
  20. package/src/molecules/swipe-actions/domain/utils/swipeActionValidator.ts +54 -0
  21. package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +24 -6
  22. package/src/offline/presentation/hooks/useOffline.ts +2 -1
  23. package/src/storage/domain/utils/devUtils.ts +7 -6
  24. package/src/storage/infrastructure/adapters/StorageService.ts +0 -9
  25. package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +0 -3
  26. package/src/tanstack/domain/utils/MetricsCalculator.ts +103 -0
  27. package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +35 -29
  28. package/src/timezone/infrastructure/utils/SimpleCache.ts +24 -2
  29. package/src/molecules/alerts/utils/alertToastHelpers.ts +0 -70
@@ -44,6 +44,14 @@ export function useCountdown(
44
44
  );
45
45
 
46
46
  const expiredRef = useRef(false);
47
+ const onExpireRef = useRef(onExpire);
48
+ const onTickRef = useRef(onTick);
49
+
50
+ // Keep refs in sync with latest callbacks
51
+ useEffect(() => {
52
+ onExpireRef.current = onExpire;
53
+ onTickRef.current = onTick;
54
+ }, [onExpire, onTick]);
47
55
 
48
56
  const updateTime = useCallback(() => {
49
57
  if (!target) return;
@@ -51,18 +59,18 @@ export function useCountdown(
51
59
  const remaining = calculateTimeRemaining(target.date);
52
60
  setTimeRemaining(remaining);
53
61
 
54
- if (onTick) {
55
- onTick(remaining);
62
+ if (onTickRef.current) {
63
+ onTickRef.current(remaining);
56
64
  }
57
65
 
58
66
  if (remaining.isExpired && !expiredRef.current) {
59
67
  expiredRef.current = true;
60
68
  setIsActive(false);
61
- if (onExpire) {
62
- onExpire();
69
+ if (onExpireRef.current) {
70
+ onExpireRef.current();
63
71
  }
64
72
  }
65
- }, [target, onTick, onExpire]);
73
+ }, [target]);
66
74
 
67
75
  useEffect(() => {
68
76
  if (!isActive || !target) return;
@@ -2,7 +2,6 @@
2
2
  * Swipe Actions Domain - Entity Layer
3
3
  *
4
4
  * Core swipe action types and configurations.
5
- * Defines pre-built action types, colors, icons, and utilities.
6
5
  *
7
6
  * @domain swipe-actions
8
7
  * @layer domain/entities
@@ -61,53 +60,6 @@ export interface SwipeableConfig {
61
60
  friction?: number;
62
61
  }
63
62
 
64
- /**
65
- * Pre-built action type configurations
66
- */
67
- export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
68
- label: string;
69
- icon: IconName;
70
- colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
71
- hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
72
- }> = {
73
- delete: {
74
- label: 'Delete',
75
- icon: 'Trash2',
76
- colorKey: 'error',
77
- hapticsIntensity: 'Heavy',
78
- },
79
- archive: {
80
- label: 'Archive',
81
- icon: 'Archive',
82
- colorKey: 'success',
83
- hapticsIntensity: 'Medium',
84
- },
85
- edit: {
86
- label: 'Edit',
87
- icon: 'Pencil',
88
- colorKey: 'primary',
89
- hapticsIntensity: 'Light',
90
- },
91
- share: {
92
- label: 'Share',
93
- icon: 'Share2',
94
- colorKey: 'secondary',
95
- hapticsIntensity: 'Light',
96
- },
97
- favorite: {
98
- label: 'Favorite',
99
- icon: 'Heart',
100
- colorKey: 'warning',
101
- hapticsIntensity: 'Light',
102
- },
103
- more: {
104
- label: 'More',
105
- icon: 'MoveHorizontal',
106
- colorKey: 'textSecondary',
107
- hapticsIntensity: 'Light',
108
- },
109
- };
110
-
111
63
  /**
112
64
  * Default swipe configuration
113
65
  */
@@ -117,78 +69,18 @@ export const DEFAULT_SWIPE_CONFIG: Required<Omit<SwipeableConfig, 'leftActions'
117
69
  friction: 2,
118
70
  };
119
71
 
120
- /**
121
- * Swipe action utility functions
122
- */
123
- export class SwipeActionUtils {
124
- /**
125
- * Gets preset configuration for action type
126
- */
127
- static getPreset(type: SwipeActionType) {
128
- if (type === 'custom') {
129
- return null;
130
- }
131
- return ACTION_PRESETS[type];
132
- }
133
-
134
- /**
135
- * Validates swipe action configuration
136
- */
137
- static validateAction(action: SwipeActionConfig): boolean {
138
- // Must have onPress handler
139
- if (!action.onPress || typeof action.onPress !== 'function') {
140
- return false;
141
- }
142
-
143
- // Custom actions must have label, icon, and color
144
- if (action.type === 'custom') {
145
- return !!(action.label && action.icon && action.color);
146
- }
147
-
148
- return true;
149
- }
150
-
151
- /**
152
- * Gets action display label
153
- */
154
- static getLabel(action: SwipeActionConfig): string {
155
- if (action.label) {
156
- return action.label;
157
- }
158
-
159
- const preset = this.getPreset(action.type);
160
- return preset?.label || 'Action';
161
- }
162
-
163
- /**
164
- * Gets action icon name
165
- */
166
- static getIcon(action: SwipeActionConfig): IconName {
167
- if (action.icon) {
168
- return action.icon;
169
- }
170
-
171
- const preset = this.getPreset(action.type);
172
- return preset?.icon || 'MoveHorizontal';
173
- }
174
-
175
- /**
176
- * Gets action color key for theme
177
- */
178
- static getColorKey(action: SwipeActionConfig): string | null {
179
- if (action.color) {
180
- return null; // Use custom color
181
- }
182
-
183
- const preset = this.getPreset(action.type);
184
- return preset?.colorKey || null;
185
- }
186
-
187
- /**
188
- * Gets haptics intensity for action
189
- */
190
- static getHapticsIntensity(action: SwipeActionConfig): 'Light' | 'Medium' | 'Heavy' {
191
- const preset = this.getPreset(action.type);
192
- return preset?.hapticsIntensity || 'Light';
193
- }
194
- }
72
+ // Re-export utilities for backward compatibility
73
+ export {
74
+ ACTION_PRESETS,
75
+ getPreset,
76
+ getActionLabel,
77
+ getActionIcon,
78
+ getActionColorKey,
79
+ getHapticsIntensity,
80
+ } from '../utils/swipeActionHelpers';
81
+
82
+ export {
83
+ validateSwipeAction,
84
+ validateSwipeActions,
85
+ getValidationError,
86
+ } from '../utils/swipeActionValidator';
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Swipe Action Helpers
3
+ *
4
+ * Helper functions for swipe action styling and behavior.
5
+ */
6
+
7
+ import type { IconName } from '../../../../atoms/icon';
8
+ import type { SwipeActionType } from '../entities/SwipeAction';
9
+
10
+ /**
11
+ * Pre-built action type configurations
12
+ */
13
+ export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
14
+ label: string;
15
+ icon: IconName;
16
+ colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
17
+ hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
18
+ }> = {
19
+ delete: {
20
+ label: 'Delete',
21
+ icon: 'Trash2',
22
+ colorKey: 'error',
23
+ hapticsIntensity: 'Heavy',
24
+ },
25
+ archive: {
26
+ label: 'Archive',
27
+ icon: 'Archive',
28
+ colorKey: 'success',
29
+ hapticsIntensity: 'Medium',
30
+ },
31
+ edit: {
32
+ label: 'Edit',
33
+ icon: 'Pencil',
34
+ colorKey: 'primary',
35
+ hapticsIntensity: 'Light',
36
+ },
37
+ share: {
38
+ label: 'Share',
39
+ icon: 'Share2',
40
+ colorKey: 'secondary',
41
+ hapticsIntensity: 'Light',
42
+ },
43
+ favorite: {
44
+ label: 'Favorite',
45
+ icon: 'Heart',
46
+ colorKey: 'warning',
47
+ hapticsIntensity: 'Light',
48
+ },
49
+ more: {
50
+ label: 'More',
51
+ icon: 'MoveHorizontal',
52
+ colorKey: 'textSecondary',
53
+ hapticsIntensity: 'Light',
54
+ },
55
+ };
56
+
57
+ /**
58
+ * Gets preset configuration for action type
59
+ */
60
+ export function getPreset(type: SwipeActionType) {
61
+ if (type === 'custom') {
62
+ return null;
63
+ }
64
+ return ACTION_PRESETS[type];
65
+ }
66
+
67
+ /**
68
+ * Gets action display label
69
+ */
70
+ export function getActionLabel(action: { type: SwipeActionType; label?: string }): string {
71
+ if (action.label) {
72
+ return action.label;
73
+ }
74
+
75
+ const preset = getPreset(action.type);
76
+ return preset?.label || 'Action';
77
+ }
78
+
79
+ /**
80
+ * Gets action icon name
81
+ */
82
+ export function getActionIcon(action: { type: SwipeActionType; icon?: IconName }): IconName {
83
+ if (action.icon) {
84
+ return action.icon;
85
+ }
86
+
87
+ const preset = getPreset(action.type);
88
+ return preset?.icon || 'MoveHorizontal';
89
+ }
90
+
91
+ /**
92
+ * Gets action color key for theme
93
+ */
94
+ export function getActionColorKey(action: { type: SwipeActionType; color?: string }): string | null {
95
+ if (action.color) {
96
+ return null; // Use custom color
97
+ }
98
+
99
+ const preset = getPreset(action.type);
100
+ return preset?.colorKey || null;
101
+ }
102
+
103
+ /**
104
+ * Gets haptics intensity for action
105
+ */
106
+ export function getHapticsIntensity(action: { type: SwipeActionType; enableHaptics?: boolean }): 'Light' | 'Medium' | 'Heavy' {
107
+ const preset = getPreset(action.type);
108
+ return preset?.hapticsIntensity || 'Light';
109
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Swipe Action Validator
3
+ *
4
+ * Validation functions for swipe action configurations.
5
+ */
6
+
7
+ import type { SwipeActionConfig } from '../entities/SwipeAction';
8
+
9
+ /**
10
+ * Validates swipe action configuration
11
+ */
12
+ export function validateSwipeAction(action: SwipeActionConfig): boolean {
13
+ // Must have onPress handler
14
+ if (!action.onPress || typeof action.onPress !== 'function') {
15
+ return false;
16
+ }
17
+
18
+ // Custom actions must have label, icon, and color
19
+ if (action.type === 'custom') {
20
+ return !!(action.label && action.icon && action.color);
21
+ }
22
+
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * Validates multiple swipe actions
28
+ */
29
+ export function validateSwipeActions(actions: SwipeActionConfig[]): boolean {
30
+ return actions.every(validateSwipeAction);
31
+ }
32
+
33
+ /**
34
+ * Gets validation error message for an action
35
+ */
36
+ export function getValidationError(action: SwipeActionConfig): string | null {
37
+ if (!action.onPress || typeof action.onPress !== 'function') {
38
+ return 'Action must have an onPress handler';
39
+ }
40
+
41
+ if (action.type === 'custom') {
42
+ if (!action.label) {
43
+ return 'Custom action must have a label';
44
+ }
45
+ if (!action.icon) {
46
+ return 'Custom action must have an icon';
47
+ }
48
+ if (!action.color) {
49
+ return 'Custom action must have a color';
50
+ }
51
+ }
52
+
53
+ return null;
54
+ }
@@ -14,7 +14,12 @@ import { AtomicText, AtomicIcon } from '../../../../atoms';
14
14
  import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
15
15
  import { HapticService } from '../../../../haptics';
16
16
  import type { SwipeActionConfig } from '../../domain/entities/SwipeAction';
17
- import { SwipeActionUtils } from '../../domain/entities/SwipeAction';
17
+ import {
18
+ getActionLabel,
19
+ getActionIcon,
20
+ getActionColorKey,
21
+ getHapticsIntensity,
22
+ } from '../../domain/entities/SwipeAction';
18
23
 
19
24
  /**
20
25
  * SwipeActionButton component props
@@ -58,19 +63,32 @@ export const SwipeActionButton: React.FC<SwipeActionButtonProps> = ({
58
63
  const tokens = useAppDesignTokens();
59
64
 
60
65
  // Get action properties
61
- const label = SwipeActionUtils.getLabel(action);
62
- const iconName = SwipeActionUtils.getIcon(action);
63
- const colorKey = SwipeActionUtils.getColorKey(action);
66
+ const label = getActionLabel(action);
67
+ const iconName = getActionIcon(action);
68
+ const colorKey = getActionColorKey(action);
64
69
  const customColor = action.color;
65
70
  const enableHaptics = action.enableHaptics !== false;
66
71
 
67
72
  // Get background color from theme or custom
68
- const backgroundColor = customColor || (colorKey ? (tokens.colors[colorKey as keyof typeof tokens.colors] as string) : tokens.colors.primary);
73
+ // Type-safe color lookup with fallback
74
+ const getBackgroundColor = (): string => {
75
+ if (customColor) {
76
+ return customColor;
77
+ }
78
+
79
+ if (colorKey && colorKey in tokens.colors) {
80
+ return tokens.colors[colorKey as keyof typeof tokens.colors];
81
+ }
82
+
83
+ return tokens.colors.primary;
84
+ };
85
+
86
+ const backgroundColor = getBackgroundColor();
69
87
 
70
88
  const handlePress = async () => {
71
89
  // Trigger haptic feedback
72
90
  if (enableHaptics) {
73
- const intensity = SwipeActionUtils.getHapticsIntensity(action);
91
+ const intensity = getHapticsIntensity(action);
74
92
  await HapticService.impact(intensity);
75
93
  }
76
94
 
@@ -56,7 +56,7 @@ export const useOffline = (config?: OfflineConfig) => {
56
56
 
57
57
  networkEvents.emit('change', networkState);
58
58
  previousStateRef.current = networkState;
59
- }, [store]);
59
+ }, [store, previousStateRef]);
60
60
 
61
61
  useEffect(() => {
62
62
  if (isInitialized.current) return;
@@ -72,6 +72,7 @@ export const useOffline = (config?: OfflineConfig) => {
72
72
  })
73
73
  .catch((_error: Error) => {
74
74
  if (isMountedRef.current && (__DEV__ || mergedConfig.debug)) {
75
+ console.error('[DesignSystem] useOffline: Failed to get initial network state', _error);
75
76
  }
76
77
  });
77
78
 
@@ -11,24 +11,25 @@ export const isDev = (): boolean => {
11
11
 
12
12
  /**
13
13
  * Log warning in development mode only
14
+ * All logs are disabled
14
15
  */
15
16
  export const devWarn = (_message: string, ..._args: unknown[]): void => {
16
- if (__DEV__) {
17
- }
17
+ // Disabled
18
18
  };
19
19
 
20
20
  /**
21
21
  * Log error in development mode only
22
+ * All logs are disabled
22
23
  */
23
24
  export const devError = (_message: string, ..._args: unknown[]): void => {
24
- if (__DEV__) {
25
- }
25
+ // Disabled
26
26
  };
27
27
 
28
28
  /**
29
29
  * Log info in development mode only
30
+ * All logs are disabled
30
31
  */
31
32
  export const devLog = (_message: string, ..._args: unknown[]): void => {
32
- if (__DEV__) {
33
- }
33
+ // Disabled
34
34
  };
35
+
@@ -20,9 +20,6 @@ export const storageService: StateStorage = {
20
20
  } catch (error) {
21
21
  const errorMessage = `StorageService: Failed to get item "${name}"`;
22
22
  devWarn(errorMessage, error);
23
- // Also log in production for debugging
24
- if (!__DEV__) {
25
- }
26
23
  return null;
27
24
  }
28
25
  },
@@ -33,9 +30,6 @@ export const storageService: StateStorage = {
33
30
  } catch (error) {
34
31
  const errorMessage = `StorageService: Failed to set item "${name}"`;
35
32
  devWarn(errorMessage, error);
36
- // Also log in production for debugging
37
- if (!__DEV__) {
38
- }
39
33
  }
40
34
  },
41
35
 
@@ -45,9 +39,6 @@ export const storageService: StateStorage = {
45
39
  } catch (error) {
46
40
  const errorMessage = `StorageService: Failed to remove item "${name}"`;
47
41
  devWarn(errorMessage, error);
48
- // Also log in production for debugging
49
- if (!__DEV__) {
50
- }
51
42
  }
52
43
  },
53
44
  };
@@ -83,9 +83,6 @@ export class BaseStorageOperations {
83
83
  } catch (error) {
84
84
  const errorMessage = `BaseStorageOperations: Failed to check if key "${key}" exists`;
85
85
  devWarn(errorMessage, error);
86
- // Also log in production for debugging
87
- if (!__DEV__) {
88
- }
89
86
  return false;
90
87
  }
91
88
  }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Metrics Calculator
3
+ *
4
+ * Calculates query performance metrics for monitoring.
5
+ */
6
+
7
+ import type { Query } from '@tanstack/react-query';
8
+ import type { QueryMetrics, DevMonitorOptions } from '../../infrastructure/monitoring/DevMonitor.types';
9
+
10
+ /**
11
+ * Metrics calculator for query performance
12
+ */
13
+ export class MetricsCalculator {
14
+ /**
15
+ * Gets unique key string for query
16
+ */
17
+ static getQueryKeyString(queryKey: readonly unknown[]): string {
18
+ return JSON.stringify(queryKey);
19
+ }
20
+
21
+ /**
22
+ * Calculates fetch time for a query
23
+ */
24
+ static calculateFetchTime(query: Query): number {
25
+ return Date.now() - (query.state.dataUpdatedAt ?? Date.now());
26
+ }
27
+
28
+ /**
29
+ * Creates initial metrics for a query
30
+ */
31
+ static createInitialMetrics(query: Query): QueryMetrics {
32
+ return {
33
+ queryKey: query.queryKey,
34
+ fetchCount: 0,
35
+ totalFetchTime: 0,
36
+ averageFetchTime: 0,
37
+ slowFetchCount: 0,
38
+ lastFetchTime: null,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Updates metrics with new fetch data
44
+ */
45
+ static updateMetrics(
46
+ current: QueryMetrics,
47
+ fetchTime: number
48
+ ): QueryMetrics {
49
+ const newFetchCount = current.fetchCount + 1;
50
+ const newTotalFetchTime = current.totalFetchTime + fetchTime;
51
+
52
+ return {
53
+ ...current,
54
+ fetchCount: newFetchCount,
55
+ totalFetchTime: newTotalFetchTime,
56
+ averageFetchTime: newTotalFetchTime / newFetchCount,
57
+ lastFetchTime: fetchTime,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Checks if fetch time exceeds slow query threshold
63
+ */
64
+ static isSlowQuery(fetchTime: number, threshold: number): boolean {
65
+ return fetchTime > threshold;
66
+ }
67
+
68
+ /**
69
+ * Increments slow fetch count if query is slow
70
+ */
71
+ static incrementSlowCountIfNeeded(
72
+ metrics: QueryMetrics,
73
+ fetchTime: number,
74
+ threshold: number
75
+ ): QueryMetrics {
76
+ if (this.isSlowQuery(fetchTime, threshold)) {
77
+ return {
78
+ ...metrics,
79
+ slowFetchCount: metrics.slowFetchCount + 1,
80
+ };
81
+ }
82
+ return metrics;
83
+ }
84
+
85
+ /**
86
+ * Calculates complete metrics for a query
87
+ */
88
+ static calculateQueryMetrics(
89
+ query: Query,
90
+ currentMetrics: QueryMetrics | null,
91
+ options: Required<DevMonitorOptions>
92
+ ): QueryMetrics {
93
+ const fetchTime = this.calculateFetchTime(query);
94
+ const baseMetrics = currentMetrics ?? this.createInitialMetrics(query);
95
+ const updatedMetrics = this.updateMetrics(baseMetrics, fetchTime);
96
+
97
+ return this.incrementSlowCountIfNeeded(
98
+ updatedMetrics,
99
+ fetchTime,
100
+ options.slowQueryThreshold
101
+ );
102
+ }
103
+ }