@umituz/react-native-design-system 4.28.10 → 4.28.12

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 (61) hide show
  1. package/package.json +36 -8
  2. package/src/atoms/AtomicAvatar.tsx +69 -40
  3. package/src/atoms/AtomicSpinner.tsx +24 -22
  4. package/src/atoms/AtomicText.tsx +32 -27
  5. package/src/atoms/AtomicTextArea.tsx +17 -15
  6. package/src/atoms/EmptyState.tsx +45 -42
  7. package/src/atoms/button/AtomicButton.tsx +8 -9
  8. package/src/atoms/card/AtomicCard.tsx +26 -8
  9. package/src/atoms/datepicker/components/DatePickerButton.tsx +8 -8
  10. package/src/atoms/datepicker/components/DatePickerModal.tsx +7 -7
  11. package/src/atoms/fab/styles/fabStyles.ts +1 -22
  12. package/src/atoms/icon/index.ts +6 -20
  13. package/src/atoms/picker/components/PickerModal.tsx +24 -4
  14. package/src/atoms/skeleton/AtomicSkeleton.tsx +9 -11
  15. package/src/carousel/Carousel.tsx +43 -20
  16. package/src/carousel/carouselCalculations.ts +12 -9
  17. package/src/carousel/index.ts +0 -1
  18. package/src/device/detection/iPadDetection.ts +5 -14
  19. package/src/device/infrastructure/services/DeviceFeatureService.ts +89 -9
  20. package/src/device/infrastructure/services/DeviceInfoService.ts +33 -0
  21. package/src/device/infrastructure/services/UserFriendlyIdService.ts +8 -6
  22. package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +56 -20
  23. package/src/device/infrastructure/utils/nativeModuleUtils.ts +16 -2
  24. package/src/device/infrastructure/utils/stringUtils.ts +51 -5
  25. package/src/filesystem/domain/utils/FileUtils.ts +5 -1
  26. package/src/image/domain/utils/ImageUtils.ts +6 -0
  27. package/src/layouts/AppHeader/AppHeader.tsx +13 -3
  28. package/src/layouts/Container/Container.tsx +19 -1
  29. package/src/layouts/FormLayout/FormLayout.tsx +20 -1
  30. package/src/layouts/Grid/Grid.tsx +34 -4
  31. package/src/layouts/ScreenHeader/ScreenHeader.tsx +4 -0
  32. package/src/layouts/ScreenLayout/ScreenLayout.tsx +42 -3
  33. package/src/molecules/Divider/types.ts +1 -1
  34. package/src/molecules/SearchBar/SearchBar.tsx +28 -24
  35. package/src/molecules/StepHeader/StepHeader.tsx +1 -1
  36. package/src/molecules/StepProgress/StepProgress.tsx +1 -1
  37. package/src/molecules/action-footer/ActionFooter.tsx +33 -32
  38. package/src/molecules/alerts/AlertModal.tsx +36 -20
  39. package/src/molecules/alerts/AlertService.ts +60 -15
  40. package/src/molecules/avatar/Avatar.tsx +48 -40
  41. package/src/molecules/avatar/AvatarGroup.tsx +8 -8
  42. package/src/molecules/bottom-sheet/components/BottomSheet.tsx +1 -1
  43. package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +1 -1
  44. package/src/molecules/bottom-sheet/components/filter/FilterSheet.tsx +1 -1
  45. package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +12 -1
  46. package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +48 -32
  47. package/src/molecules/circular-menu/CircularMenuItem.tsx +1 -1
  48. package/src/molecules/countdown/components/CountdownHeader.tsx +1 -1
  49. package/src/molecules/countdown/components/TimeUnit.tsx +1 -1
  50. package/src/molecules/hero-section/HeroSection.tsx +1 -1
  51. package/src/molecules/icon-grid/IconGrid.tsx +1 -1
  52. package/src/molecules/info-grid/InfoGrid.tsx +6 -4
  53. package/src/molecules/navigation/TabsNavigator.tsx +1 -1
  54. package/src/molecules/navigation/components/NavigationHeader.tsx +1 -1
  55. package/src/organisms/FormContainer.tsx +11 -1
  56. package/src/tanstack/domain/utils/ErrorHelpers.ts +2 -2
  57. package/src/tanstack/domain/utils/MetricsCalculator.ts +6 -1
  58. package/src/theme/core/colors/ColorUtils.ts +7 -4
  59. package/src/utils/formatters/stringFormatter.ts +18 -3
  60. package/src/utils/index.ts +140 -0
  61. package/src/utils/math/CalculationUtils.ts +10 -1
