@umituz/react-native-design-system 2.6.34 → 2.6.38
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 +2 -4
- package/src/atoms/AtomicInput.tsx +21 -4
- package/src/atoms/AtomicKeyboardAvoidingView.tsx +49 -0
- package/src/atoms/AtomicTextArea.tsx +78 -29
- package/src/atoms/index.ts +3 -0
- package/src/device/infrastructure/services/PersistentDeviceIdService.ts +6 -6
- package/src/layouts/FormLayout/FormLayout.tsx +4 -3
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +4 -4
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +12 -11
- package/src/theme/infrastructure/storage/ThemeStorage.ts +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.38",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@expo/vector-icons": ">=15.0.0",
|
|
53
53
|
"@gorhom/bottom-sheet": ">=5.0.0",
|
|
54
|
-
"@react-native-async-storage/async-storage": ">=2.0.0",
|
|
55
54
|
"@react-native-community/datetimepicker": ">=8.0.0",
|
|
56
55
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
57
56
|
"@react-navigation/native": ">=7.0.0",
|
|
@@ -98,7 +97,6 @@
|
|
|
98
97
|
"@eslint/js": "^9.39.2",
|
|
99
98
|
"@expo/vector-icons": "^15.0.0",
|
|
100
99
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
101
|
-
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
102
100
|
"@react-native-community/datetimepicker": "^8.5.1",
|
|
103
101
|
"@react-navigation/bottom-tabs": "^7.9.0",
|
|
104
102
|
"@react-navigation/native": "^7.1.26",
|
|
@@ -113,7 +111,7 @@
|
|
|
113
111
|
"@umituz/react-native-filesystem": "^2.1.7",
|
|
114
112
|
"@umituz/react-native-haptics": "^1.0.2",
|
|
115
113
|
"@umituz/react-native-localization": "^3.5.34",
|
|
116
|
-
"@umituz/react-native-storage": "
|
|
114
|
+
"@umituz/react-native-storage": "^2.6.20",
|
|
117
115
|
"@umituz/react-native-uuid": "*",
|
|
118
116
|
"eslint": "^9.39.2",
|
|
119
117
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -43,7 +43,15 @@ export interface AtomicInputProps {
|
|
|
43
43
|
/** Show character counter */
|
|
44
44
|
showCharacterCount?: boolean;
|
|
45
45
|
/** Keyboard type */
|
|
46
|
-
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad';
|
|
46
|
+
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad' | 'web-search' | 'twitter' | 'numeric' | 'visible-password';
|
|
47
|
+
/** Return key type */
|
|
48
|
+
returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
|
|
49
|
+
/** Callback when submit button is pressed */
|
|
50
|
+
onSubmitEditing?: () => void;
|
|
51
|
+
/** Blur on submit */
|
|
52
|
+
blurOnSubmit?: boolean;
|
|
53
|
+
/** Auto focus */
|
|
54
|
+
autoFocus?: boolean;
|
|
47
55
|
/** Auto-capitalize */
|
|
48
56
|
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
|
|
49
57
|
/** Auto-correct */
|
|
@@ -78,7 +86,7 @@ export interface AtomicInputProps {
|
|
|
78
86
|
* - Responsive sizing
|
|
79
87
|
* - Full accessibility support
|
|
80
88
|
*/
|
|
81
|
-
export const AtomicInput
|
|
89
|
+
export const AtomicInput = React.forwardRef<TextInput, AtomicInputProps>(({
|
|
82
90
|
variant = 'outlined',
|
|
83
91
|
state = 'default',
|
|
84
92
|
size = 'md',
|
|
@@ -95,6 +103,10 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
95
103
|
maxLength,
|
|
96
104
|
showCharacterCount = false,
|
|
97
105
|
keyboardType = 'default',
|
|
106
|
+
returnKeyType,
|
|
107
|
+
onSubmitEditing,
|
|
108
|
+
blurOnSubmit,
|
|
109
|
+
autoFocus,
|
|
98
110
|
autoCapitalize = 'sentences',
|
|
99
111
|
autoCorrect = true,
|
|
100
112
|
disabled = false,
|
|
@@ -105,7 +117,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
105
117
|
onFocus,
|
|
106
118
|
multiline = false,
|
|
107
119
|
numberOfLines,
|
|
108
|
-
}) => {
|
|
120
|
+
}, ref) => {
|
|
109
121
|
const tokens = useAppDesignTokens();
|
|
110
122
|
|
|
111
123
|
const {
|
|
@@ -198,6 +210,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
198
210
|
)}
|
|
199
211
|
|
|
200
212
|
<TextInput
|
|
213
|
+
ref={ref}
|
|
201
214
|
value={localValue}
|
|
202
215
|
onChangeText={handleTextChange}
|
|
203
216
|
placeholder={placeholder}
|
|
@@ -205,6 +218,10 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
205
218
|
secureTextEntry={secureTextEntry && !isPasswordVisible}
|
|
206
219
|
maxLength={maxLength}
|
|
207
220
|
keyboardType={keyboardType}
|
|
221
|
+
returnKeyType={returnKeyType}
|
|
222
|
+
onSubmitEditing={onSubmitEditing}
|
|
223
|
+
blurOnSubmit={blurOnSubmit}
|
|
224
|
+
autoFocus={autoFocus}
|
|
208
225
|
autoCapitalize={autoCapitalize}
|
|
209
226
|
autoCorrect={autoCorrect}
|
|
210
227
|
editable={!isDisabled}
|
|
@@ -274,7 +291,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
274
291
|
)}
|
|
275
292
|
</View>
|
|
276
293
|
);
|
|
277
|
-
};
|
|
294
|
+
});
|
|
278
295
|
|
|
279
296
|
const styles = StyleSheet.create({
|
|
280
297
|
container: {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
KeyboardAvoidingView,
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type KeyboardAvoidingViewProps,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface AtomicKeyboardAvoidingViewProps extends KeyboardAvoidingViewProps {
|
|
10
|
+
/**
|
|
11
|
+
* Optional offset to adjust the position of the content.
|
|
12
|
+
* On iOS, this is often necessary to account for headers, tabs, etc.
|
|
13
|
+
*/
|
|
14
|
+
offset?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AtomicKeyboardAvoidingView - A consistent wrapper for React Native's KeyboardAvoidingView
|
|
19
|
+
*
|
|
20
|
+
* Provides sensible defaults and OS-specific behaviors:
|
|
21
|
+
* - iOS: behavior="padding"
|
|
22
|
+
* - Android: behavior=undefined (handled by windowSoftInputMode="adjustResize")
|
|
23
|
+
*/
|
|
24
|
+
export const AtomicKeyboardAvoidingView: React.FC<AtomicKeyboardAvoidingViewProps> = ({
|
|
25
|
+
children,
|
|
26
|
+
behavior,
|
|
27
|
+
style,
|
|
28
|
+
offset = 0,
|
|
29
|
+
...props
|
|
30
|
+
}) => {
|
|
31
|
+
const defaultBehavior = Platform.OS === 'ios' ? 'padding' : undefined;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<KeyboardAvoidingView
|
|
35
|
+
behavior={behavior ?? defaultBehavior}
|
|
36
|
+
style={[styles.container, style]}
|
|
37
|
+
keyboardVerticalOffset={offset}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</KeyboardAvoidingView>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const styles = StyleSheet.create({
|
|
46
|
+
container: {
|
|
47
|
+
flex: 1,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -1,32 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Atomic Design Level: ATOM
|
|
5
|
-
* Purpose: Multiline text input across all apps
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { View, TextInput, StyleSheet, ViewStyle } from 'react-native';
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { View, TextInput, StyleSheet, type ViewStyle, type StyleProp, type TextStyle } from 'react-native';
|
|
10
3
|
import { useAppDesignTokens } from '../theme';
|
|
11
4
|
import { AtomicText } from './AtomicText';
|
|
12
5
|
|
|
13
6
|
export interface AtomicTextAreaProps {
|
|
7
|
+
/** Text area label */
|
|
14
8
|
label?: string;
|
|
9
|
+
/** Current value */
|
|
15
10
|
value?: string;
|
|
11
|
+
/** Value change callback */
|
|
16
12
|
onChangeText?: (text: string) => void;
|
|
13
|
+
/** Placeholder text */
|
|
17
14
|
placeholder?: string;
|
|
15
|
+
/** Helper text below input */
|
|
18
16
|
helperText?: string;
|
|
17
|
+
/** Error message to display */
|
|
19
18
|
errorText?: string;
|
|
19
|
+
/** Maximum character length */
|
|
20
20
|
maxLength?: number;
|
|
21
|
+
/** Number of lines (default: 4) */
|
|
21
22
|
numberOfLines?: number;
|
|
23
|
+
/** Alternative to numberOfLines */
|
|
22
24
|
rows?: number;
|
|
25
|
+
/** Minimum height override */
|
|
23
26
|
minHeight?: number;
|
|
27
|
+
/** Disabled state */
|
|
24
28
|
disabled?: boolean;
|
|
25
|
-
style
|
|
29
|
+
/** Container style */
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
/** Input text style */
|
|
32
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
33
|
+
/** Auto focus */
|
|
34
|
+
autoFocus?: boolean;
|
|
35
|
+
/** Return key type */
|
|
36
|
+
returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
|
|
37
|
+
/** Callback when submit button is pressed */
|
|
38
|
+
onSubmitEditing?: () => void;
|
|
39
|
+
/** Blur on submit */
|
|
40
|
+
blurOnSubmit?: boolean;
|
|
41
|
+
/** Test ID */
|
|
26
42
|
testID?: string;
|
|
27
43
|
}
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
/**
|
|
46
|
+
* AtomicTextArea - Multiline Text Input Component
|
|
47
|
+
* Consistent with AtomicInput but optimized for multiline usage.
|
|
48
|
+
*/
|
|
49
|
+
export const AtomicTextArea = forwardRef<TextInput, AtomicTextAreaProps>(({
|
|
30
50
|
label,
|
|
31
51
|
value,
|
|
32
52
|
onChangeText,
|
|
@@ -39,8 +59,13 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
39
59
|
minHeight,
|
|
40
60
|
disabled = false,
|
|
41
61
|
style,
|
|
62
|
+
inputStyle,
|
|
63
|
+
autoFocus,
|
|
64
|
+
returnKeyType,
|
|
65
|
+
onSubmitEditing,
|
|
66
|
+
blurOnSubmit,
|
|
42
67
|
testID,
|
|
43
|
-
}) => {
|
|
68
|
+
}, ref) => {
|
|
44
69
|
const lineCount = numberOfLines ?? rows;
|
|
45
70
|
const calculatedMinHeight = minHeight ?? lineCount * 24;
|
|
46
71
|
const tokens = useAppDesignTokens();
|
|
@@ -51,20 +76,26 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
51
76
|
{label && (
|
|
52
77
|
<AtomicText
|
|
53
78
|
type="labelMedium"
|
|
54
|
-
|
|
79
|
+
color={hasError ? 'error' : 'secondary'}
|
|
80
|
+
style={styles.label}
|
|
55
81
|
>
|
|
56
82
|
{label}
|
|
57
83
|
</AtomicText>
|
|
58
84
|
)}
|
|
59
85
|
<TextInput
|
|
86
|
+
ref={ref}
|
|
60
87
|
value={value}
|
|
61
88
|
onChangeText={onChangeText}
|
|
62
89
|
placeholder={placeholder}
|
|
63
|
-
placeholderTextColor={tokens.colors.
|
|
90
|
+
placeholderTextColor={tokens.colors.textSecondary}
|
|
64
91
|
maxLength={maxLength}
|
|
65
92
|
numberOfLines={lineCount}
|
|
66
93
|
multiline
|
|
67
94
|
editable={!disabled}
|
|
95
|
+
autoFocus={autoFocus}
|
|
96
|
+
returnKeyType={returnKeyType}
|
|
97
|
+
onSubmitEditing={onSubmitEditing}
|
|
98
|
+
blurOnSubmit={blurOnSubmit}
|
|
68
99
|
textAlignVertical="top"
|
|
69
100
|
style={[
|
|
70
101
|
styles.input,
|
|
@@ -73,39 +104,57 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
73
104
|
borderColor: hasError ? tokens.colors.error : tokens.colors.border,
|
|
74
105
|
color: tokens.colors.textPrimary,
|
|
75
106
|
minHeight: calculatedMinHeight,
|
|
107
|
+
padding: tokens.spacing.md,
|
|
108
|
+
borderRadius: tokens.borderRadius.md,
|
|
109
|
+
fontSize: 16,
|
|
76
110
|
},
|
|
111
|
+
inputStyle,
|
|
77
112
|
disabled && { opacity: 0.5 },
|
|
78
113
|
]}
|
|
79
114
|
/>
|
|
80
115
|
{(helperText || errorText) && (
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
<View style={styles.helperRow}>
|
|
117
|
+
<AtomicText
|
|
118
|
+
type="bodySmall"
|
|
119
|
+
color={hasError ? 'error' : 'secondary'}
|
|
120
|
+
style={styles.helperText}
|
|
121
|
+
>
|
|
122
|
+
{errorText || helperText}
|
|
123
|
+
</AtomicText>
|
|
124
|
+
{maxLength && value !== undefined && (
|
|
125
|
+
<AtomicText
|
|
126
|
+
type="labelSmall"
|
|
127
|
+
color="secondary"
|
|
128
|
+
style={styles.characterCount}
|
|
129
|
+
>
|
|
130
|
+
{value.length}/{maxLength}
|
|
131
|
+
</AtomicText>
|
|
132
|
+
)}
|
|
133
|
+
</View>
|
|
90
134
|
)}
|
|
91
135
|
</View>
|
|
92
136
|
);
|
|
93
|
-
};
|
|
137
|
+
});
|
|
94
138
|
|
|
95
139
|
const styles = StyleSheet.create({
|
|
96
140
|
container: {
|
|
97
|
-
|
|
141
|
+
width: '100%',
|
|
98
142
|
},
|
|
99
143
|
label: {
|
|
100
144
|
marginBottom: 8,
|
|
101
145
|
},
|
|
102
146
|
input: {
|
|
103
147
|
borderWidth: 1,
|
|
104
|
-
borderRadius: 12,
|
|
105
|
-
padding: 12,
|
|
106
|
-
fontSize: 16,
|
|
107
148
|
},
|
|
108
|
-
|
|
149
|
+
helperRow: {
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
justifyContent: 'space-between',
|
|
109
152
|
marginTop: 4,
|
|
110
153
|
},
|
|
154
|
+
helperText: {
|
|
155
|
+
flex: 1,
|
|
156
|
+
},
|
|
157
|
+
characterCount: {
|
|
158
|
+
marginLeft: 8,
|
|
159
|
+
},
|
|
111
160
|
});
|
package/src/atoms/index.ts
CHANGED
|
@@ -111,3 +111,6 @@ export { AtomicTouchable, type AtomicTouchableProps } from './AtomicTouchable';
|
|
|
111
111
|
|
|
112
112
|
// StatusBar
|
|
113
113
|
export { AtomicStatusBar, type AtomicStatusBarProps } from './status-bar';
|
|
114
|
+
|
|
115
|
+
// Keyboard Avoiding
|
|
116
|
+
export { AtomicKeyboardAvoidingView, type AtomicKeyboardAvoidingViewProps } from './AtomicKeyboardAvoidingView';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @layer infrastructure/services
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import { storageRepository, unwrap } from '@umituz/react-native-storage';
|
|
13
13
|
import { DeviceIdService } from './DeviceIdService';
|
|
14
14
|
|
|
15
15
|
const STORAGE_KEY = '@device/persistent_id';
|
|
@@ -64,7 +64,8 @@ export class PersistentDeviceIdService {
|
|
|
64
64
|
*/
|
|
65
65
|
private static async initializeDeviceId(): Promise<string> {
|
|
66
66
|
try {
|
|
67
|
-
const
|
|
67
|
+
const result = await storageRepository.getString(STORAGE_KEY, '');
|
|
68
|
+
const storedId = unwrap(result, '');
|
|
68
69
|
|
|
69
70
|
if (storedId) {
|
|
70
71
|
cachedDeviceId = storedId;
|
|
@@ -72,7 +73,7 @@ export class PersistentDeviceIdService {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
const newId = await this.createNewDeviceId();
|
|
75
|
-
await
|
|
76
|
+
await storageRepository.setString(STORAGE_KEY, newId);
|
|
76
77
|
cachedDeviceId = newId;
|
|
77
78
|
|
|
78
79
|
return newId;
|
|
@@ -101,8 +102,7 @@ export class PersistentDeviceIdService {
|
|
|
101
102
|
*/
|
|
102
103
|
static async hasStoredId(): Promise<boolean> {
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
-
return storedId !== null;
|
|
105
|
+
return await storageRepository.hasItem(STORAGE_KEY);
|
|
106
106
|
} catch {
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
@@ -114,7 +114,7 @@ export class PersistentDeviceIdService {
|
|
|
114
114
|
*/
|
|
115
115
|
static async clearStoredId(): Promise<void> {
|
|
116
116
|
try {
|
|
117
|
-
await
|
|
117
|
+
await storageRepository.removeItem(STORAGE_KEY);
|
|
118
118
|
cachedDeviceId = null;
|
|
119
119
|
initializationPromise = null;
|
|
120
120
|
} catch {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useMemo } from 'react';
|
|
9
|
-
import { View, ScrollView,
|
|
9
|
+
import { View, ScrollView, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
|
|
10
10
|
import { useAppDesignTokens } from '../../theme';
|
|
11
11
|
import { useResponsive } from '../../responsive';
|
|
12
|
+
import { AtomicKeyboardAvoidingView } from '../../atoms';
|
|
12
13
|
|
|
13
14
|
export interface FormLayoutProps {
|
|
14
15
|
/** Form fields and content */
|
|
@@ -103,9 +104,9 @@ export const FormLayout: React.FC<FormLayoutProps> = ({
|
|
|
103
104
|
const mainContent = disableKeyboardAvoid ? (
|
|
104
105
|
scrollableContent
|
|
105
106
|
) : (
|
|
106
|
-
<
|
|
107
|
+
<AtomicKeyboardAvoidingView style={styles.container}>
|
|
107
108
|
{scrollableContent}
|
|
108
|
-
</
|
|
109
|
+
</AtomicKeyboardAvoidingView>
|
|
109
110
|
);
|
|
110
111
|
|
|
111
112
|
return (
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import React, { useMemo } from 'react';
|
|
27
|
-
import { View, ScrollView, StyleSheet,
|
|
27
|
+
import { View, ScrollView, StyleSheet, type ViewStyle, type RefreshControlProps } from 'react-native';
|
|
28
28
|
import { SafeAreaView, useSafeAreaInsets, type Edge } from '../../safe-area';
|
|
29
29
|
import { useAppDesignTokens } from '../../theme';
|
|
30
30
|
import { getScreenLayoutConfig } from '../../responsive/responsiveLayout';
|
|
31
|
+
import { AtomicKeyboardAvoidingView } from '../../atoms';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* NOTE: This component now works in conjunction with the SafeAreaProvider
|
|
@@ -206,12 +207,11 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
206
207
|
const ContentWrapper: React.FC<{ children: React.ReactNode }> = ({ children: wrapperChildren }) => {
|
|
207
208
|
if (keyboardAvoiding) {
|
|
208
209
|
return (
|
|
209
|
-
<
|
|
210
|
+
<AtomicKeyboardAvoidingView
|
|
210
211
|
style={styles.keyboardAvoidingView}
|
|
211
|
-
behavior="padding"
|
|
212
212
|
>
|
|
213
213
|
{wrapperChildren}
|
|
214
|
-
</
|
|
214
|
+
</AtomicKeyboardAvoidingView>
|
|
215
215
|
);
|
|
216
216
|
}
|
|
217
217
|
return <>{wrapperChildren}</>;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { create } from 'zustand';
|
|
15
15
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
16
|
-
import
|
|
16
|
+
import { storageRepository, unwrap, storageService } from '@umituz/react-native-storage';
|
|
17
17
|
import type { CalendarEvent, CreateCalendarEventRequest, UpdateCalendarEventRequest } from '../../domain/entities/CalendarEvent.entity';
|
|
18
18
|
import { CalendarService } from '../services/CalendarService';
|
|
19
19
|
|
|
@@ -101,16 +101,17 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
101
101
|
loadEvents: async () => {
|
|
102
102
|
set({ isLoading: true, error: null });
|
|
103
103
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const result = await storageRepository.getItem<CalendarEvent[]>(STORAGE_KEY, []);
|
|
105
|
+
const events = unwrap(result, []);
|
|
106
|
+
|
|
107
|
+
if (events && events.length > 0) {
|
|
107
108
|
// Restore Date objects
|
|
108
|
-
const
|
|
109
|
+
const hydratedEvents = events.map((event) => ({
|
|
109
110
|
...event,
|
|
110
111
|
createdAt: new Date(event.createdAt),
|
|
111
112
|
updatedAt: new Date(event.updatedAt),
|
|
112
113
|
}));
|
|
113
|
-
set({ events, isLoading: false });
|
|
114
|
+
set({ events: hydratedEvents, isLoading: false });
|
|
114
115
|
} else {
|
|
115
116
|
set({ isLoading: false });
|
|
116
117
|
}
|
|
@@ -137,7 +138,7 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
137
138
|
};
|
|
138
139
|
|
|
139
140
|
const events = [...get().events, newEvent];
|
|
140
|
-
await
|
|
141
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
141
142
|
set({ events, isLoading: false });
|
|
142
143
|
} catch {
|
|
143
144
|
set({
|
|
@@ -164,7 +165,7 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
164
165
|
return event;
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
await
|
|
168
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
168
169
|
set({ events, isLoading: false });
|
|
169
170
|
} catch {
|
|
170
171
|
set({
|
|
@@ -181,7 +182,7 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
181
182
|
set({ isLoading: true, error: null });
|
|
182
183
|
try {
|
|
183
184
|
const events = get().events.filter((event) => event.id !== id);
|
|
184
|
-
await
|
|
185
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
185
186
|
set({ events, isLoading: false });
|
|
186
187
|
} catch {
|
|
187
188
|
set({
|
|
@@ -294,7 +295,7 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
294
295
|
clearAllEvents: async () => {
|
|
295
296
|
set({ isLoading: true, error: null });
|
|
296
297
|
try {
|
|
297
|
-
await
|
|
298
|
+
await storageRepository.removeItem(STORAGE_KEY);
|
|
298
299
|
set({ events: [], isLoading: false });
|
|
299
300
|
} catch {
|
|
300
301
|
set({
|
|
@@ -307,7 +308,7 @@ export const useCalendarStore = create<CalendarState & { actions: CalendarAction
|
|
|
307
308
|
}),
|
|
308
309
|
{
|
|
309
310
|
name: 'calendar-storage',
|
|
310
|
-
storage: createJSONStorage(() =>
|
|
311
|
+
storage: createJSONStorage(() => storageService),
|
|
311
312
|
partialize: (state) => ({ events: state.events }),
|
|
312
313
|
}
|
|
313
314
|
)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Apps should use this for theme persistence.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import { storageRepository, unwrap } from '@umituz/react-native-storage';
|
|
10
10
|
import type { ThemeMode } from '../../core/ColorPalette';
|
|
11
11
|
import { DESIGN_CONSTANTS } from '../../core/constants/DesignConstants';
|
|
12
12
|
|
|
@@ -18,7 +18,9 @@ export class ThemeStorage {
|
|
|
18
18
|
*/
|
|
19
19
|
static async getThemeMode(): Promise<ThemeMode | null> {
|
|
20
20
|
try {
|
|
21
|
-
const
|
|
21
|
+
const result = await storageRepository.getString(STORAGE_KEY, '');
|
|
22
|
+
const value = unwrap(result, '');
|
|
23
|
+
|
|
22
24
|
if (!value) {
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
@@ -45,7 +47,7 @@ export class ThemeStorage {
|
|
|
45
47
|
throw new Error(`Invalid theme mode: ${mode}`);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
await
|
|
50
|
+
await storageRepository.setString(STORAGE_KEY, mode);
|
|
49
51
|
} catch (error) {
|
|
50
52
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
51
53
|
// Re-throw validation errors but swallow storage errors to prevent app crashes
|
|
@@ -60,7 +62,7 @@ export class ThemeStorage {
|
|
|
60
62
|
*/
|
|
61
63
|
static async clearThemeMode(): Promise<void> {
|
|
62
64
|
try {
|
|
63
|
-
await
|
|
65
|
+
await storageRepository.removeItem(STORAGE_KEY);
|
|
64
66
|
} catch {
|
|
65
67
|
// Don't throw - clearing storage is not critical
|
|
66
68
|
}
|