@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.
- package/package.json +36 -8
- package/src/atoms/AtomicAvatar.tsx +69 -40
- package/src/atoms/AtomicSpinner.tsx +24 -22
- package/src/atoms/AtomicText.tsx +32 -27
- package/src/atoms/AtomicTextArea.tsx +17 -15
- package/src/atoms/EmptyState.tsx +45 -42
- package/src/atoms/button/AtomicButton.tsx +8 -9
- package/src/atoms/card/AtomicCard.tsx +26 -8
- package/src/atoms/datepicker/components/DatePickerButton.tsx +8 -8
- package/src/atoms/datepicker/components/DatePickerModal.tsx +7 -7
- package/src/atoms/fab/styles/fabStyles.ts +1 -22
- package/src/atoms/icon/index.ts +6 -20
- package/src/atoms/picker/components/PickerModal.tsx +24 -4
- package/src/atoms/skeleton/AtomicSkeleton.tsx +9 -11
- package/src/carousel/Carousel.tsx +43 -20
- package/src/carousel/carouselCalculations.ts +12 -9
- package/src/carousel/index.ts +0 -1
- package/src/device/detection/iPadDetection.ts +5 -14
- package/src/device/infrastructure/services/DeviceFeatureService.ts +89 -9
- package/src/device/infrastructure/services/DeviceInfoService.ts +33 -0
- package/src/device/infrastructure/services/UserFriendlyIdService.ts +8 -6
- package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +56 -20
- package/src/device/infrastructure/utils/nativeModuleUtils.ts +16 -2
- package/src/device/infrastructure/utils/stringUtils.ts +51 -5
- package/src/filesystem/domain/utils/FileUtils.ts +5 -1
- package/src/image/domain/utils/ImageUtils.ts +6 -0
- package/src/layouts/AppHeader/AppHeader.tsx +13 -3
- package/src/layouts/Container/Container.tsx +19 -1
- package/src/layouts/FormLayout/FormLayout.tsx +20 -1
- package/src/layouts/Grid/Grid.tsx +34 -4
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +4 -0
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +42 -3
- package/src/molecules/Divider/types.ts +1 -1
- package/src/molecules/SearchBar/SearchBar.tsx +28 -24
- package/src/molecules/StepHeader/StepHeader.tsx +1 -1
- package/src/molecules/StepProgress/StepProgress.tsx +1 -1
- package/src/molecules/action-footer/ActionFooter.tsx +33 -32
- package/src/molecules/alerts/AlertModal.tsx +36 -20
- package/src/molecules/alerts/AlertService.ts +60 -15
- package/src/molecules/avatar/Avatar.tsx +48 -40
- package/src/molecules/avatar/AvatarGroup.tsx +8 -8
- package/src/molecules/bottom-sheet/components/BottomSheet.tsx +1 -1
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +1 -1
- package/src/molecules/bottom-sheet/components/filter/FilterSheet.tsx +1 -1
- package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +12 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +48 -32
- package/src/molecules/circular-menu/CircularMenuItem.tsx +1 -1
- package/src/molecules/countdown/components/CountdownHeader.tsx +1 -1
- package/src/molecules/countdown/components/TimeUnit.tsx +1 -1
- package/src/molecules/hero-section/HeroSection.tsx +1 -1
- package/src/molecules/icon-grid/IconGrid.tsx +1 -1
- package/src/molecules/info-grid/InfoGrid.tsx +6 -4
- package/src/molecules/navigation/TabsNavigator.tsx +1 -1
- package/src/molecules/navigation/components/NavigationHeader.tsx +1 -1
- package/src/organisms/FormContainer.tsx +11 -1
- package/src/tanstack/domain/utils/ErrorHelpers.ts +2 -2
- package/src/tanstack/domain/utils/MetricsCalculator.ts +6 -1
- package/src/theme/core/colors/ColorUtils.ts +7 -4
- package/src/utils/formatters/stringFormatter.ts +18 -3
- package/src/utils/index.ts +140 -0
- 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 '../../
|
|
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 "../../
|
|
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 '../../
|
|
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 '../../
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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 '../../
|
|
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={
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 '../../
|
|
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 '../../
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 '../../../
|
|
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 '../../../
|
|
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 '../../../../
|
|
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
|
-
|
|
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
|
/**
|