@@ -9,10 +9,10 @@ import { useAppDesignTokens } from '../../theme';
9
9
  import { AtomicIcon, useIconName } from '../../atoms';
10
10
  import { AtomicSpinner } from '../../atoms/AtomicSpinner';
11
11
  import type { SearchBarProps } from './types';
12
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
12
+ import { calculateResponsiveSize } from '../../responsive';
13
13
  import { MISC_SIZES } from '../../constants';
14
14
 
15
- export const SearchBar: React.FC<SearchBarProps> = ({
15
+ export const SearchBar: React.FC<SearchBarProps> = React.memo(({
16
16
  value,
17
17
  onChangeText,
18
18
  onSubmit,
@@ -22,8 +22,8 @@ export const SearchBar: React.FC<SearchBarProps> = ({
22
22
  placeholder = 'Search...',
23
23
  loading = false,
24
24
  disabled = false,
25
- containerStyle,
26
- inputStyle,
25
+ containerStyle: propContainerStyle,
26
+ inputStyle: propInputStyle,
27
27
  testID,
28
28
  }) => {
29
29
  const tokens = useAppDesignTokens();
@@ -42,19 +42,30 @@ export const SearchBar: React.FC<SearchBarProps> = ({
42
42
 
43
43
  const spacingMultiplier = tokens.spacingMultiplier;
44
44
 
45
+ const containerStyle = useMemo(() => [
46
+ styles.container,
47
+ {
48
+ backgroundColor: tokens.colors.surfaceVariant,
49
+ borderColor: tokens.colors.border,
50
+ height: calculateResponsiveSize(MISC_SIZES.searchInputHeight, spacingMultiplier),
51
+ paddingHorizontal: calculateResponsiveSize(tokens.spacing.md, spacingMultiplier),
52
+ borderRadius: calculateResponsiveSize(MISC_SIZES.searchBorderRadius, spacingMultiplier),
53
+ },
54
+ propContainerStyle,
55
+ ], [tokens.colors.surfaceVariant, tokens.colors.border, spacingMultiplier, propContainerStyle]);
56
+
57
+ const inputTextStyle = useMemo(() => [
58
+ styles.input,
59
+ {
60
+ color: tokens.colors.textPrimary,
61
+ fontSize: tokens.typography.bodyMedium.responsiveFontSize,
62
+ },
63
+ propInputStyle,
64
+ ], [styles.input, tokens.colors.textPrimary, tokens.typography.bodyMedium.responsiveFontSize, propInputStyle]);
65
+
45
66
  return (
46
67
  <View
47
- style={[
48
- styles.container,
49
- {
50
- backgroundColor: tokens.colors.surfaceVariant,
51
- borderColor: tokens.colors.border,
52
- height: calculateResponsiveSize(MISC_SIZES.searchInputHeight, spacingMultiplier),
53
- paddingHorizontal: calculateResponsiveSize(tokens.spacing.md, spacingMultiplier),
54
- borderRadius: calculateResponsiveSize(MISC_SIZES.searchBorderRadius, spacingMultiplier),
55
- },
56
- containerStyle,
57
- ]}
68
+ style={containerStyle}
58
69
  testID={testID}
59
70
  >
60
71
  <View style={styles.searchIcon}>
@@ -77,14 +88,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
77
88
  returnKeyType="search"
78
89
  autoCapitalize="none"
79
90
  autoCorrect={false}
80
- style={[
81
- styles.input,
82
- {
83
- color: tokens.colors.textPrimary,
84
- fontSize: tokens.typography.bodyMedium.responsiveFontSize,
85
- },
86
- inputStyle,
87
- ]}
91
+ style={inputTextStyle}
88
92
  />
89
93
 
90
94
  {(loading || showClear) && (
@@ -116,7 +120,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
116
120
  )}
117
121
  </View>
118
122
  );
119
- };
123
+ });
120
124
 
121
125
  const styles = StyleSheet.create({
122
126
  container: {
@@ -12,7 +12,7 @@ import { useAppDesignTokens } from "../../theme/hooks/useAppDesignTokens";
12
12
  import {
13
13
  calculateResponsiveSize,
14
14
  calculateLineHeight,
15
- } from "../../utils/responsiveUtils";
15
+ } from "../../responsive";
16
16
  import { SPACING, STEP_INDICATOR } from "../../constants";
17
17
  import { createMappedArray } from "../../utils";
18
18
 
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { View, StyleSheet, ViewStyle } from "react-native";
3
3
  import { useAppDesignTokens } from '../../theme/hooks/useAppDesignTokens';
4
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
4
+ import { calculateResponsiveSize } from '../../responsive';
5
5
  import { STEP_INDICATOR } from '../../constants';
6
6
  import { createMappedArray } from '../../utils/arrayUtils';
7
7
 
@@ -5,7 +5,7 @@ import { AtomicText } from '../../atoms/AtomicText';
5
5
  import { AtomicIcon } from '../../atoms';
6
6
  import { useAppDesignTokens } from '../../theme';
7
7
  import type { ActionFooterProps } from './types';
8
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
8
+ import { calculateResponsiveSize } from '../../responsive';
9
9
  import { NAVIGATION } from '../../constants';
10
10
 
11
11
  const createStyles = (spacingMultiplier: number) => StyleSheet.create({
@@ -58,38 +58,39 @@ export const ActionFooter = React.memo<ActionFooterProps>(({
58
58
  const tokens = useAppDesignTokens();
59
59
  const spacingMultiplier = tokens.spacingMultiplier;
60
60
 
61
- const baseStyles = createStyles(spacingMultiplier);
62
-
63
61
  const themedStyles = useMemo(
64
- () => ({
65
- container: {
66
- ...baseStyles.container,
67
- paddingVertical: tokens.spacing.md,
68
- gap: tokens.spacing.md,
69
- },
70
- backButton: {
71
- ...baseStyles.backButton,
72
- borderRadius: tokens.borders.radius.lg,
73
- backgroundColor: tokens.colors.surface,
74
- borderColor: tokens.colors.outlineVariant,
75
- },
76
- actionButton: {
77
- ...baseStyles.actionButton,
78
- borderRadius: tokens.borders.radius.lg,
79
- },
80
- actionContent: {
81
- ...baseStyles.actionContent,
82
- backgroundColor: tokens.colors.primary,
83
- gap: tokens.spacing.sm,
84
- paddingHorizontal: tokens.spacing.lg,
85
- },
86
- actionText: {
87
- ...baseStyles.actionText,
88
- color: tokens.colors.onPrimary,
89
- fontSize: calculateResponsiveSize(18, spacingMultiplier),
90
- },
91
- }),
92
- [baseStyles, tokens, spacingMultiplier],
62
+ () => {
63
+ const baseStyles = createStyles(spacingMultiplier);
64
+ return {
65
+ container: {
66
+ ...baseStyles.container,
67
+ paddingVertical: tokens.spacing.md,
68
+ gap: tokens.spacing.md,
69
+ },
70
+ backButton: {
71
+ ...baseStyles.backButton,
72
+ borderRadius: tokens.borders.radius.lg,
73
+ backgroundColor: tokens.colors.surface,
74
+ borderColor: tokens.colors.outlineVariant,
75
+ },
76
+ actionButton: {
77
+ ...baseStyles.actionButton,
78
+ borderRadius: tokens.borders.radius.lg,
79
+ },
80
+ actionContent: {
81
+ ...baseStyles.actionContent,
82
+ backgroundColor: tokens.colors.primary,
83
+ gap: tokens.spacing.sm,
84
+ paddingHorizontal: tokens.spacing.lg,
85
+ },
86
+ actionText: {
87
+ ...baseStyles.actionText,
88
+ color: tokens.colors.onPrimary,
89
+ fontSize: calculateResponsiveSize(18, spacingMultiplier),
90
+ },
91
+ };
92
+ },
93
+ [tokens, spacingMultiplier],
93
94
  );
94
95
 
95
96
  const handleBackPress = useCallback(() => {
@@ -9,7 +9,7 @@ import { useAppDesignTokens } from '../../theme';
9
9
  import { Alert, AlertType } from './AlertTypes';
10
10
  import { getAlertBackgroundColor } from './utils/alertUtils';
11
11
  import { useAlertDismissHandler } from './hooks';
12
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
12
+ import { calculateResponsiveSize } from '../../responsive';
13
13
  import { MODAL_SIZES, ALERT_MODAL_ICON } from '../../constants';
14
14
 
15
15
  interface AlertModalProps {
@@ -82,6 +82,36 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
82
82
  },
83
83
  }), [spacingMultiplier, tokens]);
84
84
 
85
+ const modalStyle = useMemo(() => [
86
+ styles.modal,
87
+ {
88
+ backgroundColor: tokens.colors.backgroundPrimary,
89
+ borderRadius: tokens.borders.radius.xl ?? 20,
90
+ borderWidth: 1,
91
+ borderColor: tokens.colors.border,
92
+ }
93
+ ], [styles.modal, tokens.colors.backgroundPrimary, tokens.borders.radius.xl, tokens.colors.border]);
94
+
95
+ const iconCircleStyle = useMemo(() => [
96
+ styles.iconCircle,
97
+ { backgroundColor: accentColor + '22' }
98
+ ], [styles.iconCircle, accentColor]);
99
+
100
+ const titleStyle = useMemo(() => [
101
+ styles.title,
102
+ { color: tokens.colors.textPrimary }
103
+ ], [styles.title, tokens.colors.textPrimary]);
104
+
105
+ const messageStyle = useMemo(() => [
106
+ styles.message,
107
+ { color: tokens.colors.textSecondary }
108
+ ], [styles.message, tokens.colors.textSecondary]);
109
+
110
+ const actionsContainerStyle = useMemo(() => [
111
+ hasTwoActions ? styles.actionsRow : styles.actionsColumn,
112
+ { marginTop: tokens.spacing.lg, gap: tokens.spacing.sm }
113
+ ], [hasTwoActions, styles.actionsRow, styles.actionsColumn, tokens.spacing.lg, tokens.spacing.sm]);
114
+
85
115
  return (
86
116
  <Modal
87
117
  visible
@@ -94,20 +124,9 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
94
124
  style={styles.backdrop}
95
125
  onPress={alert.dismissible ? handleClose : undefined}
96
126
  />
97
- <View style={[
98
- styles.modal,
99
- {
100
- backgroundColor: tokens.colors.backgroundPrimary,
101
- borderRadius: tokens.borders.radius.xl ?? 20,
102
- borderWidth: 1,
103
- borderColor: tokens.colors.border,
104
- }
105
- ]}>
127
+ <View style={modalStyle}>
106
128
  {/* Icon circle */}
107
- <View style={[
108
- styles.iconCircle,
109
- { backgroundColor: accentColor + '22' }
110
- ]}>
129
+ <View style={iconCircleStyle}>
111
130
  <AtomicIcon
112
131
  name={iconName}
113
132
  customSize={calculateResponsiveSize(36, spacingMultiplier)}
@@ -118,7 +137,7 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
118
137
  {/* Title */}
119
138
  <AtomicText
120
139
  type="titleLarge"
121
- style={[styles.title, { color: tokens.colors.textPrimary }]}
140
+ style={titleStyle}
122
141
  >
123
142
  {alert.title}
124
143
  </AtomicText>
@@ -127,17 +146,14 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
127
146
  {!!alert.message && (
128
147
  <AtomicText
129
148
  type="bodyMedium"
130
- style={[styles.message, { color: tokens.colors.textSecondary }]}
149
+ style={messageStyle}
131
150
  >
132
151
  {alert.message}
133
152
  </AtomicText>
134
153
  )}
135
154
 
136
155
  {/* Actions */}
137
- <View style={[
138
- hasTwoActions ? styles.actionsRow : styles.actionsColumn,
139
- { marginTop: tokens.spacing.lg, gap: tokens.spacing.sm }
140
- ]}>
156
+ <View style={actionsContainerStyle}>
141
157
  {alert.actions.length === 0 ? (
142
158
  <AtomicButton
143
159
  title="Close"
@@ -7,6 +7,12 @@ import { Alert, AlertType, AlertMode, AlertOptions, AlertPosition } from './Aler
7
7
  import { useAlertStore } from './AlertStore';
8
8
 
9
9
  export class AlertService {
10
+ // Debouncing state
11
+ private static lastAlertTime = 0;
12
+ private static debounceDelay = 300; // ms
13
+ private static pendingAlertTimeout: ReturnType<typeof setTimeout> | null = null;
14
+ private static pendingAlert: { type: AlertType; mode: AlertMode; title: string; message?: string; options?: AlertOptions } | null = null;
15
+
10
16
  /**
11
17
  * Creates a base Alert object with defaults
12
18
  */
@@ -59,38 +65,71 @@ export class AlertService {
59
65
  return this.createAlert(AlertType.INFO, AlertMode.TOAST, title, message, options);
60
66
  }
61
67
 
68
+ /**
69
+ * Add alert with debouncing to prevent spam
70
+ */
71
+ private static addAlertDebounced(type: AlertType, mode: AlertMode, title: string, message?: string, options?: AlertOptions): string {
72
+ const now = Date.now();
73
+ const timeSinceLastAlert = now - this.lastAlertTime;
74
+
75
+ // Clear any pending alert
76
+ if (this.pendingAlertTimeout) {
77
+ clearTimeout(this.pendingAlertTimeout);
78
+ this.pendingAlertTimeout = null;
79
+ }
80
+
81
+ // If enough time has passed, show immediately
82
+ if (timeSinceLastAlert >= this.debounceDelay) {
83
+ const alert = this.createAlert(type, mode, title, message, options);
84
+ useAlertStore.getState().addAlert(alert);
85
+ this.lastAlertTime = now;
86
+ return alert.id;
87
+ }
88
+
89
+ // Otherwise, debounce and show the latest alert after delay
90
+ this.pendingAlert = { type, mode, title, message, options };
91
+ this.pendingAlertTimeout = setTimeout(() => {
92
+ if (this.pendingAlert) {
93
+ const alert = this.createAlert(
94
+ this.pendingAlert.type,
95
+ this.pendingAlert.mode,
96
+ this.pendingAlert.title,
97
+ this.pendingAlert.message,
98
+ this.pendingAlert.options
99
+ );
100
+ useAlertStore.getState().addAlert(alert);
101
+ this.lastAlertTime = Date.now();
102
+ this.pendingAlert = null;
103
+ this.pendingAlertTimeout = null;
104
+ }
105
+ }, this.debounceDelay);
106
+
107
+ // Return a placeholder ID (the real alert will be shown after debounce)
108
+ return `pending-${Date.now()}`;
109
+ }
110
+
62
111
  /**
63
112
  * Convenience methods to show alerts directly from outside React components
64
113
  * These access the Zustand store directly without requiring hooks
65
114
  */
66
115
  static success(title: string, message?: string, options?: AlertOptions): string {
67
- const alert = this.createSuccessAlert(title, message, options);
68
- useAlertStore.getState().addAlert(alert);
69
- return alert.id;
116
+ return this.addAlertDebounced(AlertType.SUCCESS, AlertMode.TOAST, title, message, options);
70
117
  }
71
118
 
72
119
  static error(title: string, message?: string, options?: AlertOptions): string {
73
- const alert = this.createErrorAlert(title, message, options);
74
- useAlertStore.getState().addAlert(alert);
75
- return alert.id;
120
+ return this.addAlertDebounced(AlertType.ERROR, AlertMode.TOAST, title, message, options);
76
121
  }
77
122
 
78
123
  static warning(title: string, message?: string, options?: AlertOptions): string {
79
- const alert = this.createWarningAlert(title, message, options);
80
- useAlertStore.getState().addAlert(alert);
81
- return alert.id;
124
+ return this.addAlertDebounced(AlertType.WARNING, AlertMode.TOAST, title, message, options);
82
125
  }
83
126
 
84
127
  static info(title: string, message?: string, options?: AlertOptions): string {
85
- const alert = this.createInfoAlert(title, message, options);
86
- useAlertStore.getState().addAlert(alert);
87
- return alert.id;
128
+ return this.addAlertDebounced(AlertType.INFO, AlertMode.TOAST, title, message, options);
88
129
  }
89
130
 
90
131
  static show(type: AlertType, mode: AlertMode, title: string, message?: string, options?: AlertOptions): string {
91
- const alert = this.createAlert(type, mode, title, message, options);
92
- useAlertStore.getState().addAlert(alert);
93
- return alert.id;
132
+ return this.addAlertDebounced(type, mode, title, message, options);
94
133
  }
95
134
 
96
135
  static dismiss(id: string): void {
@@ -98,6 +137,12 @@ export class AlertService {
98
137
  }
99
138
 
100
139
  static clear(): void {
140
+ // Clear any pending alert
141
+ if (this.pendingAlertTimeout) {
142
+ clearTimeout(this.pendingAlertTimeout);
143
+ this.pendingAlertTimeout = null;
144
+ this.pendingAlert = null;
145
+ }
101
146
  useAlertStore.getState().clearAlerts();
102
147
  }
103
148
  }
@@ -12,7 +12,7 @@ import { AtomicText, AtomicIcon } from '../../atoms';
12
12
  import type { AvatarSize, AvatarShape } from './Avatar.types';
13
13
  import type { SizeConfig } from './Avatar.types';
14
14
  import { AVATAR_SIZES } from '../../constants';
15
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
15
+ import { calculateResponsiveSize } from '../../responsive';
16
16
  import { AvatarUtils } from './Avatar.utils';
17
17
 
18
18
  export interface AvatarProps {
@@ -48,23 +48,33 @@ const AvatarContent: React.FC<AvatarContentProps> = React.memo(({
48
48
  icon,
49
49
  config,
50
50
  borderRadius,
51
- imageStyle,
51
+ imageStyle: propImageStyle,
52
52
  }) => {
53
53
  const tokens = useAppDesignTokens();
54
54
 
55
+ const imageStyle = useMemo(() => [
56
+ styles.image,
57
+ {
58
+ width: config.size,
59
+ height: config.size,
60
+ borderRadius,
61
+ },
62
+ propImageStyle,
63
+ ], [config.size, borderRadius, propImageStyle]);
64
+
65
+ const initialsStyle = useMemo(() => [
66
+ styles.initials,
67
+ {
68
+ fontSize: config.fontSize,
69
+ color: tokens.colors.textInverse,
70
+ },
71
+ ], [config.fontSize, tokens.colors.textInverse]);
72
+
55
73
  if (hasImage) {
56
74
  return (
57
75
  <Image
58
76
  source={{ uri }}
59
- style={[
60
- styles.image,
61
- {
62
- width: config.size,
63
- height: config.size,
64
- borderRadius,
65
- },
66
- imageStyle,
67
- ]}
77
+ style={imageStyle}
68
78
  />
69
79
  );
70
80
  }
@@ -73,13 +83,7 @@ const AvatarContent: React.FC<AvatarContentProps> = React.memo(({
73
83
  return (
74
84
  <AtomicText
75
85
  type="bodyMedium"
76
- style={[
77
- styles.initials,
78
- {
79
- fontSize: config.fontSize,
80
- color: tokens.colors.textInverse,
81
- },
82
- ]}
86
+ style={initialsStyle}
83
87
  >
84
88
  {initials}
85
89
  </AtomicText>
@@ -154,20 +158,35 @@ export const Avatar: React.FC<AvatarProps> = ({
154
158
  right: 0,
155
159
  }), []);
156
160
 
161
+ const containerStyle = useMemo(() => [
162
+ styles.container,
163
+ {
164
+ width: config.size,
165
+ height: config.size,
166
+ borderRadius,
167
+ backgroundColor: bgColor,
168
+ },
169
+ style,
170
+ ], [config.size, borderRadius, bgColor, style]);
171
+
172
+ const statusStyle = useMemo(() => [
173
+ styles.statusIndicator,
174
+ {
175
+ width: config.statusSize,
176
+ height: config.statusSize,
177
+ borderRadius: config.statusSize / 2,
178
+ backgroundColor: AvatarUtils.getStatusColor(status),
179
+ borderWidth: config.borderWidth,
180
+ borderColor: tokens.colors.onBackground,
181
+ ...statusPosition,
182
+ },
183
+ ], [config.statusSize, config.borderWidth, status, tokens.colors.onBackground, statusPosition]);
184
+
157
185
  const AvatarWrapper = onPress ? TouchableOpacity : View;
158
186
 
159
187
  return (
160
188
  <AvatarWrapper
161
- style={[
162
- styles.container,
163
- {
164
- width: config.size,
165
- height: config.size,
166
- borderRadius,
167
- backgroundColor: bgColor,
168
- },
169
- style,
170
- ]}
189
+ style={containerStyle}
171
190
  onPress={onPress}
172
191
  disabled={!onPress}
173
192
  accessibilityRole={onPress ? 'button' : 'image'}
@@ -187,18 +206,7 @@ export const Avatar: React.FC<AvatarProps> = ({
187
206
 
188
207
  {showStatus && (
189
208
  <View
190
- style={[
191
- styles.statusIndicator,
192
- {
193
- width: config.statusSize,
194
- height: config.statusSize,
195
- borderRadius: config.statusSize / 2,
196
- backgroundColor: AvatarUtils.getStatusColor(status),
197
- borderWidth: config.borderWidth,
198
- borderColor: tokens.colors.onBackground,
199
- ...statusPosition,
200
- },
201
- ]}
209
+ style={statusStyle}
202
210
  accessibilityLabel={`Status: ${status}`}
203
211
  accessibilityRole="none"
204
212
  />
@@ -12,7 +12,7 @@ import { AtomicText } from '../../atoms';
12
12
  import { Avatar } from './Avatar';
13
13
  import type { AvatarSize, AvatarShape } from './Avatar.types';
14
14
  import { AVATAR_SIZES } from '../../constants';
15
- import { calculateResponsiveSize } from '../../utils/responsiveUtils';
15
+ import { calculateResponsiveSize } from '../../responsive';
16
16
  import type { SizeConfig } from './Avatar.types';
17
17
 
18
18
  const AVATAR_CONSTANTS = {
@@ -58,13 +58,13 @@ const AvatarItem = React.memo<{
58
58
  spacing: number;
59
59
  avatarStyle: any;
60
60
  }>(({ item, index, size, shape, spacing, avatarStyle }) => {
61
- const wrapperStyle = useMemo(
62
- () => [
63
- styles.avatarWrapper,
64
- index > 0 && { marginLeft: spacing },
65
- ],
66
- [index, spacing]
67
- );
61
+ const wrapperStyle = useMemo(() => {
62
+ const baseStyle = [styles.avatarWrapper];
63
+ if (index > 0) {
64
+ baseStyle.push({ marginLeft: spacing });
65
+ }
66
+ return baseStyle;
67
+ }, [index, spacing]);
68
68
 
69
69
  return (
70
70
  <View style={wrapperStyle}>
@@ -3,7 +3,7 @@ import { Modal, View, StyleSheet, Pressable } from 'react-native';
3
3
  import { useAppDesignTokens } from '../../../theme';
4
4
  import { useSafeAreaInsets } from '../../../safe-area';
5
5
  import { getResponsiveBottomSheetLayout } from '../../../responsive';
6
- import { calculateResponsiveSize } from '../../../utils/responsiveUtils';
6
+ import { calculateResponsiveSize } from '../../../responsive';
7
7
  import { BOTTOM_SHEET_HANDLE } from '../../../constants';
8
8
  import type {
9
9
  BottomSheetRef,
@@ -3,7 +3,7 @@ import { Modal, View, StyleSheet, Pressable } from 'react-native';
3
3
  import { useAppDesignTokens } from '../../../theme';
4
4
  import { useSafeAreaInsets } from '../../../safe-area';
5
5
  import { getResponsiveBottomSheetLayout } from '../../../responsive';
6
- import { calculateResponsiveSize } from '../../../utils/responsiveUtils';
6
+ import { calculateResponsiveSize } from '../../../responsive';
7
7
  import { BOTTOM_SHEET_HANDLE } from '../../../constants';
8
8
  import type { BottomSheetModalRef, BottomSheetModalProps } from '../types/BottomSheet';
9
9
 
@@ -3,7 +3,7 @@ import { View, StyleSheet, ScrollView, Modal, Pressable, GestureResponderEvent }
3
3
  import { useSafeAreaInsets } from "../../../../safe-area";
4
4
  import { AtomicButton } from '../../../../atoms';
5
5
  import { useAppDesignTokens } from '../../../../theme';
6
- import { calculateResponsiveSize } from '../../../../utils/responsiveUtils';
6
+ import { calculateResponsiveSize } from '../../../../responsive';
7
7
  import { BOTTOM_SHEET_HANDLE } from '../../../../constants';
8
8
  import type { FilterOption } from "../../types/Filter";
9
9
  import { FilterUtils } from "../../types/Filter";
@@ -96,7 +96,18 @@ export class DateUtilities {
96
96
  const year = parts[0] ?? 0;
97
97
  const month = parts[1] ?? 1;
98
98
  const day = parts[2] ?? 1;
99
- return new Date(year, month - 1, day);
99
+
100
+ const date = new Date(year, month - 1, day);
101
+
102
+ // Validate the date is not invalid
103
+ if (isNaN(date.getTime())) {
104
+ if (__DEV__) {
105
+ console.warn(`[DateUtilities] Invalid date string: ${dateString}`);
106
+ }
107
+ throw new Error(`Invalid date string: ${dateString}`);
108
+ }
109
+
110
+ return date;
100
111
  }
101
112
 
102
113
  /**